copilot-api-node20 0.5.14-node20 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/main.js CHANGED
@@ -4,7 +4,7 @@ import consola from "consola";
4
4
  import fs from "node:fs/promises";
5
5
  import os from "node:os";
6
6
  import path from "node:path";
7
- import { randomUUID } from "node:crypto";
7
+ import { createHash, randomBytes, randomUUID } from "node:crypto";
8
8
  import clipboard from "clipboardy";
9
9
  import process$1 from "node:process";
10
10
  import { serve } from "srvx";
@@ -12,21 +12,26 @@ import invariant from "tiny-invariant";
12
12
  import { execSync } from "node:child_process";
13
13
  import { Hono } from "hono";
14
14
  import { cors } from "hono/cors";
15
- import { logger } from "hono/logger";
16
15
  import { streamSSE } from "hono/streaming";
17
- import { countTokens } from "gpt-tokenizer/model/gpt-4o";
18
16
  import { events } from "fetch-event-stream";
17
+ import { request } from "undici";
19
18
 
20
19
  //#region src/lib/paths.ts
21
20
  const APP_DIR = path.join(os.homedir(), ".local", "share", "copilot-api");
22
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");
23
24
  const PATHS = {
24
25
  APP_DIR,
25
- GITHUB_TOKEN_PATH
26
+ GITHUB_TOKEN_PATH,
27
+ MACHINE_ID_PATH,
28
+ SESSION_ID_PATH
26
29
  };
27
30
  async function ensurePaths() {
28
31
  await fs.mkdir(PATHS.APP_DIR, { recursive: true });
29
32
  await ensureFile(PATHS.GITHUB_TOKEN_PATH);
33
+ await ensureFile(PATHS.MACHINE_ID_PATH);
34
+ await ensureFile(PATHS.SESSION_ID_PATH);
30
35
  }
31
36
  async function ensureFile(filePath) {
32
37
  try {
@@ -43,32 +48,230 @@ const state = {
43
48
  accountType: "individual",
44
49
  manualApprove: false,
45
50
  rateLimitWait: false,
46
- showToken: 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
+ }
47
66
  };
48
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
+
49
207
  //#endregion
50
208
  //#region src/lib/api-config.ts
51
209
  const standardHeaders = () => ({
52
210
  "content-type": "application/json",
53
211
  accept: "application/json"
54
212
  });
55
- const COPILOT_VERSION = "0.26.7";
213
+ const COPILOT_VERSION = "0.32.2025100203";
56
214
  const EDITOR_PLUGIN_VERSION = `copilot-chat/${COPILOT_VERSION}`;
57
215
  const USER_AGENT = `GitHubCopilotChat/${COPILOT_VERSION}`;
58
- const API_VERSION = "2025-04-01";
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
+ };
59
251
  const copilotBaseUrl = (state$1) => state$1.accountType === "individual" ? "https://api.githubcopilot.com" : `https://api.${state$1.accountType}.githubcopilot.com`;
60
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.");
61
254
  const headers = {
62
255
  Authorization: `Bearer ${state$1.copilotToken}`,
63
- "content-type": standardHeaders()["content-type"],
64
- "copilot-integration-id": "vscode-chat",
65
- "editor-version": `vscode/${state$1.vsCodeVersion}`,
66
- "editor-plugin-version": EDITOR_PLUGIN_VERSION,
67
- "user-agent": USER_AGENT,
68
- "openai-intent": "conversation-panel",
69
- "x-github-api-version": API_VERSION,
70
- "x-request-id": randomUUID(),
71
- "x-vscode-user-agent-library-version": "electron-fetch"
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"
72
275
  };
73
276
  if (vision) headers["copilot-vision-request"] = "true";
74
277
  return headers;
@@ -81,11 +284,119 @@ const githubHeaders = (state$1) => ({
81
284
  "editor-plugin-version": EDITOR_PLUGIN_VERSION,
82
285
  "user-agent": USER_AGENT,
83
286
  "x-github-api-version": API_VERSION,
84
- "x-vscode-user-agent-library-version": "electron-fetch"
287
+ "x-vscode-user-agent-library-version": "node-fetch"
85
288
  });
86
289
  const GITHUB_BASE_URL = "https://github.com";
87
- const GITHUB_CLIENT_ID = "Iv1.b507a08c87ecfe98";
88
- const GITHUB_APP_SCOPES = ["read:user"].join(" ");
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);
89
400
 
