Skip to content

Commit 6f18dec

Browse files
authored
fix: add Copilot IDE headers to resolved models (#82275)
* fix: add copilot headers to resolved models * fix copilot header imports * fix prod typecheck
1 parent f1a55cb commit 6f18dec

5 files changed

Lines changed: 112 additions & 29 deletions

File tree

src/agents/copilot-dynamic-headers.ts

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,30 @@
11
import type { Context } from "@earendil-works/pi-ai";
2-
import { COPILOT_INTEGRATION_ID, buildCopilotIdeHeaders } from "../plugin-sdk/provider-auth.js";
32

4-
export { COPILOT_INTEGRATION_ID, buildCopilotIdeHeaders } from "../plugin-sdk/provider-auth.js";
3+
/** @deprecated GitHub Copilot provider-owned helper; do not use from third-party plugins. */
4+
export const COPILOT_EDITOR_VERSION = "vscode/1.107.0";
5+
/** @deprecated GitHub Copilot provider-owned helper; do not use from third-party plugins. */
6+
export const COPILOT_USER_AGENT = "GitHubCopilotChat/0.35.0";
7+
/** @deprecated GitHub Copilot provider-owned helper; do not use from third-party plugins. */
8+
export const COPILOT_EDITOR_PLUGIN_VERSION = "copilot-chat/0.35.0";
9+
/** @deprecated GitHub Copilot provider-owned helper; do not use from third-party plugins. */
10+
export const COPILOT_GITHUB_API_VERSION = "2025-04-01";
11+
/** @deprecated GitHub Copilot provider-owned helper; do not use from third-party plugins. */
12+
export const COPILOT_INTEGRATION_ID = "vscode-chat";
13+
14+
/** @deprecated GitHub Copilot provider-owned helper; do not use from third-party plugins. */
15+
export function buildCopilotIdeHeaders(
16+
params: {
17+
includeApiVersion?: boolean;
18+
} = {},
19+
): Record<string, string> {
20+
return {
21+
"Accept-Encoding": "identity",
22+
"Editor-Version": COPILOT_EDITOR_VERSION,
23+
"Editor-Plugin-Version": COPILOT_EDITOR_PLUGIN_VERSION,
24+
"User-Agent": COPILOT_USER_AGENT,
25+
...(params.includeApiVersion ? { "X-Github-Api-Version": COPILOT_GITHUB_API_VERSION } : {}),
26+
};
27+
}
528

629
function inferCopilotInitiator(messages: Context["messages"]): "agent" | "user" {
730
const last = messages[messages.length - 1];

src/agents/pi-embedded-runner/model.test.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,7 @@ vi.mock("./openrouter-model-capabilities.js", () => ({
147147
}));
148148

149149
import type { OpenClawConfig } from "../../config/config.js";
150+
import { COPILOT_INTEGRATION_ID, buildCopilotIdeHeaders } from "../copilot-dynamic-headers.js";
150151
import { getModelProviderLocalService } from "../provider-local-service.js";
151152
import { getModelProviderRequestTransport } from "../provider-request-config.js";
152153
import { buildForwardCompatTemplate } from "./model.forward-compat.test-support.js";
@@ -897,6 +898,40 @@ describe("resolveModel", () => {
897898
});
898899
});
899900

