diff --git a/packages/opencode/src/server/routes/provider.ts b/packages/opencode/src/server/routes/provider.ts index 64fe34f450..59a4a686b2 100644 --- a/packages/opencode/src/server/routes/provider.ts +++ b/packages/opencode/src/server/routes/provider.ts @@ -28,7 +28,7 @@ export const ProviderRoutes = lazy(() => "application/json": { schema: resolver( z.object({ - all: ModelsDev.Provider.array(), + all: Provider.Info.array(), default: z.record(z.string(), z.string()), connected: z.array(z.string()), }), diff --git a/packages/opencode/src/server/routes/session.ts b/packages/opencode/src/server/routes/session.ts index c33c5e989b..b4c13bca0f 100644 --- a/packages/opencode/src/server/routes/session.ts +++ b/packages/opencode/src/server/routes/session.ts @@ -906,7 +906,7 @@ export const SessionRoutes = lazy(() => description: "Created message", content: { "application/json": { - schema: resolver(MessageV2.Assistant), + schema: resolver(MessageV2.WithParts), }, }, }, diff --git a/packages/opencode/test/tool/webfetch.test.ts b/packages/opencode/test/tool/webfetch.test.ts index 5233f10816..00e9dcc96c 100644 --- a/packages/opencode/test/tool/webfetch.test.ts +++ b/packages/opencode/test/tool/webfetch.test.ts @@ -17,58 +17,25 @@ const ctx = { ask: async () => {}, } -type TimerID = ReturnType - -async function withFetch( - mockFetch: (input: string | URL | Request, init?: RequestInit) => Promise, - fn: () => Promise, -) { - const originalFetch = globalThis.fetch - globalThis.fetch = mockFetch as unknown as typeof fetch - try { - await fn() - } finally { - globalThis.fetch = originalFetch - } -} - -async function withTimers(fn: (state: { ids: TimerID[]; cleared: TimerID[] }) => Promise) { - const set = globalThis.setTimeout - const clear = globalThis.clearTimeout - const ids: TimerID[] = [] - const cleared: TimerID[] = [] - - globalThis.setTimeout = ((...args: Parameters) => { - const id = set(...args) - ids.push(id) - return id - }) as typeof setTimeout - - globalThis.clearTimeout = ((id?: TimerID) => { - if (id !== undefined) cleared.push(id) - return clear(id) - }) as typeof clearTimeout - - try { - await fn({ ids, cleared }) - } finally { - ids.forEach(clear) - globalThis.setTimeout = set - globalThis.clearTimeout = clear - } +async function withFetch(fetch: (req: Request) => Response | Promise, fn: (url: URL) => Promise) { + using server = Bun.serve({ port: 0, fetch }) + await fn(server.url) } describe("tool.webfetch", () => { test("returns image responses as file attachments", async () => { const bytes = new Uint8Array([137, 80, 78, 71, 13, 10, 26, 10]) await withFetch( - async () => new Response(bytes, { status: 200, headers: { "content-type": "IMAGE/PNG; charset=binary" } }), - async () => { + () => new Response(bytes, { status: 200, headers: { "content-type": "IMAGE/PNG; charset=binary" } }), + async (url) => { await Instance.provide({ directory: projectRoot, fn: async () => { const webfetch = await WebFetchTool.init() - const result = await webfetch.execute({ url: "https://example.com/image.png", format: "markdown" }, ctx) + const result = await webfetch.execute( + { url: new URL("/image.png", url).toString(), format: "markdown" }, + ctx, + ) expect(result.output).toBe("Image fetched successfully") expect(result.attachments).toBeDefined() expect(result.attachments?.length).toBe(1) @@ -87,17 +54,17 @@ describe("tool.webfetch", () => { test("keeps svg as text output", async () => { const svg = 'hello' await withFetch( - async () => + () => new Response(svg, { status: 200, headers: { "content-type": "image/svg+xml; charset=UTF-8" }, }), - async () => { + async (url) => { await Instance.provide({ directory: projectRoot, fn: async () => { const webfetch = await WebFetchTool.init() - const result = await webfetch.execute({ url: "https://example.com/image.svg", format: "html" }, ctx) + const result = await webfetch.execute({ url: new URL("/image.svg", url).toString(), format: "html" }, ctx) expect(result.output).toContain(" { test("keeps text responses as text output", async () => { await withFetch( - async () => + () => new Response("hello from webfetch", { status: 200, headers: { "content-type": "text/plain; charset=utf-8" }, }), - async () => { + async (url) => { await Instance.provide({ directory: projectRoot, fn: async () => { const webfetch = await WebFetchTool.init() - const result = await webfetch.execute({ url: "https://example.com/file.txt", format: "text" }, ctx) + const result = await webfetch.execute({ url: new URL("/file.txt", url).toString(), format: "text" }, ctx) expect(result.output).toBe("hello from webfetch") expect(result.attachments).toBeUndefined() }, @@ -126,28 +93,4 @@ describe("tool.webfetch", () => { }, ) }) - - test("clears timeout when fetch rejects", async () => { - await withTimers(async ({ ids, cleared }) => { - await withFetch( - async () => { - throw new Error("boom") - }, - async () => { - await Instance.provide({ - directory: projectRoot, - fn: async () => { - const webfetch = await WebFetchTool.init() - await expect( - webfetch.execute({ url: "https://example.com/file.txt", format: "text" }, ctx), - ).rejects.toThrow("boom") - }, - }) - }, - ) - - expect(ids).toHaveLength(1) - expect(cleared).toContain(ids[0]) - }) - }) }) diff --git a/packages/sdk/js/src/v2/gen/types.gen.ts b/packages/sdk/js/src/v2/gen/types.gen.ts index 0a9aa4358e..e21e093c6f 100644 --- a/packages/sdk/js/src/v2/gen/types.gen.ts +++ b/packages/sdk/js/src/v2/gen/types.gen.ts @@ -3936,7 +3936,10 @@ export type SessionShellResponses = { /** * Created message */ - 200: AssistantMessage + 200: { + info: Message + parts: Array + } } export type SessionShellResponse = SessionShellResponses[keyof SessionShellResponses] @@ -4212,68 +4215,7 @@ export type ProviderListResponses = { * List of providers */ 200: { - all: Array<{ - api?: string - name: string - env: Array - id: string - npm?: string - models: { - [key: string]: { - id: string - name: string - family?: string - release_date: string - attachment: boolean - reasoning: boolean - temperature: boolean - tool_call: boolean - interleaved?: - | true - | { - field: "reasoning_content" | "reasoning_details" - } - cost?: { - input: number - output: number - cache_read?: number - cache_write?: number - context_over_200k?: { - input: number - output: number - cache_read?: number - cache_write?: number - } - } - limit: { - context: number - input?: number - output: number - } - modalities?: { - input: Array<"text" | "audio" | "image" | "video" | "pdf"> - output: Array<"text" | "audio" | "image" | "video" | "pdf"> - } - experimental?: boolean - status?: "alpha" | "beta" | "deprecated" - options: { - [key: string]: unknown - } - headers?: { - [key: string]: string - } - provider?: { - npm?: string - api?: string - } - variants?: { - [key: string]: { - [key: string]: unknown - } - } - } - } - }> + all: Array default: { [key: string]: string } diff --git a/packages/sdk/openapi.json b/packages/sdk/openapi.json index 207b400a7d..0f3b2c3971 100644 --- a/packages/sdk/openapi.json +++ b/packages/sdk/openapi.json @@ -4098,7 +4098,19 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/AssistantMessage" + "type": "object", + "properties": { + "info": { + "$ref": "#/components/schemas/Message" + }, + "parts": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Part" + } + } + }, + "required": ["info", "parts"] } } } @@ -4790,211 +4802,7 @@ "all": { "type": "array", "items": { - "type": "object", - "properties": { - "api": { - "type": "string" - }, - "name": { - "type": "string" - }, - "env": { - "type": "array", - "items": { - "type": "string" - } - }, - "id": { - "type": "string" - }, - "npm": { - "type": "string" - }, - "models": { - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "family": { - "type": "string" - }, - "release_date": { - "type": "string" - }, - "attachment": { - "type": "boolean" - }, - "reasoning": { - "type": "boolean" - }, - "temperature": { - "type": "boolean" - }, - "tool_call": { - "type": "boolean" - }, - "interleaved": { - "anyOf": [ - { - "type": "boolean", - "const": true - }, - { - "type": "object", - "properties": { - "field": { - "type": "string", - "enum": ["reasoning_content", "reasoning_details"] - } - }, - "required": ["field"], - "additionalProperties": false - } - ] - }, - "cost": { - "type": "object", - "properties": { - "input": { - "type": "number" - }, - "output": { - "type": "number" - }, - "cache_read": { - "type": "number" - }, - "cache_write": { - "type": "number" - }, - "context_over_200k": { - "type": "object", - "properties": { - "input": { - "type": "number" - }, - "output": { - "type": "number" - }, - "cache_read": { - "type": "number" - }, - "cache_write": { - "type": "number" - } - }, - "required": ["input", "output"] - } - }, - "required": ["input", "output"] - }, - "limit": { - "type": "object", - "properties": { - "context": { - "type": "number" - }, - "input": { - "type": "number" - }, - "output": { - "type": "number" - } - }, - "required": ["context", "output"] - }, - "modalities": { - "type": "object", - "properties": { - "input": { - "type": "array", - "items": { - "type": "string", - "enum": ["text", "audio", "image", "video", "pdf"] - } - }, - "output": { - "type": "array", - "items": { - "type": "string", - "enum": ["text", "audio", "image", "video", "pdf"] - } - } - }, - "required": ["input", "output"] - }, - "experimental": { - "type": "boolean" - }, - "status": { - "type": "string", - "enum": ["alpha", "beta", "deprecated"] - }, - "options": { - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": {} - }, - "headers": { - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": { - "type": "string" - } - }, - "provider": { - "type": "object", - "properties": { - "npm": { - "type": "string" - }, - "api": { - "type": "string" - } - } - }, - "variants": { - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": { - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": {} - } - } - }, - "required": [ - "id", - "name", - "release_date", - "attachment", - "reasoning", - "temperature", - "tool_call", - "limit", - "options" - ] - } - } - }, - "required": ["name", "env", "id", "models"] + "$ref": "#/components/schemas/Provider" } }, "default": {