mirror of
https://github.com/moltbot/moltbot.git
synced 2026-05-13 15:47:28 +00:00
fix(github-copilot): mint tokens with vscode chat identity
This commit is contained in:
committed by
Peter Steinberger
parent
6d89bf65e0
commit
c20450a82b
@@ -48,6 +48,7 @@ Docs: https://docs.openclaw.ai
|
||||
- OpenAI/Codex: point gateway missing-key recovery and wizard docs at the canonical `openai/gpt-5.5` plus Codex OAuth route, and fix trajectory export errors so they suggest the valid `openclaw sessions` command.
|
||||
- Google/Gemini: normalize retired `google/gemini-3-pro-preview` primary, fallback, and model-map refs during config load and unrelated config writes so saved config keeps targeting Gemini 3.1 Pro Preview.
|
||||
- Google/Gemini: normalize retired Gemini 3 Pro Preview ids inside emitted Google provider model config, so regenerated models.json rows test `google/gemini-3.1-pro-preview`.
|
||||
- GitHub Copilot: mint short-lived Copilot API tokens with the same `vscode-chat` integration identity used by runtime requests, and refresh legacy cached tokens missing that identity so image-capable Copilot models no longer inherit the `copilot-language-server` scope. Fixes #79946.
|
||||
- Plugins/doctor: drop stale managed npm install records when `openclaw doctor --fix` removes npm packages that shadow bundled plugins, so the rebuilt registry no longer resurrects the removed package metadata.
|
||||
- Discord/voice: reuse or suppress late realtime consult tool calls without stealing newer speaker context or speaking forced fallback answers twice.
|
||||
- Discord/voice: synthesize realtime playback timestamps from emitted Discord PCM so OpenAI realtime barge-in truncation no longer sees `audioEndMs=0` and skips legitimate interruptions.
|
||||
|
||||
@@ -359,6 +359,7 @@ describe("github-copilot token", () => {
|
||||
token: "cached;proxy-ep=proxy.example.com;",
|
||||
expiresAt: now + 60 * 60 * 1000,
|
||||
updatedAt: now,
|
||||
integrationId: "vscode-chat",
|
||||
});
|
||||
|
||||
const fetchImpl = vi.fn();
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { Context } from "@mariozechner/pi-ai";
|
||||
import { buildCopilotIdeHeaders } from "../plugin-sdk/provider-auth.js";
|
||||
import { COPILOT_INTEGRATION_ID, buildCopilotIdeHeaders } from "../plugin-sdk/provider-auth.js";
|
||||
|
||||
export { buildCopilotIdeHeaders } from "../plugin-sdk/provider-auth.js";
|
||||
export { COPILOT_INTEGRATION_ID, buildCopilotIdeHeaders } from "../plugin-sdk/provider-auth.js";
|
||||
|
||||
function inferCopilotInitiator(messages: Context["messages"]): "agent" | "user" {
|
||||
const last = messages[messages.length - 1];
|
||||
@@ -43,7 +43,7 @@ export function buildCopilotDynamicHeaders(params: {
|
||||
}): Record<string, string> {
|
||||
return {
|
||||
...buildCopilotIdeHeaders(),
|
||||
"Copilot-Integration-Id": "vscode-chat",
|
||||
"Copilot-Integration-Id": COPILOT_INTEGRATION_ID,
|
||||
"Openai-Organization": "github-copilot",
|
||||
"x-initiator": inferCopilotInitiator(params.messages),
|
||||
...(params.hasImages ? { "Copilot-Vision-Request": "true" } : {}),
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { buildCopilotIdeHeaders } from "./copilot-dynamic-headers.js";
|
||||
import { COPILOT_INTEGRATION_ID, buildCopilotIdeHeaders } from "./copilot-dynamic-headers.js";
|
||||
import {
|
||||
deriveCopilotApiBaseUrlFromToken,
|
||||
resolveCopilotApiToken,
|
||||
@@ -47,7 +47,7 @@ describe("resolveCopilotApiToken", () => {
|
||||
expect(result.expiresAt).toBe(12_345_678_901_000);
|
||||
});
|
||||
|
||||
it("sends IDE headers when exchanging the GitHub token", async () => {
|
||||
it("sends IDE and integration headers when exchanging the GitHub token", async () => {
|
||||
const fetchImpl = vi.fn(async () => ({
|
||||
ok: true,
|
||||
json: async () => ({
|
||||
@@ -71,7 +71,40 @@ describe("resolveCopilotApiToken", () => {
|
||||
expect(init.headers).toEqual({
|
||||
Accept: "application/json",
|
||||
Authorization: "Bearer github-token",
|
||||
"Copilot-Integration-Id": COPILOT_INTEGRATION_ID,
|
||||
...buildCopilotIdeHeaders({ includeApiVersion: true }),
|
||||
});
|
||||
});
|
||||
|
||||
it("refreshes legacy cached tokens without the vscode-chat integration identity", async () => {
|
||||
const fetchImpl = vi.fn(async () => ({
|
||||
ok: true,
|
||||
json: async () => ({
|
||||
token: "fresh-copilot-token",
|
||||
expires_at: Math.floor(Date.now() / 1000) + 3600,
|
||||
}),
|
||||
}));
|
||||
const saveJsonFileImpl = vi.fn();
|
||||
|
||||
const result = await resolveCopilotApiToken({
|
||||
githubToken: "github-token",
|
||||
cachePath: "/tmp/github-copilot-token-test.json",
|
||||
loadJsonFileImpl: () => ({
|
||||
token: "legacy-copilot-token",
|
||||
expiresAt: Date.now() + 60 * 60 * 1000,
|
||||
updatedAt: Date.now(),
|
||||
}),
|
||||
saveJsonFileImpl,
|
||||
fetchImpl: fetchImpl as unknown as typeof fetch,
|
||||
});
|
||||
|
||||
expect(result.token).toBe("fresh-copilot-token");
|
||||
expect(fetchImpl).toHaveBeenCalledTimes(1);
|
||||
expect(saveJsonFileImpl).toHaveBeenCalledWith("/tmp/github-copilot-token-test.json", {
|
||||
token: "fresh-copilot-token",
|
||||
expiresAt: expect.any(Number),
|
||||
updatedAt: expect.any(Number),
|
||||
integrationId: COPILOT_INTEGRATION_ID,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -98,12 +98,14 @@ export const COPILOT_EDITOR_VERSION = "vscode/1.96.2";
|
||||
export const COPILOT_USER_AGENT = "GitHubCopilotChat/0.26.7";
|
||||
export const COPILOT_EDITOR_PLUGIN_VERSION = "copilot-chat/0.35.0";
|
||||
export const COPILOT_GITHUB_API_VERSION = "2025-04-01";
|
||||
export const COPILOT_INTEGRATION_ID = "vscode-chat";
|
||||
export const DEFAULT_COPILOT_API_BASE_URL = "https://api.individual.githubcopilot.com";
|
||||
|
||||
export type CachedCopilotToken = {
|
||||
token: string;
|
||||
expiresAt: number;
|
||||
updatedAt: number;
|
||||
integrationId?: string;
|
||||
};
|
||||
|
||||
export function buildCopilotIdeHeaders(
|
||||
@@ -124,7 +126,7 @@ function resolveCopilotTokenCachePath(env: NodeJS.ProcessEnv = process.env) {
|
||||
}
|
||||
|
||||
function isCopilotTokenUsable(cache: CachedCopilotToken, now = Date.now()): boolean {
|
||||
return cache.expiresAt - now > 5 * 60 * 1000;
|
||||
return cache.integrationId === COPILOT_INTEGRATION_ID && cache.expiresAt - now > 5 * 60 * 1000;
|
||||
}
|
||||
|
||||
function parseCopilotTokenResponse(value: unknown): {
|
||||
@@ -232,6 +234,7 @@ export async function resolveCopilotApiToken(params: {
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
Authorization: `Bearer ${params.githubToken}`,
|
||||
"Copilot-Integration-Id": COPILOT_INTEGRATION_ID,
|
||||
...buildCopilotIdeHeaders({ includeApiVersion: true }),
|
||||
},
|
||||
});
|
||||
@@ -245,6 +248,7 @@ export async function resolveCopilotApiToken(params: {
|
||||
token: json.token,
|
||||
expiresAt: json.expiresAt,
|
||||
updatedAt: Date.now(),
|
||||
integrationId: COPILOT_INTEGRATION_ID,
|
||||
};
|
||||
saveJsonFileFn(cachePath, payload);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user