diff --git a/packages/opencode/src/plugin/github-copilot/copilot.ts b/packages/opencode/src/plugin/github-copilot/copilot.ts index 9c744db89b..161bf859f7 100644 --- a/packages/opencode/src/plugin/github-copilot/copilot.ts +++ b/packages/opencode/src/plugin/github-copilot/copilot.ts @@ -361,6 +361,7 @@ export async function CopilotAuthPlugin(input: PluginInput): Promise { if (!incoming.model.providerID.includes("github-copilot")) return output.headers["X-GitHub-Api-Version"] = API_VERSION + output.headers["X-Interaction-Id"] = incoming.sessionID if (incoming.agent === "title") { output.headers["X-Interaction-Type"] = "agent-session-name-generation" } diff --git a/packages/opencode/test/plugin/github-copilot.test.ts b/packages/opencode/test/plugin/github-copilot.test.ts new file mode 100644 index 0000000000..cd1864b795 --- /dev/null +++ b/packages/opencode/test/plugin/github-copilot.test.ts @@ -0,0 +1,56 @@ +import { expect, test } from "bun:test" +import type { Hooks } from "@opencode-ai/plugin" +import { CopilotAuthPlugin } from "@/plugin/github-copilot/copilot" + +type ChatHeaders = NonNullable + +async function hook() { + const hooks = await CopilotAuthPlugin({ + directory: "", + project: {} as never, + worktree: "", + experimental_workspace: { register() {} }, + serverUrl: new URL("http://localhost"), + $: {} as never, + client: { + session: { + message: async () => ({ data: { parts: [] } }), + get: async () => ({ data: {} }), + }, + } as never, + }) + return hooks["chat.headers"]! +} + +function input(sessionID: string, providerID: string, npm: string) { + return { + sessionID, + agent: "build", + model: { providerID, api: { npm } }, + message: { id: "msg_test", sessionID }, + } as Parameters[0] +} + +test.each([ + ["github-copilot", "@ai-sdk/github-copilot"], + ["github-copilot", "@ai-sdk/anthropic"], + ["github-copilot-enterprise", "@ai-sdk/github-copilot"], + ["github-copilot-enterprise", "@ai-sdk/anthropic"], +])("uses the session ID for %s interaction headers with %s", async (providerID, npm) => { + const headers = await hook() + for (const sessionID of ["ses_one", "ses_one", "ses_two"]) { + const output = { headers: { "x-existing": "preserved" } } + await headers(input(sessionID, providerID, npm), output) + expect(output.headers).toMatchObject({ + "X-Interaction-Id": sessionID, + "x-existing": "preserved", + }) + } +}) + +test("does not add interaction headers to other providers", async () => { + const headers = await hook() + const output = { headers: { "x-existing": "preserved" } } + await headers(input("ses_one", "openai", "@ai-sdk/openai"), output) + expect(output.headers).toEqual({ "x-existing": "preserved" }) +})