Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 29 additions & 1 deletion src/api/providers/__tests__/opencode-go.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down Expand Up @@ -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}` }),
}),
)
})
Expand All @@ -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}` }),
}),
)
})
Expand Down Expand Up @@ -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" }]
Expand Down Expand Up @@ -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" }]
Expand Down Expand Up @@ -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 () => {
Expand Down
17 changes: 14 additions & 3 deletions src/api/providers/opencode-go.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -378,7 +382,10 @@ export class OpencodeGoHandler extends RouterProvider implements SingleCompletio

let stream: AsyncIterable<OpenAI.Responses.ResponseStreamEvent>
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}`)
Expand Down Expand Up @@ -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}`)
Expand Down
Loading