901+
it("adds GitHub Copilot IDE headers to dynamic resolved model headers for Pi-native compaction", () => {
902+
const result = resolveModelForTest("github-copilot", "gpt-5.5", "/tmp/agent");
903+
const model = expectResolvedModel(result) as unknown as { headers?: Record<string, string> };
904+
905+
expect(model.headers).toEqual({
906+
...buildCopilotIdeHeaders(),
907+
"Copilot-Integration-Id": COPILOT_INTEGRATION_ID,
908+
"Openai-Organization": "github-copilot",
909+
});
910+
});
911+
912+
it("adds GitHub Copilot IDE headers to configured resolved model headers for Pi-native compaction", () => {
913+
const cfg = {
914+
models: {
915+
providers: {
916+
"github-copilot": {
917+
baseUrl: "https://api.githubcopilot.com",
918+
api: "openai-responses",
919+
models: [makeModel("gpt-5.5")],
920+
},
921+
},
922+
},
923+
} as unknown as OpenClawConfig;
924+
925+
const result = resolveModelForTest("github-copilot", "gpt-5.5", "/tmp/agent", cfg);
926+
const model = expectResolvedModel(result) as unknown as { headers?: Record<string, string> };
927+
928+
expect(model.headers).toEqual({
929+
...buildCopilotIdeHeaders(),
930+
"Copilot-Integration-Id": COPILOT_INTEGRATION_ID,
931+
"Openai-Organization": "github-copilot",
932+
});
933+
});
934+
900935
it("includes provider headers in provider fallback model", () => {
901936
const cfg = {
902937
models: {

src/agents/pi-embedded-runner/model.ts

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -545,11 +545,22 @@ function applyConfiguredProviderOverrides(params: {
545545
readModelParams(discoveredModel.params),
546546
defaultModelParams,
547547
);
548+
const discoveredHeaders = sanitizeModelHeaders(discoveredModel.headers, {
549+
stripSecretRefMarkers: true,
550+
});
551+
const requestConfig = resolveProviderRequestConfig({
552+
provider: params.provider,
553+
api: discoveredModel.api,
554+
baseUrl: discoveredModel.baseUrl,
555+
discoveredHeaders,
556+
capability: "llm",
557+
transport: "stream",
558+
});
548559
return {
549560
...discoveredModel,
550561
...(resolvedParams ? { params: resolvedParams } : {}),
551562
// Discovered models originate from models.json and may contain persistence markers.
552-
headers: sanitizeModelHeaders(discoveredModel.headers, { stripSecretRefMarkers: true }),
563+
headers: requestConfig.headers,
553564
};
554565
}
555566
const configuredModel =
@@ -572,6 +583,18 @@ function applyConfiguredProviderOverrides(params: {
572583
stripSecretRefMarkers: true,
573584
});
574585
const providerParams = readModelParams(providerConfig.params);
586+
const passthroughRequestConfig = resolveProviderRequestConfig({
587+
provider: params.provider,
588+
api: discoveredModel.api,
589+
baseUrl: discoveredModel.baseUrl,
590+
discoveredHeaders,
591+
providerHeaders,
592+
modelHeaders: configuredHeaders,
593+
authHeader: providerConfig.authHeader,
594+
request: providerRequest,
595+
capability: "llm",
596+
transport: "stream",
597+
});
575598
if (
576599
!configuredModel &&
577600
!providerConfig.baseUrl &&
@@ -593,7 +616,7 @@ function applyConfiguredProviderOverrides(params: {
593616
...discoveredModel,
594617
...(resolvedParams ? { params: resolvedParams } : {}),
595618
...(requestTimeoutMs !== undefined ? { requestTimeoutMs } : {}),
596-
headers: discoveredHeaders,
619+
headers: passthroughRequestConfig.headers,
597620
};
598621
}
599622
const resolvedParams = mergeModelParams(

src/agents/provider-request-config.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import type {
77
import { assertSecretInputResolved } from "../config/types.secrets.js";
88
import type { PinnedDispatcherPolicy } from "../infra/net/ssrf.js";
99
import { normalizeLowercaseStringOrEmpty } from "../shared/string-coerce.js";
10+
import { COPILOT_INTEGRATION_ID, buildCopilotIdeHeaders } from "./copilot-dynamic-headers.js";
1011
import type {
1112
ProviderRequestCapabilities,
1213
ProviderRequestCapability,
@@ -395,6 +396,19 @@ export function normalizeBaseUrl(
395396
return raw.replace(/\/+$/, "");
396397
}
397398

399+
function resolveProviderDefaultRequestHeaders(
400+
provider: string | undefined,
401+
): Record<string, string> | undefined {
402+
if (normalizeLowercaseStringOrEmpty(provider) !== "github-copilot") {
403+
return undefined;
404+
}
405+
return {
406+
...buildCopilotIdeHeaders(),
407+
"Copilot-Integration-Id": COPILOT_INTEGRATION_ID,
408+
"Openai-Organization": "github-copilot",
409+
};
410+
}
411+
398412
function mergeProviderRequestHeaders(
399413
...headerSets: Array<Record<string, string> | undefined>
400414
): Record<string, string> | undefined {
@@ -643,6 +657,7 @@ export function resolveProviderRequestPolicyConfig(
643657
});
644658
const extraHeaders = applyResolvedAuthHeader(
645659
mergeProviderRequestHeaders(
660+
resolveProviderDefaultRequestHeaders(params.provider),
646661
params.discoveredHeaders,
647662
params.providerHeaders,
648663
params.modelHeaders,

src/plugin-sdk/provider-auth.ts

Lines changed: 12 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ import { resolveApiKeyForProfile } from "../agents/auth-profiles/oauth.js";
66
import { resolveAuthProfileOrder } from "../agents/auth-profiles/order.js";
77
import { listProfilesForProvider } from "../agents/auth-profiles/profiles.js";
88
import { ensureAuthProfileStore } from "../agents/auth-profiles/store.js";
9+
import {
10+
COPILOT_INTEGRATION_ID,
11+
buildCopilotIdeHeaders,
12+
} from "../agents/copilot-dynamic-headers.js";
913
import { resolveEnvApiKey } from "../agents/model-auth-env.js";
1014
import type { OpenClawConfig } from "../config/config.js";
1115
import { resolveStateDir } from "../config/paths.js";
@@ -94,19 +98,17 @@ export {
9498
DEFAULT_OAUTH_REFRESH_MARGIN_MS,
9599
hasUsableOAuthCredential,
96100
} from "../agents/auth-profiles/credential-state.js";
101+
export {
102+
COPILOT_EDITOR_PLUGIN_VERSION,
103+
COPILOT_EDITOR_VERSION,
104+
COPILOT_GITHUB_API_VERSION,
105+
COPILOT_INTEGRATION_ID,
106+
COPILOT_USER_AGENT,
107+
buildCopilotIdeHeaders,
108+
} from "../agents/copilot-dynamic-headers.js";
97109

98110
const COPILOT_TOKEN_URL = "https://api.github.com/copilot_internal/v2/token";
99111

100-
/** @deprecated GitHub Copilot provider-owned helper; do not use from third-party plugins. */
101-
export const COPILOT_EDITOR_VERSION = "vscode/1.107.0";
102-
/** @deprecated GitHub Copilot provider-owned helper; do not use from third-party plugins. */
103-
export const COPILOT_USER_AGENT = "GitHubCopilotChat/0.35.0";
104-
/** @deprecated GitHub Copilot provider-owned helper; do not use from third-party plugins. */
105-
export const COPILOT_EDITOR_PLUGIN_VERSION = "copilot-chat/0.35.0";
106-
/** @deprecated GitHub Copilot provider-owned helper; do not use from third-party plugins. */
107-
export const COPILOT_GITHUB_API_VERSION = "2025-04-01";
108-
/** @deprecated GitHub Copilot provider-owned helper; do not use from third-party plugins. */
109-
export const COPILOT_INTEGRATION_ID = "vscode-chat";
110112
/** @deprecated GitHub Copilot provider-owned helper; do not use from third-party plugins. */
111113
export const DEFAULT_COPILOT_API_BASE_URL = "https://api.individual.githubcopilot.com";
112114

@@ -118,21 +120,6 @@ export type CachedCopilotToken = {
118120
integrationId?: string;
119121
};
120122

121-
/** @deprecated GitHub Copilot provider-owned helper; do not use from third-party plugins. */
122-
export function buildCopilotIdeHeaders(
123-
params: {
124-
includeApiVersion?: boolean;
125-
} = {},
126-
): Record<string, string> {
127-
return {
128-
"Accept-Encoding": "identity",
129-
"Editor-Version": COPILOT_EDITOR_VERSION,
130-
"Editor-Plugin-Version": COPILOT_EDITOR_PLUGIN_VERSION,
131-
"User-Agent": COPILOT_USER_AGENT,
132-
...(params.includeApiVersion ? { "X-Github-Api-Version": COPILOT_GITHUB_API_VERSION } : {}),
133-
};
134-
}
135-
136123
function resolveCopilotTokenCachePath(env: NodeJS.ProcessEnv = process.env) {
137124
return path.join(resolveStateDir(env), "credentials", "github-copilot.token.json");
138125
}

0 commit comments

Comments
 (0)