90
401
  //#endregion
91
402
  //#region src/lib/error.ts
@@ -97,6 +408,25 @@ var HTTPError = class extends Error {
97
408
  }
98
409
  };
99
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
+ }
100
430
  consola.error("Error occurred:", error);
101
431
  if (error instanceof HTTPError) {
102
432
  const errorText = await error.response.text();
@@ -208,26 +538,10 @@ const getModels = async () => {
208
538
 
209
539
  //#endregion
210
540
  //#region src/services/get-vscode-version.ts
211
- const FALLBACK = "1.98.1";
212
- async function getVSCodeVersion() {
213
- const controller = new AbortController();
214
- const timeout = setTimeout(() => {
215
- controller.abort();
216
- }, 5e3);
217
- try {
218
- const response = await fetch("https://aur.archlinux.org/cgit/aur.git/plain/PKGBUILD?h=visual-studio-code-bin", { signal: controller.signal });
219
- const pkgbuild = await response.text();
220
- const pkgverRegex = /pkgver=([0-9.]+)/;
221
- const match = pkgbuild.match(pkgverRegex);
222
- if (match) return match[1];
223
- return FALLBACK;
224
- } catch {
225
- return FALLBACK;
226
- } finally {
227
- clearTimeout(timeout);
228
- }
541
+ function getVSCodeVersion() {
542
+ return "1.105.0-insider";
229
543
  }
230
- await getVSCodeVersion();
544
+ getVSCodeVersion();
231
545
 
232
546
  //#endregion
233
547
  //#region src/lib/utils.ts
@@ -239,8 +553,8 @@ async function cacheModels() {
239
553
  const models = await getModels();
240
554
  state.models = models;
241
555
  }
242
- const cacheVSCodeVersion = async () => {
243
- const response = await getVSCodeVersion();
556
+ const cacheVSCodeVersion = () => {
557
+ const response = getVSCodeVersion();
244
558
  state.vsCodeVersion = response;
245
559
  consola.info(`Using VSCode version: ${response}`);
246
560
  };
@@ -292,6 +606,25 @@ async function pollAccessToken(deviceCode) {
292
606
 
293
607
  //#endregion
294
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
+ }
295
628
  const readGithubToken = () => fs.readFile(PATHS.GITHUB_TOKEN_PATH, "utf8");
296
629
  const writeGithubToken = (token) => fs.writeFile(PATHS.GITHUB_TOKEN_PATH, token);
297
630
  const setupCopilotToken = async () => {
@@ -300,18 +633,52 @@ const setupCopilotToken = async () => {
300
633
  consola.debug("GitHub Copilot Token fetched successfully!");
301
634
  if (state.showToken) consola.info("Copilot token:", token);
302
635
  const refreshInterval = (refresh_in - 60) * 1e3;
303
- setInterval(async () => {
304
- consola.debug("Refreshing Copilot token");
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;
305
642
  try {
306
- const { token: token$1 } = await getCopilotToken();
307
- state.copilotToken = token$1;
308
- consola.debug("Copilot token refreshed");
309
- if (state.showToken) consola.info("Refreshed Copilot token:", token$1);
310
- } catch (error) {
311
- consola.error("Failed to refresh Copilot token:", error);
312
- throw error;
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;
313
663
  }
664
+ };
665
+ tokenRefreshInterval = setInterval(() => {
666
+ refreshTokenWithRetry(5, 1e3, "scheduled").catch((error) => {
667
+ consola.error("Unexpected error in scheduled token refresh:", error);
668
+ });
314
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);
315
682
  };
316
683
  async function setupGitHubToken(options) {
317
684
  try {
@@ -444,7 +811,8 @@ const checkUsage = defineCommand({
444
811
  async function getPackageVersion() {
445
812
  try {
446
813
  const packageJsonPath = new URL("../package.json", import.meta.url).pathname;
447
- const packageJson = JSON.parse(await fs.readFile(packageJsonPath, "utf8"));
814
+ const packageJsonBuffer = await fs.readFile(packageJsonPath);
815
+ const packageJson = JSON.parse(packageJsonBuffer.toString());
448
816
  return packageJson.version;
449
817
  } catch {
450
818
  return "unknown";
@@ -573,6 +941,24 @@ function generateEnvScript(envVars, commandToRun = "") {
573
941
  return commandBlock || commandToRun;
574
942
  }
575
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
+
576
962
  //#endregion
577
963
  //#region src/lib/approval.ts
578
964
  const awaitApproval = async () => {
@@ -580,6 +966,28 @@ const awaitApproval = async () => {
580
966
  if (!response) throw new HTTPError("Request rejected", Response.json({ message: "Request rejected" }, { status: 403 }));
581
967
  };
582
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
+
583
991
  //#endregion
584
992
  //#region src/lib/rate-limit.ts
585
993
  async function checkRateLimit(state$1) {
@@ -607,40 +1015,52 @@ async function checkRateLimit(state$1) {
607
1015
  }
608
1016
 
609
1017
  //#endregion
610
- //#region src/lib/tokenizer.ts
611
- const getTokenCount = (messages) => {
612
- const simplifiedMessages = messages.map((message) => {
613
- let content = "";
614
- if (typeof message.content === "string") content = message.content;
615
- else if (Array.isArray(message.content)) content = message.content.filter((part) => part.type === "text").map((part) => part.text).join("");
616
- return {
617
- ...message,
618
- content
619
- };
620
- });
621
- let inputMessages = simplifiedMessages.filter((message) => {
622
- return message.role !== "tool";
623
- });
624
- let outputMessages = [];
625
- const lastMessage = simplifiedMessages.at(-1);
626
- if (lastMessage?.role === "assistant") {
627
- inputMessages = simplifiedMessages.slice(0, -1);
628
- outputMessages = [lastMessage];
629
- }
630
- const inputTokens = countTokens(inputMessages);
631
- const outputTokens = countTokens(outputMessages);
632
- return {
633
- input: inputTokens,
634
- output: outputTokens
635
- };
636
- };
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
+ }
637
1056
 
638
1057
  //#endregion
639
1058
  //#region src/services/copilot/create-chat-completions.ts
640
1059
  const createChatCompletions = async (payload) => {
641
1060
  if (!state.copilotToken) throw new Error("Copilot token not found");
642
- const enableVision = payload.messages.some((x) => typeof x.content !== "string" && x.content?.some((x$1) => x$1.type === "image_url"));
643
- const isAgentCall = payload.messages.some((msg) => ["assistant", "tool"].includes(msg.role));
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));
644
1064
  const headers = {
645
1065
  ...copilotHeaders(state, enableVision),
646
1066
  "X-Initiator": isAgentCall ? "agent" : "user"
@@ -651,17 +1071,20 @@ const createChatCompletions = async (payload) => {
651
1071
  controller.abort();
652
1072
  }, timeoutMs);
653
1073
  try {
654
- const response = await fetch(`${copilotBaseUrl(state)}/chat/completions`, {
1074
+ const { statusCode, headers: responseHeaders, body } = await request(`${copilotBaseUrl(state)}/chat/completions`, {
655
1075
  method: "POST",
656
1076
  headers,
657
- body: JSON.stringify(payload),
658
- signal: controller.signal
1077
+ body: JSON.stringify(sanitizedPayload),
1078
+ signal: controller.signal,
1079
+ headersTimeout: timeoutMs,
1080
+ bodyTimeout: timeoutMs * 3
659
1081
  });
660
- if (!response.ok) {
661
- consola.error("Failed to create chat completions", response);
662
- throw new HTTPError("Failed to create chat completions", response);
663
- }
664
- if (payload.stream) return events(response);
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);
665
1088
  return await response.json();
666
1089
  } finally {
667
1090
  clearTimeout(timeout);
@@ -674,7 +1097,7 @@ async function handleCompletion$1(c) {
674
1097
  await checkRateLimit(state);
675
1098
  let payload = await c.req.json();
676
1099
  consola.debug("Request payload:", JSON.stringify(payload).slice(-400));
677
- consola.info("Current token count:", getTokenCount(payload.messages));
1100
+ c.set("requestData", { model: payload.model });
678
1101
  if (state.manualApprove) await awaitApproval();
679
1102
  if (isNullish(payload.max_tokens)) {
680
1103
  const selectedModel = state.models?.data.find((model) => model.id === payload.model);
@@ -684,17 +1107,42 @@ async function handleCompletion$1(c) {
684
1107
  };
685
1108
  consola.debug("Set max_tokens to:", JSON.stringify(payload.max_tokens));
686
1109
  }
1110
+ const copilotStart = performance.now();
687
1111
  const response = await createChatCompletions(payload);
1112
+ const copilotDuration = performance.now() - copilotStart;
688
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);
689
1118
  consola.debug("Non-streaming response:", JSON.stringify(response));
690
1119
  return c.json(response);
691
1120
  }
692
- consola.debug("Streaming response");
693
1121
  return streamSSE(c, async (stream) => {
1122
+ let finalUsage = null;
694
1123
  for await (const chunk of response) {
695
- consola.debug("Streaming chunk:", JSON.stringify(chunk));
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 {}
696
1134
  await stream.writeSSE(chunk);
697
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);
698
1146
  });
699
1147
  }
700
1148
  const isNonStreaming$1 = (response) => Object.hasOwn(response, "choices");
@@ -738,8 +1186,15 @@ const createEmbeddings = async (payload) => {
738
1186
  const embeddingRoutes = new Hono();
739
1187
  embeddingRoutes.post("/", async (c) => {
740
1188
  try {
741
- const paylod = await c.req.json();
742
- const response = await createEmbeddings(paylod);
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);
743
1198
  return c.json(response);
744
1199
  } catch (error) {
745
1200
  return await forwardError(c, error);
@@ -807,7 +1262,7 @@ function handleUserMessage(message) {
807
1262
  for (const block of toolResultBlocks) newMessages.push({
808
1263
  role: "tool",
809
1264
  tool_call_id: block.tool_use_id,
810
- content: block.content
1265
+ content: mapContent(block.content)
811
1266
  });
812
1267
  if (otherBlocks.length > 0) newMessages.push({
813
1268
  role: "user",
@@ -1076,9 +1531,16 @@ async function handleCompletion(c) {
1076
1531
  consola.debug("Anthropic request payload:", JSON.stringify(anthropicPayload));
1077
1532
  const openAIPayload = translateToOpenAI(anthropicPayload);
1078
1533
  consola.debug("Translated OpenAI request payload:", JSON.stringify(openAIPayload));
1534
+ c.set("requestData", { model: openAIPayload.model });
1535
+ const copilotStart = performance.now();
1079
1536
  if (state.manualApprove) await awaitApproval();
1080
1537
  const response = await createChatCompletions(openAIPayload);
1538
+ const copilotDuration = performance.now() - copilotStart;
1081
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);
1082
1544
  consola.debug("Non-streaming response from Copilot:", JSON.stringify(response).slice(-400));
1083
1545
  const anthropicResponse = translateToAnthropic(response);
1084
1546
  consola.debug("Translated Anthropic response:", JSON.stringify(anthropicResponse));
@@ -1086,6 +1548,7 @@ async function handleCompletion(c) {
1086
1548
  }
1087
1549
  consola.debug("Streaming response from Copilot");
1088
1550
  return streamSSE(c, async (stream) => {
1551
+ let finalUsage = null;
1089
1552
  const streamState = {
1090
1553
  messageStartSent: false,
1091
1554
  contentBlockIndex: 0,
@@ -1097,6 +1560,13 @@ async function handleCompletion(c) {
1097
1560
  if (rawEvent.data === "[DONE]") break;
1098
1561
  if (!rawEvent.data) continue;
1099
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
+ }
1100
1570
  const events$1 = translateChunkToAnthropicEvents(chunk, streamState);
1101
1571
  for (const event of events$1) {
1102
1572
  consola.debug("Translated Anthropic event:", JSON.stringify(event));
@@ -1106,6 +1576,16 @@ async function handleCompletion(c) {
1106
1576
  });
1107
1577
  }
1108
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);
1109
1589
  });
1110
1590
  }
1111
1591
  const isNonStreaming = (response) => Object.hasOwn(response, "choices");
@@ -1177,7 +1657,7 @@ usageRoute.get("/", async (c) => {
1177
1657
  //#endregion
1178
1658
  //#region src/server.ts
1179
1659
  const server = new Hono();
1180
- server.use(logger());
1660
+ server.use(enhancedLogger());
1181
1661
  server.use(cors());
1182
1662
  server.get("/", (c) => c.text("Server running"));
1183
1663
  server.route("/chat/completions", completionRoutes);
@@ -1193,7 +1673,86 @@ server.post("/v1/messages/count_tokens", (c) => c.json({ input_tokens: 1 }));
1193
1673
 
1194
1674
  //#endregion
1195
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
+ }
1196
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
+ });
1197
1756
  if (options.verbose) {
1198
1757
  consola.level = 5;
1199
1758
  consola.info("Verbose logging enabled");
@@ -1205,8 +1764,10 @@ async function runServer(options) {
1205
1764
  state.rateLimitWait = options.rateLimitWait;
1206
1765
  state.showToken = options.showToken;
1207
1766
  state.timeoutMs = options.timeout;
1767
+ state.connectivity.enabled = !options.disableConnectivityMonitoring;
1208
1768
  await ensurePaths();
1209
- await cacheVSCodeVersion();
1769
+ cacheVSCodeVersion();
1770
+ await initializeVSCodeIdentifiers(state);
1210
1771
  if (options.githubToken) {
1211
1772
  state.githubToken = options.githubToken;
1212
1773
  consola.info("Using provided GitHub token");
@@ -1215,53 +1776,7 @@ async function runServer(options) {
1215
1776
  await cacheModels();
1216
1777
  consola.info(`Available models: \n${state.models?.data.map((model) => `- ${model.id}`).join("\n")}`);
1217
1778
  const serverUrl = `http://localhost:${options.port}`;
1218
- if (options.claudeCode) {
1219
- invariant(state.models, "Models should be loaded by now");
1220
- let selectedModel;
1221
- let selectedSmallModel;
1222
- if (options.model && options.smallModel) {
1223
- const availableModelIds = state.models.data.map((model) => model.id);
1224
- if (!availableModelIds.includes(options.model)) {
1225
- consola.error(`Invalid model: ${options.model}`);
1226
- consola.info(`Available models: \n${availableModelIds.join("\n")}`);
1227
- process$1.exit(1);
1228
- }
1229
- if (!availableModelIds.includes(options.smallModel)) {
1230
- consola.error(`Invalid small model: ${options.smallModel}`);
1231
- consola.info(`Available models: \n${availableModelIds.join("\n")}`);
1232
- process$1.exit(1);
1233
- }
1234
- selectedModel = options.model;
1235
- selectedSmallModel = options.smallModel;
1236
- consola.info(`Using model: ${selectedModel}`);
1237
- consola.info(`Using small model: ${selectedSmallModel}`);
1238
- } else if (options.model || options.smallModel) {
1239
- consola.error("Both --model and --small-model must be specified when using command-line model selection");
1240
- process$1.exit(1);
1241
- } else {
1242
- selectedModel = await consola.prompt("Select a model to use with Claude Code", {
1243
- type: "select",
1244
- options: state.models.data.map((model) => model.id)
1245
- });
1246
- selectedSmallModel = await consola.prompt("Select a small model to use with Claude Code", {
1247
- type: "select",
1248
- options: state.models.data.map((model) => model.id)
1249
- });
1250
- }
1251
- const command = generateEnvScript({
1252
- ANTHROPIC_BASE_URL: serverUrl,
1253
- ANTHROPIC_AUTH_TOKEN: "dummy",
1254
- ANTHROPIC_MODEL: selectedModel,
1255
- ANTHROPIC_SMALL_FAST_MODEL: selectedSmallModel
1256
- }, "claude");
1257
- try {
1258
- clipboard.writeSync(command);
1259
- consola.success("Copied Claude Code command to clipboard!");
1260
- } catch {
1261
- consola.warn("Failed to copy to clipboard. Here is the Claude Code command:");
1262
- consola.log(command);
1263
- }
1264
- }
1779
+ await setupClaudeCodeConfiguration(options, serverUrl);
1265
1780
  consola.box(`🌐 Usage Viewer: https://ericc-ch.github.io/copilot-api?endpoint=${serverUrl}/usage`);
1266
1781
  serve({
1267
1782
  fetch: server.fetch,
@@ -1338,6 +1853,11 @@ const start = defineCommand({
1338
1853
  alias: "t",
1339
1854
  type: "string",
1340
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"
1341
1861
  }
1342
1862
  },
1343
1863
  run({ args }) {
@@ -1357,7 +1877,8 @@ const start = defineCommand({
1357
1877
  model: args.model,
1358
1878
  smallModel: args["small-model"],
1359
1879
  showToken: args["show-token"],
1360
- timeout
1880
+ timeout,
1881
+ disableConnectivityMonitoring: args["disable-connectivity-monitoring"]
1361
1882
  });
1362
1883
  }
1363
1884
  });