diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index 2cf41048..0d66d97d 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -13,6 +13,7 @@ ### Fixed +- Fixed OpenCode Zen/Go requests to send per-session OpenCode routing headers ([#4847](https://github.com/earendil-works/pi/issues/4847)). - Fixed Amazon Bedrock provider loading under strict package managers by inheriting the declared `@smithy/node-http-handler` dependency from `@earendil-works/pi-ai` ([#4842](https://github.com/earendil-works/pi/issues/4842)). - Fixed exported session HTML to escape quote characters in attribute values ([#4832](https://github.com/earendil-works/pi/issues/4832)). - Fixed GitHub Copilot device-code login to keep opening the verification URL in browser-capable environments while ignoring browser launch failures for headless use. diff --git a/packages/coding-agent/src/core/sdk.ts b/packages/coding-agent/src/core/sdk.ts index f19581e4..b79261ad 100644 --- a/packages/coding-agent/src/core/sdk.ts +++ b/packages/coding-agent/src/core/sdk.ts @@ -129,7 +129,15 @@ function getDefaultAgentDir(): string { function getAttributionHeaders( model: Model, settingsManager: SettingsManager, + sessionId?: string, ): Record | undefined { + if ( + sessionId && + (model.provider === "opencode" || model.provider === "opencode-go" || model.baseUrl.includes("opencode.ai")) + ) { + return { "x-opencode-session": sessionId, "x-opencode-client": "pi" }; + } + if (!isInstallTelemetryEnabled(settingsManager)) { return undefined; } @@ -332,7 +340,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {} throw new Error(auth.error); } const providerRetrySettings = settingsManager.getProviderRetrySettings(); - const attributionHeaders = getAttributionHeaders(model, settingsManager); + const attributionHeaders = getAttributionHeaders(model, settingsManager, options?.sessionId); return streamSimple(model, context, { ...options, apiKey: auth.apiKey, diff --git a/packages/coding-agent/test/sdk-openrouter-attribution.test.ts b/packages/coding-agent/test/sdk-openrouter-attribution.test.ts index 70bb8709..c7032103 100644 --- a/packages/coding-agent/test/sdk-openrouter-attribution.test.ts +++ b/packages/coding-agent/test/sdk-openrouter-attribution.test.ts @@ -86,6 +86,7 @@ describe("createAgentSession OpenRouter attribution headers", () => { telemetryEnabled?: boolean; providerHeaders?: Record; requestHeaders?: Record; + sessionId?: string; } = {}, ): Promise | undefined> { const settingsManager = SettingsManager.create(cwd, agentDir); @@ -112,6 +113,11 @@ describe("createAgentSession OpenRouter attribution headers", () => { registeredProviders.push(model.provider); } + const sessionManager = SessionManager.inMemory(cwd); + if (options.sessionId) { + sessionManager.newSession({ id: options.sessionId }); + } + const { session } = await createAgentSession({ cwd, agentDir, @@ -119,14 +125,17 @@ describe("createAgentSession OpenRouter attribution headers", () => { authStorage, modelRegistry, settingsManager, - sessionManager: SessionManager.inMemory(cwd), + sessionManager, }); try { await session.agent.streamFn( model, { messages: [] }, - options.requestHeaders ? { headers: options.requestHeaders } : undefined, + { + sessionId: session.sessionId, + ...(options.requestHeaders ? { headers: options.requestHeaders } : {}), + }, ); return capturedOptions?.headers; } finally { @@ -178,4 +187,26 @@ describe("createAgentSession OpenRouter attribution headers", () => { expect(headers?.["X-OpenRouter-Title"]).toBe("request-title"); expect(headers?.["X-OpenRouter-Categories"]).toBe("provider-category"); }); + + it("adds OpenCode session headers", async () => { + const headers = await captureHeaders(createModel("opencode", "https://opencode.ai/zen/v1"), { + sessionId: "opencode-session", + }); + + expect(headers?.["x-opencode-session"]).toBe("opencode-session"); + expect(headers?.["x-opencode-client"]).toBe("pi"); + }); + + it("lets configured OpenCode headers override the defaults", async () => { + const headers = await captureHeaders(createModel("opencode", "https://opencode.ai/zen/v1"), { + sessionId: "opencode-session", + providerHeaders: { + "x-opencode-session": "configured-session", + "x-opencode-client": "configured-client", + }, + }); + + expect(headers?.["x-opencode-session"]).toBe("configured-session"); + expect(headers?.["x-opencode-client"]).toBe("configured-client"); + }); });