copilot-api-node20 0.7.0 → 0.8.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 CHANGED
@@ -1,1904 +1,2 @@
1
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 { createHash, randomBytes, randomUUID } from "node:crypto";
8
- import clipboard from "clipboardy";
9
- import process$1 from "node:process";
10
- import { serve } from "srvx";
11
- import invariant from "tiny-invariant";
12
- import { execSync } from "node:child_process";
13
- import { Hono } from "hono";
14
- import { cors } from "hono/cors";
15
- import { streamSSE } from "hono/streaming";
16
- import { events } from "fetch-event-stream";
17
- import { request } from "undici";
18
-
19
- //#region src/lib/paths.ts
20
- const APP_DIR = path.join(os.homedir(), ".local", "share", "copilot-api");
21
- const GITHUB_TOKEN_PATH = path.join(APP_DIR, "github_token");
22
- const MACHINE_ID_PATH = path.join(APP_DIR, "machine_id");
23
- const SESSION_ID_PATH = path.join(APP_DIR, "session_id");
24
- const PATHS = {
25
- APP_DIR,
26
- GITHUB_TOKEN_PATH,
27
- MACHINE_ID_PATH,
28
- SESSION_ID_PATH
29
- };
30
- async function ensurePaths() {
31
- await fs.mkdir(PATHS.APP_DIR, { recursive: true });
32
- await ensureFile(PATHS.GITHUB_TOKEN_PATH);
33
- await ensureFile(PATHS.MACHINE_ID_PATH);
34
- await ensureFile(PATHS.SESSION_ID_PATH);
35
- }
36
- async function ensureFile(filePath) {
37
- try {
38
- await fs.access(filePath, fs.constants.W_OK);
39
- } catch {
40
- await fs.writeFile(filePath, "");
41
- await fs.chmod(filePath, 384);
42
- }
43
- }
44
-
45
- //#endregion
46
- //#region src/lib/state.ts
47
- const state = {
48
- accountType: "individual",
49
- manualApprove: false,
50
- rateLimitWait: false,
51
- showToken: false,
52
- connectivity: {
53
- enabled: true,
54
- probeEndpoints: [
55
- "https://api.github.com",
56
- "https://www.google.com",
57
- "https://1.1.1.1"
58
- ],
59
- fastProbeInterval: 5e3,
60
- slowProbeInterval: 6e4,
61
- timeoutMs: 5e3,
62
- jitterMaxMs: 1e3,
63
- connectionPooling: true,
64
- dnsCache: true
65
- }
66
- };
67
-
68
- //#endregion
69
- //#region src/lib/connectivity.ts
70
- var ConnectivityMonitor = class extends EventTarget {
71
- isOnline = true;
72
- lastChecked = (/* @__PURE__ */ new Date()).toISOString();
73
- consecutiveFailures = 0;
74
- lastErrorType;
75
- lastErrorMessage;
76
- lastSuccessfulEndpoint;
77
- endpointFailureStats = {};
78
- checkInterval;
79
- abortController;
80
- on(event, listener) {
81
- this.addEventListener(event, listener);
82
- return this;
83
- }
84
- off(event, listener) {
85
- this.removeEventListener(event, listener);
86
- return this;
87
- }
88
- start() {
89
- if (!state.connectivity.enabled) {
90
- consola.debug("Connectivity monitoring disabled");
91
- return;
92
- }
93
- consola.info("Starting connectivity monitor", {
94
- probeEndpoints: state.connectivity.probeEndpoints,
95
- fastInterval: state.connectivity.fastProbeInterval
96
- });
97
- this.scheduleNextCheck();
98
- const cleanup = () => {
99
- this.stop();
100
- process.exit(0);
101
- };
102
- process.on("SIGINT", cleanup);
103
- process.on("SIGTERM", cleanup);
104
- }
105
- stop() {
106
- consola.debug("Stopping connectivity monitor");
107
- if (this.checkInterval) {
108
- clearTimeout(this.checkInterval);
109
- this.checkInterval = void 0;
110
- }
111
- if (this.abortController) {
112
- this.abortController.abort();
113
- this.abortController = void 0;
114
- }
115
- }
116
- scheduleNextCheck() {
117
- if (this.checkInterval) clearTimeout(this.checkInterval);
118
- const baseInterval = this.isOnline ? state.connectivity.slowProbeInterval : state.connectivity.fastProbeInterval;
119
- const jitter = Math.random() * state.connectivity.jitterMaxMs;
120
- const interval = baseInterval + jitter;
121
- this.checkInterval = setTimeout(() => {
122
- this.performConnectivityCheck().catch((error) => {
123
- consola.error("Connectivity check failed:", error);
124
- });
125
- }, interval);
126
- }
127
- async performConnectivityCheck() {
128
- this.abortController = new AbortController();
129
- const timeoutId = setTimeout(() => this.abortController?.abort(), state.connectivity.timeoutMs);
130
- try {
131
- let success = false;
132
- for (const endpoint of state.connectivity.probeEndpoints) try {
133
- const response = await fetch(endpoint, {
134
- method: "HEAD",
135
- signal: this.abortController.signal,
136
- headers: {
137
- "User-Agent": "copilot-api-connectivity-monitor/1.0",
138
- ...state.connectivity.dnsCache && { "Cache-Control": "max-age=300" }
139
- }
140
- });
141
- if (response.ok) {
142
- success = true;
143
- this.lastSuccessfulEndpoint = endpoint;
144
- break;
145
- }
146
- } catch (error) {
147
- this.endpointFailureStats[endpoint] = (this.endpointFailureStats[endpoint] || 0) + 1;
148
- consola.debug(`Probe failed for ${endpoint}:`, error);
149
- }
150
- this.updateConnectivityState(success);
151
- } catch (error) {
152
- this.handleConnectivityError(error);
153
- } finally {
154
- clearTimeout(timeoutId);
155
- this.scheduleNextCheck();
156
- }
157
- }
158
- updateConnectivityState(isOnline) {
159
- const wasOnline = this.isOnline;
160
- this.isOnline = isOnline;
161
- this.lastChecked = (/* @__PURE__ */ new Date()).toISOString();
162
- if (isOnline) {
163
- if (this.consecutiveFailures > 0) consola.info(`Connectivity restored after ${this.consecutiveFailures} failures`);
164
- this.consecutiveFailures = 0;
165
- this.lastErrorType = void 0;
166
- this.lastErrorMessage = void 0;
167
- if (!wasOnline) this.dispatchEvent(new CustomEvent("online"));
168
- } else {
169
- this.consecutiveFailures++;
170
- if (wasOnline) {
171
- consola.warn("Connectivity lost");
172
- this.dispatchEvent(new CustomEvent("offline"));
173
- }
174
- }
175
- }
176
- handleConnectivityError(error) {
177
- this.lastErrorType = error.name;
178
- this.lastErrorMessage = error.message;
179
- consola.error("Connectivity check failed:", error);
180
- this.updateConnectivityState(false);
181
- }
182
- getConnectivityStats() {
183
- return {
184
- isOnline: this.isOnline,
185
- lastChecked: this.lastChecked,
186
- consecutiveFailures: this.consecutiveFailures,
187
- lastErrorType: this.lastErrorType,
188
- lastErrorMessage: this.lastErrorMessage
189
- };
190
- }
191
- getPerformanceStats() {
192
- const currentInterval = this.isOnline ? state.connectivity.slowProbeInterval : state.connectivity.fastProbeInterval;
193
- const nextCheckEstimate = new Date(Date.now() + currentInterval + Math.random() * state.connectivity.jitterMaxMs).toISOString();
194
- return {
195
- currentInterval,
196
- nextCheckEstimate,
197
- lastSuccessfulEndpoint: this.lastSuccessfulEndpoint,
198
- endpointFailureStats: { ...this.endpointFailureStats },
199
- jitterEnabled: state.connectivity.jitterMaxMs > 0,
200
- connectionPooling: state.connectivity.connectionPooling,
201
- dnsCache: state.connectivity.dnsCache
202
- };
203
- }
204
- };
205
- const connectivityMonitor = new ConnectivityMonitor();
206
-
207
- //#endregion
208
- //#region src/lib/api-config.ts
209
- const standardHeaders = () => ({
210
- "content-type": "application/json",
211
- accept: "application/json"
212
- });
213
- const COPILOT_VERSION = "0.32.2025100203";
214
- const EDITOR_PLUGIN_VERSION = `copilot-chat/${COPILOT_VERSION}`;
215
- const USER_AGENT = `GitHubCopilotChat/${COPILOT_VERSION}`;
216
- const API_VERSION = "2025-08-20";
217
- const generateMachineId = () => {
218
- const hash = createHash("sha256");
219
- hash.update(process.platform);
220
- hash.update(process.arch);
221
- hash.update(process.env.USER || process.env.USERNAME || "anonymous");
222
- hash.update(os.hostname());
223
- hash.update(Date.now().toString());
224
- hash.update(randomBytes(16));
225
- return hash.digest("hex");
226
- };
227
- const readMachineId = async () => {
228
- try {
229
- const machineId = await fs.readFile(PATHS.MACHINE_ID_PATH, "utf8");
230
- if (machineId.trim()) return machineId.trim();
231
- } catch {}
232
- const newMachineId = generateMachineId();
233
- await fs.writeFile(PATHS.MACHINE_ID_PATH, newMachineId);
234
- await fs.chmod(PATHS.MACHINE_ID_PATH, 384);
235
- return newMachineId;
236
- };
237
- const readSessionId = async () => {
238
- try {
239
- const sessionId = await fs.readFile(PATHS.SESSION_ID_PATH, "utf8");
240
- if (sessionId.trim()) return sessionId.trim();
241
- } catch {}
242
- const newSessionId = randomUUID();
243
- await fs.writeFile(PATHS.SESSION_ID_PATH, newSessionId);
244
- await fs.chmod(PATHS.SESSION_ID_PATH, 384);
245
- return newSessionId;
246
- };
247
- const initializeVSCodeIdentifiers = async (state$1) => {
248
- if (!state$1.machineId) state$1.machineId = await readMachineId();
249
- if (!state$1.sessionId) state$1.sessionId = await readSessionId();
250
- };
251
- const copilotBaseUrl = (state$1) => state$1.accountType === "individual" ? "https://api.githubcopilot.com" : `https://api.${state$1.accountType}.githubcopilot.com`;
252
- const copilotHeaders = (state$1, vision = false) => {
253
- if (!state$1.machineId || !state$1.sessionId) throw new Error("VSCode identifiers not initialized. Call initializeVSCodeIdentifiers() during startup.");
254
- const headers = {
255
- Authorization: `Bearer ${state$1.copilotToken}`,
256
- "Content-Type": "application/json",
257
- accept: "*/*",
258
- "accept-encoding": "br, gzip, deflate",
259
- "accept-language": "*",
260
- "sec-fetch-mode": "cors",
261
- "Copilot-Integration-Id": "vscode-chat",
262
- "Editor-Version": `vscode/${state$1.vsCodeVersion}`,
263
- "Editor-Plugin-Version": EDITOR_PLUGIN_VERSION,
264
- "User-Agent": USER_AGENT,
265
- "VScode-MachineId": state$1.machineId,
266
- "VScode-SessionId": state$1.sessionId,
267
- "OpenAI-Intent": "conversation-agent",
268
- "X-Interaction-Type": "conversation-agent",
269
- "X-VSCode-User-Agent-Library-Version": "node-fetch",
270
- "X-GitHub-Api-Version": API_VERSION,
271
- "X-Interaction-Id": randomUUID(),
272
- "X-Request-Id": randomUUID(),
273
- "openai-intent": "conversation-agent",
274
- "x-interaction-type": "conversation-agent"
275
- };
276
- if (vision) headers["copilot-vision-request"] = "true";
277
- return headers;
278
- };
279
- const GITHUB_API_BASE_URL = "https://api.github.com";
280
- const githubHeaders = (state$1) => ({
281
- ...standardHeaders(),
282
- authorization: `token ${state$1.githubToken}`,
283
- "editor-version": `vscode/${state$1.vsCodeVersion}`,
284
- "editor-plugin-version": EDITOR_PLUGIN_VERSION,
285
- "user-agent": USER_AGENT,
286
- "x-github-api-version": API_VERSION,
287
- "x-vscode-user-agent-library-version": "node-fetch"
288
- });
289
- const GITHUB_BASE_URL = "https://github.com";
290
- const GITHUB_CLIENT_ID = "01ab8ac9400c4e429b23";
291
- const GITHUB_APP_SCOPES = ["user:email"].join(" ");
292
-
293
- //#endregion
294
- //#region src/lib/logger/completion-logger.ts
295
- const humanize = (num) => {
296
- if (num >= 1e5) return `${Math.round(num / 1e3)}K`;
297
- if (num >= 1e4) return `${Math.round(num / 1e3)}K`;
298
- if (num >= 1e3) return `${(num / 1e3).toFixed(1)}K`;
299
- return num.toString();
300
- };
301
- const pad = (str, length) => str.padEnd(length);
302
- const getContextPercentage = (contextWindow, model) => {
303
- if (!state.models) return "";
304
- const selectedModel = state.models.data.find((m) => m.id === model);
305
- if (!selectedModel) return "";
306
- const maxContextTokens = selectedModel.capabilities.limits.max_context_window_tokens;
307
- if (!maxContextTokens) return "";
308
- const percentage = (contextWindow / maxContextTokens * 100).toFixed(1);
309
- return ` (${percentage}%)`;
310
- };
311
- const formatTokenUsage = (requestData) => {
312
- const parts = [];
313
- if (requestData.model) {
314
- const model = pad(requestData.model, 18);
315
- parts.push(model);
316
- }
317
- if (requestData.tokenUsage) {
318
- const usage = requestData.tokenUsage;
319
- const contextWindow = (usage.inputTokens || 0) + (usage.outputTokens || 0);
320
- const contextPercentage = requestData.model ? getContextPercentage(contextWindow, requestData.model) : "";
321
- const input = humanize(usage.inputTokens || 0).padStart(5);
322
- const output = humanize(usage.outputTokens || 0).padStart(5);
323
- const tokenPart = `↑${input} │ ↓${output}`;
324
- const tokens = pad(tokenPart, 18);
325
- const contextNum = contextWindow.toString();
326
- const contextFormatted = contextPercentage ? `${contextNum}${contextPercentage.padStart(15 - contextNum.length)}` : contextNum.padEnd(15);
327
- parts.push(`Tokens: ${tokens} | Context: ${contextFormatted}`);
328
- } else if (requestData.model) {
329
- const tokens = pad("N/A", 18);
330
- const context = "N/A".padEnd(15);
331
- parts.push(`Tokens: ${tokens} | Context: ${context}`);
332
- }
333
- if (requestData.copilotDuration) {
334
- const apiDuration = pad(`${Math.round(requestData.copilotDuration)}ms`, 8);
335
- parts.push(`API: ${apiDuration}`);
336
- }
337
- return parts.length > 0 ? ` | ${parts.join(" | ")}` : "";
338
- };
339
- const CompletionLogger = {
340
- completionCallbacks: /* @__PURE__ */ new Map(),
341
- registerCompletion(requestId, context, startTime) {
342
- this.completionCallbacks.set(requestId, {
343
- context,
344
- startTime,
345
- requestId
346
- });
347
- },
348
- executeCompletion(requestId) {
349
- const data = this.completionCallbacks.get(requestId);
350
- if (data) {
351
- this.logCompletion(data);
352
- this.completionCallbacks.delete(requestId);
353
- }
354
- },
355
- logCompletion(data) {
356
- const { context: c, startTime } = data;
357
- const end = Date.now();
358
- const duration = end - startTime;
359
- const requestData = c.get("requestData");
360
- const method = pad(c.req.method, 4);
361
- const path$1 = pad(c.req.path, 18);
362
- const status = pad(c.res.status.toString(), 3);
363
- const durationStr = pad(`${duration}ms`, 8);
364
- let logLine = ` --> ${method}${path$1}${status} ${durationStr}`;
365
- if (requestData) logLine += formatTokenUsage(requestData);
366
- consola.info(logLine);
367
- },
368
- logRateLimit(data, response) {
369
- const { context: c, startTime } = data;
370
- const end = Date.now();
371
- const duration = end - startTime;
372
- const rateLimitExceeded = response.headers.get("x-ratelimit-exceeded") || "";
373
- const rateLimitType = rateLimitExceeded.split(":")[1] || "unknown";
374
- const retry = response.headers.get("retry-after") || response.headers.get("x-ratelimit-user-retry-after") || "?";
375
- const method = pad(c.req.method, 4);
376
- const path$1 = pad(c.req.path, 18);
377
- const status = pad("429", 3);
378
- const durationStr = pad(`${duration}ms`, 8);
379
- let logLine = `⚠ --> ${method}${path$1}${status} ${durationStr}`;
380
- logLine += ` | Rate limited (${retry}s retry) | ${rateLimitType}`;
381
- const requestData = c.get("requestData");
382
- if (requestData?.copilotDuration) {
383
- const apiDuration = pad(`${Math.round(requestData.copilotDuration)}ms`, 8);
384
- logLine += ` | API: ${apiDuration}`;
385
- }
386
- consola.info(logLine);
387
- },
388
- cleanup() {
389
- const MAX_CALLBACKS = 1e3;
390
- const fiveMinutesAgo = Date.now() - 300 * 1e3;
391
- for (const [requestId, data] of this.completionCallbacks) if (data.startTime < fiveMinutesAgo) this.completionCallbacks.delete(requestId);
392
- if (this.completionCallbacks.size > MAX_CALLBACKS) {
393
- const entries = Array.from(this.completionCallbacks.entries()).sort(([, a], [, b]) => a.startTime - b.startTime);
394
- const toRemove = entries.slice(0, entries.length - MAX_CALLBACKS);
395
- for (const [requestId] of toRemove) this.completionCallbacks.delete(requestId);
396
- }
397
- }
398
- };
399
- setInterval(() => CompletionLogger.cleanup(), 60 * 1e3);
400
-
401
- //#endregion
402
- //#region src/lib/error.ts
403
- var HTTPError = class extends Error {
404
- response;
405
- constructor(message, response) {
406
- super(message);
407
- this.response = response;
408
- }
409
- };
410
- async function forwardError(c, error) {
411
- if (error instanceof HTTPError && error.response.status === 429) {
412
- const requestId = c.get("requestId");
413
- const completionData = CompletionLogger.completionCallbacks.get(requestId);
414
- if (completionData) {
415
- CompletionLogger.logRateLimit(completionData, error.response);
416
- CompletionLogger.completionCallbacks.delete(requestId);
417
- }
418
- const errorText = await error.response.text();
419
- let errorJson;
420
- try {
421
- errorJson = JSON.parse(errorText);
422
- } catch {
423
- errorJson = { error: {
424
- message: errorText,
425
- type: "error"
426
- } };
427
- }
428
- return c.json(errorJson, 429);
429
- }
430
- consola.error("Error occurred:", error);
431
- if (error instanceof HTTPError) {
432
- const errorText = await error.response.text();
433
- let errorJson;
434
- try {
435
- errorJson = JSON.parse(errorText);
436
- } catch {
437
- errorJson = errorText;
438
- }
439
- consola.error("HTTP error:", errorJson);
440
- return c.json({ error: {
441
- message: errorText,
442
- type: "error"
443
- } }, error.response.status);
444
- }
445
- return c.json({ error: {
446
- message: error.message,
447
- type: "error"
448
- } }, 500);
449
- }
450
-
451
- //#endregion
452
- //#region src/services/github/get-copilot-token.ts
453
- const getCopilotToken = async () => {
454
- const controller = new AbortController();
455
- const timeoutMs = state.timeoutMs ?? 12e4;
456
- const timeout = setTimeout(() => {
457
- controller.abort();
458
- }, timeoutMs);
459
- try {
460
- const response = await fetch(`${GITHUB_API_BASE_URL}/copilot_internal/v2/token`, {
461
- headers: githubHeaders(state),
462
- signal: controller.signal
463
- });
464
- if (!response.ok) throw new HTTPError("Failed to get Copilot token", response);
465
- return await response.json();
466
- } finally {
467
- clearTimeout(timeout);
468
- }
469
- };
470
-
471
- //#endregion
472
- //#region src/services/github/get-device-code.ts
473
- async function getDeviceCode() {
474
- const controller = new AbortController();
475
- const timeoutMs = state.timeoutMs ?? 12e4;
476
- const timeout = setTimeout(() => {
477
- controller.abort();
478
- }, timeoutMs);
479
- try {
480
- const response = await fetch(`${GITHUB_BASE_URL}/login/device/code`, {
481
- method: "POST",
482
- headers: standardHeaders(),
483
- body: JSON.stringify({
484
- client_id: GITHUB_CLIENT_ID,
485
- scope: GITHUB_APP_SCOPES
486
- }),
487
- signal: controller.signal
488
- });
489
- if (!response.ok) throw new HTTPError("Failed to get device code", response);
490
- return await response.json();
491
- } finally {
492
- clearTimeout(timeout);
493
- }
494
- }
495
-
496
- //#endregion
497
- //#region src/services/github/get-user.ts
498
- async function getGitHubUser() {
499
- const controller = new AbortController();
500
- const timeoutMs = state.timeoutMs ?? 12e4;
501
- const timeout = setTimeout(() => {
502
- controller.abort();
503
- }, timeoutMs);
504
- try {
505
- const response = await fetch(`${GITHUB_API_BASE_URL}/user`, {
506
- headers: {
507
- authorization: `token ${state.githubToken}`,
508
- ...standardHeaders()
509
- },
510
- signal: controller.signal
511
- });
512
- if (!response.ok) throw new HTTPError("Failed to get GitHub user", response);
513
- return await response.json();
514
- } finally {
515
- clearTimeout(timeout);
516
- }
517
- }
518
-
519
- //#endregion
520
- //#region src/services/copilot/get-models.ts
521
- const getModels = async () => {
522
- const controller = new AbortController();
523
- const timeoutMs = state.timeoutMs ?? 12e4;
524
- const timeout = setTimeout(() => {
525
- controller.abort();
526
- }, timeoutMs);
527
- try {
528
- const response = await fetch(`${copilotBaseUrl(state)}/models`, {
529
- headers: copilotHeaders(state),
530
- signal: controller.signal
531
- });
532
- if (!response.ok) throw new HTTPError("Failed to get models", response);
533
- return await response.json();
534
- } finally {
535
- clearTimeout(timeout);
536
- }
537
- };
538
-
539
- //#endregion
540
- //#region src/services/get-vscode-version.ts
541
- function getVSCodeVersion() {
542
- return "1.105.0-insider";
543
- }
544
- getVSCodeVersion();
545
-
546
- //#endregion
547
- //#region src/lib/utils.ts
548
- const sleep = (ms) => new Promise((resolve) => {
549
- setTimeout(resolve, ms);
550
- });
551
- const isNullish = (value) => value === null || value === void 0;
552
- async function cacheModels() {
553
- const models = await getModels();
554
- state.models = models;
555
- }
556
- const cacheVSCodeVersion = () => {
557
- const response = getVSCodeVersion();
558
- state.vsCodeVersion = response;
559
- consola.info(`Using VSCode version: ${response}`);
560
- };
561
-
562
- //#endregion
563
- //#region src/services/github/poll-access-token.ts
564
- async function pollAccessToken(deviceCode) {
565
- const sleepDuration = (deviceCode.interval + 1) * 1e3;
566
- consola.debug(`Polling access token with interval of ${sleepDuration}ms`);
567
- while (true) {
568
- const controller = new AbortController();
569
- const timeoutMs = state.timeoutMs ?? 12e4;
570
- const timeout = setTimeout(() => {
571
- controller.abort();
572
- }, timeoutMs);
573
- try {
574
- const response = await fetch(`${GITHUB_BASE_URL}/login/oauth/access_token`, {
575
- method: "POST",
576
- headers: standardHeaders(),
577
- body: JSON.stringify({
578
- client_id: GITHUB_CLIENT_ID,
579
- device_code: deviceCode.device_code,
580
- grant_type: "urn:ietf:params:oauth:grant-type:device_code"
581
- }),
582
- signal: controller.signal
583
- });
584
- if (!response.ok) {
585
- await sleep(sleepDuration);
586
- consola.error("Failed to poll access token:", await response.text());
587
- continue;
588
- }
589
- const json = await response.json();
590
- consola.debug("Polling access token response:", json);
591
- const { access_token } = json;
592
- if (access_token) return access_token;
593
- else await sleep(sleepDuration);
594
- } catch (error) {
595
- if (error instanceof Error && error.name === "AbortError") {
596
- consola.error("Access token polling timed out");
597
- await sleep(sleepDuration);
598
- continue;
599
- }
600
- throw error;
601
- } finally {
602
- clearTimeout(timeout);
603
- }
604
- }
605
- }
606
-
607
- //#endregion
608
- //#region src/lib/token.ts
609
- let tokenRefreshInterval;
610
- let isRefreshPending = false;
611
- let onlineHandler;
612
- let offlineHandler;
613
- function cleanupTokenManagement() {
614
- consola.debug("Cleaning up token management");
615
- if (tokenRefreshInterval) {
616
- clearInterval(tokenRefreshInterval);
617
- tokenRefreshInterval = void 0;
618
- }
619
- if (onlineHandler) {
620
- connectivityMonitor.off("online", onlineHandler);
621
- onlineHandler = void 0;
622
- }
623
- if (offlineHandler) {
624
- connectivityMonitor.off("offline", offlineHandler);
625
- offlineHandler = void 0;
626
- }
627
- }
628
- const readGithubToken = () => fs.readFile(PATHS.GITHUB_TOKEN_PATH, "utf8");
629
- const writeGithubToken = (token) => fs.writeFile(PATHS.GITHUB_TOKEN_PATH, token);
630
- const setupCopilotToken = async () => {
631
- const { token, refresh_in } = await getCopilotToken();
632
- state.copilotToken = token;
633
- consola.debug("GitHub Copilot Token fetched successfully!");
634
- if (state.showToken) consola.info("Copilot token:", token);
635
- const refreshInterval = (refresh_in - 60) * 1e3;
636
- const refreshTokenWithRetry = async (maxRetries = 5, baseDelay = 1e3, reason = "scheduled") => {
637
- if (isRefreshPending) {
638
- consola.debug("Token refresh already in progress, skipping");
639
- return;
640
- }
641
- isRefreshPending = true;
642
- try {
643
- for (let attempt = 1; attempt <= maxRetries; attempt++) try {
644
- consola.debug(`Refreshing Copilot token (${reason}, attempt ${attempt}/${maxRetries})`);
645
- const { token: token$1 } = await getCopilotToken();
646
- state.copilotToken = token$1;
647
- consola.debug("Copilot token refreshed successfully");
648
- if (state.showToken) consola.info("Refreshed Copilot token:", token$1);
649
- return;
650
- } catch (error) {
651
- const isLastAttempt = attempt === maxRetries;
652
- consola.error(`Failed to refresh Copilot token (attempt ${attempt}/${maxRetries}):`, error);
653
- if (isLastAttempt) {
654
- consola.error("All token refresh attempts failed. Service may be unavailable until next scheduled refresh.");
655
- return;
656
- }
657
- const delay = baseDelay * Math.pow(2, attempt - 1);
658
- consola.debug(`Retrying token refresh in ${delay}ms...`);
659
- await new Promise((resolve) => setTimeout(resolve, delay));
660
- }
661
- } finally {
662
- isRefreshPending = false;
663
- }
664
- };
665
- tokenRefreshInterval = setInterval(() => {
666
- refreshTokenWithRetry(5, 1e3, "scheduled").catch((error) => {
667
- consola.error("Unexpected error in scheduled token refresh:", error);
668
- });
669
- }, refreshInterval);
670
- connectivityMonitor.start();
671
- onlineHandler = () => {
672
- consola.debug("Network connectivity restored, attempting immediate token refresh");
673
- refreshTokenWithRetry(3, 500, "network-restored").catch((error) => {
674
- consola.error("Unexpected error in network-restored token refresh:", error);
675
- });
676
- };
677
- offlineHandler = () => {
678
- consola.debug("Network connectivity lost");
679
- };
680
- connectivityMonitor.on("online", onlineHandler);
681
- connectivityMonitor.on("offline", offlineHandler);
682
- };
683
- async function setupGitHubToken(options) {
684
- try {
685
- const githubToken = await readGithubToken();
686
- if (githubToken && !options?.force) {
687
- state.githubToken = githubToken;
688
- if (state.showToken) consola.info("GitHub token:", githubToken);
689
- await logUser();
690
- return;
691
- }
692
- consola.info("Not logged in, getting new access token");
693
- const response = await getDeviceCode();
694
- consola.debug("Device code response:", response);
695
- consola.info(`Please enter the code "${response.user_code}" in ${response.verification_uri}`);
696
- const token = await pollAccessToken(response);
697
- await writeGithubToken(token);
698
- state.githubToken = token;
699
- if (state.showToken) consola.info("GitHub token:", token);
700
- await logUser();
701
- } catch (error) {
702
- if (error instanceof HTTPError) {
703
- consola.error("Failed to get GitHub token:", await error.response.json());
704
- throw error;
705
- }
706
- consola.error("Failed to get GitHub token:", error);
707
- throw error;
708
- }
709
- }
710
- async function logUser() {
711
- const user = await getGitHubUser();
712
- consola.info(`Logged in as ${user.login}`);
713
- }
714
-
715
- //#endregion
716
- //#region src/auth.ts
717
- async function runAuth(options) {
718
- if (options.verbose) {
719
- consola.level = 5;
720
- consola.info("Verbose logging enabled");
721
- }
722
- state.showToken = options.showToken;
723
- await ensurePaths();
724
- await setupGitHubToken({ force: true });
725
- consola.success("GitHub token written to", PATHS.GITHUB_TOKEN_PATH);
726
- }
727
- const auth = defineCommand({
728
- meta: {
729
- name: "auth",
730
- description: "Run GitHub auth flow without running the server"
731
- },
732
- args: {
733
- verbose: {
734
- alias: "v",
735
- type: "boolean",
736
- default: false,
737
- description: "Enable verbose logging"
738
- },
739
- "show-token": {
740
- type: "boolean",
741
- default: false,
742
- description: "Show GitHub token on auth"
743
- }
744
- },
745
- run({ args }) {
746
- return runAuth({
747
- verbose: args.verbose,
748
- showToken: args["show-token"]
749
- });
750
- }
751
- });
752
-
753
- //#endregion
754
- //#region src/services/github/get-copilot-usage.ts
755
- const getCopilotUsage = async () => {
756
- const controller = new AbortController();
757
- const timeoutMs = state.timeoutMs ?? 12e4;
758
- const timeout = setTimeout(() => {
759
- controller.abort();
760
- }, timeoutMs);
761
- try {
762
- const response = await fetch(`${GITHUB_API_BASE_URL}/copilot_internal/user`, {
763
- headers: githubHeaders(state),
764
- signal: controller.signal
765
- });
766
- if (!response.ok) throw new HTTPError("Failed to get Copilot usage", response);
767
- return await response.json();
768
- } finally {
769
- clearTimeout(timeout);
770
- }
771
- };
772
-
773
- //#endregion
774
- //#region src/check-usage.ts
775
- const checkUsage = defineCommand({
776
- meta: {
777
- name: "check-usage",
778
- description: "Show current GitHub Copilot usage/quota information"
779
- },
780
- async run() {
781
- await ensurePaths();
782
- await setupGitHubToken();
783
- try {
784
- const usage = await getCopilotUsage();
785
- const premium = usage.quota_snapshots.premium_interactions;
786
- const premiumTotal = premium.entitlement;
787
- const premiumUsed = premiumTotal - premium.remaining;
788
- const premiumPercentUsed = premiumTotal > 0 ? premiumUsed / premiumTotal * 100 : 0;
789
- const premiumPercentRemaining = premium.percent_remaining;
790
- function summarizeQuota(name, snap) {
791
- if (!snap) return `${name}: N/A`;
792
- const total = snap.entitlement;
793
- const used = total - snap.remaining;
794
- const percentUsed = total > 0 ? used / total * 100 : 0;
795
- const percentRemaining = snap.percent_remaining;
796
- return `${name}: ${used}/${total} used (${percentUsed.toFixed(1)}% used, ${percentRemaining.toFixed(1)}% remaining)`;
797
- }
798
- const premiumLine = `Premium: ${premiumUsed}/${premiumTotal} used (${premiumPercentUsed.toFixed(1)}% used, ${premiumPercentRemaining.toFixed(1)}% remaining)`;
799
- const chatLine = summarizeQuota("Chat", usage.quota_snapshots.chat);
800
- const completionsLine = summarizeQuota("Completions", usage.quota_snapshots.completions);
801
- consola.box(`Copilot Usage (plan: ${usage.copilot_plan})\nQuota resets: ${usage.quota_reset_date}\n\nQuotas:\n ${premiumLine}\n ${chatLine}\n ${completionsLine}`);
802
- } catch (err) {
803
- consola.error("Failed to fetch Copilot usage:", err);
804
- process.exit(1);
805
- }
806
- }
807
- });
808
-
809
- //#endregion
810
- //#region src/debug.ts
811
- async function getPackageVersion() {
812
- try {
813
- const packageJsonPath = new URL("../package.json", import.meta.url).pathname;
814
- const packageJsonBuffer = await fs.readFile(packageJsonPath);
815
- const packageJson = JSON.parse(packageJsonBuffer.toString());
816
- return packageJson.version;
817
- } catch {
818
- return "unknown";
819
- }
820
- }
821
- function getRuntimeInfo() {
822
- const isBun = typeof Bun !== "undefined";
823
- return {
824
- name: isBun ? "bun" : "node",
825
- version: isBun ? Bun.version : process.version.slice(1),
826
- platform: os.platform(),
827
- arch: os.arch()
828
- };
829
- }
830
- async function checkTokenExists() {
831
- try {
832
- const stats = await fs.stat(PATHS.GITHUB_TOKEN_PATH);
833
- if (!stats.isFile()) return false;
834
- const content = await fs.readFile(PATHS.GITHUB_TOKEN_PATH, "utf8");
835
- return content.trim().length > 0;
836
- } catch {
837
- return false;
838
- }
839
- }
840
- async function getDebugInfo() {
841
- const [version, tokenExists] = await Promise.all([getPackageVersion(), checkTokenExists()]);
842
- return {
843
- version,
844
- runtime: getRuntimeInfo(),
845
- paths: {
846
- APP_DIR: PATHS.APP_DIR,
847
- GITHUB_TOKEN_PATH: PATHS.GITHUB_TOKEN_PATH
848
- },
849
- tokenExists
850
- };
851
- }
852
- function printDebugInfoPlain(info) {
853
- consola.info(`copilot-api debug
854
-
855
- Version: ${info.version}
856
- Runtime: ${info.runtime.name} ${info.runtime.version} (${info.runtime.platform} ${info.runtime.arch})
857
-
858
- Paths:
859
- - APP_DIR: ${info.paths.APP_DIR}
860
- - GITHUB_TOKEN_PATH: ${info.paths.GITHUB_TOKEN_PATH}
861
-
862
- Token exists: ${info.tokenExists ? "Yes" : "No"}`);
863
- }
864
- function printDebugInfoJson(info) {
865
- console.log(JSON.stringify(info, null, 2));
866
- }
867
- async function runDebug(options) {
868
- const debugInfo = await getDebugInfo();
869
- if (options.json) printDebugInfoJson(debugInfo);
870
- else printDebugInfoPlain(debugInfo);
871
- }
872
- const debug = defineCommand({
873
- meta: {
874
- name: "debug",
875
- description: "Print debug information about the application"
876
- },
877
- args: { json: {
878
- type: "boolean",
879
- default: false,
880
- description: "Output debug information as JSON"
881
- } },
882
- run({ args }) {
883
- return runDebug({ json: args.json });
884
- }
885
- });
886
-
887
- //#endregion
888
- //#region src/lib/shell.ts
889
- function getShell() {
890
- const { platform, ppid, env } = process$1;
891
- if (platform === "win32") {
892
- try {
893
- const command = `wmic process get ParentProcessId,Name | findstr "${ppid}"`;
894
- const parentProcess = execSync(command, { stdio: "pipe" }).toString();
895
- if (parentProcess.toLowerCase().includes("powershell.exe")) return "powershell";
896
- } catch {
897
- return "cmd";
898
- }
899
- return "cmd";
900
- } else {
901
- const shellPath = env.SHELL;
902
- if (shellPath) {
903
- if (shellPath.endsWith("zsh")) return "zsh";
904
- if (shellPath.endsWith("fish")) return "fish";
905
- if (shellPath.endsWith("bash")) return "bash";
906
- }
907
- return "sh";
908
- }
909
- }
910
- /**
911
- * Generates a copy-pasteable script to set multiple environment variables
912
- * and run a subsequent command.
913
- * @param {EnvVars} envVars - An object of environment variables to set.
914
- * @param {string} commandToRun - The command to run after setting the variables.
915
- * @returns {string} The formatted script string.
916
- */
917
- function generateEnvScript(envVars, commandToRun = "") {
918
- const shell = getShell();
919
- const filteredEnvVars = Object.entries(envVars).filter(([, value]) => value !== void 0);
920
- let commandBlock;
921
- switch (shell) {
922
- case "powershell":
923
- commandBlock = filteredEnvVars.map(([key, value]) => `$env:${key} = ${value}`).join("; ");
924
- break;
925
- case "cmd":
926
- commandBlock = filteredEnvVars.map(([key, value]) => `set ${key}=${value}`).join(" & ");
927
- break;
928
- case "fish":
929
- commandBlock = filteredEnvVars.map(([key, value]) => `set -gx ${key} ${value}`).join("; ");
930
- break;
931
- default: {
932
- const assignments = filteredEnvVars.map(([key, value]) => `${key}=${value}`).join(" ");
933
- commandBlock = filteredEnvVars.length > 0 ? `export ${assignments}` : "";
934
- break;
935
- }
936
- }
937
- if (commandBlock && commandToRun) {
938
- const separator = shell === "cmd" ? " & " : " && ";
939
- return `${commandBlock}${separator}${commandToRun}`;
940
- }
941
- return commandBlock || commandToRun;
942
- }
943
-
944
- //#endregion
945
- //#region src/lib/logger/enhanced-hono-logger.ts
946
- function generateRequestId() {
947
- return `req_${Date.now()}_${Math.random().toString(36).slice(2, 11)}`;
948
- }
949
- function enhancedLogger() {
950
- return async (c, next) => {
951
- const start$1 = Date.now();
952
- const requestId = generateRequestId();
953
- c.set("requestId", requestId);
954
- c.header("x-request-id", requestId);
955
- CompletionLogger.registerCompletion(requestId, c, start$1);
956
- await next();
957
- const requestData = c.get("requestData");
958
- if (requestData?.tokenUsage) CompletionLogger.executeCompletion(requestId);
959
- };
960
- }
961
-
962
- //#endregion
963
- //#region src/lib/approval.ts
964
- const awaitApproval = async () => {
965
- const response = await consola.prompt(`Accept incoming request?`, { type: "confirm" });
966
- if (!response) throw new HTTPError("Request rejected", Response.json({ message: "Request rejected" }, { status: 403 }));
967
- };
968
-
969
- //#endregion
970
- //#region src/lib/logger/token-tracker.ts
971
- const TOKEN_PRICING = {
972
- input: 3e-6,
973
- output: 15e-6
974
- };
975
- function parseGitHubCopilotUsage(usageData) {
976
- const inputTokens = usageData.prompt_tokens || 0;
977
- const outputTokens = usageData.completion_tokens || 0;
978
- const totalTokens = usageData.total_tokens || inputTokens + outputTokens;
979
- const estimatedCost = inputTokens * TOKEN_PRICING.input + outputTokens * TOKEN_PRICING.output;
980
- return {
981
- inputTokens,
982
- outputTokens,
983
- totalTokens,
984
- estimatedCost,
985
- cachedTokens: usageData.prompt_tokens_details?.cached_tokens,
986
- acceptedPredictionTokens: usageData.completion_tokens_details?.accepted_prediction_tokens,
987
- rejectedPredictionTokens: usageData.completion_tokens_details?.rejected_prediction_tokens
988
- };
989
- }
990
-
991
- //#endregion
992
- //#region src/lib/rate-limit.ts
993
- async function checkRateLimit(state$1) {
994
- if (state$1.rateLimitSeconds === void 0) return;
995
- const now = Date.now();
996
- if (!state$1.lastRequestTimestamp) {
997
- state$1.lastRequestTimestamp = now;
998
- return;
999
- }
1000
- const elapsedSeconds = (now - state$1.lastRequestTimestamp) / 1e3;
1001
- if (elapsedSeconds > state$1.rateLimitSeconds) {
1002
- state$1.lastRequestTimestamp = now;
1003
- return;
1004
- }
1005
- const waitTimeSeconds = Math.ceil(state$1.rateLimitSeconds - elapsedSeconds);
1006
- if (!state$1.rateLimitWait) {
1007
- consola.warn(`Rate limit exceeded. Need to wait ${waitTimeSeconds} more seconds.`);
1008
- throw new HTTPError("Rate limit exceeded", Response.json({ message: "Rate limit exceeded" }, { status: 429 }));
1009
- }
1010
- const waitTimeMs = waitTimeSeconds * 1e3;
1011
- consola.warn(`Rate limit reached. Waiting ${waitTimeSeconds} seconds before proceeding...`);
1012
- await sleep(waitTimeMs);
1013
- state$1.lastRequestTimestamp = now;
1014
- consola.info("Rate limit wait completed, proceeding with request");
1015
- }
1016
-
1017
- //#endregion
1018
- //#region src/lib/sanitize.ts
1019
- /**
1020
- * Sanitizes JSON payloads by removing ANSI escape sequences and invisible Unicode characters
1021
- * that can cause GitHub Copilot API to return 400 Bad Request errors.
1022
- */
1023
- /**
1024
- * Removes ANSI escape sequences and problematic Unicode characters from a string
1025
- */
1026
- function sanitizeString(str) {
1027
- let result = str;
1028
- const escChar = String.fromCodePoint(27);
1029
- const parts = result.split(escChar);
1030
- if (parts.length > 1) {
1031
- result = parts[0];
1032
- for (let i = 1; i < parts.length; i++) {
1033
- const part = parts[i];
1034
- const match = part.match(/^\[[0-9;]*[a-z]/i);
1035
- result += match ? part.slice(match[0].length) : escChar + part;
1036
- }
1037
- }
1038
- return result.replaceAll(/[\u200B-\u200D\uFEFF]/g, "").replaceAll(/[\u2060-\u2064]/g, "").replaceAll(/[\u206A-\u206F]/g, "").replaceAll(/[\u180E\u2000-\u200A\u2028\u2029\u202F\u205F\u3000]/g, "").replaceAll(/[\uFFF0-\uFFFF]/g, "").replaceAll(/[\x00-\x08\v\f\x0E-\x1F]/g, "");
1039
- }
1040
- /**
1041
- * Recursively sanitizes all string values in an object or array
1042
- */
1043
- function sanitizePayload(payload) {
1044
- if (typeof payload === "string") return sanitizeString(payload);
1045
- if (Array.isArray(payload)) {
1046
- const sanitizedArray = payload.map((item) => sanitizePayload(item));
1047
- return sanitizedArray;
1048
- }
1049
- if (payload && typeof payload === "object") {
1050
- const sanitized = {};
1051
- for (const [key, value] of Object.entries(payload)) sanitized[key] = sanitizePayload(value);
1052
- return sanitized;
1053
- }
1054
- return payload;
1055
- }
1056
-
1057
- //#endregion
1058
- //#region src/services/copilot/create-chat-completions.ts
1059
- const createChatCompletions = async (payload) => {
1060
- if (!state.copilotToken) throw new Error("Copilot token not found");
1061
- const sanitizedPayload = sanitizePayload(payload);
1062
- const enableVision = sanitizedPayload.messages.some((x) => typeof x.content !== "string" && x.content?.some((x$1) => x$1.type === "image_url"));
1063
- const isAgentCall = sanitizedPayload.messages.some((msg) => ["assistant", "tool"].includes(msg.role));
1064
- const headers = {
1065
- ...copilotHeaders(state, enableVision),
1066
- "X-Initiator": isAgentCall ? "agent" : "user"
1067
- };
1068
- const controller = new AbortController();
1069
- const timeoutMs = state.timeoutMs ?? 12e4;
1070
- const timeout = setTimeout(() => {
1071
- controller.abort();
1072
- }, timeoutMs);
1073
- try {
1074
- const { statusCode, headers: responseHeaders, body } = await request(`${copilotBaseUrl(state)}/chat/completions`, {
1075
- method: "POST",
1076
- headers,
1077
- body: JSON.stringify(sanitizedPayload),
1078
- signal: controller.signal,
1079
- headersTimeout: timeoutMs,
1080
- bodyTimeout: timeoutMs * 3
1081
- });
1082
- const response = new Response(body, {
1083
- status: statusCode,
1084
- headers: responseHeaders
1085
- });
1086
- if (!response.ok) throw new HTTPError("Failed to create chat completions", response);
1087
- if (sanitizedPayload.stream) return events(response);
1088
- return await response.json();
1089
- } finally {
1090
- clearTimeout(timeout);
1091
- }
1092
- };
1093
-
1094
- //#endregion
1095
- //#region src/routes/chat-completions/handler.ts
1096
- async function handleCompletion$1(c) {
1097
- await checkRateLimit(state);
1098
- let payload = await c.req.json();
1099
- consola.debug("Request payload:", JSON.stringify(payload).slice(-400));
1100
- c.set("requestData", { model: payload.model });
1101
- if (state.manualApprove) await awaitApproval();
1102
- if (isNullish(payload.max_tokens)) {
1103
- const selectedModel = state.models?.data.find((model) => model.id === payload.model);
1104
- payload = {
1105
- ...payload,
1106
- max_tokens: selectedModel?.capabilities.limits.max_output_tokens
1107
- };
1108
- consola.debug("Set max_tokens to:", JSON.stringify(payload.max_tokens));
1109
- }
1110
- const copilotStart = performance.now();
1111
- const response = await createChatCompletions(payload);
1112
- const copilotDuration = performance.now() - copilotStart;
1113
- if (isNonStreaming$1(response)) {
1114
- const requestData = c.get("requestData") || {};
1115
- if (response.usage) requestData.tokenUsage = parseGitHubCopilotUsage(response.usage);
1116
- requestData.copilotDuration = copilotDuration;
1117
- c.set("requestData", requestData);
1118
- consola.debug("Non-streaming response:", JSON.stringify(response));
1119
- return c.json(response);
1120
- }
1121
- return streamSSE(c, async (stream) => {
1122
- let finalUsage = null;
1123
- for await (const chunk of response) {
1124
- if (chunk.data && chunk.data !== "[DONE]") try {
1125
- const parsed = JSON.parse(chunk.data);
1126
- if (parsed.usage) {
1127
- finalUsage = parsed.usage;
1128
- const requestData = c.get("requestData") || {};
1129
- requestData.tokenUsage = parseGitHubCopilotUsage(finalUsage);
1130
- requestData.copilotDuration = copilotDuration;
1131
- c.set("requestData", requestData);
1132
- }
1133
- } catch {}
1134
- await stream.writeSSE(chunk);
1135
- }
1136
- if (finalUsage) {
1137
- const requestData = c.get("requestData") || {};
1138
- if (!requestData.tokenUsage) {
1139
- requestData.tokenUsage = parseGitHubCopilotUsage(finalUsage);
1140
- requestData.copilotDuration = copilotDuration;
1141
- c.set("requestData", requestData);
1142
- }
1143
- }
1144
- const requestId = c.get("requestId");
1145
- if (requestId) CompletionLogger.executeCompletion(requestId);
1146
- });
1147
- }
1148
- const isNonStreaming$1 = (response) => Object.hasOwn(response, "choices");
1149
-
1150
- //#endregion
1151
- //#region src/routes/chat-completions/route.ts
1152
- const completionRoutes = new Hono();
1153
- completionRoutes.post("/", async (c) => {
1154
- try {
1155
- return await handleCompletion$1(c);
1156
- } catch (error) {
1157
- return await forwardError(c, error);
1158
- }
1159
- });
1160
-
1161
- //#endregion
1162
- //#region src/services/copilot/create-embeddings.ts
1163
- const createEmbeddings = async (payload) => {
1164
- if (!state.copilotToken) throw new Error("Copilot token not found");
1165
- const controller = new AbortController();
1166
- const timeoutMs = state.timeoutMs ?? 12e4;
1167
- const timeout = setTimeout(() => {
1168
- controller.abort();
1169
- }, timeoutMs);
1170
- try {
1171
- const response = await fetch(`${copilotBaseUrl(state)}/embeddings`, {
1172
- method: "POST",
1173
- headers: copilotHeaders(state),
1174
- body: JSON.stringify(payload),
1175
- signal: controller.signal
1176
- });
1177
- if (!response.ok) throw new HTTPError("Failed to create embeddings", response);
1178
- return await response.json();
1179
- } finally {
1180
- clearTimeout(timeout);
1181
- }
1182
- };
1183
-
1184
- //#endregion
1185
- //#region src/routes/embeddings/route.ts
1186
- const embeddingRoutes = new Hono();
1187
- embeddingRoutes.post("/", async (c) => {
1188
- try {
1189
- const payload = await c.req.json();
1190
- c.set("requestData", { model: payload.model });
1191
- const copilotStart = performance.now();
1192
- const response = await createEmbeddings(payload);
1193
- const copilotDuration = performance.now() - copilotStart;
1194
- const requestData = c.get("requestData") || {};
1195
- requestData.tokenUsage = parseGitHubCopilotUsage(response.usage);
1196
- requestData.copilotDuration = copilotDuration;
1197
- c.set("requestData", requestData);
1198
- return c.json(response);
1199
- } catch (error) {
1200
- return await forwardError(c, error);
1201
- }
1202
- });
1203
-
1204
- //#endregion
1205
- //#region src/routes/messages/utils.ts
1206
- function mapOpenAIStopReasonToAnthropic(finishReason) {
1207
- if (finishReason === null) return null;
1208
- const stopReasonMap = {
1209
- stop: "end_turn",
1210
- length: "max_tokens",
1211
- tool_calls: "tool_use",
1212
- content_filter: "end_turn"
1213
- };
1214
- return stopReasonMap[finishReason];
1215
- }
1216
-
1217
- //#endregion
1218
- //#region src/routes/messages/non-stream-translation.ts
1219
- function translateToOpenAI(payload) {
1220
- return {
1221
- model: translateModelName(payload.model),
1222
- messages: translateAnthropicMessagesToOpenAI(payload.messages, payload.system),
1223
- max_tokens: payload.max_tokens,
1224
- stop: payload.stop_sequences,
1225
- stream: payload.stream,
1226
- temperature: payload.temperature,
1227
- top_p: payload.top_p,
1228
- user: payload.metadata?.user_id,
1229
- tools: translateAnthropicToolsToOpenAI(payload.tools),
1230
- tool_choice: translateAnthropicToolChoiceToOpenAI(payload.tool_choice)
1231
- };
1232
- }
1233
- function translateModelName(model) {
1234
- if (model.startsWith("claude-sonnet-4-")) return model.replace(/^claude-sonnet-4-.*/, "claude-sonnet-4");
1235
- else if (model.startsWith("claude-opus-")) return model.replace(/^claude-opus-4-.*/, "claude-opus-4");
1236
- return model;
1237
- }
1238
- function translateAnthropicMessagesToOpenAI(anthropicMessages, system) {
1239
- const systemMessages = handleSystemPrompt(system);
1240
- const otherMessages = anthropicMessages.flatMap((message) => message.role === "user" ? handleUserMessage(message) : handleAssistantMessage(message));
1241
- return [...systemMessages, ...otherMessages];
1242
- }
1243
- function handleSystemPrompt(system) {
1244
- if (!system) return [];
1245
- if (typeof system === "string") return [{
1246
- role: "system",
1247
- content: system
1248
- }];
1249
- else {
1250
- const systemText = system.map((block) => block.text).join("\n\n");
1251
- return [{
1252
- role: "system",
1253
- content: systemText
1254
- }];
1255
- }
1256
- }
1257
- function handleUserMessage(message) {
1258
- const newMessages = [];
1259
- if (Array.isArray(message.content)) {
1260
- const toolResultBlocks = message.content.filter((block) => block.type === "tool_result");
1261
- const otherBlocks = message.content.filter((block) => block.type !== "tool_result");
1262
- for (const block of toolResultBlocks) newMessages.push({
1263
- role: "tool",
1264
- tool_call_id: block.tool_use_id,
1265
- content: mapContent(block.content)
1266
- });
1267
- if (otherBlocks.length > 0) newMessages.push({
1268
- role: "user",
1269
- content: mapContent(otherBlocks)
1270
- });
1271
- } else newMessages.push({
1272
- role: "user",
1273
- content: mapContent(message.content)
1274
- });
1275
- return newMessages;
1276
- }
1277
- function handleAssistantMessage(message) {
1278
- if (!Array.isArray(message.content)) return [{
1279
- role: "assistant",
1280
- content: mapContent(message.content)
1281
- }];
1282
- const toolUseBlocks = message.content.filter((block) => block.type === "tool_use");
1283
- const textBlocks = message.content.filter((block) => block.type === "text");
1284
- const thinkingBlocks = message.content.filter((block) => block.type === "thinking");
1285
- const allTextContent = [...textBlocks.map((b) => b.text), ...thinkingBlocks.map((b) => b.thinking)].join("\n\n");
1286
- return toolUseBlocks.length > 0 ? [{
1287
- role: "assistant",
1288
- content: allTextContent || null,
1289
- tool_calls: toolUseBlocks.map((toolUse) => ({
1290
- id: toolUse.id,
1291
- type: "function",
1292
- function: {
1293
- name: toolUse.name,
1294
- arguments: JSON.stringify(toolUse.input)
1295
- }
1296
- }))
1297
- }] : [{
1298
- role: "assistant",
1299
- content: mapContent(message.content)
1300
- }];
1301
- }
1302
- function mapContent(content) {
1303
- if (typeof content === "string") return content;
1304
- if (!Array.isArray(content)) return null;
1305
- const hasImage = content.some((block) => block.type === "image");
1306
- if (!hasImage) return content.filter((block) => block.type === "text" || block.type === "thinking").map((block) => block.type === "text" ? block.text : block.thinking).join("\n\n");
1307
- const contentParts = [];
1308
- for (const block of content) switch (block.type) {
1309
- case "text":
1310
- contentParts.push({
1311
- type: "text",
1312
- text: block.text
1313
- });
1314
- break;
1315
- case "thinking":
1316
- contentParts.push({
1317
- type: "text",
1318
- text: block.thinking
1319
- });
1320
- break;
1321
- case "image":
1322
- contentParts.push({
1323
- type: "image_url",
1324
- image_url: { url: `data:${block.source.media_type};base64,${block.source.data}` }
1325
- });
1326
- break;
1327
- }
1328
- return contentParts;
1329
- }
1330
- function translateAnthropicToolsToOpenAI(anthropicTools) {
1331
- if (!anthropicTools) return void 0;
1332
- return anthropicTools.map((tool) => ({
1333
- type: "function",
1334
- function: {
1335
- name: tool.name,
1336
- description: tool.description,
1337
- parameters: tool.input_schema
1338
- }
1339
- }));
1340
- }
1341
- function translateAnthropicToolChoiceToOpenAI(anthropicToolChoice) {
1342
- if (!anthropicToolChoice) return void 0;
1343
- switch (anthropicToolChoice.type) {
1344
- case "auto": return "auto";
1345
- case "any": return "required";
1346
- case "tool":
1347
- if (anthropicToolChoice.name) return {
1348
- type: "function",
1349
- function: { name: anthropicToolChoice.name }
1350
- };
1351
- return void 0;
1352
- case "none": return "none";
1353
- default: return void 0;
1354
- }
1355
- }
1356
- function translateToAnthropic(response) {
1357
- const allTextBlocks = [];
1358
- const allToolUseBlocks = [];
1359
- let stopReason = null;
1360
- stopReason = response.choices[0]?.finish_reason ?? stopReason;
1361
- for (const choice of response.choices) {
1362
- const textBlocks = getAnthropicTextBlocks(choice.message.content);
1363
- const toolUseBlocks = getAnthropicToolUseBlocks(choice.message.tool_calls);
1364
- allTextBlocks.push(...textBlocks);
1365
- allToolUseBlocks.push(...toolUseBlocks);
1366
- if (choice.finish_reason === "tool_calls" || stopReason === "stop") stopReason = choice.finish_reason;
1367
- }
1368
- return {
1369
- id: response.id,
1370
- type: "message",
1371
- role: "assistant",
1372
- model: response.model,
1373
- content: [...allTextBlocks, ...allToolUseBlocks],
1374
- stop_reason: mapOpenAIStopReasonToAnthropic(stopReason),
1375
- stop_sequence: null,
1376
- usage: {
1377
- input_tokens: response.usage?.prompt_tokens ?? 0,
1378
- output_tokens: response.usage?.completion_tokens ?? 0
1379
- }
1380
- };
1381
- }
1382
- function getAnthropicTextBlocks(messageContent) {
1383
- if (typeof messageContent === "string") return [{
1384
- type: "text",
1385
- text: messageContent
1386
- }];
1387
- if (Array.isArray(messageContent)) return messageContent.filter((part) => part.type === "text").map((part) => ({
1388
- type: "text",
1389
- text: part.text
1390
- }));
1391
- return [];
1392
- }
1393
- function getAnthropicToolUseBlocks(toolCalls) {
1394
- if (!toolCalls) return [];
1395
- return toolCalls.map((toolCall) => ({
1396
- type: "tool_use",
1397
- id: toolCall.id,
1398
- name: toolCall.function.name,
1399
- input: JSON.parse(toolCall.function.arguments)
1400
- }));
1401
- }
1402
-
1403
- //#endregion
1404
- //#region src/routes/messages/stream-translation.ts
1405
- function isToolBlockOpen(state$1) {
1406
- if (!state$1.contentBlockOpen) return false;
1407
- return Object.values(state$1.toolCalls).some((tc) => tc.anthropicBlockIndex === state$1.contentBlockIndex);
1408
- }
1409
- function translateChunkToAnthropicEvents(chunk, state$1) {
1410
- const events$1 = [];
1411
- if (chunk.choices.length === 0) return events$1;
1412
- const choice = chunk.choices[0];
1413
- const { delta } = choice;
1414
- if (!state$1.messageStartSent) {
1415
- events$1.push({
1416
- type: "message_start",
1417
- message: {
1418
- id: chunk.id,
1419
- type: "message",
1420
- role: "assistant",
1421
- content: [],
1422
- model: chunk.model,
1423
- stop_reason: null,
1424
- stop_sequence: null,
1425
- usage: {
1426
- input_tokens: chunk.usage?.prompt_tokens ?? 0,
1427
- output_tokens: 0
1428
- }
1429
- }
1430
- });
1431
- state$1.messageStartSent = true;
1432
- }
1433
- if (delta.content) {
1434
- if (isToolBlockOpen(state$1)) {
1435
- events$1.push({
1436
- type: "content_block_stop",
1437
- index: state$1.contentBlockIndex
1438
- });
1439
- state$1.contentBlockIndex++;
1440
- state$1.contentBlockOpen = false;
1441
- }
1442
- if (!state$1.contentBlockOpen) {
1443
- events$1.push({
1444
- type: "content_block_start",
1445
- index: state$1.contentBlockIndex,
1446
- content_block: {
1447
- type: "text",
1448
- text: ""
1449
- }
1450
- });
1451
- state$1.contentBlockOpen = true;
1452
- }
1453
- events$1.push({
1454
- type: "content_block_delta",
1455
- index: state$1.contentBlockIndex,
1456
- delta: {
1457
- type: "text_delta",
1458
- text: delta.content
1459
- }
1460
- });
1461
- }
1462
- if (delta.tool_calls) for (const toolCall of delta.tool_calls) {
1463
- if (toolCall.id && toolCall.function?.name) {
1464
- if (state$1.contentBlockOpen) {
1465
- events$1.push({
1466
- type: "content_block_stop",
1467
- index: state$1.contentBlockIndex
1468
- });
1469
- state$1.contentBlockIndex++;
1470
- state$1.contentBlockOpen = false;
1471
- }
1472
- const anthropicBlockIndex = state$1.contentBlockIndex;
1473
- state$1.toolCalls[toolCall.index] = {
1474
- id: toolCall.id,
1475
- name: toolCall.function.name,
1476
- anthropicBlockIndex
1477
- };
1478
- events$1.push({
1479
- type: "content_block_start",
1480
- index: anthropicBlockIndex,
1481
- content_block: {
1482
- type: "tool_use",
1483
- id: toolCall.id,
1484
- name: toolCall.function.name,
1485
- input: {}
1486
- }
1487
- });
1488
- state$1.contentBlockOpen = true;
1489
- }
1490
- if (toolCall.function?.arguments) {
1491
- const toolCallInfo = state$1.toolCalls[toolCall.index];
1492
- if (toolCallInfo) events$1.push({
1493
- type: "content_block_delta",
1494
- index: toolCallInfo.anthropicBlockIndex,
1495
- delta: {
1496
- type: "input_json_delta",
1497
- partial_json: toolCall.function.arguments
1498
- }
1499
- });
1500
- }
1501
- }
1502
- if (choice.finish_reason) {
1503
- if (state$1.contentBlockOpen) {
1504
- events$1.push({
1505
- type: "content_block_stop",
1506
- index: state$1.contentBlockIndex
1507
- });
1508
- state$1.contentBlockOpen = false;
1509
- }
1510
- events$1.push({
1511
- type: "message_delta",
1512
- delta: {
1513
- stop_reason: mapOpenAIStopReasonToAnthropic(choice.finish_reason),
1514
- stop_sequence: null
1515
- },
1516
- usage: {
1517
- input_tokens: chunk.usage?.prompt_tokens ?? 0,
1518
- output_tokens: chunk.usage?.completion_tokens ?? 0,
1519
- ...chunk.usage?.prompt_tokens_details?.cached_tokens !== void 0 && { cache_read_input_tokens: chunk.usage.prompt_tokens_details.cached_tokens }
1520
- }
1521
- }, { type: "message_stop" });
1522
- }
1523
- return events$1;
1524
- }
1525
-
1526
- //#endregion
1527
- //#region src/routes/messages/handler.ts
1528
- async function handleCompletion(c) {
1529
- await checkRateLimit(state);
1530
- const anthropicPayload = await c.req.json();
1531
- consola.debug("Anthropic request payload:", JSON.stringify(anthropicPayload));
1532
- const openAIPayload = translateToOpenAI(anthropicPayload);
1533
- consola.debug("Translated OpenAI request payload:", JSON.stringify(openAIPayload));
1534
- c.set("requestData", { model: openAIPayload.model });
1535
- const copilotStart = performance.now();
1536
- if (state.manualApprove) await awaitApproval();
1537
- const response = await createChatCompletions(openAIPayload);
1538
- const copilotDuration = performance.now() - copilotStart;
1539
- if (isNonStreaming(response)) {
1540
- const requestData = c.get("requestData") || {};
1541
- if (response.usage) requestData.tokenUsage = parseGitHubCopilotUsage(response.usage);
1542
- requestData.copilotDuration = copilotDuration;
1543
- c.set("requestData", requestData);
1544
- consola.debug("Non-streaming response from Copilot:", JSON.stringify(response).slice(-400));
1545
- const anthropicResponse = translateToAnthropic(response);
1546
- consola.debug("Translated Anthropic response:", JSON.stringify(anthropicResponse));
1547
- return c.json(anthropicResponse);
1548
- }
1549
- consola.debug("Streaming response from Copilot");
1550
- return streamSSE(c, async (stream) => {
1551
- let finalUsage = null;
1552
- const streamState = {
1553
- messageStartSent: false,
1554
- contentBlockIndex: 0,
1555
- contentBlockOpen: false,
1556
- toolCalls: {}
1557
- };
1558
- for await (const rawEvent of response) {
1559
- consola.debug("Copilot raw stream event:", JSON.stringify(rawEvent));
1560
- if (rawEvent.data === "[DONE]") break;
1561
- if (!rawEvent.data) continue;
1562
- const chunk = JSON.parse(rawEvent.data);
1563
- if (chunk.usage) {
1564
- finalUsage = chunk.usage;
1565
- const requestData = c.get("requestData") || {};
1566
- requestData.tokenUsage = parseGitHubCopilotUsage(finalUsage);
1567
- requestData.copilotDuration = copilotDuration;
1568
- c.set("requestData", requestData);
1569
- }
1570
- const events$1 = translateChunkToAnthropicEvents(chunk, streamState);
1571
- for (const event of events$1) {
1572
- consola.debug("Translated Anthropic event:", JSON.stringify(event));
1573
- await stream.writeSSE({
1574
- event: event.type,
1575
- data: JSON.stringify(event)
1576
- });
1577
- }
1578
- }
1579
- if (finalUsage) {
1580
- const requestData = c.get("requestData") || {};
1581
- if (!requestData.tokenUsage) {
1582
- requestData.tokenUsage = parseGitHubCopilotUsage(finalUsage);
1583
- requestData.copilotDuration = copilotDuration;
1584
- c.set("requestData", requestData);
1585
- }
1586
- }
1587
- const requestId = c.get("requestId");
1588
- if (requestId) CompletionLogger.executeCompletion(requestId);
1589
- });
1590
- }
1591
- const isNonStreaming = (response) => Object.hasOwn(response, "choices");
1592
-
1593
- //#endregion
1594
- //#region src/routes/messages/route.ts
1595
- const messageRoutes = new Hono();
1596
- messageRoutes.post("/", async (c) => {
1597
- try {
1598
- return await handleCompletion(c);
1599
- } catch (error) {
1600
- return await forwardError(c, error);
1601
- }
1602
- });
1603
-
1604
- //#endregion
1605
- //#region src/routes/models/route.ts
1606
- const modelRoutes = new Hono();
1607
- modelRoutes.get("/", async (c) => {
1608
- try {
1609
- if (!state.models) await cacheModels();
1610
- const models = state.models?.data.map((model) => ({
1611
- id: model.id,
1612
- object: "model",
1613
- type: "model",
1614
- created: 0,
1615
- created_at: (/* @__PURE__ */ new Date(0)).toISOString(),
1616
- owned_by: model.vendor,
1617
- display_name: model.name
1618
- }));
1619
- return c.json({
1620
- object: "list",
1621
- data: models,
1622
- has_more: false
1623
- });
1624
- } catch (error) {
1625
- return await forwardError(c, error);
1626
- }
1627
- });
1628
-
1629
- //#endregion
1630
- //#region src/routes/token/route.ts
1631
- const tokenRoute = new Hono();
1632
- tokenRoute.get("/", (c) => {
1633
- try {
1634
- return c.json({ token: state.copilotToken });
1635
- } catch (error) {
1636
- console.error("Error fetching token:", error);
1637
- return c.json({
1638
- error: "Failed to fetch token",
1639
- token: null
1640
- }, 500);
1641
- }
1642
- });
1643
-
1644
- //#endregion
1645
- //#region src/routes/usage/route.ts
1646
- const usageRoute = new Hono();
1647
- usageRoute.get("/", async (c) => {
1648
- try {
1649
- const usage = await getCopilotUsage();
1650
- return c.json(usage);
1651
- } catch (error) {
1652
- console.error("Error fetching Copilot usage:", error);
1653
- return c.json({ error: "Failed to fetch Copilot usage" }, 500);
1654
- }
1655
- });
1656
-
1657
- //#endregion
1658
- //#region src/server.ts
1659
- const server = new Hono();
1660
- server.use(enhancedLogger());
1661
- server.use(cors());
1662
- server.get("/", (c) => c.text("Server running"));
1663
- server.route("/chat/completions", completionRoutes);
1664
- server.route("/models", modelRoutes);
1665
- server.route("/embeddings", embeddingRoutes);
1666
- server.route("/usage", usageRoute);
1667
- server.route("/token", tokenRoute);
1668
- server.route("/v1/chat/completions", completionRoutes);
1669
- server.route("/v1/models", modelRoutes);
1670
- server.route("/v1/embeddings", embeddingRoutes);
1671
- server.route("/v1/messages", messageRoutes);
1672
- server.post("/v1/messages/count_tokens", (c) => c.json({ input_tokens: 1 }));
1673
-
1674
- //#endregion
1675
- //#region src/start.ts
1676
- const cleanupFunctions = [];
1677
- function setupGracefulShutdown() {
1678
- const cleanup = async () => {
1679
- consola.info("Gracefully shutting down...");
1680
- for (const cleanupFn of cleanupFunctions) try {
1681
- await cleanupFn();
1682
- } catch (error) {
1683
- consola.error("Error during cleanup:", error);
1684
- }
1685
- consola.info("Shutdown complete");
1686
- process$1.exit(0);
1687
- };
1688
- process$1.on("SIGINT", cleanup);
1689
- process$1.on("SIGTERM", cleanup);
1690
- process$1.on("uncaughtException", (error) => {
1691
- consola.error("Uncaught exception:", error);
1692
- cleanup().finally(() => process$1.exit(1));
1693
- });
1694
- process$1.on("unhandledRejection", (reason, promise) => {
1695
- consola.error("Unhandled promise rejection at:", promise, "reason:", reason);
1696
- cleanup().finally(() => process$1.exit(1));
1697
- });
1698
- }
1699
- async function setupClaudeCodeConfiguration(options, serverUrl) {
1700
- if (!options.claudeCode) return;
1701
- invariant(state.models, "Models should be loaded by now");
1702
- let selectedModel;
1703
- let selectedSmallModel;
1704
- if (options.model && options.smallModel) {
1705
- const availableModelIds = state.models.data.map((model) => model.id);
1706
- if (!availableModelIds.includes(options.model)) {
1707
- consola.error(`Invalid model: ${options.model}`);
1708
- consola.info(`Available models: \n${availableModelIds.join("\n")}`);
1709
- process$1.exit(1);
1710
- }
1711
- if (!availableModelIds.includes(options.smallModel)) {
1712
- consola.error(`Invalid small model: ${options.smallModel}`);
1713
- consola.info(`Available models: \n${availableModelIds.join("\n")}`);
1714
- process$1.exit(1);
1715
- }
1716
- selectedModel = options.model;
1717
- selectedSmallModel = options.smallModel;
1718
- consola.info(`Using model: ${selectedModel}`);
1719
- consola.info(`Using small model: ${selectedSmallModel}`);
1720
- } else if (options.model || options.smallModel) {
1721
- consola.error("Both --model and --small-model must be specified when using command-line model selection");
1722
- process$1.exit(1);
1723
- } else {
1724
- selectedModel = await consola.prompt("Select a model to use with Claude Code", {
1725
- type: "select",
1726
- options: state.models.data.map((model) => model.id)
1727
- });
1728
- selectedSmallModel = await consola.prompt("Select a small model to use with Claude Code", {
1729
- type: "select",
1730
- options: state.models.data.map((model) => model.id)
1731
- });
1732
- }
1733
- const command = generateEnvScript({
1734
- ANTHROPIC_BASE_URL: serverUrl,
1735
- ANTHROPIC_AUTH_TOKEN: "dummy",
1736
- ANTHROPIC_MODEL: selectedModel,
1737
- ANTHROPIC_SMALL_FAST_MODEL: selectedSmallModel
1738
- }, "claude");
1739
- try {
1740
- clipboard.writeSync(command);
1741
- consola.success("Copied Claude Code command to clipboard!");
1742
- } catch {
1743
- consola.warn("Failed to copy to clipboard. Here is the Claude Code command:");
1744
- consola.log(command);
1745
- }
1746
- }
1747
- async function runServer(options) {
1748
- setupGracefulShutdown();
1749
- cleanupFunctions.push(() => {
1750
- consola.debug("Cleaning up connectivity monitor");
1751
- connectivityMonitor.stop();
1752
- }, () => {
1753
- consola.debug("Cleaning up token management");
1754
- cleanupTokenManagement();
1755
- });
1756
- if (options.verbose) {
1757
- consola.level = 5;
1758
- consola.info("Verbose logging enabled");
1759
- }
1760
- state.accountType = options.accountType;
1761
- if (options.accountType !== "individual") consola.info(`Using ${options.accountType} plan GitHub account`);
1762
- state.manualApprove = options.manual;
1763
- state.rateLimitSeconds = options.rateLimit;
1764
- state.rateLimitWait = options.rateLimitWait;
1765
- state.showToken = options.showToken;
1766
- state.timeoutMs = options.timeout;
1767
- state.connectivity.enabled = !options.disableConnectivityMonitoring;
1768
- await ensurePaths();
1769
- cacheVSCodeVersion();
1770
- await initializeVSCodeIdentifiers(state);
1771
- if (options.githubToken) {
1772
- state.githubToken = options.githubToken;
1773
- consola.info("Using provided GitHub token");
1774
- } else await setupGitHubToken();
1775
- await setupCopilotToken();
1776
- await cacheModels();
1777
- consola.info(`Available models: \n${state.models?.data.map((model) => `- ${model.id}`).join("\n")}`);
1778
- const serverUrl = `http://localhost:${options.port}`;
1779
- await setupClaudeCodeConfiguration(options, serverUrl);
1780
- consola.box(`🌐 Usage Viewer: https://ericc-ch.github.io/copilot-api?endpoint=${serverUrl}/usage`);
1781
- serve({
1782
- fetch: server.fetch,
1783
- port: options.port
1784
- });
1785
- }
1786
- const start = defineCommand({
1787
- meta: {
1788
- name: "start",
1789
- description: "Start the Copilot API server"
1790
- },
1791
- args: {
1792
- port: {
1793
- alias: "p",
1794
- type: "string",
1795
- default: "4141",
1796
- description: "Port to listen on"
1797
- },
1798
- verbose: {
1799
- alias: "v",
1800
- type: "boolean",
1801
- default: false,
1802
- description: "Enable verbose logging"
1803
- },
1804
- "account-type": {
1805
- alias: "a",
1806
- type: "string",
1807
- default: "individual",
1808
- description: "Account type to use (individual, business, enterprise)"
1809
- },
1810
- manual: {
1811
- type: "boolean",
1812
- default: false,
1813
- description: "Enable manual request approval"
1814
- },
1815
- "rate-limit": {
1816
- alias: "r",
1817
- type: "string",
1818
- description: "Rate limit in seconds between requests"
1819
- },
1820
- wait: {
1821
- alias: "w",
1822
- type: "boolean",
1823
- default: false,
1824
- description: "Wait instead of error when rate limit is hit. Has no effect if rate limit is not set"
1825
- },
1826
- "github-token": {
1827
- alias: "g",
1828
- type: "string",
1829
- description: "Provide GitHub token directly (must be generated using the `auth` subcommand)"
1830
- },
1831
- "claude-code": {
1832
- alias: "c",
1833
- type: "boolean",
1834
- default: false,
1835
- description: "Generate a command to launch Claude Code with Copilot API config"
1836
- },
1837
- model: {
1838
- alias: "m",
1839
- type: "string",
1840
- description: "Model to use with Claude Code (requires --claude-code)"
1841
- },
1842
- "small-model": {
1843
- alias: "s",
1844
- type: "string",
1845
- description: "Small/fast model to use with Claude Code (requires --claude-code)"
1846
- },
1847
- "show-token": {
1848
- type: "boolean",
1849
- default: false,
1850
- description: "Show GitHub and Copilot tokens on fetch and refresh"
1851
- },
1852
- timeout: {
1853
- alias: "t",
1854
- type: "string",
1855
- description: "API timeout in milliseconds (default: 120000)"
1856
- },
1857
- "disable-connectivity-monitoring": {
1858
- type: "boolean",
1859
- default: false,
1860
- description: "Disable automatic network connectivity monitoring for token refresh"
1861
- }
1862
- },
1863
- run({ args }) {
1864
- const rateLimitRaw = args["rate-limit"];
1865
- const rateLimit = rateLimitRaw === void 0 ? void 0 : Number.parseInt(rateLimitRaw, 10);
1866
- const timeoutRaw = args.timeout;
1867
- const timeout = timeoutRaw === void 0 ? 12e4 : Number.parseInt(timeoutRaw, 10);
1868
- return runServer({
1869
- port: Number.parseInt(args.port, 10),
1870
- verbose: args.verbose,
1871
- accountType: args["account-type"],
1872
- manual: args.manual,
1873
- rateLimit,
1874
- rateLimitWait: args.wait,
1875
- githubToken: args["github-token"],
1876
- claudeCode: args["claude-code"],
1877
- model: args.model,
1878
- smallModel: args["small-model"],
1879
- showToken: args["show-token"],
1880
- timeout,
1881
- disableConnectivityMonitoring: args["disable-connectivity-monitoring"]
1882
- });
1883
- }
1884
- });
1885
-
1886
- //#endregion
1887
- //#region src/main.ts
1888
- const main = defineCommand({
1889
- meta: {
1890
- name: "copilot-api",
1891
- description: "A wrapper around GitHub Copilot API to make it OpenAI compatible, making it usable for other tools."
1892
- },
1893
- subCommands: {
1894
- auth,
1895
- start,
1896
- "check-usage": checkUsage,
1897
- debug
1898
- }
1899
- });
1900
- await runMain(main);
1901
-
1902
- //#endregion
1903
- export { };
1904
- //# sourceMappingURL=main.js.map
2
+ import{CompletionLogger as S,GITHUB_API_BASE_URL as G,GITHUB_APP_SCOPES as de,GITHUB_BASE_URL as R,GITHUB_CLIENT_ID as L,HTTPError as y,PATHS as w,copilotBaseUrl as E,copilotHeaders as P,ensurePaths as j,forwardError as I,getGitHubUser as pe,githubHeaders as J,initializeVSCodeIdentifiers as me,standardHeaders as W,state as s}from"./get-user-BJ4s6iMX.js";import{defineCommand as T,runMain as fe}from"citty";import i from"consola";import C from"node:fs/promises";import K from"node:os";import he from"clipboardy";import g from"node:process";import{serve as ge}from"srvx";import ye from"tiny-invariant";import{execSync as we}from"node:child_process";import{Hono as k}from"hono";import{cors as ke}from"hono/cors";import{streamSSE as V}from"hono/streaming";import{events as be}from"fetch-event-stream";import{request as _e}from"undici";var ve=class extends EventTarget{isOnline=!0;lastChecked=new Date().toISOString();consecutiveFailures=0;lastErrorType;lastErrorMessage;lastSuccessfulEndpoint;endpointFailureStats={};checkInterval;abortController;on(e,t){return this.addEventListener(e,t),this}off(e,t){return this.removeEventListener(e,t),this}start(){if(!s.connectivity.enabled){i.debug("Network monitoring disabled");return}i.info("Starting network monitor",{probeEndpoints:s.connectivity.probeEndpoints,fastInterval:s.connectivity.fastProbeInterval}),this.scheduleNextCheck();let e=()=>{this.stop(),process.exit(0)};process.on("SIGINT",e),process.on("SIGTERM",e)}stop(){i.debug("Stopping connectivity monitor"),this.checkInterval&&=(clearTimeout(this.checkInterval),void 0),this.abortController&&=(this.abortController.abort(),void 0)}scheduleNextCheck(){this.checkInterval&&clearTimeout(this.checkInterval);let e=this.isOnline?s.connectivity.slowProbeInterval:s.connectivity.fastProbeInterval,t=Math.random()*s.connectivity.jitterMaxMs,o=e+t;this.checkInterval=setTimeout(()=>{this.performConnectivityCheck().catch(n=>{i.error("Connectivity check failed:",n)})},o)}async performConnectivityCheck(){this.abortController=new AbortController;let e=setTimeout(()=>this.abortController?.abort(),s.connectivity.timeoutMs);try{let t=!1;for(let o of s.connectivity.probeEndpoints)try{if((await fetch(o,{method:"HEAD",signal:this.abortController.signal,headers:{"\u0055\u0073\u0065\u0072\u002d\u0041\u0067\u0065\u006e\u0074":"service-api-connectivity-monitor/1.0",...s.connectivity.dnsCache&&{"Cache-Control":"max-age=300"}}})).ok){t=!0,this.lastSuccessfulEndpoint=o;break}}catch(n){this.endpointFailureStats[o]=(this.endpointFailureStats[o]||0)+1,i.debug(`Probe failed for ${o}:`,n)}this.updateConnectivityState(t)}catch(t){this.handleConnectivityError(t)}finally{clearTimeout(e),this.scheduleNextCheck()}}updateConnectivityState(e){let t=this.isOnline;this.isOnline=e,this.lastChecked=new Date().toISOString(),e?(this.consecutiveFailures>0&&i.info(`Connectivity restored after ${this.consecutiveFailures} failures`),this.consecutiveFailures=0,this.lastErrorType=void 0,this.lastErrorMessage=void 0,t||this.dispatchEvent(new CustomEvent("online"))):(this.consecutiveFailures++,t&&(i.warn("Network lost"),this.dispatchEvent(new CustomEvent("offline"))))}handleConnectivityError(e){this.lastErrorType=e.name,this.lastErrorMessage=e.message,i.error("Connectivity check failed:",e),this.updateConnectivityState(!1)}getConnectivityStats(){return{isOnline:this.isOnline,lastChecked:this.lastChecked,consecutiveFailures:this.consecutiveFailures,lastErrorType:this.lastErrorType,lastErrorMessage:this.lastErrorMessage}}getPerformanceStats(){let e=this.isOnline?s.connectivity.slowProbeInterval:s.connectivity.fastProbeInterval,t=new Date(Date.now()+e+Math.random()*s.connectivity.jitterMaxMs).toISOString();return{currentInterval:e,nextCheckEstimate:t,lastSuccessfulEndpoint:this.lastSuccessfulEndpoint,endpointFailureStats:{...this.endpointFailureStats},jitterEnabled:s.connectivity.jitterMaxMs>0,connectionPooling:s.connectivity.connectionPooling,dnsCache:s.connectivity.dnsCache}}};const v=new ve,z=async()=>{let e=new AbortController,t=s.timeoutMs??12e4,o=setTimeout(()=>{e.abort()},t);try{let n=await fetch(`${G}/\u0063\u006f\u0070\u0069\u006c\u006f\u0074_internal/v2/token`,{headers:J(s),signal:e.signal});if(!n.ok)throw new y("\u0046\u0061\u0069\u006c\u0065\u0064\u0020\u0074\u006f\u0020\u0067\u0065\u0074\u0020\u0043\u006f\u0070\u0069\u006c\u006f\u0074\u0020\u0074\u006f\u006b\u0065\u006e",n);return await n.json()}finally{clearTimeout(o)}};async function Te(){let e=new AbortController,t=s.timeoutMs??12e4,o=setTimeout(()=>{e.abort()},t);try{let n=await fetch(`${R}/login/device/code`,{method:"POST",headers:W(),body:JSON.stringify({client_id:L,scope:de}),signal:e.signal});if(!n.ok)throw new y("Failed to get device code",n);return await n.json()}finally{clearTimeout(o)}}const Ce=async()=>{let e=new AbortController,t=s.timeoutMs??12e4,o=setTimeout(()=>{e.abort()},t);try{let n=await fetch(`${E(s)}/models`,{headers:P(s),signal:e.signal});if(!n.ok)throw new y("Failed to get models",n);return await n.json()}finally{clearTimeout(o)}};function Q(){return"1.105.0-insider"}Q();const A=e=>new Promise(t=>{setTimeout(t,e)}),xe=e=>e==null;async function X(){let e=await Ce();s.models=e}const Se=()=>{let e=Q();s.vsCodeVersion=e,i.info(`Using VSCode version:${e}`)};async function Ie(e){let t=(e.interval+1)*1e3;for(i.debug(`Polling access token with interval of ${t}ms`);;){let o=new AbortController,n=s.timeoutMs??12e4,r=setTimeout(()=>{o.abort()},n);try{let a=await fetch(`${R}/login/oauth/access_token`,{method:"POST",headers:W(),body:JSON.stringify({client_id:L,device_code:e.device_code,grant_type:"urn:ietf:params:oauth:grant-type:device_code"}),signal:o.signal});if(!a.ok){await A(t),i.error("Failed to poll access token:",await a.text());continue}let l=await a.json();i.debug("Polling access token response:",l);let{access_token:c}=l;if(c)return c;await A(t)}catch(a){if(a instanceof Error&&a.name==="AbortError"){i.error("Access token polling timed out"),await A(t);continue}throw a}finally{clearTimeout(r)}}}let N,H=!1,$,O;function Ae(){i.debug("Cleaning up token management"),N&&=(clearInterval(N),void 0),$&&=(v.off("online",$),void 0),O&&=(v.off("offline",O),void 0)}const $e=()=>C.readFile(w.GITHUB_TOKEN_PATH,"utf8"),Oe=e=>C.writeFile(w.GITHUB_TOKEN_PATH,e),Ee=async()=>{let{token:e,refresh_in:t}=await z();s.copilotToken=e,i.debug("GitHub Copilot Token fetched successfully!"),s.showToken&&i.info("Copilot token:",e);let o=(t-60)*1e3,n=async(r=5,a=1e3,l="scheduled")=>{if(H){i.debug("Service refresh already in progress,skipping");return}H=!0;try{for(let c=1;c<=r;c++)try{i.debug(`Refreshing Copilot token (${l},attempt ${c}/${r})`);let{token:u}=await z();s.copilotToken=u,i.debug("Copilot token refreshed successfully"),s.showToken&&i.info("Refreshed Copilot token:",u);return}catch(u){let f=c===r;if(i.error(`Failed to refresh Copilot token (attempt ${c}/${r}):`,u),f){i.error("All token refresh attempts failed. Service may be unavailable until next scheduled refresh.");return}let d=a*2**(c-1);i.debug(`Retrying token refresh in ${d}ms...`),await new Promise(p=>setTimeout(p,d))}}finally{H=!1}};N=setInterval(()=>{n(5,1e3,"scheduled").catch(r=>{i.error("Unexpected error in scheduled token refresh:",r)})},o),v.start(),$=()=>{i.debug("Network status restored,attempting immediate token refresh"),n(3,500,"network-restored").catch(r=>{i.error("Unexpected error in network-restored token refresh:",r)})},O=()=>{i.debug("Network status lost")},v.on("online",$),v.on("offline",O)};async function q(e){try{let t=await $e();if(t&&!e?.force){s.githubToken=t,s.showToken&&i.info("GitHub token:",t),await Y();return}i.info("Authentication required");let o=await Te();i.debug("Device code response:",o),i.info(`Please enter the code "${o.user_code}" in ${o.verification_uri}`);let n=await Ie(o);await Oe(n),s.githubToken=n,s.showToken&&i.info("GitHub token:",n),await Y()}catch(t){throw t instanceof y?(i.error("Failed to get GitHub token:",await t.response.json()),t):(i.error("Failed to get GitHub token:",t),t)}}async function Y(){let e=await pe();i.info(`Logged in as ${e.login}`)}async function Pe(e){e.verbose&&(i.level=5,i.info("Verbose logging enabled")),s.showToken=e.showToken,await j(),await q({force:!0}),i.success("GitHub token written to",w.GITHUB_TOKEN_PATH)}const je=T({meta:{name:"auth",description:"Run GitHub auth flow without running the server"},args:{verbose:{alias:"v",type:"boolean",default:!1,description:"Enable verbose logging"},"show-token":{type:"boolean",default:!1,description:"Show GitHub token on auth"}},run({args:e}){return Pe({verbose:e.verbose,showToken:e["show-token"]})}}),Z=async()=>{let e=new AbortController,t=s.timeoutMs??12e4,o=setTimeout(()=>{e.abort()},t);try{let n=await fetch(`${G}/\u0063\u006f\u0070\u0069\u006c\u006f\u0074_internal/user`,{headers:J(s),signal:e.signal});if(!n.ok)throw new y("Failed to get Copilot usage",n);return await n.json()}finally{clearTimeout(o)}},Ne=T({meta:{name:"check-usage",description:"Show current GitHub Copilot usage/quota information"},async run(){await j(),await q();try{let c=function(p,_){if(!_)return`${p}:N/A`;let h=_.entitlement,M=h-_.remaining,ce=h>0?M/h*100:0,ue=_.percent_remaining;return`${p}:${M}/${h} used (${ce.toFixed(1)}% used,${ue.toFixed(1)}% remaining)`};var e=c;let t=await Z(),o=t.quota_snapshots.premium_interactions,n=o.entitlement,r=n-o.remaining,a=n>0?r/n*100:0,l=o.percent_remaining,u=`Premium:${r}/${n} used (${a.toFixed(1)}% used,${l.toFixed(1)}% remaining)`,f=c("Chat",t.quota_snapshots.chat),d=c("Completions",t.quota_snapshots.completions);i.box(`Copilot Usage (plan:${t.copilot_plan})Quota resets:${t.quota_reset_date}Quotas:${u} ${f} ${d}`)}catch(t){i.error("Failed to fetch Copilot usage:",t),process.exit(1)}}});async function He(){try{let e=new URL("../package.json",import.meta.url).pathname,t=await C.readFile(e);return JSON.parse(t.toString()).version}catch{return"unknown"}}function qe(){let e=typeof Bun<"u";return{name:e?"bun":"node",version:e?Bun.version:process.version.slice(1),platform:K.platform(),arch:K.arch()}}async function Fe(){try{return(await C.stat(w.GITHUB_TOKEN_PATH)).isFile()?(await C.readFile(w.GITHUB_TOKEN_PATH,"utf8")).trim().length>0:!1}catch{return!1}}async function De(){let[e,t]=await Promise.all([He(),Fe()]);return{version:e,runtime:qe(),paths:{APP_DIR:w.APP_DIR,GITHUB_TOKEN_PATH:w.GITHUB_TOKEN_PATH},tokenExists:t}}function Ue(e){i.info(`copilot-api debugVersion:${e.version}Runtime:${e.runtime.name} ${e.runtime.version} (${e.runtime.platform} ${e.runtime.arch})Paths:- APP_DIR:${e.paths.APP_DIR}- GITHUB_TOKEN_PATH:${e.paths.GITHUB_TOKEN_PATH}Token exists:${e.tokenExists?"Yes":"No"}`)}function Be(e){console.log(JSON.stringify(e,null,2))}async function Me(e){let t=await De();e.json?Be(t):Ue(t)}const Ge=T({meta:{name:"debug",description:"Print debug information about the application"},args:{json:{type:"boolean",default:!1,description:"Output debug information as JSON"}},run({args:e}){return Me({json:e.json})}});function Re(){let{platform:e,ppid:t,env:o}=g;if(e==="win32"){try{let n=`wmic process get ParentProcessId,Name | findstr "${t}"`;if(we(n,{stdio:"pipe"}).toString().toLowerCase().includes("powershell.exe"))return"powershell"}catch{return"cmd"}return"cmd"}else{let n=o.SHELL;if(n){if(n.endsWith("zsh"))return"zsh";if(n.endsWith("fish"))return"fish";if(n.endsWith("bash"))return"bash"}return"sh"}}function Le(e,t=""){let o=Re(),n=Object.entries(e).filter(([,a])=>a!==void 0),r;switch(o){case"powershell":r=n.map(([a,l])=>`$env:${a} =${l}`).join(";");break;case"cmd":r=n.map(([a,l])=>`set ${a}=${l}`).join(" & ");break;case"fish":r=n.map(([a,l])=>`set -gx ${a} ${l}`).join(";");break;default:{let a=n.map(([l,c])=>`${l}=${c}`).join(" ");r=n.length>0?`export ${a}`:"";break}}return r&&t?`${r}${o==="cmd"?" & ":" && "}${t}`:r||t}function Je(){return`req_${Date.now()}_${Math.random().toString(36).slice(2,11)}`}function We(){return async(e,t)=>{let o=Date.now(),n=Je();e.set("requestId",n),e.header("x-request-id",n),S.registerCompletion(n,e,o),await t(),e.get("requestData")?.tokenUsage&&S.executeCompletion(n)}}const ee=async()=>{if(!await i.prompt("Accept incoming request?",{type:"confirm"}))throw new y("Request rejected",Response.json({message:"Request rejected"},{status:403}))},te={input:3e-6,output:15e-6};function b(e){let t=e.prompt_tokens||0,o=e.completion_tokens||0,n=e.total_tokens||t+o,r=t*te.input+o*te.output;return{inputTokens:t,outputTokens:o,totalTokens:n,estimatedCost:r,cachedTokens:e.prompt_tokens_details?.cached_tokens,acceptedPredictionTokens:e.completion_tokens_details?.accepted_prediction_tokens,rejectedPredictionTokens:e.completion_tokens_details?.rejected_prediction_tokens}}async function ne(e){if(e.rateLimitSeconds===void 0)return;let t=Date.now();if(!e.lastRequestTimestamp){e.lastRequestTimestamp=t;return}let o=(t-e.lastRequestTimestamp)/1e3;if(o>e.rateLimitSeconds){e.lastRequestTimestamp=t;return}let n=Math.ceil(e.rateLimitSeconds-o);if(!e.rateLimitWait)throw i.warn(`Rate limit exceeded. Need to wait ${n} more seconds.`),new y("Rate limit exceeded",Response.json({message:"Rate limit exceeded"},{status:429}));let r=n*1e3;i.warn(`Rate limit reached. Waiting ${n} seconds before proceeding...`),await A(r),e.lastRequestTimestamp=t,i.info("Rate limit wait completed,proceeding with request")}function Ke(e){let t=e,o=String.fromCodePoint(27),n=t.split(o);if(n.length>1){t=n[0];for(let r=1;r<n.length;r++){let a=n[r],l=a.match(/^\[[0-9;]*[a-z]/i);t+=l?a.slice(l[0].length):o+a}}return t.replaceAll(/[\u200B-\u200D\uFEFF]/g,"").replaceAll(/[\u2060-\u2064]/g,"").replaceAll(/[\u206A-\u206F]/g,"").replaceAll(/[\u180E\u2000-\u200A\u2028\u2029\u202F\u205F\u3000]/g,"").replaceAll(/[\uFFF0-\uFFFF]/g,"").replaceAll(/[\x00-\x08\v\f\x0E-\x1F]/g,"")}function F(e){if(typeof e=="string")return Ke(e);if(Array.isArray(e))return e.map(o=>F(o));if(e&&typeof e=="object"){let t={};for(let[o,n]of Object.entries(e))t[o]=F(n);return t}return e}const oe=async e=>{if(!s.copilotToken)throw Error("\u0043\u006f\u0070\u0069\u006c\u006f\u0074\u0020\u0074\u006f\u006b\u0065\u006e\u0020\u006e\u006f\u0074\u0020\u0066\u006f\u0075\u006e\u0064");let t=F(e),o=t.messages.some(u=>typeof u.content!="string"&&u.content?.some(f=>f.type==="image_url")),n=t.messages.some(u=>["assistant","tool"].includes(u.role)),r={...P(s,o),"X-Initiator":n?"agent":"user"},a=new AbortController,l=s.timeoutMs??12e4,c=setTimeout(()=>{a.abort()},l);try{let{statusCode:u,headers:f,body:d}=await _e(`${E(s)}/\u0063\u0068\u0061\u0074\u002f\u0063\u006f\u006d\u0070\u006c\u0065\u0074\u0069\u006f\u006e\u0073`,{method:"POST",headers:r,body:JSON.stringify(t),signal:a.signal,headersTimeout:l,bodyTimeout:l*3}),p=new Response(d,{status:u,headers:f});if(!p.ok)throw new y("Failed to create chat completions",p);return t.stream?be(p):await p.json()}finally{clearTimeout(c)}};async function Ve(e){await ne(s);let t=await e.req.json();if(i.debug("Request data:",JSON.stringify(t).slice(-400)),e.set("requestData",{model:t.model}),s.manualApprove&&await ee(),xe(t.max_tokens)){let a=s.models?.data.find(l=>l.id===t.model);t={...t,max_tokens:a?.capabilities.limits.max_output_tokens},i.debug("Set max_tokens to:",JSON.stringify(t.max_tokens))}let o=performance.now(),n=await oe(t),r=performance.now()-o;if(ze(n)){let a=e.get("requestData")||{};return n.usage&&(a.tokenUsage=b(n.usage)),a.copilotDuration=r,e.set("requestData",a),i.debug("Response data:",JSON.stringify(n)),e.json(n)}return V(e,async a=>{let l=null;for await(let u of n){let f=u;if(u.data&&u.data!=="[DONE]")try{let d=JSON.parse(u.data);if(d.usage){l=d.usage;let p=e.get("requestData")||{};p.tokenUsage=b(l),p.copilotDuration=r,e.set("requestData",p)}if(d.usage?.prompt_tokens_details?.cached_tokens!==void 0){let p={...d,usage:{...d.usage,prompt_tokens_details:void 0}};f={...u,data:JSON.stringify(p)}}}catch{}await a.writeSSE(f)}if(l){let u=e.get("requestData")||{};u.tokenUsage||(u.tokenUsage=b(l),u.copilotDuration=r,e.set("requestData",u))}let c=e.get("requestId");c&&S.executeCompletion(c)})}const ze=e=>Object.hasOwn(e,"choices"),D=new k;D.post("/",async e=>{try{return await Ve(e)}catch(t){return await I(e,t)}});const Qe=async e=>{if(!s.copilotToken)throw Error("\u0043\u006f\u0070\u0069\u006c\u006f\u0074\u0020\u0074\u006f\u006b\u0065\u006e\u0020\u006e\u006f\u0074\u0020\u0066\u006f\u0075\u006e\u0064");let t=new AbortController,o=s.timeoutMs??12e4,n=setTimeout(()=>{t.abort()},o);try{let r=await fetch(`${E(s)}/embeddings`,{method:"POST",headers:P(s),body:JSON.stringify(e),signal:t.signal});if(!r.ok)throw new y("Failed to create embeddings",r);return await r.json()}finally{clearTimeout(n)}},U=new k;U.post("/",async e=>{try{let t=await e.req.json();e.set("requestData",{model:t.model});let o=performance.now(),n=await Qe(t),r=performance.now()-o,a=e.get("requestData")||{};return a.tokenUsage=b(n.usage),a.copilotDuration=r,e.set("requestData",a),e.json(n)}catch(t){return await I(e,t)}});function ie(e){return e===null?null:{stop:"end_turn",length:"max_tokens",tool_calls:"tool_use",content_filter:"end_turn"}[e]}function Xe(e){return{model:Ye(e.model),messages:Ze(e.messages,e.system),max_tokens:e.max_tokens,stop:e.stop_sequences,stream:e.stream,temperature:e.temperature,top_p:e.top_p,user:e.metadata?.user_id,tools:ot(e.tools),tool_choice:it(e.tool_choice)}}function Ye(e){return e.startsWith("claude-sonnet-4-")?e.replace(/^claude-sonnet-4-.*/,"claude-sonnet-4"):e.startsWith("claude-opus-")?e.replace(/^claude-opus-4-.*/,"claude-opus-4"):e}function Ze(e,t){let o=et(t),n=e.flatMap(r=>r.role==="user"?tt(r):nt(r));return[...o,...n]}function et(e){return e?typeof e=="string"?[{role:"system",content:e}]:[{role:"system",content:e.map(o=>o.text).join(``)}]:[]}function tt(e){let t=[];if(Array.isArray(e.content)){let o=e.content.filter(r=>r.type==="tool_result"),n=e.content.filter(r=>r.type!=="tool_result");for(let r of o)t.push({role:"tool",tool_call_id:r.tool_use_id,content:x(r.content)});n.length>0&&t.push({role:"user",content:x(n)})}else t.push({role:"user",content:x(e.content)});return t}function nt(e){if(!Array.isArray(e.content))return[{role:"assistant",content:x(e.content)}];let t=e.content.filter(a=>a.type==="tool_use"),o=e.content.filter(a=>a.type==="text"),n=e.content.filter(a=>a.type==="thinking"),r=[...o.map(a=>a.text),...n.map(a=>a.thinking)].join(``);return t.length>0?[{role:"assistant",content:r||null,tool_calls:t.map(a=>({id:a.id,type:"function",function:{name:a.name,arguments:JSON.stringify(a.input)}}))}]:[{role:"assistant",content:x(e.content)}]}function x(e){if(typeof e=="string")return e;if(!Array.isArray(e))return null;if(!e.some(n=>n.type==="image"))return e.filter(n=>n.type==="text"||n.type==="thinking").map(n=>n.type==="text"?n.text:n.thinking).join(``);let o=[];for(let n of e)switch(n.type){case"text":o.push({type:"text",text:n.text});break;case"thinking":o.push({type:"text",text:n.thinking});break;case"image":o.push({type:"image_url",image_url:{url:`data:${n.source.media_type};base64,${n.source.data}`}});break}return o}function ot(e){if(e)return e.map(t=>({type:"function",function:{name:t.name,description:t.description,parameters:t.input_schema}}))}function it(e){if(e)switch(e.type){case"auto":return"auto";case"any":return"required";case"tool":return e.name?{type:"function",function:{name:e.name}}:void 0;case"none":return"none";default:return}}function at(e){let t=[],o=[],n=null;n=e.choices[0]?.finish_reason??n;for(let r of e.choices){let a=rt(r.message.content),l=st(r.message.tool_calls);t.push(...a),o.push(...l),(r.finish_reason==="tool_calls"||n==="stop")&&(n=r.finish_reason)}return{id:e.id,type:"message",role:"assistant",model:e.model,content:[...t,...o],stop_reason:ie(n),stop_sequence:null,usage:{input_tokens:e.usage?.prompt_tokens??0,output_tokens:e.usage?.completion_tokens??0}}}function rt(e){return typeof e=="string"?[{type:"text",text:e}]:Array.isArray(e)?e.filter(t=>t.type==="text").map(t=>({type:"text",text:t.text})):[]}function st(e){return e?e.map(t=>({type:"tool_use",id:t.id,name:t.function.name,input:JSON.parse(t.function.arguments)})):[]}function lt(e){return e.contentBlockOpen?Object.values(e.toolCalls).some(t=>t.anthropicBlockIndex===e.contentBlockIndex):!1}function ct(e,t){let o=[];if(e.choices.length===0)return o;let n=e.choices[0],{delta:r}=n;if(t.messageStartSent||=(o.push({type:"message_start",message:{id:e.id,type:"message",role:"assistant",content:[],model:e.model,stop_reason:null,stop_sequence:null,usage:{input_tokens:e.usage?.prompt_tokens??0,output_tokens:0}}}),!0),r.content&&(lt(t)&&(o.push({type:"content_block_stop",index:t.contentBlockIndex}),t.contentBlockIndex++,t.contentBlockOpen=!1),t.contentBlockOpen||=(o.push({type:"content_block_start",index:t.contentBlockIndex,content_block:{type:"text",text:""}}),!0),o.push({type:"content_block_delta",index:t.contentBlockIndex,delta:{type:"text_delta",text:r.content}})),r.tool_calls)for(let a of r.tool_calls){if(a.id&&a.function?.name){t.contentBlockOpen&&=(o.push({type:"content_block_stop",index:t.contentBlockIndex}),t.contentBlockIndex++,!1);let l=t.contentBlockIndex;t.toolCalls[a.index]={id:a.id,name:a.function.name,anthropicBlockIndex:l},o.push({type:"content_block_start",index:l,content_block:{type:"tool_use",id:a.id,name:a.function.name,input:{}}}),t.contentBlockOpen=!0}if(a.function?.arguments){let l=t.toolCalls[a.index];l&&o.push({type:"content_block_delta",index:l.anthropicBlockIndex,delta:{type:"input_json_delta",partial_json:a.function.arguments}})}}return n.finish_reason&&(t.contentBlockOpen&&=(o.push({type:"content_block_stop",index:t.contentBlockIndex}),!1),o.push({type:"message_delta",delta:{stop_reason:ie(n.finish_reason),stop_sequence:null},usage:{input_tokens:e.usage?.prompt_tokens??0,output_tokens:e.usage?.completion_tokens??0}},{type:"message_stop"})),o}async function ut(e){await ne(s);let t=await e.req.json();i.debug("API request payload:",JSON.stringify(t));let o=Xe(t);i.debug("Processed API request payload:",JSON.stringify(o)),e.set("requestData",{model:o.model});let n=performance.now();s.manualApprove&&await ee();let r=await oe(o),a=performance.now()-n;if(dt(r)){let l=e.get("requestData")||{};r.usage&&(l.tokenUsage=b(r.usage)),l.copilotDuration=a,e.set("requestData",l),i.debug("Non-streaming response from Copilot:",JSON.stringify(r).slice(-400));let c=at(r);return i.debug("Processed API response:",JSON.stringify(c)),e.json(c)}return i.debug("Streaming response from service"),V(e,async l=>{let c=null,u={messageStartSent:!1,contentBlockIndex:0,contentBlockOpen:!1,toolCalls:{}};for await(let d of r){if(i.debug("Copilot raw stream event:",JSON.stringify(d)),d.data==="[DONE]")break;if(!d.data)continue;let p=JSON.parse(d.data);if(p.usage){c=p.usage;let h=e.get("requestData")||{};h.tokenUsage=b(c),h.copilotDuration=a,e.set("requestData",h)}let _=ct(p,u);for(let h of _)i.debug("Processed API event:",JSON.stringify(h)),await l.writeSSE({event:h.type,data:JSON.stringify(h)})}if(c){let d=e.get("requestData")||{};d.tokenUsage||(d.tokenUsage=b(c),d.copilotDuration=a,e.set("requestData",d))}let f=e.get("requestId");f&&S.executeCompletion(f)})}const dt=e=>Object.hasOwn(e,"choices"),ae=new k;ae.post("/",async e=>{try{return await ut(e)}catch(t){return await I(e,t)}});const B=new k;B.get("/",async e=>{try{s.models||await X();let t=s.models?.data.map(o=>({id:o.id,object:"model",type:"model",created:0,created_at:new Date(0).toISOString(),owned_by:o.vendor,display_name:o.name,context_length:o.capabilities.limits?.max_context_window_tokens}));return e.json({object:"list",data:t,has_more:!1})}catch(t){return await I(e,t)}});const re=new k;re.get("/",e=>{try{return e.json({token:s.copilotToken})}catch(t){return console.error("Error fetching token:",t),e.json({error:"Failed to fetch token",token:null},500)}});const se=new k;se.get("/",async e=>{try{let t=await Z();return e.json(t)}catch(t){return console.error("Error fetching Copilot usage:",t),e.json({error:"Failed to fetch Copilot usage"},500)}});const m=new k;m.use(We()),m.use(ke()),m.get("/",e=>e.text("Server running")),m.route("/chat/completions",D),m.route("\u002f\u006d\u006f\u0064\u0065\u006c\u0073",B),m.route("\u002f\u0065\u006d\u0062\u0065\u0064\u0064\u0069\u006e\u0067\u0073",U),m.route("/usage",se),m.route("/token",re),m.route("\u002f\u0076\u0031\u002f\u0063\u0068\u0061\u0074\u002f\u0063\u006f\u006d\u0070\u006c\u0065\u0074\u0069\u006f\u006e\u0073",D),m.route("\u002f\u0076\u0031\u002f\u006d\u006f\u0064\u0065\u006c\u0073",B),m.route("\u002f\u0076\u0031\u002f\u0065\u006d\u0062\u0065\u0064\u0064\u0069\u006e\u0067\u0073",U),m.route("\u002f\u0076\u0031\u002f\u006d\u0065\u0073\u0073\u0061\u0067\u0065\u0073",ae),m.post("/v1/messages/count_tokens",e=>e.json({input_tokens:1}));const le=[];function pt(){let e=async()=>{i.info("Gracefully shutting down...");for(let t of le)try{await t()}catch(o){i.error("Error during cleanup:",o)}i.info("Shutdown complete"),g.exit(0)};g.on("SIGINT",e),g.on("SIGTERM",e),g.on("uncaughtException",t=>{i.error("Uncaught exception:",t),e().finally(()=>g.exit(1))}),g.on("unhandledRejection",(t,o)=>{i.error("Unhandled promise rejection at:",o,"reason:",t),e().finally(()=>g.exit(1))})}async function mt(e,t){if(!e.claudeCode)return;ye(s.models,"Models should be loaded by now");let o,n;if(e.model&&e.smallModel){let a=s.models.data.map(l=>l.id);a.includes(e.model)||(i.error(`Invalid model:${e.model}`),i.info(`Available models:${a.join(``)}`),g.exit(1)),a.includes(e.smallModel)||(i.error(`Invalid small model:${e.smallModel}`),i.info(`Available models:${a.join(``)}`),g.exit(1)),o=e.model,n=e.smallModel,i.info(`Using model:${o}`),i.info(`Using small model:${n}`)}else e.model||e.smallModel?(i.error("Both --model and --small-model must be specified when using command-line model selection"),g.exit(1)):(o=await i.prompt("Select a model to use with Claude Code",{type:"select",options:s.models.data.map(a=>a.id)}),n=await i.prompt("Select a small model to use with Claude Code",{type:"select",options:s.models.data.map(a=>a.id)}));let r=Le({ANTHROPIC_BASE_URL:t,ANTHROPIC_AUTH_TOKEN:"dummy",ANTHROPIC_MODEL:o,ANTHROPIC_SMALL_FAST_MODEL:n},"claude");try{he.writeSync(r),i.success("Copied Claude Code command to clipboard!")}catch{i.warn("Failed to copy to clipboard. Here is the Claude Code command:"),i.log(r)}}async function ft(e){pt(),le.push(()=>{i.debug("Cleaning up connectivity monitor"),v.stop()},()=>{i.debug("Cleaning up token management"),Ae()}),e.verbose&&(i.level=5,i.info("Verbose logging enabled")),s.accountType=e.accountType,e.accountType!=="individual"&&i.info(`Using ${e.accountType} plan GitHub account`),s.manualApprove=e.manual,s.rateLimitSeconds=e.rateLimit,s.rateLimitWait=e.rateLimitWait,s.showToken=e.showToken,s.timeoutMs=e.timeout,s.connectivity.enabled=!e.disableConnectivityMonitoring,await j(),Se(),e.githubToken?(s.githubToken=e.githubToken,i.info("Using provided auth token")):await q();try{let{getGitHubUser:o}=await import("./get-user-BT-kLu95.js"),n=await o();await me(s,n.login)}catch(o){i.error("Failed to get GitHub user info for machine ID generation:",o),i.error("Cannot proceed without GitHub user information"),g.exit(1)}await Ee(),await X(),i.info("Available services:");for(let o of s.models?.data??[]){let n=o.capabilities.limits?.max_context_window_tokens,r=n?` (${n.toLocaleString()} tokens)`:"";i.info(`- ${o.id}${r}`)}let t=`http://localhost:${e.port}`;await mt(e,t),i.box(`\u{1F310} Usage Viewer:https://ericc-ch.\u0067\u0069\u0074\u0068\u0075\u0062.io/copilot-api?endpoint=${t}/usage`),ge({fetch:m.fetch,port:e.port})}const ht=T({meta:{name:"start",description:"Start the Copilot API server"},args:{port:{alias:"p",type:"string",default:"4141",description:"Port to listen on"},verbose:{alias:"v",type:"boolean",default:!1,description:"Enable verbose logging"},"account-type":{alias:"a",type:"string",default:"individual",description:"Account type to use (individual,business,enterprise)"},manual:{type:"boolean",default:!1,description:"Enable manual request approval"},"rate-limit":{alias:"r",type:"string",description:"Rate limit in seconds between requests"},wait:{alias:"w",type:"boolean",default:!1,description:"Wait instead of error when rate limit is hit. Has no effect if rate limit is not set"},"\u0067\u0069\u0074\u0068\u0075\u0062\u002d\u0074\u006f\u006b\u0065\u006e":{alias:"g",type:"string",description:"Provide GitHub token directly (must be generated using the `auth` subcommand)"},"claude-code":{alias:"c",type:"boolean",default:!1,description:"Generate a command to launch Claude Code with Copilot API config"},model:{alias:"m",type:"string",description:"Model to use with Claude Code (requires --claude-code)"},"small-model":{alias:"s",type:"string",description:"Small/fast model to use with Claude Code (requires --claude-code)"},"show-token":{type:"boolean",default:!1,description:"Show GitHub and Copilot tokens on fetch and refresh"},timeout:{alias:"t",type:"string",description:"API timeout in milliseconds (default:120000)"},"disable-connectivity-monitoring":{type:"boolean",default:!1,description:"Disable automatic network connectivity monitoring for token refresh"}},run({args:e}){let t=e["rate-limit"],o=t===void 0?void 0:Number.parseInt(t,10),n=e.timeout,r=n===void 0?12e4:Number.parseInt(n,10);return ft({port:Number.parseInt(e.port,10),verbose:e.verbose,accountType:e["account-type"],manual:e.manual,rateLimit:o,rateLimitWait:e.wait,githubToken:e["\u0067\u0069\u0074\u0068\u0075\u0062\u002d\u0074\u006f\u006b\u0065\u006e"],claudeCode:e["claude-code"],model:e.model,smallModel:e["small-model"],showToken:e["show-token"],timeout:r,disableConnectivityMonitoring:e["disable-connectivity-monitoring"]})}}),gt=T({meta:{name:"\u0063\u006f\u0070\u0069\u006c\u006f\u0074\u002d\u0061\u0070\u0069",description:"A wrapper around GitHub Copilot API to make it OpenAI compatible,making it usable for other tools."},subCommands:{auth:je,start:ht,"check-usage":Ne,debug:Ge}});await fe(gt);