copilot-api-node20 0.5.14-node20
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/LICENSE +21 -0
- package/README.md +373 -0
- package/dist/main.js +1383 -0
- package/dist/main.js.map +1 -0
- package/package.json +65 -0
package/dist/main.js
ADDED
|
@@ -0,0 +1,1383 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { defineCommand, runMain } from "citty";
|
|
3
|
+
import consola from "consola";
|
|
4
|
+
import fs from "node:fs/promises";
|
|
5
|
+
import os from "node:os";
|
|
6
|
+
import path from "node:path";
|
|
7
|
+
import { randomUUID } from "node:crypto";
|
|
8
|
+
import clipboard from "clipboardy";
|
|
9
|
+
import 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 { logger } from "hono/logger";
|
|
16
|
+
import { streamSSE } from "hono/streaming";
|
|
17
|
+
import { countTokens } from "gpt-tokenizer/model/gpt-4o";
|
|
18
|
+
import { events } from "fetch-event-stream";
|
|
19
|
+
|
|
20
|
+
//#region src/lib/paths.ts
|
|
21
|
+
const APP_DIR = path.join(os.homedir(), ".local", "share", "copilot-api");
|
|
22
|
+
const GITHUB_TOKEN_PATH = path.join(APP_DIR, "github_token");
|
|
23
|
+
const PATHS = {
|
|
24
|
+
APP_DIR,
|
|
25
|
+
GITHUB_TOKEN_PATH
|
|
26
|
+
};
|
|
27
|
+
async function ensurePaths() {
|
|
28
|
+
await fs.mkdir(PATHS.APP_DIR, { recursive: true });
|
|
29
|
+
await ensureFile(PATHS.GITHUB_TOKEN_PATH);
|
|
30
|
+
}
|
|
31
|
+
async function ensureFile(filePath) {
|
|
32
|
+
try {
|
|
33
|
+
await fs.access(filePath, fs.constants.W_OK);
|
|
34
|
+
} catch {
|
|
35
|
+
await fs.writeFile(filePath, "");
|
|
36
|
+
await fs.chmod(filePath, 384);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
//#endregion
|
|
41
|
+
//#region src/lib/state.ts
|
|
42
|
+
const state = {
|
|
43
|
+
accountType: "individual",
|
|
44
|
+
manualApprove: false,
|
|
45
|
+
rateLimitWait: false,
|
|
46
|
+
showToken: false
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
//#endregion
|
|
50
|
+
//#region src/lib/api-config.ts
|
|
51
|
+
const standardHeaders = () => ({
|
|
52
|
+
"content-type": "application/json",
|
|
53
|
+
accept: "application/json"
|
|
54
|
+
});
|
|
55
|
+
const COPILOT_VERSION = "0.26.7";
|
|
56
|
+
const EDITOR_PLUGIN_VERSION = `copilot-chat/${COPILOT_VERSION}`;
|
|
57
|
+
const USER_AGENT = `GitHubCopilotChat/${COPILOT_VERSION}`;
|
|
58
|
+
const API_VERSION = "2025-04-01";
|
|
59
|
+
const copilotBaseUrl = (state$1) => state$1.accountType === "individual" ? "https://api.githubcopilot.com" : `https://api.${state$1.accountType}.githubcopilot.com`;
|
|
60
|
+
const copilotHeaders = (state$1, vision = false) => {
|
|
61
|
+
const headers = {
|
|
62
|
+
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"
|
|
72
|
+
};
|
|
73
|
+
if (vision) headers["copilot-vision-request"] = "true";
|
|
74
|
+
return headers;
|
|
75
|
+
};
|
|
76
|
+
const GITHUB_API_BASE_URL = "https://api.github.com";
|
|
77
|
+
const githubHeaders = (state$1) => ({
|
|
78
|
+
...standardHeaders(),
|
|
79
|
+
authorization: `token ${state$1.githubToken}`,
|
|
80
|
+
"editor-version": `vscode/${state$1.vsCodeVersion}`,
|
|
81
|
+
"editor-plugin-version": EDITOR_PLUGIN_VERSION,
|
|
82
|
+
"user-agent": USER_AGENT,
|
|
83
|
+
"x-github-api-version": API_VERSION,
|
|
84
|
+
"x-vscode-user-agent-library-version": "electron-fetch"
|
|
85
|
+
});
|
|
86
|
+
const GITHUB_BASE_URL = "https://github.com";
|
|
87
|
+
const GITHUB_CLIENT_ID = "Iv1.b507a08c87ecfe98";
|
|
88
|
+
const GITHUB_APP_SCOPES = ["read:user"].join(" ");
|
|
89
|
+
|
|
90
|
+
//#endregion
|
|
91
|
+
//#region src/lib/error.ts
|
|
92
|
+
var HTTPError = class extends Error {
|
|
93
|
+
response;
|
|
94
|
+
constructor(message, response) {
|
|
95
|
+
super(message);
|
|
96
|
+
this.response = response;
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
async function forwardError(c, error) {
|
|
100
|
+
consola.error("Error occurred:", error);
|
|
101
|
+
if (error instanceof HTTPError) {
|
|
102
|
+
const errorText = await error.response.text();
|
|
103
|
+
let errorJson;
|
|
104
|
+
try {
|
|
105
|
+
errorJson = JSON.parse(errorText);
|
|
106
|
+
} catch {
|
|
107
|
+
errorJson = errorText;
|
|
108
|
+
}
|
|
109
|
+
consola.error("HTTP error:", errorJson);
|
|
110
|
+
return c.json({ error: {
|
|
111
|
+
message: errorText,
|
|
112
|
+
type: "error"
|
|
113
|
+
} }, error.response.status);
|
|
114
|
+
}
|
|
115
|
+
return c.json({ error: {
|
|
116
|
+
message: error.message,
|
|
117
|
+
type: "error"
|
|
118
|
+
} }, 500);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
//#endregion
|
|
122
|
+
//#region src/services/github/get-copilot-token.ts
|
|
123
|
+
const getCopilotToken = async () => {
|
|
124
|
+
const controller = new AbortController();
|
|
125
|
+
const timeoutMs = state.timeoutMs ?? 12e4;
|
|
126
|
+
const timeout = setTimeout(() => {
|
|
127
|
+
controller.abort();
|
|
128
|
+
}, timeoutMs);
|
|
129
|
+
try {
|
|
130
|
+
const response = await fetch(`${GITHUB_API_BASE_URL}/copilot_internal/v2/token`, {
|
|
131
|
+
headers: githubHeaders(state),
|
|
132
|
+
signal: controller.signal
|
|
133
|
+
});
|
|
134
|
+
if (!response.ok) throw new HTTPError("Failed to get Copilot token", response);
|
|
135
|
+
return await response.json();
|
|
136
|
+
} finally {
|
|
137
|
+
clearTimeout(timeout);
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
//#endregion
|
|
142
|
+
//#region src/services/github/get-device-code.ts
|
|
143
|
+
async function getDeviceCode() {
|
|
144
|
+
const controller = new AbortController();
|
|
145
|
+
const timeoutMs = state.timeoutMs ?? 12e4;
|
|
146
|
+
const timeout = setTimeout(() => {
|
|
147
|
+
controller.abort();
|
|
148
|
+
}, timeoutMs);
|
|
149
|
+
try {
|
|
150
|
+
const response = await fetch(`${GITHUB_BASE_URL}/login/device/code`, {
|
|
151
|
+
method: "POST",
|
|
152
|
+
headers: standardHeaders(),
|
|
153
|
+
body: JSON.stringify({
|
|
154
|
+
client_id: GITHUB_CLIENT_ID,
|
|
155
|
+
scope: GITHUB_APP_SCOPES
|
|
156
|
+
}),
|
|
157
|
+
signal: controller.signal
|
|
158
|
+
});
|
|
159
|
+
if (!response.ok) throw new HTTPError("Failed to get device code", response);
|
|
160
|
+
return await response.json();
|
|
161
|
+
} finally {
|
|
162
|
+
clearTimeout(timeout);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
//#endregion
|
|
167
|
+
//#region src/services/github/get-user.ts
|
|
168
|
+
async function getGitHubUser() {
|
|
169
|
+
const controller = new AbortController();
|
|
170
|
+
const timeoutMs = state.timeoutMs ?? 12e4;
|
|
171
|
+
const timeout = setTimeout(() => {
|
|
172
|
+
controller.abort();
|
|
173
|
+
}, timeoutMs);
|
|
174
|
+
try {
|
|
175
|
+
const response = await fetch(`${GITHUB_API_BASE_URL}/user`, {
|
|
176
|
+
headers: {
|
|
177
|
+
authorization: `token ${state.githubToken}`,
|
|
178
|
+
...standardHeaders()
|
|
179
|
+
},
|
|
180
|
+
signal: controller.signal
|
|
181
|
+
});
|
|
182
|
+
if (!response.ok) throw new HTTPError("Failed to get GitHub user", response);
|
|
183
|
+
return await response.json();
|
|
184
|
+
} finally {
|
|
185
|
+
clearTimeout(timeout);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
//#endregion
|
|
190
|
+
//#region src/services/copilot/get-models.ts
|
|
191
|
+
const getModels = async () => {
|
|
192
|
+
const controller = new AbortController();
|
|
193
|
+
const timeoutMs = state.timeoutMs ?? 12e4;
|
|
194
|
+
const timeout = setTimeout(() => {
|
|
195
|
+
controller.abort();
|
|
196
|
+
}, timeoutMs);
|
|
197
|
+
try {
|
|
198
|
+
const response = await fetch(`${copilotBaseUrl(state)}/models`, {
|
|
199
|
+
headers: copilotHeaders(state),
|
|
200
|
+
signal: controller.signal
|
|
201
|
+
});
|
|
202
|
+
if (!response.ok) throw new HTTPError("Failed to get models", response);
|
|
203
|
+
return await response.json();
|
|
204
|
+
} finally {
|
|
205
|
+
clearTimeout(timeout);
|
|
206
|
+
}
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
//#endregion
|
|
210
|
+
//#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
|
+
}
|
|
229
|
+
}
|
|
230
|
+
await getVSCodeVersion();
|
|
231
|
+
|
|
232
|
+
//#endregion
|
|
233
|
+
//#region src/lib/utils.ts
|
|
234
|
+
const sleep = (ms) => new Promise((resolve) => {
|
|
235
|
+
setTimeout(resolve, ms);
|
|
236
|
+
});
|
|
237
|
+
const isNullish = (value) => value === null || value === void 0;
|
|
238
|
+
async function cacheModels() {
|
|
239
|
+
const models = await getModels();
|
|
240
|
+
state.models = models;
|
|
241
|
+
}
|
|
242
|
+
const cacheVSCodeVersion = async () => {
|
|
243
|
+
const response = await getVSCodeVersion();
|
|
244
|
+
state.vsCodeVersion = response;
|
|
245
|
+
consola.info(`Using VSCode version: ${response}`);
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
//#endregion
|
|
249
|
+
//#region src/services/github/poll-access-token.ts
|
|
250
|
+
async function pollAccessToken(deviceCode) {
|
|
251
|
+
const sleepDuration = (deviceCode.interval + 1) * 1e3;
|
|
252
|
+
consola.debug(`Polling access token with interval of ${sleepDuration}ms`);
|
|
253
|
+
while (true) {
|
|
254
|
+
const controller = new AbortController();
|
|
255
|
+
const timeoutMs = state.timeoutMs ?? 12e4;
|
|
256
|
+
const timeout = setTimeout(() => {
|
|
257
|
+
controller.abort();
|
|
258
|
+
}, timeoutMs);
|
|
259
|
+
try {
|
|
260
|
+
const response = await fetch(`${GITHUB_BASE_URL}/login/oauth/access_token`, {
|
|
261
|
+
method: "POST",
|
|
262
|
+
headers: standardHeaders(),
|
|
263
|
+
body: JSON.stringify({
|
|
264
|
+
client_id: GITHUB_CLIENT_ID,
|
|
265
|
+
device_code: deviceCode.device_code,
|
|
266
|
+
grant_type: "urn:ietf:params:oauth:grant-type:device_code"
|
|
267
|
+
}),
|
|
268
|
+
signal: controller.signal
|
|
269
|
+
});
|
|
270
|
+
if (!response.ok) {
|
|
271
|
+
await sleep(sleepDuration);
|
|
272
|
+
consola.error("Failed to poll access token:", await response.text());
|
|
273
|
+
continue;
|
|
274
|
+
}
|
|
275
|
+
const json = await response.json();
|
|
276
|
+
consola.debug("Polling access token response:", json);
|
|
277
|
+
const { access_token } = json;
|
|
278
|
+
if (access_token) return access_token;
|
|
279
|
+
else await sleep(sleepDuration);
|
|
280
|
+
} catch (error) {
|
|
281
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
282
|
+
consola.error("Access token polling timed out");
|
|
283
|
+
await sleep(sleepDuration);
|
|
284
|
+
continue;
|
|
285
|
+
}
|
|
286
|
+
throw error;
|
|
287
|
+
} finally {
|
|
288
|
+
clearTimeout(timeout);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
//#endregion
|
|
294
|
+
//#region src/lib/token.ts
|
|
295
|
+
const readGithubToken = () => fs.readFile(PATHS.GITHUB_TOKEN_PATH, "utf8");
|
|
296
|
+
const writeGithubToken = (token) => fs.writeFile(PATHS.GITHUB_TOKEN_PATH, token);
|
|
297
|
+
const setupCopilotToken = async () => {
|
|
298
|
+
const { token, refresh_in } = await getCopilotToken();
|
|
299
|
+
state.copilotToken = token;
|
|
300
|
+
consola.debug("GitHub Copilot Token fetched successfully!");
|
|
301
|
+
if (state.showToken) consola.info("Copilot token:", token);
|
|
302
|
+
const refreshInterval = (refresh_in - 60) * 1e3;
|
|
303
|
+
setInterval(async () => {
|
|
304
|
+
consola.debug("Refreshing Copilot token");
|
|
305
|
+
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;
|
|
313
|
+
}
|
|
314
|
+
}, refreshInterval);
|
|
315
|
+
};
|
|
316
|
+
async function setupGitHubToken(options) {
|
|
317
|
+
try {
|
|
318
|
+
const githubToken = await readGithubToken();
|
|
319
|
+
if (githubToken && !options?.force) {
|
|
320
|
+
state.githubToken = githubToken;
|
|
321
|
+
if (state.showToken) consola.info("GitHub token:", githubToken);
|
|
322
|
+
await logUser();
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
consola.info("Not logged in, getting new access token");
|
|
326
|
+
const response = await getDeviceCode();
|
|
327
|
+
consola.debug("Device code response:", response);
|
|
328
|
+
consola.info(`Please enter the code "${response.user_code}" in ${response.verification_uri}`);
|
|
329
|
+
const token = await pollAccessToken(response);
|
|
330
|
+
await writeGithubToken(token);
|
|
331
|
+
state.githubToken = token;
|
|
332
|
+
if (state.showToken) consola.info("GitHub token:", token);
|
|
333
|
+
await logUser();
|
|
334
|
+
} catch (error) {
|
|
335
|
+
if (error instanceof HTTPError) {
|
|
336
|
+
consola.error("Failed to get GitHub token:", await error.response.json());
|
|
337
|
+
throw error;
|
|
338
|
+
}
|
|
339
|
+
consola.error("Failed to get GitHub token:", error);
|
|
340
|
+
throw error;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
async function logUser() {
|
|
344
|
+
const user = await getGitHubUser();
|
|
345
|
+
consola.info(`Logged in as ${user.login}`);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
//#endregion
|
|
349
|
+
//#region src/auth.ts
|
|
350
|
+
async function runAuth(options) {
|
|
351
|
+
if (options.verbose) {
|
|
352
|
+
consola.level = 5;
|
|
353
|
+
consola.info("Verbose logging enabled");
|
|
354
|
+
}
|
|
355
|
+
state.showToken = options.showToken;
|
|
356
|
+
await ensurePaths();
|
|
357
|
+
await setupGitHubToken({ force: true });
|
|
358
|
+
consola.success("GitHub token written to", PATHS.GITHUB_TOKEN_PATH);
|
|
359
|
+
}
|
|
360
|
+
const auth = defineCommand({
|
|
361
|
+
meta: {
|
|
362
|
+
name: "auth",
|
|
363
|
+
description: "Run GitHub auth flow without running the server"
|
|
364
|
+
},
|
|
365
|
+
args: {
|
|
366
|
+
verbose: {
|
|
367
|
+
alias: "v",
|
|
368
|
+
type: "boolean",
|
|
369
|
+
default: false,
|
|
370
|
+
description: "Enable verbose logging"
|
|
371
|
+
},
|
|
372
|
+
"show-token": {
|
|
373
|
+
type: "boolean",
|
|
374
|
+
default: false,
|
|
375
|
+
description: "Show GitHub token on auth"
|
|
376
|
+
}
|
|
377
|
+
},
|
|
378
|
+
run({ args }) {
|
|
379
|
+
return runAuth({
|
|
380
|
+
verbose: args.verbose,
|
|
381
|
+
showToken: args["show-token"]
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
//#endregion
|
|
387
|
+
//#region src/services/github/get-copilot-usage.ts
|
|
388
|
+
const getCopilotUsage = async () => {
|
|
389
|
+
const controller = new AbortController();
|
|
390
|
+
const timeoutMs = state.timeoutMs ?? 12e4;
|
|
391
|
+
const timeout = setTimeout(() => {
|
|
392
|
+
controller.abort();
|
|
393
|
+
}, timeoutMs);
|
|
394
|
+
try {
|
|
395
|
+
const response = await fetch(`${GITHUB_API_BASE_URL}/copilot_internal/user`, {
|
|
396
|
+
headers: githubHeaders(state),
|
|
397
|
+
signal: controller.signal
|
|
398
|
+
});
|
|
399
|
+
if (!response.ok) throw new HTTPError("Failed to get Copilot usage", response);
|
|
400
|
+
return await response.json();
|
|
401
|
+
} finally {
|
|
402
|
+
clearTimeout(timeout);
|
|
403
|
+
}
|
|
404
|
+
};
|
|
405
|
+
|
|
406
|
+
//#endregion
|
|
407
|
+
//#region src/check-usage.ts
|
|
408
|
+
const checkUsage = defineCommand({
|
|
409
|
+
meta: {
|
|
410
|
+
name: "check-usage",
|
|
411
|
+
description: "Show current GitHub Copilot usage/quota information"
|
|
412
|
+
},
|
|
413
|
+
async run() {
|
|
414
|
+
await ensurePaths();
|
|
415
|
+
await setupGitHubToken();
|
|
416
|
+
try {
|
|
417
|
+
const usage = await getCopilotUsage();
|
|
418
|
+
const premium = usage.quota_snapshots.premium_interactions;
|
|
419
|
+
const premiumTotal = premium.entitlement;
|
|
420
|
+
const premiumUsed = premiumTotal - premium.remaining;
|
|
421
|
+
const premiumPercentUsed = premiumTotal > 0 ? premiumUsed / premiumTotal * 100 : 0;
|
|
422
|
+
const premiumPercentRemaining = premium.percent_remaining;
|
|
423
|
+
function summarizeQuota(name, snap) {
|
|
424
|
+
if (!snap) return `${name}: N/A`;
|
|
425
|
+
const total = snap.entitlement;
|
|
426
|
+
const used = total - snap.remaining;
|
|
427
|
+
const percentUsed = total > 0 ? used / total * 100 : 0;
|
|
428
|
+
const percentRemaining = snap.percent_remaining;
|
|
429
|
+
return `${name}: ${used}/${total} used (${percentUsed.toFixed(1)}% used, ${percentRemaining.toFixed(1)}% remaining)`;
|
|
430
|
+
}
|
|
431
|
+
const premiumLine = `Premium: ${premiumUsed}/${premiumTotal} used (${premiumPercentUsed.toFixed(1)}% used, ${premiumPercentRemaining.toFixed(1)}% remaining)`;
|
|
432
|
+
const chatLine = summarizeQuota("Chat", usage.quota_snapshots.chat);
|
|
433
|
+
const completionsLine = summarizeQuota("Completions", usage.quota_snapshots.completions);
|
|
434
|
+
consola.box(`Copilot Usage (plan: ${usage.copilot_plan})\nQuota resets: ${usage.quota_reset_date}\n\nQuotas:\n ${premiumLine}\n ${chatLine}\n ${completionsLine}`);
|
|
435
|
+
} catch (err) {
|
|
436
|
+
consola.error("Failed to fetch Copilot usage:", err);
|
|
437
|
+
process.exit(1);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
//#endregion
|
|
443
|
+
//#region src/debug.ts
|
|
444
|
+
async function getPackageVersion() {
|
|
445
|
+
try {
|
|
446
|
+
const packageJsonPath = new URL("../package.json", import.meta.url).pathname;
|
|
447
|
+
const packageJson = JSON.parse(await fs.readFile(packageJsonPath, "utf8"));
|
|
448
|
+
return packageJson.version;
|
|
449
|
+
} catch {
|
|
450
|
+
return "unknown";
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
function getRuntimeInfo() {
|
|
454
|
+
const isBun = typeof Bun !== "undefined";
|
|
455
|
+
return {
|
|
456
|
+
name: isBun ? "bun" : "node",
|
|
457
|
+
version: isBun ? Bun.version : process.version.slice(1),
|
|
458
|
+
platform: os.platform(),
|
|
459
|
+
arch: os.arch()
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
async function checkTokenExists() {
|
|
463
|
+
try {
|
|
464
|
+
const stats = await fs.stat(PATHS.GITHUB_TOKEN_PATH);
|
|
465
|
+
if (!stats.isFile()) return false;
|
|
466
|
+
const content = await fs.readFile(PATHS.GITHUB_TOKEN_PATH, "utf8");
|
|
467
|
+
return content.trim().length > 0;
|
|
468
|
+
} catch {
|
|
469
|
+
return false;
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
async function getDebugInfo() {
|
|
473
|
+
const [version, tokenExists] = await Promise.all([getPackageVersion(), checkTokenExists()]);
|
|
474
|
+
return {
|
|
475
|
+
version,
|
|
476
|
+
runtime: getRuntimeInfo(),
|
|
477
|
+
paths: {
|
|
478
|
+
APP_DIR: PATHS.APP_DIR,
|
|
479
|
+
GITHUB_TOKEN_PATH: PATHS.GITHUB_TOKEN_PATH
|
|
480
|
+
},
|
|
481
|
+
tokenExists
|
|
482
|
+
};
|
|
483
|
+
}
|
|
484
|
+
function printDebugInfoPlain(info) {
|
|
485
|
+
consola.info(`copilot-api debug
|
|
486
|
+
|
|
487
|
+
Version: ${info.version}
|
|
488
|
+
Runtime: ${info.runtime.name} ${info.runtime.version} (${info.runtime.platform} ${info.runtime.arch})
|
|
489
|
+
|
|
490
|
+
Paths:
|
|
491
|
+
- APP_DIR: ${info.paths.APP_DIR}
|
|
492
|
+
- GITHUB_TOKEN_PATH: ${info.paths.GITHUB_TOKEN_PATH}
|
|
493
|
+
|
|
494
|
+
Token exists: ${info.tokenExists ? "Yes" : "No"}`);
|
|
495
|
+
}
|
|
496
|
+
function printDebugInfoJson(info) {
|
|
497
|
+
console.log(JSON.stringify(info, null, 2));
|
|
498
|
+
}
|
|
499
|
+
async function runDebug(options) {
|
|
500
|
+
const debugInfo = await getDebugInfo();
|
|
501
|
+
if (options.json) printDebugInfoJson(debugInfo);
|
|
502
|
+
else printDebugInfoPlain(debugInfo);
|
|
503
|
+
}
|
|
504
|
+
const debug = defineCommand({
|
|
505
|
+
meta: {
|
|
506
|
+
name: "debug",
|
|
507
|
+
description: "Print debug information about the application"
|
|
508
|
+
},
|
|
509
|
+
args: { json: {
|
|
510
|
+
type: "boolean",
|
|
511
|
+
default: false,
|
|
512
|
+
description: "Output debug information as JSON"
|
|
513
|
+
} },
|
|
514
|
+
run({ args }) {
|
|
515
|
+
return runDebug({ json: args.json });
|
|
516
|
+
}
|
|
517
|
+
});
|
|
518
|
+
|
|
519
|
+
//#endregion
|
|
520
|
+
//#region src/lib/shell.ts
|
|
521
|
+
function getShell() {
|
|
522
|
+
const { platform, ppid, env } = process$1;
|
|
523
|
+
if (platform === "win32") {
|
|
524
|
+
try {
|
|
525
|
+
const command = `wmic process get ParentProcessId,Name | findstr "${ppid}"`;
|
|
526
|
+
const parentProcess = execSync(command, { stdio: "pipe" }).toString();
|
|
527
|
+
if (parentProcess.toLowerCase().includes("powershell.exe")) return "powershell";
|
|
528
|
+
} catch {
|
|
529
|
+
return "cmd";
|
|
530
|
+
}
|
|
531
|
+
return "cmd";
|
|
532
|
+
} else {
|
|
533
|
+
const shellPath = env.SHELL;
|
|
534
|
+
if (shellPath) {
|
|
535
|
+
if (shellPath.endsWith("zsh")) return "zsh";
|
|
536
|
+
if (shellPath.endsWith("fish")) return "fish";
|
|
537
|
+
if (shellPath.endsWith("bash")) return "bash";
|
|
538
|
+
}
|
|
539
|
+
return "sh";
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
/**
|
|
543
|
+
* Generates a copy-pasteable script to set multiple environment variables
|
|
544
|
+
* and run a subsequent command.
|
|
545
|
+
* @param {EnvVars} envVars - An object of environment variables to set.
|
|
546
|
+
* @param {string} commandToRun - The command to run after setting the variables.
|
|
547
|
+
* @returns {string} The formatted script string.
|
|
548
|
+
*/
|
|
549
|
+
function generateEnvScript(envVars, commandToRun = "") {
|
|
550
|
+
const shell = getShell();
|
|
551
|
+
const filteredEnvVars = Object.entries(envVars).filter(([, value]) => value !== void 0);
|
|
552
|
+
let commandBlock;
|
|
553
|
+
switch (shell) {
|
|
554
|
+
case "powershell":
|
|
555
|
+
commandBlock = filteredEnvVars.map(([key, value]) => `$env:${key} = ${value}`).join("; ");
|
|
556
|
+
break;
|
|
557
|
+
case "cmd":
|
|
558
|
+
commandBlock = filteredEnvVars.map(([key, value]) => `set ${key}=${value}`).join(" & ");
|
|
559
|
+
break;
|
|
560
|
+
case "fish":
|
|
561
|
+
commandBlock = filteredEnvVars.map(([key, value]) => `set -gx ${key} ${value}`).join("; ");
|
|
562
|
+
break;
|
|
563
|
+
default: {
|
|
564
|
+
const assignments = filteredEnvVars.map(([key, value]) => `${key}=${value}`).join(" ");
|
|
565
|
+
commandBlock = filteredEnvVars.length > 0 ? `export ${assignments}` : "";
|
|
566
|
+
break;
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
if (commandBlock && commandToRun) {
|
|
570
|
+
const separator = shell === "cmd" ? " & " : " && ";
|
|
571
|
+
return `${commandBlock}${separator}${commandToRun}`;
|
|
572
|
+
}
|
|
573
|
+
return commandBlock || commandToRun;
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
//#endregion
|
|
577
|
+
//#region src/lib/approval.ts
|
|
578
|
+
const awaitApproval = async () => {
|
|
579
|
+
const response = await consola.prompt(`Accept incoming request?`, { type: "confirm" });
|
|
580
|
+
if (!response) throw new HTTPError("Request rejected", Response.json({ message: "Request rejected" }, { status: 403 }));
|
|
581
|
+
};
|
|
582
|
+
|
|
583
|
+
//#endregion
|
|
584
|
+
//#region src/lib/rate-limit.ts
|
|
585
|
+
async function checkRateLimit(state$1) {
|
|
586
|
+
if (state$1.rateLimitSeconds === void 0) return;
|
|
587
|
+
const now = Date.now();
|
|
588
|
+
if (!state$1.lastRequestTimestamp) {
|
|
589
|
+
state$1.lastRequestTimestamp = now;
|
|
590
|
+
return;
|
|
591
|
+
}
|
|
592
|
+
const elapsedSeconds = (now - state$1.lastRequestTimestamp) / 1e3;
|
|
593
|
+
if (elapsedSeconds > state$1.rateLimitSeconds) {
|
|
594
|
+
state$1.lastRequestTimestamp = now;
|
|
595
|
+
return;
|
|
596
|
+
}
|
|
597
|
+
const waitTimeSeconds = Math.ceil(state$1.rateLimitSeconds - elapsedSeconds);
|
|
598
|
+
if (!state$1.rateLimitWait) {
|
|
599
|
+
consola.warn(`Rate limit exceeded. Need to wait ${waitTimeSeconds} more seconds.`);
|
|
600
|
+
throw new HTTPError("Rate limit exceeded", Response.json({ message: "Rate limit exceeded" }, { status: 429 }));
|
|
601
|
+
}
|
|
602
|
+
const waitTimeMs = waitTimeSeconds * 1e3;
|
|
603
|
+
consola.warn(`Rate limit reached. Waiting ${waitTimeSeconds} seconds before proceeding...`);
|
|
604
|
+
await sleep(waitTimeMs);
|
|
605
|
+
state$1.lastRequestTimestamp = now;
|
|
606
|
+
consola.info("Rate limit wait completed, proceeding with request");
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
//#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
|
+
};
|
|
637
|
+
|
|
638
|
+
//#endregion
|
|
639
|
+
//#region src/services/copilot/create-chat-completions.ts
|
|
640
|
+
const createChatCompletions = async (payload) => {
|
|
641
|
+
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));
|
|
644
|
+
const headers = {
|
|
645
|
+
...copilotHeaders(state, enableVision),
|
|
646
|
+
"X-Initiator": isAgentCall ? "agent" : "user"
|
|
647
|
+
};
|
|
648
|
+
const controller = new AbortController();
|
|
649
|
+
const timeoutMs = state.timeoutMs ?? 12e4;
|
|
650
|
+
const timeout = setTimeout(() => {
|
|
651
|
+
controller.abort();
|
|
652
|
+
}, timeoutMs);
|
|
653
|
+
try {
|
|
654
|
+
const response = await fetch(`${copilotBaseUrl(state)}/chat/completions`, {
|
|
655
|
+
method: "POST",
|
|
656
|
+
headers,
|
|
657
|
+
body: JSON.stringify(payload),
|
|
658
|
+
signal: controller.signal
|
|
659
|
+
});
|
|
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);
|
|
665
|
+
return await response.json();
|
|
666
|
+
} finally {
|
|
667
|
+
clearTimeout(timeout);
|
|
668
|
+
}
|
|
669
|
+
};
|
|
670
|
+
|
|
671
|
+
//#endregion
|
|
672
|
+
//#region src/routes/chat-completions/handler.ts
|
|
673
|
+
async function handleCompletion$1(c) {
|
|
674
|
+
await checkRateLimit(state);
|
|
675
|
+
let payload = await c.req.json();
|
|
676
|
+
consola.debug("Request payload:", JSON.stringify(payload).slice(-400));
|
|
677
|
+
consola.info("Current token count:", getTokenCount(payload.messages));
|
|
678
|
+
if (state.manualApprove) await awaitApproval();
|
|
679
|
+
if (isNullish(payload.max_tokens)) {
|
|
680
|
+
const selectedModel = state.models?.data.find((model) => model.id === payload.model);
|
|
681
|
+
payload = {
|
|
682
|
+
...payload,
|
|
683
|
+
max_tokens: selectedModel?.capabilities.limits.max_output_tokens
|
|
684
|
+
};
|
|
685
|
+
consola.debug("Set max_tokens to:", JSON.stringify(payload.max_tokens));
|
|
686
|
+
}
|
|
687
|
+
const response = await createChatCompletions(payload);
|
|
688
|
+
if (isNonStreaming$1(response)) {
|
|
689
|
+
consola.debug("Non-streaming response:", JSON.stringify(response));
|
|
690
|
+
return c.json(response);
|
|
691
|
+
}
|
|
692
|
+
consola.debug("Streaming response");
|
|
693
|
+
return streamSSE(c, async (stream) => {
|
|
694
|
+
for await (const chunk of response) {
|
|
695
|
+
consola.debug("Streaming chunk:", JSON.stringify(chunk));
|
|
696
|
+
await stream.writeSSE(chunk);
|
|
697
|
+
}
|
|
698
|
+
});
|
|
699
|
+
}
|
|
700
|
+
const isNonStreaming$1 = (response) => Object.hasOwn(response, "choices");
|
|
701
|
+
|
|
702
|
+
//#endregion
|
|
703
|
+
//#region src/routes/chat-completions/route.ts
|
|
704
|
+
const completionRoutes = new Hono();
|
|
705
|
+
completionRoutes.post("/", async (c) => {
|
|
706
|
+
try {
|
|
707
|
+
return await handleCompletion$1(c);
|
|
708
|
+
} catch (error) {
|
|
709
|
+
return await forwardError(c, error);
|
|
710
|
+
}
|
|
711
|
+
});
|
|
712
|
+
|
|
713
|
+
//#endregion
|
|
714
|
+
//#region src/services/copilot/create-embeddings.ts
|
|
715
|
+
const createEmbeddings = async (payload) => {
|
|
716
|
+
if (!state.copilotToken) throw new Error("Copilot token not found");
|
|
717
|
+
const controller = new AbortController();
|
|
718
|
+
const timeoutMs = state.timeoutMs ?? 12e4;
|
|
719
|
+
const timeout = setTimeout(() => {
|
|
720
|
+
controller.abort();
|
|
721
|
+
}, timeoutMs);
|
|
722
|
+
try {
|
|
723
|
+
const response = await fetch(`${copilotBaseUrl(state)}/embeddings`, {
|
|
724
|
+
method: "POST",
|
|
725
|
+
headers: copilotHeaders(state),
|
|
726
|
+
body: JSON.stringify(payload),
|
|
727
|
+
signal: controller.signal
|
|
728
|
+
});
|
|
729
|
+
if (!response.ok) throw new HTTPError("Failed to create embeddings", response);
|
|
730
|
+
return await response.json();
|
|
731
|
+
} finally {
|
|
732
|
+
clearTimeout(timeout);
|
|
733
|
+
}
|
|
734
|
+
};
|
|
735
|
+
|
|
736
|
+
//#endregion
|
|
737
|
+
//#region src/routes/embeddings/route.ts
|
|
738
|
+
const embeddingRoutes = new Hono();
|
|
739
|
+
embeddingRoutes.post("/", async (c) => {
|
|
740
|
+
try {
|
|
741
|
+
const paylod = await c.req.json();
|
|
742
|
+
const response = await createEmbeddings(paylod);
|
|
743
|
+
return c.json(response);
|
|
744
|
+
} catch (error) {
|
|
745
|
+
return await forwardError(c, error);
|
|
746
|
+
}
|
|
747
|
+
});
|
|
748
|
+
|
|
749
|
+
//#endregion
|
|
750
|
+
//#region src/routes/messages/utils.ts
|
|
751
|
+
function mapOpenAIStopReasonToAnthropic(finishReason) {
|
|
752
|
+
if (finishReason === null) return null;
|
|
753
|
+
const stopReasonMap = {
|
|
754
|
+
stop: "end_turn",
|
|
755
|
+
length: "max_tokens",
|
|
756
|
+
tool_calls: "tool_use",
|
|
757
|
+
content_filter: "end_turn"
|
|
758
|
+
};
|
|
759
|
+
return stopReasonMap[finishReason];
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
//#endregion
|
|
763
|
+
//#region src/routes/messages/non-stream-translation.ts
|
|
764
|
+
function translateToOpenAI(payload) {
|
|
765
|
+
return {
|
|
766
|
+
model: translateModelName(payload.model),
|
|
767
|
+
messages: translateAnthropicMessagesToOpenAI(payload.messages, payload.system),
|
|
768
|
+
max_tokens: payload.max_tokens,
|
|
769
|
+
stop: payload.stop_sequences,
|
|
770
|
+
stream: payload.stream,
|
|
771
|
+
temperature: payload.temperature,
|
|
772
|
+
top_p: payload.top_p,
|
|
773
|
+
user: payload.metadata?.user_id,
|
|
774
|
+
tools: translateAnthropicToolsToOpenAI(payload.tools),
|
|
775
|
+
tool_choice: translateAnthropicToolChoiceToOpenAI(payload.tool_choice)
|
|
776
|
+
};
|
|
777
|
+
}
|
|
778
|
+
function translateModelName(model) {
|
|
779
|
+
if (model.startsWith("claude-sonnet-4-")) return model.replace(/^claude-sonnet-4-.*/, "claude-sonnet-4");
|
|
780
|
+
else if (model.startsWith("claude-opus-")) return model.replace(/^claude-opus-4-.*/, "claude-opus-4");
|
|
781
|
+
return model;
|
|
782
|
+
}
|
|
783
|
+
function translateAnthropicMessagesToOpenAI(anthropicMessages, system) {
|
|
784
|
+
const systemMessages = handleSystemPrompt(system);
|
|
785
|
+
const otherMessages = anthropicMessages.flatMap((message) => message.role === "user" ? handleUserMessage(message) : handleAssistantMessage(message));
|
|
786
|
+
return [...systemMessages, ...otherMessages];
|
|
787
|
+
}
|
|
788
|
+
function handleSystemPrompt(system) {
|
|
789
|
+
if (!system) return [];
|
|
790
|
+
if (typeof system === "string") return [{
|
|
791
|
+
role: "system",
|
|
792
|
+
content: system
|
|
793
|
+
}];
|
|
794
|
+
else {
|
|
795
|
+
const systemText = system.map((block) => block.text).join("\n\n");
|
|
796
|
+
return [{
|
|
797
|
+
role: "system",
|
|
798
|
+
content: systemText
|
|
799
|
+
}];
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
function handleUserMessage(message) {
|
|
803
|
+
const newMessages = [];
|
|
804
|
+
if (Array.isArray(message.content)) {
|
|
805
|
+
const toolResultBlocks = message.content.filter((block) => block.type === "tool_result");
|
|
806
|
+
const otherBlocks = message.content.filter((block) => block.type !== "tool_result");
|
|
807
|
+
for (const block of toolResultBlocks) newMessages.push({
|
|
808
|
+
role: "tool",
|
|
809
|
+
tool_call_id: block.tool_use_id,
|
|
810
|
+
content: block.content
|
|
811
|
+
});
|
|
812
|
+
if (otherBlocks.length > 0) newMessages.push({
|
|
813
|
+
role: "user",
|
|
814
|
+
content: mapContent(otherBlocks)
|
|
815
|
+
});
|
|
816
|
+
} else newMessages.push({
|
|
817
|
+
role: "user",
|
|
818
|
+
content: mapContent(message.content)
|
|
819
|
+
});
|
|
820
|
+
return newMessages;
|
|
821
|
+
}
|
|
822
|
+
function handleAssistantMessage(message) {
|
|
823
|
+
if (!Array.isArray(message.content)) return [{
|
|
824
|
+
role: "assistant",
|
|
825
|
+
content: mapContent(message.content)
|
|
826
|
+
}];
|
|
827
|
+
const toolUseBlocks = message.content.filter((block) => block.type === "tool_use");
|
|
828
|
+
const textBlocks = message.content.filter((block) => block.type === "text");
|
|
829
|
+
const thinkingBlocks = message.content.filter((block) => block.type === "thinking");
|
|
830
|
+
const allTextContent = [...textBlocks.map((b) => b.text), ...thinkingBlocks.map((b) => b.thinking)].join("\n\n");
|
|
831
|
+
return toolUseBlocks.length > 0 ? [{
|
|
832
|
+
role: "assistant",
|
|
833
|
+
content: allTextContent || null,
|
|
834
|
+
tool_calls: toolUseBlocks.map((toolUse) => ({
|
|
835
|
+
id: toolUse.id,
|
|
836
|
+
type: "function",
|
|
837
|
+
function: {
|
|
838
|
+
name: toolUse.name,
|
|
839
|
+
arguments: JSON.stringify(toolUse.input)
|
|
840
|
+
}
|
|
841
|
+
}))
|
|
842
|
+
}] : [{
|
|
843
|
+
role: "assistant",
|
|
844
|
+
content: mapContent(message.content)
|
|
845
|
+
}];
|
|
846
|
+
}
|
|
847
|
+
function mapContent(content) {
|
|
848
|
+
if (typeof content === "string") return content;
|
|
849
|
+
if (!Array.isArray(content)) return null;
|
|
850
|
+
const hasImage = content.some((block) => block.type === "image");
|
|
851
|
+
if (!hasImage) return content.filter((block) => block.type === "text" || block.type === "thinking").map((block) => block.type === "text" ? block.text : block.thinking).join("\n\n");
|
|
852
|
+
const contentParts = [];
|
|
853
|
+
for (const block of content) switch (block.type) {
|
|
854
|
+
case "text":
|
|
855
|
+
contentParts.push({
|
|
856
|
+
type: "text",
|
|
857
|
+
text: block.text
|
|
858
|
+
});
|
|
859
|
+
break;
|
|
860
|
+
case "thinking":
|
|
861
|
+
contentParts.push({
|
|
862
|
+
type: "text",
|
|
863
|
+
text: block.thinking
|
|
864
|
+
});
|
|
865
|
+
break;
|
|
866
|
+
case "image":
|
|
867
|
+
contentParts.push({
|
|
868
|
+
type: "image_url",
|
|
869
|
+
image_url: { url: `data:${block.source.media_type};base64,${block.source.data}` }
|
|
870
|
+
});
|
|
871
|
+
break;
|
|
872
|
+
}
|
|
873
|
+
return contentParts;
|
|
874
|
+
}
|
|
875
|
+
function translateAnthropicToolsToOpenAI(anthropicTools) {
|
|
876
|
+
if (!anthropicTools) return void 0;
|
|
877
|
+
return anthropicTools.map((tool) => ({
|
|
878
|
+
type: "function",
|
|
879
|
+
function: {
|
|
880
|
+
name: tool.name,
|
|
881
|
+
description: tool.description,
|
|
882
|
+
parameters: tool.input_schema
|
|
883
|
+
}
|
|
884
|
+
}));
|
|
885
|
+
}
|
|
886
|
+
function translateAnthropicToolChoiceToOpenAI(anthropicToolChoice) {
|
|
887
|
+
if (!anthropicToolChoice) return void 0;
|
|
888
|
+
switch (anthropicToolChoice.type) {
|
|
889
|
+
case "auto": return "auto";
|
|
890
|
+
case "any": return "required";
|
|
891
|
+
case "tool":
|
|
892
|
+
if (anthropicToolChoice.name) return {
|
|
893
|
+
type: "function",
|
|
894
|
+
function: { name: anthropicToolChoice.name }
|
|
895
|
+
};
|
|
896
|
+
return void 0;
|
|
897
|
+
case "none": return "none";
|
|
898
|
+
default: return void 0;
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
function translateToAnthropic(response) {
|
|
902
|
+
const allTextBlocks = [];
|
|
903
|
+
const allToolUseBlocks = [];
|
|
904
|
+
let stopReason = null;
|
|
905
|
+
stopReason = response.choices[0]?.finish_reason ?? stopReason;
|
|
906
|
+
for (const choice of response.choices) {
|
|
907
|
+
const textBlocks = getAnthropicTextBlocks(choice.message.content);
|
|
908
|
+
const toolUseBlocks = getAnthropicToolUseBlocks(choice.message.tool_calls);
|
|
909
|
+
allTextBlocks.push(...textBlocks);
|
|
910
|
+
allToolUseBlocks.push(...toolUseBlocks);
|
|
911
|
+
if (choice.finish_reason === "tool_calls" || stopReason === "stop") stopReason = choice.finish_reason;
|
|
912
|
+
}
|
|
913
|
+
return {
|
|
914
|
+
id: response.id,
|
|
915
|
+
type: "message",
|
|
916
|
+
role: "assistant",
|
|
917
|
+
model: response.model,
|
|
918
|
+
content: [...allTextBlocks, ...allToolUseBlocks],
|
|
919
|
+
stop_reason: mapOpenAIStopReasonToAnthropic(stopReason),
|
|
920
|
+
stop_sequence: null,
|
|
921
|
+
usage: {
|
|
922
|
+
input_tokens: response.usage?.prompt_tokens ?? 0,
|
|
923
|
+
output_tokens: response.usage?.completion_tokens ?? 0
|
|
924
|
+
}
|
|
925
|
+
};
|
|
926
|
+
}
|
|
927
|
+
function getAnthropicTextBlocks(messageContent) {
|
|
928
|
+
if (typeof messageContent === "string") return [{
|
|
929
|
+
type: "text",
|
|
930
|
+
text: messageContent
|
|
931
|
+
}];
|
|
932
|
+
if (Array.isArray(messageContent)) return messageContent.filter((part) => part.type === "text").map((part) => ({
|
|
933
|
+
type: "text",
|
|
934
|
+
text: part.text
|
|
935
|
+
}));
|
|
936
|
+
return [];
|
|
937
|
+
}
|
|
938
|
+
function getAnthropicToolUseBlocks(toolCalls) {
|
|
939
|
+
if (!toolCalls) return [];
|
|
940
|
+
return toolCalls.map((toolCall) => ({
|
|
941
|
+
type: "tool_use",
|
|
942
|
+
id: toolCall.id,
|
|
943
|
+
name: toolCall.function.name,
|
|
944
|
+
input: JSON.parse(toolCall.function.arguments)
|
|
945
|
+
}));
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
//#endregion
|
|
949
|
+
//#region src/routes/messages/stream-translation.ts
|
|
950
|
+
function isToolBlockOpen(state$1) {
|
|
951
|
+
if (!state$1.contentBlockOpen) return false;
|
|
952
|
+
return Object.values(state$1.toolCalls).some((tc) => tc.anthropicBlockIndex === state$1.contentBlockIndex);
|
|
953
|
+
}
|
|
954
|
+
function translateChunkToAnthropicEvents(chunk, state$1) {
|
|
955
|
+
const events$1 = [];
|
|
956
|
+
if (chunk.choices.length === 0) return events$1;
|
|
957
|
+
const choice = chunk.choices[0];
|
|
958
|
+
const { delta } = choice;
|
|
959
|
+
if (!state$1.messageStartSent) {
|
|
960
|
+
events$1.push({
|
|
961
|
+
type: "message_start",
|
|
962
|
+
message: {
|
|
963
|
+
id: chunk.id,
|
|
964
|
+
type: "message",
|
|
965
|
+
role: "assistant",
|
|
966
|
+
content: [],
|
|
967
|
+
model: chunk.model,
|
|
968
|
+
stop_reason: null,
|
|
969
|
+
stop_sequence: null,
|
|
970
|
+
usage: {
|
|
971
|
+
input_tokens: chunk.usage?.prompt_tokens ?? 0,
|
|
972
|
+
output_tokens: 0
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
});
|
|
976
|
+
state$1.messageStartSent = true;
|
|
977
|
+
}
|
|
978
|
+
if (delta.content) {
|
|
979
|
+
if (isToolBlockOpen(state$1)) {
|
|
980
|
+
events$1.push({
|
|
981
|
+
type: "content_block_stop",
|
|
982
|
+
index: state$1.contentBlockIndex
|
|
983
|
+
});
|
|
984
|
+
state$1.contentBlockIndex++;
|
|
985
|
+
state$1.contentBlockOpen = false;
|
|
986
|
+
}
|
|
987
|
+
if (!state$1.contentBlockOpen) {
|
|
988
|
+
events$1.push({
|
|
989
|
+
type: "content_block_start",
|
|
990
|
+
index: state$1.contentBlockIndex,
|
|
991
|
+
content_block: {
|
|
992
|
+
type: "text",
|
|
993
|
+
text: ""
|
|
994
|
+
}
|
|
995
|
+
});
|
|
996
|
+
state$1.contentBlockOpen = true;
|
|
997
|
+
}
|
|
998
|
+
events$1.push({
|
|
999
|
+
type: "content_block_delta",
|
|
1000
|
+
index: state$1.contentBlockIndex,
|
|
1001
|
+
delta: {
|
|
1002
|
+
type: "text_delta",
|
|
1003
|
+
text: delta.content
|
|
1004
|
+
}
|
|
1005
|
+
});
|
|
1006
|
+
}
|
|
1007
|
+
if (delta.tool_calls) for (const toolCall of delta.tool_calls) {
|
|
1008
|
+
if (toolCall.id && toolCall.function?.name) {
|
|
1009
|
+
if (state$1.contentBlockOpen) {
|
|
1010
|
+
events$1.push({
|
|
1011
|
+
type: "content_block_stop",
|
|
1012
|
+
index: state$1.contentBlockIndex
|
|
1013
|
+
});
|
|
1014
|
+
state$1.contentBlockIndex++;
|
|
1015
|
+
state$1.contentBlockOpen = false;
|
|
1016
|
+
}
|
|
1017
|
+
const anthropicBlockIndex = state$1.contentBlockIndex;
|
|
1018
|
+
state$1.toolCalls[toolCall.index] = {
|
|
1019
|
+
id: toolCall.id,
|
|
1020
|
+
name: toolCall.function.name,
|
|
1021
|
+
anthropicBlockIndex
|
|
1022
|
+
};
|
|
1023
|
+
events$1.push({
|
|
1024
|
+
type: "content_block_start",
|
|
1025
|
+
index: anthropicBlockIndex,
|
|
1026
|
+
content_block: {
|
|
1027
|
+
type: "tool_use",
|
|
1028
|
+
id: toolCall.id,
|
|
1029
|
+
name: toolCall.function.name,
|
|
1030
|
+
input: {}
|
|
1031
|
+
}
|
|
1032
|
+
});
|
|
1033
|
+
state$1.contentBlockOpen = true;
|
|
1034
|
+
}
|
|
1035
|
+
if (toolCall.function?.arguments) {
|
|
1036
|
+
const toolCallInfo = state$1.toolCalls[toolCall.index];
|
|
1037
|
+
if (toolCallInfo) events$1.push({
|
|
1038
|
+
type: "content_block_delta",
|
|
1039
|
+
index: toolCallInfo.anthropicBlockIndex,
|
|
1040
|
+
delta: {
|
|
1041
|
+
type: "input_json_delta",
|
|
1042
|
+
partial_json: toolCall.function.arguments
|
|
1043
|
+
}
|
|
1044
|
+
});
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
if (choice.finish_reason) {
|
|
1048
|
+
if (state$1.contentBlockOpen) {
|
|
1049
|
+
events$1.push({
|
|
1050
|
+
type: "content_block_stop",
|
|
1051
|
+
index: state$1.contentBlockIndex
|
|
1052
|
+
});
|
|
1053
|
+
state$1.contentBlockOpen = false;
|
|
1054
|
+
}
|
|
1055
|
+
events$1.push({
|
|
1056
|
+
type: "message_delta",
|
|
1057
|
+
delta: {
|
|
1058
|
+
stop_reason: mapOpenAIStopReasonToAnthropic(choice.finish_reason),
|
|
1059
|
+
stop_sequence: null
|
|
1060
|
+
},
|
|
1061
|
+
usage: {
|
|
1062
|
+
input_tokens: chunk.usage?.prompt_tokens ?? 0,
|
|
1063
|
+
output_tokens: chunk.usage?.completion_tokens ?? 0,
|
|
1064
|
+
...chunk.usage?.prompt_tokens_details?.cached_tokens !== void 0 && { cache_read_input_tokens: chunk.usage.prompt_tokens_details.cached_tokens }
|
|
1065
|
+
}
|
|
1066
|
+
}, { type: "message_stop" });
|
|
1067
|
+
}
|
|
1068
|
+
return events$1;
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
//#endregion
|
|
1072
|
+
//#region src/routes/messages/handler.ts
|
|
1073
|
+
async function handleCompletion(c) {
|
|
1074
|
+
await checkRateLimit(state);
|
|
1075
|
+
const anthropicPayload = await c.req.json();
|
|
1076
|
+
consola.debug("Anthropic request payload:", JSON.stringify(anthropicPayload));
|
|
1077
|
+
const openAIPayload = translateToOpenAI(anthropicPayload);
|
|
1078
|
+
consola.debug("Translated OpenAI request payload:", JSON.stringify(openAIPayload));
|
|
1079
|
+
if (state.manualApprove) await awaitApproval();
|
|
1080
|
+
const response = await createChatCompletions(openAIPayload);
|
|
1081
|
+
if (isNonStreaming(response)) {
|
|
1082
|
+
consola.debug("Non-streaming response from Copilot:", JSON.stringify(response).slice(-400));
|
|
1083
|
+
const anthropicResponse = translateToAnthropic(response);
|
|
1084
|
+
consola.debug("Translated Anthropic response:", JSON.stringify(anthropicResponse));
|
|
1085
|
+
return c.json(anthropicResponse);
|
|
1086
|
+
}
|
|
1087
|
+
consola.debug("Streaming response from Copilot");
|
|
1088
|
+
return streamSSE(c, async (stream) => {
|
|
1089
|
+
const streamState = {
|
|
1090
|
+
messageStartSent: false,
|
|
1091
|
+
contentBlockIndex: 0,
|
|
1092
|
+
contentBlockOpen: false,
|
|
1093
|
+
toolCalls: {}
|
|
1094
|
+
};
|
|
1095
|
+
for await (const rawEvent of response) {
|
|
1096
|
+
consola.debug("Copilot raw stream event:", JSON.stringify(rawEvent));
|
|
1097
|
+
if (rawEvent.data === "[DONE]") break;
|
|
1098
|
+
if (!rawEvent.data) continue;
|
|
1099
|
+
const chunk = JSON.parse(rawEvent.data);
|
|
1100
|
+
const events$1 = translateChunkToAnthropicEvents(chunk, streamState);
|
|
1101
|
+
for (const event of events$1) {
|
|
1102
|
+
consola.debug("Translated Anthropic event:", JSON.stringify(event));
|
|
1103
|
+
await stream.writeSSE({
|
|
1104
|
+
event: event.type,
|
|
1105
|
+
data: JSON.stringify(event)
|
|
1106
|
+
});
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
});
|
|
1110
|
+
}
|
|
1111
|
+
const isNonStreaming = (response) => Object.hasOwn(response, "choices");
|
|
1112
|
+
|
|
1113
|
+
//#endregion
|
|
1114
|
+
//#region src/routes/messages/route.ts
|
|
1115
|
+
const messageRoutes = new Hono();
|
|
1116
|
+
messageRoutes.post("/", async (c) => {
|
|
1117
|
+
try {
|
|
1118
|
+
return await handleCompletion(c);
|
|
1119
|
+
} catch (error) {
|
|
1120
|
+
return await forwardError(c, error);
|
|
1121
|
+
}
|
|
1122
|
+
});
|
|
1123
|
+
|
|
1124
|
+
//#endregion
|
|
1125
|
+
//#region src/routes/models/route.ts
|
|
1126
|
+
const modelRoutes = new Hono();
|
|
1127
|
+
modelRoutes.get("/", async (c) => {
|
|
1128
|
+
try {
|
|
1129
|
+
if (!state.models) await cacheModels();
|
|
1130
|
+
const models = state.models?.data.map((model) => ({
|
|
1131
|
+
id: model.id,
|
|
1132
|
+
object: "model",
|
|
1133
|
+
type: "model",
|
|
1134
|
+
created: 0,
|
|
1135
|
+
created_at: (/* @__PURE__ */ new Date(0)).toISOString(),
|
|
1136
|
+
owned_by: model.vendor,
|
|
1137
|
+
display_name: model.name
|
|
1138
|
+
}));
|
|
1139
|
+
return c.json({
|
|
1140
|
+
object: "list",
|
|
1141
|
+
data: models,
|
|
1142
|
+
has_more: false
|
|
1143
|
+
});
|
|
1144
|
+
} catch (error) {
|
|
1145
|
+
return await forwardError(c, error);
|
|
1146
|
+
}
|
|
1147
|
+
});
|
|
1148
|
+
|
|
1149
|
+
//#endregion
|
|
1150
|
+
//#region src/routes/token/route.ts
|
|
1151
|
+
const tokenRoute = new Hono();
|
|
1152
|
+
tokenRoute.get("/", (c) => {
|
|
1153
|
+
try {
|
|
1154
|
+
return c.json({ token: state.copilotToken });
|
|
1155
|
+
} catch (error) {
|
|
1156
|
+
console.error("Error fetching token:", error);
|
|
1157
|
+
return c.json({
|
|
1158
|
+
error: "Failed to fetch token",
|
|
1159
|
+
token: null
|
|
1160
|
+
}, 500);
|
|
1161
|
+
}
|
|
1162
|
+
});
|
|
1163
|
+
|
|
1164
|
+
//#endregion
|
|
1165
|
+
//#region src/routes/usage/route.ts
|
|
1166
|
+
const usageRoute = new Hono();
|
|
1167
|
+
usageRoute.get("/", async (c) => {
|
|
1168
|
+
try {
|
|
1169
|
+
const usage = await getCopilotUsage();
|
|
1170
|
+
return c.json(usage);
|
|
1171
|
+
} catch (error) {
|
|
1172
|
+
console.error("Error fetching Copilot usage:", error);
|
|
1173
|
+
return c.json({ error: "Failed to fetch Copilot usage" }, 500);
|
|
1174
|
+
}
|
|
1175
|
+
});
|
|
1176
|
+
|
|
1177
|
+
//#endregion
|
|
1178
|
+
//#region src/server.ts
|
|
1179
|
+
const server = new Hono();
|
|
1180
|
+
server.use(logger());
|
|
1181
|
+
server.use(cors());
|
|
1182
|
+
server.get("/", (c) => c.text("Server running"));
|
|
1183
|
+
server.route("/chat/completions", completionRoutes);
|
|
1184
|
+
server.route("/models", modelRoutes);
|
|
1185
|
+
server.route("/embeddings", embeddingRoutes);
|
|
1186
|
+
server.route("/usage", usageRoute);
|
|
1187
|
+
server.route("/token", tokenRoute);
|
|
1188
|
+
server.route("/v1/chat/completions", completionRoutes);
|
|
1189
|
+
server.route("/v1/models", modelRoutes);
|
|
1190
|
+
server.route("/v1/embeddings", embeddingRoutes);
|
|
1191
|
+
server.route("/v1/messages", messageRoutes);
|
|
1192
|
+
server.post("/v1/messages/count_tokens", (c) => c.json({ input_tokens: 1 }));
|
|
1193
|
+
|
|
1194
|
+
//#endregion
|
|
1195
|
+
//#region src/start.ts
|
|
1196
|
+
async function runServer(options) {
|
|
1197
|
+
if (options.verbose) {
|
|
1198
|
+
consola.level = 5;
|
|
1199
|
+
consola.info("Verbose logging enabled");
|
|
1200
|
+
}
|
|
1201
|
+
state.accountType = options.accountType;
|
|
1202
|
+
if (options.accountType !== "individual") consola.info(`Using ${options.accountType} plan GitHub account`);
|
|
1203
|
+
state.manualApprove = options.manual;
|
|
1204
|
+
state.rateLimitSeconds = options.rateLimit;
|
|
1205
|
+
state.rateLimitWait = options.rateLimitWait;
|
|
1206
|
+
state.showToken = options.showToken;
|
|
1207
|
+
state.timeoutMs = options.timeout;
|
|
1208
|
+
await ensurePaths();
|
|
1209
|
+
await cacheVSCodeVersion();
|
|
1210
|
+
if (options.githubToken) {
|
|
1211
|
+
state.githubToken = options.githubToken;
|
|
1212
|
+
consola.info("Using provided GitHub token");
|
|
1213
|
+
} else await setupGitHubToken();
|
|
1214
|
+
await setupCopilotToken();
|
|
1215
|
+
await cacheModels();
|
|
1216
|
+
consola.info(`Available models: \n${state.models?.data.map((model) => `- ${model.id}`).join("\n")}`);
|
|
1217
|
+
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
|
+
}
|
|
1265
|
+
consola.box(`🌐 Usage Viewer: https://ericc-ch.github.io/copilot-api?endpoint=${serverUrl}/usage`);
|
|
1266
|
+
serve({
|
|
1267
|
+
fetch: server.fetch,
|
|
1268
|
+
port: options.port
|
|
1269
|
+
});
|
|
1270
|
+
}
|
|
1271
|
+
const start = defineCommand({
|
|
1272
|
+
meta: {
|
|
1273
|
+
name: "start",
|
|
1274
|
+
description: "Start the Copilot API server"
|
|
1275
|
+
},
|
|
1276
|
+
args: {
|
|
1277
|
+
port: {
|
|
1278
|
+
alias: "p",
|
|
1279
|
+
type: "string",
|
|
1280
|
+
default: "4141",
|
|
1281
|
+
description: "Port to listen on"
|
|
1282
|
+
},
|
|
1283
|
+
verbose: {
|
|
1284
|
+
alias: "v",
|
|
1285
|
+
type: "boolean",
|
|
1286
|
+
default: false,
|
|
1287
|
+
description: "Enable verbose logging"
|
|
1288
|
+
},
|
|
1289
|
+
"account-type": {
|
|
1290
|
+
alias: "a",
|
|
1291
|
+
type: "string",
|
|
1292
|
+
default: "individual",
|
|
1293
|
+
description: "Account type to use (individual, business, enterprise)"
|
|
1294
|
+
},
|
|
1295
|
+
manual: {
|
|
1296
|
+
type: "boolean",
|
|
1297
|
+
default: false,
|
|
1298
|
+
description: "Enable manual request approval"
|
|
1299
|
+
},
|
|
1300
|
+
"rate-limit": {
|
|
1301
|
+
alias: "r",
|
|
1302
|
+
type: "string",
|
|
1303
|
+
description: "Rate limit in seconds between requests"
|
|
1304
|
+
},
|
|
1305
|
+
wait: {
|
|
1306
|
+
alias: "w",
|
|
1307
|
+
type: "boolean",
|
|
1308
|
+
default: false,
|
|
1309
|
+
description: "Wait instead of error when rate limit is hit. Has no effect if rate limit is not set"
|
|
1310
|
+
},
|
|
1311
|
+
"github-token": {
|
|
1312
|
+
alias: "g",
|
|
1313
|
+
type: "string",
|
|
1314
|
+
description: "Provide GitHub token directly (must be generated using the `auth` subcommand)"
|
|
1315
|
+
},
|
|
1316
|
+
"claude-code": {
|
|
1317
|
+
alias: "c",
|
|
1318
|
+
type: "boolean",
|
|
1319
|
+
default: false,
|
|
1320
|
+
description: "Generate a command to launch Claude Code with Copilot API config"
|
|
1321
|
+
},
|
|
1322
|
+
model: {
|
|
1323
|
+
alias: "m",
|
|
1324
|
+
type: "string",
|
|
1325
|
+
description: "Model to use with Claude Code (requires --claude-code)"
|
|
1326
|
+
},
|
|
1327
|
+
"small-model": {
|
|
1328
|
+
alias: "s",
|
|
1329
|
+
type: "string",
|
|
1330
|
+
description: "Small/fast model to use with Claude Code (requires --claude-code)"
|
|
1331
|
+
},
|
|
1332
|
+
"show-token": {
|
|
1333
|
+
type: "boolean",
|
|
1334
|
+
default: false,
|
|
1335
|
+
description: "Show GitHub and Copilot tokens on fetch and refresh"
|
|
1336
|
+
},
|
|
1337
|
+
timeout: {
|
|
1338
|
+
alias: "t",
|
|
1339
|
+
type: "string",
|
|
1340
|
+
description: "API timeout in milliseconds (default: 120000)"
|
|
1341
|
+
}
|
|
1342
|
+
},
|
|
1343
|
+
run({ args }) {
|
|
1344
|
+
const rateLimitRaw = args["rate-limit"];
|
|
1345
|
+
const rateLimit = rateLimitRaw === void 0 ? void 0 : Number.parseInt(rateLimitRaw, 10);
|
|
1346
|
+
const timeoutRaw = args.timeout;
|
|
1347
|
+
const timeout = timeoutRaw === void 0 ? 12e4 : Number.parseInt(timeoutRaw, 10);
|
|
1348
|
+
return runServer({
|
|
1349
|
+
port: Number.parseInt(args.port, 10),
|
|
1350
|
+
verbose: args.verbose,
|
|
1351
|
+
accountType: args["account-type"],
|
|
1352
|
+
manual: args.manual,
|
|
1353
|
+
rateLimit,
|
|
1354
|
+
rateLimitWait: args.wait,
|
|
1355
|
+
githubToken: args["github-token"],
|
|
1356
|
+
claudeCode: args["claude-code"],
|
|
1357
|
+
model: args.model,
|
|
1358
|
+
smallModel: args["small-model"],
|
|
1359
|
+
showToken: args["show-token"],
|
|
1360
|
+
timeout
|
|
1361
|
+
});
|
|
1362
|
+
}
|
|
1363
|
+
});
|
|
1364
|
+
|
|
1365
|
+
//#endregion
|
|
1366
|
+
//#region src/main.ts
|
|
1367
|
+
const main = defineCommand({
|
|
1368
|
+
meta: {
|
|
1369
|
+
name: "copilot-api",
|
|
1370
|
+
description: "A wrapper around GitHub Copilot API to make it OpenAI compatible, making it usable for other tools."
|
|
1371
|
+
},
|
|
1372
|
+
subCommands: {
|
|
1373
|
+
auth,
|
|
1374
|
+
start,
|
|
1375
|
+
"check-usage": checkUsage,
|
|
1376
|
+
debug
|
|
1377
|
+
}
|
|
1378
|
+
});
|
|
1379
|
+
await runMain(main);
|
|
1380
|
+
|
|
1381
|
+
//#endregion
|
|
1382
|
+
export { };
|
|
1383
|
+
//# sourceMappingURL=main.js.map
|