mirror of
https://github.com/anomalyco/opencode.git
synced 2026-08-03 08:46:15 -04:00
Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 04c23b8846 | |||
| 8f3465c951 | |||
| 466b75b19d | |||
| 833dd2ed7f | |||
| bbe985b4d0 | |||
| 5b1321a8ca | |||
| 52c98a4eeb | |||
| b6f85c2250 |
@@ -124,7 +124,7 @@
|
|||||||
"opencode2": "./bin/opencode2.cjs",
|
"opencode2": "./bin/opencode2.cjs",
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@agentclientprotocol/sdk": "0.21.0",
|
"@agentclientprotocol/sdk": "1.2.1",
|
||||||
"@effect/platform-node": "catalog:",
|
"@effect/platform-node": "catalog:",
|
||||||
"@opencode-ai/client": "workspace:*",
|
"@opencode-ai/client": "workspace:*",
|
||||||
"@opencode-ai/plugin": "workspace:*",
|
"@opencode-ai/plugin": "workspace:*",
|
||||||
@@ -1173,7 +1173,7 @@
|
|||||||
|
|
||||||
"@adobe/css-tools": ["@adobe/css-tools@4.5.0", "", {}, "sha512-6OzddxPio9UiWTCemp4N8cYLV2ZN1ncRnV1cVGtve7dhPOtRkleRyx32GQCYSwDYgaHU3USMm84tNsvKzRCa1Q=="],
|
"@adobe/css-tools": ["@adobe/css-tools@4.5.0", "", {}, "sha512-6OzddxPio9UiWTCemp4N8cYLV2ZN1ncRnV1cVGtve7dhPOtRkleRyx32GQCYSwDYgaHU3USMm84tNsvKzRCa1Q=="],
|
||||||
|
|
||||||
"@agentclientprotocol/sdk": ["@agentclientprotocol/sdk@0.21.0", "", { "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" } }, "sha512-ONj+Q8qOdNQp5XbH5jnMwzT9IKZJsSN0p0lkceS4GtUtNOPVLpNzSS8gqQdGMKfBvA0ESbkL8BTaSN1Rc9miEw=="],
|
"@agentclientprotocol/sdk": ["@agentclientprotocol/sdk@1.2.1", "", { "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" } }, "sha512-jwYUdOQR7tc+Zfch53VL4JJyUNK/46q03uUTYb+PjECsmnNl94XFXOfYLJ8RBpMNidXd1rpOAVgb0vqD98xImA=="],
|
||||||
|
|
||||||
"@ai-sdk/alibaba": ["@ai-sdk/alibaba@1.0.17", "", { "dependencies": { "@ai-sdk/openai-compatible": "2.0.41", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ZbE+U5bWz2JBc5DERLowx5+TKbjGBE93LqKZAWvuEn7HOSQMraxFMZuc0ST335QZJAyfBOzh7m1mPQ+y7EaaoA=="],
|
"@ai-sdk/alibaba": ["@ai-sdk/alibaba@1.0.17", "", { "dependencies": { "@ai-sdk/openai-compatible": "2.0.41", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ZbE+U5bWz2JBc5DERLowx5+TKbjGBE93LqKZAWvuEn7HOSQMraxFMZuc0ST335QZJAyfBOzh7m1mPQ+y7EaaoA=="],
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
"typecheck": "tsgo --noEmit"
|
"typecheck": "tsgo --noEmit"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@agentclientprotocol/sdk": "0.21.0",
|
"@agentclientprotocol/sdk": "1.2.1",
|
||||||
"@effect/platform-node": "catalog:",
|
"@effect/platform-node": "catalog:",
|
||||||
"@opencode-ai/client": "workspace:*",
|
"@opencode-ai/client": "workspace:*",
|
||||||
"@opencode-ai/plugin": "workspace:*",
|
"@opencode-ai/plugin": "workspace:*",
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import {
|
|||||||
type AuthenticateRequest,
|
type AuthenticateRequest,
|
||||||
type CancelNotification,
|
type CancelNotification,
|
||||||
type CloseSessionRequest,
|
type CloseSessionRequest,
|
||||||
|
type DeleteSessionRequest,
|
||||||
type ForkSessionRequest,
|
type ForkSessionRequest,
|
||||||
type InitializeRequest,
|
type InitializeRequest,
|
||||||
type ListSessionsRequest,
|
type ListSessionsRequest,
|
||||||
@@ -13,7 +14,6 @@ import {
|
|||||||
type PromptRequest,
|
type PromptRequest,
|
||||||
type ResumeSessionRequest,
|
type ResumeSessionRequest,
|
||||||
type SetSessionConfigOptionRequest,
|
type SetSessionConfigOptionRequest,
|
||||||
type SetSessionModelRequest,
|
|
||||||
type SetSessionModeRequest,
|
type SetSessionModeRequest,
|
||||||
} from "@agentclientprotocol/sdk"
|
} from "@agentclientprotocol/sdk"
|
||||||
import type { OpenCodeClient } from "@opencode-ai/client/promise"
|
import type { OpenCodeClient } from "@opencode-ai/client/promise"
|
||||||
@@ -28,12 +28,12 @@ export function create(client: OpenCodeClient, connection: AgentSideConnection)
|
|||||||
newSession: (params: NewSessionRequest) => run(service.newSession(params)),
|
newSession: (params: NewSessionRequest) => run(service.newSession(params)),
|
||||||
loadSession: (params: LoadSessionRequest) => run(service.loadSession(params)),
|
loadSession: (params: LoadSessionRequest) => run(service.loadSession(params)),
|
||||||
listSessions: (params: ListSessionsRequest) => run(service.listSessions(params)),
|
listSessions: (params: ListSessionsRequest) => run(service.listSessions(params)),
|
||||||
|
deleteSession: (params: DeleteSessionRequest) => run(service.deleteSession(params)),
|
||||||
resumeSession: (params: ResumeSessionRequest) => run(service.resumeSession(params)),
|
resumeSession: (params: ResumeSessionRequest) => run(service.resumeSession(params)),
|
||||||
closeSession: (params: CloseSessionRequest) => run(service.closeSession(params)),
|
closeSession: (params: CloseSessionRequest) => run(service.closeSession(params)),
|
||||||
unstable_forkSession: (params: ForkSessionRequest) => run(service.forkSession(params)),
|
unstable_forkSession: (params: ForkSessionRequest) => run(service.forkSession(params)),
|
||||||
setSessionConfigOption: (params: SetSessionConfigOptionRequest) => run(service.setSessionConfigOption(params)),
|
setSessionConfigOption: (params: SetSessionConfigOptionRequest) => run(service.setSessionConfigOption(params)),
|
||||||
setSessionMode: (params: SetSessionModeRequest) => run(service.setSessionMode(params)),
|
setSessionMode: (params: SetSessionModeRequest) => run(service.setSessionMode(params)),
|
||||||
unstable_setSessionModel: (params: SetSessionModelRequest) => run(service.setSessionModel(params)),
|
|
||||||
prompt: (params: PromptRequest) => run(service.prompt(params)),
|
prompt: (params: PromptRequest) => run(service.prompt(params)),
|
||||||
cancel: (params: CancelNotification) => run(service.cancel(params)),
|
cancel: (params: CancelNotification) => run(service.cancel(params)),
|
||||||
} satisfies Agent
|
} satisfies Agent
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ export async function streamTurn(input: {
|
|||||||
readonly sessionID: string
|
readonly sessionID: string
|
||||||
readonly cwd: string
|
readonly cwd: string
|
||||||
readonly start: TurnStart
|
readonly start: TurnStart
|
||||||
readonly userMessageID?: string | null
|
readonly writeTextFile: boolean
|
||||||
readonly submit: (signal: AbortSignal) => Promise<unknown>
|
readonly submit: (signal: AbortSignal) => Promise<unknown>
|
||||||
readonly control: TurnControl
|
readonly control: TurnControl
|
||||||
}): Promise<PromptResponse> {
|
}): Promise<PromptResponse> {
|
||||||
@@ -170,6 +170,7 @@ export async function streamTurn(input: {
|
|||||||
tools.delete(event.data.callID)
|
tools.delete(event.data.callID)
|
||||||
await syncEditedFiles({
|
await syncEditedFiles({
|
||||||
connection: input.connection,
|
connection: input.connection,
|
||||||
|
writeTextFile: input.writeTextFile,
|
||||||
sessionID: input.sessionID,
|
sessionID: input.sessionID,
|
||||||
cwd: input.cwd,
|
cwd: input.cwd,
|
||||||
toolName: current.name,
|
toolName: current.name,
|
||||||
@@ -231,7 +232,7 @@ export async function streamTurn(input: {
|
|||||||
if (!started) {
|
if (!started) {
|
||||||
streamController.abort()
|
streamController.abort()
|
||||||
await completed.catch(() => {})
|
await completed.catch(() => {})
|
||||||
return response(undefined, undefined, "interrupted", true, undefined, input.userMessageID)
|
return response(undefined, undefined, "interrupted", true, undefined)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const terminal = await completed
|
const terminal = await completed
|
||||||
@@ -246,7 +247,6 @@ export async function streamTurn(input: {
|
|||||||
terminal,
|
terminal,
|
||||||
control.cancelled,
|
control.cancelled,
|
||||||
finish,
|
finish,
|
||||||
input.userMessageID,
|
|
||||||
)
|
)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
streamController.abort()
|
streamController.abort()
|
||||||
@@ -400,7 +400,6 @@ function response(
|
|||||||
terminal: "succeeded" | "failed" | "interrupted",
|
terminal: "succeeded" | "failed" | "interrupted",
|
||||||
cancelled: boolean,
|
cancelled: boolean,
|
||||||
finish: SessionMessageAssistant["finish"],
|
finish: SessionMessageAssistant["finish"],
|
||||||
messageID: string | null | undefined,
|
|
||||||
): PromptResponse {
|
): PromptResponse {
|
||||||
const error = assistant?.error ?? executionError
|
const error = assistant?.error ?? executionError
|
||||||
if (error?.type === "provider.auth") throw new ACPError.AuthRequiredError()
|
if (error?.type === "provider.auth") throw new ACPError.AuthRequiredError()
|
||||||
@@ -423,7 +422,7 @@ function response(
|
|||||||
}
|
}
|
||||||
: undefined
|
: undefined
|
||||||
const stopReason = resolveStopReason({ terminal, cancelled, finish, error: error?.type })
|
const stopReason = resolveStopReason({ terminal, cancelled, finish, error: error?.type })
|
||||||
return { stopReason, ...(usage ? { usage } : {}), ...(messageID ? { userMessageId: messageID } : {}), _meta: {} }
|
return { stopReason, ...(usage ? { usage } : {}), _meta: {} }
|
||||||
}
|
}
|
||||||
|
|
||||||
function resolveStopReason(input: {
|
function resolveStopReason(input: {
|
||||||
|
|||||||
@@ -53,13 +53,14 @@ export async function replyPermission(input: {
|
|||||||
|
|
||||||
export async function syncEditedFiles(input: {
|
export async function syncEditedFiles(input: {
|
||||||
readonly connection: Partial<Pick<AgentSideConnection, "writeTextFile">>
|
readonly connection: Partial<Pick<AgentSideConnection, "writeTextFile">>
|
||||||
|
readonly writeTextFile: boolean
|
||||||
readonly sessionID: string
|
readonly sessionID: string
|
||||||
readonly cwd: string
|
readonly cwd: string
|
||||||
readonly toolName: string
|
readonly toolName: string
|
||||||
readonly toolInput: ToolInput
|
readonly toolInput: ToolInput
|
||||||
readonly structured: Readonly<Record<string, unknown>>
|
readonly structured: Readonly<Record<string, unknown>>
|
||||||
}) {
|
}) {
|
||||||
if (!input.connection.writeTextFile || toToolKind(input.toolName) !== "edit") return
|
if (!input.writeTextFile || !input.connection.writeTextFile || toToolKind(input.toolName) !== "edit") return
|
||||||
const files = Array.isArray(input.structured.files)
|
const files = Array.isArray(input.structured.files)
|
||||||
? input.structured.files.flatMap((file): string[] => {
|
? input.structured.files.flatMap((file): string[] => {
|
||||||
if (!file || typeof file !== "object") return []
|
if (!file || typeof file !== "object") return []
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ import type {
|
|||||||
CancelNotification,
|
CancelNotification,
|
||||||
CloseSessionRequest,
|
CloseSessionRequest,
|
||||||
CloseSessionResponse,
|
CloseSessionResponse,
|
||||||
|
DeleteSessionRequest,
|
||||||
|
DeleteSessionResponse,
|
||||||
ForkSessionRequest,
|
ForkSessionRequest,
|
||||||
ForkSessionResponse,
|
ForkSessionResponse,
|
||||||
InitializeRequest,
|
InitializeRequest,
|
||||||
@@ -33,8 +35,6 @@ import type {
|
|||||||
ResumeSessionResponse,
|
ResumeSessionResponse,
|
||||||
SetSessionConfigOptionRequest,
|
SetSessionConfigOptionRequest,
|
||||||
SetSessionConfigOptionResponse,
|
SetSessionConfigOptionResponse,
|
||||||
SetSessionModelRequest,
|
|
||||||
SetSessionModelResponse,
|
|
||||||
SetSessionModeRequest,
|
SetSessionModeRequest,
|
||||||
SetSessionModeResponse,
|
SetSessionModeResponse,
|
||||||
} from "@agentclientprotocol/sdk"
|
} from "@agentclientprotocol/sdk"
|
||||||
@@ -47,7 +47,8 @@ import { ACPError } from "./error"
|
|||||||
|
|
||||||
export const AuthMethodID = "opencode-login"
|
export const AuthMethodID = "opencode-login"
|
||||||
|
|
||||||
type Connection = Pick<AgentSideConnection, "sessionUpdate" | "requestPermission">
|
type Connection = Pick<AgentSideConnection, "sessionUpdate" | "requestPermission"> &
|
||||||
|
Partial<Pick<AgentSideConnection, "writeTextFile">>
|
||||||
|
|
||||||
type Catalog = {
|
type Catalog = {
|
||||||
readonly providers: ConfigOptionProvider[]
|
readonly providers: ConfigOptionProvider[]
|
||||||
@@ -83,12 +84,12 @@ export interface Interface {
|
|||||||
newSession(input: NewSessionRequest): Promise<NewSessionResponse>
|
newSession(input: NewSessionRequest): Promise<NewSessionResponse>
|
||||||
loadSession(input: LoadSessionRequest): Promise<LoadSessionResponse>
|
loadSession(input: LoadSessionRequest): Promise<LoadSessionResponse>
|
||||||
listSessions(input: ListSessionsRequest): Promise<ListSessionsResponse>
|
listSessions(input: ListSessionsRequest): Promise<ListSessionsResponse>
|
||||||
|
deleteSession(input: DeleteSessionRequest): Promise<DeleteSessionResponse>
|
||||||
resumeSession(input: ResumeSessionRequest): Promise<ResumeSessionResponse>
|
resumeSession(input: ResumeSessionRequest): Promise<ResumeSessionResponse>
|
||||||
closeSession(input: CloseSessionRequest): Promise<CloseSessionResponse>
|
closeSession(input: CloseSessionRequest): Promise<CloseSessionResponse>
|
||||||
forkSession(input: ForkSessionRequest): Promise<ForkSessionResponse>
|
forkSession(input: ForkSessionRequest): Promise<ForkSessionResponse>
|
||||||
setSessionConfigOption(input: SetSessionConfigOptionRequest): Promise<SetSessionConfigOptionResponse>
|
setSessionConfigOption(input: SetSessionConfigOptionRequest): Promise<SetSessionConfigOptionResponse>
|
||||||
setSessionMode(input: SetSessionModeRequest): Promise<SetSessionModeResponse>
|
setSessionMode(input: SetSessionModeRequest): Promise<SetSessionModeResponse>
|
||||||
setSessionModel(input: SetSessionModelRequest): Promise<SetSessionModelResponse>
|
|
||||||
prompt(input: PromptRequest): Promise<PromptResponse>
|
prompt(input: PromptRequest): Promise<PromptResponse>
|
||||||
cancel(input: CancelNotification): Promise<void>
|
cancel(input: CancelNotification): Promise<void>
|
||||||
}
|
}
|
||||||
@@ -98,6 +99,7 @@ export function make(input: { readonly client: OpenCodeClient; readonly connecti
|
|||||||
const catalogs = new Map<string, Promise<Catalog>>()
|
const catalogs = new Map<string, Promise<Catalog>>()
|
||||||
const registeredMcp = new Map<string, Set<string>>()
|
const registeredMcp = new Map<string, Set<string>>()
|
||||||
const active = new Map<string, TurnControl>()
|
const active = new Map<string, TurnControl>()
|
||||||
|
const capabilities = { writeTextFile: false }
|
||||||
|
|
||||||
const catalog = (cwd: string) => {
|
const catalog = (cwd: string) => {
|
||||||
const cached = catalogs.get(cwd)
|
const cached = catalogs.get(cwd)
|
||||||
@@ -157,6 +159,7 @@ export function make(input: { readonly client: OpenCodeClient; readonly connecti
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
initialize: async (params) => {
|
initialize: async (params) => {
|
||||||
|
capabilities.writeTextFile = params.clientCapabilities?.fs?.writeTextFile === true
|
||||||
const authMethod: AuthMethod = {
|
const authMethod: AuthMethod = {
|
||||||
description: "Run `opencode auth login` in the terminal",
|
description: "Run `opencode auth login` in the terminal",
|
||||||
name: "Login with opencode",
|
name: "Login with opencode",
|
||||||
@@ -173,7 +176,7 @@ export function make(input: { readonly client: OpenCodeClient; readonly connecti
|
|||||||
loadSession: true,
|
loadSession: true,
|
||||||
mcpCapabilities: { http: true, sse: false },
|
mcpCapabilities: { http: true, sse: false },
|
||||||
promptCapabilities: { embeddedContext: true, image: true },
|
promptCapabilities: { embeddedContext: true, image: true },
|
||||||
sessionCapabilities: { close: {}, fork: {}, list: {}, resume: {} },
|
sessionCapabilities: { close: {}, delete: {}, fork: {}, list: {}, resume: {} },
|
||||||
},
|
},
|
||||||
authMethods: [authMethod],
|
authMethods: [authMethod],
|
||||||
agentInfo: { name: "OpenCode", version: OPENCODE_VERSION },
|
agentInfo: { name: "OpenCode", version: OPENCODE_VERSION },
|
||||||
@@ -216,6 +219,14 @@ export function make(input: { readonly client: OpenCodeClient; readonly connecti
|
|||||||
...(page.cursor.next ? { nextCursor: page.cursor.next } : {}),
|
...(page.cursor.next ? { nextCursor: page.cursor.next } : {}),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
deleteSession: async (params) => {
|
||||||
|
await input.client.session.remove({ sessionID: params.sessionId }).catch((error) => {
|
||||||
|
if (!isSessionNotFoundError(error)) throw error
|
||||||
|
})
|
||||||
|
sessions.delete(params.sessionId)
|
||||||
|
registeredMcp.delete(params.sessionId)
|
||||||
|
return {}
|
||||||
|
},
|
||||||
resumeSession: async (params) => {
|
resumeSession: async (params) => {
|
||||||
const session = await getSession(input.client, params.sessionId)
|
const session = await getSession(input.client, params.sessionId)
|
||||||
const state = await attach(session, session.location.directory, params.mcpServers ?? [])
|
const state = await attach(session, session.location.directory, params.mcpServers ?? [])
|
||||||
@@ -270,13 +281,6 @@ export function make(input: { readonly client: OpenCodeClient; readonly connecti
|
|||||||
await selectMode(input.client, await requireSession(params.sessionId), params.modeId)
|
await selectMode(input.client, await requireSession(params.sessionId), params.modeId)
|
||||||
return {}
|
return {}
|
||||||
},
|
},
|
||||||
setSessionModel: async (params) => {
|
|
||||||
const state = await requireSession(params.sessionId)
|
|
||||||
const selected = requireModel(state.catalog, params.modelId)
|
|
||||||
state.model = selected
|
|
||||||
await input.client.session.switchModel({ sessionID: state.id, model: selected })
|
|
||||||
return {}
|
|
||||||
},
|
|
||||||
prompt: async (params) => {
|
prompt: async (params) => {
|
||||||
const state = await requireSession(params.sessionId)
|
const state = await requireSession(params.sessionId)
|
||||||
if (active.has(state.id)) {
|
if (active.has(state.id)) {
|
||||||
@@ -295,7 +299,7 @@ export function make(input: { readonly client: OpenCodeClient; readonly connecti
|
|||||||
sessionID: state.id,
|
sessionID: state.id,
|
||||||
cwd: state.cwd,
|
cwd: state.cwd,
|
||||||
start: prepared.start,
|
start: prepared.start,
|
||||||
userMessageID: params.messageId,
|
writeTextFile: capabilities.writeTextFile,
|
||||||
control,
|
control,
|
||||||
submit: (signal) => submitPrompt(input.client, state, prepared, signal),
|
submit: (signal) => submitPrompt(input.client, state, prepared, signal),
|
||||||
}).finally(() => {
|
}).finally(() => {
|
||||||
@@ -479,6 +483,7 @@ async function registerMcpServers(
|
|||||||
|
|
||||||
function mcpConfig(server: McpServer) {
|
function mcpConfig(server: McpServer) {
|
||||||
if ("type" in server) {
|
if ("type" in server) {
|
||||||
|
if (server.type === "acp") throw new Error("MCP-over-ACP is not supported")
|
||||||
return {
|
return {
|
||||||
type: "remote" as const,
|
type: "remote" as const,
|
||||||
url: server.url,
|
url: server.url,
|
||||||
|
|||||||
@@ -140,11 +140,11 @@ export const Commands = Spec.make(typeof OPENCODE_CLI_NAME === "string" ? OPENCO
|
|||||||
Flag.withDefault(false),
|
Flag.withDefault(false),
|
||||||
),
|
),
|
||||||
replay: Flag.boolean("replay").pipe(
|
replay: Flag.boolean("replay").pipe(
|
||||||
Flag.withDescription("Replay session history on resume and after resize"),
|
Flag.withDescription("Restore session history on resume and resize (disable with --no-replay)"),
|
||||||
Flag.withDefault(true),
|
Flag.optional,
|
||||||
),
|
),
|
||||||
replayLimit: Flag.integer("replay-limit").pipe(
|
replayLimit: Flag.integer("replay-limit").pipe(
|
||||||
Flag.withDescription("Cap visible replay to the newest N messages"),
|
Flag.withDescription("Limit replay to the newest N messages (default: 200)"),
|
||||||
Flag.optional,
|
Flag.optional,
|
||||||
),
|
),
|
||||||
model: Flag.string("model").pipe(
|
model: Flag.string("model").pipe(
|
||||||
|
|||||||
@@ -28,8 +28,8 @@ export default Runtime.handler(Commands.commands.mini, (input) =>
|
|||||||
model: Option.getOrUndefined(input.model),
|
model: Option.getOrUndefined(input.model),
|
||||||
agent: Option.getOrUndefined(input.agent),
|
agent: Option.getOrUndefined(input.agent),
|
||||||
prompt: Option.getOrUndefined(input.prompt),
|
prompt: Option.getOrUndefined(input.prompt),
|
||||||
replay: input.replay,
|
replay: Option.getOrUndefined(input.replay) ?? resolved.mini?.replay ?? true,
|
||||||
replayLimit: Option.getOrUndefined(input.replayLimit),
|
replayLimit: Option.getOrUndefined(input.replayLimit) ?? resolved.mini?.replay_limit,
|
||||||
demo: input.demo,
|
demo: input.demo,
|
||||||
tuiConfig: resolved,
|
tuiConfig: resolved,
|
||||||
config: {
|
config: {
|
||||||
|
|||||||
@@ -439,6 +439,7 @@ describe("acp event behavior", () => {
|
|||||||
sessionID: "ses_cancel",
|
sessionID: "ses_cancel",
|
||||||
cwd: "/workspace",
|
cwd: "/workspace",
|
||||||
start: { type: "input", id: "input_cancel" },
|
start: { type: "input", id: "input_cancel" },
|
||||||
|
writeTextFile: false,
|
||||||
control,
|
control,
|
||||||
submit: async (signal) => {
|
submit: async (signal) => {
|
||||||
await fixture.client.session.prompt(
|
await fixture.client.session.prompt(
|
||||||
@@ -481,6 +482,7 @@ describe("acp event behavior", () => {
|
|||||||
sessionID: "ses_cancel_admission",
|
sessionID: "ses_cancel_admission",
|
||||||
cwd: "/workspace",
|
cwd: "/workspace",
|
||||||
start: { type: "input", id: "input_cancel_admission" },
|
start: { type: "input", id: "input_cancel_admission" },
|
||||||
|
writeTextFile: false,
|
||||||
control,
|
control,
|
||||||
submit: (signal) =>
|
submit: (signal) =>
|
||||||
fixture.client.session.prompt(
|
fixture.client.session.prompt(
|
||||||
@@ -566,7 +568,7 @@ function turn(input: {
|
|||||||
sessionID: input.sessionID,
|
sessionID: input.sessionID,
|
||||||
cwd: "/workspace",
|
cwd: "/workspace",
|
||||||
start: { type: "input", id: input.inputID },
|
start: { type: "input", id: input.inputID },
|
||||||
userMessageID: `client_${input.inputID}`,
|
writeTextFile: false,
|
||||||
control: { cancelled: false, admission: new AbortController() },
|
control: { cancelled: false, admission: new AbortController() },
|
||||||
submit: (signal) =>
|
submit: (signal) =>
|
||||||
input.fixture.client.session.prompt({ sessionID: input.sessionID, id: input.inputID, text: "hello" }, { signal }),
|
input.fixture.client.session.prompt({ sessionID: input.sessionID, id: input.inputID, text: "hello" }, { signal }),
|
||||||
|
|||||||
@@ -85,7 +85,6 @@ test("acp prompt resolves after ordered turn updates", async () => {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const id = "msg_prompt"
|
const id = "msg_prompt"
|
||||||
const userMessageID = "client-message"
|
|
||||||
const response = await streamTurn({
|
const response = await streamTurn({
|
||||||
client,
|
client,
|
||||||
connection: {
|
connection: {
|
||||||
@@ -97,7 +96,7 @@ test("acp prompt resolves after ordered turn updates", async () => {
|
|||||||
sessionID: "ses_test",
|
sessionID: "ses_test",
|
||||||
cwd: "/workspace",
|
cwd: "/workspace",
|
||||||
start: { type: "input", id },
|
start: { type: "input", id },
|
||||||
userMessageID,
|
writeTextFile: false,
|
||||||
control: { cancelled: false, admission: new AbortController() },
|
control: { cancelled: false, admission: new AbortController() },
|
||||||
submit: () => client.session.prompt({ sessionID: "ses_test", id, text: "hi" }),
|
submit: () => client.session.prompt({ sessionID: "ses_test", id, text: "hi" }),
|
||||||
})
|
})
|
||||||
@@ -112,7 +111,7 @@ test("acp prompt resolves after ordered turn updates", async () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
expect(response).toMatchObject({ stopReason: "end_turn", userMessageId: userMessageID, usage: { totalTokens: 2 } })
|
expect(response).toMatchObject({ stopReason: "end_turn", usage: { totalTokens: 2 } })
|
||||||
} finally {
|
} finally {
|
||||||
events?.close()
|
events?.close()
|
||||||
await server.stop(true)
|
await server.stop(true)
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ describe("acp initialize/auth subprocess", () => {
|
|||||||
expect(initialized.agentCapabilities?.mcpCapabilities?.sse).toBe(false)
|
expect(initialized.agentCapabilities?.mcpCapabilities?.sse).toBe(false)
|
||||||
expect(initialized.agentCapabilities?.loadSession).toBe(true)
|
expect(initialized.agentCapabilities?.loadSession).toBe(true)
|
||||||
expect(initialized.agentCapabilities?.sessionCapabilities?.close).toEqual({})
|
expect(initialized.agentCapabilities?.sessionCapabilities?.close).toEqual({})
|
||||||
|
expect(initialized.agentCapabilities?.sessionCapabilities?.delete).toEqual({})
|
||||||
expect(initialized.agentCapabilities?.sessionCapabilities?.fork).toEqual({})
|
expect(initialized.agentCapabilities?.sessionCapabilities?.fork).toEqual({})
|
||||||
expect(initialized.agentCapabilities?.sessionCapabilities?.list).toEqual({})
|
expect(initialized.agentCapabilities?.sessionCapabilities?.list).toEqual({})
|
||||||
expect(initialized.agentCapabilities?.sessionCapabilities?.resume).toEqual({})
|
expect(initialized.agentCapabilities?.sessionCapabilities?.resume).toEqual({})
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import type {
|
import type {
|
||||||
CloseSessionResponse,
|
CloseSessionResponse,
|
||||||
|
DeleteSessionResponse,
|
||||||
ListSessionsResponse,
|
ListSessionsResponse,
|
||||||
LoadSessionResponse,
|
LoadSessionResponse,
|
||||||
ResumeSessionResponse,
|
ResumeSessionResponse,
|
||||||
@@ -60,6 +61,20 @@ describe("acp lifecycle subprocess", () => {
|
|||||||
expect(listed.sessions.some((item) => item.sessionId === session.sessionId)).toBe(true)
|
expect(listed.sessions.some((item) => item.sessionId === session.sessionId)).toBe(true)
|
||||||
}, 60_000)
|
}, 60_000)
|
||||||
|
|
||||||
|
test("delete capability and delete request", async () => {
|
||||||
|
await using fixture = await createAcpFixture()
|
||||||
|
const acp = fixture.spawn()
|
||||||
|
const initialized = await initialize(acp)
|
||||||
|
expect(initialized.agentCapabilities?.sessionCapabilities?.delete).toEqual({})
|
||||||
|
const session = await newSession(acp, fixture.home)
|
||||||
|
|
||||||
|
expect(
|
||||||
|
expectOk(await acp.request<DeleteSessionResponse>("session/delete", { sessionId: session.sessionId })),
|
||||||
|
).toEqual({})
|
||||||
|
const listed = expectOk(await acp.request<ListSessionsResponse>("session/list", { cwd: fixture.home }))
|
||||||
|
expect(listed.sessions.some((item) => item.sessionId === session.sessionId)).toBe(false)
|
||||||
|
}, 60_000)
|
||||||
|
|
||||||
test("resume capability advertisement", async () => {
|
test("resume capability advertisement", async () => {
|
||||||
await using fixture = await createAcpFixture()
|
await using fixture = await createAcpFixture()
|
||||||
const initialized = await initialize(fixture.spawn())
|
const initialized = await initialize(fixture.spawn())
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import fs from "node:fs/promises"
|
|||||||
import os from "node:os"
|
import os from "node:os"
|
||||||
import path from "node:path"
|
import path from "node:path"
|
||||||
import { streamTurn } from "../../src/acp/event"
|
import { streamTurn } from "../../src/acp/event"
|
||||||
|
import { syncEditedFiles } from "../../src/acp/permission"
|
||||||
import { createSseFixture, durableEvent, ephemeralEvent, withTimeout } from "./sse-fixture"
|
import { createSseFixture, durableEvent, ephemeralEvent, withTimeout } from "./sse-fixture"
|
||||||
|
|
||||||
type SessionUpdateParams = Parameters<AgentSideConnection["sessionUpdate"]>[0]
|
type SessionUpdateParams = Parameters<AgentSideConnection["sessionUpdate"]>[0]
|
||||||
@@ -12,6 +13,27 @@ type Connection = Pick<AgentSideConnection, "sessionUpdate" | "requestPermission
|
|||||||
type Fixture = ReturnType<typeof createSseFixture>
|
type Fixture = ReturnType<typeof createSseFixture>
|
||||||
|
|
||||||
describe("acp permission behavior", () => {
|
describe("acp permission behavior", () => {
|
||||||
|
test("does not sync edits when writeTextFile was not advertised", async () => {
|
||||||
|
const writes: Parameters<AgentSideConnection["writeTextFile"]>[0][] = []
|
||||||
|
|
||||||
|
await syncEditedFiles({
|
||||||
|
connection: {
|
||||||
|
writeTextFile: async (input) => {
|
||||||
|
writes.push(input)
|
||||||
|
return {}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
writeTextFile: false,
|
||||||
|
sessionID: "ses_no_write",
|
||||||
|
cwd: "/workspace",
|
||||||
|
toolName: "edit",
|
||||||
|
toolInput: { filePath: "/workspace/file.ts" },
|
||||||
|
structured: {},
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(writes).toEqual([])
|
||||||
|
})
|
||||||
|
|
||||||
test("forwards allow-once and allow-always selections to the generated client", async () => {
|
test("forwards allow-once and allow-always selections to the generated client", async () => {
|
||||||
const permissionRequests: RequestPermissionRequest[] = []
|
const permissionRequests: RequestPermissionRequest[] = []
|
||||||
const fixture = createSseFixture({
|
const fixture = createSseFixture({
|
||||||
@@ -465,6 +487,7 @@ function startTurn(fixture: Fixture, connection: Connection, sessionID: string,
|
|||||||
sessionID,
|
sessionID,
|
||||||
cwd,
|
cwd,
|
||||||
start: { type: "input", id: inputID },
|
start: { type: "input", id: inputID },
|
||||||
|
writeTextFile: true,
|
||||||
control: { cancelled: false, admission: new AbortController() },
|
control: { cancelled: false, admission: new AbortController() },
|
||||||
submit: (signal) => fixture.client.session.prompt({ sessionID, id: inputID, text: "hello" }, { signal }),
|
submit: (signal) => fixture.client.session.prompt({ sessionID, id: inputID, text: "hello" }, { signal }),
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { describe, expect, test } from "bun:test"
|
import { describe, expect, test } from "bun:test"
|
||||||
import type { McpServer, SessionConfigOption } from "@agentclientprotocol/sdk"
|
import type { McpServer, SessionConfigOption } from "@agentclientprotocol/sdk"
|
||||||
import { makeACPFixture, makeSession, secondModel, testModel } from "./service-fixture"
|
import { makeACPFixture, makeSession, secondModel } from "./service-fixture"
|
||||||
|
|
||||||
describe("acp service directory behavior", () => {
|
describe("acp service directory behavior", () => {
|
||||||
test("creates sessions from a catalog shared by concurrent callers in the same cwd", async () => {
|
test("creates sessions from a catalog shared by concurrent callers in the same cwd", async () => {
|
||||||
@@ -134,7 +134,6 @@ describe("acp service directory behavior", () => {
|
|||||||
configId: "mode",
|
configId: "mode",
|
||||||
value: "plan",
|
value: "plan",
|
||||||
})
|
})
|
||||||
await fixture.service.setSessionModel({ sessionId: session.sessionId, modelId: "test/test-model/high" })
|
|
||||||
await fixture.service.setSessionMode({ sessionId: session.sessionId, modeId: "build" })
|
await fixture.service.setSessionMode({ sessionId: session.sessionId, modeId: "build" })
|
||||||
|
|
||||||
expect(currentValue(selectedModel, "model")).toBe("test/second-model")
|
expect(currentValue(selectedModel, "model")).toBe("test/second-model")
|
||||||
@@ -148,7 +147,6 @@ describe("acp service directory behavior", () => {
|
|||||||
).toEqual([
|
).toEqual([
|
||||||
{ model: { providerID: "test", id: secondModel.id } },
|
{ model: { providerID: "test", id: secondModel.id } },
|
||||||
{ model: { providerID: "test", id: secondModel.id, variant: "medium" } },
|
{ model: { providerID: "test", id: secondModel.id, variant: "medium" } },
|
||||||
{ model: { providerID: "test", id: testModel.id, variant: "high" } },
|
|
||||||
])
|
])
|
||||||
expect(
|
expect(
|
||||||
fixture.requests
|
fixture.requests
|
||||||
|
|||||||
@@ -225,6 +225,33 @@ describe("acp service lifecycle", () => {
|
|||||||
"/api/session/missing/interrupt",
|
"/api/session/missing/interrupt",
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test("deletes sessions from backing and local storage", async () => {
|
||||||
|
await using fixture = makeACPFixture({
|
||||||
|
fetch(request) {
|
||||||
|
if (request.method === "POST" && request.path === "/api/session") {
|
||||||
|
return Response.json({ data: makeSession("ses_delete") })
|
||||||
|
}
|
||||||
|
if (request.method === "DELETE" && request.path === "/api/session/ses_delete") {
|
||||||
|
return new Response(null, { status: 204 })
|
||||||
|
}
|
||||||
|
return undefined
|
||||||
|
},
|
||||||
|
})
|
||||||
|
const session = await fixture.service.newSession({ cwd: "/workspace", mcpServers: [] })
|
||||||
|
|
||||||
|
expect(await fixture.service.deleteSession({ sessionId: session.sessionId })).toEqual({})
|
||||||
|
expect(fixture.requests).toContainEqual({
|
||||||
|
method: "DELETE",
|
||||||
|
path: "/api/session/ses_delete",
|
||||||
|
query: {},
|
||||||
|
body: undefined,
|
||||||
|
})
|
||||||
|
const missing = await fixture.service
|
||||||
|
.setSessionConfigOption({ sessionId: session.sessionId, configId: "effort", value: "high" })
|
||||||
|
.catch((error: unknown) => error)
|
||||||
|
expect(missing).toMatchObject({ _tag: "ACPSessionNotFoundError", sessionId: session.sessionId })
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
function currentValue(result: { readonly configOptions?: readonly SessionConfigOption[] | null }, id: string) {
|
function currentValue(result: { readonly configOptions?: readonly SessionConfigOption[] | null }, id: string) {
|
||||||
|
|||||||
@@ -45,17 +45,14 @@ describe("acp service prompt routing and usage", () => {
|
|||||||
|
|
||||||
const commandResult = await fixture.service.prompt({
|
const commandResult = await fixture.service.prompt({
|
||||||
sessionId: session.sessionId,
|
sessionId: session.sessionId,
|
||||||
messageId: "client-command",
|
|
||||||
prompt: [{ type: "text", text: "/review now" }],
|
prompt: [{ type: "text", text: "/review now" }],
|
||||||
})
|
})
|
||||||
const skillResult = await fixture.service.prompt({
|
const skillResult = await fixture.service.prompt({
|
||||||
sessionId: session.sessionId,
|
sessionId: session.sessionId,
|
||||||
messageId: "client-skill",
|
|
||||||
prompt: [{ type: "text", text: "/verify" }],
|
prompt: [{ type: "text", text: "/verify" }],
|
||||||
})
|
})
|
||||||
const compactResult = await fixture.service.prompt({
|
const compactResult = await fixture.service.prompt({
|
||||||
sessionId: session.sessionId,
|
sessionId: session.sessionId,
|
||||||
messageId: "client-compact",
|
|
||||||
prompt: [{ type: "text", text: "/compact" }],
|
prompt: [{ type: "text", text: "/compact" }],
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -154,13 +151,11 @@ describe("acp service prompt routing and usage", () => {
|
|||||||
|
|
||||||
const response = await fixture.service.prompt({
|
const response = await fixture.service.prompt({
|
||||||
sessionId: session.sessionId,
|
sessionId: session.sessionId,
|
||||||
messageId: "client-message",
|
|
||||||
prompt: [{ type: "text", text: "hello" }],
|
prompt: [{ type: "text", text: "hello" }],
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(response).toEqual({
|
expect(response).toEqual({
|
||||||
stopReason: "end_turn",
|
stopReason: "end_turn",
|
||||||
userMessageId: "client-message",
|
|
||||||
usage: {
|
usage: {
|
||||||
inputTokens: 100,
|
inputTokens: 100,
|
||||||
outputTokens: 40,
|
outputTokens: 40,
|
||||||
|
|||||||
@@ -214,11 +214,15 @@ describe("mini command", () => {
|
|||||||
expect(result.exitCode).toBe(0)
|
expect(result.exitCode).toBe(0)
|
||||||
expect(result.stdout).toContain("--server string")
|
expect(result.stdout).toContain("--server string")
|
||||||
expect(result.stdout).toContain("--prompt string")
|
expect(result.stdout).toContain("--prompt string")
|
||||||
|
expect(result.stdout).toContain("--replay")
|
||||||
|
expect(result.stdout).toContain("disable with --no-replay")
|
||||||
|
expect(result.stdout).toContain("--replay-limit integer")
|
||||||
|
expect(result.stdout).toContain("Limit replay to the newest N messages (default: 200)")
|
||||||
expect(result.stdout).not.toContain("SUBCOMMANDS")
|
expect(result.stdout).not.toContain("SUBCOMMANDS")
|
||||||
})
|
})
|
||||||
|
|
||||||
test("routes local and explicit-server invocations into mini", async () => {
|
test("routes local and explicit-server invocations into mini", async () => {
|
||||||
for (const args of [["mini"], ["mini", "--server", "http://127.0.0.1:1"]]) {
|
for (const args of [["mini"], ["mini", "--no-replay"], ["mini", "--server", "http://127.0.0.1:1"]]) {
|
||||||
const result = await cli(args)
|
const result = await cli(args)
|
||||||
|
|
||||||
expect(result.exitCode).toBe(1)
|
expect(result.exitCode).toBe(1)
|
||||||
|
|||||||
@@ -1030,22 +1030,13 @@ export interface VcsApi<E = never> {
|
|||||||
readonly diff: VcsDiffOperation<E>
|
readonly diff: VcsDiffOperation<E>
|
||||||
}
|
}
|
||||||
|
|
||||||
type Endpoint26_0Request = Parameters<RawClient["server.path"]["path.get"]>[0]
|
export type Endpoint26_0Output = EffectValue<ReturnType<RawClient["server.debug"]["debug.location"]>>
|
||||||
export type Endpoint26_0Input = { readonly location?: Endpoint26_0Request["query"]["location"] }
|
export type DebugLocationListOperation<E = never> = () => Effect.Effect<Endpoint26_0Output, E>
|
||||||
export type Endpoint26_0Output = EffectValue<ReturnType<RawClient["server.path"]["path.get"]>>
|
|
||||||
export type PathGetOperation<E = never> = (input?: Endpoint26_0Input) => Effect.Effect<Endpoint26_0Output, E>
|
|
||||||
|
|
||||||
export interface PathApi<E = never> {
|
type Endpoint26_1Request = Parameters<RawClient["server.debug"]["debug.location.evict"]>[0]
|
||||||
readonly get: PathGetOperation<E>
|
export type Endpoint26_1Input = { readonly location?: Endpoint26_1Request["query"]["location"] }
|
||||||
}
|
export type Endpoint26_1Output = EffectValue<ReturnType<RawClient["server.debug"]["debug.location.evict"]>>
|
||||||
|
export type DebugLocationEvictOperation<E = never> = (input?: Endpoint26_1Input) => Effect.Effect<Endpoint26_1Output, E>
|
||||||
export type Endpoint27_0Output = EffectValue<ReturnType<RawClient["server.debug"]["debug.location"]>>
|
|
||||||
export type DebugLocationListOperation<E = never> = () => Effect.Effect<Endpoint27_0Output, E>
|
|
||||||
|
|
||||||
type Endpoint27_1Request = Parameters<RawClient["server.debug"]["debug.location.evict"]>[0]
|
|
||||||
export type Endpoint27_1Input = { readonly location?: Endpoint27_1Request["query"]["location"] }
|
|
||||||
export type Endpoint27_1Output = EffectValue<ReturnType<RawClient["server.debug"]["debug.location.evict"]>>
|
|
||||||
export type DebugLocationEvictOperation<E = never> = (input?: Endpoint27_1Input) => Effect.Effect<Endpoint27_1Output, E>
|
|
||||||
|
|
||||||
export interface DebugApi<E = never> {
|
export interface DebugApi<E = never> {
|
||||||
readonly location: { readonly list: DebugLocationListOperation<E>; readonly evict: DebugLocationEvictOperation<E> }
|
readonly location: { readonly list: DebugLocationListOperation<E>; readonly evict: DebugLocationEvictOperation<E> }
|
||||||
@@ -1078,6 +1069,5 @@ export interface AppApi<E = never> {
|
|||||||
readonly reference: ReferenceApi<E>
|
readonly reference: ReferenceApi<E>
|
||||||
readonly projectCopy: ProjectCopyApi<E>
|
readonly projectCopy: ProjectCopyApi<E>
|
||||||
readonly vcs: VcsApi<E>
|
readonly vcs: VcsApi<E>
|
||||||
readonly path: PathApi<E>
|
|
||||||
readonly debug: DebugApi<E>
|
readonly debug: DebugApi<E>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1232,23 +1232,16 @@ const Endpoint25_1 = (raw: RawClient["server.vcs"]) => (input: Endpoint25_1Input
|
|||||||
|
|
||||||
const adaptGroup25 = (raw: RawClient["server.vcs"]) => ({ status: Endpoint25_0(raw), diff: Endpoint25_1(raw) })
|
const adaptGroup25 = (raw: RawClient["server.vcs"]) => ({ status: Endpoint25_0(raw), diff: Endpoint25_1(raw) })
|
||||||
|
|
||||||
type Endpoint26_0Request = Parameters<RawClient["server.path"]["path.get"]>[0]
|
const Endpoint26_0 = (raw: RawClient["server.debug"]) => () =>
|
||||||
type Endpoint26_0Input = { readonly location?: Endpoint26_0Request["query"]["location"] }
|
|
||||||
const Endpoint26_0 = (raw: RawClient["server.path"]) => (input?: Endpoint26_0Input) =>
|
|
||||||
raw["path.get"]({ query: { location: input?.["location"] } }).pipe(Effect.mapError(mapClientError))
|
|
||||||
|
|
||||||
const adaptGroup26 = (raw: RawClient["server.path"]) => ({ get: Endpoint26_0(raw) })
|
|
||||||
|
|
||||||
const Endpoint27_0 = (raw: RawClient["server.debug"]) => () =>
|
|
||||||
raw["debug.location"]({}).pipe(Effect.mapError(mapClientError))
|
raw["debug.location"]({}).pipe(Effect.mapError(mapClientError))
|
||||||
|
|
||||||
type Endpoint27_1Request = Parameters<RawClient["server.debug"]["debug.location.evict"]>[0]
|
type Endpoint26_1Request = Parameters<RawClient["server.debug"]["debug.location.evict"]>[0]
|
||||||
type Endpoint27_1Input = { readonly location?: Endpoint27_1Request["query"]["location"] }
|
type Endpoint26_1Input = { readonly location?: Endpoint26_1Request["query"]["location"] }
|
||||||
const Endpoint27_1 = (raw: RawClient["server.debug"]) => (input?: Endpoint27_1Input) =>
|
const Endpoint26_1 = (raw: RawClient["server.debug"]) => (input?: Endpoint26_1Input) =>
|
||||||
raw["debug.location.evict"]({ query: { location: input?.["location"] } }).pipe(Effect.mapError(mapClientError))
|
raw["debug.location.evict"]({ query: { location: input?.["location"] } }).pipe(Effect.mapError(mapClientError))
|
||||||
|
|
||||||
const adaptGroup27 = (raw: RawClient["server.debug"]) => ({
|
const adaptGroup26 = (raw: RawClient["server.debug"]) => ({
|
||||||
location: { list: Endpoint27_0(raw), evict: Endpoint27_1(raw) },
|
location: { list: Endpoint26_0(raw), evict: Endpoint26_1(raw) },
|
||||||
})
|
})
|
||||||
|
|
||||||
const adaptClient = (raw: RawClient) => ({
|
const adaptClient = (raw: RawClient) => ({
|
||||||
@@ -1278,8 +1271,7 @@ const adaptClient = (raw: RawClient) => ({
|
|||||||
reference: adaptGroup23(raw["server.reference"]),
|
reference: adaptGroup23(raw["server.reference"]),
|
||||||
projectCopy: adaptGroup24(raw["server.projectCopy"]),
|
projectCopy: adaptGroup24(raw["server.projectCopy"]),
|
||||||
vcs: adaptGroup25(raw["server.vcs"]),
|
vcs: adaptGroup25(raw["server.vcs"]),
|
||||||
path: adaptGroup26(raw["server.path"]),
|
debug: adaptGroup26(raw["server.debug"]),
|
||||||
debug: adaptGroup27(raw["server.debug"]),
|
|
||||||
})
|
})
|
||||||
|
|
||||||
export const make = (options?: { readonly baseUrl?: URL | string }) =>
|
export const make = (options?: { readonly baseUrl?: URL | string }) =>
|
||||||
|
|||||||
@@ -3,22 +3,13 @@ type Client = ReturnType<typeof import("./generated/client.js").make>
|
|||||||
export type AgentApi = Client["agent"]
|
export type AgentApi = Client["agent"]
|
||||||
export type CommandApi = Client["command"]
|
export type CommandApi = Client["command"]
|
||||||
export type EventApi = Client["event"]
|
export type EventApi = Client["event"]
|
||||||
export type FileApi = Client["file"]
|
|
||||||
export type IntegrationApi = Client["integration"]
|
export type IntegrationApi = Client["integration"]
|
||||||
export type MessageApi = Client["message"]
|
|
||||||
export type McpApi = Client["mcp"]
|
|
||||||
export type ModelApi = Client["model"]
|
export type ModelApi = Client["model"]
|
||||||
export type PluginApi = Client["plugin"]
|
export type PluginApi = Client["plugin"]
|
||||||
export type PermissionApi = Client["permission"]
|
|
||||||
export type PathApi = Client["path"]
|
|
||||||
export type ProjectApi = Client["project"]
|
|
||||||
export type ProviderApi = Client["provider"]
|
export type ProviderApi = Client["provider"]
|
||||||
export type QuestionApi = Client["question"]
|
|
||||||
export type ReferenceApi = Client["reference"]
|
export type ReferenceApi = Client["reference"]
|
||||||
export type SessionApi = Client["session"]
|
export type SessionApi = Client["session"]
|
||||||
export type SkillApi = Client["skill"]
|
export type SkillApi = Client["skill"]
|
||||||
export type PtyApi = Client["pty"]
|
|
||||||
export type VcsApi = Client["vcs"]
|
|
||||||
|
|
||||||
export interface CatalogApi {
|
export interface CatalogApi {
|
||||||
readonly provider: ProviderApi
|
readonly provider: ProviderApi
|
||||||
|
|||||||
@@ -204,8 +204,6 @@ import type {
|
|||||||
VcsStatusOutput,
|
VcsStatusOutput,
|
||||||
VcsDiffInput,
|
VcsDiffInput,
|
||||||
VcsDiffOutput,
|
VcsDiffOutput,
|
||||||
PathGetInput,
|
|
||||||
PathGetOutput,
|
|
||||||
DebugLocationListOutput,
|
DebugLocationListOutput,
|
||||||
DebugLocationEvictInput,
|
DebugLocationEvictInput,
|
||||||
DebugLocationEvictOutput,
|
DebugLocationEvictOutput,
|
||||||
@@ -1710,20 +1708,6 @@ export function make(options: ClientOptions) {
|
|||||||
requestOptions,
|
requestOptions,
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
path: {
|
|
||||||
get: (input?: PathGetInput, requestOptions?: RequestOptions) =>
|
|
||||||
request<PathGetOutput>(
|
|
||||||
{
|
|
||||||
method: "GET",
|
|
||||||
path: `/api/path`,
|
|
||||||
query: { location: input?.["location"] },
|
|
||||||
successStatus: 200,
|
|
||||||
declaredStatuses: [401, 400],
|
|
||||||
empty: false,
|
|
||||||
},
|
|
||||||
requestOptions,
|
|
||||||
),
|
|
||||||
},
|
|
||||||
debug: {
|
debug: {
|
||||||
location: {
|
location: {
|
||||||
list: (requestOptions?: RequestOptions) =>
|
list: (requestOptions?: RequestOptions) =>
|
||||||
|
|||||||
@@ -533,8 +533,6 @@ export type VcsFileStatus = {
|
|||||||
status: "added" | "deleted" | "modified"
|
status: "added" | "deleted" | "modified"
|
||||||
}
|
}
|
||||||
|
|
||||||
export type PathInfo = { home: string; state: string; config: string; worktree: string; directory: string }
|
|
||||||
|
|
||||||
export type SessionMessageModelSelected = {
|
export type SessionMessageModelSelected = {
|
||||||
id: string
|
id: string
|
||||||
metadata?: { [x: string]: JsonValue }
|
metadata?: { [x: string]: JsonValue }
|
||||||
@@ -551,7 +549,6 @@ export type CommandInfo = {
|
|||||||
agent?: string
|
agent?: string
|
||||||
model?: ModelRef
|
model?: ModelRef
|
||||||
subtask?: boolean
|
subtask?: boolean
|
||||||
source?: "command" | "mcp"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProviderRequest = {
|
export type ProviderRequest = {
|
||||||
@@ -4955,14 +4952,6 @@ export type VcsDiffOutput = {
|
|||||||
data: Array<FileDiffInfo>
|
data: Array<FileDiffInfo>
|
||||||
}
|
}
|
||||||
|
|
||||||
export type PathGetInput = {
|
|
||||||
readonly location?: {
|
|
||||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
|
||||||
}["location"]
|
|
||||||
}
|
|
||||||
|
|
||||||
export type PathGetOutput = PathInfo
|
|
||||||
|
|
||||||
export type DebugLocationListOutput = Array<LocationRef>
|
export type DebugLocationListOutput = Array<LocationRef>
|
||||||
|
|
||||||
export type DebugLocationEvictInput = {
|
export type DebugLocationEvictInput = {
|
||||||
|
|||||||
@@ -4,22 +4,13 @@ export type {
|
|||||||
CatalogApi,
|
CatalogApi,
|
||||||
CommandApi,
|
CommandApi,
|
||||||
EventApi,
|
EventApi,
|
||||||
FileApi,
|
|
||||||
IntegrationApi,
|
IntegrationApi,
|
||||||
MessageApi,
|
|
||||||
McpApi,
|
|
||||||
ModelApi,
|
ModelApi,
|
||||||
PermissionApi,
|
|
||||||
PathApi,
|
|
||||||
PluginApi,
|
PluginApi,
|
||||||
ProjectApi,
|
|
||||||
ProviderApi,
|
ProviderApi,
|
||||||
QuestionApi,
|
|
||||||
ReferenceApi,
|
ReferenceApi,
|
||||||
SessionApi,
|
SessionApi,
|
||||||
SkillApi,
|
SkillApi,
|
||||||
PtyApi,
|
|
||||||
VcsApi,
|
|
||||||
} from "./api.js"
|
} from "./api.js"
|
||||||
export type { EventSubscribeOutput as OpenCodeEvent } from "./generated/types"
|
export type { EventSubscribeOutput as OpenCodeEvent } from "./generated/types"
|
||||||
export type OpenCodeClient = ReturnType<typeof import("./generated/client").make>
|
export type OpenCodeClient = ReturnType<typeof import("./generated/client").make>
|
||||||
|
|||||||
@@ -31,7 +31,6 @@ test("exposes every standard HTTP API group", () => {
|
|||||||
"reference",
|
"reference",
|
||||||
"projectCopy",
|
"projectCopy",
|
||||||
"vcs",
|
"vcs",
|
||||||
"path",
|
|
||||||
"debug",
|
"debug",
|
||||||
])
|
])
|
||||||
expect(Object.keys(client.debug)).toEqual(["location"])
|
expect(Object.keys(client.debug)).toEqual(["location"])
|
||||||
|
|||||||
@@ -116,7 +116,7 @@ const layer = Layer.effect(
|
|||||||
draft.providers.set(providerID, record)
|
draft.providers.set(providerID, record)
|
||||||
}
|
}
|
||||||
const model =
|
const model =
|
||||||
record.models.get(modelID) ?? (ModelV2.Info.empty(providerID, modelID) as ModelV2.MutableInfo)
|
record.models.get(modelID) ?? (ModelV2.Info.default(providerID, modelID) as ModelV2.MutableInfo)
|
||||||
if (!record.models.has(modelID)) record.models.set(modelID, model)
|
if (!record.models.has(modelID)) record.models.set(modelID, model)
|
||||||
fn(model)
|
fn(model)
|
||||||
model.id = modelID
|
model.id = modelID
|
||||||
|
|||||||
@@ -85,7 +85,6 @@ export const layer = (options?: ShellSelect.Options) => Layer.effect(
|
|||||||
name: mcpCommandName(prompt.server, prompt.name),
|
name: mcpCommandName(prompt.server, prompt.name),
|
||||||
template: "",
|
template: "",
|
||||||
description: prompt.description,
|
description: prompt.description,
|
||||||
source: "mcp",
|
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
@@ -99,10 +98,7 @@ export const layer = (options?: ShellSelect.Options) => Layer.effect(
|
|||||||
return (yield* mcpCommands()).find((command) => command.name === name)
|
return (yield* mcpCommands()).find((command) => command.name === name)
|
||||||
}),
|
}),
|
||||||
list: Effect.fn("CommandV2.list")(function* () {
|
list: Effect.fn("CommandV2.list")(function* () {
|
||||||
const commands = Array.from(state.get().commands.values()).map((command) => ({
|
const commands = Array.from(state.get().commands.values()) as Info[]
|
||||||
...command,
|
|
||||||
source: command.source ?? "command" as const,
|
|
||||||
}))
|
|
||||||
const names = new Set(commands.map((command) => command.name))
|
const names = new Set(commands.map((command) => command.name))
|
||||||
return [
|
return [
|
||||||
...commands,
|
...commands,
|
||||||
|
|||||||
@@ -135,7 +135,7 @@ function build(id: ModelV2.ID, remote: UsableModel, baseURL: string, previous?:
|
|||||||
const released = previous?.time.released || Date.parse(version)
|
const released = previous?.time.released || Date.parse(version)
|
||||||
|
|
||||||
return ModelV2.Info.make({
|
return ModelV2.Info.make({
|
||||||
...ModelV2.Info.empty(ProviderV2.ID.githubCopilot, id),
|
...ModelV2.Info.default(ProviderV2.ID.githubCopilot, id),
|
||||||
id,
|
id,
|
||||||
modelID: ModelV2.ID.make(remote.id),
|
modelID: ModelV2.ID.make(remote.id),
|
||||||
providerID: ProviderV2.ID.githubCopilot,
|
providerID: ProviderV2.ID.githubCopilot,
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ const it = testEffect(AISDK.locationLayer)
|
|||||||
|
|
||||||
const model = (packageName: string, settings: Record<string, unknown> = {}) =>
|
const model = (packageName: string, settings: Record<string, unknown> = {}) =>
|
||||||
ModelV2.Info.make({
|
ModelV2.Info.make({
|
||||||
...ModelV2.Info.empty(ProviderV2.ID.make("test-provider"), ModelV2.ID.make("catalog-model")),
|
...ModelV2.Info.default(ProviderV2.ID.make("test-provider"), ModelV2.ID.make("catalog-model")),
|
||||||
modelID: ModelV2.ID.make("api-model"),
|
modelID: ModelV2.ID.make("api-model"),
|
||||||
package: ProviderV2.aisdk(packageName),
|
package: ProviderV2.aisdk(packageName),
|
||||||
settings,
|
settings,
|
||||||
|
|||||||
@@ -59,7 +59,6 @@ describe("CommandV2", () => {
|
|||||||
providerID: ProviderV2.ID.make("anthropic"),
|
providerID: ProviderV2.ID.make("anthropic"),
|
||||||
variant: ModelV2.VariantID.make("high"),
|
variant: ModelV2.VariantID.make("high"),
|
||||||
},
|
},
|
||||||
source: "command",
|
|
||||||
}),
|
}),
|
||||||
])
|
])
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -95,10 +95,9 @@ Review files`,
|
|||||||
variant: ModelV2.VariantID.make("high"),
|
variant: ModelV2.VariantID.make("high"),
|
||||||
},
|
},
|
||||||
subtask: true,
|
subtask: true,
|
||||||
source: "command",
|
|
||||||
}),
|
}),
|
||||||
CommandV2.Info.make({ name: "empty", template: "", source: "command" }),
|
CommandV2.Info.make({ name: "empty", template: "" }),
|
||||||
CommandV2.Info.make({ name: "nested/docs", template: "Write docs", source: "command" }),
|
CommandV2.Info.make({ name: "nested/docs", template: "Write docs" }),
|
||||||
])
|
])
|
||||||
|
|
||||||
yield* Effect.promise(() => fs.writeFile(path.join(tmp.path, "commands", "review.md"), "Review again"))
|
yield* Effect.promise(() => fs.writeFile(path.join(tmp.path, "commands", "review.md"), "Review again"))
|
||||||
|
|||||||
@@ -49,6 +49,86 @@ function withEnv<A, E, R>(vars: Record<string, string | undefined>, effect: () =
|
|||||||
const decode = Schema.decodeUnknownSync(Config.Info)
|
const decode = Schema.decodeUnknownSync(Config.Info)
|
||||||
|
|
||||||
describe("ConfigProviderPlugin.Plugin", () => {
|
describe("ConfigProviderPlugin.Plugin", () => {
|
||||||
|
it.effect("defaults custom models to agent capabilities", () =>
|
||||||
|
Effect.gen(function* () {
|
||||||
|
const catalog = yield* Catalog.Service
|
||||||
|
const providerID = ProviderV2.ID.make("custom")
|
||||||
|
const modelID = ModelV2.ID.make("chat")
|
||||||
|
const config = Config.Service.of({
|
||||||
|
entries: () =>
|
||||||
|
Effect.succeed([
|
||||||
|
new Config.Document({
|
||||||
|
type: "document",
|
||||||
|
info: decode({
|
||||||
|
providers: {
|
||||||
|
custom: {
|
||||||
|
package: "aisdk:@ai-sdk/openai-compatible",
|
||||||
|
models: { chat: {} },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
})
|
||||||
|
|
||||||
|
yield* addPlugin(config)
|
||||||
|
|
||||||
|
const model = required(yield* catalog.model.get(providerID, modelID))
|
||||||
|
expect(model.capabilities).toEqual({ tools: true, input: ["text", "image"], output: ["text"] })
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
it.effect("preserves catalog capabilities unless config overrides them", () =>
|
||||||
|
Effect.gen(function* () {
|
||||||
|
const catalog = yield* Catalog.Service
|
||||||
|
const providerID = ProviderV2.ID.make("custom")
|
||||||
|
const inheritedID = ModelV2.ID.make("inherited")
|
||||||
|
const overriddenID = ModelV2.ID.make("overridden")
|
||||||
|
yield* catalog.transform((draft) => {
|
||||||
|
draft.model.update(providerID, inheritedID, (model) => {
|
||||||
|
model.capabilities = { tools: false, input: ["text"], output: ["text"] }
|
||||||
|
})
|
||||||
|
draft.model.update(providerID, overriddenID, (model) => {
|
||||||
|
model.capabilities = { tools: false, input: ["text"], output: ["text"] }
|
||||||
|
})
|
||||||
|
})
|
||||||
|
const config = Config.Service.of({
|
||||||
|
entries: () =>
|
||||||
|
Effect.succeed([
|
||||||
|
new Config.Document({
|
||||||
|
type: "document",
|
||||||
|
info: decode({
|
||||||
|
providers: {
|
||||||
|
custom: {
|
||||||
|
package: "aisdk:@ai-sdk/openai-compatible",
|
||||||
|
models: {
|
||||||
|
inherited: { name: "Inherited" },
|
||||||
|
overridden: {
|
||||||
|
capabilities: { tools: true, input: ["text", "image"], output: ["text"] },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
})
|
||||||
|
|
||||||
|
yield* addPlugin(config)
|
||||||
|
|
||||||
|
expect((yield* catalog.model.get(providerID, inheritedID))?.capabilities).toEqual({
|
||||||
|
tools: false,
|
||||||
|
input: ["text"],
|
||||||
|
output: ["text"],
|
||||||
|
})
|
||||||
|
expect((yield* catalog.model.get(providerID, overriddenID))?.capabilities).toEqual({
|
||||||
|
tools: true,
|
||||||
|
input: ["text", "image"],
|
||||||
|
output: ["text"],
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
it.effect("keeps configured model variant bodies unchanged", () =>
|
it.effect("keeps configured model variant bodies unchanged", () =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const catalog = yield* Catalog.Service
|
const catalog = yield* Catalog.Service
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import { Effect, Layer, Stream } from "effect"
|
|||||||
import { testEffect } from "./lib/effect"
|
import { testEffect } from "./lib/effect"
|
||||||
|
|
||||||
const selected = ModelV2.Info.make({
|
const selected = ModelV2.Info.make({
|
||||||
...ModelV2.Info.empty(ProviderV2.ID.make("test-provider"), ModelV2.ID.make("gemini")),
|
...ModelV2.Info.default(ProviderV2.ID.make("test-provider"), ModelV2.ID.make("gemini")),
|
||||||
package: ProviderV2.aisdk("@ai-sdk/google"),
|
package: ProviderV2.aisdk("@ai-sdk/google"),
|
||||||
})
|
})
|
||||||
const runtime = Model.make({ id: "gemini", provider: "test-provider", route: OpenAIChat.route })
|
const runtime = Model.make({ id: "gemini", provider: "test-provider", route: OpenAIChat.route })
|
||||||
|
|||||||
@@ -49,12 +49,12 @@ test("defensively syncs advertised Copilot models", async () => {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const existing = ModelV2.Info.make({
|
const existing = ModelV2.Info.make({
|
||||||
...ModelV2.Info.empty(ProviderV2.ID.githubCopilot, ModelV2.ID.make("gpt-5")),
|
...ModelV2.Info.default(ProviderV2.ID.githubCopilot, ModelV2.ID.make("gpt-5")),
|
||||||
modelID: ModelV2.ID.make("gpt-5"),
|
modelID: ModelV2.ID.make("gpt-5"),
|
||||||
name: "GPT-5 local",
|
name: "GPT-5 local",
|
||||||
})
|
})
|
||||||
const stale = ModelV2.Info.make({
|
const stale = ModelV2.Info.make({
|
||||||
...ModelV2.Info.empty(ProviderV2.ID.githubCopilot, ModelV2.ID.make("stale")),
|
...ModelV2.Info.default(ProviderV2.ID.githubCopilot, ModelV2.ID.make("stale")),
|
||||||
modelID: ModelV2.ID.make("stale"),
|
modelID: ModelV2.ID.make("stale"),
|
||||||
})
|
})
|
||||||
const models = await CopilotModels.get(server.url.origin, {}, [existing, stale])
|
const models = await CopilotModels.get(server.url.origin, {}, [existing, stale])
|
||||||
|
|||||||
@@ -108,7 +108,7 @@ describe("AmazonBedrockPlugin", () => {
|
|||||||
yield* addPlugin()
|
yield* addPlugin()
|
||||||
const result = yield* aisdk.runSDK({
|
const result = yield* aisdk.runSDK({
|
||||||
model: ModelV2.Info.make({
|
model: ModelV2.Info.make({
|
||||||
...ModelV2.Info.empty(ProviderV2.ID.amazonBedrock, ModelV2.ID.make("anthropic.claude-sonnet-4-5")),
|
...ModelV2.Info.default(ProviderV2.ID.amazonBedrock, ModelV2.ID.make("anthropic.claude-sonnet-4-5")),
|
||||||
modelID: ModelV2.ID.make("anthropic.claude-sonnet-4-5"),
|
modelID: ModelV2.ID.make("anthropic.claude-sonnet-4-5"),
|
||||||
package: ProviderV2.aisdk("test-provider"),
|
package: ProviderV2.aisdk("test-provider"),
|
||||||
}),
|
}),
|
||||||
@@ -134,7 +134,7 @@ describe("AmazonBedrockPlugin", () => {
|
|||||||
yield* addPlugin()
|
yield* addPlugin()
|
||||||
const result = yield* aisdk.runSDK({
|
const result = yield* aisdk.runSDK({
|
||||||
model: ModelV2.Info.make({
|
model: ModelV2.Info.make({
|
||||||
...ModelV2.Info.empty(ProviderV2.ID.amazonBedrock, ModelV2.ID.make("anthropic.claude-sonnet-4-5")),
|
...ModelV2.Info.default(ProviderV2.ID.amazonBedrock, ModelV2.ID.make("anthropic.claude-sonnet-4-5")),
|
||||||
modelID: ModelV2.ID.make("anthropic.claude-sonnet-4-5"),
|
modelID: ModelV2.ID.make("anthropic.claude-sonnet-4-5"),
|
||||||
package: ProviderV2.aisdk("test-provider"),
|
package: ProviderV2.aisdk("test-provider"),
|
||||||
}),
|
}),
|
||||||
@@ -169,7 +169,7 @@ describe("AmazonBedrockPlugin", () => {
|
|||||||
yield* addPlugin()
|
yield* addPlugin()
|
||||||
const result = yield* aisdk.runSDK({
|
const result = yield* aisdk.runSDK({
|
||||||
model: ModelV2.Info.make({
|
model: ModelV2.Info.make({
|
||||||
...ModelV2.Info.empty(ProviderV2.ID.amazonBedrock, ModelV2.ID.make("anthropic.claude-sonnet-4-5")),
|
...ModelV2.Info.default(ProviderV2.ID.amazonBedrock, ModelV2.ID.make("anthropic.claude-sonnet-4-5")),
|
||||||
modelID: ModelV2.ID.make("anthropic.claude-sonnet-4-5"),
|
modelID: ModelV2.ID.make("anthropic.claude-sonnet-4-5"),
|
||||||
package: ProviderV2.aisdk("test-provider"),
|
package: ProviderV2.aisdk("test-provider"),
|
||||||
}),
|
}),
|
||||||
@@ -190,7 +190,7 @@ describe("AmazonBedrockPlugin", () => {
|
|||||||
yield* addPlugin()
|
yield* addPlugin()
|
||||||
const result = yield* aisdk.runSDK({
|
const result = yield* aisdk.runSDK({
|
||||||
model: ModelV2.Info.make({
|
model: ModelV2.Info.make({
|
||||||
...ModelV2.Info.empty(ProviderV2.ID.amazonBedrock, ModelV2.ID.make("anthropic.claude-sonnet-4-5")),
|
...ModelV2.Info.default(ProviderV2.ID.amazonBedrock, ModelV2.ID.make("anthropic.claude-sonnet-4-5")),
|
||||||
modelID: ModelV2.ID.make("anthropic.claude-sonnet-4-5"),
|
modelID: ModelV2.ID.make("anthropic.claude-sonnet-4-5"),
|
||||||
package: ProviderV2.aisdk("test-provider"),
|
package: ProviderV2.aisdk("test-provider"),
|
||||||
}),
|
}),
|
||||||
@@ -210,7 +210,7 @@ describe("AmazonBedrockPlugin", () => {
|
|||||||
yield* addPlugin()
|
yield* addPlugin()
|
||||||
const result = yield* aisdk.runSDK({
|
const result = yield* aisdk.runSDK({
|
||||||
model: ModelV2.Info.make({
|
model: ModelV2.Info.make({
|
||||||
...ModelV2.Info.empty(ProviderV2.ID.amazonBedrock, ModelV2.ID.make("anthropic.claude-sonnet-4-5")),
|
...ModelV2.Info.default(ProviderV2.ID.amazonBedrock, ModelV2.ID.make("anthropic.claude-sonnet-4-5")),
|
||||||
modelID: ModelV2.ID.make("anthropic.claude-sonnet-4-5"),
|
modelID: ModelV2.ID.make("anthropic.claude-sonnet-4-5"),
|
||||||
package: ProviderV2.aisdk("test-provider"),
|
package: ProviderV2.aisdk("test-provider"),
|
||||||
}),
|
}),
|
||||||
@@ -230,7 +230,7 @@ describe("AmazonBedrockPlugin", () => {
|
|||||||
yield* addPlugin()
|
yield* addPlugin()
|
||||||
const result = yield* aisdk.runSDK({
|
const result = yield* aisdk.runSDK({
|
||||||
model: ModelV2.Info.make({
|
model: ModelV2.Info.make({
|
||||||
...ModelV2.Info.empty(ProviderV2.ID.amazonBedrock, ModelV2.ID.make("anthropic.claude-sonnet-4-5")),
|
...ModelV2.Info.default(ProviderV2.ID.amazonBedrock, ModelV2.ID.make("anthropic.claude-sonnet-4-5")),
|
||||||
modelID: ModelV2.ID.make("anthropic.claude-sonnet-4-5"),
|
modelID: ModelV2.ID.make("anthropic.claude-sonnet-4-5"),
|
||||||
package: ProviderV2.aisdk("test-provider"),
|
package: ProviderV2.aisdk("test-provider"),
|
||||||
}),
|
}),
|
||||||
@@ -251,7 +251,7 @@ describe("AmazonBedrockPlugin", () => {
|
|||||||
yield* addPlugin()
|
yield* addPlugin()
|
||||||
const result = yield* aisdk.runSDK({
|
const result = yield* aisdk.runSDK({
|
||||||
model: ModelV2.Info.make({
|
model: ModelV2.Info.make({
|
||||||
...ModelV2.Info.empty(ProviderV2.ID.amazonBedrock, ModelV2.ID.make("anthropic.claude-sonnet-4-5")),
|
...ModelV2.Info.default(ProviderV2.ID.amazonBedrock, ModelV2.ID.make("anthropic.claude-sonnet-4-5")),
|
||||||
modelID: ModelV2.ID.make("anthropic.claude-sonnet-4-5"),
|
modelID: ModelV2.ID.make("anthropic.claude-sonnet-4-5"),
|
||||||
package: ProviderV2.aisdk("test-provider"),
|
package: ProviderV2.aisdk("test-provider"),
|
||||||
}),
|
}),
|
||||||
@@ -281,7 +281,7 @@ describe("AmazonBedrockPlugin", () => {
|
|||||||
yield* addPlugin()
|
yield* addPlugin()
|
||||||
const result = yield* aisdk.runSDK({
|
const result = yield* aisdk.runSDK({
|
||||||
model: ModelV2.Info.make({
|
model: ModelV2.Info.make({
|
||||||
...ModelV2.Info.empty(ProviderV2.ID.amazonBedrock, ModelV2.ID.make("anthropic.claude-sonnet-4-5")),
|
...ModelV2.Info.default(ProviderV2.ID.amazonBedrock, ModelV2.ID.make("anthropic.claude-sonnet-4-5")),
|
||||||
modelID: ModelV2.ID.make("anthropic.claude-sonnet-4-5"),
|
modelID: ModelV2.ID.make("anthropic.claude-sonnet-4-5"),
|
||||||
package: ProviderV2.aisdk("test-provider"),
|
package: ProviderV2.aisdk("test-provider"),
|
||||||
}),
|
}),
|
||||||
@@ -310,7 +310,7 @@ describe("AmazonBedrockPlugin", () => {
|
|||||||
yield* addPlugin()
|
yield* addPlugin()
|
||||||
const result = yield* aisdk.runSDK({
|
const result = yield* aisdk.runSDK({
|
||||||
model: ModelV2.Info.make({
|
model: ModelV2.Info.make({
|
||||||
...ModelV2.Info.empty(ProviderV2.ID.amazonBedrock, ModelV2.ID.make("openai.gpt-5.5")),
|
...ModelV2.Info.default(ProviderV2.ID.amazonBedrock, ModelV2.ID.make("openai.gpt-5.5")),
|
||||||
modelID: ModelV2.ID.make("openai.gpt-5.5"),
|
modelID: ModelV2.ID.make("openai.gpt-5.5"),
|
||||||
package: ProviderV2.aisdk("@ai-sdk/amazon-bedrock/mantle"),
|
package: ProviderV2.aisdk("@ai-sdk/amazon-bedrock/mantle"),
|
||||||
}),
|
}),
|
||||||
@@ -338,7 +338,7 @@ describe("AmazonBedrockPlugin", () => {
|
|||||||
yield* addPlugin()
|
yield* addPlugin()
|
||||||
yield* aisdk.runLanguage({
|
yield* aisdk.runLanguage({
|
||||||
model: ModelV2.Info.make({
|
model: ModelV2.Info.make({
|
||||||
...ModelV2.Info.empty(ProviderV2.ID.amazonBedrock, ModelV2.ID.make("openai.gpt-5.5")),
|
...ModelV2.Info.default(ProviderV2.ID.amazonBedrock, ModelV2.ID.make("openai.gpt-5.5")),
|
||||||
modelID: ModelV2.ID.make("openai.gpt-5.5"),
|
modelID: ModelV2.ID.make("openai.gpt-5.5"),
|
||||||
package: ProviderV2.aisdk("@ai-sdk/amazon-bedrock/mantle"),
|
package: ProviderV2.aisdk("@ai-sdk/amazon-bedrock/mantle"),
|
||||||
}),
|
}),
|
||||||
@@ -347,7 +347,7 @@ describe("AmazonBedrockPlugin", () => {
|
|||||||
})
|
})
|
||||||
yield* aisdk.runLanguage({
|
yield* aisdk.runLanguage({
|
||||||
model: ModelV2.Info.make({
|
model: ModelV2.Info.make({
|
||||||
...ModelV2.Info.empty(ProviderV2.ID.amazonBedrock, ModelV2.ID.make("openai.gpt-oss-safeguard-120b")),
|
...ModelV2.Info.default(ProviderV2.ID.amazonBedrock, ModelV2.ID.make("openai.gpt-oss-safeguard-120b")),
|
||||||
modelID: ModelV2.ID.make("openai.gpt-oss-safeguard-120b"),
|
modelID: ModelV2.ID.make("openai.gpt-oss-safeguard-120b"),
|
||||||
package: ProviderV2.aisdk("@ai-sdk/amazon-bedrock/mantle"),
|
package: ProviderV2.aisdk("@ai-sdk/amazon-bedrock/mantle"),
|
||||||
}),
|
}),
|
||||||
@@ -365,7 +365,7 @@ describe("AmazonBedrockPlugin", () => {
|
|||||||
yield* addPlugin()
|
yield* addPlugin()
|
||||||
const result = yield* aisdk.runSDK({
|
const result = yield* aisdk.runSDK({
|
||||||
model: ModelV2.Info.make({
|
model: ModelV2.Info.make({
|
||||||
...ModelV2.Info.empty(ProviderV2.ID.amazonBedrock, ModelV2.ID.make("anthropic.claude-sonnet-4-5")),
|
...ModelV2.Info.default(ProviderV2.ID.amazonBedrock, ModelV2.ID.make("anthropic.claude-sonnet-4-5")),
|
||||||
modelID: ModelV2.ID.make("anthropic.claude-sonnet-4-5"),
|
modelID: ModelV2.ID.make("anthropic.claude-sonnet-4-5"),
|
||||||
package: ProviderV2.aisdk("@ai-sdk/amazon-bedrock/anthropic"),
|
package: ProviderV2.aisdk("@ai-sdk/amazon-bedrock/anthropic"),
|
||||||
}),
|
}),
|
||||||
@@ -393,7 +393,7 @@ describe("AmazonBedrockPlugin", () => {
|
|||||||
yield* addPlugin()
|
yield* addPlugin()
|
||||||
const result = yield* aisdk.runSDK({
|
const result = yield* aisdk.runSDK({
|
||||||
model: ModelV2.Info.make({
|
model: ModelV2.Info.make({
|
||||||
...ModelV2.Info.empty(ProviderV2.ID.amazonBedrock, ModelV2.ID.make("anthropic.claude-sonnet-4-5")),
|
...ModelV2.Info.default(ProviderV2.ID.amazonBedrock, ModelV2.ID.make("anthropic.claude-sonnet-4-5")),
|
||||||
modelID: ModelV2.ID.make("anthropic.claude-sonnet-4-5"),
|
modelID: ModelV2.ID.make("anthropic.claude-sonnet-4-5"),
|
||||||
package: ProviderV2.aisdk("test-provider"),
|
package: ProviderV2.aisdk("test-provider"),
|
||||||
}),
|
}),
|
||||||
@@ -425,7 +425,7 @@ describe("AmazonBedrockPlugin", () => {
|
|||||||
yield* addPlugin()
|
yield* addPlugin()
|
||||||
yield* aisdk.runLanguage({
|
yield* aisdk.runLanguage({
|
||||||
model: ModelV2.Info.make({
|
model: ModelV2.Info.make({
|
||||||
...ModelV2.Info.empty(ProviderV2.ID.amazonBedrock, ModelV2.ID.make("anthropic.claude-sonnet-4-5")),
|
...ModelV2.Info.default(ProviderV2.ID.amazonBedrock, ModelV2.ID.make("anthropic.claude-sonnet-4-5")),
|
||||||
modelID: ModelV2.ID.make("anthropic.claude-sonnet-4-5"),
|
modelID: ModelV2.ID.make("anthropic.claude-sonnet-4-5"),
|
||||||
package: ProviderV2.aisdk("test-provider"),
|
package: ProviderV2.aisdk("test-provider"),
|
||||||
}),
|
}),
|
||||||
@@ -434,7 +434,7 @@ describe("AmazonBedrockPlugin", () => {
|
|||||||
})
|
})
|
||||||
yield* aisdk.runLanguage({
|
yield* aisdk.runLanguage({
|
||||||
model: ModelV2.Info.make({
|
model: ModelV2.Info.make({
|
||||||
...ModelV2.Info.empty(ProviderV2.ID.amazonBedrock, ModelV2.ID.make("anthropic.claude-sonnet-4-5")),
|
...ModelV2.Info.default(ProviderV2.ID.amazonBedrock, ModelV2.ID.make("anthropic.claude-sonnet-4-5")),
|
||||||
modelID: ModelV2.ID.make("anthropic.claude-sonnet-4-5"),
|
modelID: ModelV2.ID.make("anthropic.claude-sonnet-4-5"),
|
||||||
package: ProviderV2.aisdk("test-provider"),
|
package: ProviderV2.aisdk("test-provider"),
|
||||||
}),
|
}),
|
||||||
@@ -443,7 +443,7 @@ describe("AmazonBedrockPlugin", () => {
|
|||||||
})
|
})
|
||||||
yield* aisdk.runLanguage({
|
yield* aisdk.runLanguage({
|
||||||
model: ModelV2.Info.make({
|
model: ModelV2.Info.make({
|
||||||
...ModelV2.Info.empty(ProviderV2.ID.amazonBedrock, ModelV2.ID.make("global.anthropic.claude-sonnet-4-5")),
|
...ModelV2.Info.default(ProviderV2.ID.amazonBedrock, ModelV2.ID.make("global.anthropic.claude-sonnet-4-5")),
|
||||||
modelID: ModelV2.ID.make("global.anthropic.claude-sonnet-4-5"),
|
modelID: ModelV2.ID.make("global.anthropic.claude-sonnet-4-5"),
|
||||||
package: ProviderV2.aisdk("test-provider"),
|
package: ProviderV2.aisdk("test-provider"),
|
||||||
}),
|
}),
|
||||||
@@ -452,7 +452,7 @@ describe("AmazonBedrockPlugin", () => {
|
|||||||
})
|
})
|
||||||
yield* aisdk.runLanguage({
|
yield* aisdk.runLanguage({
|
||||||
model: ModelV2.Info.make({
|
model: ModelV2.Info.make({
|
||||||
...ModelV2.Info.empty(ProviderV2.ID.amazonBedrock, ModelV2.ID.make("anthropic.claude-sonnet-4-5")),
|
...ModelV2.Info.default(ProviderV2.ID.amazonBedrock, ModelV2.ID.make("anthropic.claude-sonnet-4-5")),
|
||||||
modelID: ModelV2.ID.make("anthropic.claude-sonnet-4-5"),
|
modelID: ModelV2.ID.make("anthropic.claude-sonnet-4-5"),
|
||||||
package: ProviderV2.aisdk("test-provider"),
|
package: ProviderV2.aisdk("test-provider"),
|
||||||
}),
|
}),
|
||||||
@@ -461,7 +461,7 @@ describe("AmazonBedrockPlugin", () => {
|
|||||||
})
|
})
|
||||||
yield* aisdk.runLanguage({
|
yield* aisdk.runLanguage({
|
||||||
model: ModelV2.Info.make({
|
model: ModelV2.Info.make({
|
||||||
...ModelV2.Info.empty(ProviderV2.ID.amazonBedrock, ModelV2.ID.make("anthropic.claude-sonnet-4-5")),
|
...ModelV2.Info.default(ProviderV2.ID.amazonBedrock, ModelV2.ID.make("anthropic.claude-sonnet-4-5")),
|
||||||
modelID: ModelV2.ID.make("anthropic.claude-sonnet-4-5"),
|
modelID: ModelV2.ID.make("anthropic.claude-sonnet-4-5"),
|
||||||
package: ProviderV2.aisdk("test-provider"),
|
package: ProviderV2.aisdk("test-provider"),
|
||||||
}),
|
}),
|
||||||
@@ -487,7 +487,7 @@ describe("AmazonBedrockPlugin", () => {
|
|||||||
yield* addPlugin()
|
yield* addPlugin()
|
||||||
yield* aisdk.runLanguage({
|
yield* aisdk.runLanguage({
|
||||||
model: ModelV2.Info.make({
|
model: ModelV2.Info.make({
|
||||||
...ModelV2.Info.empty(ProviderV2.ID.amazonBedrock, ModelV2.ID.make("anthropic.claude-sonnet-4-5")),
|
...ModelV2.Info.default(ProviderV2.ID.amazonBedrock, ModelV2.ID.make("anthropic.claude-sonnet-4-5")),
|
||||||
modelID: ModelV2.ID.make("anthropic.claude-sonnet-4-5"),
|
modelID: ModelV2.ID.make("anthropic.claude-sonnet-4-5"),
|
||||||
package: ProviderV2.aisdk("test-provider"),
|
package: ProviderV2.aisdk("test-provider"),
|
||||||
}),
|
}),
|
||||||
@@ -574,7 +574,7 @@ describe("AmazonBedrockPlugin", () => {
|
|||||||
for (const item of cases) {
|
for (const item of cases) {
|
||||||
yield* aisdk.runLanguage({
|
yield* aisdk.runLanguage({
|
||||||
model: ModelV2.Info.make({
|
model: ModelV2.Info.make({
|
||||||
...ModelV2.Info.empty(ProviderV2.ID.amazonBedrock, ModelV2.ID.make(item.modelID)),
|
...ModelV2.Info.default(ProviderV2.ID.amazonBedrock, ModelV2.ID.make(item.modelID)),
|
||||||
modelID: ModelV2.ID.make(item.modelID),
|
modelID: ModelV2.ID.make(item.modelID),
|
||||||
package: ProviderV2.aisdk("test-provider"),
|
package: ProviderV2.aisdk("test-provider"),
|
||||||
}),
|
}),
|
||||||
@@ -594,7 +594,7 @@ describe("AmazonBedrockPlugin", () => {
|
|||||||
yield* addPlugin()
|
yield* addPlugin()
|
||||||
const result = yield* aisdk.runLanguage({
|
const result = yield* aisdk.runLanguage({
|
||||||
model: ModelV2.Info.make({
|
model: ModelV2.Info.make({
|
||||||
...ModelV2.Info.empty(ProviderV2.ID.openai, ModelV2.ID.make("anthropic.claude-sonnet-4-5")),
|
...ModelV2.Info.default(ProviderV2.ID.openai, ModelV2.ID.make("anthropic.claude-sonnet-4-5")),
|
||||||
modelID: ModelV2.ID.make("anthropic.claude-sonnet-4-5"),
|
modelID: ModelV2.ID.make("anthropic.claude-sonnet-4-5"),
|
||||||
package: ProviderV2.aisdk("test-provider"),
|
package: ProviderV2.aisdk("test-provider"),
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ describe("AnthropicPlugin", () => {
|
|||||||
yield* addPlugin()
|
yield* addPlugin()
|
||||||
const result = yield* aisdk.runSDK({
|
const result = yield* aisdk.runSDK({
|
||||||
model: ModelV2.Info.make({
|
model: ModelV2.Info.make({
|
||||||
...ModelV2.Info.empty(ProviderV2.ID.make("custom-anthropic"), ModelV2.ID.make("claude-sonnet-4-5")),
|
...ModelV2.Info.default(ProviderV2.ID.make("custom-anthropic"), ModelV2.ID.make("claude-sonnet-4-5")),
|
||||||
modelID: ModelV2.ID.make("claude-sonnet-4-5"),
|
modelID: ModelV2.ID.make("claude-sonnet-4-5"),
|
||||||
package: ProviderV2.aisdk("@ai-sdk/anthropic"),
|
package: ProviderV2.aisdk("@ai-sdk/anthropic"),
|
||||||
}),
|
}),
|
||||||
@@ -81,7 +81,7 @@ describe("AnthropicPlugin", () => {
|
|||||||
yield* addPlugin()
|
yield* addPlugin()
|
||||||
const result = yield* aisdk.runSDK({
|
const result = yield* aisdk.runSDK({
|
||||||
model: ModelV2.Info.make({
|
model: ModelV2.Info.make({
|
||||||
...ModelV2.Info.empty(ProviderV2.ID.anthropic, ModelV2.ID.make("claude-sonnet-4-5")),
|
...ModelV2.Info.default(ProviderV2.ID.anthropic, ModelV2.ID.make("claude-sonnet-4-5")),
|
||||||
modelID: ModelV2.ID.make("claude-sonnet-4-5"),
|
modelID: ModelV2.ID.make("claude-sonnet-4-5"),
|
||||||
package: ProviderV2.aisdk("@ai-sdk/anthropic"),
|
package: ProviderV2.aisdk("@ai-sdk/anthropic"),
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -121,7 +121,7 @@ describe("AzureCognitiveServicesPlugin", () => {
|
|||||||
yield* addPlugin()
|
yield* addPlugin()
|
||||||
yield* aisdk.runLanguage({
|
yield* aisdk.runLanguage({
|
||||||
model: ModelV2.Info.make({
|
model: ModelV2.Info.make({
|
||||||
...ModelV2.Info.empty(ProviderV2.ID.make("azure-cognitive-services"), ModelV2.ID.make("deployment")),
|
...ModelV2.Info.default(ProviderV2.ID.make("azure-cognitive-services"), ModelV2.ID.make("deployment")),
|
||||||
modelID: ModelV2.ID.make("deployment"),
|
modelID: ModelV2.ID.make("deployment"),
|
||||||
package: "aisdk:test-provider",
|
package: "aisdk:test-provider",
|
||||||
}),
|
}),
|
||||||
@@ -140,7 +140,7 @@ describe("AzureCognitiveServicesPlugin", () => {
|
|||||||
yield* addPlugin()
|
yield* addPlugin()
|
||||||
yield* aisdk.runLanguage({
|
yield* aisdk.runLanguage({
|
||||||
model: ModelV2.Info.make({
|
model: ModelV2.Info.make({
|
||||||
...ModelV2.Info.empty(ProviderV2.ID.make("azure-cognitive-services"), ModelV2.ID.make("deployment")),
|
...ModelV2.Info.default(ProviderV2.ID.make("azure-cognitive-services"), ModelV2.ID.make("deployment")),
|
||||||
modelID: ModelV2.ID.make("deployment"),
|
modelID: ModelV2.ID.make("deployment"),
|
||||||
package: "aisdk:test-provider",
|
package: "aisdk:test-provider",
|
||||||
}),
|
}),
|
||||||
@@ -149,7 +149,7 @@ describe("AzureCognitiveServicesPlugin", () => {
|
|||||||
})
|
})
|
||||||
const ignored = yield* aisdk.runLanguage({
|
const ignored = yield* aisdk.runLanguage({
|
||||||
model: ModelV2.Info.make({
|
model: ModelV2.Info.make({
|
||||||
...ModelV2.Info.empty(ProviderV2.ID.openai, ModelV2.ID.make("deployment")),
|
...ModelV2.Info.default(ProviderV2.ID.openai, ModelV2.ID.make("deployment")),
|
||||||
modelID: ModelV2.ID.make("deployment"),
|
modelID: ModelV2.ID.make("deployment"),
|
||||||
package: "aisdk:test-provider",
|
package: "aisdk:test-provider",
|
||||||
}),
|
}),
|
||||||
@@ -170,7 +170,7 @@ describe("AzureCognitiveServicesPlugin", () => {
|
|||||||
yield* addPlugin()
|
yield* addPlugin()
|
||||||
yield* aisdk.runLanguage({
|
yield* aisdk.runLanguage({
|
||||||
model: ModelV2.Info.make({
|
model: ModelV2.Info.make({
|
||||||
...ModelV2.Info.empty(ProviderV2.ID.make("azure-cognitive-services"), ModelV2.ID.make("messages-deployment")),
|
...ModelV2.Info.default(ProviderV2.ID.make("azure-cognitive-services"), ModelV2.ID.make("messages-deployment")),
|
||||||
modelID: ModelV2.ID.make("messages-deployment"),
|
modelID: ModelV2.ID.make("messages-deployment"),
|
||||||
package: "aisdk:test-provider",
|
package: "aisdk:test-provider",
|
||||||
}),
|
}),
|
||||||
@@ -179,7 +179,7 @@ describe("AzureCognitiveServicesPlugin", () => {
|
|||||||
})
|
})
|
||||||
yield* aisdk.runLanguage({
|
yield* aisdk.runLanguage({
|
||||||
model: ModelV2.Info.make({
|
model: ModelV2.Info.make({
|
||||||
...ModelV2.Info.empty(ProviderV2.ID.make("azure-cognitive-services"), ModelV2.ID.make("chat-deployment")),
|
...ModelV2.Info.default(ProviderV2.ID.make("azure-cognitive-services"), ModelV2.ID.make("chat-deployment")),
|
||||||
modelID: ModelV2.ID.make("chat-deployment"),
|
modelID: ModelV2.ID.make("chat-deployment"),
|
||||||
package: "aisdk:test-provider",
|
package: "aisdk:test-provider",
|
||||||
}),
|
}),
|
||||||
@@ -188,7 +188,7 @@ describe("AzureCognitiveServicesPlugin", () => {
|
|||||||
})
|
})
|
||||||
yield* aisdk.runLanguage({
|
yield* aisdk.runLanguage({
|
||||||
model: ModelV2.Info.make({
|
model: ModelV2.Info.make({
|
||||||
...ModelV2.Info.empty(ProviderV2.ID.make("azure-cognitive-services"), ModelV2.ID.make("language-deployment")),
|
...ModelV2.Info.default(ProviderV2.ID.make("azure-cognitive-services"), ModelV2.ID.make("language-deployment")),
|
||||||
modelID: ModelV2.ID.make("language-deployment"),
|
modelID: ModelV2.ID.make("language-deployment"),
|
||||||
package: "aisdk:test-provider",
|
package: "aisdk:test-provider",
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -148,7 +148,7 @@ describe("AzurePlugin", () => {
|
|||||||
yield* addPlugin()
|
yield* addPlugin()
|
||||||
const result = yield* aisdk.runSDK({
|
const result = yield* aisdk.runSDK({
|
||||||
model: ModelV2.Info.make({
|
model: ModelV2.Info.make({
|
||||||
...ModelV2.Info.empty(ProviderV2.ID.azure, ModelV2.ID.make("deployment")),
|
...ModelV2.Info.default(ProviderV2.ID.azure, ModelV2.ID.make("deployment")),
|
||||||
modelID: ModelV2.ID.make("deployment"),
|
modelID: ModelV2.ID.make("deployment"),
|
||||||
package: ProviderV2.aisdk("test-provider"),
|
package: ProviderV2.aisdk("test-provider"),
|
||||||
}),
|
}),
|
||||||
@@ -168,7 +168,7 @@ describe("AzurePlugin", () => {
|
|||||||
const exit = yield* aisdk
|
const exit = yield* aisdk
|
||||||
.runSDK({
|
.runSDK({
|
||||||
model: ModelV2.Info.make({
|
model: ModelV2.Info.make({
|
||||||
...ModelV2.Info.empty(ProviderV2.ID.azure, ModelV2.ID.make("deployment")),
|
...ModelV2.Info.default(ProviderV2.ID.azure, ModelV2.ID.make("deployment")),
|
||||||
modelID: ModelV2.ID.make("deployment"),
|
modelID: ModelV2.ID.make("deployment"),
|
||||||
package: ProviderV2.aisdk("test-provider"),
|
package: ProviderV2.aisdk("test-provider"),
|
||||||
}),
|
}),
|
||||||
@@ -189,7 +189,7 @@ describe("AzurePlugin", () => {
|
|||||||
yield* addPlugin()
|
yield* addPlugin()
|
||||||
yield* aisdk.runLanguage({
|
yield* aisdk.runLanguage({
|
||||||
model: ModelV2.Info.make({
|
model: ModelV2.Info.make({
|
||||||
...ModelV2.Info.empty(ProviderV2.ID.azure, ModelV2.ID.make("deployment")),
|
...ModelV2.Info.default(ProviderV2.ID.azure, ModelV2.ID.make("deployment")),
|
||||||
modelID: ModelV2.ID.make("deployment"),
|
modelID: ModelV2.ID.make("deployment"),
|
||||||
package: ProviderV2.aisdk("test-provider"),
|
package: ProviderV2.aisdk("test-provider"),
|
||||||
}),
|
}),
|
||||||
@@ -208,7 +208,7 @@ describe("AzurePlugin", () => {
|
|||||||
yield* addPlugin()
|
yield* addPlugin()
|
||||||
yield* aisdk.runLanguage({
|
yield* aisdk.runLanguage({
|
||||||
model: ModelV2.Info.make({
|
model: ModelV2.Info.make({
|
||||||
...ModelV2.Info.empty(ProviderV2.ID.azure, ModelV2.ID.make("deployment")),
|
...ModelV2.Info.default(ProviderV2.ID.azure, ModelV2.ID.make("deployment")),
|
||||||
modelID: ModelV2.ID.make("deployment"),
|
modelID: ModelV2.ID.make("deployment"),
|
||||||
package: ProviderV2.aisdk("test-provider"),
|
package: ProviderV2.aisdk("test-provider"),
|
||||||
}),
|
}),
|
||||||
@@ -227,7 +227,7 @@ describe("AzurePlugin", () => {
|
|||||||
yield* addPlugin()
|
yield* addPlugin()
|
||||||
yield* aisdk.runLanguage({
|
yield* aisdk.runLanguage({
|
||||||
model: ModelV2.Info.make({
|
model: ModelV2.Info.make({
|
||||||
...ModelV2.Info.empty(ProviderV2.ID.azure, ModelV2.ID.make("deployment")),
|
...ModelV2.Info.default(ProviderV2.ID.azure, ModelV2.ID.make("deployment")),
|
||||||
modelID: ModelV2.ID.make("deployment"),
|
modelID: ModelV2.ID.make("deployment"),
|
||||||
package: ProviderV2.aisdk("test-provider"),
|
package: ProviderV2.aisdk("test-provider"),
|
||||||
body: { useCompletionUrls: true },
|
body: { useCompletionUrls: true },
|
||||||
@@ -247,7 +247,7 @@ describe("AzurePlugin", () => {
|
|||||||
yield* addPlugin()
|
yield* addPlugin()
|
||||||
yield* aisdk.runLanguage({
|
yield* aisdk.runLanguage({
|
||||||
model: ModelV2.Info.make({
|
model: ModelV2.Info.make({
|
||||||
...ModelV2.Info.empty(ProviderV2.ID.azure, ModelV2.ID.make("deployment")),
|
...ModelV2.Info.default(ProviderV2.ID.azure, ModelV2.ID.make("deployment")),
|
||||||
modelID: ModelV2.ID.make("deployment"),
|
modelID: ModelV2.ID.make("deployment"),
|
||||||
package: ProviderV2.aisdk("test-provider"),
|
package: ProviderV2.aisdk("test-provider"),
|
||||||
}),
|
}),
|
||||||
@@ -256,7 +256,7 @@ describe("AzurePlugin", () => {
|
|||||||
})
|
})
|
||||||
const ignored = yield* aisdk.runLanguage({
|
const ignored = yield* aisdk.runLanguage({
|
||||||
model: ModelV2.Info.make({
|
model: ModelV2.Info.make({
|
||||||
...ModelV2.Info.empty(ProviderV2.ID.openai, ModelV2.ID.make("deployment")),
|
...ModelV2.Info.default(ProviderV2.ID.openai, ModelV2.ID.make("deployment")),
|
||||||
modelID: ModelV2.ID.make("deployment"),
|
modelID: ModelV2.ID.make("deployment"),
|
||||||
package: ProviderV2.aisdk("test-provider"),
|
package: ProviderV2.aisdk("test-provider"),
|
||||||
}),
|
}),
|
||||||
@@ -280,7 +280,7 @@ describe("AzurePlugin", () => {
|
|||||||
yield* addPlugin()
|
yield* addPlugin()
|
||||||
yield* aisdk.runLanguage({
|
yield* aisdk.runLanguage({
|
||||||
model: ModelV2.Info.make({
|
model: ModelV2.Info.make({
|
||||||
...ModelV2.Info.empty(ProviderV2.ID.azure, ModelV2.ID.make("messages-deployment")),
|
...ModelV2.Info.default(ProviderV2.ID.azure, ModelV2.ID.make("messages-deployment")),
|
||||||
modelID: ModelV2.ID.make("messages-deployment"),
|
modelID: ModelV2.ID.make("messages-deployment"),
|
||||||
package: ProviderV2.aisdk("test-provider"),
|
package: ProviderV2.aisdk("test-provider"),
|
||||||
}),
|
}),
|
||||||
@@ -289,7 +289,7 @@ describe("AzurePlugin", () => {
|
|||||||
})
|
})
|
||||||
yield* aisdk.runLanguage({
|
yield* aisdk.runLanguage({
|
||||||
model: ModelV2.Info.make({
|
model: ModelV2.Info.make({
|
||||||
...ModelV2.Info.empty(ProviderV2.ID.azure, ModelV2.ID.make("language-deployment")),
|
...ModelV2.Info.default(ProviderV2.ID.azure, ModelV2.ID.make("language-deployment")),
|
||||||
modelID: ModelV2.ID.make("language-deployment"),
|
modelID: ModelV2.ID.make("language-deployment"),
|
||||||
package: ProviderV2.aisdk("test-provider"),
|
package: ProviderV2.aisdk("test-provider"),
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ describe("CerebrasPlugin", () => {
|
|||||||
yield* addPlugin()
|
yield* addPlugin()
|
||||||
const result = yield* aisdk.runSDK({
|
const result = yield* aisdk.runSDK({
|
||||||
model: ModelV2.Info.make({
|
model: ModelV2.Info.make({
|
||||||
...ModelV2.Info.empty(
|
...ModelV2.Info.default(
|
||||||
ProviderV2.ID.make("custom-cerebras"),
|
ProviderV2.ID.make("custom-cerebras"),
|
||||||
ModelV2.ID.make("llama-4-scout-17b-16e-instruct"),
|
ModelV2.ID.make("llama-4-scout-17b-16e-instruct"),
|
||||||
),
|
),
|
||||||
@@ -88,7 +88,7 @@ describe("CerebrasPlugin", () => {
|
|||||||
yield* addPlugin()
|
yield* addPlugin()
|
||||||
yield* aisdk.runSDK({
|
yield* aisdk.runSDK({
|
||||||
model: ModelV2.Info.make({
|
model: ModelV2.Info.make({
|
||||||
...ModelV2.Info.empty(
|
...ModelV2.Info.default(
|
||||||
ProviderV2.ID.make("custom-cerebras"),
|
ProviderV2.ID.make("custom-cerebras"),
|
||||||
ModelV2.ID.make("llama-4-scout-17b-16e-instruct"),
|
ModelV2.ID.make("llama-4-scout-17b-16e-instruct"),
|
||||||
),
|
),
|
||||||
@@ -110,7 +110,7 @@ describe("CerebrasPlugin", () => {
|
|||||||
yield* addPlugin()
|
yield* addPlugin()
|
||||||
const result = yield* aisdk.runSDK({
|
const result = yield* aisdk.runSDK({
|
||||||
model: ModelV2.Info.make({
|
model: ModelV2.Info.make({
|
||||||
...ModelV2.Info.empty(
|
...ModelV2.Info.default(
|
||||||
ProviderV2.ID.make("custom-cerebras"),
|
ProviderV2.ID.make("custom-cerebras"),
|
||||||
ModelV2.ID.make("llama-4-scout-17b-16e-instruct"),
|
ModelV2.ID.make("llama-4-scout-17b-16e-instruct"),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -117,7 +117,7 @@ describe("CloudflareAIGatewayPlugin", () => {
|
|||||||
yield* addPlugin()
|
yield* addPlugin()
|
||||||
const result = yield* aisdk.runSDK({
|
const result = yield* aisdk.runSDK({
|
||||||
model: ModelV2.Info.make({
|
model: ModelV2.Info.make({
|
||||||
...ModelV2.Info.empty(ProviderV2.ID.make("cloudflare-ai-gateway"), ModelV2.ID.make("openai/gpt-5")),
|
...ModelV2.Info.default(ProviderV2.ID.make("cloudflare-ai-gateway"), ModelV2.ID.make("openai/gpt-5")),
|
||||||
modelID: ModelV2.ID.make("openai/gpt-5"),
|
modelID: ModelV2.ID.make("openai/gpt-5"),
|
||||||
package: "aisdk:test-provider",
|
package: "aisdk:test-provider",
|
||||||
}),
|
}),
|
||||||
@@ -139,7 +139,7 @@ describe("CloudflareAIGatewayPlugin", () => {
|
|||||||
|
|
||||||
yield* aisdk.runSDK({
|
yield* aisdk.runSDK({
|
||||||
model: ModelV2.Info.make({
|
model: ModelV2.Info.make({
|
||||||
...ModelV2.Info.empty(ProviderV2.ID.make("cloudflare-ai-gateway"), ModelV2.ID.make("openai/gpt-5")),
|
...ModelV2.Info.default(ProviderV2.ID.make("cloudflare-ai-gateway"), ModelV2.ID.make("openai/gpt-5")),
|
||||||
modelID: ModelV2.ID.make("openai/gpt-5"),
|
modelID: ModelV2.ID.make("openai/gpt-5"),
|
||||||
package: "aisdk:test-provider",
|
package: "aisdk:test-provider",
|
||||||
}),
|
}),
|
||||||
@@ -184,7 +184,7 @@ describe("CloudflareAIGatewayPlugin", () => {
|
|||||||
|
|
||||||
yield* aisdk.runSDK({
|
yield* aisdk.runSDK({
|
||||||
model: ModelV2.Info.make({
|
model: ModelV2.Info.make({
|
||||||
...ModelV2.Info.empty(ProviderV2.ID.make("cloudflare-ai-gateway"), ModelV2.ID.make("openai/gpt-5")),
|
...ModelV2.Info.default(ProviderV2.ID.make("cloudflare-ai-gateway"), ModelV2.ID.make("openai/gpt-5")),
|
||||||
modelID: ModelV2.ID.make("openai/gpt-5"),
|
modelID: ModelV2.ID.make("openai/gpt-5"),
|
||||||
package: "aisdk:test-provider",
|
package: "aisdk:test-provider",
|
||||||
}),
|
}),
|
||||||
@@ -214,7 +214,7 @@ describe("CloudflareAIGatewayPlugin", () => {
|
|||||||
|
|
||||||
yield* aisdk.runSDK({
|
yield* aisdk.runSDK({
|
||||||
model: ModelV2.Info.make({
|
model: ModelV2.Info.make({
|
||||||
...ModelV2.Info.empty(ProviderV2.ID.make("cloudflare-ai-gateway"), ModelV2.ID.make("openai/gpt-5")),
|
...ModelV2.Info.default(ProviderV2.ID.make("cloudflare-ai-gateway"), ModelV2.ID.make("openai/gpt-5")),
|
||||||
modelID: ModelV2.ID.make("openai/gpt-5"),
|
modelID: ModelV2.ID.make("openai/gpt-5"),
|
||||||
package: "aisdk:test-provider",
|
package: "aisdk:test-provider",
|
||||||
}),
|
}),
|
||||||
@@ -252,7 +252,7 @@ describe("CloudflareAIGatewayPlugin", () => {
|
|||||||
|
|
||||||
yield* aisdk.runSDK({
|
yield* aisdk.runSDK({
|
||||||
model: ModelV2.Info.make({
|
model: ModelV2.Info.make({
|
||||||
...ModelV2.Info.empty(ProviderV2.ID.make("cloudflare-ai-gateway"), ModelV2.ID.make("openai/gpt-5")),
|
...ModelV2.Info.default(ProviderV2.ID.make("cloudflare-ai-gateway"), ModelV2.ID.make("openai/gpt-5")),
|
||||||
modelID: ModelV2.ID.make("openai/gpt-5"),
|
modelID: ModelV2.ID.make("openai/gpt-5"),
|
||||||
package: "aisdk:test-provider",
|
package: "aisdk:test-provider",
|
||||||
}),
|
}),
|
||||||
@@ -284,7 +284,7 @@ describe("CloudflareAIGatewayPlugin", () => {
|
|||||||
|
|
||||||
yield* aisdk.runSDK({
|
yield* aisdk.runSDK({
|
||||||
model: ModelV2.Info.make({
|
model: ModelV2.Info.make({
|
||||||
...ModelV2.Info.empty(ProviderV2.ID.make("cloudflare-ai-gateway"), ModelV2.ID.make("openai/gpt-5")),
|
...ModelV2.Info.default(ProviderV2.ID.make("cloudflare-ai-gateway"), ModelV2.ID.make("openai/gpt-5")),
|
||||||
modelID: ModelV2.ID.make("openai/gpt-5"),
|
modelID: ModelV2.ID.make("openai/gpt-5"),
|
||||||
package: "aisdk:test-provider",
|
package: "aisdk:test-provider",
|
||||||
}),
|
}),
|
||||||
@@ -307,7 +307,7 @@ describe("CloudflareAIGatewayPlugin", () => {
|
|||||||
|
|
||||||
const result = yield* aisdk.runSDK({
|
const result = yield* aisdk.runSDK({
|
||||||
model: ModelV2.Info.make({
|
model: ModelV2.Info.make({
|
||||||
...ModelV2.Info.empty(ProviderV2.ID.make("cloudflare-ai-gateway"), ModelV2.ID.make("openai/gpt-5")),
|
...ModelV2.Info.default(ProviderV2.ID.make("cloudflare-ai-gateway"), ModelV2.ID.make("openai/gpt-5")),
|
||||||
modelID: ModelV2.ID.make("openai/gpt-5"),
|
modelID: ModelV2.ID.make("openai/gpt-5"),
|
||||||
package: "aisdk:test-provider",
|
package: "aisdk:test-provider",
|
||||||
}),
|
}),
|
||||||
@@ -331,7 +331,7 @@ describe("CloudflareAIGatewayPlugin", () => {
|
|||||||
|
|
||||||
const result = yield* aisdk.runSDK({
|
const result = yield* aisdk.runSDK({
|
||||||
model: ModelV2.Info.make({
|
model: ModelV2.Info.make({
|
||||||
...ModelV2.Info.empty(ProviderV2.ID.make("cloudflare-ai-gateway"), ModelV2.ID.make("openai/gpt-5")),
|
...ModelV2.Info.default(ProviderV2.ID.make("cloudflare-ai-gateway"), ModelV2.ID.make("openai/gpt-5")),
|
||||||
modelID: ModelV2.ID.make("openai/gpt-5"),
|
modelID: ModelV2.ID.make("openai/gpt-5"),
|
||||||
package: "aisdk:test-provider",
|
package: "aisdk:test-provider",
|
||||||
}),
|
}),
|
||||||
@@ -361,7 +361,7 @@ describe("CloudflareAIGatewayPlugin", () => {
|
|||||||
|
|
||||||
const result = yield* aisdk.runSDK({
|
const result = yield* aisdk.runSDK({
|
||||||
model: ModelV2.Info.make({
|
model: ModelV2.Info.make({
|
||||||
...ModelV2.Info.empty(ProviderV2.ID.make("cloudflare-ai-gateway"), ModelV2.ID.make("openai/gpt-5")),
|
...ModelV2.Info.default(ProviderV2.ID.make("cloudflare-ai-gateway"), ModelV2.ID.make("openai/gpt-5")),
|
||||||
modelID: ModelV2.ID.make("openai/gpt-5"),
|
modelID: ModelV2.ID.make("openai/gpt-5"),
|
||||||
package: "aisdk:test-provider",
|
package: "aisdk:test-provider",
|
||||||
}),
|
}),
|
||||||
@@ -385,7 +385,7 @@ describe("CloudflareAIGatewayPlugin", () => {
|
|||||||
|
|
||||||
const result = yield* aisdk.runSDK({
|
const result = yield* aisdk.runSDK({
|
||||||
model: ModelV2.Info.make({
|
model: ModelV2.Info.make({
|
||||||
...ModelV2.Info.empty(
|
...ModelV2.Info.default(
|
||||||
ProviderV2.ID.make("cloudflare-ai-gateway"),
|
ProviderV2.ID.make("cloudflare-ai-gateway"),
|
||||||
ModelV2.ID.make("anthropic/claude-sonnet-4-5"),
|
ModelV2.ID.make("anthropic/claude-sonnet-4-5"),
|
||||||
),
|
),
|
||||||
@@ -417,7 +417,7 @@ describe("CloudflareAIGatewayPlugin", () => {
|
|||||||
|
|
||||||
const result = yield* aisdk.runSDK({
|
const result = yield* aisdk.runSDK({
|
||||||
model: ModelV2.Info.make({
|
model: ModelV2.Info.make({
|
||||||
...ModelV2.Info.empty(ProviderV2.ID.make("cloudflare-ai-gateway"), ModelV2.ID.make("openai/gpt-5")),
|
...ModelV2.Info.default(ProviderV2.ID.make("cloudflare-ai-gateway"), ModelV2.ID.make("openai/gpt-5")),
|
||||||
modelID: ModelV2.ID.make("openai/gpt-5"),
|
modelID: ModelV2.ID.make("openai/gpt-5"),
|
||||||
package: "aisdk:test-provider",
|
package: "aisdk:test-provider",
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ describe("CloudflareWorkersAIPlugin", () => {
|
|||||||
const provider = required(yield* catalog.provider.get(ProviderV2.ID.make("cloudflare-workers-ai")))
|
const provider = required(yield* catalog.provider.get(ProviderV2.ID.make("cloudflare-workers-ai")))
|
||||||
const sdk = yield* aisdk.runSDK({
|
const sdk = yield* aisdk.runSDK({
|
||||||
model: ModelV2.Info.make({
|
model: ModelV2.Info.make({
|
||||||
...ModelV2.Info.empty(ProviderV2.ID.make("cloudflare-workers-ai"), ModelV2.ID.make("@cf/model")),
|
...ModelV2.Info.default(ProviderV2.ID.make("cloudflare-workers-ai"), ModelV2.ID.make("@cf/model")),
|
||||||
modelID: ModelV2.ID.make("@cf/model"),
|
modelID: ModelV2.ID.make("@cf/model"),
|
||||||
package: provider.package,
|
package: provider.package,
|
||||||
settings: provider.settings,
|
settings: provider.settings,
|
||||||
@@ -138,7 +138,7 @@ describe("CloudflareWorkersAIPlugin", () => {
|
|||||||
yield* addPlugin()
|
yield* addPlugin()
|
||||||
const result = yield* aisdk.runSDK({
|
const result = yield* aisdk.runSDK({
|
||||||
model: ModelV2.Info.make({
|
model: ModelV2.Info.make({
|
||||||
...ModelV2.Info.empty(ProviderV2.ID.make("cloudflare-workers-ai"), ModelV2.ID.make("@cf/model")),
|
...ModelV2.Info.default(ProviderV2.ID.make("cloudflare-workers-ai"), ModelV2.ID.make("@cf/model")),
|
||||||
modelID: ModelV2.ID.make("@cf/model"),
|
modelID: ModelV2.ID.make("@cf/model"),
|
||||||
package: "aisdk:@ai-sdk/openai-compatible",
|
package: "aisdk:@ai-sdk/openai-compatible",
|
||||||
settings: { baseURL: "https://proxy.example/v1" },
|
settings: { baseURL: "https://proxy.example/v1" },
|
||||||
@@ -178,7 +178,7 @@ describe("CloudflareWorkersAIPlugin", () => {
|
|||||||
yield* addPlugin()
|
yield* addPlugin()
|
||||||
const result = yield* aisdk.runSDK({
|
const result = yield* aisdk.runSDK({
|
||||||
model: ModelV2.Info.make({
|
model: ModelV2.Info.make({
|
||||||
...ModelV2.Info.empty(ProviderV2.ID.make("cloudflare-workers-ai"), ModelV2.ID.make("@cf/model")),
|
...ModelV2.Info.default(ProviderV2.ID.make("cloudflare-workers-ai"), ModelV2.ID.make("@cf/model")),
|
||||||
modelID: ModelV2.ID.make("@cf/model"),
|
modelID: ModelV2.ID.make("@cf/model"),
|
||||||
package: "aisdk:@ai-sdk/openai-compatible",
|
package: "aisdk:@ai-sdk/openai-compatible",
|
||||||
settings: { baseURL: "https://proxy.example/v1" },
|
settings: { baseURL: "https://proxy.example/v1" },
|
||||||
@@ -207,7 +207,7 @@ describe("CloudflareWorkersAIPlugin", () => {
|
|||||||
yield* addPlugin()
|
yield* addPlugin()
|
||||||
const result = yield* aisdk.runSDK({
|
const result = yield* aisdk.runSDK({
|
||||||
model: ModelV2.Info.make({
|
model: ModelV2.Info.make({
|
||||||
...ModelV2.Info.empty(ProviderV2.ID.make("cloudflare-workers-ai"), ModelV2.ID.make("@cf/model")),
|
...ModelV2.Info.default(ProviderV2.ID.make("cloudflare-workers-ai"), ModelV2.ID.make("@cf/model")),
|
||||||
modelID: ModelV2.ID.make("@cf/model"),
|
modelID: ModelV2.ID.make("@cf/model"),
|
||||||
package: "aisdk:@ai-sdk/openai-compatible",
|
package: "aisdk:@ai-sdk/openai-compatible",
|
||||||
settings: { baseURL: "https://api.cloudflare.com/client/v4/accounts/${CLOUDFLARE_ACCOUNT_ID}/ai/v1" },
|
settings: { baseURL: "https://api.cloudflare.com/client/v4/accounts/${CLOUDFLARE_ACCOUNT_ID}/ai/v1" },
|
||||||
@@ -233,7 +233,7 @@ describe("CloudflareWorkersAIPlugin", () => {
|
|||||||
yield* addPlugin()
|
yield* addPlugin()
|
||||||
const result = yield* aisdk.runLanguage({
|
const result = yield* aisdk.runLanguage({
|
||||||
model: ModelV2.Info.make({
|
model: ModelV2.Info.make({
|
||||||
...ModelV2.Info.empty(ProviderV2.ID.make("cloudflare-workers-ai"), ModelV2.ID.make("alias")),
|
...ModelV2.Info.default(ProviderV2.ID.make("cloudflare-workers-ai"), ModelV2.ID.make("alias")),
|
||||||
modelID: ModelV2.ID.make("@cf/api-model"),
|
modelID: ModelV2.ID.make("@cf/api-model"),
|
||||||
package: "aisdk:test-provider",
|
package: "aisdk:test-provider",
|
||||||
}),
|
}),
|
||||||
@@ -253,7 +253,7 @@ describe("CloudflareWorkersAIPlugin", () => {
|
|||||||
yield* addPlugin()
|
yield* addPlugin()
|
||||||
const result = yield* aisdk.runSDK({
|
const result = yield* aisdk.runSDK({
|
||||||
model: ModelV2.Info.make({
|
model: ModelV2.Info.make({
|
||||||
...ModelV2.Info.empty(ProviderV2.ID.make("cloudflare-workers-ai"), ModelV2.ID.make("@cf/model")),
|
...ModelV2.Info.default(ProviderV2.ID.make("cloudflare-workers-ai"), ModelV2.ID.make("@cf/model")),
|
||||||
modelID: ModelV2.ID.make("@cf/model"),
|
modelID: ModelV2.ID.make("@cf/model"),
|
||||||
package: "aisdk:@ai-sdk/anthropic",
|
package: "aisdk:@ai-sdk/anthropic",
|
||||||
settings: { baseURL: "https://proxy.example/v1" },
|
settings: { baseURL: "https://proxy.example/v1" },
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ describe("DynamicProviderPlugin", () => {
|
|||||||
yield* addPlugin()
|
yield* addPlugin()
|
||||||
const result = yield* aisdk.runSDK({
|
const result = yield* aisdk.runSDK({
|
||||||
model: ModelV2.Info.make({
|
model: ModelV2.Info.make({
|
||||||
...ModelV2.Info.empty(ProviderV2.ID.make("custom"), ModelV2.ID.make("test-model")),
|
...ModelV2.Info.default(ProviderV2.ID.make("custom"), ModelV2.ID.make("test-model")),
|
||||||
modelID: ModelV2.ID.make("test-model"),
|
modelID: ModelV2.ID.make("test-model"),
|
||||||
package: ProviderV2.aisdk(fixtureProvider),
|
package: ProviderV2.aisdk(fixtureProvider),
|
||||||
}),
|
}),
|
||||||
@@ -72,7 +72,7 @@ describe("DynamicProviderPlugin", () => {
|
|||||||
yield* addPlugin()
|
yield* addPlugin()
|
||||||
const result = yield* aisdk.runSDK({
|
const result = yield* aisdk.runSDK({
|
||||||
model: ModelV2.Info.make({
|
model: ModelV2.Info.make({
|
||||||
...ModelV2.Info.empty(ProviderV2.ID.make("custom"), ModelV2.ID.make("test-model")),
|
...ModelV2.Info.default(ProviderV2.ID.make("custom"), ModelV2.ID.make("test-model")),
|
||||||
modelID: ModelV2.ID.make("test-model"),
|
modelID: ModelV2.ID.make("test-model"),
|
||||||
package: ProviderV2.aisdk(fixtureProvider),
|
package: ProviderV2.aisdk(fixtureProvider),
|
||||||
}),
|
}),
|
||||||
@@ -90,7 +90,7 @@ describe("DynamicProviderPlugin", () => {
|
|||||||
yield* addPlugin()
|
yield* addPlugin()
|
||||||
const result = yield* aisdk.runSDK({
|
const result = yield* aisdk.runSDK({
|
||||||
model: ModelV2.Info.make({
|
model: ModelV2.Info.make({
|
||||||
...ModelV2.Info.empty(ProviderV2.ID.make("custom-provider"), ModelV2.ID.make("test-model")),
|
...ModelV2.Info.default(ProviderV2.ID.make("custom-provider"), ModelV2.ID.make("test-model")),
|
||||||
modelID: ModelV2.ID.make("test-model"),
|
modelID: ModelV2.ID.make("test-model"),
|
||||||
package: ProviderV2.aisdk(fixtureProvider),
|
package: ProviderV2.aisdk(fixtureProvider),
|
||||||
}),
|
}),
|
||||||
@@ -107,7 +107,7 @@ describe("DynamicProviderPlugin", () => {
|
|||||||
yield* addPlugin(npmEntrypoint(fixtureProviderPath))
|
yield* addPlugin(npmEntrypoint(fixtureProviderPath))
|
||||||
const result = yield* aisdk.runSDK({
|
const result = yield* aisdk.runSDK({
|
||||||
model: ModelV2.Info.make({
|
model: ModelV2.Info.make({
|
||||||
...ModelV2.Info.empty(ProviderV2.ID.make("npm-provider"), ModelV2.ID.make("test-model")),
|
...ModelV2.Info.default(ProviderV2.ID.make("npm-provider"), ModelV2.ID.make("test-model")),
|
||||||
modelID: ModelV2.ID.make("test-model"),
|
modelID: ModelV2.ID.make("test-model"),
|
||||||
package: "aisdk:fixture-provider",
|
package: "aisdk:fixture-provider",
|
||||||
}),
|
}),
|
||||||
@@ -125,7 +125,7 @@ describe("DynamicProviderPlugin", () => {
|
|||||||
const exit = yield* aisdk
|
const exit = yield* aisdk
|
||||||
.language(
|
.language(
|
||||||
ModelV2.Info.make({
|
ModelV2.Info.make({
|
||||||
...ModelV2.Info.empty(ProviderV2.ID.make("missing-entrypoint"), ModelV2.ID.make("alias")),
|
...ModelV2.Info.default(ProviderV2.ID.make("missing-entrypoint"), ModelV2.ID.make("alias")),
|
||||||
modelID: ModelV2.ID.make("alias"),
|
modelID: ModelV2.ID.make("alias"),
|
||||||
package: "aisdk:fixture-provider",
|
package: "aisdk:fixture-provider",
|
||||||
}),
|
}),
|
||||||
@@ -143,7 +143,7 @@ describe("DynamicProviderPlugin", () => {
|
|||||||
const exit = yield* aisdk
|
const exit = yield* aisdk
|
||||||
.language(
|
.language(
|
||||||
ModelV2.Info.make({
|
ModelV2.Info.make({
|
||||||
...ModelV2.Info.empty(ProviderV2.ID.make("bad-import"), ModelV2.ID.make("alias")),
|
...ModelV2.Info.default(ProviderV2.ID.make("bad-import"), ModelV2.ID.make("alias")),
|
||||||
modelID: ModelV2.ID.make("alias"),
|
modelID: ModelV2.ID.make("alias"),
|
||||||
package: "aisdk:file:///missing/provider-factory.js",
|
package: "aisdk:file:///missing/provider-factory.js",
|
||||||
}),
|
}),
|
||||||
@@ -163,7 +163,7 @@ describe("DynamicProviderPlugin", () => {
|
|||||||
const exit = yield* aisdk
|
const exit = yield* aisdk
|
||||||
.language(
|
.language(
|
||||||
ModelV2.Info.make({
|
ModelV2.Info.make({
|
||||||
...ModelV2.Info.empty(ProviderV2.ID.make("missing-factory"), ModelV2.ID.make("alias")),
|
...ModelV2.Info.default(ProviderV2.ID.make("missing-factory"), ModelV2.ID.make("alias")),
|
||||||
modelID: ModelV2.ID.make("alias"),
|
modelID: ModelV2.ID.make("alias"),
|
||||||
package: "aisdk:fixture-provider",
|
package: "aisdk:fixture-provider",
|
||||||
}),
|
}),
|
||||||
@@ -181,7 +181,7 @@ describe("DynamicProviderPlugin", () => {
|
|||||||
yield* addPlugin()
|
yield* addPlugin()
|
||||||
const language = yield* aisdk.language(
|
const language = yield* aisdk.language(
|
||||||
ModelV2.Info.make({
|
ModelV2.Info.make({
|
||||||
...ModelV2.Info.empty(ProviderV2.ID.make("custom"), ModelV2.ID.make("alias")),
|
...ModelV2.Info.default(ProviderV2.ID.make("custom"), ModelV2.ID.make("alias")),
|
||||||
modelID: ModelV2.ID.make("test-model-api"),
|
modelID: ModelV2.ID.make("test-model-api"),
|
||||||
package: ProviderV2.aisdk(fixtureProvider),
|
package: ProviderV2.aisdk(fixtureProvider),
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ providers.forEach((item) =>
|
|||||||
const host = yield* PluginHost.make(plugin)
|
const host = yield* PluginHost.make(plugin)
|
||||||
yield* item.plugin.effect(host)
|
yield* item.plugin.effect(host)
|
||||||
const model = ModelV2.Info.make({
|
const model = ModelV2.Info.make({
|
||||||
...ModelV2.Info.empty(ProviderV2.ID.make(item.id), modelID),
|
...ModelV2.Info.default(ProviderV2.ID.make(item.id), modelID),
|
||||||
modelID,
|
modelID,
|
||||||
package: ProviderV2.aisdk(item.package),
|
package: ProviderV2.aisdk(item.package),
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ describe("GithubCopilotPlugin", () => {
|
|||||||
yield* addPlugin()
|
yield* addPlugin()
|
||||||
const ignored = yield* aisdk.runSDK({
|
const ignored = yield* aisdk.runSDK({
|
||||||
model: ModelV2.Info.make({
|
model: ModelV2.Info.make({
|
||||||
...ModelV2.Info.empty(ProviderV2.ID.make("github-copilot"), ModelV2.ID.make("gpt-5")),
|
...ModelV2.Info.default(ProviderV2.ID.make("github-copilot"), ModelV2.ID.make("gpt-5")),
|
||||||
modelID: ModelV2.ID.make("gpt-5"),
|
modelID: ModelV2.ID.make("gpt-5"),
|
||||||
package: "aisdk:test-provider",
|
package: "aisdk:test-provider",
|
||||||
}),
|
}),
|
||||||
@@ -108,7 +108,7 @@ describe("GithubCopilotPlugin", () => {
|
|||||||
})
|
})
|
||||||
const result = yield* aisdk.runSDK({
|
const result = yield* aisdk.runSDK({
|
||||||
model: ModelV2.Info.make({
|
model: ModelV2.Info.make({
|
||||||
...ModelV2.Info.empty(ProviderV2.ID.make("github-copilot"), ModelV2.ID.make("gpt-5")),
|
...ModelV2.Info.default(ProviderV2.ID.make("github-copilot"), ModelV2.ID.make("gpt-5")),
|
||||||
modelID: ModelV2.ID.make("gpt-5"),
|
modelID: ModelV2.ID.make("gpt-5"),
|
||||||
package: "aisdk:test-provider",
|
package: "aisdk:test-provider",
|
||||||
}),
|
}),
|
||||||
@@ -128,7 +128,7 @@ describe("GithubCopilotPlugin", () => {
|
|||||||
yield* addPlugin()
|
yield* addPlugin()
|
||||||
yield* aisdk.runLanguage({
|
yield* aisdk.runLanguage({
|
||||||
model: ModelV2.Info.make({
|
model: ModelV2.Info.make({
|
||||||
...ModelV2.Info.empty(ProviderV2.ID.make("github-copilot"), ModelV2.ID.make("claude-sonnet-4")),
|
...ModelV2.Info.default(ProviderV2.ID.make("github-copilot"), ModelV2.ID.make("claude-sonnet-4")),
|
||||||
modelID: ModelV2.ID.make("claude-sonnet-4"),
|
modelID: ModelV2.ID.make("claude-sonnet-4"),
|
||||||
package: "aisdk:test-provider",
|
package: "aisdk:test-provider",
|
||||||
}),
|
}),
|
||||||
@@ -147,7 +147,7 @@ describe("GithubCopilotPlugin", () => {
|
|||||||
yield* addPlugin()
|
yield* addPlugin()
|
||||||
yield* aisdk.runLanguage({
|
yield* aisdk.runLanguage({
|
||||||
model: ModelV2.Info.make({
|
model: ModelV2.Info.make({
|
||||||
...ModelV2.Info.empty(ProviderV2.ID.make("github-copilot"), ModelV2.ID.make("alias")),
|
...ModelV2.Info.default(ProviderV2.ID.make("github-copilot"), ModelV2.ID.make("alias")),
|
||||||
modelID: ModelV2.ID.make("claude-sonnet-4"),
|
modelID: ModelV2.ID.make("claude-sonnet-4"),
|
||||||
package: "aisdk:test-provider",
|
package: "aisdk:test-provider",
|
||||||
}),
|
}),
|
||||||
@@ -166,7 +166,7 @@ describe("GithubCopilotPlugin", () => {
|
|||||||
yield* addPlugin()
|
yield* addPlugin()
|
||||||
yield* aisdk.runLanguage({
|
yield* aisdk.runLanguage({
|
||||||
model: ModelV2.Info.make({
|
model: ModelV2.Info.make({
|
||||||
...ModelV2.Info.empty(ProviderV2.ID.make("github-copilot"), ModelV2.ID.make("gpt-5")),
|
...ModelV2.Info.default(ProviderV2.ID.make("github-copilot"), ModelV2.ID.make("gpt-5")),
|
||||||
modelID: ModelV2.ID.make("gpt-5"),
|
modelID: ModelV2.ID.make("gpt-5"),
|
||||||
package: "aisdk:test-provider",
|
package: "aisdk:test-provider",
|
||||||
}),
|
}),
|
||||||
@@ -175,7 +175,7 @@ describe("GithubCopilotPlugin", () => {
|
|||||||
})
|
})
|
||||||
yield* aisdk.runLanguage({
|
yield* aisdk.runLanguage({
|
||||||
model: ModelV2.Info.make({
|
model: ModelV2.Info.make({
|
||||||
...ModelV2.Info.empty(ProviderV2.ID.make("github-copilot"), ModelV2.ID.make("gpt-5.1-codex")),
|
...ModelV2.Info.default(ProviderV2.ID.make("github-copilot"), ModelV2.ID.make("gpt-5.1-codex")),
|
||||||
modelID: ModelV2.ID.make("gpt-5.1-codex"),
|
modelID: ModelV2.ID.make("gpt-5.1-codex"),
|
||||||
package: "aisdk:test-provider",
|
package: "aisdk:test-provider",
|
||||||
}),
|
}),
|
||||||
@@ -184,7 +184,7 @@ describe("GithubCopilotPlugin", () => {
|
|||||||
})
|
})
|
||||||
yield* aisdk.runLanguage({
|
yield* aisdk.runLanguage({
|
||||||
model: ModelV2.Info.make({
|
model: ModelV2.Info.make({
|
||||||
...ModelV2.Info.empty(ProviderV2.ID.make("github-copilot"), ModelV2.ID.make("gpt-4o")),
|
...ModelV2.Info.default(ProviderV2.ID.make("github-copilot"), ModelV2.ID.make("gpt-4o")),
|
||||||
modelID: ModelV2.ID.make("gpt-4o"),
|
modelID: ModelV2.ID.make("gpt-4o"),
|
||||||
package: "aisdk:test-provider",
|
package: "aisdk:test-provider",
|
||||||
}),
|
}),
|
||||||
@@ -193,7 +193,7 @@ describe("GithubCopilotPlugin", () => {
|
|||||||
})
|
})
|
||||||
yield* aisdk.runLanguage({
|
yield* aisdk.runLanguage({
|
||||||
model: ModelV2.Info.make({
|
model: ModelV2.Info.make({
|
||||||
...ModelV2.Info.empty(ProviderV2.ID.make("github-copilot"), ModelV2.ID.make("gpt-5-mini")),
|
...ModelV2.Info.default(ProviderV2.ID.make("github-copilot"), ModelV2.ID.make("gpt-5-mini")),
|
||||||
modelID: ModelV2.ID.make("gpt-5-mini"),
|
modelID: ModelV2.ID.make("gpt-5-mini"),
|
||||||
package: "aisdk:test-provider",
|
package: "aisdk:test-provider",
|
||||||
}),
|
}),
|
||||||
@@ -202,7 +202,7 @@ describe("GithubCopilotPlugin", () => {
|
|||||||
})
|
})
|
||||||
yield* aisdk.runLanguage({
|
yield* aisdk.runLanguage({
|
||||||
model: ModelV2.Info.make({
|
model: ModelV2.Info.make({
|
||||||
...ModelV2.Info.empty(ProviderV2.ID.make("github-copilot"), ModelV2.ID.make("gpt-5-mini-2025-08-07")),
|
...ModelV2.Info.default(ProviderV2.ID.make("github-copilot"), ModelV2.ID.make("gpt-5-mini-2025-08-07")),
|
||||||
modelID: ModelV2.ID.make("gpt-5-mini-2025-08-07"),
|
modelID: ModelV2.ID.make("gpt-5-mini-2025-08-07"),
|
||||||
package: "aisdk:test-provider",
|
package: "aisdk:test-provider",
|
||||||
}),
|
}),
|
||||||
@@ -227,7 +227,7 @@ describe("GithubCopilotPlugin", () => {
|
|||||||
yield* addPlugin()
|
yield* addPlugin()
|
||||||
yield* aisdk.runLanguage({
|
yield* aisdk.runLanguage({
|
||||||
model: ModelV2.Info.make({
|
model: ModelV2.Info.make({
|
||||||
...ModelV2.Info.empty(ProviderV2.ID.make("github-copilot"), ModelV2.ID.make("mai-code-1-flash-picker")),
|
...ModelV2.Info.default(ProviderV2.ID.make("github-copilot"), ModelV2.ID.make("mai-code-1-flash-picker")),
|
||||||
modelID: ModelV2.ID.make("mai-code-1-flash-picker"),
|
modelID: ModelV2.ID.make("mai-code-1-flash-picker"),
|
||||||
package: "aisdk:test-provider",
|
package: "aisdk:test-provider",
|
||||||
settings: { endpoint: "responses" },
|
settings: { endpoint: "responses" },
|
||||||
@@ -237,7 +237,7 @@ describe("GithubCopilotPlugin", () => {
|
|||||||
})
|
})
|
||||||
yield* aisdk.runLanguage({
|
yield* aisdk.runLanguage({
|
||||||
model: ModelV2.Info.make({
|
model: ModelV2.Info.make({
|
||||||
...ModelV2.Info.empty(ProviderV2.ID.make("github-copilot"), ModelV2.ID.make("gpt-5")),
|
...ModelV2.Info.default(ProviderV2.ID.make("github-copilot"), ModelV2.ID.make("gpt-5")),
|
||||||
modelID: ModelV2.ID.make("gpt-5"),
|
modelID: ModelV2.ID.make("gpt-5"),
|
||||||
package: "aisdk:test-provider",
|
package: "aisdk:test-provider",
|
||||||
settings: { endpoint: "chat" },
|
settings: { endpoint: "chat" },
|
||||||
@@ -257,7 +257,7 @@ describe("GithubCopilotPlugin", () => {
|
|||||||
yield* addPlugin()
|
yield* addPlugin()
|
||||||
yield* aisdk.runLanguage({
|
yield* aisdk.runLanguage({
|
||||||
model: ModelV2.Info.make({
|
model: ModelV2.Info.make({
|
||||||
...ModelV2.Info.empty(ProviderV2.ID.make("github-copilot"), ModelV2.ID.make("default")),
|
...ModelV2.Info.default(ProviderV2.ID.make("github-copilot"), ModelV2.ID.make("default")),
|
||||||
modelID: ModelV2.ID.make("gpt-5"),
|
modelID: ModelV2.ID.make("gpt-5"),
|
||||||
package: "aisdk:test-provider",
|
package: "aisdk:test-provider",
|
||||||
}),
|
}),
|
||||||
@@ -266,7 +266,7 @@ describe("GithubCopilotPlugin", () => {
|
|||||||
})
|
})
|
||||||
yield* aisdk.runLanguage({
|
yield* aisdk.runLanguage({
|
||||||
model: ModelV2.Info.make({
|
model: ModelV2.Info.make({
|
||||||
...ModelV2.Info.empty(ProviderV2.ID.make("github-copilot"), ModelV2.ID.make("small")),
|
...ModelV2.Info.default(ProviderV2.ID.make("github-copilot"), ModelV2.ID.make("small")),
|
||||||
modelID: ModelV2.ID.make("gpt-5-mini"),
|
modelID: ModelV2.ID.make("gpt-5-mini"),
|
||||||
package: "aisdk:test-provider",
|
package: "aisdk:test-provider",
|
||||||
}),
|
}),
|
||||||
@@ -275,7 +275,7 @@ describe("GithubCopilotPlugin", () => {
|
|||||||
})
|
})
|
||||||
yield* aisdk.runLanguage({
|
yield* aisdk.runLanguage({
|
||||||
model: ModelV2.Info.make({
|
model: ModelV2.Info.make({
|
||||||
...ModelV2.Info.empty(ProviderV2.ID.make("github-copilot"), ModelV2.ID.make("sonnet")),
|
...ModelV2.Info.default(ProviderV2.ID.make("github-copilot"), ModelV2.ID.make("sonnet")),
|
||||||
modelID: ModelV2.ID.make("claude-sonnet-4"),
|
modelID: ModelV2.ID.make("claude-sonnet-4"),
|
||||||
package: "aisdk:test-provider",
|
package: "aisdk:test-provider",
|
||||||
}),
|
}),
|
||||||
@@ -324,7 +324,7 @@ describe("GithubCopilotPlugin", () => {
|
|||||||
yield* addPlugin()
|
yield* addPlugin()
|
||||||
const result = yield* aisdk.runLanguage({
|
const result = yield* aisdk.runLanguage({
|
||||||
model: ModelV2.Info.make({
|
model: ModelV2.Info.make({
|
||||||
...ModelV2.Info.empty(ProviderV2.ID.make("openai"), ModelV2.ID.make("gpt-5")),
|
...ModelV2.Info.default(ProviderV2.ID.make("openai"), ModelV2.ID.make("gpt-5")),
|
||||||
modelID: ModelV2.ID.make("gpt-5"),
|
modelID: ModelV2.ID.make("gpt-5"),
|
||||||
package: "aisdk:test-provider",
|
package: "aisdk:test-provider",
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ describe("GitLabPlugin", () => {
|
|||||||
yield* addPlugin()
|
yield* addPlugin()
|
||||||
yield* aisdk.runSDK({
|
yield* aisdk.runSDK({
|
||||||
model: ModelV2.Info.make({
|
model: ModelV2.Info.make({
|
||||||
...ModelV2.Info.empty(ProviderV2.ID.make("gitlab"), ModelV2.ID.make("claude")),
|
...ModelV2.Info.default(ProviderV2.ID.make("gitlab"), ModelV2.ID.make("claude")),
|
||||||
modelID: ModelV2.ID.make("claude"),
|
modelID: ModelV2.ID.make("claude"),
|
||||||
package: "aisdk:test-provider",
|
package: "aisdk:test-provider",
|
||||||
}),
|
}),
|
||||||
@@ -107,7 +107,7 @@ describe("GitLabPlugin", () => {
|
|||||||
yield* addPlugin()
|
yield* addPlugin()
|
||||||
yield* aisdk.runSDK({
|
yield* aisdk.runSDK({
|
||||||
model: ModelV2.Info.make({
|
model: ModelV2.Info.make({
|
||||||
...ModelV2.Info.empty(ProviderV2.ID.make("gitlab"), ModelV2.ID.make("claude")),
|
...ModelV2.Info.default(ProviderV2.ID.make("gitlab"), ModelV2.ID.make("claude")),
|
||||||
modelID: ModelV2.ID.make("claude"),
|
modelID: ModelV2.ID.make("claude"),
|
||||||
package: "aisdk:test-provider",
|
package: "aisdk:test-provider",
|
||||||
}),
|
}),
|
||||||
@@ -133,7 +133,7 @@ describe("GitLabPlugin", () => {
|
|||||||
yield* addPlugin()
|
yield* addPlugin()
|
||||||
yield* aisdk.runSDK({
|
yield* aisdk.runSDK({
|
||||||
model: ModelV2.Info.make({
|
model: ModelV2.Info.make({
|
||||||
...ModelV2.Info.empty(ProviderV2.ID.make("gitlab"), ModelV2.ID.make("claude")),
|
...ModelV2.Info.default(ProviderV2.ID.make("gitlab"), ModelV2.ID.make("claude")),
|
||||||
modelID: ModelV2.ID.make("claude"),
|
modelID: ModelV2.ID.make("claude"),
|
||||||
package: "aisdk:test-provider",
|
package: "aisdk:test-provider",
|
||||||
}),
|
}),
|
||||||
@@ -175,7 +175,7 @@ describe("GitLabPlugin", () => {
|
|||||||
yield* addPlugin()
|
yield* addPlugin()
|
||||||
const result = yield* aisdk.runSDK({
|
const result = yield* aisdk.runSDK({
|
||||||
model: ModelV2.Info.make({
|
model: ModelV2.Info.make({
|
||||||
...ModelV2.Info.empty(ProviderV2.ID.make("gitlab"), ModelV2.ID.make("claude")),
|
...ModelV2.Info.default(ProviderV2.ID.make("gitlab"), ModelV2.ID.make("claude")),
|
||||||
modelID: ModelV2.ID.make("claude"),
|
modelID: ModelV2.ID.make("claude"),
|
||||||
package: "aisdk:test-provider",
|
package: "aisdk:test-provider",
|
||||||
}),
|
}),
|
||||||
@@ -195,7 +195,7 @@ describe("GitLabPlugin", () => {
|
|||||||
yield* addPlugin()
|
yield* addPlugin()
|
||||||
const result = yield* aisdk.runLanguage({
|
const result = yield* aisdk.runLanguage({
|
||||||
model: ModelV2.Info.make({
|
model: ModelV2.Info.make({
|
||||||
...ModelV2.Info.empty(ProviderV2.ID.make("gitlab"), ModelV2.ID.make("duo-workflow-custom")),
|
...ModelV2.Info.default(ProviderV2.ID.make("gitlab"), ModelV2.ID.make("duo-workflow-custom")),
|
||||||
modelID: ModelV2.ID.make("duo-workflow-custom"),
|
modelID: ModelV2.ID.make("duo-workflow-custom"),
|
||||||
package: "aisdk:test-provider",
|
package: "aisdk:test-provider",
|
||||||
headers: {},
|
headers: {},
|
||||||
@@ -229,7 +229,7 @@ describe("GitLabPlugin", () => {
|
|||||||
yield* addPlugin()
|
yield* addPlugin()
|
||||||
const result = yield* aisdk.runLanguage({
|
const result = yield* aisdk.runLanguage({
|
||||||
model: ModelV2.Info.make({
|
model: ModelV2.Info.make({
|
||||||
...ModelV2.Info.empty(ProviderV2.ID.make("gitlab"), ModelV2.ID.make("duo-workflow-exact")),
|
...ModelV2.Info.default(ProviderV2.ID.make("gitlab"), ModelV2.ID.make("duo-workflow-exact")),
|
||||||
modelID: ModelV2.ID.make("duo-workflow-exact"),
|
modelID: ModelV2.ID.make("duo-workflow-exact"),
|
||||||
package: "aisdk:test-provider",
|
package: "aisdk:test-provider",
|
||||||
}),
|
}),
|
||||||
@@ -257,7 +257,7 @@ describe("GitLabPlugin", () => {
|
|||||||
yield* addPlugin()
|
yield* addPlugin()
|
||||||
yield* aisdk.runLanguage({
|
yield* aisdk.runLanguage({
|
||||||
model: ModelV2.Info.make({
|
model: ModelV2.Info.make({
|
||||||
...ModelV2.Info.empty(ProviderV2.ID.make("gitlab"), ModelV2.ID.make("duo-workflow-custom")),
|
...ModelV2.Info.default(ProviderV2.ID.make("gitlab"), ModelV2.ID.make("duo-workflow-custom")),
|
||||||
modelID: ModelV2.ID.make("duo-workflow-custom"),
|
modelID: ModelV2.ID.make("duo-workflow-custom"),
|
||||||
package: "aisdk:test-provider",
|
package: "aisdk:test-provider",
|
||||||
headers: {},
|
headers: {},
|
||||||
@@ -284,7 +284,7 @@ describe("GitLabPlugin", () => {
|
|||||||
yield* addPlugin()
|
yield* addPlugin()
|
||||||
yield* aisdk.runLanguage({
|
yield* aisdk.runLanguage({
|
||||||
model: ModelV2.Info.make({
|
model: ModelV2.Info.make({
|
||||||
...ModelV2.Info.empty(ProviderV2.ID.make("gitlab"), ModelV2.ID.make("claude")),
|
...ModelV2.Info.default(ProviderV2.ID.make("gitlab"), ModelV2.ID.make("claude")),
|
||||||
modelID: ModelV2.ID.make("claude"),
|
modelID: ModelV2.ID.make("claude"),
|
||||||
package: "aisdk:test-provider",
|
package: "aisdk:test-provider",
|
||||||
headers: { h: "v" },
|
headers: { h: "v" },
|
||||||
|
|||||||
@@ -116,7 +116,7 @@ describe("GoogleVertexAnthropicPlugin", () => {
|
|||||||
yield* addPlugin(GoogleVertexAnthropicPlugin)
|
yield* addPlugin(GoogleVertexAnthropicPlugin)
|
||||||
const result = yield* aisdk.runSDK({
|
const result = yield* aisdk.runSDK({
|
||||||
model: ModelV2.Info.make({
|
model: ModelV2.Info.make({
|
||||||
...ModelV2.Info.empty(
|
...ModelV2.Info.default(
|
||||||
ProviderV2.ID.make("google-vertex-anthropic"),
|
ProviderV2.ID.make("google-vertex-anthropic"),
|
||||||
ModelV2.ID.make("claude-sonnet-4-5"),
|
ModelV2.ID.make("claude-sonnet-4-5"),
|
||||||
),
|
),
|
||||||
@@ -143,7 +143,7 @@ describe("GoogleVertexAnthropicPlugin", () => {
|
|||||||
yield* addPlugin(GoogleVertexAnthropicPlugin)
|
yield* addPlugin(GoogleVertexAnthropicPlugin)
|
||||||
const result = yield* aisdk.runSDK({
|
const result = yield* aisdk.runSDK({
|
||||||
model: ModelV2.Info.make({
|
model: ModelV2.Info.make({
|
||||||
...ModelV2.Info.empty(
|
...ModelV2.Info.default(
|
||||||
ProviderV2.ID.make("google-vertex-anthropic"),
|
ProviderV2.ID.make("google-vertex-anthropic"),
|
||||||
ModelV2.ID.make("claude-sonnet-4-5"),
|
ModelV2.ID.make("claude-sonnet-4-5"),
|
||||||
),
|
),
|
||||||
@@ -167,7 +167,7 @@ describe("GoogleVertexAnthropicPlugin", () => {
|
|||||||
yield* addPlugin(GoogleVertexAnthropicPlugin)
|
yield* addPlugin(GoogleVertexAnthropicPlugin)
|
||||||
const result = yield* aisdk.runSDK({
|
const result = yield* aisdk.runSDK({
|
||||||
model: ModelV2.Info.make({
|
model: ModelV2.Info.make({
|
||||||
...ModelV2.Info.empty(ProviderV2.ID.make("google-vertex"), ModelV2.ID.make("claude-sonnet-4-5")),
|
...ModelV2.Info.default(ProviderV2.ID.make("google-vertex"), ModelV2.ID.make("claude-sonnet-4-5")),
|
||||||
modelID: ModelV2.ID.make("claude-sonnet-4-5"),
|
modelID: ModelV2.ID.make("claude-sonnet-4-5"),
|
||||||
package: "aisdk:test-provider",
|
package: "aisdk:test-provider",
|
||||||
}),
|
}),
|
||||||
@@ -187,7 +187,7 @@ describe("GoogleVertexAnthropicPlugin", () => {
|
|||||||
yield* addPlugin(GoogleVertexAnthropicPlugin)
|
yield* addPlugin(GoogleVertexAnthropicPlugin)
|
||||||
const result = yield* aisdk.runSDK({
|
const result = yield* aisdk.runSDK({
|
||||||
model: ModelV2.Info.make({
|
model: ModelV2.Info.make({
|
||||||
...ModelV2.Info.empty(ProviderV2.ID.make("google-vertex"), ModelV2.ID.make("claude-sonnet-4-5")),
|
...ModelV2.Info.default(ProviderV2.ID.make("google-vertex"), ModelV2.ID.make("claude-sonnet-4-5")),
|
||||||
modelID: ModelV2.ID.make("claude-sonnet-4-5"),
|
modelID: ModelV2.ID.make("claude-sonnet-4-5"),
|
||||||
package: "aisdk:test-provider",
|
package: "aisdk:test-provider",
|
||||||
}),
|
}),
|
||||||
@@ -206,7 +206,7 @@ describe("GoogleVertexAnthropicPlugin", () => {
|
|||||||
yield* addPlugin(GoogleVertexAnthropicPlugin)
|
yield* addPlugin(GoogleVertexAnthropicPlugin)
|
||||||
const sdkResult = yield* aisdk.runSDK({
|
const sdkResult = yield* aisdk.runSDK({
|
||||||
model: ModelV2.Info.make({
|
model: ModelV2.Info.make({
|
||||||
...ModelV2.Info.empty(ProviderV2.ID.make("google-vertex"), ModelV2.ID.make(" claude-sonnet-4-5 ")),
|
...ModelV2.Info.default(ProviderV2.ID.make("google-vertex"), ModelV2.ID.make(" claude-sonnet-4-5 ")),
|
||||||
modelID: ModelV2.ID.make(" claude-sonnet-4-5 "),
|
modelID: ModelV2.ID.make(" claude-sonnet-4-5 "),
|
||||||
package: "aisdk:test-provider",
|
package: "aisdk:test-provider",
|
||||||
}),
|
}),
|
||||||
@@ -215,7 +215,7 @@ describe("GoogleVertexAnthropicPlugin", () => {
|
|||||||
})
|
})
|
||||||
const languageResult = yield* aisdk.runLanguage({
|
const languageResult = yield* aisdk.runLanguage({
|
||||||
model: ModelV2.Info.make({
|
model: ModelV2.Info.make({
|
||||||
...ModelV2.Info.empty(ProviderV2.ID.make("google-vertex"), ModelV2.ID.make(" claude-sonnet-4-5 ")),
|
...ModelV2.Info.default(ProviderV2.ID.make("google-vertex"), ModelV2.ID.make(" claude-sonnet-4-5 ")),
|
||||||
modelID: ModelV2.ID.make(" claude-sonnet-4-5 "),
|
modelID: ModelV2.ID.make(" claude-sonnet-4-5 "),
|
||||||
package: "aisdk:test-provider",
|
package: "aisdk:test-provider",
|
||||||
}),
|
}),
|
||||||
@@ -238,7 +238,7 @@ describe("GoogleVertexAnthropicPlugin", () => {
|
|||||||
yield* addPlugin(GoogleVertexAnthropicPlugin)
|
yield* addPlugin(GoogleVertexAnthropicPlugin)
|
||||||
yield* aisdk.runLanguage({
|
yield* aisdk.runLanguage({
|
||||||
model: ModelV2.Info.make({
|
model: ModelV2.Info.make({
|
||||||
...ModelV2.Info.empty(ProviderV2.ID.make("google-vertex-anthropic"), ModelV2.ID.make(" claude-sonnet-4-5 ")),
|
...ModelV2.Info.default(ProviderV2.ID.make("google-vertex-anthropic"), ModelV2.ID.make(" claude-sonnet-4-5 ")),
|
||||||
modelID: ModelV2.ID.make(" claude-sonnet-4-5 "),
|
modelID: ModelV2.ID.make(" claude-sonnet-4-5 "),
|
||||||
package: "aisdk:test-provider",
|
package: "aisdk:test-provider",
|
||||||
}),
|
}),
|
||||||
@@ -257,7 +257,7 @@ describe("GoogleVertexAnthropicPlugin", () => {
|
|||||||
yield* addPlugin(GoogleVertexAnthropicPlugin)
|
yield* addPlugin(GoogleVertexAnthropicPlugin)
|
||||||
const result = yield* aisdk.runLanguage({
|
const result = yield* aisdk.runLanguage({
|
||||||
model: ModelV2.Info.make({
|
model: ModelV2.Info.make({
|
||||||
...ModelV2.Info.empty(ProviderV2.ID.make("google-vertex"), ModelV2.ID.make("claude-sonnet-4-5")),
|
...ModelV2.Info.default(ProviderV2.ID.make("google-vertex"), ModelV2.ID.make("claude-sonnet-4-5")),
|
||||||
modelID: ModelV2.ID.make("claude-sonnet-4-5"),
|
modelID: ModelV2.ID.make("claude-sonnet-4-5"),
|
||||||
package: "aisdk:test-provider",
|
package: "aisdk:test-provider",
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -172,7 +172,7 @@ describe("GoogleVertexPlugin", () => {
|
|||||||
const provider = required(yield* catalog.provider.get(ProviderV2.ID.make("google-vertex")))
|
const provider = required(yield* catalog.provider.get(ProviderV2.ID.make("google-vertex")))
|
||||||
yield* aisdk.runSDK({
|
yield* aisdk.runSDK({
|
||||||
model: ModelV2.Info.make({
|
model: ModelV2.Info.make({
|
||||||
...ModelV2.Info.empty(ProviderV2.ID.make("google-vertex"), ModelV2.ID.make("gemini")),
|
...ModelV2.Info.default(ProviderV2.ID.make("google-vertex"), ModelV2.ID.make("gemini")),
|
||||||
modelID: ModelV2.ID.make("gemini"),
|
modelID: ModelV2.ID.make("gemini"),
|
||||||
package: "aisdk:@ai-sdk/google-vertex",
|
package: "aisdk:@ai-sdk/google-vertex",
|
||||||
}),
|
}),
|
||||||
@@ -294,7 +294,7 @@ describe("GoogleVertexPlugin", () => {
|
|||||||
yield* addPlugin()
|
yield* addPlugin()
|
||||||
yield* aisdk.runSDK({
|
yield* aisdk.runSDK({
|
||||||
model: ModelV2.Info.make({
|
model: ModelV2.Info.make({
|
||||||
...ModelV2.Info.empty(ProviderV2.ID.make("google-vertex"), ModelV2.ID.make("gemini")),
|
...ModelV2.Info.default(ProviderV2.ID.make("google-vertex"), ModelV2.ID.make("gemini")),
|
||||||
modelID: ModelV2.ID.make("gemini"),
|
modelID: ModelV2.ID.make("gemini"),
|
||||||
package: "aisdk:@ai-sdk/google-vertex",
|
package: "aisdk:@ai-sdk/google-vertex",
|
||||||
}),
|
}),
|
||||||
@@ -339,7 +339,7 @@ describe("GoogleVertexPlugin", () => {
|
|||||||
() =>
|
() =>
|
||||||
aisdk.runSDK({
|
aisdk.runSDK({
|
||||||
model: ModelV2.Info.make({
|
model: ModelV2.Info.make({
|
||||||
...ModelV2.Info.empty(ProviderV2.ID.make("google-vertex"), ModelV2.ID.make("gemini")),
|
...ModelV2.Info.default(ProviderV2.ID.make("google-vertex"), ModelV2.ID.make("gemini")),
|
||||||
modelID: ModelV2.ID.make("gemini"),
|
modelID: ModelV2.ID.make("gemini"),
|
||||||
package: "aisdk:@ai-sdk/openai-compatible",
|
package: "aisdk:@ai-sdk/openai-compatible",
|
||||||
}),
|
}),
|
||||||
@@ -367,7 +367,7 @@ describe("GoogleVertexPlugin", () => {
|
|||||||
yield* addPlugin()
|
yield* addPlugin()
|
||||||
yield* aisdk.runLanguage({
|
yield* aisdk.runLanguage({
|
||||||
model: ModelV2.Info.make({
|
model: ModelV2.Info.make({
|
||||||
...ModelV2.Info.empty(ProviderV2.ID.make("google-vertex"), ModelV2.ID.make(" gemini-2.5-pro ")),
|
...ModelV2.Info.default(ProviderV2.ID.make("google-vertex"), ModelV2.ID.make(" gemini-2.5-pro ")),
|
||||||
modelID: ModelV2.ID.make(" gemini-2.5-pro "),
|
modelID: ModelV2.ID.make(" gemini-2.5-pro "),
|
||||||
package: "aisdk:test-provider",
|
package: "aisdk:test-provider",
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ describe("GooglePlugin", () => {
|
|||||||
yield* addPlugin()
|
yield* addPlugin()
|
||||||
const result = yield* aisdk.runSDK({
|
const result = yield* aisdk.runSDK({
|
||||||
model: ModelV2.Info.make({
|
model: ModelV2.Info.make({
|
||||||
...ModelV2.Info.empty(ProviderV2.ID.make("custom-google"), ModelV2.ID.make("gemini")),
|
...ModelV2.Info.default(ProviderV2.ID.make("custom-google"), ModelV2.ID.make("gemini")),
|
||||||
modelID: ModelV2.ID.make("gemini"),
|
modelID: ModelV2.ID.make("gemini"),
|
||||||
package: "aisdk:@ai-sdk/google",
|
package: "aisdk:@ai-sdk/google",
|
||||||
}),
|
}),
|
||||||
@@ -45,7 +45,7 @@ describe("GooglePlugin", () => {
|
|||||||
yield* addPlugin()
|
yield* addPlugin()
|
||||||
const result = yield* aisdk.runSDK({
|
const result = yield* aisdk.runSDK({
|
||||||
model: ModelV2.Info.make({
|
model: ModelV2.Info.make({
|
||||||
...ModelV2.Info.empty(ProviderV2.ID.make("google"), ModelV2.ID.make("gemini")),
|
...ModelV2.Info.default(ProviderV2.ID.make("google"), ModelV2.ID.make("gemini")),
|
||||||
modelID: ModelV2.ID.make("gemini"),
|
modelID: ModelV2.ID.make("gemini"),
|
||||||
package: "aisdk:@ai-sdk/google",
|
package: "aisdk:@ai-sdk/google",
|
||||||
}),
|
}),
|
||||||
@@ -63,7 +63,7 @@ describe("GooglePlugin", () => {
|
|||||||
yield* addPlugin()
|
yield* addPlugin()
|
||||||
const sdkEvent = yield* aisdk.runSDK({
|
const sdkEvent = yield* aisdk.runSDK({
|
||||||
model: ModelV2.Info.make({
|
model: ModelV2.Info.make({
|
||||||
...ModelV2.Info.empty(ProviderV2.ID.make("custom-google"), ModelV2.ID.make("alias")),
|
...ModelV2.Info.default(ProviderV2.ID.make("custom-google"), ModelV2.ID.make("alias")),
|
||||||
modelID: ModelV2.ID.make("gemini-api"),
|
modelID: ModelV2.ID.make("gemini-api"),
|
||||||
package: "aisdk:@ai-sdk/google",
|
package: "aisdk:@ai-sdk/google",
|
||||||
}),
|
}),
|
||||||
@@ -88,7 +88,7 @@ describe("GooglePlugin", () => {
|
|||||||
|
|
||||||
const resolved = yield* aisdk.model(
|
const resolved = yield* aisdk.model(
|
||||||
ModelV2.Info.make({
|
ModelV2.Info.make({
|
||||||
...ModelV2.Info.empty(ProviderV2.ID.make("custom-google"), ModelV2.ID.make("alias")),
|
...ModelV2.Info.default(ProviderV2.ID.make("custom-google"), ModelV2.ID.make("alias")),
|
||||||
modelID: ModelV2.ID.make("gemini-api"),
|
modelID: ModelV2.ID.make("gemini-api"),
|
||||||
package: "aisdk:@ai-sdk/google",
|
package: "aisdk:@ai-sdk/google",
|
||||||
settings: { apiKey: "test" },
|
settings: { apiKey: "test" },
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ describe("OpenAICompatiblePlugin", () => {
|
|||||||
yield* addPlugin()
|
yield* addPlugin()
|
||||||
const defaulted = yield* aisdk.runSDK({
|
const defaulted = yield* aisdk.runSDK({
|
||||||
model: ModelV2.Info.make({
|
model: ModelV2.Info.make({
|
||||||
...ModelV2.Info.empty(ProviderV2.ID.make("custom"), ModelV2.ID.make("model")),
|
...ModelV2.Info.default(ProviderV2.ID.make("custom"), ModelV2.ID.make("model")),
|
||||||
modelID: ModelV2.ID.make("model"),
|
modelID: ModelV2.ID.make("model"),
|
||||||
package: "aisdk:test-provider",
|
package: "aisdk:test-provider",
|
||||||
}),
|
}),
|
||||||
@@ -35,7 +35,7 @@ describe("OpenAICompatiblePlugin", () => {
|
|||||||
})
|
})
|
||||||
const disabled = yield* aisdk.runSDK({
|
const disabled = yield* aisdk.runSDK({
|
||||||
model: ModelV2.Info.make({
|
model: ModelV2.Info.make({
|
||||||
...ModelV2.Info.empty(ProviderV2.ID.make("custom"), ModelV2.ID.make("model")),
|
...ModelV2.Info.default(ProviderV2.ID.make("custom"), ModelV2.ID.make("model")),
|
||||||
modelID: ModelV2.ID.make("model"),
|
modelID: ModelV2.ID.make("model"),
|
||||||
package: "aisdk:test-provider",
|
package: "aisdk:test-provider",
|
||||||
}),
|
}),
|
||||||
@@ -54,7 +54,7 @@ describe("OpenAICompatiblePlugin", () => {
|
|||||||
yield* addPlugin()
|
yield* addPlugin()
|
||||||
const result = yield* aisdk.runSDK({
|
const result = yield* aisdk.runSDK({
|
||||||
model: ModelV2.Info.make({
|
model: ModelV2.Info.make({
|
||||||
...ModelV2.Info.empty(ProviderV2.ID.make("custom"), ModelV2.ID.make("model")),
|
...ModelV2.Info.default(ProviderV2.ID.make("custom"), ModelV2.ID.make("model")),
|
||||||
modelID: ModelV2.ID.make("model"),
|
modelID: ModelV2.ID.make("model"),
|
||||||
package: "aisdk:test-provider",
|
package: "aisdk:test-provider",
|
||||||
}),
|
}),
|
||||||
@@ -78,7 +78,7 @@ describe("OpenAICompatiblePlugin", () => {
|
|||||||
)
|
)
|
||||||
yield* aisdk.runSDK({
|
yield* aisdk.runSDK({
|
||||||
model: ModelV2.Info.make({
|
model: ModelV2.Info.make({
|
||||||
...ModelV2.Info.empty(ProviderV2.ID.make("custom-provider"), ModelV2.ID.make("model")),
|
...ModelV2.Info.default(ProviderV2.ID.make("custom-provider"), ModelV2.ID.make("model")),
|
||||||
modelID: ModelV2.ID.make("model"),
|
modelID: ModelV2.ID.make("model"),
|
||||||
package: "aisdk:test-provider",
|
package: "aisdk:test-provider",
|
||||||
}),
|
}),
|
||||||
@@ -99,7 +99,7 @@ describe("OpenAICompatiblePlugin", () => {
|
|||||||
yield* addPlugin()
|
yield* addPlugin()
|
||||||
const result = yield* aisdk.runSDK({
|
const result = yield* aisdk.runSDK({
|
||||||
model: ModelV2.Info.make({
|
model: ModelV2.Info.make({
|
||||||
...ModelV2.Info.empty(ProviderV2.ID.make("cloudflare-workers-ai"), ModelV2.ID.make("model")),
|
...ModelV2.Info.default(ProviderV2.ID.make("cloudflare-workers-ai"), ModelV2.ID.make("model")),
|
||||||
modelID: ModelV2.ID.make("model"),
|
modelID: ModelV2.ID.make("model"),
|
||||||
package: "aisdk:test-provider",
|
package: "aisdk:test-provider",
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ describe("OpenAIPlugin", () => {
|
|||||||
yield* addPlugin()
|
yield* addPlugin()
|
||||||
const result = yield* aisdk.runSDK({
|
const result = yield* aisdk.runSDK({
|
||||||
model: ModelV2.Info.make({
|
model: ModelV2.Info.make({
|
||||||
...ModelV2.Info.empty(ProviderV2.ID.make("custom-openai"), ModelV2.ID.make("gpt-5")),
|
...ModelV2.Info.default(ProviderV2.ID.make("custom-openai"), ModelV2.ID.make("gpt-5")),
|
||||||
modelID: ModelV2.ID.make("gpt-5"),
|
modelID: ModelV2.ID.make("gpt-5"),
|
||||||
package: ProviderV2.aisdk("test-provider"),
|
package: ProviderV2.aisdk("test-provider"),
|
||||||
}),
|
}),
|
||||||
@@ -86,7 +86,7 @@ describe("OpenAIPlugin", () => {
|
|||||||
yield* addPlugin()
|
yield* addPlugin()
|
||||||
const result = yield* aisdk.runSDK({
|
const result = yield* aisdk.runSDK({
|
||||||
model: ModelV2.Info.make({
|
model: ModelV2.Info.make({
|
||||||
...ModelV2.Info.empty(ProviderV2.ID.openai, ModelV2.ID.make("gpt-5")),
|
...ModelV2.Info.default(ProviderV2.ID.openai, ModelV2.ID.make("gpt-5")),
|
||||||
modelID: ModelV2.ID.make("gpt-5"),
|
modelID: ModelV2.ID.make("gpt-5"),
|
||||||
package: ProviderV2.aisdk("test-provider"),
|
package: ProviderV2.aisdk("test-provider"),
|
||||||
}),
|
}),
|
||||||
@@ -105,7 +105,7 @@ describe("OpenAIPlugin", () => {
|
|||||||
yield* addPlugin()
|
yield* addPlugin()
|
||||||
const result = yield* aisdk.runLanguage({
|
const result = yield* aisdk.runLanguage({
|
||||||
model: ModelV2.Info.make({
|
model: ModelV2.Info.make({
|
||||||
...ModelV2.Info.empty(ProviderV2.ID.openai, ModelV2.ID.make("alias")),
|
...ModelV2.Info.default(ProviderV2.ID.openai, ModelV2.ID.make("alias")),
|
||||||
modelID: ModelV2.ID.make("gpt-5"),
|
modelID: ModelV2.ID.make("gpt-5"),
|
||||||
package: ProviderV2.aisdk("test-provider"),
|
package: ProviderV2.aisdk("test-provider"),
|
||||||
}),
|
}),
|
||||||
@@ -125,7 +125,7 @@ describe("OpenAIPlugin", () => {
|
|||||||
yield* addPlugin()
|
yield* addPlugin()
|
||||||
const result = yield* aisdk.runLanguage({
|
const result = yield* aisdk.runLanguage({
|
||||||
model: ModelV2.Info.make({
|
model: ModelV2.Info.make({
|
||||||
...ModelV2.Info.empty(ProviderV2.ID.anthropic, ModelV2.ID.make("gpt-5")),
|
...ModelV2.Info.default(ProviderV2.ID.anthropic, ModelV2.ID.make("gpt-5")),
|
||||||
modelID: ModelV2.ID.make("gpt-5"),
|
modelID: ModelV2.ID.make("gpt-5"),
|
||||||
package: ProviderV2.aisdk("test-provider"),
|
package: ProviderV2.aisdk("test-provider"),
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -293,7 +293,7 @@ describe("OpencodePlugin", () => {
|
|||||||
package: ProviderV2.aisdk("test-provider"),
|
package: ProviderV2.aisdk("test-provider"),
|
||||||
})
|
})
|
||||||
const model = ModelV2.Info.make({
|
const model = ModelV2.Info.make({
|
||||||
...ModelV2.Info.empty(provider.id, ModelV2.ID.make("paid")),
|
...ModelV2.Info.default(provider.id, ModelV2.ID.make("paid")),
|
||||||
modelID: ModelV2.ID.make("paid"),
|
modelID: ModelV2.ID.make("paid"),
|
||||||
package: ProviderV2.aisdk("test-provider"),
|
package: ProviderV2.aisdk("test-provider"),
|
||||||
cost: cost(1),
|
cost: cost(1),
|
||||||
@@ -320,7 +320,7 @@ describe("OpencodePlugin", () => {
|
|||||||
package: ProviderV2.aisdk("test-provider"),
|
package: ProviderV2.aisdk("test-provider"),
|
||||||
})
|
})
|
||||||
const model = ModelV2.Info.make({
|
const model = ModelV2.Info.make({
|
||||||
...ModelV2.Info.empty(provider.id, ModelV2.ID.make("free")),
|
...ModelV2.Info.default(provider.id, ModelV2.ID.make("free")),
|
||||||
modelID: ModelV2.ID.make("free"),
|
modelID: ModelV2.ID.make("free"),
|
||||||
package: ProviderV2.aisdk("test-provider"),
|
package: ProviderV2.aisdk("test-provider"),
|
||||||
cost: cost(0),
|
cost: cost(0),
|
||||||
@@ -347,7 +347,7 @@ describe("OpencodePlugin", () => {
|
|||||||
package: ProviderV2.aisdk("test-provider"),
|
package: ProviderV2.aisdk("test-provider"),
|
||||||
})
|
})
|
||||||
const model = ModelV2.Info.make({
|
const model = ModelV2.Info.make({
|
||||||
...ModelV2.Info.empty(provider.id, ModelV2.ID.make("output-only")),
|
...ModelV2.Info.default(provider.id, ModelV2.ID.make("output-only")),
|
||||||
modelID: ModelV2.ID.make("output-only"),
|
modelID: ModelV2.ID.make("output-only"),
|
||||||
package: ProviderV2.aisdk("test-provider"),
|
package: ProviderV2.aisdk("test-provider"),
|
||||||
cost: cost(0, 1),
|
cost: cost(0, 1),
|
||||||
@@ -376,7 +376,7 @@ describe("OpencodePlugin", () => {
|
|||||||
package: ProviderV2.aisdk("test-provider"),
|
package: ProviderV2.aisdk("test-provider"),
|
||||||
})
|
})
|
||||||
const model = ModelV2.Info.make({
|
const model = ModelV2.Info.make({
|
||||||
...ModelV2.Info.empty(provider.id, ModelV2.ID.make("paid")),
|
...ModelV2.Info.default(provider.id, ModelV2.ID.make("paid")),
|
||||||
modelID: ModelV2.ID.make("paid"),
|
modelID: ModelV2.ID.make("paid"),
|
||||||
package: ProviderV2.aisdk("test-provider"),
|
package: ProviderV2.aisdk("test-provider"),
|
||||||
cost: cost(1),
|
cost: cost(1),
|
||||||
@@ -410,7 +410,7 @@ describe("OpencodePlugin", () => {
|
|||||||
package: ProviderV2.aisdk("test-provider"),
|
package: ProviderV2.aisdk("test-provider"),
|
||||||
})
|
})
|
||||||
const model = ModelV2.Info.make({
|
const model = ModelV2.Info.make({
|
||||||
...ModelV2.Info.empty(provider.id, ModelV2.ID.make("paid")),
|
...ModelV2.Info.default(provider.id, ModelV2.ID.make("paid")),
|
||||||
modelID: ModelV2.ID.make("paid"),
|
modelID: ModelV2.ID.make("paid"),
|
||||||
package: ProviderV2.aisdk("test-provider"),
|
package: ProviderV2.aisdk("test-provider"),
|
||||||
cost: cost(1),
|
cost: cost(1),
|
||||||
@@ -438,7 +438,7 @@ describe("OpencodePlugin", () => {
|
|||||||
settings: { apiKey: "configured" },
|
settings: { apiKey: "configured" },
|
||||||
})
|
})
|
||||||
const model = ModelV2.Info.make({
|
const model = ModelV2.Info.make({
|
||||||
...ModelV2.Info.empty(provider.id, ModelV2.ID.make("paid")),
|
...ModelV2.Info.default(provider.id, ModelV2.ID.make("paid")),
|
||||||
modelID: ModelV2.ID.make("paid"),
|
modelID: ModelV2.ID.make("paid"),
|
||||||
package: ProviderV2.aisdk("test-provider"),
|
package: ProviderV2.aisdk("test-provider"),
|
||||||
cost: cost(1),
|
cost: cost(1),
|
||||||
@@ -468,7 +468,7 @@ describe("OpencodePlugin", () => {
|
|||||||
package: ProviderV2.aisdk("test-provider"),
|
package: ProviderV2.aisdk("test-provider"),
|
||||||
})
|
})
|
||||||
const model = ModelV2.Info.make({
|
const model = ModelV2.Info.make({
|
||||||
...ModelV2.Info.empty(provider.id, ModelV2.ID.make("paid")),
|
...ModelV2.Info.default(provider.id, ModelV2.ID.make("paid")),
|
||||||
modelID: ModelV2.ID.make("paid"),
|
modelID: ModelV2.ID.make("paid"),
|
||||||
package: ProviderV2.aisdk("test-provider"),
|
package: ProviderV2.aisdk("test-provider"),
|
||||||
cost: cost(1),
|
cost: cost(1),
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ describe("OpenRouterPlugin", () => {
|
|||||||
|
|
||||||
const ignored = yield* aisdk.runSDK({
|
const ignored = yield* aisdk.runSDK({
|
||||||
model: ModelV2.Info.make({
|
model: ModelV2.Info.make({
|
||||||
...ModelV2.Info.empty(ProviderV2.ID.openrouter, ModelV2.ID.make("openai/gpt-5")),
|
...ModelV2.Info.default(ProviderV2.ID.openrouter, ModelV2.ID.make("openai/gpt-5")),
|
||||||
modelID: ModelV2.ID.make("openai/gpt-5"),
|
modelID: ModelV2.ID.make("openai/gpt-5"),
|
||||||
package: ProviderV2.aisdk("test-provider"),
|
package: ProviderV2.aisdk("test-provider"),
|
||||||
}),
|
}),
|
||||||
@@ -65,7 +65,7 @@ describe("OpenRouterPlugin", () => {
|
|||||||
|
|
||||||
const result = yield* aisdk.runSDK({
|
const result = yield* aisdk.runSDK({
|
||||||
model: ModelV2.Info.make({
|
model: ModelV2.Info.make({
|
||||||
...ModelV2.Info.empty(ProviderV2.ID.make("custom"), ModelV2.ID.make("openai/gpt-5")),
|
...ModelV2.Info.default(ProviderV2.ID.make("custom"), ModelV2.ID.make("openai/gpt-5")),
|
||||||
modelID: ModelV2.ID.make("openai/gpt-5"),
|
modelID: ModelV2.ID.make("openai/gpt-5"),
|
||||||
package: ProviderV2.aisdk("test-provider"),
|
package: ProviderV2.aisdk("test-provider"),
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ function withEnv<A, E, R>(vars: Record<string, string | undefined>, effect: () =
|
|||||||
|
|
||||||
function model(providerID: string) {
|
function model(providerID: string) {
|
||||||
return ModelV2.Info.make({
|
return ModelV2.Info.make({
|
||||||
...ModelV2.Info.empty(ProviderV2.ID.make(providerID), ModelV2.ID.make("sap-model")),
|
...ModelV2.Info.default(ProviderV2.ID.make(providerID), ModelV2.ID.make("sap-model")),
|
||||||
modelID: ModelV2.ID.make("sap-model"),
|
modelID: ModelV2.ID.make("sap-model"),
|
||||||
package: ProviderV2.aisdk(fixtureProvider),
|
package: ProviderV2.aisdk(fixtureProvider),
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ describe("SnowflakeCortexPlugin", () => {
|
|||||||
yield* addPlugin()
|
yield* addPlugin()
|
||||||
const result = yield* aisdk.runSDK({
|
const result = yield* aisdk.runSDK({
|
||||||
model: ModelV2.Info.make({
|
model: ModelV2.Info.make({
|
||||||
...ModelV2.Info.empty(ProviderV2.ID.make("openai"), ModelV2.ID.make("gpt-4")),
|
...ModelV2.Info.default(ProviderV2.ID.make("openai"), ModelV2.ID.make("gpt-4")),
|
||||||
modelID: ModelV2.ID.make("gpt-4"),
|
modelID: ModelV2.ID.make("gpt-4"),
|
||||||
package: "aisdk:test-provider",
|
package: "aisdk:test-provider",
|
||||||
}),
|
}),
|
||||||
@@ -77,7 +77,7 @@ describe("SnowflakeCortexPlugin", () => {
|
|||||||
yield* addPlugin()
|
yield* addPlugin()
|
||||||
const result = yield* aisdk.runSDK({
|
const result = yield* aisdk.runSDK({
|
||||||
model: ModelV2.Info.make({
|
model: ModelV2.Info.make({
|
||||||
...ModelV2.Info.empty(ProviderV2.ID.make("snowflake-cortex"), ModelV2.ID.make("claude-sonnet-4-6")),
|
...ModelV2.Info.default(ProviderV2.ID.make("snowflake-cortex"), ModelV2.ID.make("claude-sonnet-4-6")),
|
||||||
modelID: ModelV2.ID.make("claude-sonnet-4-6"),
|
modelID: ModelV2.ID.make("claude-sonnet-4-6"),
|
||||||
package: "aisdk:test-provider",
|
package: "aisdk:test-provider",
|
||||||
}),
|
}),
|
||||||
@@ -97,7 +97,7 @@ describe("SnowflakeCortexPlugin", () => {
|
|||||||
yield* addPlugin()
|
yield* addPlugin()
|
||||||
const result = yield* aisdk.runSDK({
|
const result = yield* aisdk.runSDK({
|
||||||
model: ModelV2.Info.make({
|
model: ModelV2.Info.make({
|
||||||
...ModelV2.Info.empty(ProviderV2.ID.make("snowflake-cortex"), ModelV2.ID.make("claude-sonnet-4-6")),
|
...ModelV2.Info.default(ProviderV2.ID.make("snowflake-cortex"), ModelV2.ID.make("claude-sonnet-4-6")),
|
||||||
modelID: ModelV2.ID.make("claude-sonnet-4-6"),
|
modelID: ModelV2.ID.make("claude-sonnet-4-6"),
|
||||||
package: "aisdk:test-provider",
|
package: "aisdk:test-provider",
|
||||||
}),
|
}),
|
||||||
@@ -121,7 +121,7 @@ describe("SnowflakeCortexPlugin", () => {
|
|||||||
yield* addPlugin()
|
yield* addPlugin()
|
||||||
const result = yield* aisdk.runSDK({
|
const result = yield* aisdk.runSDK({
|
||||||
model: ModelV2.Info.make({
|
model: ModelV2.Info.make({
|
||||||
...ModelV2.Info.empty(ProviderV2.ID.make("snowflake-cortex"), ModelV2.ID.make("claude-sonnet-4-6")),
|
...ModelV2.Info.default(ProviderV2.ID.make("snowflake-cortex"), ModelV2.ID.make("claude-sonnet-4-6")),
|
||||||
modelID: ModelV2.ID.make("claude-sonnet-4-6"),
|
modelID: ModelV2.ID.make("claude-sonnet-4-6"),
|
||||||
package: "aisdk:test-provider",
|
package: "aisdk:test-provider",
|
||||||
}),
|
}),
|
||||||
@@ -141,7 +141,7 @@ describe("SnowflakeCortexPlugin", () => {
|
|||||||
yield* addPlugin()
|
yield* addPlugin()
|
||||||
const result = yield* aisdk.runSDK({
|
const result = yield* aisdk.runSDK({
|
||||||
model: ModelV2.Info.make({
|
model: ModelV2.Info.make({
|
||||||
...ModelV2.Info.empty(ProviderV2.ID.make("snowflake-cortex"), ModelV2.ID.make("claude-sonnet-4-6")),
|
...ModelV2.Info.default(ProviderV2.ID.make("snowflake-cortex"), ModelV2.ID.make("claude-sonnet-4-6")),
|
||||||
modelID: ModelV2.ID.make("claude-sonnet-4-6"),
|
modelID: ModelV2.ID.make("claude-sonnet-4-6"),
|
||||||
package: "aisdk:test-provider",
|
package: "aisdk:test-provider",
|
||||||
}),
|
}),
|
||||||
@@ -165,7 +165,7 @@ describe("SnowflakeCortexPlugin", () => {
|
|||||||
yield* addPlugin()
|
yield* addPlugin()
|
||||||
const result = yield* aisdk.runSDK({
|
const result = yield* aisdk.runSDK({
|
||||||
model: ModelV2.Info.make({
|
model: ModelV2.Info.make({
|
||||||
...ModelV2.Info.empty(ProviderV2.ID.make("snowflake-cortex"), ModelV2.ID.make("claude-sonnet-4-6")),
|
...ModelV2.Info.default(ProviderV2.ID.make("snowflake-cortex"), ModelV2.ID.make("claude-sonnet-4-6")),
|
||||||
modelID: ModelV2.ID.make("claude-sonnet-4-6"),
|
modelID: ModelV2.ID.make("claude-sonnet-4-6"),
|
||||||
package: "aisdk:test-provider",
|
package: "aisdk:test-provider",
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ describe("VercelPlugin", () => {
|
|||||||
yield* addPlugin()
|
yield* addPlugin()
|
||||||
const event = yield* aisdk.runSDK({
|
const event = yield* aisdk.runSDK({
|
||||||
model: ModelV2.Info.make({
|
model: ModelV2.Info.make({
|
||||||
...ModelV2.Info.empty(ProviderV2.ID.make("custom-vercel"), ModelV2.ID.make("v0-1.0-md")),
|
...ModelV2.Info.default(ProviderV2.ID.make("custom-vercel"), ModelV2.ID.make("v0-1.0-md")),
|
||||||
modelID: ModelV2.ID.make("v0-1.0-md"),
|
modelID: ModelV2.ID.make("v0-1.0-md"),
|
||||||
package: "aisdk:@ai-sdk/vercel",
|
package: "aisdk:@ai-sdk/vercel",
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ describe("XAIPlugin", () => {
|
|||||||
|
|
||||||
const ignored = yield* aisdk.runSDK({
|
const ignored = yield* aisdk.runSDK({
|
||||||
model: ModelV2.Info.make({
|
model: ModelV2.Info.make({
|
||||||
...ModelV2.Info.empty(ProviderV2.ID.make("xai"), ModelV2.ID.make("grok-4")),
|
...ModelV2.Info.default(ProviderV2.ID.make("xai"), ModelV2.ID.make("grok-4")),
|
||||||
modelID: ModelV2.ID.make("grok-4"),
|
modelID: ModelV2.ID.make("grok-4"),
|
||||||
package: "aisdk:@ai-sdk/xai",
|
package: "aisdk:@ai-sdk/xai",
|
||||||
}),
|
}),
|
||||||
@@ -72,7 +72,7 @@ describe("XAIPlugin", () => {
|
|||||||
|
|
||||||
const result = yield* aisdk.runSDK({
|
const result = yield* aisdk.runSDK({
|
||||||
model: ModelV2.Info.make({
|
model: ModelV2.Info.make({
|
||||||
...ModelV2.Info.empty(ProviderV2.ID.make("xai"), ModelV2.ID.make("grok-4")),
|
...ModelV2.Info.default(ProviderV2.ID.make("xai"), ModelV2.ID.make("grok-4")),
|
||||||
modelID: ModelV2.ID.make("grok-4"),
|
modelID: ModelV2.ID.make("grok-4"),
|
||||||
package: "aisdk:@ai-sdk/xai",
|
package: "aisdk:@ai-sdk/xai",
|
||||||
}),
|
}),
|
||||||
@@ -92,7 +92,7 @@ describe("XAIPlugin", () => {
|
|||||||
|
|
||||||
const result = yield* aisdk.runSDK({
|
const result = yield* aisdk.runSDK({
|
||||||
model: ModelV2.Info.make({
|
model: ModelV2.Info.make({
|
||||||
...ModelV2.Info.empty(ProviderV2.ID.make("custom-xai"), ModelV2.ID.make("grok-4")),
|
...ModelV2.Info.default(ProviderV2.ID.make("custom-xai"), ModelV2.ID.make("grok-4")),
|
||||||
modelID: ModelV2.ID.make("grok-4"),
|
modelID: ModelV2.ID.make("grok-4"),
|
||||||
package: "aisdk:@ai-sdk/xai",
|
package: "aisdk:@ai-sdk/xai",
|
||||||
}),
|
}),
|
||||||
@@ -112,7 +112,7 @@ describe("XAIPlugin", () => {
|
|||||||
yield* addPlugin()
|
yield* addPlugin()
|
||||||
const result = yield* aisdk.runLanguage({
|
const result = yield* aisdk.runLanguage({
|
||||||
model: ModelV2.Info.make({
|
model: ModelV2.Info.make({
|
||||||
...ModelV2.Info.empty(ProviderV2.ID.make("xai"), ModelV2.ID.make("alias")),
|
...ModelV2.Info.default(ProviderV2.ID.make("xai"), ModelV2.ID.make("alias")),
|
||||||
modelID: ModelV2.ID.make("grok-4"),
|
modelID: ModelV2.ID.make("grok-4"),
|
||||||
package: "aisdk:@ai-sdk/xai",
|
package: "aisdk:@ai-sdk/xai",
|
||||||
}),
|
}),
|
||||||
@@ -133,7 +133,7 @@ describe("XAIPlugin", () => {
|
|||||||
yield* addPlugin()
|
yield* addPlugin()
|
||||||
const result = yield* aisdk.runLanguage({
|
const result = yield* aisdk.runLanguage({
|
||||||
model: ModelV2.Info.make({
|
model: ModelV2.Info.make({
|
||||||
...ModelV2.Info.empty(ProviderV2.ID.openai, ModelV2.ID.make("grok-4")),
|
...ModelV2.Info.default(ProviderV2.ID.openai, ModelV2.ID.make("grok-4")),
|
||||||
modelID: ModelV2.ID.make("grok-4"),
|
modelID: ModelV2.ID.make("grok-4"),
|
||||||
package: "aisdk:@ai-sdk/xai",
|
package: "aisdk:@ai-sdk/xai",
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -170,8 +170,8 @@ test("Core reuses the canonical shared schemas", async () => {
|
|||||||
for (const [core, shared] of schemas) expect(core).toBe(shared)
|
for (const [core, shared] of schemas) expect(core).toBe(shared)
|
||||||
|
|
||||||
expect(Agent.Info.empty(Agent.ID.make("test"))).toEqual(AgentV2.Info.empty(AgentV2.ID.make("test")))
|
expect(Agent.Info.empty(Agent.ID.make("test"))).toEqual(AgentV2.Info.empty(AgentV2.ID.make("test")))
|
||||||
expect(Model.Info.empty(Provider.ID.make("test"), Model.ID.make("model"))).toEqual(
|
expect(Model.Info.default(Provider.ID.make("test"), Model.ID.make("model"))).toEqual(
|
||||||
ModelV2.Info.empty(ProviderV2.ID.make("test"), ModelV2.ID.make("model")),
|
ModelV2.Info.default(ProviderV2.ID.make("test"), ModelV2.ID.make("model")),
|
||||||
)
|
)
|
||||||
expect(Provider.Info.empty(Provider.ID.make("test"))).toEqual(ProviderV2.Info.empty(ProviderV2.ID.make("test")))
|
expect(Provider.Info.empty(Provider.ID.make("test"))).toEqual(ProviderV2.Info.empty(ProviderV2.ID.make("test")))
|
||||||
expect(Skill.Source.key(Skill.DirectorySource.make({ type: "directory", path: AbsolutePath.make("/tmp") }))).toBe(
|
expect(Skill.Source.key(Skill.DirectorySource.make({ type: "directory", path: AbsolutePath.make("/tmp") }))).toBe(
|
||||||
|
|||||||
@@ -94,9 +94,10 @@ You can also map a friendly catalog ID to a different API model ID with `modelID
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Here `openai/coding-default` is the selectable catalog reference, while `gpt-5.2` is sent to the provider. When adding a
|
Here `openai/coding-default` is the selectable catalog reference, while `gpt-5.2` is sent to the provider. A model that is
|
||||||
model that is not already in the catalog, set accurate `capabilities` and `limit` values so OpenCode can expose tools and
|
not already in the catalog defaults to tool support, text and image input, and text output. Set accurate `capabilities`
|
||||||
enforce the correct context limits. Set `disabled: true` on a model entry to hide it from the available catalog.
|
and `limit` values when those defaults do not match the model or OpenCode needs to enforce its context limits. Set
|
||||||
|
`disabled: true` on a model entry to hide it from the available catalog.
|
||||||
|
|
||||||
OpenAI-compatible models that stream reasoning through a custom assistant-message field can set
|
OpenAI-compatible models that stream reasoning through a custom assistant-message field can set
|
||||||
`compatibility.reasoningField`:
|
`compatibility.reasoningField`:
|
||||||
@@ -193,8 +194,9 @@ For an OpenAI-compatible server, define a provider package, endpoint, and at lea
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Use the server's real model name, limits, modalities, and tool support. OpenCode cannot infer these for a model you add
|
Use the server's real model name, limits, modalities, and tool support. OpenCode applies the custom-model capability
|
||||||
manually. If the endpoint requires a key, add `apiKey` to provider `settings` using an environment substitution such as
|
defaults described above but cannot infer the server's actual limits or whether those defaults are accurate. If the
|
||||||
|
endpoint requires a key, add `apiKey` to provider `settings` using an environment substitution such as
|
||||||
`"apiKey": "{env:LOCAL_API_KEY}"`; do not commit secrets.
|
`"apiKey": "{env:LOCAL_API_KEY}"`; do not commit secrets.
|
||||||
|
|
||||||
### Model references
|
### Model references
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import { Schema, SchemaAST } from "effect"
|
import { Schema, SchemaAST } from "effect"
|
||||||
import { format } from "prettier"
|
import { format } from "prettier"
|
||||||
import { ThemeDefinition, ThemeFile } from "../../tui/src/theme/v2/schema"
|
import { ThemeDefinition, ThemeDocument } from "../../tui/src/theme/v2/schema"
|
||||||
|
|
||||||
const target = import.meta.dir + "/../snippets/generated/theme-tokens.mdx"
|
const target = import.meta.dir + "/../snippets/generated/theme-tokens.mdx"
|
||||||
const root = requireObject(ThemeDefinition.ast)
|
const root = requireObject(ThemeDefinition.ast)
|
||||||
@@ -52,8 +52,8 @@ const example = {
|
|||||||
default: "#101014",
|
default: "#101014",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
} satisfies ThemeFile
|
} satisfies ThemeDocument
|
||||||
Schema.decodeUnknownSync(ThemeFile)(example)
|
Schema.decodeUnknownSync(ThemeDocument)(example)
|
||||||
const output = await format(
|
const output = await format(
|
||||||
`{/* Generated by packages/docs/script/generate-theme-tokens.ts. Do not edit. */}
|
`{/* Generated by packages/docs/script/generate-theme-tokens.ts. Do not edit. */}
|
||||||
|
|
||||||
|
|||||||
@@ -30,7 +30,6 @@ import { CredentialGroup } from "./groups/credential.js"
|
|||||||
import { ProjectGroup } from "./groups/project.js"
|
import { ProjectGroup } from "./groups/project.js"
|
||||||
import { ProjectCopyGroup } from "./groups/project-copy.js"
|
import { ProjectCopyGroup } from "./groups/project-copy.js"
|
||||||
import { VcsGroup } from "./groups/vcs.js"
|
import { VcsGroup } from "./groups/vcs.js"
|
||||||
import { PathGroup } from "./groups/path.js"
|
|
||||||
|
|
||||||
type LocationGroups<LocationId extends HttpApiMiddleware.AnyId> =
|
type LocationGroups<LocationId extends HttpApiMiddleware.AnyId> =
|
||||||
| HttpApiGroup.AddMiddleware<typeof LocationGroup, LocationId>
|
| HttpApiGroup.AddMiddleware<typeof LocationGroup, LocationId>
|
||||||
@@ -51,7 +50,6 @@ type LocationGroups<LocationId extends HttpApiMiddleware.AnyId> =
|
|||||||
| HttpApiGroup.AddMiddleware<typeof ReferenceGroup, LocationId>
|
| HttpApiGroup.AddMiddleware<typeof ReferenceGroup, LocationId>
|
||||||
| HttpApiGroup.AddMiddleware<typeof ProjectCopyGroup, LocationId>
|
| HttpApiGroup.AddMiddleware<typeof ProjectCopyGroup, LocationId>
|
||||||
| HttpApiGroup.AddMiddleware<typeof VcsGroup, LocationId>
|
| HttpApiGroup.AddMiddleware<typeof VcsGroup, LocationId>
|
||||||
| HttpApiGroup.AddMiddleware<typeof PathGroup, LocationId>
|
|
||||||
|
|
||||||
type SessionGroups<SessionLocationId extends HttpApiMiddleware.AnyId, SessionLocationService> =
|
type SessionGroups<SessionLocationId extends HttpApiMiddleware.AnyId, SessionLocationService> =
|
||||||
| ReturnType<typeof makeSessionGroup<SessionLocationId, SessionLocationService>>
|
| ReturnType<typeof makeSessionGroup<SessionLocationId, SessionLocationService>>
|
||||||
@@ -170,7 +168,6 @@ const makeApiFromGroup = <
|
|||||||
.add(ReferenceGroup.middleware(locationMiddleware))
|
.add(ReferenceGroup.middleware(locationMiddleware))
|
||||||
.add(ProjectCopyGroup.middleware(locationMiddleware))
|
.add(ProjectCopyGroup.middleware(locationMiddleware))
|
||||||
.add(VcsGroup.middleware(locationMiddleware))
|
.add(VcsGroup.middleware(locationMiddleware))
|
||||||
.add(PathGroup.middleware(locationMiddleware))
|
|
||||||
.add(DebugGroup)
|
.add(DebugGroup)
|
||||||
.annotateMerge(
|
.annotateMerge(
|
||||||
OpenApi.annotations({
|
OpenApi.annotations({
|
||||||
|
|||||||
@@ -60,7 +60,6 @@ export const groupNames = {
|
|||||||
"server.project": "project",
|
"server.project": "project",
|
||||||
"server.projectCopy": "projectCopy",
|
"server.projectCopy": "projectCopy",
|
||||||
"server.vcs": "vcs",
|
"server.vcs": "vcs",
|
||||||
"server.path": "path",
|
|
||||||
} as const
|
} as const
|
||||||
|
|
||||||
export const promiseOmitEndpoints = new Set(["pty.connect", "pty.connectToken"])
|
export const promiseOmitEndpoints = new Set(["pty.connect", "pty.connectToken"])
|
||||||
|
|||||||
@@ -1,20 +0,0 @@
|
|||||||
import { Path } from "@opencode-ai/schema/path"
|
|
||||||
import { HttpApiEndpoint, HttpApiGroup, OpenApi } from "effect/unstable/httpapi"
|
|
||||||
import { LocationQuery, locationQueryOpenApi } from "./location.js"
|
|
||||||
|
|
||||||
export const PathGroup = HttpApiGroup.make("server.path")
|
|
||||||
.add(
|
|
||||||
HttpApiEndpoint.get("path.get", "/api/path", {
|
|
||||||
query: LocationQuery,
|
|
||||||
success: Path.Info,
|
|
||||||
})
|
|
||||||
.annotateMerge(locationQueryOpenApi)
|
|
||||||
.annotateMerge(
|
|
||||||
OpenApi.annotations({
|
|
||||||
identifier: "v2.path.get",
|
|
||||||
summary: "Get paths",
|
|
||||||
description: "Get process and location paths.",
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.annotateMerge(OpenApi.annotations({ title: "path", description: "Location and process paths." }))
|
|
||||||
@@ -16,7 +16,6 @@ export const Info = Schema.Struct({
|
|||||||
agent: Agent.ID.pipe(optional),
|
agent: Agent.ID.pipe(optional),
|
||||||
model: Model.Ref.pipe(optional),
|
model: Model.Ref.pipe(optional),
|
||||||
subtask: Schema.Boolean.pipe(optional),
|
subtask: Schema.Boolean.pipe(optional),
|
||||||
source: Schema.Literals(["command", "mcp"]).pipe(optional),
|
|
||||||
}).annotate({ identifier: "Command.Info" })
|
}).annotate({ identifier: "Command.Info" })
|
||||||
|
|
||||||
export const Event = {
|
export const Event = {
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ export { Model } from "./model.js"
|
|||||||
export { Money } from "./money.js"
|
export { Money } from "./money.js"
|
||||||
export { Permission } from "./permission.js"
|
export { Permission } from "./permission.js"
|
||||||
export { PermissionSaved } from "./permission-saved.js"
|
export { PermissionSaved } from "./permission-saved.js"
|
||||||
export { Path } from "./path.js"
|
|
||||||
export { Project } from "./project.js"
|
export { Project } from "./project.js"
|
||||||
export { ProjectCopy } from "./project-copy.js"
|
export { ProjectCopy } from "./project-copy.js"
|
||||||
export { Provider } from "./provider.js"
|
export { Provider } from "./provider.js"
|
||||||
|
|||||||
@@ -106,13 +106,13 @@ export const Info = Schema.Struct({
|
|||||||
.annotate({ identifier: "Model.Info" })
|
.annotate({ identifier: "Model.Info" })
|
||||||
.pipe(
|
.pipe(
|
||||||
statics(() => ({
|
statics(() => ({
|
||||||
empty: (providerID: Provider.ID, id: ID) =>
|
default: (providerID: Provider.ID, id: ID) =>
|
||||||
({
|
({
|
||||||
id,
|
id,
|
||||||
modelID: id,
|
modelID: id,
|
||||||
providerID,
|
providerID,
|
||||||
name: id,
|
name: id,
|
||||||
capabilities: { tools: false, input: [], output: [] },
|
capabilities: { tools: true, input: ["text", "image"], output: ["text"] },
|
||||||
variants: [],
|
variants: [],
|
||||||
time: { released: 0 },
|
time: { released: 0 },
|
||||||
cost: [],
|
cost: [],
|
||||||
|
|||||||
@@ -1,13 +0,0 @@
|
|||||||
export * as Path from "./path.js"
|
|
||||||
|
|
||||||
import { Schema } from "effect"
|
|
||||||
import { AbsolutePath } from "./schema.js"
|
|
||||||
|
|
||||||
export const Info = Schema.Struct({
|
|
||||||
home: AbsolutePath,
|
|
||||||
state: AbsolutePath,
|
|
||||||
config: AbsolutePath,
|
|
||||||
worktree: AbsolutePath,
|
|
||||||
directory: AbsolutePath,
|
|
||||||
}).annotate({ identifier: "Path.Info" })
|
|
||||||
export interface Info extends Schema.Schema.Type<typeof Info> {}
|
|
||||||
@@ -83,7 +83,7 @@ describe("contract hygiene", () => {
|
|||||||
|
|
||||||
test("model defaults and provider overlays preserve public invariants", () => {
|
test("model defaults and provider overlays preserve public invariants", () => {
|
||||||
const id = Model.ID.make("model")
|
const id = Model.ID.make("model")
|
||||||
expect(Model.Info.empty(Provider.ID.make("provider"), id)).toMatchObject({ modelID: id, variants: [] })
|
expect(Model.Info.default(Provider.ID.make("provider"), id)).toMatchObject({ modelID: id, variants: [] })
|
||||||
expect(() =>
|
expect(() =>
|
||||||
Schema.decodeUnknownSync(Provider.Info)({
|
Schema.decodeUnknownSync(Provider.Info)({
|
||||||
id: "provider",
|
id: "provider",
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ import { CredentialHandler } from "./handlers/credential"
|
|||||||
import { ProjectHandler } from "./handlers/project"
|
import { ProjectHandler } from "./handlers/project"
|
||||||
import { ProjectCopyHandler } from "./handlers/project-copy"
|
import { ProjectCopyHandler } from "./handlers/project-copy"
|
||||||
import { VcsHandler } from "./handlers/vcs"
|
import { VcsHandler } from "./handlers/vcs"
|
||||||
import { PathHandler } from "./handlers/path"
|
|
||||||
import { EventFeed } from "./event-feed"
|
import { EventFeed } from "./event-feed"
|
||||||
|
|
||||||
export const handlers = Layer.mergeAll(
|
export const handlers = Layer.mergeAll(
|
||||||
@@ -57,5 +56,4 @@ export const handlers = Layer.mergeAll(
|
|||||||
ReferenceHandler,
|
ReferenceHandler,
|
||||||
ProjectCopyHandler,
|
ProjectCopyHandler,
|
||||||
VcsHandler,
|
VcsHandler,
|
||||||
PathHandler,
|
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,22 +0,0 @@
|
|||||||
import { Location } from "@opencode-ai/core/location"
|
|
||||||
import { AbsolutePath } from "@opencode-ai/core/schema"
|
|
||||||
import { Global } from "@opencode-ai/util/global"
|
|
||||||
import { Effect } from "effect"
|
|
||||||
import { HttpApiBuilder } from "effect/unstable/httpapi"
|
|
||||||
import { Api } from "../api"
|
|
||||||
|
|
||||||
export const PathHandler = HttpApiBuilder.group(Api, "server.path", (handlers) =>
|
|
||||||
handlers.handle("path.get", () =>
|
|
||||||
Effect.gen(function* () {
|
|
||||||
const global = yield* Global.Service
|
|
||||||
const location = yield* Location.Service
|
|
||||||
return {
|
|
||||||
home: AbsolutePath.make(global.home),
|
|
||||||
state: AbsolutePath.make(global.state),
|
|
||||||
config: AbsolutePath.make(global.config),
|
|
||||||
worktree: location.project.directory,
|
|
||||||
directory: location.directory,
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
@@ -60,7 +60,6 @@ const applicationServices = LayerNode.group([
|
|||||||
WellKnown.node,
|
WellKnown.node,
|
||||||
PtyEnvironment.node,
|
PtyEnvironment.node,
|
||||||
LocationServiceMap.node,
|
LocationServiceMap.node,
|
||||||
Global.node,
|
|
||||||
SessionRestart.node,
|
SessionRestart.node,
|
||||||
])
|
])
|
||||||
|
|
||||||
@@ -135,9 +134,7 @@ function makeRoutes<AuthError, AuthServices>(
|
|||||||
Layer.flatMap((context) => {
|
Layer.flatMap((context) => {
|
||||||
const services = Layer.succeedContext(context)
|
const services = Layer.succeedContext(context)
|
||||||
const requestServices = Layer.merge(
|
const requestServices = Layer.merge(
|
||||||
Layer.succeedContext(
|
Layer.succeedContext(Context.pick(PermissionSaved.Service, Project.Service, WellKnown.Service)(context)),
|
||||||
Context.pick(Global.Service, PermissionSaved.Service, Project.Service, WellKnown.Service)(context),
|
|
||||||
),
|
|
||||||
ServerInfo.layer(serviceURLs, options.app),
|
ServerInfo.layer(serviceURLs, options.app),
|
||||||
)
|
)
|
||||||
return HttpApiBuilder.layer(Api, { openapiPath: "/openapi.json" }).pipe(
|
return HttpApiBuilder.layer(Api, { openapiPath: "/openapi.json" }).pipe(
|
||||||
|
|||||||
@@ -65,6 +65,7 @@ import { DialogThemeList } from "./component/dialog-theme-list"
|
|||||||
import { DialogHelp } from "./ui/dialog-help"
|
import { DialogHelp } from "./ui/dialog-help"
|
||||||
import { DialogAgent } from "./component/dialog-agent"
|
import { DialogAgent } from "./component/dialog-agent"
|
||||||
import { DialogSessionList } from "./component/dialog-session-list"
|
import { DialogSessionList } from "./component/dialog-session-list"
|
||||||
|
import { ThemeErrorToast } from "./component/theme-error-toast"
|
||||||
import { ThemeProvider, useTheme } from "./context/theme"
|
import { ThemeProvider, useTheme } from "./context/theme"
|
||||||
import { Home } from "./routes/home"
|
import { Home } from "./routes/home"
|
||||||
import { Session } from "./routes/session"
|
import { Session } from "./routes/session"
|
||||||
@@ -337,6 +338,7 @@ export const run = Effect.fn("Tui.run")(function* (input: TuiInput) {
|
|||||||
<DataProvider>
|
<DataProvider>
|
||||||
<LocationProvider>
|
<LocationProvider>
|
||||||
<ThemeProvider mode={mode}>
|
<ThemeProvider mode={mode}>
|
||||||
|
<ThemeErrorToast />
|
||||||
<LocalProvider>
|
<LocalProvider>
|
||||||
<PromptStashProvider>
|
<PromptStashProvider>
|
||||||
<DialogProvider>
|
<DialogProvider>
|
||||||
|
|||||||
@@ -59,6 +59,7 @@ export function DevToolsBar() {
|
|||||||
const canSwitchMode = () => supports(nextMode())
|
const canSwitchMode = () => supports(nextMode())
|
||||||
const runtime = createMemo(() => runtimeStatus(frontendSamples()))
|
const runtime = createMemo(() => runtimeStatus(frontendSamples()))
|
||||||
const timing = () => config.data.debug?.timing ?? false
|
const timing = () => config.data.debug?.timing ?? false
|
||||||
|
const turnTokens = () => config.data.debug?.turn_tokens ?? false
|
||||||
|
|
||||||
const offEscape = keymap.intercept(
|
const offEscape = keymap.intercept(
|
||||||
"key",
|
"key",
|
||||||
@@ -352,6 +353,16 @@ export function DevToolsBar() {
|
|||||||
>
|
>
|
||||||
{timing() ? "[x]" : "[ ]"} Time to first draw
|
{timing() ? "[x]" : "[ ]"} Time to first draw
|
||||||
</Action>
|
</Action>
|
||||||
|
<Action
|
||||||
|
onClick={() =>
|
||||||
|
void config.update((draft) => {
|
||||||
|
draft.debug = { ...draft.debug, turn_tokens: !turnTokens() }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
hoverBackground
|
||||||
|
>
|
||||||
|
{turnTokens() ? "[x]" : "[ ]"} Turn token usage
|
||||||
|
</Action>
|
||||||
</box>
|
</box>
|
||||||
<For each={groups()}>
|
<For each={groups()}>
|
||||||
{(group) => (
|
{(group) => (
|
||||||
@@ -403,7 +414,7 @@ function PanelBox(props: ParentProps) {
|
|||||||
position="absolute"
|
position="absolute"
|
||||||
zIndex={2600}
|
zIndex={2600}
|
||||||
bottom={1}
|
bottom={1}
|
||||||
left={0}
|
left={-1}
|
||||||
width={42}
|
width={42}
|
||||||
paddingLeft={2}
|
paddingLeft={2}
|
||||||
paddingRight={2}
|
paddingRight={2}
|
||||||
|
|||||||
@@ -222,14 +222,6 @@ const settings: Setting[] = [
|
|||||||
values: [false, true],
|
values: [false, true],
|
||||||
labels: ["off", "on"],
|
labels: ["off", "on"],
|
||||||
},
|
},
|
||||||
{
|
|
||||||
title: "DevTools: Timing",
|
|
||||||
category: "Debug",
|
|
||||||
path: ["debug", "timing"],
|
|
||||||
default: true,
|
|
||||||
values: [false, true],
|
|
||||||
labels: ["off", "on"],
|
|
||||||
},
|
|
||||||
]
|
]
|
||||||
|
|
||||||
export function DialogConfig() {
|
export function DialogConfig() {
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import { onCleanup } from "solid-js"
|
||||||
|
import { useTheme } from "../context/theme"
|
||||||
|
import { useToast } from "../ui/toast"
|
||||||
|
|
||||||
|
export function ThemeErrorToast() {
|
||||||
|
const theme = useTheme()
|
||||||
|
const toast = useToast()
|
||||||
|
|
||||||
|
onCleanup(
|
||||||
|
theme.onError(({ name, error }) =>
|
||||||
|
toast.show({
|
||||||
|
variant: "error",
|
||||||
|
title: `Failed to load theme: ${name}`,
|
||||||
|
message: error.message,
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
@@ -142,6 +142,12 @@ export const Info = Schema.Struct({
|
|||||||
mono: Schema.optional(Schema.Boolean).annotate({
|
mono: Schema.optional(Schema.Boolean).annotate({
|
||||||
description: "Use monochrome ASCII output",
|
description: "Use monochrome ASCII output",
|
||||||
}),
|
}),
|
||||||
|
replay: Schema.optional(Schema.Boolean).annotate({
|
||||||
|
description: "Restore session history on resume and terminal resize",
|
||||||
|
}),
|
||||||
|
replay_limit: Schema.optional(Schema.Int.check(Schema.isGreaterThan(0))).annotate({
|
||||||
|
description: "Maximum number of newest messages restored during replay",
|
||||||
|
}),
|
||||||
}),
|
}),
|
||||||
).annotate({ description: "Mini transcript presentation settings" }),
|
).annotate({ description: "Mini transcript presentation settings" }),
|
||||||
hints: Schema.optional(
|
hints: Schema.optional(
|
||||||
@@ -153,6 +159,7 @@ export const Info = Schema.Struct({
|
|||||||
Schema.Struct({
|
Schema.Struct({
|
||||||
devtools: Schema.optional(Schema.Boolean).annotate({ description: "Show the DevTools debug bar" }),
|
devtools: Schema.optional(Schema.Boolean).annotate({ description: "Show the DevTools debug bar" }),
|
||||||
timing: Schema.optional(Schema.Boolean).annotate({ description: "Show time-to-first-draw diagnostics" }),
|
timing: Schema.optional(Schema.Boolean).annotate({ description: "Show time-to-first-draw diagnostics" }),
|
||||||
|
turn_tokens: Schema.optional(Schema.Boolean).annotate({ description: "Show per-turn token usage diagnostics" }),
|
||||||
}),
|
}),
|
||||||
).annotate({ description: "Debugging settings" }),
|
).annotate({ description: "Debugging settings" }),
|
||||||
animations: Schema.optional(Schema.Boolean).annotate({ description: "Enable interface animations" }),
|
animations: Schema.optional(Schema.Boolean).annotate({ description: "Enable interface animations" }),
|
||||||
|
|||||||
@@ -5,21 +5,20 @@ import {
|
|||||||
addTheme,
|
addTheme,
|
||||||
allThemes,
|
allThemes,
|
||||||
hasTheme,
|
hasTheme,
|
||||||
isTheme,
|
parseTheme,
|
||||||
selectedForeground,
|
selectedForeground,
|
||||||
setCustomThemes,
|
setCustomThemes,
|
||||||
setSystemTheme,
|
setSystemTheme,
|
||||||
subscribeThemes,
|
subscribeThemes,
|
||||||
upsertTheme,
|
upsertTheme,
|
||||||
type Theme,
|
type Theme,
|
||||||
type ThemeJson,
|
type ThemeDocumentSource,
|
||||||
} from "../theme"
|
} from "../theme"
|
||||||
import { generateSyntax } from "../theme/v2/syntax"
|
import { generateSyntax } from "../theme/v2/syntax"
|
||||||
import { generateSystem, terminalMode } from "../theme/system"
|
import { generateSystem, terminalMode } from "../theme/system"
|
||||||
import { discoverThemes, themeDirectories } from "../theme/discovery"
|
import { discoverThemes, themeDirectories } from "../theme/discovery"
|
||||||
import { createComponentTheme, type ComponentTheme } from "../theme/v2/component"
|
import { createComponentTheme, type ComponentTheme } from "../theme/v2/component"
|
||||||
import { resolveThemeFile } from "../theme/v2/resolve"
|
import { resolveThemeDocument } from "../theme/v2/resolve"
|
||||||
import { migrateV1 } from "../theme/v2/v1-migrate"
|
|
||||||
import { themeModes } from "../theme/v2/select"
|
import { themeModes } from "../theme/v2/select"
|
||||||
import { createEffect, createMemo, onCleanup, onMount, type Accessor, type ParentProps } from "solid-js"
|
import { createEffect, createMemo, onCleanup, onMount, type Accessor, type ParentProps } from "solid-js"
|
||||||
import { createStore, produce } from "solid-js/store"
|
import { createStore, produce } from "solid-js/store"
|
||||||
@@ -29,6 +28,36 @@ import { Global } from "@opencode-ai/util/global"
|
|||||||
import { DevTools } from "../devtools"
|
import { DevTools } from "../devtools"
|
||||||
|
|
||||||
const themePerformance = DevTools.register({ id: "theme-performance", title: "Theme performance" })
|
const themePerformance = DevTools.register({ id: "theme-performance", title: "Theme performance" })
|
||||||
|
export type ThemeError = { name: string; error: Error }
|
||||||
|
type ThemeErrorHandler = (event: ThemeError) => void
|
||||||
|
|
||||||
|
function createThemeErrors() {
|
||||||
|
let handler: ThemeErrorHandler | undefined
|
||||||
|
let pending: ThemeError | undefined
|
||||||
|
|
||||||
|
return {
|
||||||
|
emit(name: string, cause: unknown) {
|
||||||
|
const event = { name, error: cause instanceof Error ? cause : new Error(String(cause)) }
|
||||||
|
if (handler) {
|
||||||
|
handler(event)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
pending = event
|
||||||
|
},
|
||||||
|
onError(next: ThemeErrorHandler) {
|
||||||
|
handler = next
|
||||||
|
if (pending) {
|
||||||
|
next(pending)
|
||||||
|
pending = undefined
|
||||||
|
}
|
||||||
|
return () => {
|
||||||
|
if (handler === next) handler = undefined
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const themeErrors = createThemeErrors()
|
||||||
|
|
||||||
export type ThemeSource = Readonly<{
|
export type ThemeSource = Readonly<{
|
||||||
discover(): Promise<Record<string, unknown>>
|
discover(): Promise<Record<string, unknown>>
|
||||||
@@ -61,7 +90,7 @@ export {
|
|||||||
const THEME_REFRESH_DELAYS = [250, 1000] as const
|
const THEME_REFRESH_DELAYS = [250, 1000] as const
|
||||||
|
|
||||||
type State = {
|
type State = {
|
||||||
themes: Record<string, ThemeJson>
|
themes: Record<string, ThemeDocumentSource>
|
||||||
mode: "dark" | "light"
|
mode: "dark" | "light"
|
||||||
lock: "dark" | "light" | undefined
|
lock: "dark" | "light" | undefined
|
||||||
active: string
|
active: string
|
||||||
@@ -84,6 +113,7 @@ type ThemeService = {
|
|||||||
unlock(): void
|
unlock(): void
|
||||||
setMode(mode?: "dark" | "light", persist?: boolean): boolean
|
setMode(mode?: "dark" | "light", persist?: boolean): boolean
|
||||||
set(theme: string): boolean
|
set(theme: string): boolean
|
||||||
|
onError(handler: ThemeErrorHandler): () => void
|
||||||
readonly ready: boolean
|
readonly ready: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -139,12 +169,7 @@ const themeContext = createSimpleContext({
|
|||||||
return themes
|
return themes
|
||||||
.discover()
|
.discover()
|
||||||
.then((themes) => {
|
.then((themes) => {
|
||||||
setCustomThemes(
|
setCustomThemes(themes)
|
||||||
Object.entries(themes).reduce<Record<string, ThemeJson>>((result, [name, theme]) => {
|
|
||||||
if (isTheme(theme)) result[name] = theme
|
|
||||||
return result
|
|
||||||
}, {}),
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
.catch(() => setStore("active", "opencode"))
|
.catch(() => setStore("active", "opencode"))
|
||||||
}
|
}
|
||||||
@@ -269,30 +294,26 @@ const themeContext = createSimpleContext({
|
|||||||
})
|
})
|
||||||
|
|
||||||
const initStarted = performance.now()
|
const initStarted = performance.now()
|
||||||
const source = createMemo(() => store.themes[store.active] ?? store.themes.opencode)
|
const selected = createMemo(() => {
|
||||||
const sourceName = createMemo(() => (store.themes[store.active] ? store.active : "opencode"))
|
const name = store.themes[store.active] ? store.active : "opencode"
|
||||||
const file = createMemo(() => migrateV1(source()))
|
try {
|
||||||
const modes = createMemo(() => themeModes(file()))
|
return loadTheme(store.themes[name], name, store.mode)
|
||||||
const mode = () => {
|
} catch (error) {
|
||||||
const supported = modes()
|
if (name === "opencode") throw error
|
||||||
if (supported.includes(store.mode)) return store.mode
|
themeErrors.emit(name, error)
|
||||||
return supported[0] ?? store.mode
|
setStore("active", "opencode")
|
||||||
|
return loadTheme(store.themes.opencode, "opencode", store.mode)
|
||||||
}
|
}
|
||||||
const valuesV2 = createMemo(() => resolveThemeFile(file(), mode(), sourceName()))
|
})
|
||||||
|
const modes = () => selected().modes
|
||||||
|
const mode = () => selected().mode
|
||||||
|
const valuesV2 = () => selected().theme
|
||||||
valuesV2()
|
valuesV2()
|
||||||
themePerformance.set("Init", `${(performance.now() - initStarted).toFixed(2)} ms`)
|
themePerformance.set("Init", `${(performance.now() - initStarted).toFixed(2)} ms`)
|
||||||
const themeV2 = createComponentTheme(valuesV2, mode)
|
const themeV2 = createComponentTheme(valuesV2, mode)
|
||||||
const contextsV2 = {
|
const contextsV2 = {
|
||||||
elevated: createComponentTheme(() => {
|
elevated: createComponentTheme(() => valuesV2().contexts["@context:elevated"] ?? valuesV2(), mode),
|
||||||
const theme = valuesV2().contexts["@context:elevated"]
|
overlay: createComponentTheme(() => valuesV2().contexts["@context:overlay"] ?? valuesV2(), mode),
|
||||||
if (!theme) throw new Error("Theme context is not defined: elevated")
|
|
||||||
return theme
|
|
||||||
}, mode),
|
|
||||||
overlay: createComponentTheme(() => {
|
|
||||||
const theme = valuesV2().contexts["@context:overlay"]
|
|
||||||
if (!theme) throw new Error("Theme context is not defined: overlay")
|
|
||||||
return theme
|
|
||||||
}, mode),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
createEffect(() => renderer.setBackgroundColor(valuesV2().background.default))
|
createEffect(() => renderer.setBackgroundColor(valuesV2().background.default))
|
||||||
@@ -331,6 +352,7 @@ const themeContext = createSimpleContext({
|
|||||||
.catch(() => {})
|
.catch(() => {})
|
||||||
return true
|
return true
|
||||||
},
|
},
|
||||||
|
onError: themeErrors.onError,
|
||||||
get ready() {
|
get ready() {
|
||||||
return store.ready
|
return store.ready
|
||||||
},
|
},
|
||||||
@@ -354,6 +376,14 @@ export function ThemeContextProvider(props: ParentProps<{ context: ContextName }
|
|||||||
</themeContext.context.Provider>
|
</themeContext.context.Provider>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function loadTheme(source: ThemeDocumentSource, name: string, requested: "dark" | "light") {
|
||||||
|
const document = parseTheme(source, name)
|
||||||
|
const modes = themeModes(document)
|
||||||
|
const mode = modes.includes(requested) ? requested : (modes[0] ?? requested)
|
||||||
|
return { modes, mode, theme: resolveThemeDocument(document, mode) }
|
||||||
|
}
|
||||||
|
|
||||||
export function createSyntaxStyleMemo(factory: () => SyntaxStyle) {
|
export function createSyntaxStyleMemo(factory: () => SyntaxStyle) {
|
||||||
const renderer = useRenderer()
|
const renderer = useRenderer()
|
||||||
const retained = new Set<SyntaxStyle>()
|
const retained = new Set<SyntaxStyle>()
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import type { TuiThemeCurrent } from "@opencode-ai/plugin/tui"
|
|||||||
import { ansiToRgba } from "../theme/color"
|
import { ansiToRgba } from "../theme/color"
|
||||||
import { resolveThemeColors } from "../theme/resolve"
|
import { resolveThemeColors } from "../theme/resolve"
|
||||||
import { terminalMode } from "../theme/system"
|
import { terminalMode } from "../theme/system"
|
||||||
import type { ThemeJson } from "../theme/v1"
|
import type { ThemeV1Json } from "../theme/v1"
|
||||||
import type { EntryKind, RunTuiConfig } from "./types"
|
import type { EntryKind, RunTuiConfig } from "./types"
|
||||||
|
|
||||||
type Tone = {
|
type Tone = {
|
||||||
@@ -184,7 +184,7 @@ function splashShadow(indexed: RGBA[], base: RGBA, overlay: RGBA, value: number)
|
|||||||
return nearestIndexed(indexed, mixed)
|
return nearestIndexed(indexed, mixed)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function resolveTheme(theme: ThemeJson, pick: "dark" | "light"): TuiThemeCurrent {
|
export function resolveTheme(theme: ThemeV1Json, pick: "dark" | "light"): TuiThemeCurrent {
|
||||||
const resolved = resolveThemeColors(theme, pick, (code) => RGBA.fromIndex(code, ansiToRgba(code)))
|
const resolved = resolveThemeColors(theme, pick, (code) => RGBA.fromIndex(code, ansiToRgba(code)))
|
||||||
return {
|
return {
|
||||||
...resolved.theme,
|
...resolved.theme,
|
||||||
@@ -246,7 +246,7 @@ function generateMutedTextColor(bg: RGBA, isDark: boolean, map: (rgba: RGBA) =>
|
|||||||
return map(RGBA.fromInts(gray, gray, gray))
|
return map(RGBA.fromInts(gray, gray, gray))
|
||||||
}
|
}
|
||||||
|
|
||||||
export function generateSystem(colors: TerminalColors, pick: "dark" | "light"): ThemeJson {
|
export function generateSystem(colors: TerminalColors, pick: "dark" | "light"): ThemeV1Json {
|
||||||
const bg_snapshot = RGBA.fromHex(colors.defaultBackground ?? colors.palette[0]!)
|
const bg_snapshot = RGBA.fromHex(colors.defaultBackground ?? colors.palette[0]!)
|
||||||
const fg_snapshot = RGBA.fromHex(colors.defaultForeground ?? colors.palette[7]!)
|
const fg_snapshot = RGBA.fromHex(colors.defaultForeground ?? colors.palette[7]!)
|
||||||
const bg = RGBA.defaultBackground(bg_snapshot)
|
const bg = RGBA.defaultBackground(bg_snapshot)
|
||||||
|
|||||||
@@ -1029,11 +1029,13 @@ export function Session() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function SessionRowView(props: {
|
type SessionRowViewProps = {
|
||||||
row: SessionRow
|
row: SessionRow
|
||||||
message: (messageID: string) => SessionMessageInfo | undefined
|
message: (messageID: string) => SessionMessageInfo | undefined
|
||||||
boundaryID?: string
|
boundaryID?: string
|
||||||
}) {
|
}
|
||||||
|
|
||||||
|
function SessionRowView(props: SessionRowViewProps) {
|
||||||
return (
|
return (
|
||||||
<box id={props.boundaryID} marginTop={1} flexShrink={0}>
|
<box id={props.boundaryID} marginTop={1} flexShrink={0}>
|
||||||
<Switch>
|
<Switch>
|
||||||
@@ -1072,11 +1074,109 @@ function SessionRowView(props: {
|
|||||||
</Show>
|
</Show>
|
||||||
)}
|
)}
|
||||||
</Match>
|
</Match>
|
||||||
|
<Match when={props.row.type === "turn-usage" ? props.row : undefined}>
|
||||||
|
{(row) => (
|
||||||
|
<TurnTokenUsage
|
||||||
|
messageIDs={row().messageIDs}
|
||||||
|
previousCacheRead={row().previousCacheRead}
|
||||||
|
message={props.message}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Match>
|
||||||
</Switch>
|
</Switch>
|
||||||
</box>
|
</box>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function TurnTokenUsage(props: {
|
||||||
|
messageIDs: string[]
|
||||||
|
previousCacheRead?: number
|
||||||
|
message: (messageID: string) => SessionMessageInfo | undefined
|
||||||
|
}) {
|
||||||
|
const config = useConfig()
|
||||||
|
const { themeV2 } = useTheme()
|
||||||
|
const steps = createMemo(() => {
|
||||||
|
let previousCacheRead = props.previousCacheRead
|
||||||
|
return props.messageIDs.flatMap((messageID) => {
|
||||||
|
const message = props.message(messageID)
|
||||||
|
if (message?.type !== "assistant" || !message.tokens) return []
|
||||||
|
const total =
|
||||||
|
message.tokens.input +
|
||||||
|
message.tokens.output +
|
||||||
|
message.tokens.reasoning +
|
||||||
|
message.tokens.cache.read +
|
||||||
|
message.tokens.cache.write
|
||||||
|
if (total === 0) return []
|
||||||
|
const newTokens = total - message.tokens.cache.read
|
||||||
|
const cacheBust =
|
||||||
|
previousCacheRead !== undefined && message.tokens.cache.read < previousCacheRead
|
||||||
|
? previousCacheRead - message.tokens.cache.read
|
||||||
|
: undefined
|
||||||
|
previousCacheRead = message.tokens.cache.read
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
finish: message.finish === "tool-calls" ? "tool-call" : (message.finish ?? "unknown"),
|
||||||
|
newTokens,
|
||||||
|
cached: message.tokens.cache.read,
|
||||||
|
total,
|
||||||
|
cacheBust,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
})
|
||||||
|
})
|
||||||
|
const columns = createMemo(() => ({
|
||||||
|
step: Math.max("Step".length, ...steps().map((item) => item.finish.length)),
|
||||||
|
newTokens: Math.max("New".length, ...steps().map((item) => item.newTokens.toLocaleString().length)),
|
||||||
|
cached: Math.max("Cached".length, ...steps().map((item) => item.cached.toLocaleString().length)),
|
||||||
|
total: Math.max("Total".length, ...steps().map((item) => item.total.toLocaleString().length)),
|
||||||
|
}))
|
||||||
|
return (
|
||||||
|
<Show when={config.data.debug?.turn_tokens === true && steps().length > 0}>
|
||||||
|
<box paddingLeft={3} flexDirection="column">
|
||||||
|
<box flexDirection="row">
|
||||||
|
<text width={INLINE_TOOL_ICON_WIDTH} fg={themeV2.text.subdued}>
|
||||||
|
◈
|
||||||
|
</text>
|
||||||
|
<text fg={themeV2.text.subdued} attributes={TextAttributes.BOLD}>
|
||||||
|
Tokens
|
||||||
|
</text>
|
||||||
|
</box>
|
||||||
|
<box paddingLeft={INLINE_TOOL_ICON_WIDTH}>
|
||||||
|
<text fg={themeV2.text.subdued} attributes={TextAttributes.ITALIC}>
|
||||||
|
{"Step".padEnd(columns().step + 2)}
|
||||||
|
{"New".padStart(columns().newTokens)}
|
||||||
|
{" "}
|
||||||
|
{"Cached".padStart(columns().cached)}
|
||||||
|
{" "}
|
||||||
|
{"Total".padStart(columns().total)}
|
||||||
|
</text>
|
||||||
|
</box>
|
||||||
|
<For each={steps()}>
|
||||||
|
{(item) => (
|
||||||
|
<box paddingLeft={INLINE_TOOL_ICON_WIDTH} flexDirection="column">
|
||||||
|
<text fg={themeV2.text.subdued}>
|
||||||
|
{item.finish.padEnd(columns().step + 2)}
|
||||||
|
<span style={{ attributes: TextAttributes.BOLD }}>
|
||||||
|
{item.newTokens.toLocaleString().padStart(columns().newTokens)}
|
||||||
|
</span>
|
||||||
|
{" "}
|
||||||
|
{item.cached.toLocaleString().padStart(columns().cached)}
|
||||||
|
{" "}
|
||||||
|
{item.total.toLocaleString().padStart(columns().total)}
|
||||||
|
</text>
|
||||||
|
<Show when={item.cacheBust !== undefined}>
|
||||||
|
<text fg={themeV2.text.feedback.error.default}>
|
||||||
|
! Cache bust: {item.cacheBust?.toLocaleString()} fewer cached tokens than the previous step
|
||||||
|
</text>
|
||||||
|
</Show>
|
||||||
|
</box>
|
||||||
|
)}
|
||||||
|
</For>
|
||||||
|
</box>
|
||||||
|
</Show>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
function BackgroundToolHint(props: { messages: SessionMessageInfo[] }) {
|
function BackgroundToolHint(props: { messages: SessionMessageInfo[] }) {
|
||||||
const { themeV2 } = useTheme()
|
const { themeV2 } = useTheme()
|
||||||
const shortcut = Keymap.useShortcut("session.background")
|
const shortcut = Keymap.useShortcut("session.background")
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import type { SessionMessageAssistant, SessionMessageInfo } from "@opencode-ai/client"
|
import type { SessionMessageAssistant, SessionMessageInfo } from "@opencode-ai/client"
|
||||||
import { createEffect, on, onCleanup, type Accessor } from "solid-js"
|
import { createEffect, on, onCleanup, type Accessor } from "solid-js"
|
||||||
import { createStore, produce, reconcile } from "solid-js/store"
|
import { createStore, produce, reconcile } from "solid-js/store"
|
||||||
|
import { useConfig } from "../../config"
|
||||||
import { useData } from "../../context/data"
|
import { useData } from "../../context/data"
|
||||||
import { useClient } from "../../context/client"
|
import { useClient } from "../../context/client"
|
||||||
|
|
||||||
@@ -27,18 +28,25 @@ export type SessionRow =
|
|||||||
completed: boolean
|
completed: boolean
|
||||||
}
|
}
|
||||||
| { type: "assistant-footer"; messageID: string }
|
| { type: "assistant-footer"; messageID: string }
|
||||||
|
| { type: "turn-usage"; messageIDs: string[]; previousCacheRead?: number }
|
||||||
|
|
||||||
export function createSessionRows(sessionID: Accessor<string>) {
|
export function createSessionRows(sessionID: Accessor<string>) {
|
||||||
const data = useData()
|
const data = useData()
|
||||||
const client = useClient()
|
const client = useClient()
|
||||||
|
const config = useConfig()
|
||||||
const [rows, setRows] = createStore<SessionRow[]>([])
|
const [rows, setRows] = createStore<SessionRow[]>([])
|
||||||
const revertBoundary = () => data.session.get(sessionID())?.revert?.messageID
|
const revertBoundary = () => data.session.get(sessionID())?.revert?.messageID
|
||||||
|
const turnTokens = () => config.data.debug?.turn_tokens === true
|
||||||
|
|
||||||
function reduce() {
|
function reduce() {
|
||||||
const messages = data.session.message.list(sessionID())
|
const messages = data.session.message.list(sessionID())
|
||||||
const inputs = new Set(data.session.input.list(sessionID()))
|
const inputs = new Set(data.session.input.list(sessionID()))
|
||||||
const boundary = revertBoundary()
|
const boundary = revertBoundary()
|
||||||
const rows = reduceSessionRows(boundary ? messages.filter((message) => message.id < boundary) : messages, inputs)
|
const rows = reduceSessionRows(
|
||||||
|
boundary ? messages.filter((message) => message.id < boundary) : messages,
|
||||||
|
inputs,
|
||||||
|
turnTokens(),
|
||||||
|
)
|
||||||
partitionPending(rows, pendingPermissions())
|
partitionPending(rows, pendingPermissions())
|
||||||
const position = rows.findIndex((row) => row.type === "message" && inputs.has(row.messageID))
|
const position = rows.findIndex((row) => row.type === "message" && inputs.has(row.messageID))
|
||||||
rows.splice(
|
rows.splice(
|
||||||
@@ -127,6 +135,10 @@ export function createSessionRows(sessionID: Accessor<string>) {
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
createEffect(
|
||||||
|
on(turnTokens, () => setRows(reconcile(reduce()))),
|
||||||
|
)
|
||||||
|
|
||||||
const appendMessage = (messageID: string) =>
|
const appendMessage = (messageID: string) =>
|
||||||
setRows(
|
setRows(
|
||||||
produce((draft) => {
|
produce((draft) => {
|
||||||
@@ -246,9 +258,12 @@ export function createSessionRows(sessionID: Accessor<string>) {
|
|||||||
data.on("session.step.ended", (event) => {
|
data.on("session.step.ended", (event) => {
|
||||||
if (event.data.sessionID !== sessionID() || ["tool-calls", "unknown"].includes(event.data.finish)) return
|
if (event.data.sessionID !== sessionID() || ["tool-calls", "unknown"].includes(event.data.finish)) return
|
||||||
appendFooter(event.data.assistantMessageID)
|
appendFooter(event.data.assistantMessageID)
|
||||||
|
if (turnTokens()) setRows(reconcile(reduce()))
|
||||||
}),
|
}),
|
||||||
data.on("session.step.failed", (event) => {
|
data.on("session.step.failed", (event) => {
|
||||||
if (event.data.sessionID === sessionID()) appendFooter(event.data.assistantMessageID)
|
if (event.data.sessionID !== sessionID()) return
|
||||||
|
appendFooter(event.data.assistantMessageID)
|
||||||
|
if (turnTokens()) setRows(reconcile(reduce()))
|
||||||
}),
|
}),
|
||||||
]
|
]
|
||||||
onCleanup(() => subscriptions.forEach((unsubscribe) => unsubscribe()))
|
onCleanup(() => subscriptions.forEach((unsubscribe) => unsubscribe()))
|
||||||
@@ -256,10 +271,17 @@ export function createSessionRows(sessionID: Accessor<string>) {
|
|||||||
return rows
|
return rows
|
||||||
}
|
}
|
||||||
|
|
||||||
export function reduceSessionRows(messages: SessionMessageInfo[], inputs = new Set<string>()) {
|
export function reduceSessionRows(
|
||||||
|
messages: SessionMessageInfo[],
|
||||||
|
inputs = new Set<string>(),
|
||||||
|
turnTokens = false,
|
||||||
|
) {
|
||||||
const isInput = (message: SessionMessageInfo) => inputs.has(message.id)
|
const isInput = (message: SessionMessageInfo) => inputs.has(message.id)
|
||||||
const pendingCompactions = messages.filter((message) => message.type === "compaction" && message.status === "running")
|
const pendingCompactions = messages.filter((message) => message.type === "compaction" && message.status === "running")
|
||||||
const pending = new Set([...pendingCompactions.map((message) => message.id), ...inputs])
|
const pending = new Set([...pendingCompactions.map((message) => message.id), ...inputs])
|
||||||
|
const usage = turnTokens
|
||||||
|
? { steps: [] as SessionMessageAssistant[], previousTurnCacheRead: undefined as number | undefined }
|
||||||
|
: undefined
|
||||||
return [
|
return [
|
||||||
...messages.filter((message) => !pending.has(message.id)),
|
...messages.filter((message) => !pending.has(message.id)),
|
||||||
...pendingCompactions,
|
...pendingCompactions,
|
||||||
@@ -271,20 +293,47 @@ export function reduceSessionRows(messages: SessionMessageInfo[], inputs = new S
|
|||||||
rows.push({ type: "message", messageID: message.id })
|
rows.push({ type: "message", messageID: message.id })
|
||||||
return rows
|
return rows
|
||||||
}
|
}
|
||||||
|
usage?.steps.push(message)
|
||||||
const ordinals = { text: 0, reasoning: 0 }
|
const ordinals = { text: 0, reasoning: 0 }
|
||||||
message.content.forEach((part) => {
|
message.content.forEach((part) => {
|
||||||
const partID = part.type === "tool" ? part.id : `${part.type}:${ordinals[part.type]++}`
|
const partID = part.type === "tool" ? part.id : `${part.type}:${ordinals[part.type]++}`
|
||||||
if ((part.type === "text" || part.type === "reasoning") && !part.text.trim()) return
|
if ((part.type === "text" || part.type === "reasoning") && !part.text.trim()) return
|
||||||
append(rows, { messageID: message.id, partID }, part)
|
append(rows, { messageID: message.id, partID }, part)
|
||||||
})
|
})
|
||||||
if ((message.finish && !["tool-calls", "unknown"].includes(message.finish)) || message.error || message.retry) {
|
const terminal = (message.finish && !["tool-calls", "unknown"].includes(message.finish)) || message.error
|
||||||
|
if (terminal || message.retry) {
|
||||||
completePrevious(rows)
|
completePrevious(rows)
|
||||||
rows.push({ type: "assistant-footer", messageID: message.id })
|
rows.push({ type: "assistant-footer", messageID: message.id })
|
||||||
}
|
}
|
||||||
|
if (terminal && usage) {
|
||||||
|
const stepsWithUsage = usage.steps.filter(hasTokenUsage)
|
||||||
|
const last = stepsWithUsage.at(-1)
|
||||||
|
if (last) {
|
||||||
|
rows.push({
|
||||||
|
type: "turn-usage",
|
||||||
|
messageIDs: stepsWithUsage.map((step) => step.id),
|
||||||
|
...(usage.previousTurnCacheRead === undefined
|
||||||
|
? {}
|
||||||
|
: { previousCacheRead: usage.previousTurnCacheRead }),
|
||||||
|
})
|
||||||
|
usage.previousTurnCacheRead = last.tokens.cache.read
|
||||||
|
}
|
||||||
|
usage.steps.length = 0
|
||||||
|
}
|
||||||
return rows
|
return rows
|
||||||
}, [])
|
}, [])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function hasTokenUsage(
|
||||||
|
message: SessionMessageAssistant,
|
||||||
|
): message is SessionMessageAssistant & { tokens: NonNullable<SessionMessageAssistant["tokens"]> } {
|
||||||
|
return message.tokens !== undefined && tokenTotal(message.tokens) > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
function tokenTotal(tokens: NonNullable<SessionMessageAssistant["tokens"]>) {
|
||||||
|
return tokens.input + tokens.output + tokens.reasoning + tokens.cache.read + tokens.cache.write
|
||||||
|
}
|
||||||
|
|
||||||
export function messageBoundaryIDs(rows: SessionRow[], messages: SessionMessageInfo[]) {
|
export function messageBoundaryIDs(rows: SessionRow[], messages: SessionMessageInfo[]) {
|
||||||
const byID = new Map(messages.map((message) => [message.id, message]))
|
const byID = new Map(messages.map((message) => [message.id, message]))
|
||||||
const seen = new Set<string>()
|
const seen = new Set<string>()
|
||||||
@@ -309,6 +358,8 @@ function rowBoundaryMessageID(row: SessionRow, messages: Map<string, SessionMess
|
|||||||
? row.refs[0]?.messageID
|
? row.refs[0]?.messageID
|
||||||
: row.type === "assistant-footer"
|
: row.type === "assistant-footer"
|
||||||
? row.messageID
|
? row.messageID
|
||||||
|
: row.type === "turn-usage"
|
||||||
|
? row.messageIDs[0]
|
||||||
: undefined
|
: undefined
|
||||||
if (!messageID) return undefined
|
if (!messageID) return undefined
|
||||||
const message = messages.get(messageID)
|
const message = messages.get(messageID)
|
||||||
|
|||||||
@@ -1,17 +1,29 @@
|
|||||||
|
import { Schema } from "effect"
|
||||||
import { resolveThemeColors } from "./resolve"
|
import { resolveThemeColors } from "./resolve"
|
||||||
import { DEFAULT_THEMES, type Theme, type ThemeJson } from "./v1"
|
import { DEFAULT_THEMES_V1, type Theme, type ThemeV1Json } from "./v1"
|
||||||
|
import { DEFAULT_THEMES_V2 } from "./v2/defaults"
|
||||||
|
import { resolveThemeDocument, themeDecodeError } from "./v2/resolve"
|
||||||
|
import { ThemeDocument } from "./v2/schema"
|
||||||
|
import { migrateV1 } from "./v2/v1-migrate"
|
||||||
|
|
||||||
export { DEFAULT_THEMES, generateSyntax, selectedForeground, type Theme, type ThemeJson } from "./v1"
|
export { DEFAULT_THEMES, DEFAULT_THEMES_V1, generateSyntax, selectedForeground, type Theme, type ThemeV1Json } from "./v1"
|
||||||
|
export { DEFAULT_THEMES_V2 } from "./v2/defaults"
|
||||||
|
export { resolveThemeDocument, type ThemeDocument }
|
||||||
|
|
||||||
const pluginThemes: Record<string, ThemeJson> = {}
|
export type ThemeDocumentSource = Record<string, unknown>
|
||||||
let customThemes: Record<string, ThemeJson> = {}
|
|
||||||
let systemTheme: ThemeJson | undefined
|
const pluginThemes: Record<string, ThemeDocumentSource> = {}
|
||||||
const listeners = new Set<(themes: Record<string, ThemeJson>) => void>()
|
let customThemes: Record<string, ThemeDocumentSource> = {}
|
||||||
|
let systemTheme: ThemeDocumentSource | undefined
|
||||||
|
const listeners = new Set<(themes: Record<string, ThemeDocumentSource>) => void>()
|
||||||
|
const parsed = new WeakMap<object, ThemeDocument>()
|
||||||
|
const decodeThemeDocument = Schema.decodeUnknownSync(ThemeDocument)
|
||||||
|
|
||||||
function listThemes() {
|
function listThemes() {
|
||||||
// Priority: defaults < plugin installs < custom files < generated system.
|
// Priority: defaults < plugin installs < custom files < generated system.
|
||||||
const themes = {
|
const themes: Record<string, ThemeDocumentSource> = {
|
||||||
...DEFAULT_THEMES,
|
...DEFAULT_THEMES_V1,
|
||||||
|
...DEFAULT_THEMES_V2,
|
||||||
...pluginThemes,
|
...pluginThemes,
|
||||||
...customThemes,
|
...customThemes,
|
||||||
}
|
}
|
||||||
@@ -31,23 +43,40 @@ export function allThemes() {
|
|||||||
return listThemes()
|
return listThemes()
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isTheme(theme: unknown): theme is ThemeJson {
|
export function isThemeSource(source: unknown): source is ThemeDocumentSource {
|
||||||
if (typeof theme !== "object" || theme === null || Array.isArray(theme)) return false
|
if (typeof source !== "object" || source === null || Array.isArray(source)) return false
|
||||||
const value = Reflect.get(theme, "theme")
|
return "theme" in source || "version" in source
|
||||||
return typeof value === "object" && value !== null && !Array.isArray(value)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function subscribeThemes(listener: (themes: Record<string, ThemeJson>) => void) {
|
export function parseTheme(source: ThemeDocumentSource, name = "theme") {
|
||||||
|
const cached = parsed.get(source)
|
||||||
|
if (cached) return cached
|
||||||
|
|
||||||
|
const version = source.version ?? 1
|
||||||
|
const document =
|
||||||
|
version === 1
|
||||||
|
? migrateV1(source as ThemeV1Json)
|
||||||
|
: version === 2
|
||||||
|
? decodeV2Theme(source, name)
|
||||||
|
: unsupportedThemeVersion(version)
|
||||||
|
|
||||||
|
parsed.set(source, document)
|
||||||
|
return document
|
||||||
|
}
|
||||||
|
|
||||||
|
export function subscribeThemes(listener: (themes: Record<string, ThemeDocumentSource>) => void) {
|
||||||
listeners.add(listener)
|
listeners.add(listener)
|
||||||
return () => listeners.delete(listener)
|
return () => listeners.delete(listener)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function setCustomThemes(themes: Record<string, ThemeJson>) {
|
export function setCustomThemes(themes: Record<string, unknown>) {
|
||||||
customThemes = themes
|
customThemes = Object.fromEntries(
|
||||||
|
Object.entries(themes).filter((entry): entry is [string, ThemeDocumentSource] => isThemeSource(entry[1])),
|
||||||
|
)
|
||||||
syncThemes()
|
syncThemes()
|
||||||
}
|
}
|
||||||
|
|
||||||
export function setSystemTheme(theme: ThemeJson | undefined) {
|
export function setSystemTheme(theme: ThemeDocumentSource | undefined) {
|
||||||
systemTheme = theme
|
systemTheme = theme
|
||||||
syncThemes()
|
syncThemes()
|
||||||
}
|
}
|
||||||
@@ -59,7 +88,7 @@ export function hasTheme(name: string) {
|
|||||||
|
|
||||||
export function addTheme(name: string, theme: unknown) {
|
export function addTheme(name: string, theme: unknown) {
|
||||||
if (!name) return false
|
if (!name) return false
|
||||||
if (!isTheme(theme)) return false
|
if (!isThemeSource(theme)) return false
|
||||||
if (hasTheme(name)) return false
|
if (hasTheme(name)) return false
|
||||||
pluginThemes[name] = theme
|
pluginThemes[name] = theme
|
||||||
syncThemes()
|
syncThemes()
|
||||||
@@ -68,7 +97,7 @@ export function addTheme(name: string, theme: unknown) {
|
|||||||
|
|
||||||
export function upsertTheme(name: string, theme: unknown) {
|
export function upsertTheme(name: string, theme: unknown) {
|
||||||
if (!name) return false
|
if (!name) return false
|
||||||
if (!isTheme(theme)) return false
|
if (!isThemeSource(theme)) return false
|
||||||
if (customThemes[name] !== undefined) {
|
if (customThemes[name] !== undefined) {
|
||||||
customThemes[name] = theme
|
customThemes[name] = theme
|
||||||
} else {
|
} else {
|
||||||
@@ -78,7 +107,7 @@ export function upsertTheme(name: string, theme: unknown) {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
export function resolveTheme(theme: ThemeJson, mode: "dark" | "light"): Theme {
|
export function resolveTheme(theme: ThemeV1Json, mode: "dark" | "light"): Theme {
|
||||||
const resolved = resolveThemeColors(theme, mode)
|
const resolved = resolveThemeColors(theme, mode)
|
||||||
return {
|
return {
|
||||||
...resolved.theme,
|
...resolved.theme,
|
||||||
@@ -86,3 +115,15 @@ export function resolveTheme(theme: ThemeJson, mode: "dark" | "light"): Theme {
|
|||||||
thinkingOpacity: resolved.thinkingOpacity,
|
thinkingOpacity: resolved.thinkingOpacity,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function decodeV2Theme(source: ThemeDocumentSource, name: string) {
|
||||||
|
try {
|
||||||
|
return decodeThemeDocument(source)
|
||||||
|
} catch (error) {
|
||||||
|
throw themeDecodeError(error, name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function unsupportedThemeVersion(version: unknown): never {
|
||||||
|
throw new Error(`Unsupported theme version: ${String(version)}`)
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { RGBA } from "@opentui/core"
|
import { RGBA } from "@opentui/core"
|
||||||
import { ansiToRgba } from "./color"
|
import { ansiToRgba } from "./color"
|
||||||
import type { ColorValue, Theme, ThemeColor, ThemeJson } from "./v1"
|
import type { ColorValue, Theme, ThemeColor, ThemeV1Json } from "./v1"
|
||||||
|
|
||||||
export function resolveThemeColors(
|
export function resolveThemeColors(
|
||||||
theme: ThemeJson,
|
theme: ThemeV1Json,
|
||||||
mode: "dark" | "light",
|
mode: "dark" | "light",
|
||||||
resolveAnsi: (code: number) => RGBA = ansiToRgba,
|
resolveAnsi: (code: number) => RGBA = ansiToRgba,
|
||||||
) {
|
) {
|
||||||
@@ -43,7 +43,9 @@ export function resolveThemeColors(
|
|||||||
? resolveColor(theme.theme.selectedListItemText!)
|
? resolveColor(theme.theme.selectedListItemText!)
|
||||||
: resolved.background!,
|
: resolved.background!,
|
||||||
backgroundMenu:
|
backgroundMenu:
|
||||||
theme.theme.backgroundMenu === undefined ? resolved.backgroundElement! : resolveColor(theme.theme.backgroundMenu),
|
theme.theme.backgroundMenu === undefined
|
||||||
|
? resolved.backgroundElement!
|
||||||
|
: resolveColor(theme.theme.backgroundMenu),
|
||||||
} satisfies Omit<Theme, "_hasSelectedListItemText" | "thinkingOpacity">,
|
} satisfies Omit<Theme, "_hasSelectedListItemText" | "thinkingOpacity">,
|
||||||
hasSelectedListItemText,
|
hasSelectedListItemText,
|
||||||
thinkingOpacity: theme.theme.thinkingOpacity ?? 0.6,
|
thinkingOpacity: theme.theme.thinkingOpacity ?? 0.6,
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ export type Variant = {
|
|||||||
light: HexColor | RefName
|
light: HexColor | RefName
|
||||||
}
|
}
|
||||||
export type ColorValue = HexColor | RefName | Variant | RGBA | number
|
export type ColorValue = HexColor | RefName | Variant | RGBA | number
|
||||||
export type ThemeJson = {
|
export type ThemeV1Json = {
|
||||||
$schema?: string
|
$schema?: string
|
||||||
defs?: Record<string, HexColor | RefName>
|
defs?: Record<string, HexColor | RefName>
|
||||||
theme: Omit<Record<ThemeColor, ColorValue>, "selectedListItemText" | "backgroundMenu"> & {
|
theme: Omit<Record<ThemeColor, ColorValue>, "selectedListItemText" | "backgroundMenu"> & {
|
||||||
@@ -108,7 +108,7 @@ export type ThemeJson = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DEFAULT_THEMES: Record<string, ThemeJson> = {
|
export const DEFAULT_THEMES_V1: Record<string, ThemeV1Json> = {
|
||||||
aura,
|
aura,
|
||||||
ayu,
|
ayu,
|
||||||
catppuccin,
|
catppuccin,
|
||||||
@@ -144,6 +144,8 @@ export const DEFAULT_THEMES: Record<string, ThemeJson> = {
|
|||||||
carbonfox,
|
carbonfox,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const DEFAULT_THEMES = DEFAULT_THEMES_V1
|
||||||
|
|
||||||
export function selectedForeground(theme: Theme, bg?: RGBA): RGBA {
|
export function selectedForeground(theme: Theme, bg?: RGBA): RGBA {
|
||||||
if (theme._hasSelectedListItemText) return theme.selectedListItemText
|
if (theme._hasSelectedListItemText) return theme.selectedListItemText
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { HueName, ThemeFile } from "./schema"
|
import type { HueName, ThemeDocument } from "./schema"
|
||||||
|
|
||||||
export const DEFAULT_CATEGORICAL = [
|
export const DEFAULT_CATEGORICAL = [
|
||||||
"blue",
|
"blue",
|
||||||
@@ -437,4 +437,8 @@ export const DEFAULT_THEME = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
} satisfies ThemeFile
|
} satisfies ThemeDocument
|
||||||
|
|
||||||
|
export const DEFAULT_THEMES_V2 = {
|
||||||
|
"opencode-v2": DEFAULT_THEME,
|
||||||
|
} as const satisfies Record<string, ThemeDocument>
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ export {
|
|||||||
SyntaxDefinition,
|
SyntaxDefinition,
|
||||||
SyntaxToken,
|
SyntaxToken,
|
||||||
ThemeDefinition,
|
ThemeDefinition,
|
||||||
ThemeFile,
|
ThemeDocument,
|
||||||
type BackgroundDefinition,
|
type BackgroundDefinition,
|
||||||
type DiffDefinition,
|
type DiffDefinition,
|
||||||
type FileThemeDefinition,
|
type FileThemeDefinition,
|
||||||
@@ -43,6 +43,6 @@ export type {
|
|||||||
ResolvedThemeView,
|
ResolvedThemeView,
|
||||||
StatefulColor,
|
StatefulColor,
|
||||||
} from "./types"
|
} from "./types"
|
||||||
export { DEFAULT_CATEGORICAL } from "./defaults"
|
export { DEFAULT_CATEGORICAL, DEFAULT_THEME, DEFAULT_THEMES_V2 } from "./defaults"
|
||||||
export { migrateV1 } from "./v1-migrate"
|
export { migrateV1 } from "./v1-migrate"
|
||||||
export { selectTheme, selectThemeMode, supportsThemeMode, themeModes } from "./select"
|
export { selectTheme, selectThemeMode, supportsThemeMode, themeModes } from "./select"
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import {
|
|||||||
HueAlias,
|
HueAlias,
|
||||||
HueStep,
|
HueStep,
|
||||||
ThemeDefinition,
|
ThemeDefinition,
|
||||||
ThemeFile,
|
ThemeDocument,
|
||||||
} from "./schema"
|
} from "./schema"
|
||||||
import type {
|
import type {
|
||||||
ActionStateKey,
|
ActionStateKey,
|
||||||
@@ -26,7 +26,6 @@ import type {
|
|||||||
import { selectTheme, selectThemeMode } from "./select"
|
import { selectTheme, selectThemeMode } from "./select"
|
||||||
|
|
||||||
const decodeThemeDefinitionSchema = Schema.decodeUnknownSync(ThemeDefinition)
|
const decodeThemeDefinitionSchema = Schema.decodeUnknownSync(ThemeDefinition)
|
||||||
const decodeThemeFileSchema = Schema.decodeUnknownSync(ThemeFile)
|
|
||||||
|
|
||||||
function decodeThemeDefinition(input: unknown) {
|
function decodeThemeDefinition(input: unknown) {
|
||||||
try {
|
try {
|
||||||
@@ -36,27 +35,18 @@ function decodeThemeDefinition(input: unknown) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function decodeThemeFile(input: unknown, name: string) {
|
export function themeDecodeError(error: unknown, name: string) {
|
||||||
try {
|
|
||||||
return decodeThemeFileSchema(input)
|
|
||||||
} catch (error) {
|
|
||||||
throw themeDecodeError(error, name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function themeDecodeError(error: unknown, name: string) {
|
|
||||||
const message = Schema.isSchemaError(error) ? error.message : String(error)
|
const message = Schema.isSchemaError(error) ? error.message : String(error)
|
||||||
const value = /got ("[^"]*"|\S+)/.exec(message)?.[1] ?? "value"
|
const value = /got ("[^"]*"|\S+)/.exec(message)?.[1] ?? "value"
|
||||||
return new Error(`Invalid theme: ${name} ${value} is an invalid value`, { cause: error })
|
return new Error(`Invalid theme: ${name} ${value} is an invalid value`, { cause: error })
|
||||||
}
|
}
|
||||||
|
|
||||||
export function resolveThemeFile(file: ThemeFile, mode?: "light" | "dark", name = "theme") {
|
export function resolveThemeDocument(document: ThemeDocument, mode?: "light" | "dark") {
|
||||||
const decoded = decodeThemeFile(file, name)
|
const selected = selectThemeMode(document, mode)
|
||||||
const selected = selectThemeMode(decoded, mode)
|
|
||||||
const definition = selected.expanded ? selected.theme : expandTheme(selected.theme)
|
const definition = selected.expanded ? selected.theme : expandTheme(selected.theme)
|
||||||
const defaults = expandTheme(selectTheme(DEFAULT_THEME, selected.mode))
|
const defaults = expandTheme(selectTheme(DEFAULT_THEME, selected.mode))
|
||||||
const core = expandTokens(fallback())
|
const core = expandTokens(fallback())
|
||||||
const merged = decoded.standalone ? mergeTheme(core, definition) : mergeTheme(core, defaults, definition)
|
const merged = document.standalone ? mergeTheme(core, definition) : mergeTheme(core, defaults, definition)
|
||||||
if (!merged["hue"]) throw new Error("Standalone themes must provide hues")
|
if (!merged["hue"]) throw new Error("Standalone themes must provide hues")
|
||||||
return resolveExpandedTheme({
|
return resolveExpandedTheme({
|
||||||
...merged,
|
...merged,
|
||||||
|
|||||||
@@ -251,8 +251,8 @@ const FileMetadata = {
|
|||||||
version: Schema.Literal(2),
|
version: Schema.Literal(2),
|
||||||
standalone: Schema.optional(Schema.Boolean),
|
standalone: Schema.optional(Schema.Boolean),
|
||||||
}
|
}
|
||||||
export const ThemeFile = Schema.Union([
|
export const ThemeDocument = Schema.Union([
|
||||||
Schema.Struct({ ...FileMetadata, light: ModeDefinition, dark: Schema.optional(ModeDefinition) }),
|
Schema.Struct({ ...FileMetadata, light: ModeDefinition, dark: Schema.optional(ModeDefinition) }),
|
||||||
Schema.Struct({ ...FileMetadata, light: Schema.optional(ModeDefinition), dark: ModeDefinition }),
|
Schema.Struct({ ...FileMetadata, light: Schema.optional(ModeDefinition), dark: ModeDefinition }),
|
||||||
])
|
])
|
||||||
export type ThemeFile = Schema.Schema.Type<typeof ThemeFile>
|
export type ThemeDocument = Schema.Schema.Type<typeof ThemeDocument>
|
||||||
|
|||||||
@@ -5,45 +5,45 @@ import type {
|
|||||||
Mode,
|
Mode,
|
||||||
ModeDefinition,
|
ModeDefinition,
|
||||||
ThemeDefinition,
|
ThemeDefinition,
|
||||||
ThemeFile,
|
ThemeDocument,
|
||||||
} from "./index"
|
} from "./index"
|
||||||
|
|
||||||
export function selectTheme(
|
export function selectTheme(
|
||||||
file: ThemeFile & { light: ThemeDefinition; dark: ThemeDefinition },
|
document: ThemeDocument & { light: ThemeDefinition; dark: ThemeDefinition },
|
||||||
mode?: Mode,
|
mode?: Mode,
|
||||||
): ThemeDefinition
|
): ThemeDefinition
|
||||||
export function selectTheme(file: ThemeFile, mode?: Mode): FileThemeDefinition
|
export function selectTheme(document: ThemeDocument, mode?: Mode): FileThemeDefinition
|
||||||
export function selectTheme(file: ThemeFile, mode?: Mode) {
|
export function selectTheme(document: ThemeDocument, mode?: Mode) {
|
||||||
return selectThemeMode(file, mode).theme
|
return selectThemeMode(document, mode).theme
|
||||||
}
|
}
|
||||||
|
|
||||||
export function selectThemeMode(
|
export function selectThemeMode(
|
||||||
file: ThemeFile,
|
document: ThemeDocument,
|
||||||
mode: Mode = "light",
|
mode: Mode = "light",
|
||||||
): { theme: FileThemeDefinition; mode: Mode; expanded: boolean } {
|
): { theme: FileThemeDefinition; mode: Mode; expanded: boolean } {
|
||||||
const modes = themeModes(file)
|
const modes = themeModes(document)
|
||||||
const selectedMode = modes.includes(mode) ? mode : modes[0]
|
const selectedMode = modes.includes(mode) ? mode : modes[0]
|
||||||
const selected = file[selectedMode]
|
const selected = document[selectedMode]
|
||||||
if (!selected) throw new Error("Theme must provide at least one mode")
|
if (!selected) throw new Error("Theme must provide at least one mode")
|
||||||
if (merges(file.light) && merges(file.dark)) throw new Error("Light and dark themes cannot both merge modes")
|
if (merges(document.light) && merges(document.dark)) throw new Error("Light and dark themes cannot both merge modes")
|
||||||
if (!merges(selected)) return { theme: selected, mode: selectedMode, expanded: false }
|
if (!merges(selected)) return { theme: selected, mode: selectedMode, expanded: false }
|
||||||
|
|
||||||
const otherMode = selectedMode === "light" ? "dark" : "light"
|
const otherMode = selectedMode === "light" ? "dark" : "light"
|
||||||
const other = file[otherMode]
|
const other = document[otherMode]
|
||||||
if (!other) throw new Error(`The ${selectedMode} theme cannot merge without a ${otherMode} theme`)
|
if (!other) throw new Error(`The ${selectedMode} theme cannot merge without a ${otherMode} theme`)
|
||||||
const merged = mergeTheme(expandTheme(other), expandTheme(selected))
|
const merged = mergeTheme(expandTheme(other), expandTheme(selected))
|
||||||
if (!merged["hue"]) throw new Error(`The ${otherMode} theme must provide hues when ${selectedMode} merges modes`)
|
if (!merged["hue"]) throw new Error(`The ${otherMode} theme must provide hues when ${selectedMode} merges modes`)
|
||||||
return { theme: merged as FileThemeDefinition, mode: selectedMode, expanded: true }
|
return { theme: merged as FileThemeDefinition, mode: selectedMode, expanded: true }
|
||||||
}
|
}
|
||||||
|
|
||||||
export function themeModes(file: ThemeFile): readonly Mode[] {
|
export function themeModes(document: ThemeDocument): readonly Mode[] {
|
||||||
if (merges(file.light) && !file.dark) throw new Error("The light theme cannot merge without a dark theme")
|
if (merges(document.light) && !document.dark) throw new Error("The light theme cannot merge without a dark theme")
|
||||||
if (merges(file.dark) && !file.light) throw new Error("The dark theme cannot merge without a light theme")
|
if (merges(document.dark) && !document.light) throw new Error("The dark theme cannot merge without a light theme")
|
||||||
return (["light", "dark"] as const).filter((mode) => file[mode] !== undefined)
|
return (["light", "dark"] as const).filter((mode) => document[mode] !== undefined)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function supportsThemeMode(file: ThemeFile, mode: Mode) {
|
export function supportsThemeMode(document: ThemeDocument, mode: Mode) {
|
||||||
return themeModes(file).includes(mode)
|
return themeModes(document).includes(mode)
|
||||||
}
|
}
|
||||||
|
|
||||||
function merges(definition: ModeDefinition | undefined): definition is MergeModeDefinition {
|
function merges(definition: ModeDefinition | undefined): definition is MergeModeDefinition {
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { RGBA } from "@opentui/core"
|
import { RGBA } from "@opentui/core"
|
||||||
import { oklchToHex, rgbToOklch } from "@opencode-ai/ui/theme/color"
|
import { oklchToHex, rgbToOklch } from "@opencode-ai/ui/theme/color"
|
||||||
import type { Theme, ThemeJson } from "../index"
|
import type { Theme, ThemeV1Json } from "../v1"
|
||||||
import { DEFAULT_CATEGORICAL, DEFAULT_THEME } from "./defaults"
|
import { DEFAULT_CATEGORICAL, DEFAULT_THEME } from "./defaults"
|
||||||
import type { FileThemeDefinition, Mode, ThemeFile } from "./index"
|
import type { FileThemeDefinition, Mode, ThemeDocument } from "./index"
|
||||||
import { HueStep } from "./schema"
|
import { HueStep } from "./schema"
|
||||||
|
|
||||||
type ThemeColor = Exclude<keyof Theme, "thinkingOpacity" | "_hasSelectedListItemText">
|
type ThemeColor = Exclude<keyof Theme, "thinkingOpacity" | "_hasSelectedListItemText">
|
||||||
@@ -14,7 +14,7 @@ const categoricalTokens: readonly V1HueToken[] = ["secondary", "accent", "succes
|
|||||||
const minimumChroma = 0.03
|
const minimumChroma = 0.03
|
||||||
const lightThreshold = 0.6
|
const lightThreshold = 0.6
|
||||||
|
|
||||||
export function migrateV1(theme: ThemeJson): ThemeFile {
|
export function migrateV1(theme: ThemeV1Json): ThemeDocument {
|
||||||
const light = resolveV1(theme, "light")
|
const light = resolveV1(theme, "light")
|
||||||
const dark = resolveV1(theme, "dark")
|
const dark = resolveV1(theme, "dark")
|
||||||
if (light.background.a > 0 && dark.background.a > 0 && light.background.equals(dark.background)) {
|
if (light.background.a > 0 && dark.background.a > 0 && light.background.equals(dark.background)) {
|
||||||
@@ -234,7 +234,7 @@ function ambiguous(color: RGBA, chroma = toOklch(color).c) {
|
|||||||
return color.toInts()[3] === 0 || chroma < minimumChroma
|
return color.toInts()[3] === 0 || chroma < minimumChroma
|
||||||
}
|
}
|
||||||
|
|
||||||
function resolveV1(theme: ThemeJson, mode: "dark" | "light"): Theme {
|
function resolveV1(theme: ThemeV1Json, mode: "dark" | "light"): Theme {
|
||||||
const defs = theme.defs ?? {}
|
const defs = theme.defs ?? {}
|
||||||
|
|
||||||
function resolveColor(value: unknown, chain: string[] = []): RGBA {
|
function resolveColor(value: unknown, chain: string[] = []): RGBA {
|
||||||
|
|||||||
@@ -5,12 +5,14 @@ import type { OpenCodeEvent } from "@opencode-ai/client"
|
|||||||
import { SessionMessage } from "@opencode-ai/core/session/message"
|
import { SessionMessage } from "@opencode-ai/core/session/message"
|
||||||
import { EventV2 } from "@opencode-ai/core/event"
|
import { EventV2 } from "@opencode-ai/core/event"
|
||||||
import { createEffect, onMount, type ParentProps } from "solid-js"
|
import { createEffect, onMount, type ParentProps } from "solid-js"
|
||||||
|
import { ConfigProvider } from "../../../src/config"
|
||||||
import { ClientProvider, useClient } from "../../../src/context/client"
|
import { ClientProvider, useClient } from "../../../src/context/client"
|
||||||
import { DataProvider as DataProviderBase, useData } from "../../../src/context/data"
|
import { DataProvider as DataProviderBase, useData } from "../../../src/context/data"
|
||||||
import { LocationProvider, useLocation } from "../../../src/context/location"
|
import { LocationProvider, useLocation } from "../../../src/context/location"
|
||||||
import { createSessionRows, type SessionRow } from "../../../src/routes/session/rows"
|
import { createSessionRows, type SessionRow } from "../../../src/routes/session/rows"
|
||||||
import { createApi, createEventStream, createFetch, directory, json } from "../../fixture/tui-client"
|
import { createApi, createEventStream, createFetch, directory, json } from "../../fixture/tui-client"
|
||||||
import { TestTuiContexts } from "../../fixture/tui-environment"
|
import { TestTuiContexts } from "../../fixture/tui-environment"
|
||||||
|
import { createTuiResolvedConfig } from "../../fixture/tui-runtime"
|
||||||
|
|
||||||
const formFields = [{ key: "authorization", type: "external", url: "https://example.com" }] satisfies [
|
const formFields = [{ key: "authorization", type: "external", url: "https://example.com" }] satisfies [
|
||||||
{
|
{
|
||||||
@@ -32,14 +34,18 @@ function emitEvent(events: ReturnType<typeof createEventStream>, event: OpenCode
|
|||||||
events.emit({ ...event, location: { directory } })
|
events.emit({ ...event, location: { directory } })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const config = createTuiResolvedConfig()
|
||||||
|
|
||||||
function DataProvider(props: ParentProps) {
|
function DataProvider(props: ParentProps) {
|
||||||
return (
|
return (
|
||||||
|
<ConfigProvider config={config}>
|
||||||
<DataProviderBase>
|
<DataProviderBase>
|
||||||
<LocationProvider>
|
<LocationProvider>
|
||||||
<SyncLocation />
|
<SyncLocation />
|
||||||
{props.children}
|
{props.children}
|
||||||
</LocationProvider>
|
</LocationProvider>
|
||||||
</DataProviderBase>
|
</DataProviderBase>
|
||||||
|
</ConfigProvider>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
/** @jsxImportSource @opentui/solid */
|
/** @jsxImportSource @opentui/solid */
|
||||||
import { testRender } from "@opentui/solid"
|
import { testRender } from "@opentui/solid"
|
||||||
import { expect, test } from "bun:test"
|
import { expect, test } from "bun:test"
|
||||||
|
import { RGBA } from "@opentui/core"
|
||||||
import { createTuiResolvedConfig } from "../../fixture/tui-runtime"
|
import { createTuiResolvedConfig } from "../../fixture/tui-runtime"
|
||||||
import { DEFAULT_THEMES } from "../../../src/theme"
|
import { DEFAULT_THEMES } from "../../../src/theme"
|
||||||
|
import { DEFAULT_THEME } from "../../../src/theme/v2/defaults"
|
||||||
|
import { selectTheme } from "../../../src/theme/v2/select"
|
||||||
import { ConfigProvider } from "../../../src/config"
|
import { ConfigProvider } from "../../../src/config"
|
||||||
import { ThemeProvider, useTheme } from "../../../src/context/theme"
|
import { ThemeProvider, useTheme, type ThemeError } from "../../../src/context/theme"
|
||||||
|
|
||||||
async function wait(fn: () => boolean) {
|
async function wait(fn: () => boolean) {
|
||||||
const started = Date.now()
|
const started = Date.now()
|
||||||
@@ -24,6 +27,7 @@ test("uses an available mode while retaining the pinned preference", async () =>
|
|||||||
const darkOnly = structuredClone(DEFAULT_THEMES.opencode)
|
const darkOnly = structuredClone(DEFAULT_THEMES.opencode)
|
||||||
darkOnly.theme.background = "#111111"
|
darkOnly.theme.background = "#111111"
|
||||||
darkOnly.theme.text = "#eeeeee"
|
darkOnly.theme.text = "#eeeeee"
|
||||||
|
const native = { version: 2, dark: { text: { default: "#abcdef" } } } as const
|
||||||
let theme: ReturnType<typeof useTheme> | undefined
|
let theme: ReturnType<typeof useTheme> | undefined
|
||||||
|
|
||||||
function Probe() {
|
function Probe() {
|
||||||
@@ -42,7 +46,7 @@ test("uses an available mode while retaining the pinned preference", async () =>
|
|||||||
<ConfigProvider config={createTuiResolvedConfig({ theme: { name: "light-only", mode: "dark" } })}>
|
<ConfigProvider config={createTuiResolvedConfig({ theme: { name: "light-only", mode: "dark" } })}>
|
||||||
<ThemeProvider
|
<ThemeProvider
|
||||||
mode="dark"
|
mode="dark"
|
||||||
source={{ discover: () => Promise.resolve({ "light-only": lightOnly, "dark-only": darkOnly, dual }) }}
|
source={{ discover: () => Promise.resolve({ "light-only": lightOnly, "dark-only": darkOnly, dual, native }) }}
|
||||||
>
|
>
|
||||||
<Probe />
|
<Probe />
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
@@ -66,6 +70,85 @@ test("uses an available mode while retaining the pinned preference", async () =>
|
|||||||
expect(current().set("dual")).toBeTrue()
|
expect(current().set("dual")).toBeTrue()
|
||||||
await wait(() => current().mode() === "dark")
|
await wait(() => current().mode() === "dark")
|
||||||
expect(current().modes()).toEqual(["light", "dark"])
|
expect(current().modes()).toEqual(["light", "dark"])
|
||||||
|
expect(current().set("native")).toBeTrue()
|
||||||
|
await wait(() => current().selected === "native")
|
||||||
|
expect(current().modes()).toEqual(["dark"])
|
||||||
|
expect(current().themeV2.text.default.equals(RGBA.fromHex("#abcdef"))).toBeTrue()
|
||||||
|
} finally {
|
||||||
|
app.renderer.destroy()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
test.each([
|
||||||
|
["schema", { version: 2, light: { categorical: [] } }],
|
||||||
|
["mode merging", { version: 2, light: { mergeMode: true } }],
|
||||||
|
["token reference", { version: 2, light: { text: { default: "$missing" } } }],
|
||||||
|
] as const)("falls back to OpenCode when configured V2 theme %s is invalid", async (_label, source) => {
|
||||||
|
let theme: ReturnType<typeof useTheme> | undefined
|
||||||
|
let failure: ThemeError | undefined
|
||||||
|
let unsubscribe: (() => void) | undefined
|
||||||
|
|
||||||
|
function Probe() {
|
||||||
|
const value = useTheme()
|
||||||
|
theme = value
|
||||||
|
unsubscribe = value.onError((error) => (failure = error))
|
||||||
|
return <text>{value.selected}</text>
|
||||||
|
}
|
||||||
|
|
||||||
|
const app = await testRender(
|
||||||
|
() => (
|
||||||
|
<ConfigProvider config={createTuiResolvedConfig({ theme: { name: "invalid" } })}>
|
||||||
|
<ThemeProvider mode="dark" source={{ discover: () => Promise.resolve({ invalid: source }) }}>
|
||||||
|
<Probe />
|
||||||
|
</ThemeProvider>
|
||||||
|
</ConfigProvider>
|
||||||
|
),
|
||||||
|
{ width: 20, height: 2 },
|
||||||
|
)
|
||||||
|
app.renderer.start()
|
||||||
|
|
||||||
|
try {
|
||||||
|
await wait(() => theme?.ready === true)
|
||||||
|
expect(theme?.selected).toBe("opencode")
|
||||||
|
expect(failure?.name).toBe("invalid")
|
||||||
|
expect(failure?.error).toBeInstanceOf(Error)
|
||||||
|
expect(failure?.error.message.length).toBeGreaterThan(0)
|
||||||
|
} finally {
|
||||||
|
unsubscribe?.()
|
||||||
|
app.renderer.destroy()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
test("contextual themes fall back to a standalone theme's base view", async () => {
|
||||||
|
const standalone = {
|
||||||
|
version: 2,
|
||||||
|
standalone: true,
|
||||||
|
dark: { hue: selectTheme(DEFAULT_THEME, "dark").hue },
|
||||||
|
} as const
|
||||||
|
let theme: ReturnType<typeof useTheme> | undefined
|
||||||
|
|
||||||
|
function Probe() {
|
||||||
|
theme = useTheme()
|
||||||
|
return <text>{theme.selected}</text>
|
||||||
|
}
|
||||||
|
|
||||||
|
const app = await testRender(
|
||||||
|
() => (
|
||||||
|
<ConfigProvider config={createTuiResolvedConfig({ theme: { name: "standalone", mode: "dark" } })}>
|
||||||
|
<ThemeProvider mode="dark" source={{ discover: () => Promise.resolve({ standalone }) }}>
|
||||||
|
<Probe />
|
||||||
|
</ThemeProvider>
|
||||||
|
</ConfigProvider>
|
||||||
|
),
|
||||||
|
{ width: 20, height: 2 },
|
||||||
|
)
|
||||||
|
app.renderer.start()
|
||||||
|
|
||||||
|
try {
|
||||||
|
await wait(() => theme?.ready === true)
|
||||||
|
if (!theme) throw new Error("Theme provider is not mounted")
|
||||||
|
expect(theme.contextual("elevated").themeV2.text.default).toBe(theme.themeV2.text.default)
|
||||||
|
expect(theme.contextual("overlay").themeV2.background.default).toBe(theme.themeV2.background.default)
|
||||||
} finally {
|
} finally {
|
||||||
app.renderer.destroy()
|
app.renderer.destroy()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,25 @@
|
|||||||
/** @jsxImportSource @opentui/solid */
|
/** @jsxImportSource @opentui/solid */
|
||||||
import { testRender } from "@opentui/solid"
|
import { testRender } from "@opentui/solid"
|
||||||
import { expect, test } from "bun:test"
|
import { expect, test } from "bun:test"
|
||||||
|
import { Schema } from "effect"
|
||||||
import {
|
import {
|
||||||
resolve,
|
resolve,
|
||||||
ConfigProvider,
|
ConfigProvider,
|
||||||
|
Info,
|
||||||
useConfig,
|
useConfig,
|
||||||
type Interface,
|
type Interface,
|
||||||
} from "../src/config"
|
} from "../src/config"
|
||||||
|
|
||||||
|
test("validates mini replay settings", () => {
|
||||||
|
const decode = Schema.decodeUnknownSync(Info)
|
||||||
|
|
||||||
|
expect(decode({ mini: { replay: false, replay_limit: 50 } })).toEqual({
|
||||||
|
mini: { replay: false, replay_limit: 50 },
|
||||||
|
})
|
||||||
|
expect(() => decode({ mini: { replay_limit: 0 } })).toThrow()
|
||||||
|
expect(() => decode({ mini: { replay_limit: 1.5 } })).toThrow()
|
||||||
|
})
|
||||||
|
|
||||||
test("resolves nested config and keybind defaults", () => {
|
test("resolves nested config and keybind defaults", () => {
|
||||||
const config = resolve(
|
const config = resolve(
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,7 +2,16 @@ import { expect, test } from "bun:test"
|
|||||||
import { mkdir, writeFile } from "node:fs/promises"
|
import { mkdir, writeFile } from "node:fs/promises"
|
||||||
import path from "node:path"
|
import path from "node:path"
|
||||||
import type { TerminalColors } from "@opentui/core"
|
import type { TerminalColors } from "@opentui/core"
|
||||||
import { DEFAULT_THEMES, addTheme, allThemes, hasTheme, resolveTheme } from "../src/theme"
|
import {
|
||||||
|
DEFAULT_THEMES,
|
||||||
|
addTheme,
|
||||||
|
allThemes,
|
||||||
|
hasTheme,
|
||||||
|
parseTheme,
|
||||||
|
resolveTheme,
|
||||||
|
setCustomThemes,
|
||||||
|
upsertTheme,
|
||||||
|
} from "../src/theme"
|
||||||
import { discoverThemes, themeDirectories } from "../src/theme/discovery"
|
import { discoverThemes, themeDirectories } from "../src/theme/discovery"
|
||||||
import { terminalMode } from "../src/theme/system"
|
import { terminalMode } from "../src/theme/system"
|
||||||
import { tmpdir } from "./fixture/fixture"
|
import { tmpdir } from "./fixture/fixture"
|
||||||
@@ -10,7 +19,7 @@ import { tmpdir } from "./fixture/fixture"
|
|||||||
test("addTheme writes into module theme store", () => {
|
test("addTheme writes into module theme store", () => {
|
||||||
const name = `plugin-theme-${Date.now()}`
|
const name = `plugin-theme-${Date.now()}`
|
||||||
expect(addTheme(name, DEFAULT_THEMES.opencode)).toBe(true)
|
expect(addTheme(name, DEFAULT_THEMES.opencode)).toBe(true)
|
||||||
expect(allThemes()[name]).toBeDefined()
|
expect(allThemes()[name]).toBe(DEFAULT_THEMES.opencode)
|
||||||
})
|
})
|
||||||
|
|
||||||
test("addTheme keeps first theme for duplicate names", () => {
|
test("addTheme keeps first theme for duplicate names", () => {
|
||||||
@@ -22,15 +31,92 @@ test("addTheme keeps first theme for duplicate names", () => {
|
|||||||
|
|
||||||
expect(addTheme(name, one)).toBe(true)
|
expect(addTheme(name, one)).toBe(true)
|
||||||
expect(addTheme(name, two)).toBe(false)
|
expect(addTheme(name, two)).toBe(false)
|
||||||
expect(allThemes()[name]!.theme.primary).toBe("#101010")
|
expect(allThemes()[name]).toBe(one)
|
||||||
})
|
})
|
||||||
|
|
||||||
test("addTheme ignores entries without a theme object", () => {
|
test("addTheme ignores values without a V1 theme or version", () => {
|
||||||
const name = `plugin-theme-invalid-${Date.now()}`
|
const name = `plugin-theme-invalid-${Date.now()}`
|
||||||
expect(addTheme(name, { defs: { a: "#ffffff" } })).toBe(false)
|
expect(addTheme(name, { defs: { a: "#ffffff" } })).toBe(false)
|
||||||
|
expect(addTheme(name, { light: {} })).toBe(false)
|
||||||
expect(allThemes()[name]).toBeUndefined()
|
expect(allThemes()[name]).toBeUndefined()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test("addTheme defers validation of versioned sources", () => {
|
||||||
|
const name = `plugin-theme-versioned-${Date.now()}`
|
||||||
|
expect(addTheme(name, { version: 2 })).toBe(true)
|
||||||
|
expect(() => parseTheme(allThemes()[name]!, name)).toThrow(`Invalid theme: ${name}`)
|
||||||
|
})
|
||||||
|
|
||||||
|
test("parseTheme delegates malformed V1 sources and rejects unknown versions", () => {
|
||||||
|
expect(() => parseTheme({})).toThrow()
|
||||||
|
expect(() => parseTheme({ version: 3 })).toThrow("Unsupported theme version: 3")
|
||||||
|
})
|
||||||
|
|
||||||
|
test("parses unversioned and explicit V1 themes lazily once", () => {
|
||||||
|
const unversioned = structuredClone(DEFAULT_THEMES.opencode)
|
||||||
|
const explicit = { ...structuredClone(DEFAULT_THEMES.opencode), version: 1 }
|
||||||
|
const first = parseTheme(unversioned, "unversioned")
|
||||||
|
const second = parseTheme(explicit, "explicit")
|
||||||
|
|
||||||
|
expect(first.version).toBe(2)
|
||||||
|
expect(second.version).toBe(2)
|
||||||
|
expect(parseTheme(unversioned, "unversioned")).toBe(first)
|
||||||
|
expect(parseTheme(explicit, "explicit")).toBe(second)
|
||||||
|
})
|
||||||
|
|
||||||
|
test("decodes native V2 themes lazily once", () => {
|
||||||
|
const name = `plugin-theme-v2-${Date.now()}`
|
||||||
|
const source = { version: 2, light: { categorical: ["red"] } } as const
|
||||||
|
|
||||||
|
expect(addTheme(name, source)).toBe(true)
|
||||||
|
expect(allThemes()[name]).toBe(source)
|
||||||
|
const document = parseTheme(allThemes()[name]!, name)
|
||||||
|
expect(document.light?.categorical).toEqual(["red"])
|
||||||
|
expect(parseTheme(allThemes()[name]!, name)).toBe(document)
|
||||||
|
})
|
||||||
|
|
||||||
|
test("defers invalid V2 errors until parsing", () => {
|
||||||
|
const name = `plugin-theme-invalid-v2-${Date.now()}`
|
||||||
|
expect(addTheme(name, { version: 2, light: { categorical: [] } })).toBe(true)
|
||||||
|
expect(() => parseTheme(allThemes()[name]!, name)).toThrow(`Invalid theme: ${name}`)
|
||||||
|
})
|
||||||
|
|
||||||
|
test("defers invalid V1 errors until parsing", () => {
|
||||||
|
const name = `plugin-theme-invalid-v1-${Date.now()}`
|
||||||
|
const source = structuredClone(DEFAULT_THEMES.opencode)
|
||||||
|
source.defs = { ...source.defs, one: "two", two: "one" }
|
||||||
|
source.theme.primary = "one"
|
||||||
|
|
||||||
|
expect(addTheme(name, source)).toBe(true)
|
||||||
|
expect(() => parseTheme(allThemes()[name]!, name)).toThrow("Circular color reference")
|
||||||
|
})
|
||||||
|
|
||||||
|
test("replacement sources receive independent parse caches", () => {
|
||||||
|
const name = `plugin-theme-replace-${Date.now()}`
|
||||||
|
const first = structuredClone(DEFAULT_THEMES.opencode)
|
||||||
|
const second = structuredClone(DEFAULT_THEMES.opencode)
|
||||||
|
second.theme.primary = "#123456"
|
||||||
|
|
||||||
|
expect(addTheme(name, first)).toBe(true)
|
||||||
|
const previous = parseTheme(allThemes()[name]!, name)
|
||||||
|
expect(upsertTheme(name, second)).toBe(true)
|
||||||
|
const next = parseTheme(allThemes()[name]!, name)
|
||||||
|
expect(next).not.toBe(previous)
|
||||||
|
expect(parseTheme(allThemes()[name]!, name)).toBe(next)
|
||||||
|
})
|
||||||
|
|
||||||
|
test("custom themes retain precedence over plugin themes", () => {
|
||||||
|
const name = `plugin-theme-precedence-${Date.now()}`
|
||||||
|
const plugin = structuredClone(DEFAULT_THEMES.opencode)
|
||||||
|
const custom = structuredClone(DEFAULT_THEMES.opencode)
|
||||||
|
|
||||||
|
expect(addTheme(name, plugin)).toBe(true)
|
||||||
|
setCustomThemes({ [name]: custom })
|
||||||
|
expect(allThemes()[name]).toBe(custom)
|
||||||
|
setCustomThemes({})
|
||||||
|
expect(allThemes()[name]).toBe(plugin)
|
||||||
|
})
|
||||||
|
|
||||||
test("hasTheme checks theme presence", () => {
|
test("hasTheme checks theme presence", () => {
|
||||||
const name = `plugin-theme-has-${Date.now()}`
|
const name = `plugin-theme-has-${Date.now()}`
|
||||||
expect(hasTheme(name)).toBe(false)
|
expect(hasTheme(name)).toBe(false)
|
||||||
|
|||||||
@@ -1,16 +1,21 @@
|
|||||||
import { expect, test } from "bun:test"
|
import { expect, test } from "bun:test"
|
||||||
import { RGBA } from "@opentui/core"
|
import { RGBA } from "@opentui/core"
|
||||||
|
import { parseTheme, type ThemeDocumentSource } from "../../../src/theme"
|
||||||
import { DEFAULT_THEME } from "../../../src/theme/v2/defaults"
|
import { DEFAULT_THEME } from "../../../src/theme/v2/defaults"
|
||||||
import type { ThemeDefinition } from "../../../src/theme/v2"
|
import type { Mode, ThemeDefinition } from "../../../src/theme/v2"
|
||||||
import { resolveTheme, resolveThemeFile } from "../../../src/theme/v2/resolve"
|
import { resolveTheme, resolveThemeDocument } from "../../../src/theme/v2/resolve"
|
||||||
import { selectTheme } from "../../../src/theme/v2/select"
|
import { selectTheme } from "../../../src/theme/v2/select"
|
||||||
|
|
||||||
const light = selectTheme(DEFAULT_THEME, "light")
|
const light = selectTheme(DEFAULT_THEME, "light")
|
||||||
const dark = selectTheme(DEFAULT_THEME, "dark")
|
const dark = selectTheme(DEFAULT_THEME, "dark")
|
||||||
|
|
||||||
test("resolves one-mode files with defaults for the available mode", () => {
|
function resolveSource(source: ThemeDocumentSource, mode?: Mode, name?: string) {
|
||||||
const resolvedLight = resolveThemeFile({ version: 2, light: {} }, "dark")
|
return resolveThemeDocument(parseTheme(source, name), mode)
|
||||||
const resolvedDark = resolveThemeFile({ version: 2, dark: {} }, "light")
|
}
|
||||||
|
|
||||||
|
test("resolves one-mode documents with defaults for the available mode", () => {
|
||||||
|
const resolvedLight = resolveSource({ version: 2, light: {} }, "dark")
|
||||||
|
const resolvedDark = resolveSource({ version: 2, dark: {} }, "light")
|
||||||
|
|
||||||
expect(resolvedLight.background.default.equals(resolveTheme(light).background.default)).toBeTrue()
|
expect(resolvedLight.background.default.equals(resolveTheme(light).background.default)).toBeTrue()
|
||||||
expect(resolvedDark.background.default.equals(resolveTheme(dark).background.default)).toBeTrue()
|
expect(resolvedDark.background.default.equals(resolveTheme(dark).background.default)).toBeTrue()
|
||||||
@@ -18,26 +23,19 @@ test("resolves one-mode files with defaults for the available mode", () => {
|
|||||||
expect(resolvedDark.categorical.length).toBeGreaterThan(0)
|
expect(resolvedDark.categorical.length).toBeGreaterThan(0)
|
||||||
})
|
})
|
||||||
|
|
||||||
test("rejects theme files without a mode", () => {
|
test("rejects theme documents without a mode", () => {
|
||||||
// @ts-expect-error Runtime decoding also enforces the at-least-one-mode invariant.
|
expect(() => resolveSource({ version: 2 })).toThrow("Invalid theme")
|
||||||
expect(() => resolveThemeFile({ version: 2 })).toThrow("Invalid theme")
|
|
||||||
})
|
})
|
||||||
|
|
||||||
test("validates and resolves categorical hues in configured order", () => {
|
test("validates and resolves categorical hues in configured order", () => {
|
||||||
const theme = resolveThemeFile({ version: 2, light: { categorical: ["accent", "red", "interactive"] } }, "light")
|
const theme = resolveSource({ version: 2, light: { categorical: ["accent", "red", "interactive"] } }, "light")
|
||||||
|
|
||||||
expect(theme.categorical[0]).toBe(theme.hue.accent)
|
expect(theme.categorical[0]).toBe(theme.hue.accent)
|
||||||
expect(theme.categorical[1]).toBe(theme.hue.red)
|
expect(theme.categorical[1]).toBe(theme.hue.red)
|
||||||
expect(theme.categorical[2]).toBe(theme.hue.interactive)
|
expect(theme.categorical[2]).toBe(theme.hue.interactive)
|
||||||
expect(theme.contexts["@context:elevated"]?.categorical).toBe(theme.categorical)
|
expect(theme.contexts["@context:elevated"]?.categorical).toBe(theme.categorical)
|
||||||
expect(() => resolveThemeFile({ version: 2, light: { categorical: [] } }, "light")).toThrow("Invalid theme")
|
expect(() => resolveSource({ version: 2, light: { categorical: [] } }, "light")).toThrow("Invalid theme")
|
||||||
expect(() =>
|
expect(() => resolveSource({ version: 2, light: { categorical: ["magenta"] } }, "light")).toThrow("Invalid theme")
|
||||||
resolveThemeFile(
|
|
||||||
// @ts-expect-error Runtime decoding rejects unknown categorical hue names.
|
|
||||||
{ version: 2, light: { categorical: ["magenta"] } },
|
|
||||||
"light",
|
|
||||||
),
|
|
||||||
).toThrow("Invalid theme")
|
|
||||||
})
|
})
|
||||||
|
|
||||||
test("uses the default categorical order for direct definitions", () => {
|
test("uses the default categorical order for direct definitions", () => {
|
||||||
@@ -93,7 +91,7 @@ test("resolves base hue aliases and rejects circular hue aliases", () => {
|
|||||||
...light,
|
...light,
|
||||||
hue: { ...light.hue, blue: "$hue.red", purple: "$hue.blue" },
|
hue: { ...light.hue, blue: "$hue.red", purple: "$hue.blue" },
|
||||||
})
|
})
|
||||||
const overridden = resolveThemeFile({ version: 2, light: { hue: { blue: "$hue.red" } }, dark: {} }, "light")
|
const overridden = resolveSource({ version: 2, light: { hue: { blue: "$hue.red" } }, dark: {} }, "light")
|
||||||
|
|
||||||
expect(aliased.hue.blue).not.toBe(aliased.hue.red)
|
expect(aliased.hue.blue).not.toBe(aliased.hue.red)
|
||||||
expect(aliased.hue.blue[500].equals(aliased.hue.red[500])).toBeTrue()
|
expect(aliased.hue.blue[500].equals(aliased.hue.red[500])).toBeTrue()
|
||||||
@@ -131,8 +129,8 @@ test("steps by hue source when adjacent colors have equal values", () => {
|
|||||||
expect(theme.increase(theme.hue.neutral[300])).toBe(theme.hue.neutral[400])
|
expect(theme.increase(theme.hue.neutral[300])).toBe(theme.hue.neutral[400])
|
||||||
})
|
})
|
||||||
|
|
||||||
test("merges partial files with the selected OpenCode defaults", () => {
|
test("merges partial documents with the selected OpenCode defaults", () => {
|
||||||
const theme = resolveThemeFile(
|
const theme = resolveSource(
|
||||||
{
|
{
|
||||||
version: 2,
|
version: 2,
|
||||||
light: {
|
light: {
|
||||||
@@ -150,7 +148,7 @@ test("merges partial files with the selected OpenCode defaults", () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test("expands user structural fallbacks before merging defaults", () => {
|
test("expands user structural fallbacks before merging defaults", () => {
|
||||||
const expanded = resolveThemeFile(
|
const expanded = resolveSource(
|
||||||
{
|
{
|
||||||
version: 2,
|
version: 2,
|
||||||
light: {
|
light: {
|
||||||
@@ -161,7 +159,7 @@ test("expands user structural fallbacks before merging defaults", () => {
|
|||||||
},
|
},
|
||||||
"light",
|
"light",
|
||||||
)
|
)
|
||||||
const isolatedState = resolveThemeFile(
|
const isolatedState = resolveSource(
|
||||||
{
|
{
|
||||||
version: 2,
|
version: 2,
|
||||||
light: {
|
light: {
|
||||||
@@ -181,9 +179,9 @@ test("expands user structural fallbacks before merging defaults", () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test("standalone themes skip OpenCode defaults and use the red core fallback", () => {
|
test("standalone themes skip OpenCode defaults and use the red core fallback", () => {
|
||||||
const file = { version: 2, standalone: true, light: { hue: light.hue }, dark: { hue: dark.hue } } as const
|
const document = { version: 2, standalone: true, light: { hue: light.hue }, dark: { hue: dark.hue } } as const
|
||||||
const lightTheme = resolveThemeFile(file, "light")
|
const lightTheme = resolveSource(document, "light")
|
||||||
const darkTheme = resolveThemeFile(file, "dark")
|
const darkTheme = resolveSource(document, "dark")
|
||||||
|
|
||||||
expect(lightTheme.text.default.toInts()).toEqual([255, 0, 0, 255])
|
expect(lightTheme.text.default.toInts()).toEqual([255, 0, 0, 255])
|
||||||
expect(lightTheme.background.default.toInts()).toEqual([255, 0, 0, 255])
|
expect(lightTheme.background.default.toInts()).toEqual([255, 0, 0, 255])
|
||||||
@@ -192,7 +190,7 @@ test("standalone themes skip OpenCode defaults and use the red core fallback", (
|
|||||||
})
|
})
|
||||||
|
|
||||||
test("uses defaults for the selected mode when it merges the other mode", () => {
|
test("uses defaults for the selected mode when it merges the other mode", () => {
|
||||||
const theme = resolveThemeFile(
|
const theme = resolveSource(
|
||||||
{
|
{
|
||||||
version: 2,
|
version: 2,
|
||||||
light: { hue: light.hue, background: { default: "#123456" } },
|
light: { hue: light.hue, background: { default: "#123456" } },
|
||||||
@@ -217,7 +215,7 @@ test("resolves matched action variants and states", () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test("resolves elevated hover surfaces from direct colors", () => {
|
test("resolves elevated hover surfaces from direct colors", () => {
|
||||||
const theme = resolveThemeFile(
|
const theme = resolveSource(
|
||||||
{
|
{
|
||||||
version: 2,
|
version: 2,
|
||||||
light: { background: { surface: { offset: "#123456", overlay: "#234567" } } },
|
light: { background: { surface: { offset: "#123456", overlay: "#234567" } } },
|
||||||
@@ -231,7 +229,7 @@ test("resolves elevated hover surfaces from direct colors", () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test("resolves transparent colors", () => {
|
test("resolves transparent colors", () => {
|
||||||
const theme = resolveThemeFile({
|
const theme = resolveSource({
|
||||||
version: 2,
|
version: 2,
|
||||||
light: { background: { formfield: { default: "transparent" } } },
|
light: { background: { formfield: { default: "transparent" } } },
|
||||||
dark: { background: { formfield: { default: "transparent" } } },
|
dark: { background: { formfield: { default: "transparent" } } },
|
||||||
@@ -241,7 +239,7 @@ test("resolves transparent colors", () => {
|
|||||||
|
|
||||||
test("reports theme decoding failures as native errors", () => {
|
test("reports theme decoding failures as native errors", () => {
|
||||||
expect(() =>
|
expect(() =>
|
||||||
resolveThemeFile(
|
resolveSource(
|
||||||
{
|
{
|
||||||
version: 2,
|
version: 2,
|
||||||
light: { text: { default: "opaque" } },
|
light: { text: { default: "opaque" } },
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { expect, test } from "bun:test"
|
import { expect, test } from "bun:test"
|
||||||
import type { HueDefinition, ThemeDefinition, ThemeFile } from "../../../src/theme/v2"
|
import type { HueDefinition, ThemeDefinition, ThemeDocument } from "../../../src/theme/v2"
|
||||||
import { selectTheme, selectThemeMode, supportsThemeMode, themeModes } from "../../../src/theme/v2/select"
|
import { selectTheme, selectThemeMode, supportsThemeMode, themeModes } from "../../../src/theme/v2/select"
|
||||||
|
|
||||||
const hue = {} as HueDefinition
|
const hue = {} as HueDefinition
|
||||||
@@ -11,20 +11,20 @@ const dark = {
|
|||||||
} satisfies ThemeDefinition
|
} satisfies ThemeDefinition
|
||||||
|
|
||||||
test("requires and selects independent light and dark themes", () => {
|
test("requires and selects independent light and dark themes", () => {
|
||||||
const file = { version: 2, light, dark } satisfies ThemeFile
|
const document = { version: 2, light, dark } satisfies ThemeDocument
|
||||||
expect(selectTheme(file)).toBe(light)
|
expect(selectTheme(document)).toBe(light)
|
||||||
expect(selectTheme(file, "light")).toBe(light)
|
expect(selectTheme(document, "light")).toBe(light)
|
||||||
expect(selectTheme(file, "dark")).toBe(dark)
|
expect(selectTheme(document, "dark")).toBe(dark)
|
||||||
expect(selectThemeMode(file, "dark").mode).toBe("dark")
|
expect(selectThemeMode(document, "dark").mode).toBe("dark")
|
||||||
})
|
})
|
||||||
|
|
||||||
test("merges an expanded mode override over the other mode", () => {
|
test("merges an expanded mode override over the other mode", () => {
|
||||||
const file = {
|
const document = {
|
||||||
version: 2,
|
version: 2,
|
||||||
light,
|
light,
|
||||||
dark: { mergeMode: true, text: { default: "#ffffff" } },
|
dark: { mergeMode: true, text: { default: "#ffffff" } },
|
||||||
} satisfies ThemeFile
|
} satisfies ThemeDocument
|
||||||
const selected = selectTheme(file, "dark")
|
const selected = selectTheme(document, "dark")
|
||||||
|
|
||||||
expect(selected.hue).toBeDefined()
|
expect(selected.hue).toBeDefined()
|
||||||
expect(selected.text?.default).toBe("#ffffff")
|
expect(selected.text?.default).toBe("#ffffff")
|
||||||
@@ -41,8 +41,8 @@ test("replaces categorical order in a merge mode", () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test("selects the available mode when the requested mode is missing", () => {
|
test("selects the available mode when the requested mode is missing", () => {
|
||||||
const lightOnly = { version: 2, light } satisfies ThemeFile
|
const lightOnly = { version: 2, light } satisfies ThemeDocument
|
||||||
const darkOnly = { version: 2, dark } satisfies ThemeFile
|
const darkOnly = { version: 2, dark } satisfies ThemeDocument
|
||||||
|
|
||||||
expect(themeModes(lightOnly)).toEqual(["light"])
|
expect(themeModes(lightOnly)).toEqual(["light"])
|
||||||
expect(themeModes(darkOnly)).toEqual(["dark"])
|
expect(themeModes(darkOnly)).toEqual(["dark"])
|
||||||
@@ -62,10 +62,10 @@ test("rejects a merge mode without its base mode", () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test("rejects mutual mode merging", () => {
|
test("rejects mutual mode merging", () => {
|
||||||
const file = {
|
const document = {
|
||||||
version: 2,
|
version: 2,
|
||||||
light: { mergeMode: true },
|
light: { mergeMode: true },
|
||||||
dark: { mergeMode: true },
|
dark: { mergeMode: true },
|
||||||
} satisfies ThemeFile
|
} satisfies ThemeDocument
|
||||||
expect(() => selectTheme(file)).toThrow("cannot both merge")
|
expect(() => selectTheme(document)).toThrow("cannot both merge")
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { expect, test } from "bun:test"
|
import { expect, test } from "bun:test"
|
||||||
import type { BackgroundDefinition, TextDefinition, ThemeDefinition, ThemeFile } from "../../../src/theme/v2"
|
import type { BackgroundDefinition, TextDefinition, ThemeDefinition, ThemeDocument } from "../../../src/theme/v2"
|
||||||
|
|
||||||
const text = {
|
const text = {
|
||||||
default: "$hue.neutral.900",
|
default: "$hue.neutral.900",
|
||||||
@@ -51,11 +51,11 @@ const definition = {
|
|||||||
"@context:overlay": { background: { default: "$hue.neutral.300" } },
|
"@context:overlay": { background: { default: "$hue.neutral.300" } },
|
||||||
} satisfies ThemeDefinition
|
} satisfies ThemeDefinition
|
||||||
|
|
||||||
const file = { version: 2, light: definition, dark: definition } satisfies ThemeFile
|
const document = { version: 2, light: definition, dark: definition } satisfies ThemeDocument
|
||||||
const lightOnly = { version: 2, light: definition } satisfies ThemeFile
|
const lightOnly = { version: 2, light: definition } satisfies ThemeDocument
|
||||||
const darkOnly = { version: 2, dark: definition } satisfies ThemeFile
|
const darkOnly = { version: 2, dark: definition } satisfies ThemeDocument
|
||||||
// @ts-expect-error A theme file must provide at least one mode.
|
// @ts-expect-error A theme document must provide at least one mode.
|
||||||
const empty = { version: 2 } satisfies ThemeFile
|
const empty = { version: 2 } satisfies ThemeDocument
|
||||||
|
|
||||||
test("supports property-first definitions, variants, states, and contexts", () => {
|
test("supports property-first definitions, variants, states, and contexts", () => {
|
||||||
expect(text.action.primary.$hovered).toBe("$hue.neutral.200")
|
expect(text.action.primary.$hovered).toBe("$hue.neutral.200")
|
||||||
@@ -68,7 +68,7 @@ test("supports property-first definitions, variants, states, and contexts", () =
|
|||||||
expect(definition["@context:elevated"].text?.default).toBe("$hue.neutral.800")
|
expect(definition["@context:elevated"].text?.default).toBe("$hue.neutral.800")
|
||||||
expect(definition["@context:overlay"].background?.default).toBe("$hue.neutral.300")
|
expect(definition["@context:overlay"].background?.default).toBe("$hue.neutral.300")
|
||||||
expect(definition.categorical).toEqual(["blue", "accent"])
|
expect(definition.categorical).toEqual(["blue", "accent"])
|
||||||
expect(file.light).toBe(definition)
|
expect(document.light).toBe(definition)
|
||||||
expect(lightOnly.light).toBe(definition)
|
expect(lightOnly.light).toBe(definition)
|
||||||
expect(darkOnly.dark).toBe(definition)
|
expect(darkOnly.dark).toBe(definition)
|
||||||
expect(empty.version).toBe(2)
|
expect(empty.version).toBe(2)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { expect, test } from "bun:test"
|
import { expect, test } from "bun:test"
|
||||||
import { DEFAULT_THEMES, resolveTheme as resolveV1 } from "../../../src/theme"
|
import { DEFAULT_THEMES, resolveTheme as resolveV1 } from "../../../src/theme"
|
||||||
import { resolveThemeFile } from "../../../src/theme/v2/resolve"
|
import { resolveThemeDocument } from "../../../src/theme/v2/resolve"
|
||||||
import { selectThemeMode, themeModes } from "../../../src/theme/v2/select"
|
import { selectThemeMode, themeModes } from "../../../src/theme/v2/select"
|
||||||
import { migrateV1 } from "../../../src/theme/v2/v1-migrate"
|
import { migrateV1 } from "../../../src/theme/v2/v1-migrate"
|
||||||
import { DEFAULT_CATEGORICAL, DEFAULT_THEME } from "../../../src/theme/v2/defaults"
|
import { DEFAULT_CATEGORICAL, DEFAULT_THEME } from "../../../src/theme/v2/defaults"
|
||||||
@@ -9,7 +9,7 @@ test("migrates resolved V1 modes into literal V2 tokens", () => {
|
|||||||
const migrated = migrateV1(DEFAULT_THEMES.opencode)
|
const migrated = migrateV1(DEFAULT_THEMES.opencode)
|
||||||
if (!migrated.light || !migrated.dark) throw new Error("Expected both modes")
|
if (!migrated.light || !migrated.dark) throw new Error("Expected both modes")
|
||||||
const legacy = resolveV1(DEFAULT_THEMES.opencode, "light")
|
const legacy = resolveV1(DEFAULT_THEMES.opencode, "light")
|
||||||
const resolved = resolveThemeFile(migrated, "light")
|
const resolved = resolveThemeDocument(migrated, "light")
|
||||||
|
|
||||||
expect(migrated.standalone).toBeTrue()
|
expect(migrated.standalone).toBeTrue()
|
||||||
expect(migrated.light.categorical?.length).toBeGreaterThan(0)
|
expect(migrated.light.categorical?.length).toBeGreaterThan(0)
|
||||||
@@ -83,8 +83,8 @@ test("infers chromatic hues, anchors light and dark colors, and aliases ambiguou
|
|||||||
expect(migrated.light.hue?.purple).toBe("$hue.gray")
|
expect(migrated.light.hue?.purple).toBe("$hue.gray")
|
||||||
expect(migrated.light.hue?.accent).toBe("$hue.gray")
|
expect(migrated.light.hue?.accent).toBe("$hue.gray")
|
||||||
expect(migrated.light.hue?.interactive).toBe("$hue.gray")
|
expect(migrated.light.hue?.interactive).toBe("$hue.gray")
|
||||||
expect(() => resolveThemeFile(migrated, "light")).not.toThrow()
|
expect(() => resolveThemeDocument(migrated, "light")).not.toThrow()
|
||||||
expect(() => resolveThemeFile(migrated, "dark")).not.toThrow()
|
expect(() => resolveThemeDocument(migrated, "dark")).not.toThrow()
|
||||||
})
|
})
|
||||||
|
|
||||||
test("orders categorical hues by V1 semantic color mapping", () => {
|
test("orders categorical hues by V1 semantic color mapping", () => {
|
||||||
@@ -185,7 +185,7 @@ test("migrates every built-in V1 theme in its supported modes", () => {
|
|||||||
for (const source of Object.values(DEFAULT_THEMES)) {
|
for (const source of Object.values(DEFAULT_THEMES)) {
|
||||||
const migrated = migrateV1(source)
|
const migrated = migrateV1(source)
|
||||||
for (const mode of themeModes(migrated)) {
|
for (const mode of themeModes(migrated)) {
|
||||||
expect(resolveThemeFile(migrated, mode).text.default).toBeDefined()
|
expect(resolveThemeDocument(migrated, mode).text.default).toBeDefined()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user