diff --git a/src/api/providers/__tests__/opencode-go.spec.ts b/src/api/providers/__tests__/opencode-go.spec.ts index 828ffb8655..7415ea73b1 100644 --- a/src/api/providers/__tests__/opencode-go.spec.ts +++ b/src/api/providers/__tests__/opencode-go.spec.ts @@ -22,6 +22,7 @@ import { import { OpencodeGoHandler } from "../opencode-go" import { getModels } from "../fetchers/modelCache" import { ApiHandlerOptions } from "../../../shared/api" +import { Package } from "../../../shared/package" import { asyncStreamFrom, collectStream } from "../../../test-utils/stream" import { clearAllMocks } from "../../../test-utils/reset" @@ -94,6 +95,7 @@ describe("OpencodeGoHandler", () => { expect.objectContaining({ baseURL: "https://opencode.ai/zen/go/v1", apiKey: "test-key", + defaultHeaders: expect.objectContaining({ "User-Agent": `ZooCode/${Package.version}` }), }), ) }) @@ -106,6 +108,7 @@ describe("OpencodeGoHandler", () => { // NOT include the trailing `/v1` used by the OpenAI client. baseURL: "https://opencode.ai/zen/go", apiKey: "test-key", + defaultHeaders: expect.objectContaining({ "User-Agent": `ZooCode/${Package.version}` }), }), ) }) @@ -208,6 +211,17 @@ describe("OpencodeGoHandler", () => { ) }) + it("sends the stable conversation ID to chat completions", async () => { + const handler = new OpencodeGoHandler(mockOptions) + const messages: Anthropic.Messages.MessageParam[] = [{ role: "user", content: "Hi" }] + + await collectStream(handler.createMessage("sys", messages, { taskId: "conversation-123" })) + + expect(mockCreate.mock.calls[0][1]?.headers).toMatchObject({ + "x-opencode-session": "conversation-123", + }) + }) + it("forwards the model's default reasoning_effort for reasoning-capable models", async () => { const handler = new OpencodeGoHandler(mockOptions) const messages: Anthropic.Messages.MessageParam[] = [{ role: "user", content: "Hi" }] @@ -478,6 +492,17 @@ describe("OpencodeGoHandler", () => { expect(mockCreate).not.toHaveBeenCalled() }) + it("sends the stable conversation ID to the Anthropic messages endpoint", async () => { + const handler = new OpencodeGoHandler(anthropicOptions) + const messages: Anthropic.Messages.MessageParam[] = [{ role: "user", content: "Hi" }] + + await collectStream(handler.createMessage("sys", messages, { taskId: "conversation-123" })) + + expect(mockAnthropicCreate.mock.calls[0][1]?.headers).toMatchObject({ + "x-opencode-session": "conversation-123", + }) + }) + it("streams text, tool-call, usage and cost chunks from the Anthropic stream", async () => { const handler = new OpencodeGoHandler(anthropicOptions) const messages: Anthropic.Messages.MessageParam[] = [{ role: "user", content: "Hi" }] @@ -845,7 +870,10 @@ describe("OpencodeGoHandler", () => { handler.createMessage("sys", messages, { taskId: "test-task", abortSignal: controller.signal }), ) - expect(mockResponsesCreate.mock.calls[0][1]).toEqual({ signal: controller.signal }) + expect(mockResponsesCreate.mock.calls[0][1]).toEqual({ + signal: controller.signal, + headers: { "x-opencode-session": "test-task" }, + }) }) it("closes the Responses iterator when the consumer stops early", async () => { diff --git a/src/api/providers/opencode-go.ts b/src/api/providers/opencode-go.ts index 1f4faa45d6..bbfa63780a 100644 --- a/src/api/providers/opencode-go.ts +++ b/src/api/providers/opencode-go.ts @@ -247,7 +247,11 @@ export class OpencodeGoHandler extends RouterProvider implements SingleCompletio }), } - const completion = await this.client.chat.completions.create(body) + const completion = metadata?.taskId + ? await this.client.chat.completions.create(body, { + headers: { "x-opencode-session": metadata.taskId }, + }) + : await this.client.chat.completions.create(body) for await (const chunk of completion) { const delta = chunk.choices[0]?.delta @@ -378,7 +382,10 @@ export class OpencodeGoHandler extends RouterProvider implements SingleCompletio let stream: AsyncIterable try { - stream = await this.client.responses.create(requestBody, { signal: metadata?.abortSignal }) + stream = await this.client.responses.create(requestBody, { + signal: metadata?.abortSignal, + headers: metadata?.taskId ? { "x-opencode-session": metadata.taskId } : undefined, + }) } catch (error) { if (error instanceof Error) { throw new Error(`Opencode Go completion error: ${error.message}`) @@ -505,7 +512,11 @@ export class OpencodeGoHandler extends RouterProvider implements SingleCompletio // errors propagate unchanged, matching the OpenAI streaming path. let stream try { - stream = await this.anthropicClient.messages.create(requestParams) + stream = metadata?.taskId + ? await this.anthropicClient.messages.create(requestParams, { + headers: { "x-opencode-session": metadata.taskId }, + }) + : await this.anthropicClient.messages.create(requestParams) } catch (error) { if (error instanceof Error) { throw new Error(`Opencode Go completion error: ${error.message}`)