diff --git a/packages/ai/CHANGELOG.md b/packages/ai/CHANGELOG.md index 5e3df94c..01a63393 100644 --- a/packages/ai/CHANGELOG.md +++ b/packages/ai/CHANGELOG.md @@ -8,6 +8,7 @@ ### Fixed +- Fixed GitHub Copilot model availability to ignore generic `GH_TOKEN` and `GITHUB_TOKEN` environment variables, requiring OAuth login or `COPILOT_GITHUB_TOKEN` instead ([#4485](https://github.com/earendil-works/pi/issues/4485)). - Fixed Bedrock proxy handling to preserve `NO_PROXY` exclusions while using HTTP(S)-only proxy agents. - Fixed GitHub Copilot Claude test coverage to use the current Claude Sonnet 4.6 model ID. - Fixed OpenAI Responses requests for models that support disabling reasoning to send `reasoning.effort: "none"` when thinking is off. diff --git a/packages/ai/README.md b/packages/ai/README.md index e88de28e..1790a64c 100644 --- a/packages/ai/README.md +++ b/packages/ai/README.md @@ -1122,7 +1122,7 @@ In Node.js environments, you can set environment variables to avoid passing API | Xiaomi MiMo Token Plan (China) | `XIAOMI_TOKEN_PLAN_CN_API_KEY` | | Xiaomi MiMo Token Plan (Amsterdam) | `XIAOMI_TOKEN_PLAN_AMS_API_KEY` | | Xiaomi MiMo Token Plan (Singapore) | `XIAOMI_TOKEN_PLAN_SGP_API_KEY` | -| GitHub Copilot | `COPILOT_GITHUB_TOKEN` or `GH_TOKEN` or `GITHUB_TOKEN` | +| GitHub Copilot | `COPILOT_GITHUB_TOKEN` | When set, the library automatically uses these keys: diff --git a/packages/ai/src/env-api-keys.ts b/packages/ai/src/env-api-keys.ts index 846623aa..6ed76d41 100644 --- a/packages/ai/src/env-api-keys.ts +++ b/packages/ai/src/env-api-keys.ts @@ -90,7 +90,7 @@ function hasVertexAdcCredentials(): boolean { function getApiKeyEnvVars(provider: string): readonly string[] | undefined { if (provider === "github-copilot") { - return ["COPILOT_GITHUB_TOKEN", "GH_TOKEN", "GITHUB_TOKEN"]; + return ["COPILOT_GITHUB_TOKEN"]; } // ANTHROPIC_OAUTH_TOKEN takes precedence over ANTHROPIC_API_KEY diff --git a/packages/ai/test/env-api-keys.test.ts b/packages/ai/test/env-api-keys.test.ts new file mode 100644 index 00000000..6889743d --- /dev/null +++ b/packages/ai/test/env-api-keys.test.ts @@ -0,0 +1,46 @@ +import { afterEach, describe, expect, it } from "vitest"; +import { findEnvKeys, getEnvApiKey } from "../src/env-api-keys.js"; + +const originalCopilotGitHubToken = process.env.COPILOT_GITHUB_TOKEN; +const originalGhToken = process.env.GH_TOKEN; +const originalGitHubToken = process.env.GITHUB_TOKEN; + +afterEach(() => { + if (originalCopilotGitHubToken === undefined) { + delete process.env.COPILOT_GITHUB_TOKEN; + } else { + process.env.COPILOT_GITHUB_TOKEN = originalCopilotGitHubToken; + } + + if (originalGhToken === undefined) { + delete process.env.GH_TOKEN; + } else { + process.env.GH_TOKEN = originalGhToken; + } + + if (originalGitHubToken === undefined) { + delete process.env.GITHUB_TOKEN; + } else { + process.env.GITHUB_TOKEN = originalGitHubToken; + } +}); + +describe("environment API keys", () => { + it("does not treat generic GitHub tokens as GitHub Copilot credentials", () => { + delete process.env.COPILOT_GITHUB_TOKEN; + process.env.GH_TOKEN = "gh-token"; + process.env.GITHUB_TOKEN = "github-token"; + + expect(findEnvKeys("github-copilot")).toBeUndefined(); + expect(getEnvApiKey("github-copilot")).toBeUndefined(); + }); + + it("resolves GitHub Copilot credentials from COPILOT_GITHUB_TOKEN", () => { + process.env.COPILOT_GITHUB_TOKEN = "copilot-token"; + process.env.GH_TOKEN = "gh-token"; + process.env.GITHUB_TOKEN = "github-token"; + + expect(findEnvKeys("github-copilot")).toEqual(["COPILOT_GITHUB_TOKEN"]); + expect(getEnvApiKey("github-copilot")).toBe("copilot-token"); + }); +});