@nick3/copilot-api 1.3.3 → 1.3.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +25 -3
- package/dist/account-DhQb2A6q.js +17 -0
- package/dist/account-DhQb2A6q.js.map +1 -0
- package/dist/{accounts-manager-H7YGTVk8.js → accounts-manager-DjGzZIcp.js} +18 -569
- package/dist/accounts-manager-DjGzZIcp.js.map +1 -0
- package/dist/accounts-registry-c7rs5Ed9.js +180 -0
- package/dist/accounts-registry-c7rs5Ed9.js.map +1 -0
- package/dist/admin/assets/index-BB9SaCFS.js +57 -0
- package/dist/admin/assets/{index-D-pIr-q0.css → index-CvffOmW7.css} +1 -1
- package/dist/admin/index.html +2 -2
- package/dist/auth-CYJuRbDb.js +241 -0
- package/dist/auth-CYJuRbDb.js.map +1 -0
- package/dist/check-usage-CVF7i8yX.js +82 -0
- package/dist/check-usage-CVF7i8yX.js.map +1 -0
- package/dist/debug-hQJWwXtC.js +82 -0
- package/dist/debug-hQJWwXtC.js.map +1 -0
- package/dist/get-copilot-token-BwP_PxV5.js +13 -0
- package/dist/get-copilot-token-BwP_PxV5.js.map +1 -0
- package/dist/main.js +25 -735
- package/dist/main.js.map +1 -1
- package/dist/paths-DoT4SZ8f.js +49 -0
- package/dist/paths-DoT4SZ8f.js.map +1 -0
- package/dist/poll-access-token-CN92flJy.js +52 -0
- package/dist/poll-access-token-CN92flJy.js.map +1 -0
- package/dist/{server-BZhJgGbw.js → server-CM_0PrbK.js} +183 -7
- package/dist/server-CM_0PrbK.js.map +1 -0
- package/dist/start-KYMwXUvr.js +302 -0
- package/dist/start-KYMwXUvr.js.map +1 -0
- package/dist/utils-BUJfM1V2.js +335 -0
- package/dist/utils-BUJfM1V2.js.map +1 -0
- package/package.json +1 -1
- package/dist/accounts-manager-H7YGTVk8.js.map +0 -1
- package/dist/admin/assets/index-C9gbhsHu.js +0 -56
- package/dist/server-BZhJgGbw.js.map +0 -1
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
import { ensurePaths } from "./paths-DoT4SZ8f.js";
|
|
2
|
+
import { addAccountToRegistry, saveAccountToken } from "./accounts-registry-c7rs5Ed9.js";
|
|
3
|
+
import { cacheMacMachineId, cacheVSCodeVersion, cacheVsCodeSessionId, getGitHubUser, state } from "./utils-BUJfM1V2.js";
|
|
4
|
+
import { parseAccountType } from "./account-DhQb2A6q.js";
|
|
5
|
+
import { getDeviceCode, pollAccessToken } from "./poll-access-token-CN92flJy.js";
|
|
6
|
+
import "./get-copilot-token-BwP_PxV5.js";
|
|
7
|
+
import { accountsManager, getModelRefreshIntervalMs, isFreeModelLoadBalancingEnabled, mergeConfigWithDefaults } from "./accounts-manager-DjGzZIcp.js";
|
|
8
|
+
import { defineCommand } from "citty";
|
|
9
|
+
import consola from "consola";
|
|
10
|
+
import clipboard from "clipboardy";
|
|
11
|
+
import { serve } from "srvx";
|
|
12
|
+
import invariant from "tiny-invariant";
|
|
13
|
+
import { getProxyForUrl } from "proxy-from-env";
|
|
14
|
+
import { Agent, ProxyAgent, setGlobalDispatcher } from "undici";
|
|
15
|
+
import { execSync } from "node:child_process";
|
|
16
|
+
import process$1 from "node:process";
|
|
17
|
+
|
|
18
|
+
//#region src/lib/proxy.ts
|
|
19
|
+
function initProxyFromEnv() {
|
|
20
|
+
if (typeof Bun !== "undefined") return;
|
|
21
|
+
try {
|
|
22
|
+
const direct = new Agent();
|
|
23
|
+
const proxies = /* @__PURE__ */ new Map();
|
|
24
|
+
setGlobalDispatcher({
|
|
25
|
+
dispatch(options, handler) {
|
|
26
|
+
try {
|
|
27
|
+
const origin = typeof options.origin === "string" ? new URL(options.origin) : options.origin;
|
|
28
|
+
const raw = getProxyForUrl(origin.toString());
|
|
29
|
+
const proxyUrl = raw && raw.length > 0 ? raw : void 0;
|
|
30
|
+
if (!proxyUrl) {
|
|
31
|
+
consola.debug(`HTTP proxy bypass: ${origin.hostname}`);
|
|
32
|
+
return direct.dispatch(options, handler);
|
|
33
|
+
}
|
|
34
|
+
let agent = proxies.get(proxyUrl);
|
|
35
|
+
if (!agent) {
|
|
36
|
+
agent = new ProxyAgent(proxyUrl);
|
|
37
|
+
proxies.set(proxyUrl, agent);
|
|
38
|
+
}
|
|
39
|
+
let label = proxyUrl;
|
|
40
|
+
try {
|
|
41
|
+
const u = new URL(proxyUrl);
|
|
42
|
+
label = `${u.protocol}//${u.host}`;
|
|
43
|
+
} catch {}
|
|
44
|
+
consola.debug(`HTTP proxy route: ${origin.hostname} via ${label}`);
|
|
45
|
+
return agent.dispatch(options, handler);
|
|
46
|
+
} catch {
|
|
47
|
+
return direct.dispatch(options, handler);
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
close() {
|
|
51
|
+
return direct.close();
|
|
52
|
+
},
|
|
53
|
+
destroy() {
|
|
54
|
+
return direct.destroy();
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
consola.debug("HTTP proxy configured from environment (per-URL)");
|
|
58
|
+
} catch (err) {
|
|
59
|
+
consola.debug("Proxy setup skipped:", err);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
//#endregion
|
|
64
|
+
//#region src/lib/shell.ts
|
|
65
|
+
function getShell() {
|
|
66
|
+
const { platform, ppid, env } = process$1;
|
|
67
|
+
if (platform === "win32") {
|
|
68
|
+
try {
|
|
69
|
+
const command = `wmic process get ParentProcessId,Name | findstr "${ppid}"`;
|
|
70
|
+
if (execSync(command, { stdio: "pipe" }).toString().toLowerCase().includes("powershell.exe")) return "powershell";
|
|
71
|
+
} catch {
|
|
72
|
+
return "cmd";
|
|
73
|
+
}
|
|
74
|
+
return "cmd";
|
|
75
|
+
} else {
|
|
76
|
+
const shellPath = env.SHELL;
|
|
77
|
+
if (shellPath) {
|
|
78
|
+
if (shellPath.endsWith("zsh")) return "zsh";
|
|
79
|
+
if (shellPath.endsWith("fish")) return "fish";
|
|
80
|
+
if (shellPath.endsWith("bash")) return "bash";
|
|
81
|
+
}
|
|
82
|
+
return "sh";
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Generates a copy-pasteable script to set multiple environment variables
|
|
87
|
+
* and run a subsequent command.
|
|
88
|
+
* @param {EnvVars} envVars - An object of environment variables to set.
|
|
89
|
+
* @param {string} commandToRun - The command to run after setting the variables.
|
|
90
|
+
* @returns {string} The formatted script string.
|
|
91
|
+
*/
|
|
92
|
+
function generateEnvScript(envVars, commandToRun = "") {
|
|
93
|
+
const shell = getShell();
|
|
94
|
+
const filteredEnvVars = Object.entries(envVars).filter(([, value]) => value !== void 0);
|
|
95
|
+
let commandBlock;
|
|
96
|
+
switch (shell) {
|
|
97
|
+
case "powershell":
|
|
98
|
+
commandBlock = filteredEnvVars.map(([key, value]) => `$env:${key} = ${value}`).join("; ");
|
|
99
|
+
break;
|
|
100
|
+
case "cmd":
|
|
101
|
+
commandBlock = filteredEnvVars.map(([key, value]) => `set ${key}=${value}`).join(" & ");
|
|
102
|
+
break;
|
|
103
|
+
case "fish":
|
|
104
|
+
commandBlock = filteredEnvVars.map(([key, value]) => `set -gx ${key} ${value}`).join("; ");
|
|
105
|
+
break;
|
|
106
|
+
default: {
|
|
107
|
+
const assignments = filteredEnvVars.map(([key, value]) => `${key}=${value}`).join(" ");
|
|
108
|
+
commandBlock = filteredEnvVars.length > 0 ? `export ${assignments}` : "";
|
|
109
|
+
break;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
if (commandBlock && commandToRun) return `${commandBlock}${shell === "cmd" ? " & " : " && "}${commandToRun}`;
|
|
113
|
+
return commandBlock || commandToRun;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
//#endregion
|
|
117
|
+
//#region src/start.ts
|
|
118
|
+
/**
|
|
119
|
+
* Run the interactive authentication flow to add a new account.
|
|
120
|
+
* Called automatically when no accounts are found.
|
|
121
|
+
*/
|
|
122
|
+
async function runAuthFlow(accountType) {
|
|
123
|
+
consola.warn("No accounts found. Starting authentication flow...");
|
|
124
|
+
const deviceResponse = await getDeviceCode();
|
|
125
|
+
consola.info(`Please enter the code "${deviceResponse.user_code}" at ${deviceResponse.verification_uri}`);
|
|
126
|
+
const token = await pollAccessToken(deviceResponse);
|
|
127
|
+
if (state.showToken) consola.info("GitHub token:", token);
|
|
128
|
+
const accountId = (await getGitHubUser({
|
|
129
|
+
githubToken: token,
|
|
130
|
+
accountType
|
|
131
|
+
})).login;
|
|
132
|
+
await saveAccountToken(accountId, token);
|
|
133
|
+
await addAccountToRegistry({
|
|
134
|
+
id: accountId,
|
|
135
|
+
accountType,
|
|
136
|
+
addedAt: Date.now()
|
|
137
|
+
});
|
|
138
|
+
consola.success(`Account "${accountId}" added successfully!`);
|
|
139
|
+
}
|
|
140
|
+
async function runServer(options) {
|
|
141
|
+
mergeConfigWithDefaults();
|
|
142
|
+
accountsManager.setFreeModelLoadBalancingEnabled(isFreeModelLoadBalancingEnabled());
|
|
143
|
+
accountsManager.setModelsRefreshIntervalMs(getModelRefreshIntervalMs());
|
|
144
|
+
if (options.proxyEnv) initProxyFromEnv();
|
|
145
|
+
state.verbose = options.verbose;
|
|
146
|
+
if (options.verbose) {
|
|
147
|
+
consola.level = 5;
|
|
148
|
+
consola.info("Verbose logging enabled");
|
|
149
|
+
}
|
|
150
|
+
state.accountType = options.accountType;
|
|
151
|
+
if (options.accountType !== "individual") consola.info(`Using ${options.accountType} plan GitHub account`);
|
|
152
|
+
state.manualApprove = options.manual;
|
|
153
|
+
state.rateLimitSeconds = options.rateLimit;
|
|
154
|
+
state.rateLimitWait = options.rateLimitWait;
|
|
155
|
+
state.showToken = options.showToken;
|
|
156
|
+
await ensurePaths();
|
|
157
|
+
await cacheVSCodeVersion();
|
|
158
|
+
cacheMacMachineId();
|
|
159
|
+
cacheVsCodeSessionId();
|
|
160
|
+
await accountsManager.initialize(state.vsCodeVersion);
|
|
161
|
+
if (options.githubToken) {
|
|
162
|
+
await accountsManager.setTemporaryAccount(options.githubToken, options.accountType);
|
|
163
|
+
consola.info("Using provided GitHub token as temporary account");
|
|
164
|
+
}
|
|
165
|
+
if (!accountsManager.hasAccounts()) try {
|
|
166
|
+
await runAuthFlow(options.accountType);
|
|
167
|
+
accountsManager.shutdown();
|
|
168
|
+
await accountsManager.initialize(state.vsCodeVersion);
|
|
169
|
+
accountsManager.setModelsRefreshIntervalMs(getModelRefreshIntervalMs());
|
|
170
|
+
} catch (error) {
|
|
171
|
+
consola.error("Failed to add account:", error);
|
|
172
|
+
process.exit(1);
|
|
173
|
+
}
|
|
174
|
+
const models = accountsManager.getFirstAccountModels();
|
|
175
|
+
consola.info(`Available models: \n${models?.data.map((model) => `- ${model.id}`).join("\n") ?? "(no models loaded)"}`);
|
|
176
|
+
const serverUrl = `http://localhost:${options.port}`;
|
|
177
|
+
if (options.claudeCode) {
|
|
178
|
+
invariant(models, "Models should be loaded by now");
|
|
179
|
+
const selectedModel = await consola.prompt("Select a model to use with Claude Code", {
|
|
180
|
+
type: "select",
|
|
181
|
+
options: models.data.map((model) => model.id)
|
|
182
|
+
});
|
|
183
|
+
const selectedSmallModel = await consola.prompt("Select a small model to use with Claude Code", {
|
|
184
|
+
type: "select",
|
|
185
|
+
options: models.data.map((model) => model.id)
|
|
186
|
+
});
|
|
187
|
+
const command = generateEnvScript({
|
|
188
|
+
ANTHROPIC_BASE_URL: serverUrl,
|
|
189
|
+
ANTHROPIC_AUTH_TOKEN: "dummy",
|
|
190
|
+
ANTHROPIC_MODEL: selectedModel,
|
|
191
|
+
ANTHROPIC_DEFAULT_SONNET_MODEL: selectedModel,
|
|
192
|
+
ANTHROPIC_SMALL_FAST_MODEL: selectedSmallModel,
|
|
193
|
+
ANTHROPIC_DEFAULT_HAIKU_MODEL: selectedSmallModel,
|
|
194
|
+
DISABLE_NON_ESSENTIAL_MODEL_CALLS: "1",
|
|
195
|
+
CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: "1"
|
|
196
|
+
}, "claude");
|
|
197
|
+
try {
|
|
198
|
+
clipboard.writeSync(command);
|
|
199
|
+
consola.success("Copied Claude Code command to clipboard!");
|
|
200
|
+
} catch {
|
|
201
|
+
consola.warn("Failed to copy to clipboard. Here is the Claude Code command:");
|
|
202
|
+
consola.log(command);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
consola.box(`🌐 Admin UI: ${serverUrl}/admin`);
|
|
206
|
+
const { server } = await import("./server-CM_0PrbK.js");
|
|
207
|
+
serve({
|
|
208
|
+
fetch: server.fetch,
|
|
209
|
+
port: options.port,
|
|
210
|
+
bun: { idleTimeout: 0 }
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
const start = defineCommand({
|
|
214
|
+
meta: {
|
|
215
|
+
name: "start",
|
|
216
|
+
description: "Start the Copilot API server"
|
|
217
|
+
},
|
|
218
|
+
args: {
|
|
219
|
+
port: {
|
|
220
|
+
alias: "p",
|
|
221
|
+
type: "string",
|
|
222
|
+
default: "4141",
|
|
223
|
+
description: "Port to listen on"
|
|
224
|
+
},
|
|
225
|
+
verbose: {
|
|
226
|
+
alias: "v",
|
|
227
|
+
type: "boolean",
|
|
228
|
+
default: false,
|
|
229
|
+
description: "Enable verbose logging"
|
|
230
|
+
},
|
|
231
|
+
"account-type": {
|
|
232
|
+
alias: "a",
|
|
233
|
+
type: "string",
|
|
234
|
+
default: "individual",
|
|
235
|
+
description: "Account type to use (individual, business, enterprise)"
|
|
236
|
+
},
|
|
237
|
+
manual: {
|
|
238
|
+
type: "boolean",
|
|
239
|
+
default: false,
|
|
240
|
+
description: "Enable manual request approval"
|
|
241
|
+
},
|
|
242
|
+
"rate-limit": {
|
|
243
|
+
alias: "r",
|
|
244
|
+
type: "string",
|
|
245
|
+
description: "Rate limit in seconds between requests"
|
|
246
|
+
},
|
|
247
|
+
wait: {
|
|
248
|
+
alias: "w",
|
|
249
|
+
type: "boolean",
|
|
250
|
+
default: false,
|
|
251
|
+
description: "Wait instead of error when rate limit is hit. Has no effect if rate limit is not set"
|
|
252
|
+
},
|
|
253
|
+
"github-token": {
|
|
254
|
+
alias: "g",
|
|
255
|
+
type: "string",
|
|
256
|
+
description: "Provide GitHub token directly (must be generated using the `auth` subcommand)"
|
|
257
|
+
},
|
|
258
|
+
"claude-code": {
|
|
259
|
+
alias: "c",
|
|
260
|
+
type: "boolean",
|
|
261
|
+
default: false,
|
|
262
|
+
description: "Generate a command to launch Claude Code with Copilot API config"
|
|
263
|
+
},
|
|
264
|
+
"show-token": {
|
|
265
|
+
type: "boolean",
|
|
266
|
+
default: false,
|
|
267
|
+
description: "Show GitHub and Copilot tokens on fetch and refresh"
|
|
268
|
+
},
|
|
269
|
+
"proxy-env": {
|
|
270
|
+
type: "boolean",
|
|
271
|
+
default: false,
|
|
272
|
+
description: "Initialize proxy from environment variables"
|
|
273
|
+
}
|
|
274
|
+
},
|
|
275
|
+
run({ args }) {
|
|
276
|
+
const rateLimitRaw = args["rate-limit"];
|
|
277
|
+
const rateLimit = rateLimitRaw === void 0 ? void 0 : Number.parseInt(rateLimitRaw, 10);
|
|
278
|
+
let accountType;
|
|
279
|
+
try {
|
|
280
|
+
accountType = parseAccountType(args["account-type"]);
|
|
281
|
+
} catch (error) {
|
|
282
|
+
consola.error(error instanceof Error ? error.message : String(error));
|
|
283
|
+
process.exit(1);
|
|
284
|
+
}
|
|
285
|
+
return runServer({
|
|
286
|
+
port: Number.parseInt(args.port, 10),
|
|
287
|
+
verbose: args.verbose,
|
|
288
|
+
accountType,
|
|
289
|
+
manual: args.manual,
|
|
290
|
+
rateLimit,
|
|
291
|
+
rateLimitWait: args.wait,
|
|
292
|
+
githubToken: args["github-token"],
|
|
293
|
+
claudeCode: args["claude-code"],
|
|
294
|
+
showToken: args["show-token"],
|
|
295
|
+
proxyEnv: args["proxy-env"]
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
//#endregion
|
|
301
|
+
export { start };
|
|
302
|
+
//# sourceMappingURL=start-KYMwXUvr.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"start-KYMwXUvr.js","names":["process","commandBlock: string","accountType: AccountType"],"sources":["../src/lib/proxy.ts","../src/lib/shell.ts","../src/start.ts"],"sourcesContent":["import consola from \"consola\"\nimport { getProxyForUrl } from \"proxy-from-env\"\nimport { Agent, ProxyAgent, setGlobalDispatcher, type Dispatcher } from \"undici\"\n\nexport function initProxyFromEnv(): void {\n if (typeof Bun !== \"undefined\") return\n\n try {\n const direct = new Agent()\n const proxies = new Map<string, ProxyAgent>()\n\n // We only need a minimal dispatcher that implements `dispatch` at runtime.\n // Typing the object as `Dispatcher` forces TypeScript to require many\n // additional methods. Instead, keep a plain object and cast when passing\n // to `setGlobalDispatcher`.\n const dispatcher = {\n dispatch(\n options: Dispatcher.DispatchOptions,\n handler: Dispatcher.DispatchHandler,\n ) {\n try {\n const origin =\n typeof options.origin === \"string\" ?\n new URL(options.origin)\n : (options.origin as URL)\n const get = getProxyForUrl as unknown as (\n u: string,\n ) => string | undefined\n const raw = get(origin.toString())\n const proxyUrl = raw && raw.length > 0 ? raw : undefined\n if (!proxyUrl) {\n consola.debug(`HTTP proxy bypass: ${origin.hostname}`)\n return (direct as unknown as Dispatcher).dispatch(options, handler)\n }\n let agent = proxies.get(proxyUrl)\n if (!agent) {\n agent = new ProxyAgent(proxyUrl)\n proxies.set(proxyUrl, agent)\n }\n let label = proxyUrl\n try {\n const u = new URL(proxyUrl)\n label = `${u.protocol}//${u.host}`\n } catch {\n /* noop */\n }\n consola.debug(`HTTP proxy route: ${origin.hostname} via ${label}`)\n return (agent as unknown as Dispatcher).dispatch(options, handler)\n } catch {\n return (direct as unknown as Dispatcher).dispatch(options, handler)\n }\n },\n close() {\n return direct.close()\n },\n destroy() {\n return direct.destroy()\n },\n }\n\n setGlobalDispatcher(dispatcher as unknown as Dispatcher)\n consola.debug(\"HTTP proxy configured from environment (per-URL)\")\n } catch (err) {\n consola.debug(\"Proxy setup skipped:\", err)\n }\n}\n","import { execSync } from \"node:child_process\"\nimport process from \"node:process\"\n\ntype ShellName = \"bash\" | \"zsh\" | \"fish\" | \"powershell\" | \"cmd\" | \"sh\"\ntype EnvVars = Record<string, string | undefined>\n\nfunction getShell(): ShellName {\n const { platform, ppid, env } = process\n\n if (platform === \"win32\") {\n try {\n const command = `wmic process get ParentProcessId,Name | findstr \"${ppid}\"`\n const parentProcess = execSync(command, { stdio: \"pipe\" }).toString()\n\n if (parentProcess.toLowerCase().includes(\"powershell.exe\")) {\n return \"powershell\"\n }\n } catch {\n return \"cmd\"\n }\n\n return \"cmd\"\n } else {\n const shellPath = env.SHELL\n if (shellPath) {\n if (shellPath.endsWith(\"zsh\")) return \"zsh\"\n if (shellPath.endsWith(\"fish\")) return \"fish\"\n if (shellPath.endsWith(\"bash\")) return \"bash\"\n }\n\n return \"sh\"\n }\n}\n\n/**\n * Generates a copy-pasteable script to set multiple environment variables\n * and run a subsequent command.\n * @param {EnvVars} envVars - An object of environment variables to set.\n * @param {string} commandToRun - The command to run after setting the variables.\n * @returns {string} The formatted script string.\n */\nexport function generateEnvScript(\n envVars: EnvVars,\n commandToRun: string = \"\",\n): string {\n const shell = getShell()\n const filteredEnvVars = Object.entries(envVars).filter(\n ([, value]) => value !== undefined,\n ) as Array<[string, string]>\n\n let commandBlock: string\n\n switch (shell) {\n case \"powershell\": {\n commandBlock = filteredEnvVars\n .map(([key, value]) => `$env:${key} = ${value}`)\n .join(\"; \")\n break\n }\n case \"cmd\": {\n commandBlock = filteredEnvVars\n .map(([key, value]) => `set ${key}=${value}`)\n .join(\" & \")\n break\n }\n case \"fish\": {\n commandBlock = filteredEnvVars\n .map(([key, value]) => `set -gx ${key} ${value}`)\n .join(\"; \")\n break\n }\n default: {\n // bash, zsh, sh\n const assignments = filteredEnvVars\n .map(([key, value]) => `${key}=${value}`)\n .join(\" \")\n commandBlock = filteredEnvVars.length > 0 ? `export ${assignments}` : \"\"\n break\n }\n }\n\n if (commandBlock && commandToRun) {\n const separator = shell === \"cmd\" ? \" & \" : \" && \"\n return `${commandBlock}${separator}${commandToRun}`\n }\n\n return commandBlock || commandToRun\n}\n","#!/usr/bin/env node\n\nimport { defineCommand } from \"citty\"\nimport clipboard from \"clipboardy\"\nimport consola from \"consola\"\nimport { serve, type ServerHandler } from \"srvx\"\nimport invariant from \"tiny-invariant\"\n\nimport { accountsManager } from \"./lib/accounts-manager\"\nimport { addAccountToRegistry, saveAccountToken } from \"./lib/accounts-registry\"\nimport {\n getModelRefreshIntervalMs,\n isFreeModelLoadBalancingEnabled,\n mergeConfigWithDefaults,\n} from \"./lib/config\"\nimport { ensurePaths } from \"./lib/paths\"\nimport { initProxyFromEnv } from \"./lib/proxy\"\nimport { generateEnvScript } from \"./lib/shell\"\nimport { state } from \"./lib/state\"\nimport { parseAccountType, type AccountType } from \"./lib/types/account\"\nimport {\n cacheMacMachineId,\n cacheVSCodeVersion,\n cacheVsCodeSessionId,\n} from \"./lib/utils\"\nimport { getDeviceCode } from \"./services/github/get-device-code\"\nimport { getGitHubUser } from \"./services/github/get-user\"\nimport { pollAccessToken } from \"./services/github/poll-access-token\"\n\ninterface RunServerOptions {\n port: number\n verbose: boolean\n accountType: AccountType\n manual: boolean\n rateLimit?: number\n rateLimitWait: boolean\n githubToken?: string\n claudeCode: boolean\n showToken: boolean\n proxyEnv: boolean\n}\n\n/**\n * Run the interactive authentication flow to add a new account.\n * Called automatically when no accounts are found.\n */\nasync function runAuthFlow(accountType: AccountType): Promise<void> {\n consola.warn(\"No accounts found. Starting authentication flow...\")\n\n // Start device code flow\n const deviceResponse = await getDeviceCode()\n consola.info(\n `Please enter the code \"${deviceResponse.user_code}\" at ${deviceResponse.verification_uri}`,\n )\n\n // Poll for access token\n const token = await pollAccessToken(deviceResponse)\n\n if (state.showToken) {\n consola.info(\"GitHub token:\", token)\n }\n\n // Get user info to determine account ID\n const user = await getGitHubUser({\n githubToken: token,\n accountType,\n })\n const accountId = user.login\n\n // Save token and add to registry\n await saveAccountToken(accountId, token)\n await addAccountToRegistry({\n id: accountId,\n accountType,\n addedAt: Date.now(),\n })\n\n consola.success(`Account \"${accountId}\" added successfully!`)\n}\n\nexport async function runServer(options: RunServerOptions): Promise<void> {\n // Ensure config is merged with defaults at startup\n mergeConfigWithDefaults()\n accountsManager.setFreeModelLoadBalancingEnabled(\n isFreeModelLoadBalancingEnabled(),\n )\n accountsManager.setModelsRefreshIntervalMs(getModelRefreshIntervalMs())\n\n if (options.proxyEnv) {\n initProxyFromEnv()\n }\n\n state.verbose = options.verbose\n if (options.verbose) {\n consola.level = 5\n consola.info(\"Verbose logging enabled\")\n }\n\n state.accountType = options.accountType\n if (options.accountType !== \"individual\") {\n consola.info(`Using ${options.accountType} plan GitHub account`)\n }\n\n state.manualApprove = options.manual\n state.rateLimitSeconds = options.rateLimit\n state.rateLimitWait = options.rateLimitWait\n state.showToken = options.showToken\n\n await ensurePaths()\n await cacheVSCodeVersion()\n cacheMacMachineId()\n cacheVsCodeSessionId()\n\n // Initialize accounts manager with VS Code version\n await accountsManager.initialize(state.vsCodeVersion)\n\n // If --github-token is provided, set it as a temporary (high priority) account\n if (options.githubToken) {\n await accountsManager.setTemporaryAccount(\n options.githubToken,\n options.accountType,\n )\n consola.info(\"Using provided GitHub token as temporary account\")\n }\n\n // Check if we have any accounts, if not, start the auth flow\n if (!accountsManager.hasAccounts()) {\n try {\n await runAuthFlow(options.accountType)\n\n // Re-initialize accounts manager with the new account\n accountsManager.shutdown()\n await accountsManager.initialize(state.vsCodeVersion)\n accountsManager.setModelsRefreshIntervalMs(getModelRefreshIntervalMs())\n } catch (error) {\n consola.error(\"Failed to add account:\", error)\n process.exit(1)\n }\n }\n\n // Get models from the first available account\n const models = accountsManager.getFirstAccountModels()\n\n consola.info(\n `Available models: \\n${models?.data.map((model) => `- ${model.id}`).join(\"\\n\") ?? \"(no models loaded)\"}`,\n )\n\n const serverUrl = `http://localhost:${options.port}`\n\n if (options.claudeCode) {\n invariant(models, \"Models should be loaded by now\")\n\n const selectedModel = await consola.prompt(\n \"Select a model to use with Claude Code\",\n {\n type: \"select\",\n options: models.data.map((model) => model.id),\n },\n )\n\n const selectedSmallModel = await consola.prompt(\n \"Select a small model to use with Claude Code\",\n {\n type: \"select\",\n options: models.data.map((model) => model.id),\n },\n )\n\n const command = generateEnvScript(\n {\n ANTHROPIC_BASE_URL: serverUrl,\n ANTHROPIC_AUTH_TOKEN: \"dummy\",\n ANTHROPIC_MODEL: selectedModel,\n ANTHROPIC_DEFAULT_SONNET_MODEL: selectedModel,\n ANTHROPIC_SMALL_FAST_MODEL: selectedSmallModel,\n ANTHROPIC_DEFAULT_HAIKU_MODEL: selectedSmallModel,\n DISABLE_NON_ESSENTIAL_MODEL_CALLS: \"1\",\n CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: \"1\",\n },\n \"claude\",\n )\n\n try {\n clipboard.writeSync(command)\n consola.success(\"Copied Claude Code command to clipboard!\")\n } catch {\n consola.warn(\n \"Failed to copy to clipboard. Here is the Claude Code command:\",\n )\n consola.log(command)\n }\n }\n\n consola.box(`🌐 Admin UI: ${serverUrl}/admin`)\n\n const { server } = await import(\"./server\")\n\n serve({\n fetch: server.fetch as ServerHandler,\n port: options.port,\n bun: {\n idleTimeout: 0,\n },\n })\n}\n\nexport const start = defineCommand({\n meta: {\n name: \"start\",\n description: \"Start the Copilot API server\",\n },\n args: {\n port: {\n alias: \"p\",\n type: \"string\",\n default: \"4141\",\n description: \"Port to listen on\",\n },\n verbose: {\n alias: \"v\",\n type: \"boolean\",\n default: false,\n description: \"Enable verbose logging\",\n },\n \"account-type\": {\n alias: \"a\",\n type: \"string\",\n default: \"individual\",\n description: \"Account type to use (individual, business, enterprise)\",\n },\n manual: {\n type: \"boolean\",\n default: false,\n description: \"Enable manual request approval\",\n },\n \"rate-limit\": {\n alias: \"r\",\n type: \"string\",\n description: \"Rate limit in seconds between requests\",\n },\n wait: {\n alias: \"w\",\n type: \"boolean\",\n default: false,\n description:\n \"Wait instead of error when rate limit is hit. Has no effect if rate limit is not set\",\n },\n \"github-token\": {\n alias: \"g\",\n type: \"string\",\n description:\n \"Provide GitHub token directly (must be generated using the `auth` subcommand)\",\n },\n \"claude-code\": {\n alias: \"c\",\n type: \"boolean\",\n default: false,\n description:\n \"Generate a command to launch Claude Code with Copilot API config\",\n },\n \"show-token\": {\n type: \"boolean\",\n default: false,\n description: \"Show GitHub and Copilot tokens on fetch and refresh\",\n },\n \"proxy-env\": {\n type: \"boolean\",\n default: false,\n description: \"Initialize proxy from environment variables\",\n },\n },\n run({ args }) {\n const rateLimitRaw = args[\"rate-limit\"]\n const rateLimit =\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n rateLimitRaw === undefined ? undefined : Number.parseInt(rateLimitRaw, 10)\n\n let accountType: AccountType\n try {\n accountType = parseAccountType(args[\"account-type\"])\n } catch (error) {\n consola.error(error instanceof Error ? error.message : String(error))\n process.exit(1)\n }\n\n return runServer({\n port: Number.parseInt(args.port, 10),\n verbose: args.verbose,\n accountType,\n manual: args.manual,\n rateLimit,\n rateLimitWait: args.wait,\n githubToken: args[\"github-token\"],\n claudeCode: args[\"claude-code\"],\n showToken: args[\"show-token\"],\n proxyEnv: args[\"proxy-env\"],\n })\n },\n})\n"],"mappings":"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"}
|
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
import { PATHS } from "./paths-DoT4SZ8f.js";
|
|
2
|
+
import consola from "consola";
|
|
3
|
+
import fs from "node:fs/promises";
|
|
4
|
+
import { networkInterfaces } from "node:os";
|
|
5
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
6
|
+
|
|
7
|
+
//#region src/lib/state.ts
|
|
8
|
+
const state = {
|
|
9
|
+
accountType: "individual",
|
|
10
|
+
manualApprove: false,
|
|
11
|
+
rateLimitWait: false,
|
|
12
|
+
showToken: false,
|
|
13
|
+
verbose: false
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Create an AccountContext from the current global state.
|
|
17
|
+
* This is a compatibility layer for transitioning to multi-account support.
|
|
18
|
+
* @throws Error if githubToken is not set in state
|
|
19
|
+
*/
|
|
20
|
+
function accountFromState() {
|
|
21
|
+
if (!state.githubToken) throw new Error("GitHub token not set in state");
|
|
22
|
+
return {
|
|
23
|
+
githubToken: state.githubToken,
|
|
24
|
+
copilotToken: state.copilotToken,
|
|
25
|
+
accountType: state.accountType,
|
|
26
|
+
vsCodeVersion: state.vsCodeVersion
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
//#endregion
|
|
31
|
+
//#region src/lib/api-config.ts
|
|
32
|
+
const isOpencodeOauthApp = () => {
|
|
33
|
+
return process.env.COPILOT_API_OAUTH_APP?.trim() === "opencode";
|
|
34
|
+
};
|
|
35
|
+
const normalizeDomain = (input) => {
|
|
36
|
+
return input.trim().replace(/^https?:\/\//u, "").replace(/\/+$/u, "");
|
|
37
|
+
};
|
|
38
|
+
const getEnterpriseDomain = () => {
|
|
39
|
+
const raw = (process.env.COPILOT_API_ENTERPRISE_URL ?? "").trim();
|
|
40
|
+
if (!raw) return null;
|
|
41
|
+
return normalizeDomain(raw) || null;
|
|
42
|
+
};
|
|
43
|
+
const getGitHubBaseUrl = () => {
|
|
44
|
+
const resolvedDomain = getEnterpriseDomain();
|
|
45
|
+
return resolvedDomain ? `https://${resolvedDomain}` : GITHUB_BASE_URL;
|
|
46
|
+
};
|
|
47
|
+
const getGitHubApiBaseUrl = () => {
|
|
48
|
+
const resolvedDomain = getEnterpriseDomain();
|
|
49
|
+
return resolvedDomain ? `https://${resolvedDomain}/api/v3` : GITHUB_API_BASE_URL;
|
|
50
|
+
};
|
|
51
|
+
const getOpencodeOauthHeaders = () => {
|
|
52
|
+
return {
|
|
53
|
+
Accept: "application/json",
|
|
54
|
+
"Content-Type": "application/json",
|
|
55
|
+
"User-Agent": "opencode/1.2.16 ai-sdk/provider-utils/3.0.21 runtime/bun/1.3.10, opencode/1.2.16"
|
|
56
|
+
};
|
|
57
|
+
};
|
|
58
|
+
const getOauthUrls = () => {
|
|
59
|
+
const githubBaseUrl = getGitHubBaseUrl();
|
|
60
|
+
return {
|
|
61
|
+
deviceCodeUrl: `${githubBaseUrl}/login/device/code`,
|
|
62
|
+
accessTokenUrl: `${githubBaseUrl}/login/oauth/access_token`
|
|
63
|
+
};
|
|
64
|
+
};
|
|
65
|
+
const getOauthAppConfig = () => {
|
|
66
|
+
if (isOpencodeOauthApp()) return {
|
|
67
|
+
clientId: OPENCODE_GITHUB_CLIENT_ID,
|
|
68
|
+
headers: getOpencodeOauthHeaders(),
|
|
69
|
+
scope: GITHUB_APP_SCOPES
|
|
70
|
+
};
|
|
71
|
+
return {
|
|
72
|
+
clientId: GITHUB_CLIENT_ID,
|
|
73
|
+
headers: standardHeaders(),
|
|
74
|
+
scope: GITHUB_APP_SCOPES
|
|
75
|
+
};
|
|
76
|
+
};
|
|
77
|
+
const prepareInteractionHeaders = (sessionId, isSubagent, headers) => {
|
|
78
|
+
const sendInteractionHeaders = !isOpencodeOauthApp();
|
|
79
|
+
if (isSubagent) {
|
|
80
|
+
headers["x-initiator"] = "agent";
|
|
81
|
+
if (sendInteractionHeaders) headers["x-interaction-type"] = "conversation-subagent";
|
|
82
|
+
}
|
|
83
|
+
if (sessionId && sendInteractionHeaders) headers["x-interaction-id"] = sessionId;
|
|
84
|
+
};
|
|
85
|
+
const standardHeaders = () => ({
|
|
86
|
+
"content-type": "application/json",
|
|
87
|
+
accept: "application/json"
|
|
88
|
+
});
|
|
89
|
+
const COPILOT_VERSION = "0.38.2";
|
|
90
|
+
const EDITOR_PLUGIN_VERSION = `copilot-chat/${COPILOT_VERSION}`;
|
|
91
|
+
const USER_AGENT = `GitHubCopilotChat/${COPILOT_VERSION}`;
|
|
92
|
+
const API_VERSION = "2025-10-01";
|
|
93
|
+
const copilotBaseUrl = (account) => {
|
|
94
|
+
const enterpriseDomain = getEnterpriseDomain();
|
|
95
|
+
if (enterpriseDomain) return `https://copilot-api.${enterpriseDomain}`;
|
|
96
|
+
return account.accountType === "individual" ? "https://api.githubcopilot.com" : `https://api.${account.accountType}.githubcopilot.com`;
|
|
97
|
+
};
|
|
98
|
+
const copilotHeaders = (account, vision = false, requestId) => {
|
|
99
|
+
if (isOpencodeOauthApp()) {
|
|
100
|
+
const headers$1 = {
|
|
101
|
+
Authorization: `Bearer ${account.copilotToken ?? account.githubToken}`,
|
|
102
|
+
...getOpencodeOauthHeaders(),
|
|
103
|
+
"Openai-Intent": "conversation-edits"
|
|
104
|
+
};
|
|
105
|
+
if (vision) headers$1["Copilot-Vision-Request"] = "true";
|
|
106
|
+
return headers$1;
|
|
107
|
+
}
|
|
108
|
+
const resolvedRequestId = requestId ?? randomUUID();
|
|
109
|
+
const headers = {
|
|
110
|
+
Authorization: `Bearer ${account.copilotToken}`,
|
|
111
|
+
"content-type": standardHeaders()["content-type"],
|
|
112
|
+
"copilot-integration-id": "vscode-chat",
|
|
113
|
+
"editor-version": `vscode/${account.vsCodeVersion}`,
|
|
114
|
+
"editor-plugin-version": EDITOR_PLUGIN_VERSION,
|
|
115
|
+
"user-agent": USER_AGENT,
|
|
116
|
+
"openai-intent": "conversation-agent",
|
|
117
|
+
"x-github-api-version": API_VERSION,
|
|
118
|
+
"x-request-id": resolvedRequestId,
|
|
119
|
+
"x-vscode-user-agent-library-version": "electron-fetch",
|
|
120
|
+
"x-agent-task-id": resolvedRequestId,
|
|
121
|
+
"x-interaction-type": "conversation-agent"
|
|
122
|
+
};
|
|
123
|
+
if (vision) headers["copilot-vision-request"] = "true";
|
|
124
|
+
if (state.macMachineId) headers["vscode-machineid"] = state.macMachineId;
|
|
125
|
+
if (state.vsCodeSessionId) headers["vscode-sessionid"] = state.vsCodeSessionId;
|
|
126
|
+
return headers;
|
|
127
|
+
};
|
|
128
|
+
const GITHUB_API_BASE_URL = "https://api.github.com";
|
|
129
|
+
const githubHeaders = (account) => ({
|
|
130
|
+
...standardHeaders(),
|
|
131
|
+
authorization: `token ${account.githubToken}`,
|
|
132
|
+
"editor-version": `vscode/${account.vsCodeVersion}`,
|
|
133
|
+
"editor-plugin-version": EDITOR_PLUGIN_VERSION,
|
|
134
|
+
"user-agent": USER_AGENT,
|
|
135
|
+
"x-github-api-version": API_VERSION,
|
|
136
|
+
"x-vscode-user-agent-library-version": "electron-fetch"
|
|
137
|
+
});
|
|
138
|
+
const GITHUB_BASE_URL = "https://github.com";
|
|
139
|
+
const GITHUB_CLIENT_ID = "Iv1.b507a08c87ecfe98";
|
|
140
|
+
const GITHUB_APP_SCOPES = ["read:user"].join(" ");
|
|
141
|
+
const OPENCODE_GITHUB_CLIENT_ID = "Ov23li8tweQw6odWQebz";
|
|
142
|
+
|
|
143
|
+
//#endregion
|
|
144
|
+
//#region src/lib/error.ts
|
|
145
|
+
var HTTPError = class extends Error {
|
|
146
|
+
response;
|
|
147
|
+
constructor(message, response) {
|
|
148
|
+
super(message);
|
|
149
|
+
this.response = response;
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
async function forwardError(c, error) {
|
|
153
|
+
consola.error("Error occurred:", error);
|
|
154
|
+
if (error instanceof HTTPError) {
|
|
155
|
+
const errorText = await error.response.text();
|
|
156
|
+
let errorJson;
|
|
157
|
+
try {
|
|
158
|
+
errorJson = JSON.parse(errorText);
|
|
159
|
+
} catch {
|
|
160
|
+
errorJson = errorText;
|
|
161
|
+
}
|
|
162
|
+
consola.error("HTTP error:", errorJson);
|
|
163
|
+
return c.json({ error: {
|
|
164
|
+
message: errorText,
|
|
165
|
+
type: "error"
|
|
166
|
+
} }, error.response.status);
|
|
167
|
+
}
|
|
168
|
+
return c.json({ error: {
|
|
169
|
+
message: error.message,
|
|
170
|
+
type: "error"
|
|
171
|
+
} }, 500);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
//#endregion
|
|
175
|
+
//#region src/services/github/get-copilot-usage.ts
|
|
176
|
+
const getCopilotUsage = async (account) => {
|
|
177
|
+
const ctx = account ?? accountFromState();
|
|
178
|
+
const response = await fetch(`${getGitHubApiBaseUrl()}/copilot_internal/user`, { headers: githubHeaders(ctx) });
|
|
179
|
+
if (!response.ok) throw new HTTPError("Failed to get Copilot usage", response);
|
|
180
|
+
return await response.json();
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
//#endregion
|
|
184
|
+
//#region src/services/github/get-user.ts
|
|
185
|
+
async function getGitHubUser(account) {
|
|
186
|
+
const token = account?.githubToken ?? state.githubToken;
|
|
187
|
+
if (!token) throw new Error("GitHub token not set");
|
|
188
|
+
const response = await fetch(`${getGitHubApiBaseUrl()}/user`, { headers: {
|
|
189
|
+
authorization: `token ${token}`,
|
|
190
|
+
...standardHeaders()
|
|
191
|
+
} });
|
|
192
|
+
if (!response.ok) throw new HTTPError("Failed to get GitHub user", response);
|
|
193
|
+
return await response.json();
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
//#endregion
|
|
197
|
+
//#region src/services/copilot/get-models.ts
|
|
198
|
+
const getModels = async (account) => {
|
|
199
|
+
const ctx = account ?? accountFromState();
|
|
200
|
+
const response = await fetch(`${copilotBaseUrl(ctx)}/models`, { headers: copilotHeaders(ctx) });
|
|
201
|
+
if (!response.ok) throw new HTTPError("Failed to get models", response);
|
|
202
|
+
const models = await response.json();
|
|
203
|
+
try {
|
|
204
|
+
await fs.mkdir(PATHS.APP_DIR, { recursive: true });
|
|
205
|
+
await fs.writeFile(PATHS.MODELS_PATH, `${JSON.stringify(models, null, 2)}\n`, {
|
|
206
|
+
encoding: "utf8",
|
|
207
|
+
mode: 384
|
|
208
|
+
});
|
|
209
|
+
} catch {}
|
|
210
|
+
return models;
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
//#endregion
|
|
214
|
+
//#region src/services/get-vscode-version.ts
|
|
215
|
+
const FALLBACK = "1.110.1";
|
|
216
|
+
async function getVSCodeVersion() {
|
|
217
|
+
await Promise.resolve();
|
|
218
|
+
return FALLBACK;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
//#endregion
|
|
222
|
+
//#region src/lib/utils.ts
|
|
223
|
+
const sleep = (ms) => new Promise((resolve) => {
|
|
224
|
+
setTimeout(resolve, ms);
|
|
225
|
+
});
|
|
226
|
+
const isNullish = (value) => value === null || value === void 0;
|
|
227
|
+
async function cacheModels() {
|
|
228
|
+
state.models = await getModels();
|
|
229
|
+
}
|
|
230
|
+
const cacheVSCodeVersion = async () => {
|
|
231
|
+
const response = await getVSCodeVersion();
|
|
232
|
+
state.vsCodeVersion = response;
|
|
233
|
+
consola.info(`Using VSCode version: ${response}`);
|
|
234
|
+
};
|
|
235
|
+
const invalidMacAddresses = new Set([
|
|
236
|
+
"00:00:00:00:00:00",
|
|
237
|
+
"ff:ff:ff:ff:ff:ff",
|
|
238
|
+
"ac:de:48:00:11:22"
|
|
239
|
+
]);
|
|
240
|
+
function validateMacAddress(candidate) {
|
|
241
|
+
const tempCandidate = candidate.replaceAll("-", ":").toLowerCase();
|
|
242
|
+
return !invalidMacAddresses.has(tempCandidate);
|
|
243
|
+
}
|
|
244
|
+
function getMac() {
|
|
245
|
+
const ifaces = networkInterfaces();
|
|
246
|
+
for (const name in ifaces) {
|
|
247
|
+
const networkInterface = ifaces[name];
|
|
248
|
+
if (networkInterface) {
|
|
249
|
+
for (const { mac } of networkInterface) if (validateMacAddress(mac)) return mac;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
return null;
|
|
253
|
+
}
|
|
254
|
+
const cacheMacMachineId = () => {
|
|
255
|
+
const macAddress = getMac() ?? randomUUID();
|
|
256
|
+
state.macMachineId = createHash("sha256").update(macAddress, "utf8").digest("hex");
|
|
257
|
+
consola.debug(`Using machine ID: ${state.macMachineId}`);
|
|
258
|
+
};
|
|
259
|
+
const SESSION_REFRESH_BASE_MS = 3600 * 1e3;
|
|
260
|
+
const SESSION_REFRESH_JITTER_MS = 1200 * 1e3;
|
|
261
|
+
let vsCodeSessionRefreshTimer = null;
|
|
262
|
+
const generateSessionId = () => {
|
|
263
|
+
state.vsCodeSessionId = randomUUID() + Date.now().toString();
|
|
264
|
+
consola.debug(`Generated VSCode session ID: ${state.vsCodeSessionId}`);
|
|
265
|
+
};
|
|
266
|
+
const stopVsCodeSessionRefreshLoop = () => {
|
|
267
|
+
if (vsCodeSessionRefreshTimer) {
|
|
268
|
+
clearTimeout(vsCodeSessionRefreshTimer);
|
|
269
|
+
vsCodeSessionRefreshTimer = null;
|
|
270
|
+
}
|
|
271
|
+
};
|
|
272
|
+
const scheduleSessionIdRefresh = () => {
|
|
273
|
+
const randomDelay = Math.floor(Math.random() * SESSION_REFRESH_JITTER_MS);
|
|
274
|
+
const delay = SESSION_REFRESH_BASE_MS + randomDelay;
|
|
275
|
+
consola.debug(`Scheduling next VSCode session ID refresh in ${Math.round(delay / 1e3)} seconds`);
|
|
276
|
+
stopVsCodeSessionRefreshLoop();
|
|
277
|
+
vsCodeSessionRefreshTimer = setTimeout(() => {
|
|
278
|
+
try {
|
|
279
|
+
generateSessionId();
|
|
280
|
+
} catch (error) {
|
|
281
|
+
consola.error("Failed to refresh session ID, rescheduling...", error);
|
|
282
|
+
} finally {
|
|
283
|
+
scheduleSessionIdRefresh();
|
|
284
|
+
}
|
|
285
|
+
}, delay);
|
|
286
|
+
};
|
|
287
|
+
const cacheVsCodeSessionId = () => {
|
|
288
|
+
stopVsCodeSessionRefreshLoop();
|
|
289
|
+
generateSessionId();
|
|
290
|
+
scheduleSessionIdRefresh();
|
|
291
|
+
};
|
|
292
|
+
const findLastUserContent = (messages) => {
|
|
293
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
294
|
+
const msg = messages[i];
|
|
295
|
+
if (msg.role === "user" && msg.content) {
|
|
296
|
+
if (typeof msg.content === "string") return msg.content;
|
|
297
|
+
else if (Array.isArray(msg.content)) {
|
|
298
|
+
const array = msg.content.filter((n) => n.type !== "tool_result").map((n) => ({
|
|
299
|
+
...n,
|
|
300
|
+
cache_control: void 0
|
|
301
|
+
}));
|
|
302
|
+
if (array.length > 0) return JSON.stringify(array);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
return null;
|
|
307
|
+
};
|
|
308
|
+
const generateRequestIdFromPayload = (payload, sessionId) => {
|
|
309
|
+
const messages = payload.messages;
|
|
310
|
+
if (messages) {
|
|
311
|
+
const lastUserContent = typeof messages === "string" ? messages : findLastUserContent(messages);
|
|
312
|
+
if (lastUserContent) return getUUID((sessionId ?? "") + (state.macMachineId ?? "") + lastUserContent);
|
|
313
|
+
}
|
|
314
|
+
return randomUUID();
|
|
315
|
+
};
|
|
316
|
+
const getRootSessionId = (anthropicPayload, c) => {
|
|
317
|
+
let sessionId;
|
|
318
|
+
if (anthropicPayload.metadata?.user_id) {
|
|
319
|
+
const sessionMatch = (/* @__PURE__ */ new RegExp(/_session_(.+)$/)).exec(anthropicPayload.metadata.user_id);
|
|
320
|
+
sessionId = sessionMatch ? sessionMatch[1] : void 0;
|
|
321
|
+
} else sessionId = c.req.header("x-session-id");
|
|
322
|
+
if (sessionId) return getUUID(sessionId);
|
|
323
|
+
return sessionId;
|
|
324
|
+
};
|
|
325
|
+
const getUUID = (content) => {
|
|
326
|
+
const uuidBytes = createHash("sha256").update(content).digest().subarray(0, 16);
|
|
327
|
+
uuidBytes[6] = uuidBytes[6] & 15 | 64;
|
|
328
|
+
uuidBytes[8] = uuidBytes[8] & 63 | 128;
|
|
329
|
+
const uuidHex = uuidBytes.toString("hex");
|
|
330
|
+
return `${uuidHex.slice(0, 8)}-${uuidHex.slice(8, 12)}-${uuidHex.slice(12, 16)}-${uuidHex.slice(16, 20)}-${uuidHex.slice(20)}`;
|
|
331
|
+
};
|
|
332
|
+
|
|
333
|
+
//#endregion
|
|
334
|
+
export { HTTPError, accountFromState, cacheMacMachineId, cacheModels, cacheVSCodeVersion, cacheVsCodeSessionId, copilotBaseUrl, copilotHeaders, forwardError, generateRequestIdFromPayload, getCopilotUsage, getGitHubApiBaseUrl, getGitHubUser, getModels, getOauthAppConfig, getOauthUrls, getRootSessionId, getUUID, githubHeaders, isNullish, prepareInteractionHeaders, sleep, state };
|
|
335
|
+
//# sourceMappingURL=utils-BUJfM1V2.js.map
|