@daomar/copilot-api 0.7.0

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/dist/main.js ADDED
@@ -0,0 +1,1807 @@
1
+ #!/usr/bin/env node
2
+ import { defineCommand, runMain } from "citty";
3
+ import consola from "consola";
4
+ import fs from "node:fs/promises";
5
+ import os from "node:os";
6
+ import path from "node:path";
7
+ import { randomUUID } from "node:crypto";
8
+ import clipboard from "clipboardy";
9
+ import { serve } from "srvx";
10
+ import invariant from "tiny-invariant";
11
+ import { getProxyForUrl } from "proxy-from-env";
12
+ import { Agent, ProxyAgent, setGlobalDispatcher } from "undici";
13
+ import { execSync } from "node:child_process";
14
+ import process$1 from "node:process";
15
+ import { Hono } from "hono";
16
+ import { cors } from "hono/cors";
17
+ import { logger } from "hono/logger";
18
+ import { streamSSE } from "hono/streaming";
19
+ import { events } from "fetch-event-stream";
20
+
21
+ //#region src/lib/paths.ts
22
+ const APP_DIR = path.join(os.homedir(), ".local", "share", "copilot-api");
23
+ const GITHUB_TOKEN_PATH = path.join(APP_DIR, "github_token");
24
+ const PATHS = {
25
+ APP_DIR,
26
+ GITHUB_TOKEN_PATH
27
+ };
28
+ async function ensurePaths() {
29
+ await fs.mkdir(PATHS.APP_DIR, { recursive: true });
30
+ await ensureFile(PATHS.GITHUB_TOKEN_PATH);
31
+ }
32
+ async function ensureFile(filePath) {
33
+ try {
34
+ await fs.access(filePath, fs.constants.W_OK);
35
+ } catch {
36
+ await fs.writeFile(filePath, "");
37
+ await fs.chmod(filePath, 384);
38
+ }
39
+ }
40
+
41
+ //#endregion
42
+ //#region src/lib/state.ts
43
+ const state = {
44
+ accountType: "individual",
45
+ manualApprove: false,
46
+ rateLimitWait: false,
47
+ showToken: false,
48
+ verbose: false,
49
+ traceEnabled: false
50
+ };
51
+
52
+ //#endregion
53
+ //#region src/lib/api-config.ts
54
+ const standardHeaders = () => ({
55
+ "content-type": "application/json",
56
+ accept: "application/json"
57
+ });
58
+ const COPILOT_VERSION = "0.26.7";
59
+ const EDITOR_PLUGIN_VERSION = `copilot-chat/${COPILOT_VERSION}`;
60
+ const USER_AGENT = `GitHubCopilotChat/${COPILOT_VERSION}`;
61
+ const API_VERSION = "2025-04-01";
62
+ const copilotBaseUrl = (state$1) => state$1.accountType === "individual" ? "https://api.githubcopilot.com" : `https://api.${state$1.accountType}.githubcopilot.com`;
63
+ const copilotHeaders = (state$1, vision = false) => {
64
+ const headers = {
65
+ Authorization: `Bearer ${state$1.copilotToken}`,
66
+ "content-type": standardHeaders()["content-type"],
67
+ "copilot-integration-id": "vscode-chat",
68
+ "editor-version": `vscode/${state$1.vsCodeVersion}`,
69
+ "editor-plugin-version": EDITOR_PLUGIN_VERSION,
70
+ "user-agent": USER_AGENT,
71
+ "openai-intent": "conversation-panel",
72
+ "x-github-api-version": API_VERSION,
73
+ "x-request-id": randomUUID(),
74
+ "x-vscode-user-agent-library-version": "electron-fetch"
75
+ };
76
+ if (vision) headers["copilot-vision-request"] = "true";
77
+ return headers;
78
+ };
79
+ const GITHUB_API_BASE_URL = "https://api.github.com";
80
+ const githubHeaders = (state$1) => ({
81
+ ...standardHeaders(),
82
+ authorization: `token ${state$1.githubToken}`,
83
+ "editor-version": `vscode/${state$1.vsCodeVersion}`,
84
+ "editor-plugin-version": EDITOR_PLUGIN_VERSION,
85
+ "user-agent": USER_AGENT,
86
+ "x-github-api-version": API_VERSION,
87
+ "x-vscode-user-agent-library-version": "electron-fetch"
88
+ });
89
+ const GITHUB_BASE_URL = "https://github.com";
90
+ const GITHUB_CLIENT_ID = "Iv1.b507a08c87ecfe98";
91
+ const GITHUB_APP_SCOPES = ["read:user"].join(" ");
92
+
93
+ //#endregion
94
+ //#region src/lib/error.ts
95
+ var HTTPError = class extends Error {
96
+ response;
97
+ constructor(message, response) {
98
+ super(message);
99
+ this.response = response;
100
+ }
101
+ };
102
+ async function forwardError(c, error) {
103
+ consola.error("Error occurred:", error);
104
+ if (error instanceof HTTPError) {
105
+ const errorText = await error.response.text();
106
+ let errorJson;
107
+ try {
108
+ errorJson = JSON.parse(errorText);
109
+ } catch {
110
+ errorJson = errorText;
111
+ }
112
+ consola.error("HTTP error:", errorJson);
113
+ return c.json({ error: {
114
+ message: errorText,
115
+ type: "error"
116
+ } }, error.response.status);
117
+ }
118
+ return c.json({ error: {
119
+ message: error.message,
120
+ type: "error"
121
+ } }, 500);
122
+ }
123
+
124
+ //#endregion
125
+ //#region src/services/github/get-copilot-token.ts
126
+ const getCopilotToken = async () => {
127
+ const response = await fetch(`${GITHUB_API_BASE_URL}/copilot_internal/v2/token`, { headers: githubHeaders(state) });
128
+ if (!response.ok) throw new HTTPError("Failed to get Copilot token", response);
129
+ return await response.json();
130
+ };
131
+
132
+ //#endregion
133
+ //#region src/services/github/get-device-code.ts
134
+ async function getDeviceCode() {
135
+ const response = await fetch(`${GITHUB_BASE_URL}/login/device/code`, {
136
+ method: "POST",
137
+ headers: standardHeaders(),
138
+ body: JSON.stringify({
139
+ client_id: GITHUB_CLIENT_ID,
140
+ scope: GITHUB_APP_SCOPES
141
+ })
142
+ });
143
+ if (!response.ok) throw new HTTPError("Failed to get device code", response);
144
+ return await response.json();
145
+ }
146
+
147
+ //#endregion
148
+ //#region src/services/github/get-user.ts
149
+ async function getGitHubUser() {
150
+ const response = await fetch(`${GITHUB_API_BASE_URL}/user`, { headers: {
151
+ authorization: `token ${state.githubToken}`,
152
+ ...standardHeaders()
153
+ } });
154
+ if (!response.ok) throw new HTTPError("Failed to get GitHub user", response);
155
+ return await response.json();
156
+ }
157
+
158
+ //#endregion
159
+ //#region src/services/copilot/get-models.ts
160
+ const getModels = async () => {
161
+ const response = await fetch(`${copilotBaseUrl(state)}/models`, { headers: copilotHeaders(state) });
162
+ if (!response.ok) throw new HTTPError("Failed to get models", response);
163
+ return await response.json();
164
+ };
165
+
166
+ //#endregion
167
+ //#region src/services/get-vscode-version.ts
168
+ const FALLBACK = "1.104.3";
169
+ async function getVSCodeVersion() {
170
+ const controller = new AbortController();
171
+ const timeout = setTimeout(() => {
172
+ controller.abort();
173
+ }, 5e3);
174
+ try {
175
+ const match = (await (await fetch("https://aur.archlinux.org/cgit/aur.git/plain/PKGBUILD?h=visual-studio-code-bin", { signal: controller.signal })).text()).match(/pkgver=([0-9.]+)/);
176
+ if (match) return match[1];
177
+ return FALLBACK;
178
+ } catch {
179
+ return FALLBACK;
180
+ } finally {
181
+ clearTimeout(timeout);
182
+ }
183
+ }
184
+ await getVSCodeVersion();
185
+
186
+ //#endregion
187
+ //#region src/lib/utils.ts
188
+ const sleep = (ms) => new Promise((resolve) => {
189
+ setTimeout(resolve, ms);
190
+ });
191
+ const isNullish = (value) => value === null || value === void 0;
192
+ async function cacheModels() {
193
+ state.models = await getModels();
194
+ }
195
+ const cacheVSCodeVersion = async () => {
196
+ const response = await getVSCodeVersion();
197
+ state.vsCodeVersion = response;
198
+ consola.info(`Using VSCode version: ${response}`);
199
+ };
200
+
201
+ //#endregion
202
+ //#region src/services/github/poll-access-token.ts
203
+ async function pollAccessToken(deviceCode) {
204
+ const sleepDuration = (deviceCode.interval + 1) * 1e3;
205
+ consola.debug(`Polling access token with interval of ${sleepDuration}ms`);
206
+ while (true) {
207
+ const response = await fetch(`${GITHUB_BASE_URL}/login/oauth/access_token`, {
208
+ method: "POST",
209
+ headers: standardHeaders(),
210
+ body: JSON.stringify({
211
+ client_id: GITHUB_CLIENT_ID,
212
+ device_code: deviceCode.device_code,
213
+ grant_type: "urn:ietf:params:oauth:grant-type:device_code"
214
+ })
215
+ });
216
+ if (!response.ok) {
217
+ await sleep(sleepDuration);
218
+ consola.error("Failed to poll access token:", await response.text());
219
+ continue;
220
+ }
221
+ const json = await response.json();
222
+ consola.debug("Polling access token response:", json);
223
+ const { access_token } = json;
224
+ if (access_token) return access_token;
225
+ else await sleep(sleepDuration);
226
+ }
227
+ }
228
+
229
+ //#endregion
230
+ //#region src/lib/token.ts
231
+ const readGithubToken = () => fs.readFile(PATHS.GITHUB_TOKEN_PATH, "utf8");
232
+ const writeGithubToken = (token) => fs.writeFile(PATHS.GITHUB_TOKEN_PATH, token);
233
+ const setupCopilotToken = async () => {
234
+ const { token, refresh_in } = await getCopilotToken();
235
+ state.copilotToken = token;
236
+ consola.debug("GitHub Copilot Token fetched successfully!");
237
+ if (state.showToken) consola.info("Copilot token:", token);
238
+ const refreshInterval = (refresh_in - 60) * 1e3;
239
+ setInterval(async () => {
240
+ consola.debug("Refreshing Copilot token");
241
+ try {
242
+ const { token: token$1 } = await getCopilotToken();
243
+ state.copilotToken = token$1;
244
+ consola.debug("Copilot token refreshed");
245
+ if (state.showToken) consola.info("Refreshed Copilot token:", token$1);
246
+ } catch (error) {
247
+ consola.error("Failed to refresh Copilot token:", error);
248
+ throw error;
249
+ }
250
+ }, refreshInterval);
251
+ };
252
+ async function setupGitHubToken(options) {
253
+ try {
254
+ const githubToken = await readGithubToken();
255
+ if (githubToken && !options?.force) {
256
+ state.githubToken = githubToken;
257
+ if (state.showToken) consola.info("GitHub token:", githubToken);
258
+ await logUser();
259
+ return;
260
+ }
261
+ consola.info("Not logged in, getting new access token");
262
+ const response = await getDeviceCode();
263
+ consola.debug("Device code response:", response);
264
+ consola.info(`Please enter the code "${response.user_code}" in ${response.verification_uri}`);
265
+ const token = await pollAccessToken(response);
266
+ await writeGithubToken(token);
267
+ state.githubToken = token;
268
+ if (state.showToken) consola.info("GitHub token:", token);
269
+ await logUser();
270
+ } catch (error) {
271
+ if (error instanceof HTTPError) {
272
+ consola.error("Failed to get GitHub token:", await error.response.json());
273
+ throw error;
274
+ }
275
+ consola.error("Failed to get GitHub token:", error);
276
+ throw error;
277
+ }
278
+ }
279
+ async function logUser() {
280
+ const user = await getGitHubUser();
281
+ consola.info(`Logged in as ${user.login}`);
282
+ }
283
+
284
+ //#endregion
285
+ //#region src/auth.ts
286
+ async function runAuth(options) {
287
+ if (options.verbose) {
288
+ consola.level = 5;
289
+ consola.info("Verbose logging enabled");
290
+ }
291
+ state.showToken = options.showToken;
292
+ await ensurePaths();
293
+ await setupGitHubToken({ force: true });
294
+ consola.success("GitHub token written to", PATHS.GITHUB_TOKEN_PATH);
295
+ }
296
+ const auth = defineCommand({
297
+ meta: {
298
+ name: "auth",
299
+ description: "Run GitHub auth flow without running the server"
300
+ },
301
+ args: {
302
+ verbose: {
303
+ alias: "v",
304
+ type: "boolean",
305
+ default: false,
306
+ description: "Enable verbose logging"
307
+ },
308
+ "show-token": {
309
+ type: "boolean",
310
+ default: false,
311
+ description: "Show GitHub token on auth"
312
+ }
313
+ },
314
+ run({ args }) {
315
+ return runAuth({
316
+ verbose: args.verbose,
317
+ showToken: args["show-token"]
318
+ });
319
+ }
320
+ });
321
+
322
+ //#endregion
323
+ //#region src/services/github/get-copilot-usage.ts
324
+ const getCopilotUsage = async () => {
325
+ const response = await fetch(`${GITHUB_API_BASE_URL}/copilot_internal/user`, { headers: githubHeaders(state) });
326
+ if (!response.ok) throw new HTTPError("Failed to get Copilot usage", response);
327
+ return await response.json();
328
+ };
329
+
330
+ //#endregion
331
+ //#region src/check-usage.ts
332
+ const checkUsage = defineCommand({
333
+ meta: {
334
+ name: "check-usage",
335
+ description: "Show current GitHub Copilot usage/quota information"
336
+ },
337
+ async run() {
338
+ await ensurePaths();
339
+ await setupGitHubToken();
340
+ try {
341
+ const usage = await getCopilotUsage();
342
+ const premium = usage.quota_snapshots.premium_interactions;
343
+ const premiumTotal = premium.entitlement;
344
+ const premiumUsed = premiumTotal - premium.remaining;
345
+ const premiumPercentUsed = premiumTotal > 0 ? premiumUsed / premiumTotal * 100 : 0;
346
+ const premiumPercentRemaining = premium.percent_remaining;
347
+ function summarizeQuota(name, snap) {
348
+ if (!snap) return `${name}: N/A`;
349
+ const total = snap.entitlement;
350
+ const used = total - snap.remaining;
351
+ const percentUsed = total > 0 ? used / total * 100 : 0;
352
+ const percentRemaining = snap.percent_remaining;
353
+ return `${name}: ${used}/${total} used (${percentUsed.toFixed(1)}% used, ${percentRemaining.toFixed(1)}% remaining)`;
354
+ }
355
+ const premiumLine = `Premium: ${premiumUsed}/${premiumTotal} used (${premiumPercentUsed.toFixed(1)}% used, ${premiumPercentRemaining.toFixed(1)}% remaining)`;
356
+ const chatLine = summarizeQuota("Chat", usage.quota_snapshots.chat);
357
+ const completionsLine = summarizeQuota("Completions", usage.quota_snapshots.completions);
358
+ consola.box(`Copilot Usage (plan: ${usage.copilot_plan})\nQuota resets: ${usage.quota_reset_date}\n\nQuotas:\n ${premiumLine}\n ${chatLine}\n ${completionsLine}`);
359
+ } catch (err) {
360
+ consola.error("Failed to fetch Copilot usage:", err);
361
+ process.exit(1);
362
+ }
363
+ }
364
+ });
365
+
366
+ //#endregion
367
+ //#region src/debug.ts
368
+ async function getPackageVersion() {
369
+ try {
370
+ const packageJsonPath = new URL("../package.json", import.meta.url).pathname;
371
+ return JSON.parse(await fs.readFile(packageJsonPath)).version;
372
+ } catch {
373
+ return "unknown";
374
+ }
375
+ }
376
+ function getRuntimeInfo() {
377
+ const isBun = typeof Bun !== "undefined";
378
+ return {
379
+ name: isBun ? "bun" : "node",
380
+ version: isBun ? Bun.version : process.version.slice(1),
381
+ platform: os.platform(),
382
+ arch: os.arch()
383
+ };
384
+ }
385
+ async function checkTokenExists() {
386
+ try {
387
+ if (!(await fs.stat(PATHS.GITHUB_TOKEN_PATH)).isFile()) return false;
388
+ return (await fs.readFile(PATHS.GITHUB_TOKEN_PATH, "utf8")).trim().length > 0;
389
+ } catch {
390
+ return false;
391
+ }
392
+ }
393
+ async function getDebugInfo() {
394
+ const [version, tokenExists] = await Promise.all([getPackageVersion(), checkTokenExists()]);
395
+ return {
396
+ version,
397
+ runtime: getRuntimeInfo(),
398
+ paths: {
399
+ APP_DIR: PATHS.APP_DIR,
400
+ GITHUB_TOKEN_PATH: PATHS.GITHUB_TOKEN_PATH
401
+ },
402
+ tokenExists
403
+ };
404
+ }
405
+ function printDebugInfoPlain(info) {
406
+ consola.info(`copilot-api debug
407
+
408
+ Version: ${info.version}
409
+ Runtime: ${info.runtime.name} ${info.runtime.version} (${info.runtime.platform} ${info.runtime.arch})
410
+
411
+ Paths:
412
+ - APP_DIR: ${info.paths.APP_DIR}
413
+ - GITHUB_TOKEN_PATH: ${info.paths.GITHUB_TOKEN_PATH}
414
+
415
+ Token exists: ${info.tokenExists ? "Yes" : "No"}`);
416
+ }
417
+ function printDebugInfoJson(info) {
418
+ console.log(JSON.stringify(info, null, 2));
419
+ }
420
+ async function runDebug(options) {
421
+ const debugInfo = await getDebugInfo();
422
+ if (options.json) printDebugInfoJson(debugInfo);
423
+ else printDebugInfoPlain(debugInfo);
424
+ }
425
+ const debug = defineCommand({
426
+ meta: {
427
+ name: "debug",
428
+ description: "Print debug information about the application"
429
+ },
430
+ args: { json: {
431
+ type: "boolean",
432
+ default: false,
433
+ description: "Output debug information as JSON"
434
+ } },
435
+ run({ args }) {
436
+ return runDebug({ json: args.json });
437
+ }
438
+ });
439
+
440
+ //#endregion
441
+ //#region src/lib/proxy.ts
442
+ function initProxyFromEnv() {
443
+ if (typeof Bun !== "undefined") return;
444
+ try {
445
+ const direct = new Agent();
446
+ const proxies = /* @__PURE__ */ new Map();
447
+ setGlobalDispatcher({
448
+ dispatch(options, handler) {
449
+ try {
450
+ const origin = typeof options.origin === "string" ? new URL(options.origin) : options.origin;
451
+ const raw = getProxyForUrl(origin.toString());
452
+ const proxyUrl = raw && raw.length > 0 ? raw : void 0;
453
+ if (!proxyUrl) {
454
+ consola.debug(`HTTP proxy bypass: ${origin.hostname}`);
455
+ return direct.dispatch(options, handler);
456
+ }
457
+ let agent = proxies.get(proxyUrl);
458
+ if (!agent) {
459
+ agent = new ProxyAgent(proxyUrl);
460
+ proxies.set(proxyUrl, agent);
461
+ }
462
+ let label = proxyUrl;
463
+ try {
464
+ const u = new URL(proxyUrl);
465
+ label = `${u.protocol}//${u.host}`;
466
+ } catch {}
467
+ consola.debug(`HTTP proxy route: ${origin.hostname} via ${label}`);
468
+ return agent.dispatch(options, handler);
469
+ } catch {
470
+ return direct.dispatch(options, handler);
471
+ }
472
+ },
473
+ close() {
474
+ return direct.close();
475
+ },
476
+ destroy() {
477
+ return direct.destroy();
478
+ }
479
+ });
480
+ consola.debug("HTTP proxy configured from environment (per-URL)");
481
+ } catch (err) {
482
+ consola.debug("Proxy setup skipped:", err);
483
+ }
484
+ }
485
+
486
+ //#endregion
487
+ //#region src/lib/shell.ts
488
+ function getShell() {
489
+ const { platform, ppid, env } = process$1;
490
+ if (platform === "win32") {
491
+ try {
492
+ const command = `wmic process get ParentProcessId,Name | findstr "${ppid}"`;
493
+ if (execSync(command, { stdio: "pipe" }).toString().toLowerCase().includes("powershell.exe")) return "powershell";
494
+ } catch {
495
+ return "cmd";
496
+ }
497
+ return "cmd";
498
+ } else {
499
+ const shellPath = env.SHELL;
500
+ if (shellPath) {
501
+ if (shellPath.endsWith("zsh")) return "zsh";
502
+ if (shellPath.endsWith("fish")) return "fish";
503
+ if (shellPath.endsWith("bash")) return "bash";
504
+ }
505
+ return "sh";
506
+ }
507
+ }
508
+ /**
509
+ * Generates a copy-pasteable script to set multiple environment variables
510
+ * and run a subsequent command.
511
+ * @param {EnvVars} envVars - An object of environment variables to set.
512
+ * @param {string} commandToRun - The command to run after setting the variables.
513
+ * @returns {string} The formatted script string.
514
+ */
515
+ function generateEnvScript(envVars, commandToRun = "") {
516
+ const shell = getShell();
517
+ const filteredEnvVars = Object.entries(envVars).filter(([, value]) => value !== void 0);
518
+ let commandBlock;
519
+ switch (shell) {
520
+ case "powershell":
521
+ commandBlock = filteredEnvVars.map(([key, value]) => `$env:${key} = ${value}`).join("; ");
522
+ break;
523
+ case "cmd":
524
+ commandBlock = filteredEnvVars.map(([key, value]) => `set ${key}=${value}`).join(" & ");
525
+ break;
526
+ case "fish":
527
+ commandBlock = filteredEnvVars.map(([key, value]) => `set -gx ${key} ${value}`).join("; ");
528
+ break;
529
+ default: {
530
+ const assignments = filteredEnvVars.map(([key, value]) => `${key}=${value}`).join(" ");
531
+ commandBlock = filteredEnvVars.length > 0 ? `export ${assignments}` : "";
532
+ break;
533
+ }
534
+ }
535
+ if (commandBlock && commandToRun) return `${commandBlock}${shell === "cmd" ? " & " : " && "}${commandToRun}`;
536
+ return commandBlock || commandToRun;
537
+ }
538
+
539
+ //#endregion
540
+ //#region src/lib/trace.ts
541
+ /**
542
+ * Generates a timestamp string for trace file naming.
543
+ * Format: YYYYMMDD_HHmmss_SSS (e.g., 20251204_143052_123)
544
+ */
545
+ function getTimestamp() {
546
+ const now = /* @__PURE__ */ new Date();
547
+ const year = now.getFullYear();
548
+ const month = String(now.getMonth() + 1).padStart(2, "0");
549
+ const day = String(now.getDate()).padStart(2, "0");
550
+ const hours = String(now.getHours()).padStart(2, "0");
551
+ const minutes = String(now.getMinutes()).padStart(2, "0");
552
+ const seconds = String(now.getSeconds()).padStart(2, "0");
553
+ const millis = String(now.getMilliseconds()).padStart(3, "0");
554
+ return `${year}${month}${day}_${hours}${minutes}${seconds}_${millis}`;
555
+ }
556
+ /**
557
+ * Ensures the trace folder exists.
558
+ */
559
+ async function ensureTraceFolder() {
560
+ if (!state.traceEnabled || !state.traceFolder) return;
561
+ try {
562
+ await fs.mkdir(state.traceFolder, { recursive: true });
563
+ consola.debug(`Trace folder ensured: ${state.traceFolder}`);
564
+ } catch (error) {
565
+ consola.error("Failed to create trace folder:", error);
566
+ }
567
+ }
568
+ /**
569
+ * Logs a request to the trace folder.
570
+ * @param request The request payload to log
571
+ * @returns The timestamp used for the request file (to use for matching response)
572
+ */
573
+ async function traceRequest(request) {
574
+ if (!state.traceEnabled || !state.traceFolder) return null;
575
+ const timestamp = getTimestamp();
576
+ const filename = `${timestamp}.req`;
577
+ const filepath = path.join(state.traceFolder, filename);
578
+ try {
579
+ const content = JSON.stringify(request, null, 2);
580
+ await fs.writeFile(filepath, content, "utf8");
581
+ consola.debug(`Trace request written: ${filepath}`);
582
+ return timestamp;
583
+ } catch (error) {
584
+ consola.error("Failed to write trace request:", error);
585
+ return null;
586
+ }
587
+ }
588
+ /**
589
+ * Logs a response to the trace folder.
590
+ * @param response The response payload to log
591
+ * @param timestamp The timestamp from the corresponding request
592
+ */
593
+ async function traceResponse(response, timestamp) {
594
+ if (!state.traceEnabled || !state.traceFolder || !timestamp) return;
595
+ const filename = `${timestamp}.resp`;
596
+ const filepath = path.join(state.traceFolder, filename);
597
+ try {
598
+ const content = JSON.stringify(response, null, 2);
599
+ await fs.writeFile(filepath, content, "utf8");
600
+ consola.debug(`Trace response written: ${filepath}`);
601
+ } catch (error) {
602
+ consola.error("Failed to write trace response:", error);
603
+ }
604
+ }
605
+ /**
606
+ * Logs a streaming response to the trace folder.
607
+ * For streaming responses, we collect all chunks and write them at the end.
608
+ */
609
+ var StreamTracer = class {
610
+ chunks = [];
611
+ timestamp;
612
+ constructor(timestamp) {
613
+ this.timestamp = timestamp;
614
+ }
615
+ addChunk(chunk) {
616
+ if (!state.traceEnabled) return;
617
+ this.chunks.push(chunk);
618
+ }
619
+ async finish() {
620
+ if (!state.traceEnabled || !state.traceFolder || !this.timestamp) return;
621
+ const filename = `${this.timestamp}.resp`;
622
+ const filepath = path.join(state.traceFolder, filename);
623
+ try {
624
+ const content = JSON.stringify({
625
+ streaming: true,
626
+ chunks: this.chunks
627
+ }, null, 2);
628
+ await fs.writeFile(filepath, content, "utf8");
629
+ consola.debug(`Trace streaming response written: ${filepath}`);
630
+ } catch (error) {
631
+ consola.error("Failed to write trace streaming response:", error);
632
+ }
633
+ }
634
+ };
635
+
636
+ //#endregion
637
+ //#region src/lib/approval.ts
638
+ const awaitApproval = async () => {
639
+ if (!await consola.prompt(`Accept incoming request?`, { type: "confirm" })) throw new HTTPError("Request rejected", Response.json({ message: "Request rejected" }, { status: 403 }));
640
+ };
641
+
642
+ //#endregion
643
+ //#region src/lib/model-mapping.ts
644
+ /**
645
+ * Parse model mappings from environment variable.
646
+ * Format: "source1:target1,source2:target2"
647
+ * Example: "claude-sonnet-4-20250514:claude-opus-4-20250514,gpt-4:gpt-4-turbo"
648
+ */
649
+ function parseModelMappings(envValue) {
650
+ const mappings = /* @__PURE__ */ new Map();
651
+ if (!envValue || envValue.trim() === "") return mappings;
652
+ const pairs = envValue.split(",");
653
+ for (const pair of pairs) {
654
+ const trimmedPair = pair.trim();
655
+ if (!trimmedPair) continue;
656
+ const colonIndex = trimmedPair.indexOf(":");
657
+ if (colonIndex === -1) {
658
+ consola.warn(`Invalid model mapping format: "${trimmedPair}". Expected "source:target"`);
659
+ continue;
660
+ }
661
+ const source = trimmedPair.slice(0, colonIndex).trim();
662
+ const target = trimmedPair.slice(colonIndex + 1).trim();
663
+ if (!source || !target) {
664
+ consola.warn(`Invalid model mapping: "${trimmedPair}". Both source and target must be non-empty`);
665
+ continue;
666
+ }
667
+ mappings.set(source, target);
668
+ }
669
+ return mappings;
670
+ }
671
+ /**
672
+ * Apply model mapping if the requested model matches a mapping.
673
+ * Returns the mapped model name and whether a mapping was applied.
674
+ */
675
+ function applyModelMapping(modelName, mappings, verbose = false) {
676
+ const mappedModel = mappings.get(modelName);
677
+ if (mappedModel) {
678
+ if (verbose) consola.warn(`Model mapping applied: "${modelName}" -> "${mappedModel}"`);
679
+ return {
680
+ model: mappedModel,
681
+ mapped: true
682
+ };
683
+ }
684
+ return {
685
+ model: modelName,
686
+ mapped: false
687
+ };
688
+ }
689
+ const CLAUDE_FAMILIES = [
690
+ "opus",
691
+ "sonnet",
692
+ "haiku"
693
+ ];
694
+ /**
695
+ * Parse a Claude model id into its family and numeric version, regardless of
696
+ * the naming style used by the caller:
697
+ * - Anthropic / Claude Code: "claude-opus-4-6", "claude-sonnet-4-5-20250929"
698
+ * - legacy Anthropic: "claude-3-5-haiku-20241022", "claude-3-opus-20240229"
699
+ * - GitHub Copilot ids: "claude-opus-4.6", "claude-haiku-4.5"
700
+ * An 8-digit date token (e.g. 20250514) is ignored. Returns undefined for
701
+ * non-Claude models or when no version number is present.
702
+ */
703
+ function parseClaudeModel(name) {
704
+ const lower = name.toLowerCase();
705
+ if (!lower.startsWith("claude")) return void 0;
706
+ const family = CLAUDE_FAMILIES.find((f) => lower.includes(f));
707
+ if (!family) return void 0;
708
+ const numericTokens = lower.split(/[-_.]/).filter((token) => /^\d+$/.test(token) && token.length !== 8);
709
+ if (numericTokens.length === 0) return void 0;
710
+ return {
711
+ family,
712
+ version: numericTokens.join(".")
713
+ };
714
+ }
715
+ /** Compare two dotted numeric versions ("4.6" vs "4.10"). */
716
+ function compareVersions(a, b) {
717
+ const pa = a.split(".").map(Number);
718
+ const pb = b.split(".").map(Number);
719
+ const len = Math.max(pa.length, pb.length);
720
+ for (let i = 0; i < len; i++) {
721
+ const diff = (pa[i] || 0) - (pb[i] || 0);
722
+ if (diff !== 0) return diff;
723
+ }
724
+ return 0;
725
+ }
726
+ /**
727
+ * Resolve a requested model name to an id that the Copilot backend actually
728
+ * serves, using the live list of available model ids. No configuration needed.
729
+ *
730
+ * Resolution order:
731
+ * 1. exact match against an available id
732
+ * 2. normalized match (date stripped, "4-6" -> "4.6", legacy ordering)
733
+ * 3. same-family fallback: exact version, otherwise the highest available
734
+ * version in that family
735
+ * 4. otherwise the requested name is returned unchanged
736
+ */
737
+ function resolveModel(requested, availableIds, verbose = false) {
738
+ const noChange = {
739
+ model: requested,
740
+ mapped: false
741
+ };
742
+ if (!requested || availableIds.length === 0) return noChange;
743
+ if (availableIds.includes(requested)) return noChange;
744
+ const parsed = parseClaudeModel(requested);
745
+ if (!parsed) return noChange;
746
+ const result = (target) => {
747
+ if (target === requested) return noChange;
748
+ if (verbose) consola.warn(`Model auto-mapped: "${requested}" -> "${target}"`);
749
+ return {
750
+ model: target,
751
+ mapped: true
752
+ };
753
+ };
754
+ const canonical = `claude-${parsed.family}-${parsed.version}`;
755
+ if (availableIds.includes(canonical)) return result(canonical);
756
+ const familyCandidates = availableIds.map((id) => ({
757
+ id,
758
+ parsed: parseClaudeModel(id)
759
+ })).filter((c) => c.parsed?.family === parsed.family);
760
+ const cleanCandidates = familyCandidates.filter((c) => c.id === `claude-${c.parsed.family}-${c.parsed.version}`);
761
+ const pool = cleanCandidates.length > 0 ? cleanCandidates : familyCandidates;
762
+ if (pool.length === 0) return noChange;
763
+ const exact = pool.find((c) => c.parsed.version === parsed.version);
764
+ if (exact) return result(exact.id);
765
+ const highest = [...pool].sort((a, b) => compareVersions(b.parsed.version, a.parsed.version))[0];
766
+ return result(highest.id);
767
+ }
768
+ /**
769
+ * Get model mappings from environment variable.
770
+ * Caches the parsed result for performance.
771
+ */
772
+ let cachedMappings = null;
773
+ let cachedEnvValue;
774
+ function getModelMappings() {
775
+ const envValue = process.env.MODEL_MAPPINGS;
776
+ if (cachedMappings !== null && cachedEnvValue === envValue) return cachedMappings;
777
+ cachedEnvValue = envValue;
778
+ cachedMappings = parseModelMappings(envValue);
779
+ if (cachedMappings.size > 0) consola.info(`Loaded ${cachedMappings.size} model mapping(s)`);
780
+ return cachedMappings;
781
+ }
782
+
783
+ //#endregion
784
+ //#region src/lib/rate-limit.ts
785
+ async function checkRateLimit(state$1) {
786
+ if (state$1.rateLimitSeconds === void 0) return;
787
+ const now = Date.now();
788
+ if (!state$1.lastRequestTimestamp) {
789
+ state$1.lastRequestTimestamp = now;
790
+ return;
791
+ }
792
+ const elapsedSeconds = (now - state$1.lastRequestTimestamp) / 1e3;
793
+ if (elapsedSeconds > state$1.rateLimitSeconds) {
794
+ state$1.lastRequestTimestamp = now;
795
+ return;
796
+ }
797
+ const waitTimeSeconds = Math.ceil(state$1.rateLimitSeconds - elapsedSeconds);
798
+ if (!state$1.rateLimitWait) {
799
+ consola.warn(`Rate limit exceeded. Need to wait ${waitTimeSeconds} more seconds.`);
800
+ throw new HTTPError("Rate limit exceeded", Response.json({ message: "Rate limit exceeded" }, { status: 429 }));
801
+ }
802
+ const waitTimeMs = waitTimeSeconds * 1e3;
803
+ consola.warn(`Rate limit reached. Waiting ${waitTimeSeconds} seconds before proceeding...`);
804
+ await sleep(waitTimeMs);
805
+ state$1.lastRequestTimestamp = now;
806
+ consola.info("Rate limit wait completed, proceeding with request");
807
+ }
808
+
809
+ //#endregion
810
+ //#region src/lib/tokenizer.ts
811
+ const ENCODING_MAP = {
812
+ o200k_base: () => import("gpt-tokenizer/encoding/o200k_base"),
813
+ cl100k_base: () => import("gpt-tokenizer/encoding/cl100k_base"),
814
+ p50k_base: () => import("gpt-tokenizer/encoding/p50k_base"),
815
+ p50k_edit: () => import("gpt-tokenizer/encoding/p50k_edit"),
816
+ r50k_base: () => import("gpt-tokenizer/encoding/r50k_base")
817
+ };
818
+ const encodingCache = /* @__PURE__ */ new Map();
819
+ /**
820
+ * Calculate tokens for tool calls
821
+ */
822
+ const calculateToolCallsTokens = (toolCalls, encoder, constants) => {
823
+ let tokens = 0;
824
+ for (const toolCall of toolCalls) {
825
+ tokens += constants.funcInit;
826
+ tokens += encoder.encode(JSON.stringify(toolCall)).length;
827
+ }
828
+ tokens += constants.funcEnd;
829
+ return tokens;
830
+ };
831
+ /**
832
+ * Calculate tokens for content parts
833
+ */
834
+ const calculateContentPartsTokens = (contentParts, encoder) => {
835
+ let tokens = 0;
836
+ for (const part of contentParts) if (part.type === "image_url") tokens += encoder.encode(part.image_url.url).length + 85;
837
+ else if (part.text) tokens += encoder.encode(part.text).length;
838
+ return tokens;
839
+ };
840
+ /**
841
+ * Calculate tokens for a single message
842
+ */
843
+ const calculateMessageTokens = (message, encoder, constants) => {
844
+ const tokensPerMessage = 3;
845
+ const tokensPerName = 1;
846
+ let tokens = tokensPerMessage;
847
+ for (const [key, value] of Object.entries(message)) {
848
+ if (typeof value === "string") tokens += encoder.encode(value).length;
849
+ if (key === "name") tokens += tokensPerName;
850
+ if (key === "tool_calls") tokens += calculateToolCallsTokens(value, encoder, constants);
851
+ if (key === "content" && Array.isArray(value)) tokens += calculateContentPartsTokens(value, encoder);
852
+ }
853
+ return tokens;
854
+ };
855
+ /**
856
+ * Calculate tokens using custom algorithm
857
+ */
858
+ const calculateTokens = (messages, encoder, constants) => {
859
+ if (messages.length === 0) return 0;
860
+ let numTokens = 0;
861
+ for (const message of messages) numTokens += calculateMessageTokens(message, encoder, constants);
862
+ numTokens += 3;
863
+ return numTokens;
864
+ };
865
+ /**
866
+ * Get the corresponding encoder module based on encoding type
867
+ */
868
+ const getEncodeChatFunction = async (encoding) => {
869
+ if (encodingCache.has(encoding)) {
870
+ const cached = encodingCache.get(encoding);
871
+ if (cached) return cached;
872
+ }
873
+ const supportedEncoding = encoding;
874
+ if (!(supportedEncoding in ENCODING_MAP)) {
875
+ const fallbackModule = await ENCODING_MAP.o200k_base();
876
+ encodingCache.set(encoding, fallbackModule);
877
+ return fallbackModule;
878
+ }
879
+ const encodingModule = await ENCODING_MAP[supportedEncoding]();
880
+ encodingCache.set(encoding, encodingModule);
881
+ return encodingModule;
882
+ };
883
+ /**
884
+ * Get tokenizer type from model information
885
+ */
886
+ const getTokenizerFromModel = (model) => {
887
+ return model.capabilities.tokenizer || "o200k_base";
888
+ };
889
+ /**
890
+ * Get model-specific constants for token calculation
891
+ */
892
+ const getModelConstants = (model) => {
893
+ return model.id === "gpt-3.5-turbo" || model.id === "gpt-4" ? {
894
+ funcInit: 10,
895
+ propInit: 3,
896
+ propKey: 3,
897
+ enumInit: -3,
898
+ enumItem: 3,
899
+ funcEnd: 12
900
+ } : {
901
+ funcInit: 7,
902
+ propInit: 3,
903
+ propKey: 3,
904
+ enumInit: -3,
905
+ enumItem: 3,
906
+ funcEnd: 12
907
+ };
908
+ };
909
+ /**
910
+ * Calculate tokens for a single parameter
911
+ */
912
+ const calculateParameterTokens = (key, prop, context) => {
913
+ const { encoder, constants } = context;
914
+ let tokens = constants.propKey;
915
+ if (typeof prop !== "object" || prop === null) return tokens;
916
+ const param = prop;
917
+ const paramName = key;
918
+ const paramType = param.type || "string";
919
+ let paramDesc = param.description || "";
920
+ if (param.enum && Array.isArray(param.enum)) {
921
+ tokens += constants.enumInit;
922
+ for (const item of param.enum) {
923
+ tokens += constants.enumItem;
924
+ tokens += encoder.encode(String(item)).length;
925
+ }
926
+ }
927
+ if (paramDesc.endsWith(".")) paramDesc = paramDesc.slice(0, -1);
928
+ const line = `${paramName}:${paramType}:${paramDesc}`;
929
+ tokens += encoder.encode(line).length;
930
+ const excludedKeys = new Set([
931
+ "type",
932
+ "description",
933
+ "enum"
934
+ ]);
935
+ for (const propertyName of Object.keys(param)) if (!excludedKeys.has(propertyName)) {
936
+ const propertyValue = param[propertyName];
937
+ const propertyText = typeof propertyValue === "string" ? propertyValue : JSON.stringify(propertyValue);
938
+ tokens += encoder.encode(`${propertyName}:${propertyText}`).length;
939
+ }
940
+ return tokens;
941
+ };
942
+ /**
943
+ * Calculate tokens for function parameters
944
+ */
945
+ const calculateParametersTokens = (parameters, encoder, constants) => {
946
+ if (!parameters || typeof parameters !== "object") return 0;
947
+ const params = parameters;
948
+ let tokens = 0;
949
+ for (const [key, value] of Object.entries(params)) if (key === "properties") {
950
+ const properties = value;
951
+ if (Object.keys(properties).length > 0) {
952
+ tokens += constants.propInit;
953
+ for (const propKey of Object.keys(properties)) tokens += calculateParameterTokens(propKey, properties[propKey], {
954
+ encoder,
955
+ constants
956
+ });
957
+ }
958
+ } else {
959
+ const paramText = typeof value === "string" ? value : JSON.stringify(value);
960
+ tokens += encoder.encode(`${key}:${paramText}`).length;
961
+ }
962
+ return tokens;
963
+ };
964
+ /**
965
+ * Calculate tokens for a single tool
966
+ */
967
+ const calculateToolTokens = (tool, encoder, constants) => {
968
+ let tokens = constants.funcInit;
969
+ const func = tool.function;
970
+ const fName = func.name;
971
+ let fDesc = func.description || "";
972
+ if (fDesc.endsWith(".")) fDesc = fDesc.slice(0, -1);
973
+ const line = fName + ":" + fDesc;
974
+ tokens += encoder.encode(line).length;
975
+ if (typeof func.parameters === "object" && func.parameters !== null) tokens += calculateParametersTokens(func.parameters, encoder, constants);
976
+ return tokens;
977
+ };
978
+ /**
979
+ * Calculate token count for tools based on model
980
+ */
981
+ const numTokensForTools = (tools, encoder, constants) => {
982
+ let funcTokenCount = 0;
983
+ for (const tool of tools) funcTokenCount += calculateToolTokens(tool, encoder, constants);
984
+ funcTokenCount += constants.funcEnd;
985
+ return funcTokenCount;
986
+ };
987
+ /**
988
+ * Calculate the token count of messages, supporting multiple GPT encoders
989
+ */
990
+ const getTokenCount = async (payload, model) => {
991
+ const tokenizer = getTokenizerFromModel(model);
992
+ const encoder = await getEncodeChatFunction(tokenizer);
993
+ const simplifiedMessages = payload.messages;
994
+ const inputMessages = simplifiedMessages.filter((msg) => msg.role !== "assistant");
995
+ const outputMessages = simplifiedMessages.filter((msg) => msg.role === "assistant");
996
+ const constants = getModelConstants(model);
997
+ let inputTokens = calculateTokens(inputMessages, encoder, constants);
998
+ if (payload.tools && payload.tools.length > 0) inputTokens += numTokensForTools(payload.tools, encoder, constants);
999
+ const outputTokens = calculateTokens(outputMessages, encoder, constants);
1000
+ return {
1001
+ input: inputTokens,
1002
+ output: outputTokens
1003
+ };
1004
+ };
1005
+
1006
+ //#endregion
1007
+ //#region src/services/copilot/create-chat-completions.ts
1008
+ const createChatCompletions = async (payload) => {
1009
+ if (!state.copilotToken) throw new Error("Copilot token not found");
1010
+ const enableVision = payload.messages.some((x) => typeof x.content !== "string" && x.content?.some((x$1) => x$1.type === "image_url"));
1011
+ const isAgentCall = payload.messages.some((msg) => ["assistant", "tool"].includes(msg.role));
1012
+ const headers = {
1013
+ ...copilotHeaders(state, enableVision),
1014
+ "X-Initiator": isAgentCall ? "agent" : "user"
1015
+ };
1016
+ const response = await fetch(`${copilotBaseUrl(state)}/chat/completions`, {
1017
+ method: "POST",
1018
+ headers,
1019
+ body: JSON.stringify(payload)
1020
+ });
1021
+ if (!response.ok) {
1022
+ consola.error("Failed to create chat completions", response);
1023
+ throw new HTTPError("Failed to create chat completions", response);
1024
+ }
1025
+ if (payload.stream) return events(response);
1026
+ return await response.json();
1027
+ };
1028
+
1029
+ //#endregion
1030
+ //#region src/routes/chat-completions/handler.ts
1031
+ async function handleCompletion$1(c) {
1032
+ await checkRateLimit(state);
1033
+ let payload = await c.req.json();
1034
+ consola.debug("Request payload:", JSON.stringify(payload).slice(-400));
1035
+ const mappings = getModelMappings();
1036
+ const explicit = mappings.size > 0 ? applyModelMapping(payload.model, mappings, state.verbose) : {
1037
+ model: payload.model,
1038
+ mapped: false
1039
+ };
1040
+ const resolved = explicit.mapped ? explicit : resolveModel(payload.model, state.models?.data.map((m) => m.id) ?? [], state.verbose);
1041
+ if (resolved.model !== payload.model) payload = {
1042
+ ...payload,
1043
+ model: resolved.model
1044
+ };
1045
+ const traceTimestamp = await traceRequest(payload);
1046
+ const selectedModel = state.models?.data.find((model) => model.id === payload.model);
1047
+ try {
1048
+ if (selectedModel) {
1049
+ const tokenCount = await getTokenCount(payload, selectedModel);
1050
+ consola.info("Current token count:", tokenCount);
1051
+ } else consola.warn("No model selected, skipping token count calculation");
1052
+ } catch (error) {
1053
+ consola.warn("Failed to calculate token count:", error);
1054
+ }
1055
+ if (state.manualApprove) await awaitApproval();
1056
+ if (isNullish(payload.max_tokens)) {
1057
+ payload = {
1058
+ ...payload,
1059
+ max_tokens: selectedModel?.capabilities.limits.max_output_tokens
1060
+ };
1061
+ consola.debug("Set max_tokens to:", JSON.stringify(payload.max_tokens));
1062
+ }
1063
+ const response = await createChatCompletions(payload);
1064
+ if (isNonStreaming$1(response)) {
1065
+ consola.debug("Non-streaming response:", JSON.stringify(response));
1066
+ await traceResponse(response, traceTimestamp);
1067
+ return c.json(response);
1068
+ }
1069
+ consola.debug("Streaming response");
1070
+ return streamSSE(c, async (stream) => {
1071
+ const streamTracer = new StreamTracer(traceTimestamp);
1072
+ for await (const chunk of response) {
1073
+ consola.debug("Streaming chunk:", JSON.stringify(chunk));
1074
+ streamTracer.addChunk(chunk);
1075
+ await stream.writeSSE(chunk);
1076
+ }
1077
+ await streamTracer.finish();
1078
+ });
1079
+ }
1080
+ const isNonStreaming$1 = (response) => Object.hasOwn(response, "choices");
1081
+
1082
+ //#endregion
1083
+ //#region src/routes/chat-completions/route.ts
1084
+ const completionRoutes = new Hono();
1085
+ completionRoutes.post("/", async (c) => {
1086
+ try {
1087
+ return await handleCompletion$1(c);
1088
+ } catch (error) {
1089
+ return await forwardError(c, error);
1090
+ }
1091
+ });
1092
+
1093
+ //#endregion
1094
+ //#region src/services/copilot/create-embeddings.ts
1095
+ const createEmbeddings = async (payload) => {
1096
+ if (!state.copilotToken) throw new Error("Copilot token not found");
1097
+ const response = await fetch(`${copilotBaseUrl(state)}/embeddings`, {
1098
+ method: "POST",
1099
+ headers: copilotHeaders(state),
1100
+ body: JSON.stringify(payload)
1101
+ });
1102
+ if (!response.ok) throw new HTTPError("Failed to create embeddings", response);
1103
+ return await response.json();
1104
+ };
1105
+
1106
+ //#endregion
1107
+ //#region src/routes/embeddings/route.ts
1108
+ const embeddingRoutes = new Hono();
1109
+ embeddingRoutes.post("/", async (c) => {
1110
+ try {
1111
+ const paylod = await c.req.json();
1112
+ const response = await createEmbeddings(paylod);
1113
+ return c.json(response);
1114
+ } catch (error) {
1115
+ return await forwardError(c, error);
1116
+ }
1117
+ });
1118
+
1119
+ //#endregion
1120
+ //#region src/routes/messages/utils.ts
1121
+ function mapOpenAIStopReasonToAnthropic(finishReason) {
1122
+ if (finishReason === null) return null;
1123
+ return {
1124
+ stop: "end_turn",
1125
+ length: "max_tokens",
1126
+ tool_calls: "tool_use",
1127
+ content_filter: "end_turn"
1128
+ }[finishReason];
1129
+ }
1130
+
1131
+ //#endregion
1132
+ //#region src/routes/messages/non-stream-translation.ts
1133
+ function translateToOpenAI(payload) {
1134
+ return {
1135
+ model: translateModelName(payload.model),
1136
+ messages: translateAnthropicMessagesToOpenAI(payload.messages, payload.system),
1137
+ max_tokens: payload.max_tokens,
1138
+ stop: payload.stop_sequences,
1139
+ stream: payload.stream,
1140
+ temperature: payload.temperature,
1141
+ top_p: payload.top_p,
1142
+ user: payload.metadata?.user_id,
1143
+ tools: translateAnthropicToolsToOpenAI(payload.tools),
1144
+ tool_choice: translateAnthropicToolChoiceToOpenAI(payload.tool_choice)
1145
+ };
1146
+ }
1147
+ function translateModelName(model) {
1148
+ return resolveModel(model, state.models?.data.map((m) => m.id) ?? []).model;
1149
+ }
1150
+ function translateAnthropicMessagesToOpenAI(anthropicMessages, system) {
1151
+ const systemMessages = handleSystemPrompt(system);
1152
+ const otherMessages = anthropicMessages.flatMap((message) => message.role === "user" ? handleUserMessage(message) : handleAssistantMessage(message));
1153
+ const lastMessage = otherMessages.at(-1);
1154
+ if (lastMessage && lastMessage.role === "assistant") otherMessages.push({
1155
+ role: "user",
1156
+ content: "Continue."
1157
+ });
1158
+ return [...systemMessages, ...otherMessages];
1159
+ }
1160
+ function handleSystemPrompt(system) {
1161
+ if (!system) return [];
1162
+ if (typeof system === "string") return [{
1163
+ role: "system",
1164
+ content: system
1165
+ }];
1166
+ else return [{
1167
+ role: "system",
1168
+ content: system.map((block) => block.text).join("\n\n")
1169
+ }];
1170
+ }
1171
+ function handleUserMessage(message) {
1172
+ const newMessages = [];
1173
+ if (Array.isArray(message.content)) {
1174
+ const toolResultBlocks = message.content.filter((block) => block.type === "tool_result");
1175
+ const otherBlocks = message.content.filter((block) => block.type !== "tool_result");
1176
+ for (const block of toolResultBlocks) newMessages.push({
1177
+ role: "tool",
1178
+ tool_call_id: block.tool_use_id,
1179
+ content: mapContent(block.content)
1180
+ });
1181
+ if (otherBlocks.length > 0) newMessages.push({
1182
+ role: "user",
1183
+ content: mapContent(otherBlocks)
1184
+ });
1185
+ } else newMessages.push({
1186
+ role: "user",
1187
+ content: mapContent(message.content)
1188
+ });
1189
+ return newMessages;
1190
+ }
1191
+ function handleAssistantMessage(message) {
1192
+ if (!Array.isArray(message.content)) return [{
1193
+ role: "assistant",
1194
+ content: mapContent(message.content)
1195
+ }];
1196
+ const toolUseBlocks = message.content.filter((block) => block.type === "tool_use");
1197
+ const textBlocks = message.content.filter((block) => block.type === "text");
1198
+ const thinkingBlocks = message.content.filter((block) => block.type === "thinking");
1199
+ const allTextContent = [...textBlocks.map((b) => b.text), ...thinkingBlocks.map((b) => b.thinking)].join("\n\n");
1200
+ return toolUseBlocks.length > 0 ? [{
1201
+ role: "assistant",
1202
+ content: allTextContent || null,
1203
+ tool_calls: toolUseBlocks.map((toolUse) => ({
1204
+ id: toolUse.id,
1205
+ type: "function",
1206
+ function: {
1207
+ name: toolUse.name,
1208
+ arguments: JSON.stringify(toolUse.input)
1209
+ }
1210
+ }))
1211
+ }] : [{
1212
+ role: "assistant",
1213
+ content: mapContent(message.content)
1214
+ }];
1215
+ }
1216
+ function mapContent(content) {
1217
+ if (typeof content === "string") return content;
1218
+ if (!Array.isArray(content)) return null;
1219
+ if (!content.some((block) => block.type === "image")) return content.filter((block) => block.type === "text" || block.type === "thinking").map((block) => block.type === "text" ? block.text : block.thinking).join("\n\n");
1220
+ const contentParts = [];
1221
+ for (const block of content) switch (block.type) {
1222
+ case "text":
1223
+ contentParts.push({
1224
+ type: "text",
1225
+ text: block.text
1226
+ });
1227
+ break;
1228
+ case "thinking":
1229
+ contentParts.push({
1230
+ type: "text",
1231
+ text: block.thinking
1232
+ });
1233
+ break;
1234
+ case "image":
1235
+ contentParts.push({
1236
+ type: "image_url",
1237
+ image_url: { url: `data:${block.source.media_type};base64,${block.source.data}` }
1238
+ });
1239
+ break;
1240
+ }
1241
+ return contentParts;
1242
+ }
1243
+ function translateAnthropicToolsToOpenAI(anthropicTools) {
1244
+ if (!anthropicTools) return;
1245
+ return anthropicTools.map((tool) => ({
1246
+ type: "function",
1247
+ function: {
1248
+ name: tool.name,
1249
+ description: tool.description,
1250
+ parameters: tool.input_schema
1251
+ }
1252
+ }));
1253
+ }
1254
+ function translateAnthropicToolChoiceToOpenAI(anthropicToolChoice) {
1255
+ if (!anthropicToolChoice) return;
1256
+ switch (anthropicToolChoice.type) {
1257
+ case "auto": return "auto";
1258
+ case "any": return "required";
1259
+ case "tool":
1260
+ if (anthropicToolChoice.name) return {
1261
+ type: "function",
1262
+ function: { name: anthropicToolChoice.name }
1263
+ };
1264
+ return;
1265
+ case "none": return "none";
1266
+ default: return;
1267
+ }
1268
+ }
1269
+ function translateToAnthropic(response) {
1270
+ const allTextBlocks = [];
1271
+ const allToolUseBlocks = [];
1272
+ let stopReason = null;
1273
+ stopReason = response.choices[0]?.finish_reason ?? stopReason;
1274
+ for (const choice of response.choices) {
1275
+ const textBlocks = getAnthropicTextBlocks(choice.message.content);
1276
+ const toolUseBlocks = getAnthropicToolUseBlocks(choice.message.tool_calls);
1277
+ allTextBlocks.push(...textBlocks);
1278
+ allToolUseBlocks.push(...toolUseBlocks);
1279
+ if (choice.finish_reason === "tool_calls" || stopReason === "stop") stopReason = choice.finish_reason;
1280
+ }
1281
+ return {
1282
+ id: response.id,
1283
+ type: "message",
1284
+ role: "assistant",
1285
+ model: response.model,
1286
+ content: [...allTextBlocks, ...allToolUseBlocks],
1287
+ stop_reason: mapOpenAIStopReasonToAnthropic(stopReason),
1288
+ stop_sequence: null,
1289
+ usage: {
1290
+ input_tokens: (response.usage?.prompt_tokens ?? 0) - (response.usage?.prompt_tokens_details?.cached_tokens ?? 0),
1291
+ output_tokens: response.usage?.completion_tokens ?? 0,
1292
+ ...response.usage?.prompt_tokens_details?.cached_tokens !== void 0 && { cache_read_input_tokens: response.usage.prompt_tokens_details.cached_tokens }
1293
+ }
1294
+ };
1295
+ }
1296
+ function getAnthropicTextBlocks(messageContent) {
1297
+ if (typeof messageContent === "string") return [{
1298
+ type: "text",
1299
+ text: messageContent
1300
+ }];
1301
+ if (Array.isArray(messageContent)) return messageContent.filter((part) => part.type === "text").map((part) => ({
1302
+ type: "text",
1303
+ text: part.text
1304
+ }));
1305
+ return [];
1306
+ }
1307
+ function getAnthropicToolUseBlocks(toolCalls) {
1308
+ if (!toolCalls) return [];
1309
+ return toolCalls.map((toolCall) => ({
1310
+ type: "tool_use",
1311
+ id: toolCall.id,
1312
+ name: toolCall.function.name,
1313
+ input: JSON.parse(toolCall.function.arguments)
1314
+ }));
1315
+ }
1316
+
1317
+ //#endregion
1318
+ //#region src/routes/messages/count-tokens-handler.ts
1319
+ /**
1320
+ * Handles token counting for Anthropic messages
1321
+ */
1322
+ async function handleCountTokens(c) {
1323
+ try {
1324
+ const anthropicBeta = c.req.header("anthropic-beta");
1325
+ const anthropicPayload = await c.req.json();
1326
+ const openAIPayload = translateToOpenAI(anthropicPayload);
1327
+ const selectedModel = state.models?.data.find((model) => model.id === anthropicPayload.model);
1328
+ if (!selectedModel) {
1329
+ consola.warn("Model not found, returning default token count");
1330
+ return c.json({ input_tokens: 1 });
1331
+ }
1332
+ const tokenCount = await getTokenCount(openAIPayload, selectedModel);
1333
+ if (anthropicPayload.tools && anthropicPayload.tools.length > 0) {
1334
+ let mcpToolExist = false;
1335
+ if (anthropicBeta?.startsWith("claude-code")) mcpToolExist = anthropicPayload.tools.some((tool) => tool.name.startsWith("mcp__"));
1336
+ if (!mcpToolExist) {
1337
+ if (anthropicPayload.model.startsWith("claude")) tokenCount.input = tokenCount.input + 346;
1338
+ else if (anthropicPayload.model.startsWith("grok")) tokenCount.input = tokenCount.input + 480;
1339
+ }
1340
+ }
1341
+ let finalTokenCount = tokenCount.input + tokenCount.output;
1342
+ if (anthropicPayload.model.startsWith("claude")) finalTokenCount = Math.round(finalTokenCount * 1.15);
1343
+ else if (anthropicPayload.model.startsWith("grok")) finalTokenCount = Math.round(finalTokenCount * 1.03);
1344
+ consola.info("Token count:", finalTokenCount);
1345
+ return c.json({ input_tokens: finalTokenCount });
1346
+ } catch (error) {
1347
+ consola.error("Error counting tokens:", error);
1348
+ return c.json({ input_tokens: 1 });
1349
+ }
1350
+ }
1351
+
1352
+ //#endregion
1353
+ //#region src/routes/messages/stream-translation.ts
1354
+ function isToolBlockOpen(state$1) {
1355
+ if (!state$1.contentBlockOpen) return false;
1356
+ return Object.values(state$1.toolCalls).some((tc) => tc.anthropicBlockIndex === state$1.contentBlockIndex);
1357
+ }
1358
+ function translateChunkToAnthropicEvents(chunk, state$1) {
1359
+ const events$1 = [];
1360
+ if (chunk.choices.length === 0) return events$1;
1361
+ const choice = chunk.choices[0];
1362
+ const { delta } = choice;
1363
+ if (!state$1.messageStartSent) {
1364
+ events$1.push({
1365
+ type: "message_start",
1366
+ message: {
1367
+ id: chunk.id,
1368
+ type: "message",
1369
+ role: "assistant",
1370
+ content: [],
1371
+ model: chunk.model,
1372
+ stop_reason: null,
1373
+ stop_sequence: null,
1374
+ usage: {
1375
+ input_tokens: (chunk.usage?.prompt_tokens ?? 0) - (chunk.usage?.prompt_tokens_details?.cached_tokens ?? 0),
1376
+ output_tokens: 0,
1377
+ ...chunk.usage?.prompt_tokens_details?.cached_tokens !== void 0 && { cache_read_input_tokens: chunk.usage.prompt_tokens_details.cached_tokens }
1378
+ }
1379
+ }
1380
+ });
1381
+ state$1.messageStartSent = true;
1382
+ }
1383
+ if (delta.content) {
1384
+ if (isToolBlockOpen(state$1)) {
1385
+ events$1.push({
1386
+ type: "content_block_stop",
1387
+ index: state$1.contentBlockIndex
1388
+ });
1389
+ state$1.contentBlockIndex++;
1390
+ state$1.contentBlockOpen = false;
1391
+ }
1392
+ if (!state$1.contentBlockOpen) {
1393
+ events$1.push({
1394
+ type: "content_block_start",
1395
+ index: state$1.contentBlockIndex,
1396
+ content_block: {
1397
+ type: "text",
1398
+ text: ""
1399
+ }
1400
+ });
1401
+ state$1.contentBlockOpen = true;
1402
+ }
1403
+ events$1.push({
1404
+ type: "content_block_delta",
1405
+ index: state$1.contentBlockIndex,
1406
+ delta: {
1407
+ type: "text_delta",
1408
+ text: delta.content
1409
+ }
1410
+ });
1411
+ }
1412
+ if (delta.tool_calls) for (const toolCall of delta.tool_calls) {
1413
+ if (toolCall.id && toolCall.function?.name) {
1414
+ if (state$1.contentBlockOpen) {
1415
+ events$1.push({
1416
+ type: "content_block_stop",
1417
+ index: state$1.contentBlockIndex
1418
+ });
1419
+ state$1.contentBlockIndex++;
1420
+ state$1.contentBlockOpen = false;
1421
+ }
1422
+ const anthropicBlockIndex = state$1.contentBlockIndex;
1423
+ state$1.toolCalls[toolCall.index] = {
1424
+ id: toolCall.id,
1425
+ name: toolCall.function.name,
1426
+ anthropicBlockIndex
1427
+ };
1428
+ events$1.push({
1429
+ type: "content_block_start",
1430
+ index: anthropicBlockIndex,
1431
+ content_block: {
1432
+ type: "tool_use",
1433
+ id: toolCall.id,
1434
+ name: toolCall.function.name,
1435
+ input: {}
1436
+ }
1437
+ });
1438
+ state$1.contentBlockOpen = true;
1439
+ }
1440
+ if (toolCall.function?.arguments) {
1441
+ const toolCallInfo = state$1.toolCalls[toolCall.index];
1442
+ if (toolCallInfo) events$1.push({
1443
+ type: "content_block_delta",
1444
+ index: toolCallInfo.anthropicBlockIndex,
1445
+ delta: {
1446
+ type: "input_json_delta",
1447
+ partial_json: toolCall.function.arguments
1448
+ }
1449
+ });
1450
+ }
1451
+ }
1452
+ if (choice.finish_reason) {
1453
+ if (state$1.contentBlockOpen) {
1454
+ events$1.push({
1455
+ type: "content_block_stop",
1456
+ index: state$1.contentBlockIndex
1457
+ });
1458
+ state$1.contentBlockOpen = false;
1459
+ }
1460
+ events$1.push({
1461
+ type: "message_delta",
1462
+ delta: {
1463
+ stop_reason: mapOpenAIStopReasonToAnthropic(choice.finish_reason),
1464
+ stop_sequence: null
1465
+ },
1466
+ usage: {
1467
+ input_tokens: (chunk.usage?.prompt_tokens ?? 0) - (chunk.usage?.prompt_tokens_details?.cached_tokens ?? 0),
1468
+ output_tokens: chunk.usage?.completion_tokens ?? 0,
1469
+ ...chunk.usage?.prompt_tokens_details?.cached_tokens !== void 0 && { cache_read_input_tokens: chunk.usage.prompt_tokens_details.cached_tokens }
1470
+ }
1471
+ }, { type: "message_stop" });
1472
+ }
1473
+ return events$1;
1474
+ }
1475
+
1476
+ //#endregion
1477
+ //#region src/routes/messages/handler.ts
1478
+ async function handleCompletion(c) {
1479
+ await checkRateLimit(state);
1480
+ let anthropicPayload = await c.req.json();
1481
+ consola.debug("Anthropic request payload:", JSON.stringify(anthropicPayload));
1482
+ const mappings = getModelMappings();
1483
+ const explicit = mappings.size > 0 ? applyModelMapping(anthropicPayload.model, mappings, state.verbose) : {
1484
+ model: anthropicPayload.model,
1485
+ mapped: false
1486
+ };
1487
+ const resolved = explicit.mapped ? explicit : resolveModel(anthropicPayload.model, state.models?.data.map((m) => m.id) ?? [], state.verbose);
1488
+ if (resolved.model !== anthropicPayload.model) anthropicPayload = {
1489
+ ...anthropicPayload,
1490
+ model: resolved.model
1491
+ };
1492
+ const traceTimestamp = await traceRequest({
1493
+ type: "anthropic",
1494
+ original: anthropicPayload
1495
+ });
1496
+ const openAIPayload = translateToOpenAI(anthropicPayload);
1497
+ consola.debug("Translated OpenAI request payload:", JSON.stringify(openAIPayload));
1498
+ if (state.manualApprove) await awaitApproval();
1499
+ const response = await createChatCompletions(openAIPayload);
1500
+ if (isNonStreaming(response)) {
1501
+ consola.debug("Non-streaming response from Copilot:", JSON.stringify(response).slice(-400));
1502
+ const anthropicResponse = translateToAnthropic(response);
1503
+ consola.debug("Translated Anthropic response:", JSON.stringify(anthropicResponse));
1504
+ await traceResponse({
1505
+ type: "anthropic",
1506
+ openai: response,
1507
+ translated: anthropicResponse
1508
+ }, traceTimestamp);
1509
+ return c.json(anthropicResponse);
1510
+ }
1511
+ consola.debug("Streaming response from Copilot");
1512
+ return streamSSE(c, async (stream) => {
1513
+ const streamState = {
1514
+ messageStartSent: false,
1515
+ contentBlockIndex: 0,
1516
+ contentBlockOpen: false,
1517
+ toolCalls: {}
1518
+ };
1519
+ const streamTracer = new StreamTracer(traceTimestamp);
1520
+ for await (const rawEvent of response) {
1521
+ consola.debug("Copilot raw stream event:", JSON.stringify(rawEvent));
1522
+ if (rawEvent.data === "[DONE]") break;
1523
+ if (!rawEvent.data) continue;
1524
+ const chunk = JSON.parse(rawEvent.data);
1525
+ const events$1 = translateChunkToAnthropicEvents(chunk, streamState);
1526
+ for (const event of events$1) {
1527
+ consola.debug("Translated Anthropic event:", JSON.stringify(event));
1528
+ streamTracer.addChunk({
1529
+ openai: chunk,
1530
+ anthropic: event
1531
+ });
1532
+ await stream.writeSSE({
1533
+ event: event.type,
1534
+ data: JSON.stringify(event)
1535
+ });
1536
+ }
1537
+ }
1538
+ await streamTracer.finish();
1539
+ });
1540
+ }
1541
+ const isNonStreaming = (response) => Object.hasOwn(response, "choices");
1542
+
1543
+ //#endregion
1544
+ //#region src/routes/messages/route.ts
1545
+ const messageRoutes = new Hono();
1546
+ messageRoutes.post("/", async (c) => {
1547
+ try {
1548
+ return await handleCompletion(c);
1549
+ } catch (error) {
1550
+ return await forwardError(c, error);
1551
+ }
1552
+ });
1553
+ messageRoutes.post("/count_tokens", async (c) => {
1554
+ try {
1555
+ return await handleCountTokens(c);
1556
+ } catch (error) {
1557
+ return await forwardError(c, error);
1558
+ }
1559
+ });
1560
+
1561
+ //#endregion
1562
+ //#region src/routes/models/route.ts
1563
+ const modelRoutes = new Hono();
1564
+ modelRoutes.get("/", async (c) => {
1565
+ try {
1566
+ if (!state.models) await cacheModels();
1567
+ const models = state.models?.data.map((model) => ({
1568
+ id: model.id,
1569
+ object: "model",
1570
+ type: "model",
1571
+ created: 0,
1572
+ created_at: (/* @__PURE__ */ new Date(0)).toISOString(),
1573
+ owned_by: model.vendor,
1574
+ display_name: model.name
1575
+ }));
1576
+ return c.json({
1577
+ object: "list",
1578
+ data: models,
1579
+ has_more: false
1580
+ });
1581
+ } catch (error) {
1582
+ return await forwardError(c, error);
1583
+ }
1584
+ });
1585
+
1586
+ //#endregion
1587
+ //#region src/routes/token/route.ts
1588
+ const tokenRoute = new Hono();
1589
+ tokenRoute.get("/", (c) => {
1590
+ try {
1591
+ return c.json({ token: state.copilotToken });
1592
+ } catch (error) {
1593
+ console.error("Error fetching token:", error);
1594
+ return c.json({
1595
+ error: "Failed to fetch token",
1596
+ token: null
1597
+ }, 500);
1598
+ }
1599
+ });
1600
+
1601
+ //#endregion
1602
+ //#region src/routes/usage/route.ts
1603
+ const usageRoute = new Hono();
1604
+ usageRoute.get("/", async (c) => {
1605
+ try {
1606
+ const usage = await getCopilotUsage();
1607
+ return c.json(usage);
1608
+ } catch (error) {
1609
+ console.error("Error fetching Copilot usage:", error);
1610
+ return c.json({ error: "Failed to fetch Copilot usage" }, 500);
1611
+ }
1612
+ });
1613
+
1614
+ //#endregion
1615
+ //#region src/server.ts
1616
+ const server = new Hono();
1617
+ server.use(logger());
1618
+ server.use(cors());
1619
+ server.get("/", (c) => c.text("Server running"));
1620
+ server.route("/chat/completions", completionRoutes);
1621
+ server.route("/models", modelRoutes);
1622
+ server.route("/embeddings", embeddingRoutes);
1623
+ server.route("/usage", usageRoute);
1624
+ server.route("/token", tokenRoute);
1625
+ server.route("/v1/chat/completions", completionRoutes);
1626
+ server.route("/v1/models", modelRoutes);
1627
+ server.route("/v1/embeddings", embeddingRoutes);
1628
+ server.route("/v1/messages", messageRoutes);
1629
+
1630
+ //#endregion
1631
+ //#region src/start.ts
1632
+ async function runServer(options) {
1633
+ if (options.proxyEnv) initProxyFromEnv();
1634
+ if (options.verbose) {
1635
+ consola.level = 5;
1636
+ state.verbose = true;
1637
+ consola.info("Verbose logging enabled");
1638
+ }
1639
+ state.accountType = options.accountType;
1640
+ if (options.accountType !== "individual") consola.info(`Using ${options.accountType} plan GitHub account`);
1641
+ state.manualApprove = options.manual;
1642
+ state.rateLimitSeconds = options.rateLimit;
1643
+ state.rateLimitWait = options.rateLimitWait;
1644
+ state.showToken = options.showToken;
1645
+ if (options.trace) {
1646
+ state.traceEnabled = true;
1647
+ state.traceFolder = options.traceFolder || process.env.TRACE_OUTPUT_FOLDER || path.join(process.cwd(), "traces");
1648
+ consola.info(`Tracing enabled. Logs will be saved to: ${state.traceFolder}`);
1649
+ await ensureTraceFolder();
1650
+ }
1651
+ await ensurePaths();
1652
+ await cacheVSCodeVersion();
1653
+ if (options.githubToken) {
1654
+ state.githubToken = options.githubToken;
1655
+ consola.info("Using provided GitHub token");
1656
+ } else await setupGitHubToken();
1657
+ await setupCopilotToken();
1658
+ await cacheModels();
1659
+ consola.info(`Available models: \n${state.models?.data.map((model) => `- ${model.id}`).join("\n")}`);
1660
+ const serverUrl = `http://localhost:${options.port}`;
1661
+ if (options.claudeCode) {
1662
+ invariant(state.models, "Models should be loaded by now");
1663
+ const selectedModel = await consola.prompt("Select a model to use with Claude Code", {
1664
+ type: "select",
1665
+ options: state.models.data.map((model) => model.id)
1666
+ });
1667
+ const selectedSmallModel = await consola.prompt("Select a small model to use with Claude Code", {
1668
+ type: "select",
1669
+ options: state.models.data.map((model) => model.id)
1670
+ });
1671
+ const command = generateEnvScript({
1672
+ ANTHROPIC_BASE_URL: serverUrl,
1673
+ ANTHROPIC_AUTH_TOKEN: "dummy",
1674
+ ANTHROPIC_MODEL: selectedModel,
1675
+ ANTHROPIC_DEFAULT_SONNET_MODEL: selectedModel,
1676
+ ANTHROPIC_SMALL_FAST_MODEL: selectedSmallModel,
1677
+ ANTHROPIC_DEFAULT_HAIKU_MODEL: selectedSmallModel,
1678
+ DISABLE_NON_ESSENTIAL_MODEL_CALLS: "1",
1679
+ CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: "1"
1680
+ }, "claude");
1681
+ try {
1682
+ clipboard.writeSync(command);
1683
+ consola.success("Copied Claude Code command to clipboard!");
1684
+ } catch {
1685
+ consola.warn("Failed to copy to clipboard. Here is the Claude Code command:");
1686
+ consola.log(command);
1687
+ }
1688
+ }
1689
+ consola.box(`🌐 Usage Viewer: https://ericc-ch.github.io/copilot-api?endpoint=${serverUrl}/usage`);
1690
+ const idleTimeout = process.env.IDLE_TIMEOUT ? Number.parseInt(process.env.IDLE_TIMEOUT, 10) : 255;
1691
+ serve({
1692
+ fetch: server.fetch,
1693
+ port: options.port,
1694
+ bun: { idleTimeout }
1695
+ });
1696
+ }
1697
+ const start = defineCommand({
1698
+ meta: {
1699
+ name: "start",
1700
+ description: "Start the Copilot API server"
1701
+ },
1702
+ args: {
1703
+ port: {
1704
+ alias: "p",
1705
+ type: "string",
1706
+ default: "4141",
1707
+ description: "Port to listen on"
1708
+ },
1709
+ verbose: {
1710
+ alias: "v",
1711
+ type: "boolean",
1712
+ default: false,
1713
+ description: "Enable verbose logging"
1714
+ },
1715
+ "account-type": {
1716
+ alias: "a",
1717
+ type: "string",
1718
+ default: "individual",
1719
+ description: "Account type to use (individual, business, enterprise)"
1720
+ },
1721
+ manual: {
1722
+ type: "boolean",
1723
+ default: false,
1724
+ description: "Enable manual request approval"
1725
+ },
1726
+ "rate-limit": {
1727
+ alias: "r",
1728
+ type: "string",
1729
+ description: "Rate limit in seconds between requests"
1730
+ },
1731
+ wait: {
1732
+ alias: "w",
1733
+ type: "boolean",
1734
+ default: false,
1735
+ description: "Wait instead of error when rate limit is hit. Has no effect if rate limit is not set"
1736
+ },
1737
+ "github-token": {
1738
+ alias: "g",
1739
+ type: "string",
1740
+ description: "Provide GitHub token directly (must be generated using the `auth` subcommand)"
1741
+ },
1742
+ "claude-code": {
1743
+ alias: "c",
1744
+ type: "boolean",
1745
+ default: false,
1746
+ description: "Generate a command to launch Claude Code with Copilot API config"
1747
+ },
1748
+ "show-token": {
1749
+ type: "boolean",
1750
+ default: false,
1751
+ description: "Show GitHub and Copilot tokens on fetch and refresh"
1752
+ },
1753
+ "proxy-env": {
1754
+ type: "boolean",
1755
+ default: false,
1756
+ description: "Initialize proxy from environment variables"
1757
+ },
1758
+ trace: {
1759
+ alias: "t",
1760
+ type: "boolean",
1761
+ default: false,
1762
+ description: "Enable tracing to log all LLM requests and responses to files"
1763
+ },
1764
+ "trace-folder": {
1765
+ type: "string",
1766
+ description: "Folder to save trace files (defaults to TRACE_OUTPUT_FOLDER env var or ./traces)"
1767
+ }
1768
+ },
1769
+ run({ args }) {
1770
+ const rateLimitRaw = args["rate-limit"];
1771
+ const rateLimit = rateLimitRaw === void 0 ? void 0 : Number.parseInt(rateLimitRaw, 10);
1772
+ return runServer({
1773
+ port: Number.parseInt(args.port, 10),
1774
+ verbose: args.verbose,
1775
+ accountType: args["account-type"],
1776
+ manual: args.manual,
1777
+ rateLimit,
1778
+ rateLimitWait: args.wait,
1779
+ githubToken: args["github-token"],
1780
+ claudeCode: args["claude-code"],
1781
+ showToken: args["show-token"],
1782
+ proxyEnv: args["proxy-env"],
1783
+ trace: args.trace,
1784
+ traceFolder: args["trace-folder"]
1785
+ });
1786
+ }
1787
+ });
1788
+
1789
+ //#endregion
1790
+ //#region src/main.ts
1791
+ const main = defineCommand({
1792
+ meta: {
1793
+ name: "copilot-api",
1794
+ description: "A wrapper around GitHub Copilot API to make it OpenAI compatible, making it usable for other tools."
1795
+ },
1796
+ subCommands: {
1797
+ auth,
1798
+ start,
1799
+ "check-usage": checkUsage,
1800
+ debug
1801
+ }
1802
+ });
1803
+ await runMain(main);
1804
+
1805
+ //#endregion
1806
+ export { };
1807
+ //# sourceMappingURL=main.js.map