fix(github-copilot): restore usage for domain-aware OAuth profiles (#131137)

This commit is contained in:
Peter Steinberger
2026-08-27 12:37:42 -07:00
committed by GitHub
parent a509254b16
commit 8e025e4767
3 changed files with 141 additions and 2 deletions
+5
View File
@@ -333,6 +333,11 @@ configured default model is never replaced.
resolves the account-specific API endpoint, and uses the stored GitHub token
for Copilot requests. You do not need to manage runtime authentication
manually.
Usage checks also use the selected profile's GitHub token. For OAuth profiles
that carry a tenant domain, usage follows that domain before the provider's
configured domain. `COPILOT_GITHUB_DOMAIN` still takes precedence.
</Accordion>
</AccordionGroup>
+7 -2
View File
@@ -725,12 +725,17 @@ export default definePluginEntry({
},
resolveUsageAuth: async (ctx) => await ctx.resolveOAuthToken(),
fetchUsageSnapshot: async (ctx) => {
const source = parseGithubCopilotApiKey(ctx.token);
const { fetchCopilotUsage } = await loadGithubCopilotRuntime();
return await fetchCopilotUsage(
ctx.token,
source.githubToken,
ctx.timeoutMs,
ctx.fetchFn,
resolveGithubCopilotDomain({ env: ctx.env, config: ctx.config }),
resolveGithubCopilotDomain({
env: ctx.env,
explicit: source.githubDomain,
config: ctx.config,
}),
);
},
});
+129
View File
@@ -0,0 +1,129 @@
import { expectDefined } from "@openclaw/normalization-core";
import type { OpenClawConfig, OpenClawPluginApi } from "openclaw/plugin-sdk/plugin-entry";
import { createTestPluginApi } from "openclaw/plugin-sdk/plugin-test-api";
import { describe, expect, it, vi } from "vitest";
import plugin from "./index.js";
function registerProvider() {
const registerProviderMock = vi.fn<OpenClawPluginApi["registerProvider"]>();
plugin.register(createTestPluginApi({ registerProvider: registerProviderMock }));
return expectDefined(registerProviderMock.mock.calls[0]?.[0], "Copilot provider registration");
}
describe("GitHub Copilot usage credential routing", () => {
it.each([
{ label: "plain public token", expectedDomain: "github.com" },
{
label: "plain token with configured tenant",
configuredDomain: "config.ghe.com",
expectedDomain: "config.ghe.com",
},
{
label: "public OAuth metadata",
credentialDomain: "github.com",
expectedDomain: "github.com",
},
{
label: "OAuth tenant without provider config",
credentialDomain: "account.ghe.com",
expectedDomain: "account.ghe.com",
},
{
label: "OAuth tenant before provider config",
credentialDomain: "account.ghe.com",
configuredDomain: "config.ghe.com",
expectedDomain: "account.ghe.com",
},
{
label: "public OAuth before provider config",
credentialDomain: "github.com",
configuredDomain: "config.ghe.com",
expectedDomain: "github.com",
},
{
label: "environment override before OAuth and config",
credentialDomain: "account.ghe.com",
configuredDomain: "config.ghe.com",
envDomain: "override.ghe.com",
expectedDomain: "override.ghe.com",
},
])("uses the raw token and correct host for $label", async (testCase) => {
const provider = registerProvider();
const token = testCase.credentialDomain
? expectDefined(
provider.formatApiKey,
"Copilot OAuth formatter",
)({
type: "oauth",
provider: "github-copilot",
refresh: "durable-token",
access: "old-access",
expires: 0,
enterpriseUrl: testCase.credentialDomain,
})
: "durable-token";
const config: OpenClawConfig = testCase.configuredDomain
? {
models: {
providers: {
"github-copilot": {
baseUrl: "https://api.githubcopilot.com",
models: [],
params: { githubDomain: testCase.configuredDomain },
},
},
},
}
: {};
const fetchFn = vi.fn<typeof fetch>(async (input, init) => {
const request = new Request(input, init);
expect(request.url).toBe(`https://api.${testCase.expectedDomain}/copilot_internal/user`);
expect(request.headers.get("authorization")).toBe("token durable-token");
return Response.json({
copilot_plan: "business",
quota_snapshots: { premium_interactions: { percent_remaining: 75 } },
});
});
const result = await expectDefined(
provider.fetchUsageSnapshot,
"Copilot usage hook",
)({
config,
env: testCase.envDomain ? { COPILOT_GITHUB_DOMAIN: testCase.envDomain } : {},
provider: "github-copilot",
token,
timeoutMs: 5000,
fetchFn,
});
expect(fetchFn).toHaveBeenCalledOnce();
expect(result).toMatchObject({
provider: "github-copilot",
plan: "business",
windows: [{ label: "Premium", usedPercent: 25 }],
});
});
it.each([
"openclaw-github-copilot-oauth:v1:invalid-json",
'openclaw-github-copilot-oauth:v1:{"token":"durable-token","githubDomain":"attacker.example"}',
])("rejects invalid credential metadata before sending a request", async (token) => {
const provider = registerProvider();
const fetchFn = vi.fn<typeof fetch>();
await expect(
expectDefined(
provider.fetchUsageSnapshot,
"Copilot usage hook",
)({
config: {},
env: {},
provider: "github-copilot",
token,
timeoutMs: 5000,
fetchFn,
}),
).rejects.toThrow("Invalid GitHub Copilot legacy OAuth credential metadata");
expect(fetchFn).not.toHaveBeenCalled();
});
});