fix(coding-agent): add OpenCode session headers

closes #4847
This commit is contained in:
Armin Ronacher
2026-05-22 22:22:55 +02:00
parent 9b62f1f87c
commit 42379a37bd
3 changed files with 43 additions and 3 deletions

View File

@@ -13,6 +13,7 @@
### Fixed ### 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 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 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. - Fixed GitHub Copilot device-code login to keep opening the verification URL in browser-capable environments while ignoring browser launch failures for headless use.

View File

@@ -129,7 +129,15 @@ function getDefaultAgentDir(): string {
function getAttributionHeaders( function getAttributionHeaders(
model: Model<any>, model: Model<any>,
settingsManager: SettingsManager, settingsManager: SettingsManager,
sessionId?: string,
): Record<string, string> | undefined { ): Record<string, string> | 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)) { if (!isInstallTelemetryEnabled(settingsManager)) {
return undefined; return undefined;
} }
@@ -332,7 +340,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
throw new Error(auth.error); throw new Error(auth.error);
} }
const providerRetrySettings = settingsManager.getProviderRetrySettings(); const providerRetrySettings = settingsManager.getProviderRetrySettings();
const attributionHeaders = getAttributionHeaders(model, settingsManager); const attributionHeaders = getAttributionHeaders(model, settingsManager, options?.sessionId);
return streamSimple(model, context, { return streamSimple(model, context, {
...options, ...options,
apiKey: auth.apiKey, apiKey: auth.apiKey,

View File

@@ -86,6 +86,7 @@ describe("createAgentSession OpenRouter attribution headers", () => {
telemetryEnabled?: boolean; telemetryEnabled?: boolean;
providerHeaders?: Record<string, string>; providerHeaders?: Record<string, string>;
requestHeaders?: Record<string, string>; requestHeaders?: Record<string, string>;
sessionId?: string;
} = {}, } = {},
): Promise<Record<string, string> | undefined> { ): Promise<Record<string, string> | undefined> {
const settingsManager = SettingsManager.create(cwd, agentDir); const settingsManager = SettingsManager.create(cwd, agentDir);
@@ -112,6 +113,11 @@ describe("createAgentSession OpenRouter attribution headers", () => {
registeredProviders.push(model.provider); registeredProviders.push(model.provider);
} }
const sessionManager = SessionManager.inMemory(cwd);
if (options.sessionId) {
sessionManager.newSession({ id: options.sessionId });
}
const { session } = await createAgentSession({ const { session } = await createAgentSession({
cwd, cwd,
agentDir, agentDir,
@@ -119,14 +125,17 @@ describe("createAgentSession OpenRouter attribution headers", () => {
authStorage, authStorage,
modelRegistry, modelRegistry,
settingsManager, settingsManager,
sessionManager: SessionManager.inMemory(cwd), sessionManager,
}); });
try { try {
await session.agent.streamFn( await session.agent.streamFn(
model, model,
{ messages: [] }, { messages: [] },
options.requestHeaders ? { headers: options.requestHeaders } : undefined, {
sessionId: session.sessionId,
...(options.requestHeaders ? { headers: options.requestHeaders } : {}),
},
); );
return capturedOptions?.headers; return capturedOptions?.headers;
} finally { } finally {
@@ -178,4 +187,26 @@ describe("createAgentSession OpenRouter attribution headers", () => {
expect(headers?.["X-OpenRouter-Title"]).toBe("request-title"); expect(headers?.["X-OpenRouter-Title"]).toBe("request-title");
expect(headers?.["X-OpenRouter-Categories"]).toBe("provider-category"); 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");
});
}); });