
























@@ -1,21 +1,29 @@
11import { streamOpenAIResponses, type AssistantMessage, type Model } from "@mariozechner/pi-ai";
22import { buildCopilotDynamicHeaders } from "openclaw/plugin-sdk/provider-stream-shared";
33import { describe, expect, it } from "vitest";
4+import { resolveFirstGithubToken } from "./auth.js";
45import { wrapCopilotOpenAIResponsesStream } from "./stream.js";
56import { resolveCopilotApiToken } from "./token.js";
6778const LIVE =
89process.env.OPENCLAW_LIVE_TEST === "1" ||
910process.env.LIVE === "1" ||
1011process.env.GITHUB_COPILOT_LIVE_TEST === "1";
11-const GITHUB_TOKEN =
12+const ENV_GITHUB_TOKEN =
1213process.env.OPENCLAW_LIVE_GITHUB_COPILOT_TOKEN ??
1314process.env.COPILOT_GITHUB_TOKEN ??
1415process.env.GH_TOKEN ??
1516process.env.GITHUB_TOKEN ??
1617"";
1718const LIVE_MODEL_ID = process.env.OPENCLAW_LIVE_GITHUB_COPILOT_MODEL?.trim() || "gpt-5.4";
18-const describeLive = LIVE && GITHUB_TOKEN.trim().length > 0 ? describe : describe.skip;
19+const describeLive = LIVE ? describe : describe.skip;
20+21+type CopilotApiToken = {
22+token: string;
23+expiresAt: number;
24+source: string;
25+baseUrl: string;
26+};
19272028const ZERO_USAGE = {
2129input: 0,
@@ -99,6 +107,27 @@ function buildReplayAssistantMessage(connectionBoundId: string): AssistantMessag
99107};
100108}
101109110+async function resolveGithubTokenCandidates(): Promise<Array<{ source: string; token: string }>> {
111+const candidates: Array<{ source: string; token: string }> = [];
112+const envToken = ENV_GITHUB_TOKEN.trim();
113+if (envToken) {
114+candidates.push({ source: "env", token: envToken });
115+}
116+117+const profileEnv = {
118+ ...process.env,
119+COPILOT_GITHUB_TOKEN: "",
120+GH_TOKEN: "",
121+GITHUB_TOKEN: "",
122+};
123+const profile = await resolveFirstGithubToken({ env: profileEnv });
124+const profileToken = profile.githubToken.trim();
125+if (profileToken && !candidates.some((candidate) => candidate.token === profileToken)) {
126+candidates.push({ source: "auth-profile", token: profileToken });
127+}
128+return candidates;
129+}
130+102131function extractText(response: unknown): string {
103132const content = (response as { content?: Array<{ type?: string; text?: string }> }).content;
104133if (!Array.isArray(content)) {
@@ -114,22 +143,37 @@ function extractText(response: unknown): string {
114143describeLive("github-copilot connection-bound Responses IDs live", () => {
115144it("rewrites replayed connection-bound item IDs before sending to Copilot", async () => {
116145logProgress("start");
117-let token;
118-try {
119-logProgress("exchanging GitHub token for Copilot token");
120-token = await withTimeout(
121-"Copilot token exchange",
122-resolveCopilotApiToken({
123-githubToken: GITHUB_TOKEN,
124-fetchImpl: fetchWithTimeout,
125-}),
126-15_000,
127-);
128-} catch (error) {
129-logProgress(`skip (${error instanceof Error ? error.message : String(error)})`);
130-return;
146+const candidates = await resolveGithubTokenCandidates();
147+if (candidates.length === 0) {
148+throw new Error("No GitHub Copilot token found in env or auth profile");
149+}
150+151+let token: CopilotApiToken | undefined;
152+const failures: string[] = [];
153+for (const candidate of candidates) {
154+try {
155+logProgress(`exchanging ${candidate.source} GitHub token for Copilot token`);
156+token = await withTimeout(
157+"Copilot token exchange",
158+resolveCopilotApiToken({
159+githubToken: candidate.token,
160+fetchImpl: fetchWithTimeout,
161+}),
162+15_000,
163+);
164+logProgress(
165+`token ok via ${candidate.source} (${token.source.startsWith("cache:") ? "cache" : "fetched"})`,
166+);
167+break;
168+} catch (error) {
169+const message = error instanceof Error ? error.message : String(error);
170+failures.push(`${candidate.source}: ${message}`);
171+logProgress(`token exchange failed via ${candidate.source} (${message})`);
172+}
173+}
174+if (!token) {
175+throw new Error(`Copilot token exchange failed for all candidates: ${failures.join("; ")}`);
131176}
132-logProgress(`token ok (${token.source.startsWith("cache:") ? "cache" : "fetched"})`);
133177134178const model = buildModel(token.baseUrl);
135179const staleId = Buffer.from(`copilot-${"x".repeat(24)}`).toString("base64");
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。