heibaiapi 0.6.1
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 +348 -0
- package/dist/main.js +2473 -0
- package/dist/main.js.map +1 -0
- package/package.json +68 -0
package/dist/main.js
ADDED
@@ -0,0 +1,2473 @@
|
|
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 { getProxyForUrl } from "proxy-from-env";
|
9
|
+
import { Agent, ProxyAgent, setGlobalDispatcher } from "undici";
|
10
|
+
import clipboard from "clipboardy";
|
11
|
+
import { serve } from "srvx";
|
12
|
+
import invariant from "tiny-invariant";
|
13
|
+
import { execSync } from "node:child_process";
|
14
|
+
import process$1 from "node:process";
|
15
|
+
import { Hono } from "hono";
|
16
|
+
import { cors } from "hono/cors";
|
17
|
+
import { logger } from "hono/logger";
|
18
|
+
import { streamSSE } from "hono/streaming";
|
19
|
+
import { events } from "fetch-event-stream";
|
20
|
+
|
21
|
+
//#region src/lib/paths.ts
|
22
|
+
const APP_DIR = path.join(os.homedir(), ".local", "share", "heibaiapi");
|
23
|
+
const GITHUB_TOKEN_PATH = path.join(APP_DIR, "github_token");
|
24
|
+
const PATHS = {
|
25
|
+
APP_DIR,
|
26
|
+
GITHUB_TOKEN_PATH
|
27
|
+
};
|
28
|
+
async function ensurePaths() {
|
29
|
+
await fs.mkdir(PATHS.APP_DIR, { recursive: true });
|
30
|
+
await ensureFile(PATHS.GITHUB_TOKEN_PATH);
|
31
|
+
}
|
32
|
+
async function ensureFile(filePath) {
|
33
|
+
try {
|
34
|
+
await fs.access(filePath, fs.constants.W_OK);
|
35
|
+
} catch {
|
36
|
+
await fs.writeFile(filePath, "");
|
37
|
+
await fs.chmod(filePath, 384);
|
38
|
+
}
|
39
|
+
}
|
40
|
+
|
41
|
+
//#endregion
|
42
|
+
//#region src/lib/state.ts
|
43
|
+
const state = {
|
44
|
+
accountType: "individual",
|
45
|
+
manualApprove: false,
|
46
|
+
rateLimitWait: false,
|
47
|
+
showToken: false
|
48
|
+
};
|
49
|
+
|
50
|
+
//#endregion
|
51
|
+
//#region src/lib/api-config.ts
|
52
|
+
const standardHeaders = () => ({
|
53
|
+
"content-type": "application/json",
|
54
|
+
accept: "application/json"
|
55
|
+
});
|
56
|
+
const COPILOT_VERSION = "0.26.7";
|
57
|
+
const EDITOR_PLUGIN_VERSION = `copilot-chat/${COPILOT_VERSION}`;
|
58
|
+
const USER_AGENT = `GitHubCopilotChat/${COPILOT_VERSION}`;
|
59
|
+
const API_VERSION = "2025-04-01";
|
60
|
+
const copilotBaseUrl = (state$1) => state$1.accountType === "individual" ? "https://api.githubcopilot.com" : `https://api.${state$1.accountType}.githubcopilot.com`;
|
61
|
+
const copilotHeaders = (state$1, vision = false) => {
|
62
|
+
const headers = {
|
63
|
+
Authorization: `Bearer ${state$1.copilotToken}`,
|
64
|
+
"content-type": standardHeaders()["content-type"],
|
65
|
+
"copilot-integration-id": "vscode-chat",
|
66
|
+
"editor-version": `vscode/${state$1.vsCodeVersion}`,
|
67
|
+
"editor-plugin-version": EDITOR_PLUGIN_VERSION,
|
68
|
+
"user-agent": USER_AGENT,
|
69
|
+
"openai-intent": "conversation-panel",
|
70
|
+
"x-github-api-version": API_VERSION,
|
71
|
+
"x-request-id": randomUUID(),
|
72
|
+
"x-vscode-user-agent-library-version": "electron-fetch"
|
73
|
+
};
|
74
|
+
if (vision) headers["copilot-vision-request"] = "true";
|
75
|
+
return headers;
|
76
|
+
};
|
77
|
+
const GITHUB_API_BASE_URL = "https://api.github.com";
|
78
|
+
const githubHeaders = (state$1) => ({
|
79
|
+
...standardHeaders(),
|
80
|
+
authorization: `token ${state$1.githubToken}`,
|
81
|
+
"editor-version": `vscode/${state$1.vsCodeVersion}`,
|
82
|
+
"editor-plugin-version": EDITOR_PLUGIN_VERSION,
|
83
|
+
"user-agent": USER_AGENT,
|
84
|
+
"x-github-api-version": API_VERSION,
|
85
|
+
"x-vscode-user-agent-library-version": "electron-fetch"
|
86
|
+
});
|
87
|
+
const GITHUB_BASE_URL = "https://github.com";
|
88
|
+
const GITHUB_CLIENT_ID = "Iv1.b507a08c87ecfe98";
|
89
|
+
const GITHUB_APP_SCOPES = ["read:user"].join(" ");
|
90
|
+
|
91
|
+
//#endregion
|
92
|
+
//#region src/lib/error.ts
|
93
|
+
var HTTPError = class extends Error {
|
94
|
+
response;
|
95
|
+
constructor(message, response) {
|
96
|
+
super(message);
|
97
|
+
this.response = response;
|
98
|
+
}
|
99
|
+
};
|
100
|
+
async function forwardError(c, error) {
|
101
|
+
consola.error("Error occurred:", error);
|
102
|
+
if (error instanceof HTTPError) {
|
103
|
+
const errorText = await error.response.text();
|
104
|
+
let errorJson;
|
105
|
+
try {
|
106
|
+
errorJson = JSON.parse(errorText);
|
107
|
+
} catch {
|
108
|
+
errorJson = errorText;
|
109
|
+
}
|
110
|
+
consola.error("HTTP error:", errorJson);
|
111
|
+
return c.json({ error: {
|
112
|
+
message: errorText,
|
113
|
+
type: "error"
|
114
|
+
} }, error.response.status);
|
115
|
+
}
|
116
|
+
return c.json({ error: {
|
117
|
+
message: error.message,
|
118
|
+
type: "error"
|
119
|
+
} }, 500);
|
120
|
+
}
|
121
|
+
|
122
|
+
//#endregion
|
123
|
+
//#region src/services/github/get-copilot-token.ts
|
124
|
+
const getCopilotToken = async () => {
|
125
|
+
const response = await fetch(`${GITHUB_API_BASE_URL}/copilot_internal/v2/token`, { headers: githubHeaders(state) });
|
126
|
+
if (!response.ok) throw new HTTPError("Failed to get Copilot token", response);
|
127
|
+
return await response.json();
|
128
|
+
};
|
129
|
+
|
130
|
+
//#endregion
|
131
|
+
//#region src/services/github/get-device-code.ts
|
132
|
+
async function getDeviceCode() {
|
133
|
+
const response = await fetch(`${GITHUB_BASE_URL}/login/device/code`, {
|
134
|
+
method: "POST",
|
135
|
+
headers: standardHeaders(),
|
136
|
+
body: JSON.stringify({
|
137
|
+
client_id: GITHUB_CLIENT_ID,
|
138
|
+
scope: GITHUB_APP_SCOPES
|
139
|
+
})
|
140
|
+
});
|
141
|
+
if (!response.ok) throw new HTTPError("Failed to get device code", response);
|
142
|
+
return await response.json();
|
143
|
+
}
|
144
|
+
|
145
|
+
//#endregion
|
146
|
+
//#region src/services/github/get-user.ts
|
147
|
+
async function getGitHubUser() {
|
148
|
+
const response = await fetch(`${GITHUB_API_BASE_URL}/user`, { headers: {
|
149
|
+
authorization: `token ${state.githubToken}`,
|
150
|
+
...standardHeaders()
|
151
|
+
} });
|
152
|
+
if (!response.ok) throw new HTTPError("Failed to get GitHub user", response);
|
153
|
+
return await response.json();
|
154
|
+
}
|
155
|
+
|
156
|
+
//#endregion
|
157
|
+
//#region src/services/copilot/get-models.ts
|
158
|
+
const getModels = async () => {
|
159
|
+
const response = await fetch(`${copilotBaseUrl(state)}/models`, { headers: copilotHeaders(state) });
|
160
|
+
if (!response.ok) throw new HTTPError("Failed to get models", response);
|
161
|
+
return await response.json();
|
162
|
+
};
|
163
|
+
|
164
|
+
//#endregion
|
165
|
+
//#region src/services/get-vscode-version.ts
|
166
|
+
const FALLBACK = "1.104.3";
|
167
|
+
async function getVSCodeVersion() {
|
168
|
+
const controller = new AbortController();
|
169
|
+
const timeout = setTimeout(() => {
|
170
|
+
controller.abort();
|
171
|
+
}, 5e3);
|
172
|
+
try {
|
173
|
+
const match = (await (await fetch("https://aur.archlinux.org/cgit/aur.git/plain/PKGBUILD?h=visual-studio-code-bin", { signal: controller.signal })).text()).match(/pkgver=([0-9.]+)/);
|
174
|
+
if (match) return match[1];
|
175
|
+
return FALLBACK;
|
176
|
+
} catch {
|
177
|
+
return FALLBACK;
|
178
|
+
} finally {
|
179
|
+
clearTimeout(timeout);
|
180
|
+
}
|
181
|
+
}
|
182
|
+
await getVSCodeVersion();
|
183
|
+
|
184
|
+
//#endregion
|
185
|
+
//#region src/lib/utils.ts
|
186
|
+
const sleep = (ms) => new Promise((resolve) => {
|
187
|
+
setTimeout(resolve, ms);
|
188
|
+
});
|
189
|
+
const isNullish = (value) => value === null || value === void 0;
|
190
|
+
async function cacheModels() {
|
191
|
+
state.models = await getModels();
|
192
|
+
}
|
193
|
+
const cacheVSCodeVersion = async () => {
|
194
|
+
const response = await getVSCodeVersion();
|
195
|
+
state.vsCodeVersion = response;
|
196
|
+
consola.info(`Using VSCode version: ${response}`);
|
197
|
+
};
|
198
|
+
|
199
|
+
//#endregion
|
200
|
+
//#region src/services/github/poll-access-token.ts
|
201
|
+
async function pollAccessToken(deviceCode) {
|
202
|
+
const sleepDuration = (deviceCode.interval + 1) * 1e3;
|
203
|
+
consola.debug(`Polling access token with interval of ${sleepDuration}ms`);
|
204
|
+
while (true) {
|
205
|
+
const response = await fetch(`${GITHUB_BASE_URL}/login/oauth/access_token`, {
|
206
|
+
method: "POST",
|
207
|
+
headers: standardHeaders(),
|
208
|
+
body: JSON.stringify({
|
209
|
+
client_id: GITHUB_CLIENT_ID,
|
210
|
+
device_code: deviceCode.device_code,
|
211
|
+
grant_type: "urn:ietf:params:oauth:grant-type:device_code"
|
212
|
+
})
|
213
|
+
});
|
214
|
+
if (!response.ok) {
|
215
|
+
await sleep(sleepDuration);
|
216
|
+
consola.error("Failed to poll access token:", await response.text());
|
217
|
+
continue;
|
218
|
+
}
|
219
|
+
const json = await response.json();
|
220
|
+
consola.debug("Polling access token response:", json);
|
221
|
+
const { access_token } = json;
|
222
|
+
if (access_token) return access_token;
|
223
|
+
else await sleep(sleepDuration);
|
224
|
+
}
|
225
|
+
}
|
226
|
+
|
227
|
+
//#endregion
|
228
|
+
//#region src/lib/token.ts
|
229
|
+
const readGithubToken = () => fs.readFile(PATHS.GITHUB_TOKEN_PATH, "utf8");
|
230
|
+
const writeGithubToken = (token) => fs.writeFile(PATHS.GITHUB_TOKEN_PATH, token);
|
231
|
+
const setupCopilotToken = async () => {
|
232
|
+
const { token, refresh_in } = await getCopilotToken();
|
233
|
+
state.copilotToken = token;
|
234
|
+
consola.debug("GitHub Copilot Token fetched successfully!");
|
235
|
+
if (state.showToken) consola.info("Copilot token:", token);
|
236
|
+
const refreshInterval = (refresh_in - 60) * 1e3;
|
237
|
+
setInterval(async () => {
|
238
|
+
consola.debug("Refreshing Copilot token");
|
239
|
+
try {
|
240
|
+
const { token: token$1 } = await getCopilotToken();
|
241
|
+
state.copilotToken = token$1;
|
242
|
+
consola.debug("Copilot token refreshed");
|
243
|
+
if (state.showToken) consola.info("Refreshed Copilot token:", token$1);
|
244
|
+
} catch (error) {
|
245
|
+
consola.error("Failed to refresh Copilot token:", error);
|
246
|
+
throw error;
|
247
|
+
}
|
248
|
+
}, refreshInterval);
|
249
|
+
};
|
250
|
+
async function setupGitHubToken(options) {
|
251
|
+
try {
|
252
|
+
const githubToken = await readGithubToken();
|
253
|
+
if (githubToken && !options?.force) {
|
254
|
+
state.githubToken = githubToken;
|
255
|
+
if (state.showToken) consola.info("GitHub token:", githubToken);
|
256
|
+
await logUser();
|
257
|
+
return;
|
258
|
+
}
|
259
|
+
consola.info("Not logged in, getting new access token");
|
260
|
+
const response = await getDeviceCode();
|
261
|
+
consola.debug("Device code response:", response);
|
262
|
+
consola.info(`Please enter the code "${response.user_code}" in ${response.verification_uri}`);
|
263
|
+
const token = await pollAccessToken(response);
|
264
|
+
await writeGithubToken(token);
|
265
|
+
state.githubToken = token;
|
266
|
+
if (state.showToken) consola.info("GitHub token:", token);
|
267
|
+
await logUser();
|
268
|
+
} catch (error) {
|
269
|
+
if (error instanceof HTTPError) {
|
270
|
+
consola.error("Failed to get GitHub token:", await error.response.json());
|
271
|
+
throw error;
|
272
|
+
}
|
273
|
+
consola.error("Failed to get GitHub token:", error);
|
274
|
+
throw error;
|
275
|
+
}
|
276
|
+
}
|
277
|
+
async function logUser() {
|
278
|
+
const user = await getGitHubUser();
|
279
|
+
consola.info(`Logged in as ${user.login}`);
|
280
|
+
}
|
281
|
+
|
282
|
+
//#endregion
|
283
|
+
//#region src/auth.ts
|
284
|
+
async function runAuth(options) {
|
285
|
+
if (options.verbose) {
|
286
|
+
consola.level = 5;
|
287
|
+
consola.info("Verbose logging enabled");
|
288
|
+
}
|
289
|
+
state.showToken = options.showToken;
|
290
|
+
await ensurePaths();
|
291
|
+
await setupGitHubToken({ force: true });
|
292
|
+
consola.success("GitHub token written to", PATHS.GITHUB_TOKEN_PATH);
|
293
|
+
}
|
294
|
+
const auth = defineCommand({
|
295
|
+
meta: {
|
296
|
+
name: "auth",
|
297
|
+
description: "Run GitHub auth flow without running the server"
|
298
|
+
},
|
299
|
+
args: {
|
300
|
+
verbose: {
|
301
|
+
alias: "v",
|
302
|
+
type: "boolean",
|
303
|
+
default: false,
|
304
|
+
description: "Enable verbose logging"
|
305
|
+
},
|
306
|
+
"show-token": {
|
307
|
+
type: "boolean",
|
308
|
+
default: false,
|
309
|
+
description: "Show GitHub token on auth"
|
310
|
+
}
|
311
|
+
},
|
312
|
+
run({ args }) {
|
313
|
+
return runAuth({
|
314
|
+
verbose: args.verbose,
|
315
|
+
showToken: args["show-token"]
|
316
|
+
});
|
317
|
+
}
|
318
|
+
});
|
319
|
+
|
320
|
+
//#endregion
|
321
|
+
//#region src/services/github/get-copilot-usage.ts
|
322
|
+
const getCopilotUsage = async () => {
|
323
|
+
const response = await fetch(`${GITHUB_API_BASE_URL}/copilot_internal/user`, { headers: githubHeaders(state) });
|
324
|
+
if (!response.ok) throw new HTTPError("Failed to get Copilot usage", response);
|
325
|
+
return await response.json();
|
326
|
+
};
|
327
|
+
|
328
|
+
//#endregion
|
329
|
+
//#region src/check-usage.ts
|
330
|
+
const checkUsage = defineCommand({
|
331
|
+
meta: {
|
332
|
+
name: "check-usage",
|
333
|
+
description: "Show current GitHub Copilot usage/quota information"
|
334
|
+
},
|
335
|
+
async run() {
|
336
|
+
await ensurePaths();
|
337
|
+
await setupGitHubToken();
|
338
|
+
try {
|
339
|
+
const usage = await getCopilotUsage();
|
340
|
+
const premium = usage.quota_snapshots.premium_interactions;
|
341
|
+
const premiumTotal = premium.entitlement;
|
342
|
+
const premiumUsed = premiumTotal - premium.remaining;
|
343
|
+
const premiumPercentUsed = premiumTotal > 0 ? premiumUsed / premiumTotal * 100 : 0;
|
344
|
+
const premiumPercentRemaining = premium.percent_remaining;
|
345
|
+
function summarizeQuota(name, snap) {
|
346
|
+
if (!snap) return `${name}: N/A`;
|
347
|
+
const total = snap.entitlement;
|
348
|
+
const used = total - snap.remaining;
|
349
|
+
const percentUsed = total > 0 ? used / total * 100 : 0;
|
350
|
+
const percentRemaining = snap.percent_remaining;
|
351
|
+
return `${name}: ${used}/${total} used (${percentUsed.toFixed(1)}% used, ${percentRemaining.toFixed(1)}% remaining)`;
|
352
|
+
}
|
353
|
+
const premiumLine = `Premium: ${premiumUsed}/${premiumTotal} used (${premiumPercentUsed.toFixed(1)}% used, ${premiumPercentRemaining.toFixed(1)}% remaining)`;
|
354
|
+
const chatLine = summarizeQuota("Chat", usage.quota_snapshots.chat);
|
355
|
+
const completionsLine = summarizeQuota("Completions", usage.quota_snapshots.completions);
|
356
|
+
consola.box(`Copilot Usage (plan: ${usage.copilot_plan})\nQuota resets: ${usage.quota_reset_date}\n\nQuotas:\n ${premiumLine}\n ${chatLine}\n ${completionsLine}`);
|
357
|
+
} catch (err) {
|
358
|
+
consola.error("Failed to fetch Copilot usage:", err);
|
359
|
+
process.exit(1);
|
360
|
+
}
|
361
|
+
}
|
362
|
+
});
|
363
|
+
|
364
|
+
//#endregion
|
365
|
+
//#region src/debug.ts
|
366
|
+
async function getPackageVersion() {
|
367
|
+
try {
|
368
|
+
const packageJsonPath = new URL("../package.json", import.meta.url).pathname;
|
369
|
+
return JSON.parse(await fs.readFile(packageJsonPath)).version;
|
370
|
+
} catch {
|
371
|
+
return "unknown";
|
372
|
+
}
|
373
|
+
}
|
374
|
+
function getRuntimeInfo() {
|
375
|
+
const isBun = typeof Bun !== "undefined";
|
376
|
+
return {
|
377
|
+
name: isBun ? "bun" : "node",
|
378
|
+
version: isBun ? Bun.version : process.version.slice(1),
|
379
|
+
platform: os.platform(),
|
380
|
+
arch: os.arch()
|
381
|
+
};
|
382
|
+
}
|
383
|
+
async function checkTokenExists() {
|
384
|
+
try {
|
385
|
+
if (!(await fs.stat(PATHS.GITHUB_TOKEN_PATH)).isFile()) return false;
|
386
|
+
return (await fs.readFile(PATHS.GITHUB_TOKEN_PATH, "utf8")).trim().length > 0;
|
387
|
+
} catch {
|
388
|
+
return false;
|
389
|
+
}
|
390
|
+
}
|
391
|
+
async function getDebugInfo() {
|
392
|
+
const [version, tokenExists] = await Promise.all([getPackageVersion(), checkTokenExists()]);
|
393
|
+
return {
|
394
|
+
version,
|
395
|
+
runtime: getRuntimeInfo(),
|
396
|
+
paths: {
|
397
|
+
APP_DIR: PATHS.APP_DIR,
|
398
|
+
GITHUB_TOKEN_PATH: PATHS.GITHUB_TOKEN_PATH
|
399
|
+
},
|
400
|
+
tokenExists
|
401
|
+
};
|
402
|
+
}
|
403
|
+
function printDebugInfoPlain(info) {
|
404
|
+
consola.info(`heibaiapi debug
|
405
|
+
|
406
|
+
Version: ${info.version}
|
407
|
+
Runtime: ${info.runtime.name} ${info.runtime.version} (${info.runtime.platform} ${info.runtime.arch})
|
408
|
+
|
409
|
+
Paths:
|
410
|
+
- APP_DIR: ${info.paths.APP_DIR}
|
411
|
+
- GITHUB_TOKEN_PATH: ${info.paths.GITHUB_TOKEN_PATH}
|
412
|
+
|
413
|
+
Token exists: ${info.tokenExists ? "Yes" : "No"}`);
|
414
|
+
}
|
415
|
+
function printDebugInfoJson(info) {
|
416
|
+
console.log(JSON.stringify(info, null, 2));
|
417
|
+
}
|
418
|
+
async function runDebug(options) {
|
419
|
+
const debugInfo = await getDebugInfo();
|
420
|
+
if (options.json) printDebugInfoJson(debugInfo);
|
421
|
+
else printDebugInfoPlain(debugInfo);
|
422
|
+
}
|
423
|
+
const debug = defineCommand({
|
424
|
+
meta: {
|
425
|
+
name: "debug",
|
426
|
+
description: "Print debug information about the application"
|
427
|
+
},
|
428
|
+
args: { json: {
|
429
|
+
type: "boolean",
|
430
|
+
default: false,
|
431
|
+
description: "Output debug information as JSON"
|
432
|
+
} },
|
433
|
+
run({ args }) {
|
434
|
+
return runDebug({ json: args.json });
|
435
|
+
}
|
436
|
+
});
|
437
|
+
|
438
|
+
//#endregion
|
439
|
+
//#region src/lib/proxy.ts
|
440
|
+
function initProxyFromEnv() {
|
441
|
+
if (typeof Bun !== "undefined") return;
|
442
|
+
try {
|
443
|
+
const direct = new Agent();
|
444
|
+
const proxies = /* @__PURE__ */ new Map();
|
445
|
+
setGlobalDispatcher({
|
446
|
+
dispatch(options, handler) {
|
447
|
+
try {
|
448
|
+
const origin = typeof options.origin === "string" ? new URL(options.origin) : options.origin;
|
449
|
+
const raw = getProxyForUrl(origin.toString());
|
450
|
+
const proxyUrl = raw && raw.length > 0 ? raw : void 0;
|
451
|
+
if (!proxyUrl) {
|
452
|
+
consola.debug(`HTTP proxy bypass: ${origin.hostname}`);
|
453
|
+
return direct.dispatch(options, handler);
|
454
|
+
}
|
455
|
+
let agent = proxies.get(proxyUrl);
|
456
|
+
if (!agent) {
|
457
|
+
agent = new ProxyAgent(proxyUrl);
|
458
|
+
proxies.set(proxyUrl, agent);
|
459
|
+
}
|
460
|
+
let label = proxyUrl;
|
461
|
+
try {
|
462
|
+
const u = new URL(proxyUrl);
|
463
|
+
label = `${u.protocol}//${u.host}`;
|
464
|
+
} catch {}
|
465
|
+
consola.debug(`HTTP proxy route: ${origin.hostname} via ${label}`);
|
466
|
+
return agent.dispatch(options, handler);
|
467
|
+
} catch {
|
468
|
+
return direct.dispatch(options, handler);
|
469
|
+
}
|
470
|
+
},
|
471
|
+
close() {
|
472
|
+
return direct.close();
|
473
|
+
},
|
474
|
+
destroy() {
|
475
|
+
return direct.destroy();
|
476
|
+
}
|
477
|
+
});
|
478
|
+
consola.debug("HTTP proxy configured from environment (per-URL)");
|
479
|
+
} catch (err) {
|
480
|
+
consola.debug("Proxy setup skipped:", err);
|
481
|
+
}
|
482
|
+
}
|
483
|
+
|
484
|
+
//#endregion
|
485
|
+
//#region src/lib/shell.ts
|
486
|
+
function getShell() {
|
487
|
+
const { platform, ppid, env } = process$1;
|
488
|
+
if (platform === "win32") {
|
489
|
+
try {
|
490
|
+
const command = `wmic process get ParentProcessId,Name | findstr "${ppid}"`;
|
491
|
+
if (execSync(command, { stdio: "pipe" }).toString().toLowerCase().includes("powershell.exe")) return "powershell";
|
492
|
+
} catch {
|
493
|
+
return "cmd";
|
494
|
+
}
|
495
|
+
return "cmd";
|
496
|
+
} else {
|
497
|
+
const shellPath = env.SHELL;
|
498
|
+
if (shellPath) {
|
499
|
+
if (shellPath.endsWith("zsh")) return "zsh";
|
500
|
+
if (shellPath.endsWith("fish")) return "fish";
|
501
|
+
if (shellPath.endsWith("bash")) return "bash";
|
502
|
+
}
|
503
|
+
return "sh";
|
504
|
+
}
|
505
|
+
}
|
506
|
+
/**
|
507
|
+
* Generates a copy-pasteable script to set multiple environment variables
|
508
|
+
* and run a subsequent command.
|
509
|
+
* @param {EnvVars} envVars - An object of environment variables to set.
|
510
|
+
* @param {string} commandToRun - The command to run after setting the variables.
|
511
|
+
* @returns {string} The formatted script string.
|
512
|
+
*/
|
513
|
+
function generateEnvScript(envVars, commandToRun = "") {
|
514
|
+
const shell = getShell();
|
515
|
+
const filteredEnvVars = Object.entries(envVars).filter(([, value]) => value !== void 0);
|
516
|
+
let commandBlock;
|
517
|
+
switch (shell) {
|
518
|
+
case "powershell":
|
519
|
+
commandBlock = filteredEnvVars.map(([key, value]) => `$env:${key} = ${value}`).join("; ");
|
520
|
+
break;
|
521
|
+
case "cmd":
|
522
|
+
commandBlock = filteredEnvVars.map(([key, value]) => `set ${key}=${value}`).join(" & ");
|
523
|
+
break;
|
524
|
+
case "fish":
|
525
|
+
commandBlock = filteredEnvVars.map(([key, value]) => `set -gx ${key} ${value}`).join("; ");
|
526
|
+
break;
|
527
|
+
default: {
|
528
|
+
const assignments = filteredEnvVars.map(([key, value]) => `${key}=${value}`).join(" ");
|
529
|
+
commandBlock = filteredEnvVars.length > 0 ? `export ${assignments}` : "";
|
530
|
+
break;
|
531
|
+
}
|
532
|
+
}
|
533
|
+
if (commandBlock && commandToRun) return `${commandBlock}${shell === "cmd" ? " & " : " && "}${commandToRun}`;
|
534
|
+
return commandBlock || commandToRun;
|
535
|
+
}
|
536
|
+
|
537
|
+
//#endregion
|
538
|
+
//#region src/lib/approval.ts
|
539
|
+
const awaitApproval = async () => {
|
540
|
+
if (!await consola.prompt(`Accept incoming request?`, { type: "confirm" })) throw new HTTPError("Request rejected", Response.json({ message: "Request rejected" }, { status: 403 }));
|
541
|
+
};
|
542
|
+
|
543
|
+
//#endregion
|
544
|
+
//#region src/lib/rate-limit.ts
|
545
|
+
async function checkRateLimit(state$1) {
|
546
|
+
if (state$1.rateLimitSeconds === void 0) return;
|
547
|
+
const now = Date.now();
|
548
|
+
if (!state$1.lastRequestTimestamp) {
|
549
|
+
state$1.lastRequestTimestamp = now;
|
550
|
+
return;
|
551
|
+
}
|
552
|
+
const elapsedSeconds = (now - state$1.lastRequestTimestamp) / 1e3;
|
553
|
+
if (elapsedSeconds > state$1.rateLimitSeconds) {
|
554
|
+
state$1.lastRequestTimestamp = now;
|
555
|
+
return;
|
556
|
+
}
|
557
|
+
const waitTimeSeconds = Math.ceil(state$1.rateLimitSeconds - elapsedSeconds);
|
558
|
+
if (!state$1.rateLimitWait) {
|
559
|
+
consola.warn(`Rate limit exceeded. Need to wait ${waitTimeSeconds} more seconds.`);
|
560
|
+
throw new HTTPError("Rate limit exceeded", Response.json({ message: "Rate limit exceeded" }, { status: 429 }));
|
561
|
+
}
|
562
|
+
const waitTimeMs = waitTimeSeconds * 1e3;
|
563
|
+
consola.warn(`Rate limit reached. Waiting ${waitTimeSeconds} seconds before proceeding...`);
|
564
|
+
await sleep(waitTimeMs);
|
565
|
+
state$1.lastRequestTimestamp = now;
|
566
|
+
consola.info("Rate limit wait completed, proceeding with request");
|
567
|
+
}
|
568
|
+
|
569
|
+
//#endregion
|
570
|
+
//#region src/lib/tokenizer.ts
|
571
|
+
const ENCODING_MAP = {
|
572
|
+
o200k_base: () => import("gpt-tokenizer/encoding/o200k_base"),
|
573
|
+
cl100k_base: () => import("gpt-tokenizer/encoding/cl100k_base"),
|
574
|
+
p50k_base: () => import("gpt-tokenizer/encoding/p50k_base"),
|
575
|
+
p50k_edit: () => import("gpt-tokenizer/encoding/p50k_edit"),
|
576
|
+
r50k_base: () => import("gpt-tokenizer/encoding/r50k_base")
|
577
|
+
};
|
578
|
+
const encodingCache = /* @__PURE__ */ new Map();
|
579
|
+
/**
|
580
|
+
* Calculate tokens for tool calls
|
581
|
+
*/
|
582
|
+
const calculateToolCallsTokens = (toolCalls, encoder, constants) => {
|
583
|
+
let tokens = 0;
|
584
|
+
for (const toolCall of toolCalls) {
|
585
|
+
tokens += constants.funcInit;
|
586
|
+
tokens += encoder.encode(JSON.stringify(toolCall)).length;
|
587
|
+
}
|
588
|
+
tokens += constants.funcEnd;
|
589
|
+
return tokens;
|
590
|
+
};
|
591
|
+
/**
|
592
|
+
* Calculate tokens for content parts
|
593
|
+
*/
|
594
|
+
const calculateContentPartsTokens = (contentParts, encoder) => {
|
595
|
+
let tokens = 0;
|
596
|
+
for (const part of contentParts) if (part.type === "image_url") tokens += encoder.encode(part.image_url.url).length + 85;
|
597
|
+
else if (part.text) tokens += encoder.encode(part.text).length;
|
598
|
+
return tokens;
|
599
|
+
};
|
600
|
+
/**
|
601
|
+
* Calculate tokens for a single message
|
602
|
+
*/
|
603
|
+
const calculateMessageTokens = (message, encoder, constants) => {
|
604
|
+
const tokensPerMessage = 3;
|
605
|
+
const tokensPerName = 1;
|
606
|
+
let tokens = tokensPerMessage;
|
607
|
+
for (const [key, value] of Object.entries(message)) {
|
608
|
+
if (typeof value === "string") tokens += encoder.encode(value).length;
|
609
|
+
if (key === "name") tokens += tokensPerName;
|
610
|
+
if (key === "tool_calls") tokens += calculateToolCallsTokens(value, encoder, constants);
|
611
|
+
if (key === "content" && Array.isArray(value)) tokens += calculateContentPartsTokens(value, encoder);
|
612
|
+
}
|
613
|
+
return tokens;
|
614
|
+
};
|
615
|
+
/**
|
616
|
+
* Calculate tokens using custom algorithm
|
617
|
+
*/
|
618
|
+
const calculateTokens = (messages, encoder, constants) => {
|
619
|
+
if (messages.length === 0) return 0;
|
620
|
+
let numTokens = 0;
|
621
|
+
for (const message of messages) numTokens += calculateMessageTokens(message, encoder, constants);
|
622
|
+
numTokens += 3;
|
623
|
+
return numTokens;
|
624
|
+
};
|
625
|
+
/**
|
626
|
+
* Get the corresponding encoder module based on encoding type
|
627
|
+
*/
|
628
|
+
const getEncodeChatFunction = async (encoding) => {
|
629
|
+
if (encodingCache.has(encoding)) {
|
630
|
+
const cached = encodingCache.get(encoding);
|
631
|
+
if (cached) return cached;
|
632
|
+
}
|
633
|
+
const supportedEncoding = encoding;
|
634
|
+
if (!(supportedEncoding in ENCODING_MAP)) {
|
635
|
+
const fallbackModule = await ENCODING_MAP.o200k_base();
|
636
|
+
encodingCache.set(encoding, fallbackModule);
|
637
|
+
return fallbackModule;
|
638
|
+
}
|
639
|
+
const encodingModule = await ENCODING_MAP[supportedEncoding]();
|
640
|
+
encodingCache.set(encoding, encodingModule);
|
641
|
+
return encodingModule;
|
642
|
+
};
|
643
|
+
/**
|
644
|
+
* Get tokenizer type from model information
|
645
|
+
*/
|
646
|
+
const getTokenizerFromModel = (model) => {
|
647
|
+
return model.capabilities.tokenizer || "o200k_base";
|
648
|
+
};
|
649
|
+
/**
|
650
|
+
* Get model-specific constants for token calculation
|
651
|
+
*/
|
652
|
+
const getModelConstants = (model) => {
|
653
|
+
return model.id === "gpt-3.5-turbo" || model.id === "gpt-4" ? {
|
654
|
+
funcInit: 10,
|
655
|
+
propInit: 3,
|
656
|
+
propKey: 3,
|
657
|
+
enumInit: -3,
|
658
|
+
enumItem: 3,
|
659
|
+
funcEnd: 12
|
660
|
+
} : {
|
661
|
+
funcInit: 7,
|
662
|
+
propInit: 3,
|
663
|
+
propKey: 3,
|
664
|
+
enumInit: -3,
|
665
|
+
enumItem: 3,
|
666
|
+
funcEnd: 12
|
667
|
+
};
|
668
|
+
};
|
669
|
+
/**
|
670
|
+
* Calculate tokens for a single parameter
|
671
|
+
*/
|
672
|
+
const calculateParameterTokens = (key, prop, context) => {
|
673
|
+
const { encoder, constants } = context;
|
674
|
+
let tokens = constants.propKey;
|
675
|
+
if (typeof prop !== "object" || prop === null) return tokens;
|
676
|
+
const param = prop;
|
677
|
+
const paramName = key;
|
678
|
+
const paramType = param.type || "string";
|
679
|
+
let paramDesc = param.description || "";
|
680
|
+
if (param.enum && Array.isArray(param.enum)) {
|
681
|
+
tokens += constants.enumInit;
|
682
|
+
for (const item of param.enum) {
|
683
|
+
tokens += constants.enumItem;
|
684
|
+
tokens += encoder.encode(String(item)).length;
|
685
|
+
}
|
686
|
+
}
|
687
|
+
if (paramDesc.endsWith(".")) paramDesc = paramDesc.slice(0, -1);
|
688
|
+
const line = `${paramName}:${paramType}:${paramDesc}`;
|
689
|
+
tokens += encoder.encode(line).length;
|
690
|
+
const excludedKeys = new Set([
|
691
|
+
"type",
|
692
|
+
"description",
|
693
|
+
"enum"
|
694
|
+
]);
|
695
|
+
for (const propertyName of Object.keys(param)) if (!excludedKeys.has(propertyName)) {
|
696
|
+
const propertyValue = param[propertyName];
|
697
|
+
const propertyText = typeof propertyValue === "string" ? propertyValue : JSON.stringify(propertyValue);
|
698
|
+
tokens += encoder.encode(`${propertyName}:${propertyText}`).length;
|
699
|
+
}
|
700
|
+
return tokens;
|
701
|
+
};
|
702
|
+
/**
|
703
|
+
* Calculate tokens for function parameters
|
704
|
+
*/
|
705
|
+
const calculateParametersTokens = (parameters, encoder, constants) => {
|
706
|
+
if (!parameters || typeof parameters !== "object") return 0;
|
707
|
+
const params = parameters;
|
708
|
+
let tokens = 0;
|
709
|
+
for (const [key, value] of Object.entries(params)) if (key === "properties") {
|
710
|
+
const properties = value;
|
711
|
+
if (Object.keys(properties).length > 0) {
|
712
|
+
tokens += constants.propInit;
|
713
|
+
for (const propKey of Object.keys(properties)) tokens += calculateParameterTokens(propKey, properties[propKey], {
|
714
|
+
encoder,
|
715
|
+
constants
|
716
|
+
});
|
717
|
+
}
|
718
|
+
} else {
|
719
|
+
const paramText = typeof value === "string" ? value : JSON.stringify(value);
|
720
|
+
tokens += encoder.encode(`${key}:${paramText}`).length;
|
721
|
+
}
|
722
|
+
return tokens;
|
723
|
+
};
|
724
|
+
/**
|
725
|
+
* Calculate tokens for a single tool
|
726
|
+
*/
|
727
|
+
const calculateToolTokens = (tool, encoder, constants) => {
|
728
|
+
let tokens = constants.funcInit;
|
729
|
+
const func = tool.function;
|
730
|
+
const fName = func.name;
|
731
|
+
let fDesc = func.description || "";
|
732
|
+
if (fDesc.endsWith(".")) fDesc = fDesc.slice(0, -1);
|
733
|
+
const line = fName + ":" + fDesc;
|
734
|
+
tokens += encoder.encode(line).length;
|
735
|
+
if (typeof func.parameters === "object" && func.parameters !== null) tokens += calculateParametersTokens(func.parameters, encoder, constants);
|
736
|
+
return tokens;
|
737
|
+
};
|
738
|
+
/**
|
739
|
+
* Calculate token count for tools based on model
|
740
|
+
*/
|
741
|
+
const numTokensForTools = (tools, encoder, constants) => {
|
742
|
+
let funcTokenCount = 0;
|
743
|
+
for (const tool of tools) funcTokenCount += calculateToolTokens(tool, encoder, constants);
|
744
|
+
funcTokenCount += constants.funcEnd;
|
745
|
+
return funcTokenCount;
|
746
|
+
};
|
747
|
+
/**
|
748
|
+
* Calculate the token count of messages, supporting multiple GPT encoders
|
749
|
+
*/
|
750
|
+
const getTokenCount = async (payload, model) => {
|
751
|
+
const tokenizer = getTokenizerFromModel(model);
|
752
|
+
const encoder = await getEncodeChatFunction(tokenizer);
|
753
|
+
const simplifiedMessages = payload.messages;
|
754
|
+
const inputMessages = simplifiedMessages.filter((msg) => msg.role !== "assistant");
|
755
|
+
const outputMessages = simplifiedMessages.filter((msg) => msg.role === "assistant");
|
756
|
+
const constants = getModelConstants(model);
|
757
|
+
let inputTokens = calculateTokens(inputMessages, encoder, constants);
|
758
|
+
if (payload.tools && payload.tools.length > 0) inputTokens += numTokensForTools(payload.tools, encoder, constants);
|
759
|
+
const outputTokens = calculateTokens(outputMessages, encoder, constants);
|
760
|
+
return {
|
761
|
+
input: inputTokens,
|
762
|
+
output: outputTokens
|
763
|
+
};
|
764
|
+
};
|
765
|
+
|
766
|
+
//#endregion
|
767
|
+
//#region src/services/copilot/create-chat-completions.ts
|
768
|
+
const createChatCompletions = async (payload) => {
|
769
|
+
if (!state.copilotToken) throw new Error("Copilot token not found");
|
770
|
+
const enableVision = payload.messages.some((x) => typeof x.content !== "string" && x.content?.some((x$1) => x$1.type === "image_url"));
|
771
|
+
const isAgentCall = payload.messages.some((msg) => ["assistant", "tool"].includes(msg.role));
|
772
|
+
const headers = {
|
773
|
+
...copilotHeaders(state, enableVision),
|
774
|
+
"X-Initiator": isAgentCall ? "agent" : "user"
|
775
|
+
};
|
776
|
+
const response = await fetch(`${copilotBaseUrl(state)}/chat/completions`, {
|
777
|
+
method: "POST",
|
778
|
+
headers,
|
779
|
+
body: JSON.stringify(payload)
|
780
|
+
});
|
781
|
+
if (!response.ok) {
|
782
|
+
consola.error("Failed to create chat completions", response);
|
783
|
+
throw new HTTPError("Failed to create chat completions", response);
|
784
|
+
}
|
785
|
+
if (payload.stream) return events(response);
|
786
|
+
return await response.json();
|
787
|
+
};
|
788
|
+
|
789
|
+
//#endregion
|
790
|
+
//#region src/routes/chat-completions/handler.ts
|
791
|
+
async function handleCompletion$1(c) {
|
792
|
+
await checkRateLimit(state);
|
793
|
+
let payload = await c.req.json();
|
794
|
+
consola.debug("Request payload:", JSON.stringify(payload).slice(-400));
|
795
|
+
const selectedModel = state.models?.data.find((model) => model.id === payload.model);
|
796
|
+
try {
|
797
|
+
if (selectedModel) {
|
798
|
+
const tokenCount = await getTokenCount(payload, selectedModel);
|
799
|
+
consola.info("Current token count:", tokenCount);
|
800
|
+
} else consola.warn("No model selected, skipping token count calculation");
|
801
|
+
} catch (error) {
|
802
|
+
consola.warn("Failed to calculate token count:", error);
|
803
|
+
}
|
804
|
+
if (state.manualApprove) await awaitApproval();
|
805
|
+
if (isNullish(payload.max_tokens)) {
|
806
|
+
payload = {
|
807
|
+
...payload,
|
808
|
+
max_tokens: selectedModel?.capabilities.limits.max_output_tokens
|
809
|
+
};
|
810
|
+
consola.debug("Set max_tokens to:", JSON.stringify(payload.max_tokens));
|
811
|
+
}
|
812
|
+
const response = await createChatCompletions(payload);
|
813
|
+
if (isNonStreaming$1(response)) {
|
814
|
+
consola.debug("Non-streaming response:", JSON.stringify(response));
|
815
|
+
return c.json(response);
|
816
|
+
}
|
817
|
+
consola.debug("Streaming response");
|
818
|
+
return streamSSE(c, async (stream) => {
|
819
|
+
for await (const chunk of response) {
|
820
|
+
consola.debug("Streaming chunk:", JSON.stringify(chunk));
|
821
|
+
await stream.writeSSE(chunk);
|
822
|
+
}
|
823
|
+
});
|
824
|
+
}
|
825
|
+
const isNonStreaming$1 = (response) => Object.hasOwn(response, "choices");
|
826
|
+
|
827
|
+
//#endregion
|
828
|
+
//#region src/routes/chat-completions/route.ts
|
829
|
+
const completionRoutes = new Hono();
|
830
|
+
completionRoutes.post("/", async (c) => {
|
831
|
+
try {
|
832
|
+
return await handleCompletion$1(c);
|
833
|
+
} catch (error) {
|
834
|
+
return await forwardError(c, error);
|
835
|
+
}
|
836
|
+
});
|
837
|
+
|
838
|
+
//#endregion
|
839
|
+
//#region src/services/copilot/create-embeddings.ts
|
840
|
+
const createEmbeddings = async (payload) => {
|
841
|
+
if (!state.copilotToken) throw new Error("Copilot token not found");
|
842
|
+
const response = await fetch(`${copilotBaseUrl(state)}/embeddings`, {
|
843
|
+
method: "POST",
|
844
|
+
headers: copilotHeaders(state),
|
845
|
+
body: JSON.stringify(payload)
|
846
|
+
});
|
847
|
+
if (!response.ok) throw new HTTPError("Failed to create embeddings", response);
|
848
|
+
return await response.json();
|
849
|
+
};
|
850
|
+
|
851
|
+
//#endregion
|
852
|
+
//#region src/routes/embeddings/route.ts
|
853
|
+
const embeddingRoutes = new Hono();
|
854
|
+
embeddingRoutes.post("/", async (c) => {
|
855
|
+
try {
|
856
|
+
const paylod = await c.req.json();
|
857
|
+
const response = await createEmbeddings(paylod);
|
858
|
+
return c.json(response);
|
859
|
+
} catch (error) {
|
860
|
+
return await forwardError(c, error);
|
861
|
+
}
|
862
|
+
});
|
863
|
+
|
864
|
+
//#endregion
|
865
|
+
//#region src/routes/messages/utils.ts
|
866
|
+
function mapOpenAIStopReasonToAnthropic(finishReason) {
|
867
|
+
if (finishReason === null) return null;
|
868
|
+
return {
|
869
|
+
stop: "end_turn",
|
870
|
+
length: "max_tokens",
|
871
|
+
tool_calls: "tool_use",
|
872
|
+
content_filter: "end_turn"
|
873
|
+
}[finishReason];
|
874
|
+
}
|
875
|
+
|
876
|
+
//#endregion
|
877
|
+
//#region src/routes/messages/non-stream-translation.ts
|
878
|
+
function translateToOpenAI(payload) {
|
879
|
+
return {
|
880
|
+
model: translateModelName(payload.model),
|
881
|
+
messages: translateAnthropicMessagesToOpenAI(payload.messages, payload.system),
|
882
|
+
max_tokens: payload.max_tokens,
|
883
|
+
stop: payload.stop_sequences,
|
884
|
+
stream: payload.stream,
|
885
|
+
temperature: payload.temperature,
|
886
|
+
top_p: payload.top_p,
|
887
|
+
user: payload.metadata?.user_id,
|
888
|
+
tools: translateAnthropicToolsToOpenAI(payload.tools),
|
889
|
+
tool_choice: translateAnthropicToolChoiceToOpenAI(payload.tool_choice)
|
890
|
+
};
|
891
|
+
}
|
892
|
+
function translateModelName(model) {
|
893
|
+
if (model.startsWith("claude-sonnet-4-")) return model.replace(/^claude-sonnet-4-.*/, "claude-sonnet-4");
|
894
|
+
else if (model.startsWith("claude-opus-")) return model.replace(/^claude-opus-4-.*/, "claude-opus-4");
|
895
|
+
return model;
|
896
|
+
}
|
897
|
+
function translateAnthropicMessagesToOpenAI(anthropicMessages, system) {
|
898
|
+
const systemMessages = handleSystemPrompt(system);
|
899
|
+
const otherMessages = anthropicMessages.flatMap((message) => message.role === "user" ? handleUserMessage(message) : handleAssistantMessage(message));
|
900
|
+
return [...systemMessages, ...otherMessages];
|
901
|
+
}
|
902
|
+
function handleSystemPrompt(system) {
|
903
|
+
if (!system) return [];
|
904
|
+
if (typeof system === "string") return [{
|
905
|
+
role: "system",
|
906
|
+
content: system
|
907
|
+
}];
|
908
|
+
else return [{
|
909
|
+
role: "system",
|
910
|
+
content: system.map((block) => block.text).join("\n\n")
|
911
|
+
}];
|
912
|
+
}
|
913
|
+
function handleUserMessage(message) {
|
914
|
+
const newMessages = [];
|
915
|
+
if (Array.isArray(message.content)) {
|
916
|
+
const toolResultBlocks = message.content.filter((block) => block.type === "tool_result");
|
917
|
+
const otherBlocks = message.content.filter((block) => block.type !== "tool_result");
|
918
|
+
for (const block of toolResultBlocks) newMessages.push({
|
919
|
+
role: "tool",
|
920
|
+
tool_call_id: block.tool_use_id,
|
921
|
+
content: mapContent(block.content)
|
922
|
+
});
|
923
|
+
if (otherBlocks.length > 0) newMessages.push({
|
924
|
+
role: "user",
|
925
|
+
content: mapContent(otherBlocks)
|
926
|
+
});
|
927
|
+
} else newMessages.push({
|
928
|
+
role: "user",
|
929
|
+
content: mapContent(message.content)
|
930
|
+
});
|
931
|
+
return newMessages;
|
932
|
+
}
|
933
|
+
function handleAssistantMessage(message) {
|
934
|
+
if (!Array.isArray(message.content)) return [{
|
935
|
+
role: "assistant",
|
936
|
+
content: mapContent(message.content)
|
937
|
+
}];
|
938
|
+
const toolUseBlocks = message.content.filter((block) => block.type === "tool_use");
|
939
|
+
const textBlocks = message.content.filter((block) => block.type === "text");
|
940
|
+
const thinkingBlocks = message.content.filter((block) => block.type === "thinking");
|
941
|
+
const allTextContent = [...textBlocks.map((b) => b.text), ...thinkingBlocks.map((b) => b.thinking)].join("\n\n");
|
942
|
+
return toolUseBlocks.length > 0 ? [{
|
943
|
+
role: "assistant",
|
944
|
+
content: allTextContent || null,
|
945
|
+
tool_calls: toolUseBlocks.map((toolUse) => ({
|
946
|
+
id: toolUse.id,
|
947
|
+
type: "function",
|
948
|
+
function: {
|
949
|
+
name: toolUse.name,
|
950
|
+
arguments: JSON.stringify(toolUse.input)
|
951
|
+
}
|
952
|
+
}))
|
953
|
+
}] : [{
|
954
|
+
role: "assistant",
|
955
|
+
content: mapContent(message.content)
|
956
|
+
}];
|
957
|
+
}
|
958
|
+
function mapContent(content) {
|
959
|
+
if (typeof content === "string") return content;
|
960
|
+
if (!Array.isArray(content)) return null;
|
961
|
+
if (!content.some((block) => block.type === "image")) return content.filter((block) => block.type === "text" || block.type === "thinking").map((block) => block.type === "text" ? block.text : block.thinking).join("\n\n");
|
962
|
+
const contentParts = [];
|
963
|
+
for (const block of content) switch (block.type) {
|
964
|
+
case "text":
|
965
|
+
contentParts.push({
|
966
|
+
type: "text",
|
967
|
+
text: block.text
|
968
|
+
});
|
969
|
+
break;
|
970
|
+
case "thinking":
|
971
|
+
contentParts.push({
|
972
|
+
type: "text",
|
973
|
+
text: block.thinking
|
974
|
+
});
|
975
|
+
break;
|
976
|
+
case "image":
|
977
|
+
contentParts.push({
|
978
|
+
type: "image_url",
|
979
|
+
image_url: { url: `data:${block.source.media_type};base64,${block.source.data}` }
|
980
|
+
});
|
981
|
+
break;
|
982
|
+
}
|
983
|
+
return contentParts;
|
984
|
+
}
|
985
|
+
function translateAnthropicToolsToOpenAI(anthropicTools) {
|
986
|
+
if (!anthropicTools) return;
|
987
|
+
return anthropicTools.map((tool) => ({
|
988
|
+
type: "function",
|
989
|
+
function: {
|
990
|
+
name: tool.name,
|
991
|
+
description: tool.description,
|
992
|
+
parameters: tool.input_schema
|
993
|
+
}
|
994
|
+
}));
|
995
|
+
}
|
996
|
+
function translateAnthropicToolChoiceToOpenAI(anthropicToolChoice) {
|
997
|
+
if (!anthropicToolChoice) return;
|
998
|
+
switch (anthropicToolChoice.type) {
|
999
|
+
case "auto": return "auto";
|
1000
|
+
case "any": return "required";
|
1001
|
+
case "tool":
|
1002
|
+
if (anthropicToolChoice.name) return {
|
1003
|
+
type: "function",
|
1004
|
+
function: { name: anthropicToolChoice.name }
|
1005
|
+
};
|
1006
|
+
return;
|
1007
|
+
case "none": return "none";
|
1008
|
+
default: return;
|
1009
|
+
}
|
1010
|
+
}
|
1011
|
+
function translateToAnthropic(response) {
|
1012
|
+
const allTextBlocks = [];
|
1013
|
+
const allToolUseBlocks = [];
|
1014
|
+
let stopReason = null;
|
1015
|
+
stopReason = response.choices[0]?.finish_reason ?? stopReason;
|
1016
|
+
for (const choice of response.choices) {
|
1017
|
+
const textBlocks = getAnthropicTextBlocks(choice.message.content);
|
1018
|
+
const toolUseBlocks = getAnthropicToolUseBlocks(choice.message.tool_calls);
|
1019
|
+
allTextBlocks.push(...textBlocks);
|
1020
|
+
allToolUseBlocks.push(...toolUseBlocks);
|
1021
|
+
if (choice.finish_reason === "tool_calls" || stopReason === "stop") stopReason = choice.finish_reason;
|
1022
|
+
}
|
1023
|
+
return {
|
1024
|
+
id: response.id,
|
1025
|
+
type: "message",
|
1026
|
+
role: "assistant",
|
1027
|
+
model: response.model,
|
1028
|
+
content: [...allTextBlocks, ...allToolUseBlocks],
|
1029
|
+
stop_reason: mapOpenAIStopReasonToAnthropic(stopReason),
|
1030
|
+
stop_sequence: null,
|
1031
|
+
usage: {
|
1032
|
+
input_tokens: (response.usage?.prompt_tokens ?? 0) - (response.usage?.prompt_tokens_details?.cached_tokens ?? 0),
|
1033
|
+
output_tokens: response.usage?.completion_tokens ?? 0,
|
1034
|
+
...response.usage?.prompt_tokens_details?.cached_tokens !== void 0 && { cache_read_input_tokens: response.usage.prompt_tokens_details.cached_tokens }
|
1035
|
+
}
|
1036
|
+
};
|
1037
|
+
}
|
1038
|
+
function getAnthropicTextBlocks(messageContent) {
|
1039
|
+
if (typeof messageContent === "string") return [{
|
1040
|
+
type: "text",
|
1041
|
+
text: messageContent
|
1042
|
+
}];
|
1043
|
+
if (Array.isArray(messageContent)) return messageContent.filter((part) => part.type === "text").map((part) => ({
|
1044
|
+
type: "text",
|
1045
|
+
text: part.text
|
1046
|
+
}));
|
1047
|
+
return [];
|
1048
|
+
}
|
1049
|
+
function getAnthropicToolUseBlocks(toolCalls) {
|
1050
|
+
if (!toolCalls) return [];
|
1051
|
+
return toolCalls.map((toolCall) => ({
|
1052
|
+
type: "tool_use",
|
1053
|
+
id: toolCall.id,
|
1054
|
+
name: toolCall.function.name,
|
1055
|
+
input: JSON.parse(toolCall.function.arguments)
|
1056
|
+
}));
|
1057
|
+
}
|
1058
|
+
|
1059
|
+
//#endregion
|
1060
|
+
//#region src/routes/messages/count-tokens-handler.ts
|
1061
|
+
/**
|
1062
|
+
* Handles token counting for Anthropic messages
|
1063
|
+
*/
|
1064
|
+
async function handleCountTokens(c) {
|
1065
|
+
try {
|
1066
|
+
const anthropicBeta = c.req.header("anthropic-beta");
|
1067
|
+
const anthropicPayload = await c.req.json();
|
1068
|
+
const openAIPayload = translateToOpenAI(anthropicPayload);
|
1069
|
+
const selectedModel = state.models?.data.find((model) => model.id === anthropicPayload.model);
|
1070
|
+
if (!selectedModel) {
|
1071
|
+
consola.warn("Model not found, returning default token count");
|
1072
|
+
return c.json({ input_tokens: 1 });
|
1073
|
+
}
|
1074
|
+
const tokenCount = await getTokenCount(openAIPayload, selectedModel);
|
1075
|
+
if (anthropicPayload.tools && anthropicPayload.tools.length > 0) {
|
1076
|
+
let mcpToolExist = false;
|
1077
|
+
if (anthropicBeta?.startsWith("claude-code")) mcpToolExist = anthropicPayload.tools.some((tool) => tool.name.startsWith("mcp__"));
|
1078
|
+
if (!mcpToolExist) {
|
1079
|
+
if (anthropicPayload.model.startsWith("claude")) tokenCount.input = tokenCount.input + 346;
|
1080
|
+
else if (anthropicPayload.model.startsWith("grok")) tokenCount.input = tokenCount.input + 480;
|
1081
|
+
}
|
1082
|
+
}
|
1083
|
+
let finalTokenCount = tokenCount.input + tokenCount.output;
|
1084
|
+
if (anthropicPayload.model.startsWith("claude")) finalTokenCount = Math.round(finalTokenCount * 1.15);
|
1085
|
+
else if (anthropicPayload.model.startsWith("grok")) finalTokenCount = Math.round(finalTokenCount * 1.03);
|
1086
|
+
consola.info("Token count:", finalTokenCount);
|
1087
|
+
return c.json({ input_tokens: finalTokenCount });
|
1088
|
+
} catch (error) {
|
1089
|
+
consola.error("Error counting tokens:", error);
|
1090
|
+
return c.json({ input_tokens: 1 });
|
1091
|
+
}
|
1092
|
+
}
|
1093
|
+
|
1094
|
+
//#endregion
|
1095
|
+
//#region src/services/copilot/create-responses.ts
|
1096
|
+
const createResponses = async (payload, { vision, initiator }) => {
|
1097
|
+
if (!state.copilotToken) throw new Error("Copilot token not found");
|
1098
|
+
const headers = {
|
1099
|
+
...copilotHeaders(state, vision),
|
1100
|
+
"X-Initiator": initiator
|
1101
|
+
};
|
1102
|
+
const response = await fetch(`${copilotBaseUrl(state)}/responses`, {
|
1103
|
+
method: "POST",
|
1104
|
+
headers,
|
1105
|
+
body: JSON.stringify(payload)
|
1106
|
+
});
|
1107
|
+
if (!response.ok) {
|
1108
|
+
consola.error("Failed to create responses", response);
|
1109
|
+
throw new HTTPError("Failed to create responses", response);
|
1110
|
+
}
|
1111
|
+
if (payload.stream) return events(response);
|
1112
|
+
return await response.json();
|
1113
|
+
};
|
1114
|
+
|
1115
|
+
//#endregion
|
1116
|
+
//#region src/routes/messages/responses-translation.ts
|
1117
|
+
const MESSAGE_TYPE = "message";
|
1118
|
+
const translateAnthropicMessagesToResponsesPayload = (payload) => {
|
1119
|
+
const input = [];
|
1120
|
+
for (const message of payload.messages) input.push(...translateMessage(message));
|
1121
|
+
const translatedTools = convertAnthropicTools(payload.tools);
|
1122
|
+
const toolChoice = convertAnthropicToolChoice(payload.tool_choice);
|
1123
|
+
const { safetyIdentifier, promptCacheKey } = parseUserId(payload.metadata?.user_id);
|
1124
|
+
return {
|
1125
|
+
model: payload.model,
|
1126
|
+
input,
|
1127
|
+
instructions: translateSystemPrompt(payload.system),
|
1128
|
+
temperature: payload.temperature ?? null,
|
1129
|
+
top_p: payload.top_p ?? null,
|
1130
|
+
max_output_tokens: payload.max_tokens,
|
1131
|
+
tools: translatedTools,
|
1132
|
+
tool_choice: toolChoice,
|
1133
|
+
metadata: payload.metadata ? { ...payload.metadata } : null,
|
1134
|
+
safety_identifier: safetyIdentifier,
|
1135
|
+
prompt_cache_key: promptCacheKey,
|
1136
|
+
stream: payload.stream ?? null,
|
1137
|
+
store: false,
|
1138
|
+
parallel_tool_calls: true,
|
1139
|
+
reasoning: {
|
1140
|
+
effort: "high",
|
1141
|
+
summary: "auto"
|
1142
|
+
},
|
1143
|
+
include: ["reasoning.encrypted_content"]
|
1144
|
+
};
|
1145
|
+
};
|
1146
|
+
const translateMessage = (message) => {
|
1147
|
+
if (message.role === "user") return translateUserMessage(message);
|
1148
|
+
return translateAssistantMessage(message);
|
1149
|
+
};
|
1150
|
+
const translateUserMessage = (message) => {
|
1151
|
+
if (typeof message.content === "string") return [createMessage("user", message.content)];
|
1152
|
+
if (!Array.isArray(message.content)) return [];
|
1153
|
+
const items = [];
|
1154
|
+
const pendingContent = [];
|
1155
|
+
for (const block of message.content) {
|
1156
|
+
if (block.type === "tool_result") {
|
1157
|
+
flushPendingContent("user", pendingContent, items);
|
1158
|
+
items.push(createFunctionCallOutput(block));
|
1159
|
+
continue;
|
1160
|
+
}
|
1161
|
+
const converted = translateUserContentBlock(block);
|
1162
|
+
if (converted) pendingContent.push(converted);
|
1163
|
+
}
|
1164
|
+
flushPendingContent("user", pendingContent, items);
|
1165
|
+
return items;
|
1166
|
+
};
|
1167
|
+
const translateAssistantMessage = (message) => {
|
1168
|
+
if (typeof message.content === "string") return [createMessage("assistant", message.content)];
|
1169
|
+
if (!Array.isArray(message.content)) return [];
|
1170
|
+
const items = [];
|
1171
|
+
const pendingContent = [];
|
1172
|
+
for (const block of message.content) {
|
1173
|
+
if (block.type === "tool_use") {
|
1174
|
+
flushPendingContent("assistant", pendingContent, items);
|
1175
|
+
items.push(createFunctionToolCall(block));
|
1176
|
+
continue;
|
1177
|
+
}
|
1178
|
+
if (block.type === "thinking") {
|
1179
|
+
flushPendingContent("assistant", pendingContent, items);
|
1180
|
+
items.push(createReasoningContent(block));
|
1181
|
+
continue;
|
1182
|
+
}
|
1183
|
+
const converted = translateAssistantContentBlock(block);
|
1184
|
+
if (converted) pendingContent.push(converted);
|
1185
|
+
}
|
1186
|
+
flushPendingContent("assistant", pendingContent, items);
|
1187
|
+
return items;
|
1188
|
+
};
|
1189
|
+
const translateUserContentBlock = (block) => {
|
1190
|
+
switch (block.type) {
|
1191
|
+
case "text": return createTextContent(block.text);
|
1192
|
+
case "image": return createImageContent(block);
|
1193
|
+
default: return;
|
1194
|
+
}
|
1195
|
+
};
|
1196
|
+
const translateAssistantContentBlock = (block) => {
|
1197
|
+
switch (block.type) {
|
1198
|
+
case "text": return createOutPutTextContent(block.text);
|
1199
|
+
default: return;
|
1200
|
+
}
|
1201
|
+
};
|
1202
|
+
const flushPendingContent = (role, pendingContent, target) => {
|
1203
|
+
if (pendingContent.length === 0) return;
|
1204
|
+
const messageContent = pendingContent.length === 1 && isPlainText(pendingContent[0]) ? pendingContent[0].text : [...pendingContent];
|
1205
|
+
target.push(createMessage(role, messageContent));
|
1206
|
+
pendingContent.length = 0;
|
1207
|
+
};
|
1208
|
+
const createMessage = (role, content) => ({
|
1209
|
+
type: MESSAGE_TYPE,
|
1210
|
+
role,
|
1211
|
+
content
|
1212
|
+
});
|
1213
|
+
const createTextContent = (text) => ({
|
1214
|
+
type: "input_text",
|
1215
|
+
text
|
1216
|
+
});
|
1217
|
+
const createOutPutTextContent = (text) => ({
|
1218
|
+
type: "output_text",
|
1219
|
+
text
|
1220
|
+
});
|
1221
|
+
const createImageContent = (block) => ({
|
1222
|
+
type: "input_image",
|
1223
|
+
image_url: `data:${block.source.media_type};base64,${block.source.data}`
|
1224
|
+
});
|
1225
|
+
const createReasoningContent = (block) => ({
|
1226
|
+
type: "reasoning",
|
1227
|
+
summary: [{
|
1228
|
+
type: "summary_text",
|
1229
|
+
text: block.thinking
|
1230
|
+
}],
|
1231
|
+
encrypted_content: block.signature
|
1232
|
+
});
|
1233
|
+
const createFunctionToolCall = (block) => ({
|
1234
|
+
type: "function_call",
|
1235
|
+
call_id: block.id,
|
1236
|
+
name: block.name,
|
1237
|
+
arguments: JSON.stringify(block.input),
|
1238
|
+
status: "completed"
|
1239
|
+
});
|
1240
|
+
const createFunctionCallOutput = (block) => ({
|
1241
|
+
type: "function_call_output",
|
1242
|
+
call_id: block.tool_use_id,
|
1243
|
+
output: convertToolResultContent(block.content),
|
1244
|
+
status: block.is_error ? "incomplete" : "completed"
|
1245
|
+
});
|
1246
|
+
const translateSystemPrompt = (system) => {
|
1247
|
+
if (!system) return null;
|
1248
|
+
const toolUsePrompt = `
|
1249
|
+
## Tool use
|
1250
|
+
- You have access to many tools. If a tool exists to perform a specific task, you MUST use that tool instead of running a terminal command to perform that task.
|
1251
|
+
### Bash tool
|
1252
|
+
When using the Bash tool, follow these rules:
|
1253
|
+
- always run_in_background set to false, unless you are running a long-running command (e.g., a server or a watch command).
|
1254
|
+
### BashOutput tool
|
1255
|
+
When using the BashOutput tool, follow these rules:
|
1256
|
+
- Only Bash Tool run_in_background set to true, Use BashOutput to read the output later
|
1257
|
+
### TodoWrite tool
|
1258
|
+
When using the TodoWrite tool, follow these rules:
|
1259
|
+
- Skip using the TodoWrite tool for simple or straightforward tasks (roughly the easiest 25%).
|
1260
|
+
- Do not make single-step todo lists.
|
1261
|
+
- When you made a todo, update it after having performed one of the sub-tasks that you shared on the todo list.`;
|
1262
|
+
if (typeof system === "string") return system + toolUsePrompt;
|
1263
|
+
const text = system.map((block, index) => {
|
1264
|
+
if (index === 0) return block.text + toolUsePrompt;
|
1265
|
+
return block.text;
|
1266
|
+
}).join(" ");
|
1267
|
+
return text.length > 0 ? text : null;
|
1268
|
+
};
|
1269
|
+
const convertAnthropicTools = (tools) => {
|
1270
|
+
if (!tools || tools.length === 0) return null;
|
1271
|
+
return tools.map((tool) => ({
|
1272
|
+
type: "function",
|
1273
|
+
name: tool.name,
|
1274
|
+
parameters: tool.input_schema,
|
1275
|
+
strict: false,
|
1276
|
+
...tool.description ? { description: tool.description } : {}
|
1277
|
+
}));
|
1278
|
+
};
|
1279
|
+
const convertAnthropicToolChoice = (choice) => {
|
1280
|
+
if (!choice) return;
|
1281
|
+
switch (choice.type) {
|
1282
|
+
case "auto": return "auto";
|
1283
|
+
case "any": return "required";
|
1284
|
+
case "tool": return choice.name ? {
|
1285
|
+
type: "function",
|
1286
|
+
name: choice.name
|
1287
|
+
} : void 0;
|
1288
|
+
case "none": return "none";
|
1289
|
+
default: return;
|
1290
|
+
}
|
1291
|
+
};
|
1292
|
+
const isPlainText = (content) => {
|
1293
|
+
if (typeof content !== "object") return false;
|
1294
|
+
return "text" in content && typeof content.text === "string" && !("image_url" in content);
|
1295
|
+
};
|
1296
|
+
const translateResponsesResultToAnthropic = (response) => {
|
1297
|
+
const contentBlocks = mapOutputToAnthropicContent(response.output);
|
1298
|
+
const usage = mapResponsesUsage(response);
|
1299
|
+
let anthropicContent = fallbackContentBlocks(response.output_text);
|
1300
|
+
if (contentBlocks.length > 0) anthropicContent = contentBlocks;
|
1301
|
+
const stopReason = mapResponsesStopReason(response);
|
1302
|
+
return {
|
1303
|
+
id: response.id,
|
1304
|
+
type: "message",
|
1305
|
+
role: "assistant",
|
1306
|
+
content: anthropicContent,
|
1307
|
+
model: response.model,
|
1308
|
+
stop_reason: stopReason,
|
1309
|
+
stop_sequence: null,
|
1310
|
+
usage
|
1311
|
+
};
|
1312
|
+
};
|
1313
|
+
const mapOutputToAnthropicContent = (output) => {
|
1314
|
+
const contentBlocks = [];
|
1315
|
+
for (const item of output) switch (item.type) {
|
1316
|
+
case "reasoning": {
|
1317
|
+
const thinkingText = extractReasoningText(item);
|
1318
|
+
if (thinkingText.length > 0) contentBlocks.push({
|
1319
|
+
type: "thinking",
|
1320
|
+
thinking: thinkingText,
|
1321
|
+
signature: item.encrypted_content ?? ""
|
1322
|
+
});
|
1323
|
+
break;
|
1324
|
+
}
|
1325
|
+
case "function_call": {
|
1326
|
+
const toolUseBlock = createToolUseContentBlock(item);
|
1327
|
+
if (toolUseBlock) contentBlocks.push(toolUseBlock);
|
1328
|
+
break;
|
1329
|
+
}
|
1330
|
+
case "message": {
|
1331
|
+
const combinedText = combineMessageTextContent(item.content);
|
1332
|
+
if (combinedText.length > 0) contentBlocks.push({
|
1333
|
+
type: "text",
|
1334
|
+
text: combinedText
|
1335
|
+
});
|
1336
|
+
break;
|
1337
|
+
}
|
1338
|
+
default: {
|
1339
|
+
const combinedText = combineMessageTextContent(item.content);
|
1340
|
+
if (combinedText.length > 0) contentBlocks.push({
|
1341
|
+
type: "text",
|
1342
|
+
text: combinedText
|
1343
|
+
});
|
1344
|
+
}
|
1345
|
+
}
|
1346
|
+
return contentBlocks;
|
1347
|
+
};
|
1348
|
+
const combineMessageTextContent = (content) => {
|
1349
|
+
if (!Array.isArray(content)) return "";
|
1350
|
+
let aggregated = "";
|
1351
|
+
for (const block of content) {
|
1352
|
+
if (isResponseOutputText(block)) {
|
1353
|
+
aggregated += block.text;
|
1354
|
+
continue;
|
1355
|
+
}
|
1356
|
+
if (isResponseOutputRefusal(block)) {
|
1357
|
+
aggregated += block.refusal;
|
1358
|
+
continue;
|
1359
|
+
}
|
1360
|
+
if (typeof block.text === "string") {
|
1361
|
+
aggregated += block.text;
|
1362
|
+
continue;
|
1363
|
+
}
|
1364
|
+
if (typeof block.reasoning === "string") {
|
1365
|
+
aggregated += block.reasoning;
|
1366
|
+
continue;
|
1367
|
+
}
|
1368
|
+
}
|
1369
|
+
return aggregated;
|
1370
|
+
};
|
1371
|
+
const extractReasoningText = (item) => {
|
1372
|
+
const segments = [];
|
1373
|
+
const collectFromBlocks = (blocks) => {
|
1374
|
+
if (!Array.isArray(blocks)) return;
|
1375
|
+
for (const block of blocks) if (typeof block.text === "string") {
|
1376
|
+
segments.push(block.text);
|
1377
|
+
continue;
|
1378
|
+
}
|
1379
|
+
};
|
1380
|
+
collectFromBlocks(item.summary);
|
1381
|
+
return segments.join("").trim();
|
1382
|
+
};
|
1383
|
+
const createToolUseContentBlock = (call) => {
|
1384
|
+
const toolId = call.call_id ?? call.id;
|
1385
|
+
if (!call.name || !toolId) return null;
|
1386
|
+
const input = parseFunctionCallArguments(call.arguments);
|
1387
|
+
return {
|
1388
|
+
type: "tool_use",
|
1389
|
+
id: toolId,
|
1390
|
+
name: call.name,
|
1391
|
+
input
|
1392
|
+
};
|
1393
|
+
};
|
1394
|
+
const parseFunctionCallArguments = (rawArguments) => {
|
1395
|
+
if (typeof rawArguments !== "string" || rawArguments.trim().length === 0) return {};
|
1396
|
+
try {
|
1397
|
+
const parsed = JSON.parse(rawArguments);
|
1398
|
+
if (Array.isArray(parsed)) return { arguments: parsed };
|
1399
|
+
if (parsed && typeof parsed === "object") return parsed;
|
1400
|
+
} catch (error) {
|
1401
|
+
consola.warn("Failed to parse function call arguments", {
|
1402
|
+
error,
|
1403
|
+
rawArguments
|
1404
|
+
});
|
1405
|
+
}
|
1406
|
+
return { raw_arguments: rawArguments };
|
1407
|
+
};
|
1408
|
+
const fallbackContentBlocks = (outputText) => {
|
1409
|
+
if (!outputText) return [];
|
1410
|
+
return [{
|
1411
|
+
type: "text",
|
1412
|
+
text: outputText
|
1413
|
+
}];
|
1414
|
+
};
|
1415
|
+
const mapResponsesStopReason = (response) => {
|
1416
|
+
const { status, incomplete_details: incompleteDetails } = response;
|
1417
|
+
if (status === "completed") return "end_turn";
|
1418
|
+
if (status === "incomplete") {
|
1419
|
+
if (incompleteDetails?.reason === "max_output_tokens") return "max_tokens";
|
1420
|
+
if (incompleteDetails?.reason === "content_filter") return "end_turn";
|
1421
|
+
if (incompleteDetails?.reason === "tool_use") return "tool_use";
|
1422
|
+
}
|
1423
|
+
return null;
|
1424
|
+
};
|
1425
|
+
const mapResponsesUsage = (response) => {
|
1426
|
+
const inputTokens = response.usage?.input_tokens ?? 0;
|
1427
|
+
const outputTokens = response.usage?.output_tokens ?? 0;
|
1428
|
+
const inputCachedTokens = response.usage?.input_tokens_details?.cached_tokens;
|
1429
|
+
return {
|
1430
|
+
input_tokens: inputTokens - (inputCachedTokens ?? 0),
|
1431
|
+
output_tokens: outputTokens,
|
1432
|
+
...response.usage?.input_tokens_details?.cached_tokens !== void 0 && { cache_read_input_tokens: response.usage.input_tokens_details.cached_tokens }
|
1433
|
+
};
|
1434
|
+
};
|
1435
|
+
const isRecord$1 = (value) => typeof value === "object" && value !== null;
|
1436
|
+
const isResponseOutputText = (block) => isRecord$1(block) && "type" in block && block.type === "output_text";
|
1437
|
+
const isResponseOutputRefusal = (block) => isRecord$1(block) && "type" in block && block.type === "refusal";
|
1438
|
+
const parseUserId = (userId) => {
|
1439
|
+
if (!userId || typeof userId !== "string") return {
|
1440
|
+
safetyIdentifier: null,
|
1441
|
+
promptCacheKey: null
|
1442
|
+
};
|
1443
|
+
const userMatch = userId.match(/user_([^_]+)_account/);
|
1444
|
+
const safetyIdentifier = userMatch ? userMatch[1] : null;
|
1445
|
+
const sessionMatch = userId.match(/_session_(.+)$/);
|
1446
|
+
const promptCacheKey = sessionMatch ? sessionMatch[1] : null;
|
1447
|
+
return {
|
1448
|
+
safetyIdentifier,
|
1449
|
+
promptCacheKey
|
1450
|
+
};
|
1451
|
+
};
|
1452
|
+
const convertToolResultContent = (content) => {
|
1453
|
+
if (typeof content === "string") return content;
|
1454
|
+
if (Array.isArray(content)) {
|
1455
|
+
const result = [];
|
1456
|
+
for (const block of content) switch (block.type) {
|
1457
|
+
case "text":
|
1458
|
+
result.push(createTextContent(block.text));
|
1459
|
+
break;
|
1460
|
+
case "image":
|
1461
|
+
result.push(createImageContent(block));
|
1462
|
+
break;
|
1463
|
+
default: break;
|
1464
|
+
}
|
1465
|
+
return result;
|
1466
|
+
}
|
1467
|
+
return "";
|
1468
|
+
};
|
1469
|
+
|
1470
|
+
//#endregion
|
1471
|
+
//#region src/routes/messages/responses-stream-translation.ts
|
1472
|
+
const createResponsesStreamState = () => ({
|
1473
|
+
messageStartSent: false,
|
1474
|
+
messageCompleted: false,
|
1475
|
+
nextContentBlockIndex: 0,
|
1476
|
+
blockIndexByKey: /* @__PURE__ */ new Map(),
|
1477
|
+
openBlocks: /* @__PURE__ */ new Set(),
|
1478
|
+
blockHasDelta: /* @__PURE__ */ new Set(),
|
1479
|
+
functionCallStateByOutputIndex: /* @__PURE__ */ new Map(),
|
1480
|
+
functionCallOutputIndexByItemId: /* @__PURE__ */ new Map()
|
1481
|
+
});
|
1482
|
+
const translateResponsesStreamEvent = (rawEvent, state$1) => {
|
1483
|
+
const eventType = typeof rawEvent.type === "string" ? rawEvent.type : void 0;
|
1484
|
+
if (!eventType) return [];
|
1485
|
+
switch (eventType) {
|
1486
|
+
case "response.created": return handleResponseCreated(rawEvent, state$1);
|
1487
|
+
case "response.reasoning_summary_text.delta": return handleReasoningSummaryTextDelta(rawEvent, state$1);
|
1488
|
+
case "response.output_text.delta": return handleOutputTextDelta(rawEvent, state$1);
|
1489
|
+
case "response.reasoning_summary_part.done": return handleReasoningSummaryPartDone(rawEvent, state$1);
|
1490
|
+
case "response.output_text.done": return handleOutputTextDone(rawEvent, state$1);
|
1491
|
+
case "response.output_item.added": return handleOutputItemAdded(rawEvent, state$1);
|
1492
|
+
case "response.output_item.done": return handleOutputItemDone(rawEvent, state$1);
|
1493
|
+
case "response.function_call_arguments.delta": return handleFunctionCallArgumentsDelta(rawEvent, state$1);
|
1494
|
+
case "response.function_call_arguments.done": return handleFunctionCallArgumentsDone(rawEvent, state$1);
|
1495
|
+
case "response.completed":
|
1496
|
+
case "response.incomplete": return handleResponseCompleted(rawEvent, state$1);
|
1497
|
+
case "response.failed": return handleResponseFailed(rawEvent, state$1);
|
1498
|
+
case "error": return handleErrorEvent(rawEvent, state$1);
|
1499
|
+
default: return [];
|
1500
|
+
}
|
1501
|
+
};
|
1502
|
+
const handleResponseCreated = (rawEvent, state$1) => {
|
1503
|
+
const response = toResponsesResult(rawEvent.response);
|
1504
|
+
if (response) cacheResponseMetadata(state$1, response);
|
1505
|
+
return ensureMessageStart(state$1, response);
|
1506
|
+
};
|
1507
|
+
const handleOutputItemAdded = (rawEvent, state$1) => {
|
1508
|
+
const response = toResponsesResult(rawEvent.response);
|
1509
|
+
const events$1 = ensureMessageStart(state$1, response);
|
1510
|
+
const functionCallDetails = extractFunctionCallDetails(rawEvent, state$1);
|
1511
|
+
if (!functionCallDetails) return events$1;
|
1512
|
+
const { outputIndex, toolCallId, name, initialArguments, itemId } = functionCallDetails;
|
1513
|
+
if (itemId) state$1.functionCallOutputIndexByItemId.set(itemId, outputIndex);
|
1514
|
+
const blockIndex = openFunctionCallBlock(state$1, {
|
1515
|
+
outputIndex,
|
1516
|
+
toolCallId,
|
1517
|
+
name,
|
1518
|
+
events: events$1
|
1519
|
+
});
|
1520
|
+
if (initialArguments !== void 0 && initialArguments.length > 0) {
|
1521
|
+
events$1.push({
|
1522
|
+
type: "content_block_delta",
|
1523
|
+
index: blockIndex,
|
1524
|
+
delta: {
|
1525
|
+
type: "input_json_delta",
|
1526
|
+
partial_json: initialArguments
|
1527
|
+
}
|
1528
|
+
});
|
1529
|
+
state$1.blockHasDelta.add(blockIndex);
|
1530
|
+
}
|
1531
|
+
return events$1;
|
1532
|
+
};
|
1533
|
+
const handleOutputItemDone = (rawEvent, state$1) => {
|
1534
|
+
const events$1 = ensureMessageStart(state$1);
|
1535
|
+
const item = isRecord(rawEvent.item) ? rawEvent.item : void 0;
|
1536
|
+
if (!item) return events$1;
|
1537
|
+
if ((typeof item.type === "string" ? item.type : void 0) !== "reasoning") return events$1;
|
1538
|
+
const outputIndex = toNumber(rawEvent.output_index);
|
1539
|
+
const blockIndex = openThinkingBlockIfNeeded(state$1, outputIndex, events$1);
|
1540
|
+
const signature = typeof item.encrypted_content === "string" ? item.encrypted_content : "";
|
1541
|
+
if (signature) {
|
1542
|
+
events$1.push({
|
1543
|
+
type: "content_block_delta",
|
1544
|
+
index: blockIndex,
|
1545
|
+
delta: {
|
1546
|
+
type: "signature_delta",
|
1547
|
+
signature
|
1548
|
+
}
|
1549
|
+
});
|
1550
|
+
state$1.blockHasDelta.add(blockIndex);
|
1551
|
+
}
|
1552
|
+
closeBlockIfOpen(state$1, blockIndex, events$1);
|
1553
|
+
return events$1;
|
1554
|
+
};
|
1555
|
+
const handleFunctionCallArgumentsDelta = (rawEvent, state$1) => {
|
1556
|
+
const events$1 = ensureMessageStart(state$1);
|
1557
|
+
const outputIndex = resolveFunctionCallOutputIndex(state$1, rawEvent);
|
1558
|
+
if (outputIndex === void 0) return events$1;
|
1559
|
+
const deltaText = typeof rawEvent.delta === "string" ? rawEvent.delta : "";
|
1560
|
+
if (!deltaText) return events$1;
|
1561
|
+
const blockIndex = openFunctionCallBlock(state$1, {
|
1562
|
+
outputIndex,
|
1563
|
+
events: events$1
|
1564
|
+
});
|
1565
|
+
events$1.push({
|
1566
|
+
type: "content_block_delta",
|
1567
|
+
index: blockIndex,
|
1568
|
+
delta: {
|
1569
|
+
type: "input_json_delta",
|
1570
|
+
partial_json: deltaText
|
1571
|
+
}
|
1572
|
+
});
|
1573
|
+
state$1.blockHasDelta.add(blockIndex);
|
1574
|
+
return events$1;
|
1575
|
+
};
|
1576
|
+
const handleFunctionCallArgumentsDone = (rawEvent, state$1) => {
|
1577
|
+
const events$1 = ensureMessageStart(state$1);
|
1578
|
+
const outputIndex = resolveFunctionCallOutputIndex(state$1, rawEvent);
|
1579
|
+
if (outputIndex === void 0) return events$1;
|
1580
|
+
const blockIndex = openFunctionCallBlock(state$1, {
|
1581
|
+
outputIndex,
|
1582
|
+
events: events$1
|
1583
|
+
});
|
1584
|
+
const finalArguments = typeof rawEvent.arguments === "string" ? rawEvent.arguments : void 0;
|
1585
|
+
if (!state$1.blockHasDelta.has(blockIndex) && finalArguments) {
|
1586
|
+
events$1.push({
|
1587
|
+
type: "content_block_delta",
|
1588
|
+
index: blockIndex,
|
1589
|
+
delta: {
|
1590
|
+
type: "input_json_delta",
|
1591
|
+
partial_json: finalArguments
|
1592
|
+
}
|
1593
|
+
});
|
1594
|
+
state$1.blockHasDelta.add(blockIndex);
|
1595
|
+
}
|
1596
|
+
closeBlockIfOpen(state$1, blockIndex, events$1);
|
1597
|
+
const existingState = state$1.functionCallStateByOutputIndex.get(outputIndex);
|
1598
|
+
if (existingState) state$1.functionCallOutputIndexByItemId.delete(existingState.toolCallId);
|
1599
|
+
state$1.functionCallStateByOutputIndex.delete(outputIndex);
|
1600
|
+
const itemId = toNonEmptyString(rawEvent.item_id);
|
1601
|
+
if (itemId) state$1.functionCallOutputIndexByItemId.delete(itemId);
|
1602
|
+
return events$1;
|
1603
|
+
};
|
1604
|
+
const handleOutputTextDelta = (rawEvent, state$1) => {
|
1605
|
+
const events$1 = ensureMessageStart(state$1);
|
1606
|
+
const outputIndex = toNumber(rawEvent.output_index);
|
1607
|
+
const contentIndex = toNumber(rawEvent.content_index);
|
1608
|
+
const deltaText = typeof rawEvent.delta === "string" ? rawEvent.delta : "";
|
1609
|
+
if (!deltaText) return events$1;
|
1610
|
+
const blockIndex = openTextBlockIfNeeded(state$1, {
|
1611
|
+
outputIndex,
|
1612
|
+
contentIndex,
|
1613
|
+
events: events$1
|
1614
|
+
});
|
1615
|
+
events$1.push({
|
1616
|
+
type: "content_block_delta",
|
1617
|
+
index: blockIndex,
|
1618
|
+
delta: {
|
1619
|
+
type: "text_delta",
|
1620
|
+
text: deltaText
|
1621
|
+
}
|
1622
|
+
});
|
1623
|
+
state$1.blockHasDelta.add(blockIndex);
|
1624
|
+
return events$1;
|
1625
|
+
};
|
1626
|
+
const handleReasoningSummaryTextDelta = (rawEvent, state$1) => {
|
1627
|
+
const events$1 = ensureMessageStart(state$1);
|
1628
|
+
const outputIndex = toNumber(rawEvent.output_index);
|
1629
|
+
const deltaText = typeof rawEvent.delta === "string" ? rawEvent.delta : "";
|
1630
|
+
if (!deltaText) return events$1;
|
1631
|
+
const blockIndex = openThinkingBlockIfNeeded(state$1, outputIndex, events$1);
|
1632
|
+
events$1.push({
|
1633
|
+
type: "content_block_delta",
|
1634
|
+
index: blockIndex,
|
1635
|
+
delta: {
|
1636
|
+
type: "thinking_delta",
|
1637
|
+
thinking: deltaText
|
1638
|
+
}
|
1639
|
+
});
|
1640
|
+
state$1.blockHasDelta.add(blockIndex);
|
1641
|
+
return events$1;
|
1642
|
+
};
|
1643
|
+
const handleReasoningSummaryPartDone = (rawEvent, state$1) => {
|
1644
|
+
const events$1 = ensureMessageStart(state$1);
|
1645
|
+
const outputIndex = toNumber(rawEvent.output_index);
|
1646
|
+
const part = isRecord(rawEvent.part) ? rawEvent.part : void 0;
|
1647
|
+
const text = part && typeof part.text === "string" ? part.text : "";
|
1648
|
+
const blockIndex = openThinkingBlockIfNeeded(state$1, outputIndex, events$1);
|
1649
|
+
if (text && !state$1.blockHasDelta.has(blockIndex)) events$1.push({
|
1650
|
+
type: "content_block_delta",
|
1651
|
+
index: blockIndex,
|
1652
|
+
delta: {
|
1653
|
+
type: "thinking_delta",
|
1654
|
+
thinking: text
|
1655
|
+
}
|
1656
|
+
});
|
1657
|
+
return events$1;
|
1658
|
+
};
|
1659
|
+
const handleOutputTextDone = (rawEvent, state$1) => {
|
1660
|
+
const events$1 = ensureMessageStart(state$1);
|
1661
|
+
const outputIndex = toNumber(rawEvent.output_index);
|
1662
|
+
const contentIndex = toNumber(rawEvent.content_index);
|
1663
|
+
const text = typeof rawEvent.text === "string" ? rawEvent.text : "";
|
1664
|
+
const blockIndex = openTextBlockIfNeeded(state$1, {
|
1665
|
+
outputIndex,
|
1666
|
+
contentIndex,
|
1667
|
+
events: events$1
|
1668
|
+
});
|
1669
|
+
if (text && !state$1.blockHasDelta.has(blockIndex)) events$1.push({
|
1670
|
+
type: "content_block_delta",
|
1671
|
+
index: blockIndex,
|
1672
|
+
delta: {
|
1673
|
+
type: "text_delta",
|
1674
|
+
text
|
1675
|
+
}
|
1676
|
+
});
|
1677
|
+
closeBlockIfOpen(state$1, blockIndex, events$1);
|
1678
|
+
return events$1;
|
1679
|
+
};
|
1680
|
+
const handleResponseCompleted = (rawEvent, state$1) => {
|
1681
|
+
const response = toResponsesResult(rawEvent.response);
|
1682
|
+
const events$1 = ensureMessageStart(state$1, response);
|
1683
|
+
closeAllOpenBlocks(state$1, events$1);
|
1684
|
+
if (response) {
|
1685
|
+
const anthropic = translateResponsesResultToAnthropic(response);
|
1686
|
+
events$1.push({
|
1687
|
+
type: "message_delta",
|
1688
|
+
delta: {
|
1689
|
+
stop_reason: anthropic.stop_reason,
|
1690
|
+
stop_sequence: anthropic.stop_sequence
|
1691
|
+
},
|
1692
|
+
usage: anthropic.usage
|
1693
|
+
});
|
1694
|
+
} else events$1.push({
|
1695
|
+
type: "message_delta",
|
1696
|
+
delta: {
|
1697
|
+
stop_reason: null,
|
1698
|
+
stop_sequence: null
|
1699
|
+
}
|
1700
|
+
});
|
1701
|
+
events$1.push({ type: "message_stop" });
|
1702
|
+
state$1.messageCompleted = true;
|
1703
|
+
return events$1;
|
1704
|
+
};
|
1705
|
+
const handleResponseFailed = (rawEvent, state$1) => {
|
1706
|
+
const response = toResponsesResult(rawEvent.response);
|
1707
|
+
const events$1 = ensureMessageStart(state$1, response);
|
1708
|
+
closeAllOpenBlocks(state$1, events$1);
|
1709
|
+
const message = typeof rawEvent.error === "string" ? rawEvent.error : "Response generation failed.";
|
1710
|
+
events$1.push(buildErrorEvent(message));
|
1711
|
+
state$1.messageCompleted = true;
|
1712
|
+
return events$1;
|
1713
|
+
};
|
1714
|
+
const handleErrorEvent = (rawEvent, state$1) => {
|
1715
|
+
const message = typeof rawEvent.message === "string" ? rawEvent.message : "An unexpected error occurred during streaming.";
|
1716
|
+
state$1.messageCompleted = true;
|
1717
|
+
return [buildErrorEvent(message)];
|
1718
|
+
};
|
1719
|
+
const ensureMessageStart = (state$1, response) => {
|
1720
|
+
if (state$1.messageStartSent) return [];
|
1721
|
+
if (response) cacheResponseMetadata(state$1, response);
|
1722
|
+
const id = response?.id ?? state$1.currentResponseId ?? "response";
|
1723
|
+
const model = response?.model ?? state$1.currentModel ?? "";
|
1724
|
+
state$1.messageStartSent = true;
|
1725
|
+
const inputTokens = (state$1.initialInputTokens ?? 0) - (state$1.initialInputCachedTokens ?? 0);
|
1726
|
+
return [{
|
1727
|
+
type: "message_start",
|
1728
|
+
message: {
|
1729
|
+
id,
|
1730
|
+
type: "message",
|
1731
|
+
role: "assistant",
|
1732
|
+
content: [],
|
1733
|
+
model,
|
1734
|
+
stop_reason: null,
|
1735
|
+
stop_sequence: null,
|
1736
|
+
usage: {
|
1737
|
+
input_tokens: inputTokens,
|
1738
|
+
output_tokens: 0,
|
1739
|
+
...state$1.initialInputCachedTokens !== void 0 && { cache_creation_input_tokens: state$1.initialInputCachedTokens }
|
1740
|
+
}
|
1741
|
+
}
|
1742
|
+
}];
|
1743
|
+
};
|
1744
|
+
const openTextBlockIfNeeded = (state$1, params) => {
|
1745
|
+
const { outputIndex, contentIndex, events: events$1 } = params;
|
1746
|
+
const key = getBlockKey(outputIndex, contentIndex);
|
1747
|
+
let blockIndex = state$1.blockIndexByKey.get(key);
|
1748
|
+
if (blockIndex === void 0) {
|
1749
|
+
blockIndex = state$1.nextContentBlockIndex;
|
1750
|
+
state$1.nextContentBlockIndex += 1;
|
1751
|
+
state$1.blockIndexByKey.set(key, blockIndex);
|
1752
|
+
}
|
1753
|
+
if (!state$1.openBlocks.has(blockIndex)) {
|
1754
|
+
events$1.push({
|
1755
|
+
type: "content_block_start",
|
1756
|
+
index: blockIndex,
|
1757
|
+
content_block: {
|
1758
|
+
type: "text",
|
1759
|
+
text: ""
|
1760
|
+
}
|
1761
|
+
});
|
1762
|
+
state$1.openBlocks.add(blockIndex);
|
1763
|
+
}
|
1764
|
+
return blockIndex;
|
1765
|
+
};
|
1766
|
+
const openThinkingBlockIfNeeded = (state$1, outputIndex, events$1) => {
|
1767
|
+
const key = getBlockKey(outputIndex, 0);
|
1768
|
+
let blockIndex = state$1.blockIndexByKey.get(key);
|
1769
|
+
if (blockIndex === void 0) {
|
1770
|
+
blockIndex = state$1.nextContentBlockIndex;
|
1771
|
+
state$1.nextContentBlockIndex += 1;
|
1772
|
+
state$1.blockIndexByKey.set(key, blockIndex);
|
1773
|
+
}
|
1774
|
+
if (!state$1.openBlocks.has(blockIndex)) {
|
1775
|
+
events$1.push({
|
1776
|
+
type: "content_block_start",
|
1777
|
+
index: blockIndex,
|
1778
|
+
content_block: {
|
1779
|
+
type: "thinking",
|
1780
|
+
thinking: ""
|
1781
|
+
}
|
1782
|
+
});
|
1783
|
+
state$1.openBlocks.add(blockIndex);
|
1784
|
+
}
|
1785
|
+
return blockIndex;
|
1786
|
+
};
|
1787
|
+
const closeBlockIfOpen = (state$1, blockIndex, events$1) => {
|
1788
|
+
if (!state$1.openBlocks.has(blockIndex)) return;
|
1789
|
+
events$1.push({
|
1790
|
+
type: "content_block_stop",
|
1791
|
+
index: blockIndex
|
1792
|
+
});
|
1793
|
+
state$1.openBlocks.delete(blockIndex);
|
1794
|
+
state$1.blockHasDelta.delete(blockIndex);
|
1795
|
+
};
|
1796
|
+
const closeAllOpenBlocks = (state$1, events$1) => {
|
1797
|
+
for (const blockIndex of state$1.openBlocks) closeBlockIfOpen(state$1, blockIndex, events$1);
|
1798
|
+
state$1.functionCallStateByOutputIndex.clear();
|
1799
|
+
state$1.functionCallOutputIndexByItemId.clear();
|
1800
|
+
};
|
1801
|
+
const cacheResponseMetadata = (state$1, response) => {
|
1802
|
+
state$1.currentResponseId = response.id;
|
1803
|
+
state$1.currentModel = response.model;
|
1804
|
+
state$1.initialInputTokens = response.usage?.input_tokens ?? 0;
|
1805
|
+
state$1.initialInputCachedTokens = response.usage?.input_tokens_details?.cached_tokens;
|
1806
|
+
};
|
1807
|
+
const buildErrorEvent = (message) => ({
|
1808
|
+
type: "error",
|
1809
|
+
error: {
|
1810
|
+
type: "api_error",
|
1811
|
+
message
|
1812
|
+
}
|
1813
|
+
});
|
1814
|
+
const getBlockKey = (outputIndex, contentIndex) => `${outputIndex}:${contentIndex}`;
|
1815
|
+
const resolveFunctionCallOutputIndex = (state$1, rawEvent) => {
|
1816
|
+
if (typeof rawEvent.output_index === "number" || typeof rawEvent.output_index === "string" && rawEvent.output_index.length > 0) {
|
1817
|
+
const parsed = toOptionalNumber(rawEvent.output_index);
|
1818
|
+
if (parsed !== void 0) return parsed;
|
1819
|
+
}
|
1820
|
+
const itemId = toNonEmptyString(rawEvent.item_id);
|
1821
|
+
if (itemId) {
|
1822
|
+
const mapped = state$1.functionCallOutputIndexByItemId.get(itemId);
|
1823
|
+
if (mapped !== void 0) return mapped;
|
1824
|
+
}
|
1825
|
+
};
|
1826
|
+
const openFunctionCallBlock = (state$1, params) => {
|
1827
|
+
const { outputIndex, toolCallId, name, events: events$1 } = params;
|
1828
|
+
let functionCallState = state$1.functionCallStateByOutputIndex.get(outputIndex);
|
1829
|
+
if (!functionCallState) {
|
1830
|
+
const blockIndex$1 = state$1.nextContentBlockIndex;
|
1831
|
+
state$1.nextContentBlockIndex += 1;
|
1832
|
+
const resolvedToolCallId = toolCallId ?? `tool_call_${blockIndex$1}`;
|
1833
|
+
functionCallState = {
|
1834
|
+
blockIndex: blockIndex$1,
|
1835
|
+
toolCallId: resolvedToolCallId,
|
1836
|
+
name: name ?? "function"
|
1837
|
+
};
|
1838
|
+
state$1.functionCallStateByOutputIndex.set(outputIndex, functionCallState);
|
1839
|
+
state$1.functionCallOutputIndexByItemId.set(resolvedToolCallId, outputIndex);
|
1840
|
+
}
|
1841
|
+
const { blockIndex } = functionCallState;
|
1842
|
+
if (!state$1.openBlocks.has(blockIndex)) {
|
1843
|
+
events$1.push({
|
1844
|
+
type: "content_block_start",
|
1845
|
+
index: blockIndex,
|
1846
|
+
content_block: {
|
1847
|
+
type: "tool_use",
|
1848
|
+
id: functionCallState.toolCallId,
|
1849
|
+
name: functionCallState.name,
|
1850
|
+
input: {}
|
1851
|
+
}
|
1852
|
+
});
|
1853
|
+
state$1.openBlocks.add(blockIndex);
|
1854
|
+
}
|
1855
|
+
return blockIndex;
|
1856
|
+
};
|
1857
|
+
const extractFunctionCallDetails = (rawEvent, state$1) => {
|
1858
|
+
const item = isRecord(rawEvent.item) ? rawEvent.item : void 0;
|
1859
|
+
if (!item) return;
|
1860
|
+
if ((typeof item.type === "string" ? item.type : void 0) !== "function_call") return;
|
1861
|
+
const outputIndex = resolveFunctionCallOutputIndex(state$1, rawEvent);
|
1862
|
+
if (outputIndex === void 0) return;
|
1863
|
+
const callId = toNonEmptyString(item.call_id);
|
1864
|
+
const itemId = toNonEmptyString(item.id);
|
1865
|
+
const name = toNonEmptyString(item.name) ?? "function";
|
1866
|
+
const toolCallId = callId ?? itemId ?? `tool_call_${outputIndex}`;
|
1867
|
+
const initialArguments = typeof item.arguments === "string" ? item.arguments : void 0;
|
1868
|
+
return {
|
1869
|
+
outputIndex,
|
1870
|
+
toolCallId,
|
1871
|
+
name,
|
1872
|
+
initialArguments,
|
1873
|
+
itemId
|
1874
|
+
};
|
1875
|
+
};
|
1876
|
+
const toResponsesResult = (value) => isResponsesResult(value) ? value : void 0;
|
1877
|
+
const toOptionalNumber = (value) => {
|
1878
|
+
if (typeof value === "number" && Number.isFinite(value)) return value;
|
1879
|
+
if (typeof value === "string" && value.length > 0) {
|
1880
|
+
const parsed = Number(value);
|
1881
|
+
if (Number.isFinite(parsed)) return parsed;
|
1882
|
+
}
|
1883
|
+
};
|
1884
|
+
const toNonEmptyString = (value) => {
|
1885
|
+
if (typeof value === "string" && value.length > 0) return value;
|
1886
|
+
};
|
1887
|
+
const toNumber = (value) => {
|
1888
|
+
if (typeof value === "number" && Number.isFinite(value)) return value;
|
1889
|
+
if (typeof value === "string") {
|
1890
|
+
const parsed = Number(value);
|
1891
|
+
if (Number.isFinite(parsed)) return parsed;
|
1892
|
+
}
|
1893
|
+
return 0;
|
1894
|
+
};
|
1895
|
+
const isResponsesResult = (value) => {
|
1896
|
+
if (!isRecord(value)) return false;
|
1897
|
+
if (typeof value.id !== "string") return false;
|
1898
|
+
if (typeof value.model !== "string") return false;
|
1899
|
+
if (!Array.isArray(value.output)) return false;
|
1900
|
+
if (typeof value.object !== "string") return false;
|
1901
|
+
return true;
|
1902
|
+
};
|
1903
|
+
const isRecord = (value) => typeof value === "object" && value !== null;
|
1904
|
+
|
1905
|
+
//#endregion
|
1906
|
+
//#region src/routes/responses/utils.ts
|
1907
|
+
const getResponsesRequestOptions = (payload) => {
|
1908
|
+
const vision = hasVisionInput(payload);
|
1909
|
+
const initiator = hasAgentInitiator(payload) ? "agent" : "user";
|
1910
|
+
return {
|
1911
|
+
vision,
|
1912
|
+
initiator
|
1913
|
+
};
|
1914
|
+
};
|
1915
|
+
const hasAgentInitiator = (payload) => getPayloadItems(payload).some((item) => {
|
1916
|
+
if (!("role" in item) || !item.role) return true;
|
1917
|
+
return (typeof item.role === "string" ? item.role.toLowerCase() : "") === "assistant";
|
1918
|
+
});
|
1919
|
+
const hasVisionInput = (payload) => {
|
1920
|
+
return getPayloadItems(payload).some((item) => containsVisionContent(item));
|
1921
|
+
};
|
1922
|
+
const getPayloadItems = (payload) => {
|
1923
|
+
const result = [];
|
1924
|
+
const { input } = payload;
|
1925
|
+
if (Array.isArray(input)) result.push(...input);
|
1926
|
+
return result;
|
1927
|
+
};
|
1928
|
+
const containsVisionContent = (value) => {
|
1929
|
+
if (!value) return false;
|
1930
|
+
if (Array.isArray(value)) return value.some((entry) => containsVisionContent(entry));
|
1931
|
+
if (typeof value !== "object") return false;
|
1932
|
+
const record = value;
|
1933
|
+
if ((typeof record.type === "string" ? record.type.toLowerCase() : void 0) === "input_image") return true;
|
1934
|
+
if (Array.isArray(record.content)) return record.content.some((entry) => containsVisionContent(entry));
|
1935
|
+
return false;
|
1936
|
+
};
|
1937
|
+
|
1938
|
+
//#endregion
|
1939
|
+
//#region src/routes/messages/stream-translation.ts
|
1940
|
+
function isToolBlockOpen(state$1) {
|
1941
|
+
if (!state$1.contentBlockOpen) return false;
|
1942
|
+
return Object.values(state$1.toolCalls).some((tc) => tc.anthropicBlockIndex === state$1.contentBlockIndex);
|
1943
|
+
}
|
1944
|
+
function translateChunkToAnthropicEvents(chunk, state$1) {
|
1945
|
+
const events$1 = [];
|
1946
|
+
if (chunk.choices.length === 0) return events$1;
|
1947
|
+
const choice = chunk.choices[0];
|
1948
|
+
const { delta } = choice;
|
1949
|
+
if (!state$1.messageStartSent) {
|
1950
|
+
events$1.push({
|
1951
|
+
type: "message_start",
|
1952
|
+
message: {
|
1953
|
+
id: chunk.id,
|
1954
|
+
type: "message",
|
1955
|
+
role: "assistant",
|
1956
|
+
content: [],
|
1957
|
+
model: chunk.model,
|
1958
|
+
stop_reason: null,
|
1959
|
+
stop_sequence: null,
|
1960
|
+
usage: {
|
1961
|
+
input_tokens: (chunk.usage?.prompt_tokens ?? 0) - (chunk.usage?.prompt_tokens_details?.cached_tokens ?? 0),
|
1962
|
+
output_tokens: 0,
|
1963
|
+
...chunk.usage?.prompt_tokens_details?.cached_tokens !== void 0 && { cache_read_input_tokens: chunk.usage.prompt_tokens_details.cached_tokens }
|
1964
|
+
}
|
1965
|
+
}
|
1966
|
+
});
|
1967
|
+
state$1.messageStartSent = true;
|
1968
|
+
}
|
1969
|
+
if (delta.content) {
|
1970
|
+
if (isToolBlockOpen(state$1)) {
|
1971
|
+
events$1.push({
|
1972
|
+
type: "content_block_stop",
|
1973
|
+
index: state$1.contentBlockIndex
|
1974
|
+
});
|
1975
|
+
state$1.contentBlockIndex++;
|
1976
|
+
state$1.contentBlockOpen = false;
|
1977
|
+
}
|
1978
|
+
if (!state$1.contentBlockOpen) {
|
1979
|
+
events$1.push({
|
1980
|
+
type: "content_block_start",
|
1981
|
+
index: state$1.contentBlockIndex,
|
1982
|
+
content_block: {
|
1983
|
+
type: "text",
|
1984
|
+
text: ""
|
1985
|
+
}
|
1986
|
+
});
|
1987
|
+
state$1.contentBlockOpen = true;
|
1988
|
+
}
|
1989
|
+
events$1.push({
|
1990
|
+
type: "content_block_delta",
|
1991
|
+
index: state$1.contentBlockIndex,
|
1992
|
+
delta: {
|
1993
|
+
type: "text_delta",
|
1994
|
+
text: delta.content
|
1995
|
+
}
|
1996
|
+
});
|
1997
|
+
}
|
1998
|
+
if (delta.tool_calls) for (const toolCall of delta.tool_calls) {
|
1999
|
+
if (toolCall.id && toolCall.function?.name) {
|
2000
|
+
if (state$1.contentBlockOpen) {
|
2001
|
+
events$1.push({
|
2002
|
+
type: "content_block_stop",
|
2003
|
+
index: state$1.contentBlockIndex
|
2004
|
+
});
|
2005
|
+
state$1.contentBlockIndex++;
|
2006
|
+
state$1.contentBlockOpen = false;
|
2007
|
+
}
|
2008
|
+
const anthropicBlockIndex = state$1.contentBlockIndex;
|
2009
|
+
state$1.toolCalls[toolCall.index] = {
|
2010
|
+
id: toolCall.id,
|
2011
|
+
name: toolCall.function.name,
|
2012
|
+
anthropicBlockIndex
|
2013
|
+
};
|
2014
|
+
events$1.push({
|
2015
|
+
type: "content_block_start",
|
2016
|
+
index: anthropicBlockIndex,
|
2017
|
+
content_block: {
|
2018
|
+
type: "tool_use",
|
2019
|
+
id: toolCall.id,
|
2020
|
+
name: toolCall.function.name,
|
2021
|
+
input: {}
|
2022
|
+
}
|
2023
|
+
});
|
2024
|
+
state$1.contentBlockOpen = true;
|
2025
|
+
}
|
2026
|
+
if (toolCall.function?.arguments) {
|
2027
|
+
const toolCallInfo = state$1.toolCalls[toolCall.index];
|
2028
|
+
if (toolCallInfo) events$1.push({
|
2029
|
+
type: "content_block_delta",
|
2030
|
+
index: toolCallInfo.anthropicBlockIndex,
|
2031
|
+
delta: {
|
2032
|
+
type: "input_json_delta",
|
2033
|
+
partial_json: toolCall.function.arguments
|
2034
|
+
}
|
2035
|
+
});
|
2036
|
+
}
|
2037
|
+
}
|
2038
|
+
if (choice.finish_reason) {
|
2039
|
+
if (state$1.contentBlockOpen) {
|
2040
|
+
events$1.push({
|
2041
|
+
type: "content_block_stop",
|
2042
|
+
index: state$1.contentBlockIndex
|
2043
|
+
});
|
2044
|
+
state$1.contentBlockOpen = false;
|
2045
|
+
}
|
2046
|
+
events$1.push({
|
2047
|
+
type: "message_delta",
|
2048
|
+
delta: {
|
2049
|
+
stop_reason: mapOpenAIStopReasonToAnthropic(choice.finish_reason),
|
2050
|
+
stop_sequence: null
|
2051
|
+
},
|
2052
|
+
usage: {
|
2053
|
+
input_tokens: (chunk.usage?.prompt_tokens ?? 0) - (chunk.usage?.prompt_tokens_details?.cached_tokens ?? 0),
|
2054
|
+
output_tokens: chunk.usage?.completion_tokens ?? 0,
|
2055
|
+
...chunk.usage?.prompt_tokens_details?.cached_tokens !== void 0 && { cache_read_input_tokens: chunk.usage.prompt_tokens_details.cached_tokens }
|
2056
|
+
}
|
2057
|
+
}, { type: "message_stop" });
|
2058
|
+
}
|
2059
|
+
return events$1;
|
2060
|
+
}
|
2061
|
+
|
2062
|
+
//#endregion
|
2063
|
+
//#region src/routes/messages/handler.ts
|
2064
|
+
async function handleCompletion(c) {
|
2065
|
+
await checkRateLimit(state);
|
2066
|
+
const anthropicPayload = await c.req.json();
|
2067
|
+
consola.debug("Anthropic request payload:", JSON.stringify(anthropicPayload));
|
2068
|
+
const useResponsesApi = shouldUseResponsesApi(anthropicPayload.model);
|
2069
|
+
if (state.manualApprove) await awaitApproval();
|
2070
|
+
if (useResponsesApi) return await handleWithResponsesApi(c, anthropicPayload);
|
2071
|
+
return await handleWithChatCompletions(c, anthropicPayload);
|
2072
|
+
}
|
2073
|
+
const RESPONSES_ENDPOINT$1 = "/responses";
|
2074
|
+
const handleWithChatCompletions = async (c, anthropicPayload) => {
|
2075
|
+
const openAIPayload = translateToOpenAI(anthropicPayload);
|
2076
|
+
consola.debug("Translated OpenAI request payload:", JSON.stringify(openAIPayload));
|
2077
|
+
const response = await createChatCompletions(openAIPayload);
|
2078
|
+
if (isNonStreaming(response)) {
|
2079
|
+
consola.debug("Non-streaming response from Copilot:", JSON.stringify(response).slice(-400));
|
2080
|
+
const anthropicResponse = translateToAnthropic(response);
|
2081
|
+
consola.debug("Translated Anthropic response:", JSON.stringify(anthropicResponse));
|
2082
|
+
return c.json(anthropicResponse);
|
2083
|
+
}
|
2084
|
+
consola.debug("Streaming response from Copilot");
|
2085
|
+
return streamSSE(c, async (stream) => {
|
2086
|
+
const streamState = {
|
2087
|
+
messageStartSent: false,
|
2088
|
+
contentBlockIndex: 0,
|
2089
|
+
contentBlockOpen: false,
|
2090
|
+
toolCalls: {}
|
2091
|
+
};
|
2092
|
+
for await (const rawEvent of response) {
|
2093
|
+
consola.debug("Copilot raw stream event:", JSON.stringify(rawEvent));
|
2094
|
+
if (rawEvent.data === "[DONE]") break;
|
2095
|
+
if (!rawEvent.data) continue;
|
2096
|
+
const chunk = JSON.parse(rawEvent.data);
|
2097
|
+
const events$1 = translateChunkToAnthropicEvents(chunk, streamState);
|
2098
|
+
for (const event of events$1) {
|
2099
|
+
consola.debug("Translated Anthropic event:", JSON.stringify(event));
|
2100
|
+
await stream.writeSSE({
|
2101
|
+
event: event.type,
|
2102
|
+
data: JSON.stringify(event)
|
2103
|
+
});
|
2104
|
+
}
|
2105
|
+
}
|
2106
|
+
});
|
2107
|
+
};
|
2108
|
+
const handleWithResponsesApi = async (c, anthropicPayload) => {
|
2109
|
+
const responsesPayload = translateAnthropicMessagesToResponsesPayload(anthropicPayload);
|
2110
|
+
consola.debug("Translated Responses payload:", JSON.stringify(responsesPayload));
|
2111
|
+
const { vision, initiator } = getResponsesRequestOptions(responsesPayload);
|
2112
|
+
const response = await createResponses(responsesPayload, {
|
2113
|
+
vision,
|
2114
|
+
initiator
|
2115
|
+
});
|
2116
|
+
if (responsesPayload.stream && isAsyncIterable$1(response)) {
|
2117
|
+
consola.debug("Streaming response from Copilot (Responses API)");
|
2118
|
+
return streamSSE(c, async (stream) => {
|
2119
|
+
const streamState = createResponsesStreamState();
|
2120
|
+
for await (const chunk of response) {
|
2121
|
+
consola.debug("Responses raw stream event:", JSON.stringify(chunk));
|
2122
|
+
if (chunk.event === "ping") {
|
2123
|
+
await stream.writeSSE({
|
2124
|
+
event: "ping",
|
2125
|
+
data: ""
|
2126
|
+
});
|
2127
|
+
continue;
|
2128
|
+
}
|
2129
|
+
const data = chunk.data;
|
2130
|
+
if (!data) continue;
|
2131
|
+
if (data === "[DONE]") break;
|
2132
|
+
const parsed = safeJsonParse(data);
|
2133
|
+
if (!parsed) continue;
|
2134
|
+
const events$1 = translateResponsesStreamEvent(parsed, streamState);
|
2135
|
+
for (const event of events$1) {
|
2136
|
+
consola.debug("Translated Anthropic event:", JSON.stringify(event));
|
2137
|
+
await stream.writeSSE({
|
2138
|
+
event: event.type,
|
2139
|
+
data: JSON.stringify(event)
|
2140
|
+
});
|
2141
|
+
}
|
2142
|
+
}
|
2143
|
+
if (!streamState.messageCompleted) {
|
2144
|
+
consola.warn("Responses stream ended without completion; sending fallback message_stop");
|
2145
|
+
const fallback = { type: "message_stop" };
|
2146
|
+
await stream.writeSSE({
|
2147
|
+
event: fallback.type,
|
2148
|
+
data: JSON.stringify(fallback)
|
2149
|
+
});
|
2150
|
+
}
|
2151
|
+
});
|
2152
|
+
}
|
2153
|
+
consola.debug("Non-streaming Responses result:", JSON.stringify(response).slice(-400));
|
2154
|
+
const anthropicResponse = translateResponsesResultToAnthropic(response);
|
2155
|
+
consola.debug("Translated Anthropic response:", JSON.stringify(anthropicResponse));
|
2156
|
+
return c.json(anthropicResponse);
|
2157
|
+
};
|
2158
|
+
const shouldUseResponsesApi = (modelId) => {
|
2159
|
+
return (state.models?.data.find((model) => model.id === modelId))?.supported_endpoints?.includes(RESPONSES_ENDPOINT$1) ?? false;
|
2160
|
+
};
|
2161
|
+
const isNonStreaming = (response) => Object.hasOwn(response, "choices");
|
2162
|
+
const isAsyncIterable$1 = (value) => Boolean(value) && typeof value[Symbol.asyncIterator] === "function";
|
2163
|
+
const safeJsonParse = (value) => {
|
2164
|
+
try {
|
2165
|
+
return JSON.parse(value);
|
2166
|
+
} catch (error) {
|
2167
|
+
consola.warn("Failed to parse Responses stream chunk:", value, error);
|
2168
|
+
return;
|
2169
|
+
}
|
2170
|
+
};
|
2171
|
+
|
2172
|
+
//#endregion
|
2173
|
+
//#region src/routes/messages/route.ts
|
2174
|
+
const messageRoutes = new Hono();
|
2175
|
+
messageRoutes.post("/", async (c) => {
|
2176
|
+
try {
|
2177
|
+
return await handleCompletion(c);
|
2178
|
+
} catch (error) {
|
2179
|
+
return await forwardError(c, error);
|
2180
|
+
}
|
2181
|
+
});
|
2182
|
+
messageRoutes.post("/count_tokens", async (c) => {
|
2183
|
+
try {
|
2184
|
+
return await handleCountTokens(c);
|
2185
|
+
} catch (error) {
|
2186
|
+
return await forwardError(c, error);
|
2187
|
+
}
|
2188
|
+
});
|
2189
|
+
|
2190
|
+
//#endregion
|
2191
|
+
//#region src/routes/models/route.ts
|
2192
|
+
const modelRoutes = new Hono();
|
2193
|
+
modelRoutes.get("/", async (c) => {
|
2194
|
+
try {
|
2195
|
+
if (!state.models) await cacheModels();
|
2196
|
+
const models = state.models?.data.map((model) => ({
|
2197
|
+
id: model.id,
|
2198
|
+
object: "model",
|
2199
|
+
type: "model",
|
2200
|
+
created: 0,
|
2201
|
+
created_at: (/* @__PURE__ */ new Date(0)).toISOString(),
|
2202
|
+
owned_by: model.vendor,
|
2203
|
+
display_name: model.name
|
2204
|
+
}));
|
2205
|
+
return c.json({
|
2206
|
+
object: "list",
|
2207
|
+
data: models,
|
2208
|
+
has_more: false
|
2209
|
+
});
|
2210
|
+
} catch (error) {
|
2211
|
+
return await forwardError(c, error);
|
2212
|
+
}
|
2213
|
+
});
|
2214
|
+
|
2215
|
+
//#endregion
|
2216
|
+
//#region src/routes/responses/handler.ts
|
2217
|
+
const RESPONSES_ENDPOINT = "/responses";
|
2218
|
+
const handleResponses = async (c) => {
|
2219
|
+
await checkRateLimit(state);
|
2220
|
+
const payload = await c.req.json();
|
2221
|
+
consola.debug("Responses request payload:", JSON.stringify(payload));
|
2222
|
+
if (!((state.models?.data.find((model) => model.id === payload.model))?.supported_endpoints?.includes(RESPONSES_ENDPOINT) ?? false)) return c.json({ error: {
|
2223
|
+
message: "This model does not support the responses endpoint. Please choose a different model.",
|
2224
|
+
type: "invalid_request_error"
|
2225
|
+
} }, 400);
|
2226
|
+
const { vision, initiator } = getResponsesRequestOptions(payload);
|
2227
|
+
if (state.manualApprove) await awaitApproval();
|
2228
|
+
const response = await createResponses(payload, {
|
2229
|
+
vision,
|
2230
|
+
initiator
|
2231
|
+
});
|
2232
|
+
if (isStreamingRequested(payload) && isAsyncIterable(response)) {
|
2233
|
+
consola.debug("Forwarding native Responses stream");
|
2234
|
+
return streamSSE(c, async (stream) => {
|
2235
|
+
const pingInterval = setInterval(async () => {
|
2236
|
+
try {
|
2237
|
+
await stream.writeSSE({
|
2238
|
+
event: "ping",
|
2239
|
+
data: JSON.stringify({ timestamp: Date.now() })
|
2240
|
+
});
|
2241
|
+
} catch (error) {
|
2242
|
+
consola.warn("Failed to send ping:", error);
|
2243
|
+
clearInterval(pingInterval);
|
2244
|
+
}
|
2245
|
+
}, 3e3);
|
2246
|
+
try {
|
2247
|
+
for await (const chunk of response) {
|
2248
|
+
consola.debug("Responses stream chunk:", JSON.stringify(chunk));
|
2249
|
+
await stream.writeSSE({
|
2250
|
+
id: chunk.id,
|
2251
|
+
event: chunk.event,
|
2252
|
+
data: chunk.data ?? ""
|
2253
|
+
});
|
2254
|
+
}
|
2255
|
+
} finally {
|
2256
|
+
clearInterval(pingInterval);
|
2257
|
+
}
|
2258
|
+
});
|
2259
|
+
}
|
2260
|
+
consola.debug("Forwarding native Responses result:", JSON.stringify(response).slice(-400));
|
2261
|
+
return c.json(response);
|
2262
|
+
};
|
2263
|
+
const isAsyncIterable = (value) => Boolean(value) && typeof value[Symbol.asyncIterator] === "function";
|
2264
|
+
const isStreamingRequested = (payload) => Boolean(payload.stream);
|
2265
|
+
|
2266
|
+
//#endregion
|
2267
|
+
//#region src/routes/responses/route.ts
|
2268
|
+
const responsesRoutes = new Hono();
|
2269
|
+
responsesRoutes.post("/", async (c) => {
|
2270
|
+
try {
|
2271
|
+
return await handleResponses(c);
|
2272
|
+
} catch (error) {
|
2273
|
+
return await forwardError(c, error);
|
2274
|
+
}
|
2275
|
+
});
|
2276
|
+
|
2277
|
+
//#endregion
|
2278
|
+
//#region src/routes/token/route.ts
|
2279
|
+
const tokenRoute = new Hono();
|
2280
|
+
tokenRoute.get("/", (c) => {
|
2281
|
+
try {
|
2282
|
+
return c.json({ token: state.copilotToken });
|
2283
|
+
} catch (error) {
|
2284
|
+
console.error("Error fetching token:", error);
|
2285
|
+
return c.json({
|
2286
|
+
error: "Failed to fetch token",
|
2287
|
+
token: null
|
2288
|
+
}, 500);
|
2289
|
+
}
|
2290
|
+
});
|
2291
|
+
|
2292
|
+
//#endregion
|
2293
|
+
//#region src/routes/usage/route.ts
|
2294
|
+
const usageRoute = new Hono();
|
2295
|
+
usageRoute.get("/", async (c) => {
|
2296
|
+
try {
|
2297
|
+
const usage = await getCopilotUsage();
|
2298
|
+
return c.json(usage);
|
2299
|
+
} catch (error) {
|
2300
|
+
console.error("Error fetching Copilot usage:", error);
|
2301
|
+
return c.json({ error: "Failed to fetch Copilot usage" }, 500);
|
2302
|
+
}
|
2303
|
+
});
|
2304
|
+
|
2305
|
+
//#endregion
|
2306
|
+
//#region src/server.ts
|
2307
|
+
const server = new Hono();
|
2308
|
+
server.use(logger());
|
2309
|
+
server.use(cors());
|
2310
|
+
server.get("/", (c) => c.text("Server running"));
|
2311
|
+
server.route("/chat/completions", completionRoutes);
|
2312
|
+
server.route("/models", modelRoutes);
|
2313
|
+
server.route("/embeddings", embeddingRoutes);
|
2314
|
+
server.route("/usage", usageRoute);
|
2315
|
+
server.route("/token", tokenRoute);
|
2316
|
+
server.route("/responses", responsesRoutes);
|
2317
|
+
server.route("/v1/chat/completions", completionRoutes);
|
2318
|
+
server.route("/v1/models", modelRoutes);
|
2319
|
+
server.route("/v1/embeddings", embeddingRoutes);
|
2320
|
+
server.route("/v1/responses", responsesRoutes);
|
2321
|
+
server.route("/v1/messages", messageRoutes);
|
2322
|
+
|
2323
|
+
//#endregion
|
2324
|
+
//#region src/start.ts
|
2325
|
+
async function runServer(options) {
|
2326
|
+
if (options.verbose) {
|
2327
|
+
consola.level = 5;
|
2328
|
+
consola.info("Verbose logging enabled");
|
2329
|
+
}
|
2330
|
+
state.accountType = options.accountType;
|
2331
|
+
if (options.accountType !== "individual") consola.info(`Using ${options.accountType} plan GitHub account`);
|
2332
|
+
state.manualApprove = options.manual;
|
2333
|
+
state.rateLimitSeconds = options.rateLimit;
|
2334
|
+
state.rateLimitWait = options.rateLimitWait;
|
2335
|
+
state.showToken = options.showToken;
|
2336
|
+
await ensurePaths();
|
2337
|
+
await cacheVSCodeVersion();
|
2338
|
+
if (options.githubToken) {
|
2339
|
+
state.githubToken = options.githubToken;
|
2340
|
+
consola.info("Using provided GitHub token");
|
2341
|
+
} else await setupGitHubToken();
|
2342
|
+
await setupCopilotToken();
|
2343
|
+
await cacheModels();
|
2344
|
+
consola.info(`Available models: \n${state.models?.data.map((model) => `- ${model.id}`).join("\n")}`);
|
2345
|
+
const serverUrl = `http://localhost:${options.port}`;
|
2346
|
+
if (options.claudeCode) {
|
2347
|
+
invariant(state.models, "Models should be loaded by now");
|
2348
|
+
const selectedModel = await consola.prompt("Select a model to use with Claude Code", {
|
2349
|
+
type: "select",
|
2350
|
+
options: state.models.data.map((model) => model.id)
|
2351
|
+
});
|
2352
|
+
const selectedSmallModel = await consola.prompt("Select a small model to use with Claude Code", {
|
2353
|
+
type: "select",
|
2354
|
+
options: state.models.data.map((model) => model.id)
|
2355
|
+
});
|
2356
|
+
const command = generateEnvScript({
|
2357
|
+
ANTHROPIC_BASE_URL: serverUrl,
|
2358
|
+
ANTHROPIC_AUTH_TOKEN: "dummy",
|
2359
|
+
ANTHROPIC_MODEL: selectedModel,
|
2360
|
+
ANTHROPIC_DEFAULT_SONNET_MODEL: selectedModel,
|
2361
|
+
ANTHROPIC_SMALL_FAST_MODEL: selectedSmallModel,
|
2362
|
+
ANTHROPIC_DEFAULT_HAIKU_MODEL: selectedSmallModel,
|
2363
|
+
DISABLE_NON_ESSENTIAL_MODEL_CALLS: "1",
|
2364
|
+
CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: "1"
|
2365
|
+
}, "claude");
|
2366
|
+
try {
|
2367
|
+
clipboard.writeSync(command);
|
2368
|
+
consola.success("Copied Claude Code command to clipboard!");
|
2369
|
+
} catch {
|
2370
|
+
consola.warn("Failed to copy to clipboard. Here is the Claude Code command:");
|
2371
|
+
consola.log(command);
|
2372
|
+
}
|
2373
|
+
}
|
2374
|
+
consola.box(`🌐 Usage Viewer: https://ericc-ch.github.io/copilot-api?endpoint=${serverUrl}/usage`);
|
2375
|
+
serve({
|
2376
|
+
fetch: server.fetch,
|
2377
|
+
port: options.port
|
2378
|
+
});
|
2379
|
+
}
|
2380
|
+
const start = defineCommand({
|
2381
|
+
meta: {
|
2382
|
+
name: "start",
|
2383
|
+
description: "Start the Copilot API server"
|
2384
|
+
},
|
2385
|
+
args: {
|
2386
|
+
port: {
|
2387
|
+
alias: "p",
|
2388
|
+
type: "string",
|
2389
|
+
default: "4141",
|
2390
|
+
description: "Port to listen on"
|
2391
|
+
},
|
2392
|
+
verbose: {
|
2393
|
+
alias: "v",
|
2394
|
+
type: "boolean",
|
2395
|
+
default: false,
|
2396
|
+
description: "Enable verbose logging"
|
2397
|
+
},
|
2398
|
+
"account-type": {
|
2399
|
+
alias: "a",
|
2400
|
+
type: "string",
|
2401
|
+
default: "individual",
|
2402
|
+
description: "Account type to use (individual, business, enterprise)"
|
2403
|
+
},
|
2404
|
+
manual: {
|
2405
|
+
type: "boolean",
|
2406
|
+
default: false,
|
2407
|
+
description: "Enable manual request approval"
|
2408
|
+
},
|
2409
|
+
"rate-limit": {
|
2410
|
+
alias: "r",
|
2411
|
+
type: "string",
|
2412
|
+
description: "Rate limit in seconds between requests"
|
2413
|
+
},
|
2414
|
+
wait: {
|
2415
|
+
alias: "w",
|
2416
|
+
type: "boolean",
|
2417
|
+
default: false,
|
2418
|
+
description: "Wait instead of error when rate limit is hit. Has no effect if rate limit is not set"
|
2419
|
+
},
|
2420
|
+
"github-token": {
|
2421
|
+
alias: "g",
|
2422
|
+
type: "string",
|
2423
|
+
description: "Provide GitHub token directly (must be generated using the `auth` subcommand)"
|
2424
|
+
},
|
2425
|
+
"claude-code": {
|
2426
|
+
alias: "c",
|
2427
|
+
type: "boolean",
|
2428
|
+
default: false,
|
2429
|
+
description: "Generate a command to launch Claude Code with Copilot API config"
|
2430
|
+
},
|
2431
|
+
"show-token": {
|
2432
|
+
type: "boolean",
|
2433
|
+
default: false,
|
2434
|
+
description: "Show GitHub and Copilot tokens on fetch and refresh"
|
2435
|
+
}
|
2436
|
+
},
|
2437
|
+
run({ args }) {
|
2438
|
+
const rateLimitRaw = args["rate-limit"];
|
2439
|
+
const rateLimit = rateLimitRaw === void 0 ? void 0 : Number.parseInt(rateLimitRaw, 10);
|
2440
|
+
return runServer({
|
2441
|
+
port: Number.parseInt(args.port, 10),
|
2442
|
+
verbose: args.verbose,
|
2443
|
+
accountType: args["account-type"],
|
2444
|
+
manual: args.manual,
|
2445
|
+
rateLimit,
|
2446
|
+
rateLimitWait: args.wait,
|
2447
|
+
githubToken: args["github-token"],
|
2448
|
+
claudeCode: args["claude-code"],
|
2449
|
+
showToken: args["show-token"]
|
2450
|
+
});
|
2451
|
+
}
|
2452
|
+
});
|
2453
|
+
|
2454
|
+
//#endregion
|
2455
|
+
//#region src/main.ts
|
2456
|
+
const main = defineCommand({
|
2457
|
+
meta: {
|
2458
|
+
name: "heibaiapi",
|
2459
|
+
description: "A wrapper around GitHub Copilot API to make it OpenAI compatible, making it usable for other tools."
|
2460
|
+
},
|
2461
|
+
subCommands: {
|
2462
|
+
auth,
|
2463
|
+
start,
|
2464
|
+
"check-usage": checkUsage,
|
2465
|
+
debug
|
2466
|
+
}
|
2467
|
+
});
|
2468
|
+
initProxyFromEnv();
|
2469
|
+
await runMain(main);
|
2470
|
+
|
2471
|
+
//#endregion
|
2472
|
+
export { };
|
2473
|
+
//# sourceMappingURL=main.js.map
|