@ashsec/copilot-api 0.7.12 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/main.js +1544 -226
- package/dist/main.js.map +1 -1
- package/package.json +2 -1
package/dist/main.js
CHANGED
|
@@ -9,6 +9,7 @@ import { events } from "fetch-event-stream";
|
|
|
9
9
|
import clipboard from "clipboardy";
|
|
10
10
|
import { serve } from "srvx";
|
|
11
11
|
import invariant from "tiny-invariant";
|
|
12
|
+
import fs$1 from "node:fs";
|
|
12
13
|
import { getProxyForUrl } from "proxy-from-env";
|
|
13
14
|
import { Agent, ProxyAgent, setGlobalDispatcher } from "undici";
|
|
14
15
|
import { execSync } from "node:child_process";
|
|
@@ -16,84 +17,10 @@ import process$1 from "node:process";
|
|
|
16
17
|
import { Hono } from "hono";
|
|
17
18
|
import { cors } from "hono/cors";
|
|
18
19
|
import { streamSSE } from "hono/streaming";
|
|
20
|
+
import util from "node:util";
|
|
19
21
|
|
|
20
22
|
//#region package.json
|
|
21
|
-
var
|
|
22
|
-
var version = "0.7.12";
|
|
23
|
-
var description = "Turn GitHub Copilot into OpenAI/Anthropic API compatible server. Usable with Claude Code!";
|
|
24
|
-
var keywords = [
|
|
25
|
-
"proxy",
|
|
26
|
-
"github-copilot",
|
|
27
|
-
"openai-compatible"
|
|
28
|
-
];
|
|
29
|
-
var homepage = "https://github.com/ericc-ch/copilot-api";
|
|
30
|
-
var bugs = "https://github.com/ericc-ch/copilot-api/issues";
|
|
31
|
-
var repository = {
|
|
32
|
-
"type": "git",
|
|
33
|
-
"url": "git+https://github.com/ericc-ch/copilot-api.git"
|
|
34
|
-
};
|
|
35
|
-
var author = "Erick Christian <erickchristian48@gmail.com>";
|
|
36
|
-
var type = "module";
|
|
37
|
-
var bin = { "copilot-api": "./dist/main.js" };
|
|
38
|
-
var files = ["dist"];
|
|
39
|
-
var scripts = {
|
|
40
|
-
"build": "tsdown",
|
|
41
|
-
"dev": "bun run --watch ./src/main.ts",
|
|
42
|
-
"knip": "knip-bun",
|
|
43
|
-
"lint": "eslint --cache",
|
|
44
|
-
"lint:all": "eslint --cache .",
|
|
45
|
-
"prepack": "bun run build",
|
|
46
|
-
"prepare": "simple-git-hooks",
|
|
47
|
-
"release": "bumpp && bun publish --access public",
|
|
48
|
-
"start": "NODE_ENV=production bun run ./src/main.ts",
|
|
49
|
-
"typecheck": "tsc"
|
|
50
|
-
};
|
|
51
|
-
var simple_git_hooks = { "pre-commit": "bunx lint-staged" };
|
|
52
|
-
var lint_staged = { "*": "bun run lint --fix" };
|
|
53
|
-
var dependencies = {
|
|
54
|
-
"citty": "^0.1.6",
|
|
55
|
-
"clipboardy": "^5.0.0",
|
|
56
|
-
"consola": "^3.4.2",
|
|
57
|
-
"fetch-event-stream": "^0.1.5",
|
|
58
|
-
"gpt-tokenizer": "^3.0.1",
|
|
59
|
-
"hono": "^4.9.9",
|
|
60
|
-
"proxy-from-env": "^1.1.0",
|
|
61
|
-
"srvx": "^0.8.9",
|
|
62
|
-
"tiny-invariant": "^1.3.3",
|
|
63
|
-
"undici": "^7.16.0",
|
|
64
|
-
"zod": "^4.1.11"
|
|
65
|
-
};
|
|
66
|
-
var devDependencies = {
|
|
67
|
-
"@echristian/eslint-config": "^0.0.54",
|
|
68
|
-
"@types/bun": "^1.2.23",
|
|
69
|
-
"@types/proxy-from-env": "^1.0.4",
|
|
70
|
-
"bumpp": "^10.2.3",
|
|
71
|
-
"eslint": "^9.37.0",
|
|
72
|
-
"knip": "^5.64.1",
|
|
73
|
-
"lint-staged": "^16.2.3",
|
|
74
|
-
"prettier-plugin-packagejson": "^2.5.19",
|
|
75
|
-
"simple-git-hooks": "^2.13.1",
|
|
76
|
-
"tsdown": "^0.15.6",
|
|
77
|
-
"typescript": "^5.9.3"
|
|
78
|
-
};
|
|
79
|
-
var package_default = {
|
|
80
|
-
name,
|
|
81
|
-
version,
|
|
82
|
-
description,
|
|
83
|
-
keywords,
|
|
84
|
-
homepage,
|
|
85
|
-
bugs,
|
|
86
|
-
repository,
|
|
87
|
-
author,
|
|
88
|
-
type,
|
|
89
|
-
bin,
|
|
90
|
-
files,
|
|
91
|
-
scripts,
|
|
92
|
-
"simple-git-hooks": simple_git_hooks,
|
|
93
|
-
"lint-staged": lint_staged,
|
|
94
|
-
dependencies,
|
|
95
|
-
devDependencies
|
|
96
|
-
};
|
|
23
|
+
var version = "0.8.0";
|
|
97
24
|
|
|
98
25
|
//#endregion
|
|
99
26
|
//#region src/lib/paths.ts
|
|
@@ -103,6 +30,7 @@ const AZURE_OPENAI_CONFIG_PATH = path.join(APP_DIR, "azure_openai_config");
|
|
|
103
30
|
const REPLACEMENTS_CONFIG_PATH = path.join(APP_DIR, "replacements.json");
|
|
104
31
|
const PATHS = {
|
|
105
32
|
APP_DIR,
|
|
33
|
+
CONFIG_PATH: path.join(APP_DIR, "config.json"),
|
|
106
34
|
GITHUB_TOKEN_PATH,
|
|
107
35
|
AZURE_OPENAI_CONFIG_PATH,
|
|
108
36
|
REPLACEMENTS_CONFIG_PATH
|
|
@@ -128,7 +56,8 @@ const state = {
|
|
|
128
56
|
manualApprove: false,
|
|
129
57
|
rateLimitWait: false,
|
|
130
58
|
showToken: false,
|
|
131
|
-
debug: false
|
|
59
|
+
debug: false,
|
|
60
|
+
verbose: false
|
|
132
61
|
};
|
|
133
62
|
|
|
134
63
|
//#endregion
|
|
@@ -137,10 +66,10 @@ const standardHeaders = () => ({
|
|
|
137
66
|
"content-type": "application/json",
|
|
138
67
|
accept: "application/json"
|
|
139
68
|
});
|
|
140
|
-
const COPILOT_VERSION = "0.
|
|
69
|
+
const COPILOT_VERSION = "0.37.6";
|
|
141
70
|
const EDITOR_PLUGIN_VERSION = `copilot-chat/${COPILOT_VERSION}`;
|
|
142
71
|
const USER_AGENT = `GitHubCopilotChat/${COPILOT_VERSION}`;
|
|
143
|
-
const API_VERSION = "2025-
|
|
72
|
+
const API_VERSION = "2025-10-01";
|
|
144
73
|
const copilotBaseUrl = (state$1) => state$1.accountType === "individual" ? "https://api.githubcopilot.com" : `https://api.${state$1.accountType}.githubcopilot.com`;
|
|
145
74
|
const copilotHeaders = (state$1, vision = false) => {
|
|
146
75
|
const headers = {
|
|
@@ -150,7 +79,7 @@ const copilotHeaders = (state$1, vision = false) => {
|
|
|
150
79
|
"editor-version": `vscode/${state$1.vsCodeVersion}`,
|
|
151
80
|
"editor-plugin-version": EDITOR_PLUGIN_VERSION,
|
|
152
81
|
"user-agent": USER_AGENT,
|
|
153
|
-
"openai-intent": "conversation-
|
|
82
|
+
"openai-intent": "conversation-agent",
|
|
154
83
|
"x-github-api-version": API_VERSION,
|
|
155
84
|
"x-request-id": randomUUID(),
|
|
156
85
|
"x-vscode-user-agent-library-version": "electron-fetch"
|
|
@@ -654,13 +583,13 @@ const checkUsage = defineCommand({
|
|
|
654
583
|
const premiumUsed = premiumTotal - premium.remaining;
|
|
655
584
|
const premiumPercentUsed = premiumTotal > 0 ? premiumUsed / premiumTotal * 100 : 0;
|
|
656
585
|
const premiumPercentRemaining = premium.percent_remaining;
|
|
657
|
-
function summarizeQuota(name
|
|
658
|
-
if (!snap) return `${name
|
|
586
|
+
function summarizeQuota(name, snap) {
|
|
587
|
+
if (!snap) return `${name}: N/A`;
|
|
659
588
|
const total = snap.entitlement;
|
|
660
589
|
const used = total - snap.remaining;
|
|
661
590
|
const percentUsed = total > 0 ? used / total * 100 : 0;
|
|
662
591
|
const percentRemaining = snap.percent_remaining;
|
|
663
|
-
return `${name
|
|
592
|
+
return `${name}: ${used}/${total} used (${percentUsed.toFixed(1)}% used, ${percentRemaining.toFixed(1)}% remaining)`;
|
|
664
593
|
}
|
|
665
594
|
const premiumLine = `Premium: ${premiumUsed}/${premiumTotal} used (${premiumPercentUsed.toFixed(1)}% used, ${premiumPercentRemaining.toFixed(1)}% remaining)`;
|
|
666
595
|
const chatLine = summarizeQuota("Chat", usage.quota_snapshots.chat);
|
|
@@ -736,11 +665,11 @@ async function getUserReplacements() {
|
|
|
736
665
|
* Add a new user replacement rule
|
|
737
666
|
*/
|
|
738
667
|
async function addReplacement(pattern, replacement, options) {
|
|
739
|
-
const { isRegex = false, name
|
|
668
|
+
const { isRegex = false, name } = options ?? {};
|
|
740
669
|
await ensureLoaded();
|
|
741
670
|
const rule = {
|
|
742
671
|
id: `user-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`,
|
|
743
|
-
name
|
|
672
|
+
name,
|
|
744
673
|
pattern,
|
|
745
674
|
replacement,
|
|
746
675
|
isRegex,
|
|
@@ -891,11 +820,11 @@ async function applyReplacementsToPayload(payload) {
|
|
|
891
820
|
//#region src/config.ts
|
|
892
821
|
function formatRule(rule, index) {
|
|
893
822
|
const status = rule.enabled ? "✓" : "✗";
|
|
894
|
-
const type
|
|
823
|
+
const type = rule.isRegex ? "regex" : "string";
|
|
895
824
|
const system = rule.isSystem ? " [system]" : "";
|
|
896
|
-
const name
|
|
825
|
+
const name = rule.name ? ` "${rule.name}"` : "";
|
|
897
826
|
const replacement = rule.replacement || "(empty)";
|
|
898
|
-
return `${index + 1}. [${status}] (${type
|
|
827
|
+
return `${index + 1}. [${status}] (${type})${system}${name} "${rule.pattern}" → "${replacement}"`;
|
|
899
828
|
}
|
|
900
829
|
async function listReplacements() {
|
|
901
830
|
const all = await getAllReplacements();
|
|
@@ -908,11 +837,11 @@ async function listReplacements() {
|
|
|
908
837
|
console.log();
|
|
909
838
|
}
|
|
910
839
|
async function addNewReplacement() {
|
|
911
|
-
const name
|
|
840
|
+
const name = await consola.prompt("Name (optional, short description):", {
|
|
912
841
|
type: "text",
|
|
913
842
|
default: ""
|
|
914
843
|
});
|
|
915
|
-
if (typeof name
|
|
844
|
+
if (typeof name === "symbol") {
|
|
916
845
|
consola.info("Cancelled.");
|
|
917
846
|
return;
|
|
918
847
|
}
|
|
@@ -949,7 +878,7 @@ async function addNewReplacement() {
|
|
|
949
878
|
consola.info("Cancelled.");
|
|
950
879
|
return;
|
|
951
880
|
}
|
|
952
|
-
const rule = await addReplacement(pattern, replacement, matchType === "regex", name
|
|
881
|
+
const rule = await addReplacement(pattern, replacement, matchType === "regex", name || void 0);
|
|
953
882
|
consola.success(`Added rule: ${rule.name || rule.id}`);
|
|
954
883
|
}
|
|
955
884
|
async function editExistingReplacement() {
|
|
@@ -977,11 +906,11 @@ async function editExistingReplacement() {
|
|
|
977
906
|
}
|
|
978
907
|
consola.info(`\nEditing rule: ${rule.name || rule.id}`);
|
|
979
908
|
consola.info("Press Enter to keep current value.\n");
|
|
980
|
-
const name
|
|
909
|
+
const name = await consola.prompt("Name:", {
|
|
981
910
|
type: "text",
|
|
982
911
|
default: rule.name || ""
|
|
983
912
|
});
|
|
984
|
-
if (typeof name
|
|
913
|
+
if (typeof name === "symbol") {
|
|
985
914
|
consola.info("Cancelled.");
|
|
986
915
|
return;
|
|
987
916
|
}
|
|
@@ -1023,7 +952,7 @@ async function editExistingReplacement() {
|
|
|
1023
952
|
return;
|
|
1024
953
|
}
|
|
1025
954
|
const updated = await updateReplacement(selected, {
|
|
1026
|
-
name: name
|
|
955
|
+
name: name || void 0,
|
|
1027
956
|
pattern,
|
|
1028
957
|
replacement,
|
|
1029
958
|
isRegex: matchType === "regex"
|
|
@@ -1254,6 +1183,118 @@ const debug = defineCommand({
|
|
|
1254
1183
|
}
|
|
1255
1184
|
});
|
|
1256
1185
|
|
|
1186
|
+
//#endregion
|
|
1187
|
+
//#region src/lib/config.ts
|
|
1188
|
+
const gpt5ExplorationPrompt = `## Exploration and reading files
|
|
1189
|
+
- **Think first.** Before any tool call, decide ALL files/resources you will need.
|
|
1190
|
+
- **Batch everything.** If you need multiple files (even from different places), read them together.
|
|
1191
|
+
- **multi_tool_use.parallel** Use multi_tool_use.parallel to parallelize tool calls and only this.
|
|
1192
|
+
- **Only make sequential calls if you truly cannot know the next file without seeing a result first.**
|
|
1193
|
+
- **Workflow:** (a) plan all needed reads → (b) issue one parallel batch → (c) analyze results → (d) repeat if new, unpredictable reads arise.`;
|
|
1194
|
+
const gpt5CommentaryPrompt = `# Working with the user
|
|
1195
|
+
|
|
1196
|
+
You interact with the user through a terminal. You have 2 ways of communicating with the users:
|
|
1197
|
+
- Share intermediary updates in \`commentary\` channel.
|
|
1198
|
+
- After you have completed all your work, send a message to the \`final\` channel.
|
|
1199
|
+
|
|
1200
|
+
## Intermediary updates
|
|
1201
|
+
|
|
1202
|
+
- Intermediary updates go to the \`commentary\` channel.
|
|
1203
|
+
- User updates are short updates while you are working, they are NOT final answers.
|
|
1204
|
+
- You use 1-2 sentence user updates to communicate progress and new information to the user as you are doing work.
|
|
1205
|
+
- Do not begin responses with conversational interjections or meta commentary. Avoid openers such as acknowledgements ("Done —", "Got it", "Great question, ") or framing phrases.
|
|
1206
|
+
- You provide user updates frequently, every 20s.
|
|
1207
|
+
- Before exploring or doing substantial work, you start with a user update acknowledging the request and explaining your first step. You should include your understanding of the user request and explain what you will do. Avoid commenting on the request or using starters such as "Got it -" or "Understood -" etc.
|
|
1208
|
+
- When exploring, e.g. searching, reading files, you provide user updates as you go, every 20s, explaining what context you are gathering and what you've learned. Vary your sentence structure when providing these updates to avoid sounding repetitive - in particular, don't start each sentence the same way.
|
|
1209
|
+
- After you have sufficient context, and the work is substantial, you provide a longer plan (this is the only user update that may be longer than 2 sentences and can contain formatting).
|
|
1210
|
+
- Before performing file edits of any kind, you provide updates explaining what edits you are making.
|
|
1211
|
+
- As you are thinking, you very frequently provide updates even if not taking any actions, informing the user of your progress. You interrupt your thinking and send multiple updates in a row if thinking for more than 100 words.
|
|
1212
|
+
- Tone of your updates MUST match your personality.`;
|
|
1213
|
+
const defaultConfig = {
|
|
1214
|
+
auth: { apiKeys: [] },
|
|
1215
|
+
extraPrompts: {
|
|
1216
|
+
"gpt-5-mini": gpt5ExplorationPrompt,
|
|
1217
|
+
"gpt-5.1-codex-max": gpt5ExplorationPrompt,
|
|
1218
|
+
"gpt-5.3-codex": gpt5CommentaryPrompt
|
|
1219
|
+
},
|
|
1220
|
+
smallModel: "gpt-5-mini",
|
|
1221
|
+
modelReasoningEfforts: { "gpt-5-mini": "low" },
|
|
1222
|
+
useFunctionApplyPatch: true,
|
|
1223
|
+
compactUseSmallModel: true
|
|
1224
|
+
};
|
|
1225
|
+
let cachedConfig = null;
|
|
1226
|
+
function ensureConfigFile() {
|
|
1227
|
+
try {
|
|
1228
|
+
fs$1.accessSync(PATHS.CONFIG_PATH, fs$1.constants.R_OK | fs$1.constants.W_OK);
|
|
1229
|
+
} catch {
|
|
1230
|
+
fs$1.mkdirSync(PATHS.APP_DIR, { recursive: true });
|
|
1231
|
+
fs$1.writeFileSync(PATHS.CONFIG_PATH, `${JSON.stringify(defaultConfig, null, 2)}\n`, "utf8");
|
|
1232
|
+
try {
|
|
1233
|
+
fs$1.chmodSync(PATHS.CONFIG_PATH, 384);
|
|
1234
|
+
} catch {
|
|
1235
|
+
return;
|
|
1236
|
+
}
|
|
1237
|
+
}
|
|
1238
|
+
}
|
|
1239
|
+
function readConfigFromDisk() {
|
|
1240
|
+
ensureConfigFile();
|
|
1241
|
+
try {
|
|
1242
|
+
const raw = fs$1.readFileSync(PATHS.CONFIG_PATH, "utf8");
|
|
1243
|
+
if (!raw.trim()) {
|
|
1244
|
+
fs$1.writeFileSync(PATHS.CONFIG_PATH, `${JSON.stringify(defaultConfig, null, 2)}\n`, "utf8");
|
|
1245
|
+
return defaultConfig;
|
|
1246
|
+
}
|
|
1247
|
+
return JSON.parse(raw);
|
|
1248
|
+
} catch (error) {
|
|
1249
|
+
consola.error("Failed to read config file, using default config", error);
|
|
1250
|
+
return defaultConfig;
|
|
1251
|
+
}
|
|
1252
|
+
}
|
|
1253
|
+
function mergeDefaultExtraPrompts(config$1) {
|
|
1254
|
+
const extraPrompts = config$1.extraPrompts ?? {};
|
|
1255
|
+
const defaultExtraPrompts = defaultConfig.extraPrompts ?? {};
|
|
1256
|
+
if (Object.keys(defaultExtraPrompts).filter((model) => !Object.hasOwn(extraPrompts, model)).length === 0) return {
|
|
1257
|
+
mergedConfig: config$1,
|
|
1258
|
+
changed: false
|
|
1259
|
+
};
|
|
1260
|
+
return {
|
|
1261
|
+
mergedConfig: {
|
|
1262
|
+
...config$1,
|
|
1263
|
+
extraPrompts: {
|
|
1264
|
+
...defaultExtraPrompts,
|
|
1265
|
+
...extraPrompts
|
|
1266
|
+
}
|
|
1267
|
+
},
|
|
1268
|
+
changed: true
|
|
1269
|
+
};
|
|
1270
|
+
}
|
|
1271
|
+
function mergeConfigWithDefaults() {
|
|
1272
|
+
const { mergedConfig, changed } = mergeDefaultExtraPrompts(readConfigFromDisk());
|
|
1273
|
+
if (changed) try {
|
|
1274
|
+
fs$1.writeFileSync(PATHS.CONFIG_PATH, `${JSON.stringify(mergedConfig, null, 2)}\n`, "utf8");
|
|
1275
|
+
} catch (writeError) {
|
|
1276
|
+
consola.warn("Failed to write merged extraPrompts to config file", writeError);
|
|
1277
|
+
}
|
|
1278
|
+
cachedConfig = mergedConfig;
|
|
1279
|
+
return mergedConfig;
|
|
1280
|
+
}
|
|
1281
|
+
function getConfig() {
|
|
1282
|
+
cachedConfig ??= readConfigFromDisk();
|
|
1283
|
+
return cachedConfig;
|
|
1284
|
+
}
|
|
1285
|
+
function getExtraPromptForModel(model) {
|
|
1286
|
+
return getConfig().extraPrompts?.[model] ?? "";
|
|
1287
|
+
}
|
|
1288
|
+
function getSmallModel() {
|
|
1289
|
+
return getConfig().smallModel ?? "gpt-5-mini";
|
|
1290
|
+
}
|
|
1291
|
+
function getReasoningEffortForModel(model) {
|
|
1292
|
+
return getConfig().modelReasoningEfforts?.[model] ?? "high";
|
|
1293
|
+
}
|
|
1294
|
+
function shouldCompactUseSmallModel() {
|
|
1295
|
+
return getConfig().compactUseSmallModel ?? true;
|
|
1296
|
+
}
|
|
1297
|
+
|
|
1257
1298
|
//#endregion
|
|
1258
1299
|
//#region src/lib/proxy.ts
|
|
1259
1300
|
function initProxyFromEnv() {
|
|
@@ -1306,8 +1347,7 @@ function getShell() {
|
|
|
1306
1347
|
const { platform, ppid, env } = process$1;
|
|
1307
1348
|
if (platform === "win32") {
|
|
1308
1349
|
try {
|
|
1309
|
-
|
|
1310
|
-
if (execSync(command, { stdio: "pipe" }).toString().toLowerCase().includes("powershell.exe")) return "powershell";
|
|
1350
|
+
if (execSync(`wmic process get ParentProcessId,Name | findstr "${ppid}"`, { stdio: "pipe" }).toString().toLowerCase().includes("powershell.exe")) return "powershell";
|
|
1311
1351
|
} catch {
|
|
1312
1352
|
return "cmd";
|
|
1313
1353
|
}
|
|
@@ -1353,6 +1393,51 @@ function generateEnvScript(envVars, commandToRun = "") {
|
|
|
1353
1393
|
return commandBlock || commandToRun;
|
|
1354
1394
|
}
|
|
1355
1395
|
|
|
1396
|
+
//#endregion
|
|
1397
|
+
//#region src/lib/request-auth.ts
|
|
1398
|
+
function normalizeApiKeys(apiKeys) {
|
|
1399
|
+
if (!Array.isArray(apiKeys)) {
|
|
1400
|
+
if (apiKeys !== void 0) consola.warn("Invalid auth.apiKeys config. Expected an array of strings.");
|
|
1401
|
+
return [];
|
|
1402
|
+
}
|
|
1403
|
+
const normalizedKeys = apiKeys.filter((key) => typeof key === "string").map((key) => key.trim()).filter((key) => key.length > 0);
|
|
1404
|
+
if (normalizedKeys.length !== apiKeys.length) consola.warn("Invalid auth.apiKeys entries found. Only non-empty strings are allowed.");
|
|
1405
|
+
return [...new Set(normalizedKeys)];
|
|
1406
|
+
}
|
|
1407
|
+
function getConfiguredApiKeys() {
|
|
1408
|
+
return normalizeApiKeys(getConfig().auth?.apiKeys);
|
|
1409
|
+
}
|
|
1410
|
+
function extractRequestApiKey(c) {
|
|
1411
|
+
const xApiKey = c.req.header("x-api-key")?.trim();
|
|
1412
|
+
if (xApiKey) return xApiKey;
|
|
1413
|
+
const authorization = c.req.header("authorization");
|
|
1414
|
+
if (!authorization) return null;
|
|
1415
|
+
const [scheme, ...rest] = authorization.trim().split(/\s+/);
|
|
1416
|
+
if (scheme.toLowerCase() !== "bearer") return null;
|
|
1417
|
+
return rest.join(" ").trim() || null;
|
|
1418
|
+
}
|
|
1419
|
+
function createUnauthorizedResponse(c) {
|
|
1420
|
+
c.header("WWW-Authenticate", "Bearer realm=\"copilot-api\"");
|
|
1421
|
+
return c.json({ error: {
|
|
1422
|
+
message: "Unauthorized",
|
|
1423
|
+
type: "authentication_error"
|
|
1424
|
+
} }, 401);
|
|
1425
|
+
}
|
|
1426
|
+
function createAuthMiddleware(options = {}) {
|
|
1427
|
+
const getApiKeys = options.getApiKeys ?? getConfiguredApiKeys;
|
|
1428
|
+
const allowUnauthenticatedPaths = options.allowUnauthenticatedPaths ?? ["/"];
|
|
1429
|
+
const allowOptionsBypass = options.allowOptionsBypass ?? true;
|
|
1430
|
+
return async (c, next) => {
|
|
1431
|
+
if (allowOptionsBypass && c.req.method === "OPTIONS") return next();
|
|
1432
|
+
if (allowUnauthenticatedPaths.includes(c.req.path)) return next();
|
|
1433
|
+
const apiKeys = getApiKeys();
|
|
1434
|
+
if (apiKeys.length === 0) return next();
|
|
1435
|
+
const requestApiKey = extractRequestApiKey(c);
|
|
1436
|
+
if (!requestApiKey || !apiKeys.includes(requestApiKey)) return createUnauthorizedResponse(c);
|
|
1437
|
+
return next();
|
|
1438
|
+
};
|
|
1439
|
+
}
|
|
1440
|
+
|
|
1356
1441
|
//#endregion
|
|
1357
1442
|
//#region src/lib/request-logger.ts
|
|
1358
1443
|
const REQUEST_CONTEXT_KEY = "requestContext";
|
|
@@ -1413,8 +1498,7 @@ async function logRawRequest(c) {
|
|
|
1413
1498
|
if (method !== "GET" && method !== "HEAD") try {
|
|
1414
1499
|
const body = await c.req.raw.clone().text();
|
|
1415
1500
|
if (body) try {
|
|
1416
|
-
const
|
|
1417
|
-
const sanitized = sanitizeRequestBody(parsed);
|
|
1501
|
+
const sanitized = sanitizeRequestBody(JSON.parse(body));
|
|
1418
1502
|
lines.push(`${colors.dim}Body (sanitized):${colors.reset}`, ` ${JSON.stringify(sanitized, null, 2).split("\n").join("\n ")}`);
|
|
1419
1503
|
} catch {
|
|
1420
1504
|
lines.push(`${colors.dim}Body:${colors.reset} [${body.length} bytes]`);
|
|
@@ -1694,8 +1778,7 @@ const numTokensForTools = (tools, encoder, constants) => {
|
|
|
1694
1778
|
* Calculate the token count of messages, supporting multiple GPT encoders
|
|
1695
1779
|
*/
|
|
1696
1780
|
const getTokenCount = async (payload, model) => {
|
|
1697
|
-
const
|
|
1698
|
-
const encoder = await getEncodeChatFunction(tokenizer);
|
|
1781
|
+
const encoder = await getEncodeChatFunction(getTokenizerFromModel(model));
|
|
1699
1782
|
const simplifiedMessages = payload.messages;
|
|
1700
1783
|
const inputMessages = simplifiedMessages.filter((msg) => msg.role !== "assistant");
|
|
1701
1784
|
const outputMessages = simplifiedMessages.filter((msg) => msg.role === "assistant");
|
|
@@ -1711,13 +1794,17 @@ const getTokenCount = async (payload, model) => {
|
|
|
1711
1794
|
|
|
1712
1795
|
//#endregion
|
|
1713
1796
|
//#region src/services/copilot/create-chat-completions.ts
|
|
1714
|
-
const createChatCompletions = async (payload) => {
|
|
1797
|
+
const createChatCompletions = async (payload, options) => {
|
|
1715
1798
|
if (!state.copilotToken) throw new Error("Copilot token not found");
|
|
1716
1799
|
const enableVision = payload.messages.some((x) => typeof x.content !== "string" && x.content?.some((x$1) => x$1.type === "image_url"));
|
|
1717
|
-
|
|
1800
|
+
let isAgentCall = false;
|
|
1801
|
+
if (payload.messages.length > 0) {
|
|
1802
|
+
const lastMessage = payload.messages.at(-1);
|
|
1803
|
+
if (lastMessage) isAgentCall = ["assistant", "tool"].includes(lastMessage.role);
|
|
1804
|
+
}
|
|
1718
1805
|
const headers = {
|
|
1719
1806
|
...copilotHeaders(state, enableVision),
|
|
1720
|
-
"X-Initiator": isAgentCall ? "agent" : "user"
|
|
1807
|
+
"X-Initiator": options?.initiator ?? (isAgentCall ? "agent" : "user")
|
|
1721
1808
|
};
|
|
1722
1809
|
const response = await fetchWithRetry(`${copilotBaseUrl(state)}/chat/completions`, {
|
|
1723
1810
|
method: "POST",
|
|
@@ -1736,8 +1823,7 @@ const createChatCompletions = async (payload) => {
|
|
|
1736
1823
|
//#region src/routes/chat-completions/handler.ts
|
|
1737
1824
|
async function handleCompletion$1(c) {
|
|
1738
1825
|
await checkRateLimit(state);
|
|
1739
|
-
|
|
1740
|
-
let payload = await applyReplacementsToPayload(rawPayload);
|
|
1826
|
+
let payload = await applyReplacementsToPayload(await c.req.json());
|
|
1741
1827
|
payload = {
|
|
1742
1828
|
...payload,
|
|
1743
1829
|
model: normalizeModelName(payload.model)
|
|
@@ -1751,7 +1837,7 @@ async function handleCompletion$1(c) {
|
|
|
1751
1837
|
});
|
|
1752
1838
|
if (state.manualApprove) await awaitApproval();
|
|
1753
1839
|
const response$1 = await createAzureOpenAIChatCompletions(state.azureOpenAIConfig, payload);
|
|
1754
|
-
if (isNonStreaming(response$1)) {
|
|
1840
|
+
if (isNonStreaming$1(response$1)) {
|
|
1755
1841
|
consola.debug("Non-streaming response:", JSON.stringify(response$1));
|
|
1756
1842
|
if (response$1.usage) setRequestContext(c, {
|
|
1757
1843
|
inputTokens: response$1.usage.prompt_tokens,
|
|
@@ -1780,10 +1866,7 @@ async function handleCompletion$1(c) {
|
|
|
1780
1866
|
});
|
|
1781
1867
|
const selectedModel = state.models?.data.find((model) => model.id === payload.model);
|
|
1782
1868
|
try {
|
|
1783
|
-
if (selectedModel) {
|
|
1784
|
-
const tokenCount = await getTokenCount(payload, selectedModel);
|
|
1785
|
-
setRequestContext(c, { inputTokens: tokenCount.input });
|
|
1786
|
-
}
|
|
1869
|
+
if (selectedModel) setRequestContext(c, { inputTokens: (await getTokenCount(payload, selectedModel)).input });
|
|
1787
1870
|
} catch (error) {
|
|
1788
1871
|
consola.warn("Failed to calculate token count:", error);
|
|
1789
1872
|
}
|
|
@@ -1796,7 +1879,7 @@ async function handleCompletion$1(c) {
|
|
|
1796
1879
|
consola.debug("Set max_tokens to:", JSON.stringify(payload.max_tokens));
|
|
1797
1880
|
}
|
|
1798
1881
|
const response = await createChatCompletions(payload);
|
|
1799
|
-
if (isNonStreaming(response)) {
|
|
1882
|
+
if (isNonStreaming$1(response)) {
|
|
1800
1883
|
consola.debug("Non-streaming response:", JSON.stringify(response));
|
|
1801
1884
|
if (response.usage) setRequestContext(c, {
|
|
1802
1885
|
inputTokens: response.usage.prompt_tokens,
|
|
@@ -1819,7 +1902,7 @@ async function handleCompletion$1(c) {
|
|
|
1819
1902
|
}
|
|
1820
1903
|
});
|
|
1821
1904
|
}
|
|
1822
|
-
const isNonStreaming = (response) => Object.hasOwn(response, "choices");
|
|
1905
|
+
const isNonStreaming$1 = (response) => Object.hasOwn(response, "choices");
|
|
1823
1906
|
|
|
1824
1907
|
//#endregion
|
|
1825
1908
|
//#region src/routes/chat-completions/route.ts
|
|
@@ -1850,8 +1933,7 @@ const createEmbeddings = async (payload) => {
|
|
|
1850
1933
|
const embeddingRoutes = new Hono();
|
|
1851
1934
|
embeddingRoutes.post("/", async (c) => {
|
|
1852
1935
|
try {
|
|
1853
|
-
const
|
|
1854
|
-
const response = await createEmbeddings(paylod);
|
|
1936
|
+
const response = await createEmbeddings(await c.req.json());
|
|
1855
1937
|
return c.json(response);
|
|
1856
1938
|
} catch (error) {
|
|
1857
1939
|
return await forwardError(c, error);
|
|
@@ -2029,6 +2111,7 @@ function extractContentFromChoices(response) {
|
|
|
2029
2111
|
}
|
|
2030
2112
|
function buildAnthropicResponse(response, options) {
|
|
2031
2113
|
const { contentBlocks, stopReason, originalModel } = options;
|
|
2114
|
+
const cachedTokens = response.usage?.prompt_tokens_details?.cached_tokens ?? 0;
|
|
2032
2115
|
return {
|
|
2033
2116
|
id: response.id,
|
|
2034
2117
|
type: "message",
|
|
@@ -2038,9 +2121,9 @@ function buildAnthropicResponse(response, options) {
|
|
|
2038
2121
|
stop_reason: mapOpenAIStopReasonToAnthropic(stopReason),
|
|
2039
2122
|
stop_sequence: null,
|
|
2040
2123
|
usage: {
|
|
2041
|
-
input_tokens: (response.usage?.prompt_tokens ?? 0) -
|
|
2124
|
+
input_tokens: (response.usage?.prompt_tokens ?? 0) - cachedTokens,
|
|
2042
2125
|
output_tokens: response.usage?.completion_tokens ?? 0,
|
|
2043
|
-
...
|
|
2126
|
+
...cachedTokens > 0 && { cache_read_input_tokens: cachedTokens }
|
|
2044
2127
|
}
|
|
2045
2128
|
};
|
|
2046
2129
|
}
|
|
@@ -2101,6 +2184,970 @@ async function handleCountTokens(c) {
|
|
|
2101
2184
|
}
|
|
2102
2185
|
}
|
|
2103
2186
|
|
|
2187
|
+
//#endregion
|
|
2188
|
+
//#region src/lib/logger.ts
|
|
2189
|
+
const LOG_RETENTION_MS = 10080 * 60 * 1e3;
|
|
2190
|
+
const CLEANUP_INTERVAL_MS = 1440 * 60 * 1e3;
|
|
2191
|
+
const LOG_DIR = path.join(PATHS.APP_DIR, "logs");
|
|
2192
|
+
const FLUSH_INTERVAL_MS = 1e3;
|
|
2193
|
+
const MAX_BUFFER_SIZE = 100;
|
|
2194
|
+
const logStreams = /* @__PURE__ */ new Map();
|
|
2195
|
+
const logBuffers = /* @__PURE__ */ new Map();
|
|
2196
|
+
const ensureLogDirectory = () => {
|
|
2197
|
+
if (!fs$1.existsSync(LOG_DIR)) fs$1.mkdirSync(LOG_DIR, { recursive: true });
|
|
2198
|
+
};
|
|
2199
|
+
const cleanupOldLogs = () => {
|
|
2200
|
+
if (!fs$1.existsSync(LOG_DIR)) return;
|
|
2201
|
+
const now = Date.now();
|
|
2202
|
+
for (const entry of fs$1.readdirSync(LOG_DIR)) {
|
|
2203
|
+
const filePath = path.join(LOG_DIR, entry);
|
|
2204
|
+
let stats;
|
|
2205
|
+
try {
|
|
2206
|
+
stats = fs$1.statSync(filePath);
|
|
2207
|
+
} catch {
|
|
2208
|
+
continue;
|
|
2209
|
+
}
|
|
2210
|
+
if (!stats.isFile()) continue;
|
|
2211
|
+
if (now - stats.mtimeMs > LOG_RETENTION_MS) try {
|
|
2212
|
+
fs$1.rmSync(filePath);
|
|
2213
|
+
} catch {
|
|
2214
|
+
continue;
|
|
2215
|
+
}
|
|
2216
|
+
}
|
|
2217
|
+
};
|
|
2218
|
+
const formatArgs = (args) => args.map((arg) => typeof arg === "string" ? arg : util.inspect(arg, {
|
|
2219
|
+
depth: null,
|
|
2220
|
+
colors: false
|
|
2221
|
+
})).join(" ");
|
|
2222
|
+
const sanitizeName = (name) => {
|
|
2223
|
+
const normalized = name.toLowerCase().replaceAll(/[^a-z0-9]+/g, "-").replaceAll(/^-+|-+$/g, "");
|
|
2224
|
+
return normalized === "" ? "handler" : normalized;
|
|
2225
|
+
};
|
|
2226
|
+
const getLogStream = (filePath) => {
|
|
2227
|
+
let stream = logStreams.get(filePath);
|
|
2228
|
+
if (!stream || stream.destroyed) {
|
|
2229
|
+
stream = fs$1.createWriteStream(filePath, { flags: "a" });
|
|
2230
|
+
logStreams.set(filePath, stream);
|
|
2231
|
+
stream.on("error", (error) => {
|
|
2232
|
+
console.warn("Log stream error", error);
|
|
2233
|
+
logStreams.delete(filePath);
|
|
2234
|
+
});
|
|
2235
|
+
}
|
|
2236
|
+
return stream;
|
|
2237
|
+
};
|
|
2238
|
+
const flushBuffer = (filePath) => {
|
|
2239
|
+
const buffer = logBuffers.get(filePath);
|
|
2240
|
+
if (!buffer || buffer.length === 0) return;
|
|
2241
|
+
const stream = getLogStream(filePath);
|
|
2242
|
+
const content = buffer.join("\n") + "\n";
|
|
2243
|
+
stream.write(content, (error) => {
|
|
2244
|
+
if (error) console.warn("Failed to write handler log", error);
|
|
2245
|
+
});
|
|
2246
|
+
logBuffers.set(filePath, []);
|
|
2247
|
+
};
|
|
2248
|
+
const flushAllBuffers = () => {
|
|
2249
|
+
for (const filePath of logBuffers.keys()) flushBuffer(filePath);
|
|
2250
|
+
};
|
|
2251
|
+
const appendLine = (filePath, line) => {
|
|
2252
|
+
let buffer = logBuffers.get(filePath);
|
|
2253
|
+
if (!buffer) {
|
|
2254
|
+
buffer = [];
|
|
2255
|
+
logBuffers.set(filePath, buffer);
|
|
2256
|
+
}
|
|
2257
|
+
buffer.push(line);
|
|
2258
|
+
if (buffer.length >= MAX_BUFFER_SIZE) flushBuffer(filePath);
|
|
2259
|
+
};
|
|
2260
|
+
setInterval(flushAllBuffers, FLUSH_INTERVAL_MS);
|
|
2261
|
+
const cleanup = () => {
|
|
2262
|
+
flushAllBuffers();
|
|
2263
|
+
for (const stream of logStreams.values()) stream.end();
|
|
2264
|
+
logStreams.clear();
|
|
2265
|
+
logBuffers.clear();
|
|
2266
|
+
};
|
|
2267
|
+
process.on("exit", cleanup);
|
|
2268
|
+
process.on("SIGINT", () => {
|
|
2269
|
+
cleanup();
|
|
2270
|
+
process.exit(0);
|
|
2271
|
+
});
|
|
2272
|
+
process.on("SIGTERM", () => {
|
|
2273
|
+
cleanup();
|
|
2274
|
+
process.exit(0);
|
|
2275
|
+
});
|
|
2276
|
+
let lastCleanup = 0;
|
|
2277
|
+
const createHandlerLogger = (name) => {
|
|
2278
|
+
ensureLogDirectory();
|
|
2279
|
+
const sanitizedName = sanitizeName(name);
|
|
2280
|
+
const instance = consola.withTag(name);
|
|
2281
|
+
if (state.verbose) instance.level = 5;
|
|
2282
|
+
instance.setReporters([]);
|
|
2283
|
+
instance.addReporter({ log(logObj) {
|
|
2284
|
+
ensureLogDirectory();
|
|
2285
|
+
if (Date.now() - lastCleanup > CLEANUP_INTERVAL_MS) {
|
|
2286
|
+
cleanupOldLogs();
|
|
2287
|
+
lastCleanup = Date.now();
|
|
2288
|
+
}
|
|
2289
|
+
const date = logObj.date;
|
|
2290
|
+
const dateKey = date.toLocaleDateString("sv-SE");
|
|
2291
|
+
const timestamp = date.toLocaleString("sv-SE", { hour12: false });
|
|
2292
|
+
const filePath = path.join(LOG_DIR, `${sanitizedName}-${dateKey}.log`);
|
|
2293
|
+
const message = formatArgs(logObj.args);
|
|
2294
|
+
appendLine(filePath, `[${timestamp}] [${logObj.type}] [${logObj.tag || name}]${message ? ` ${message}` : ""}`);
|
|
2295
|
+
} });
|
|
2296
|
+
return instance;
|
|
2297
|
+
};
|
|
2298
|
+
|
|
2299
|
+
//#endregion
|
|
2300
|
+
//#region src/services/copilot/create-responses.ts
|
|
2301
|
+
const createResponses = async (payload, { vision, initiator }) => {
|
|
2302
|
+
if (!state.copilotToken) throw new Error("Copilot token not found");
|
|
2303
|
+
const headers = {
|
|
2304
|
+
...copilotHeaders(state, vision),
|
|
2305
|
+
"X-Initiator": initiator
|
|
2306
|
+
};
|
|
2307
|
+
payload.service_tier = null;
|
|
2308
|
+
const response = await fetch(`${copilotBaseUrl(state)}/responses`, {
|
|
2309
|
+
method: "POST",
|
|
2310
|
+
headers,
|
|
2311
|
+
body: JSON.stringify(payload)
|
|
2312
|
+
});
|
|
2313
|
+
if (!response.ok) {
|
|
2314
|
+
consola.error("Failed to create responses", response);
|
|
2315
|
+
throw new HTTPError("Failed to create responses", response);
|
|
2316
|
+
}
|
|
2317
|
+
if (payload.stream) return events(response);
|
|
2318
|
+
return await response.json();
|
|
2319
|
+
};
|
|
2320
|
+
|
|
2321
|
+
//#endregion
|
|
2322
|
+
//#region src/routes/messages/responses-translation.ts
|
|
2323
|
+
const MESSAGE_TYPE = "message";
|
|
2324
|
+
const CODEX_PHASE_MODEL = "gpt-5.3-codex";
|
|
2325
|
+
const THINKING_TEXT = "Thinking...";
|
|
2326
|
+
const translateAnthropicMessagesToResponsesPayload = (payload) => {
|
|
2327
|
+
const input = [];
|
|
2328
|
+
for (const message of payload.messages) input.push(...translateMessage(message, payload.model));
|
|
2329
|
+
const translatedTools = convertAnthropicTools(payload.tools);
|
|
2330
|
+
const toolChoice = convertAnthropicToolChoice(payload.tool_choice);
|
|
2331
|
+
const { safetyIdentifier, promptCacheKey } = parseUserId(payload.metadata?.user_id);
|
|
2332
|
+
return {
|
|
2333
|
+
model: payload.model,
|
|
2334
|
+
input,
|
|
2335
|
+
instructions: translateSystemPrompt(payload.system, payload.model),
|
|
2336
|
+
temperature: 1,
|
|
2337
|
+
top_p: payload.top_p ?? null,
|
|
2338
|
+
max_output_tokens: Math.max(payload.max_tokens, 12800),
|
|
2339
|
+
tools: translatedTools,
|
|
2340
|
+
tool_choice: toolChoice,
|
|
2341
|
+
metadata: payload.metadata ? { ...payload.metadata } : null,
|
|
2342
|
+
safety_identifier: safetyIdentifier,
|
|
2343
|
+
prompt_cache_key: promptCacheKey,
|
|
2344
|
+
stream: payload.stream ?? null,
|
|
2345
|
+
store: false,
|
|
2346
|
+
parallel_tool_calls: true,
|
|
2347
|
+
reasoning: {
|
|
2348
|
+
effort: getReasoningEffortForModel(payload.model),
|
|
2349
|
+
summary: "detailed"
|
|
2350
|
+
},
|
|
2351
|
+
include: ["reasoning.encrypted_content"]
|
|
2352
|
+
};
|
|
2353
|
+
};
|
|
2354
|
+
const translateMessage = (message, model) => {
|
|
2355
|
+
if (message.role === "user") return translateUserMessage(message);
|
|
2356
|
+
return translateAssistantMessage(message, model);
|
|
2357
|
+
};
|
|
2358
|
+
const translateUserMessage = (message) => {
|
|
2359
|
+
if (typeof message.content === "string") return [createMessage("user", message.content)];
|
|
2360
|
+
if (!Array.isArray(message.content)) return [];
|
|
2361
|
+
const items = [];
|
|
2362
|
+
const pendingContent = [];
|
|
2363
|
+
for (const block of message.content) {
|
|
2364
|
+
if (block.type === "tool_result") {
|
|
2365
|
+
flushPendingContent(pendingContent, items, { role: "user" });
|
|
2366
|
+
items.push(createFunctionCallOutput(block));
|
|
2367
|
+
continue;
|
|
2368
|
+
}
|
|
2369
|
+
const converted = translateUserContentBlock(block);
|
|
2370
|
+
if (converted) pendingContent.push(converted);
|
|
2371
|
+
}
|
|
2372
|
+
flushPendingContent(pendingContent, items, { role: "user" });
|
|
2373
|
+
return items;
|
|
2374
|
+
};
|
|
2375
|
+
const translateAssistantMessage = (message, model) => {
|
|
2376
|
+
const assistantPhase = resolveAssistantPhase(model, message.content);
|
|
2377
|
+
if (typeof message.content === "string") return [createMessage("assistant", message.content, assistantPhase)];
|
|
2378
|
+
if (!Array.isArray(message.content)) return [];
|
|
2379
|
+
const items = [];
|
|
2380
|
+
const pendingContent = [];
|
|
2381
|
+
for (const block of message.content) {
|
|
2382
|
+
if (block.type === "tool_use") {
|
|
2383
|
+
flushPendingContent(pendingContent, items, {
|
|
2384
|
+
role: "assistant",
|
|
2385
|
+
phase: assistantPhase
|
|
2386
|
+
});
|
|
2387
|
+
items.push(createFunctionToolCall(block));
|
|
2388
|
+
continue;
|
|
2389
|
+
}
|
|
2390
|
+
if (block.type === "thinking" && block.signature && block.signature.includes("@")) {
|
|
2391
|
+
flushPendingContent(pendingContent, items, {
|
|
2392
|
+
role: "assistant",
|
|
2393
|
+
phase: assistantPhase
|
|
2394
|
+
});
|
|
2395
|
+
items.push(createReasoningContent(block));
|
|
2396
|
+
continue;
|
|
2397
|
+
}
|
|
2398
|
+
const converted = translateAssistantContentBlock(block);
|
|
2399
|
+
if (converted) pendingContent.push(converted);
|
|
2400
|
+
}
|
|
2401
|
+
flushPendingContent(pendingContent, items, {
|
|
2402
|
+
role: "assistant",
|
|
2403
|
+
phase: assistantPhase
|
|
2404
|
+
});
|
|
2405
|
+
return items;
|
|
2406
|
+
};
|
|
2407
|
+
const translateUserContentBlock = (block) => {
|
|
2408
|
+
switch (block.type) {
|
|
2409
|
+
case "text": return createTextContent(block.text);
|
|
2410
|
+
case "image": return createImageContent(block);
|
|
2411
|
+
default: return;
|
|
2412
|
+
}
|
|
2413
|
+
};
|
|
2414
|
+
const translateAssistantContentBlock = (block) => {
|
|
2415
|
+
switch (block.type) {
|
|
2416
|
+
case "text": return createOutPutTextContent(block.text);
|
|
2417
|
+
default: return;
|
|
2418
|
+
}
|
|
2419
|
+
};
|
|
2420
|
+
const flushPendingContent = (pendingContent, target, message) => {
|
|
2421
|
+
if (pendingContent.length === 0) return;
|
|
2422
|
+
const messageContent = [...pendingContent];
|
|
2423
|
+
target.push(createMessage(message.role, messageContent, message.phase));
|
|
2424
|
+
pendingContent.length = 0;
|
|
2425
|
+
};
|
|
2426
|
+
const createMessage = (role, content, phase) => ({
|
|
2427
|
+
type: MESSAGE_TYPE,
|
|
2428
|
+
role,
|
|
2429
|
+
content,
|
|
2430
|
+
...role === "assistant" && phase ? { phase } : {}
|
|
2431
|
+
});
|
|
2432
|
+
const resolveAssistantPhase = (model, content) => {
|
|
2433
|
+
if (!shouldApplyCodexPhase(model)) return;
|
|
2434
|
+
if (typeof content === "string") return "final_answer";
|
|
2435
|
+
if (!Array.isArray(content)) return;
|
|
2436
|
+
if (!content.some((block) => block.type === "text")) return;
|
|
2437
|
+
return content.some((block) => block.type === "tool_use") ? "commentary" : "final_answer";
|
|
2438
|
+
};
|
|
2439
|
+
const shouldApplyCodexPhase = (model) => model === CODEX_PHASE_MODEL;
|
|
2440
|
+
const createTextContent = (text) => ({
|
|
2441
|
+
type: "input_text",
|
|
2442
|
+
text
|
|
2443
|
+
});
|
|
2444
|
+
const createOutPutTextContent = (text) => ({
|
|
2445
|
+
type: "output_text",
|
|
2446
|
+
text
|
|
2447
|
+
});
|
|
2448
|
+
const createImageContent = (block) => ({
|
|
2449
|
+
type: "input_image",
|
|
2450
|
+
image_url: `data:${block.source.media_type};base64,${block.source.data}`,
|
|
2451
|
+
detail: "auto"
|
|
2452
|
+
});
|
|
2453
|
+
const createReasoningContent = (block) => {
|
|
2454
|
+
const array = (block.signature ?? "").split("@");
|
|
2455
|
+
const signature = array[0];
|
|
2456
|
+
const id = array[1];
|
|
2457
|
+
const thinking = block.thinking === THINKING_TEXT ? "" : block.thinking;
|
|
2458
|
+
return {
|
|
2459
|
+
id,
|
|
2460
|
+
type: "reasoning",
|
|
2461
|
+
summary: thinking ? [{
|
|
2462
|
+
type: "summary_text",
|
|
2463
|
+
text: thinking
|
|
2464
|
+
}] : [],
|
|
2465
|
+
encrypted_content: signature
|
|
2466
|
+
};
|
|
2467
|
+
};
|
|
2468
|
+
const createFunctionToolCall = (block) => ({
|
|
2469
|
+
type: "function_call",
|
|
2470
|
+
call_id: block.id,
|
|
2471
|
+
name: block.name,
|
|
2472
|
+
arguments: JSON.stringify(block.input),
|
|
2473
|
+
status: "completed"
|
|
2474
|
+
});
|
|
2475
|
+
const createFunctionCallOutput = (block) => ({
|
|
2476
|
+
type: "function_call_output",
|
|
2477
|
+
call_id: block.tool_use_id,
|
|
2478
|
+
output: convertToolResultContent(block.content),
|
|
2479
|
+
status: block.is_error ? "incomplete" : "completed"
|
|
2480
|
+
});
|
|
2481
|
+
const translateSystemPrompt = (system, model) => {
|
|
2482
|
+
if (!system) return null;
|
|
2483
|
+
const extraPrompt = getExtraPromptForModel(model);
|
|
2484
|
+
if (typeof system === "string") return system + extraPrompt;
|
|
2485
|
+
const text = system.map((block, index) => {
|
|
2486
|
+
if (index === 0) return block.text + extraPrompt;
|
|
2487
|
+
return block.text;
|
|
2488
|
+
}).join(" ");
|
|
2489
|
+
return text.length > 0 ? text : null;
|
|
2490
|
+
};
|
|
2491
|
+
const convertAnthropicTools = (tools) => {
|
|
2492
|
+
if (!tools || tools.length === 0) return null;
|
|
2493
|
+
return tools.map((tool) => ({
|
|
2494
|
+
type: "function",
|
|
2495
|
+
name: tool.name,
|
|
2496
|
+
parameters: tool.input_schema,
|
|
2497
|
+
strict: false,
|
|
2498
|
+
...tool.description ? { description: tool.description } : {}
|
|
2499
|
+
}));
|
|
2500
|
+
};
|
|
2501
|
+
const convertAnthropicToolChoice = (choice) => {
|
|
2502
|
+
if (!choice) return "auto";
|
|
2503
|
+
switch (choice.type) {
|
|
2504
|
+
case "auto": return "auto";
|
|
2505
|
+
case "any": return "required";
|
|
2506
|
+
case "tool": return choice.name ? {
|
|
2507
|
+
type: "function",
|
|
2508
|
+
name: choice.name
|
|
2509
|
+
} : "auto";
|
|
2510
|
+
case "none": return "none";
|
|
2511
|
+
default: return "auto";
|
|
2512
|
+
}
|
|
2513
|
+
};
|
|
2514
|
+
const translateResponsesResultToAnthropic = (response) => {
|
|
2515
|
+
const contentBlocks = mapOutputToAnthropicContent(response.output);
|
|
2516
|
+
const usage = mapResponsesUsage(response);
|
|
2517
|
+
let anthropicContent = fallbackContentBlocks(response.output_text);
|
|
2518
|
+
if (contentBlocks.length > 0) anthropicContent = contentBlocks;
|
|
2519
|
+
const stopReason = mapResponsesStopReason(response);
|
|
2520
|
+
return {
|
|
2521
|
+
id: response.id,
|
|
2522
|
+
type: "message",
|
|
2523
|
+
role: "assistant",
|
|
2524
|
+
content: anthropicContent,
|
|
2525
|
+
model: response.model,
|
|
2526
|
+
stop_reason: stopReason,
|
|
2527
|
+
stop_sequence: null,
|
|
2528
|
+
usage
|
|
2529
|
+
};
|
|
2530
|
+
};
|
|
2531
|
+
const mapOutputToAnthropicContent = (output) => {
|
|
2532
|
+
const contentBlocks = [];
|
|
2533
|
+
for (const item of output) switch (item.type) {
|
|
2534
|
+
case "reasoning": {
|
|
2535
|
+
const thinkingText = extractReasoningText(item);
|
|
2536
|
+
if (thinkingText.length > 0) contentBlocks.push({
|
|
2537
|
+
type: "thinking",
|
|
2538
|
+
thinking: thinkingText,
|
|
2539
|
+
signature: (item.encrypted_content ?? "") + "@" + item.id
|
|
2540
|
+
});
|
|
2541
|
+
break;
|
|
2542
|
+
}
|
|
2543
|
+
case "function_call": {
|
|
2544
|
+
const toolUseBlock = createToolUseContentBlock(item);
|
|
2545
|
+
if (toolUseBlock) contentBlocks.push(toolUseBlock);
|
|
2546
|
+
break;
|
|
2547
|
+
}
|
|
2548
|
+
case "message": {
|
|
2549
|
+
const combinedText = combineMessageTextContent(item.content);
|
|
2550
|
+
if (combinedText.length > 0) contentBlocks.push({
|
|
2551
|
+
type: "text",
|
|
2552
|
+
text: combinedText
|
|
2553
|
+
});
|
|
2554
|
+
break;
|
|
2555
|
+
}
|
|
2556
|
+
default: {
|
|
2557
|
+
const combinedText = combineMessageTextContent(item.content);
|
|
2558
|
+
if (combinedText.length > 0) contentBlocks.push({
|
|
2559
|
+
type: "text",
|
|
2560
|
+
text: combinedText
|
|
2561
|
+
});
|
|
2562
|
+
}
|
|
2563
|
+
}
|
|
2564
|
+
return contentBlocks;
|
|
2565
|
+
};
|
|
2566
|
+
const combineMessageTextContent = (content) => {
|
|
2567
|
+
if (!Array.isArray(content)) return "";
|
|
2568
|
+
let aggregated = "";
|
|
2569
|
+
for (const block of content) {
|
|
2570
|
+
if (isResponseOutputText(block)) {
|
|
2571
|
+
aggregated += block.text;
|
|
2572
|
+
continue;
|
|
2573
|
+
}
|
|
2574
|
+
if (isResponseOutputRefusal(block)) {
|
|
2575
|
+
aggregated += block.refusal;
|
|
2576
|
+
continue;
|
|
2577
|
+
}
|
|
2578
|
+
if (typeof block.text === "string") {
|
|
2579
|
+
aggregated += block.text;
|
|
2580
|
+
continue;
|
|
2581
|
+
}
|
|
2582
|
+
if (typeof block.reasoning === "string") {
|
|
2583
|
+
aggregated += block.reasoning;
|
|
2584
|
+
continue;
|
|
2585
|
+
}
|
|
2586
|
+
}
|
|
2587
|
+
return aggregated;
|
|
2588
|
+
};
|
|
2589
|
+
const extractReasoningText = (item) => {
|
|
2590
|
+
const segments = [];
|
|
2591
|
+
const collectFromBlocks = (blocks) => {
|
|
2592
|
+
if (!Array.isArray(blocks)) return;
|
|
2593
|
+
for (const block of blocks) if (typeof block.text === "string") {
|
|
2594
|
+
segments.push(block.text);
|
|
2595
|
+
continue;
|
|
2596
|
+
}
|
|
2597
|
+
};
|
|
2598
|
+
if (!item.summary || item.summary.length === 0) return THINKING_TEXT;
|
|
2599
|
+
collectFromBlocks(item.summary);
|
|
2600
|
+
return segments.join("").trim();
|
|
2601
|
+
};
|
|
2602
|
+
const createToolUseContentBlock = (call) => {
|
|
2603
|
+
const toolId = call.call_id;
|
|
2604
|
+
if (!call.name || !toolId) return null;
|
|
2605
|
+
const input = parseFunctionCallArguments(call.arguments);
|
|
2606
|
+
return {
|
|
2607
|
+
type: "tool_use",
|
|
2608
|
+
id: toolId,
|
|
2609
|
+
name: call.name,
|
|
2610
|
+
input
|
|
2611
|
+
};
|
|
2612
|
+
};
|
|
2613
|
+
const parseFunctionCallArguments = (rawArguments) => {
|
|
2614
|
+
if (typeof rawArguments !== "string" || rawArguments.trim().length === 0) return {};
|
|
2615
|
+
try {
|
|
2616
|
+
const parsed = JSON.parse(rawArguments);
|
|
2617
|
+
if (Array.isArray(parsed)) return { arguments: parsed };
|
|
2618
|
+
if (parsed && typeof parsed === "object") return parsed;
|
|
2619
|
+
} catch (error) {
|
|
2620
|
+
consola.warn("Failed to parse function call arguments", {
|
|
2621
|
+
error,
|
|
2622
|
+
rawArguments
|
|
2623
|
+
});
|
|
2624
|
+
}
|
|
2625
|
+
return { raw_arguments: rawArguments };
|
|
2626
|
+
};
|
|
2627
|
+
const fallbackContentBlocks = (outputText) => {
|
|
2628
|
+
if (!outputText) return [];
|
|
2629
|
+
return [{
|
|
2630
|
+
type: "text",
|
|
2631
|
+
text: outputText
|
|
2632
|
+
}];
|
|
2633
|
+
};
|
|
2634
|
+
const mapResponsesStopReason = (response) => {
|
|
2635
|
+
const { status, incomplete_details: incompleteDetails } = response;
|
|
2636
|
+
if (status === "completed") {
|
|
2637
|
+
if (response.output.some((item) => item.type === "function_call")) return "tool_use";
|
|
2638
|
+
return "end_turn";
|
|
2639
|
+
}
|
|
2640
|
+
if (status === "incomplete") {
|
|
2641
|
+
if (incompleteDetails?.reason === "max_output_tokens") return "max_tokens";
|
|
2642
|
+
if (incompleteDetails?.reason === "content_filter") return "end_turn";
|
|
2643
|
+
}
|
|
2644
|
+
return null;
|
|
2645
|
+
};
|
|
2646
|
+
const mapResponsesUsage = (response) => {
|
|
2647
|
+
const inputTokens = response.usage?.input_tokens ?? 0;
|
|
2648
|
+
const outputTokens = response.usage?.output_tokens ?? 0;
|
|
2649
|
+
return {
|
|
2650
|
+
input_tokens: inputTokens - (response.usage?.input_tokens_details?.cached_tokens ?? 0),
|
|
2651
|
+
output_tokens: outputTokens,
|
|
2652
|
+
...response.usage?.input_tokens_details?.cached_tokens !== void 0 && { cache_read_input_tokens: response.usage.input_tokens_details.cached_tokens }
|
|
2653
|
+
};
|
|
2654
|
+
};
|
|
2655
|
+
const isRecord = (value) => typeof value === "object" && value !== null;
|
|
2656
|
+
const isResponseOutputText = (block) => isRecord(block) && "type" in block && block.type === "output_text";
|
|
2657
|
+
const isResponseOutputRefusal = (block) => isRecord(block) && "type" in block && block.type === "refusal";
|
|
2658
|
+
const parseUserId = (userId) => {
|
|
2659
|
+
if (!userId || typeof userId !== "string") return {
|
|
2660
|
+
safetyIdentifier: null,
|
|
2661
|
+
promptCacheKey: null
|
|
2662
|
+
};
|
|
2663
|
+
const userMatch = userId.match(/user_([^_]+)_account/);
|
|
2664
|
+
const safetyIdentifier = userMatch ? userMatch[1] : null;
|
|
2665
|
+
const sessionMatch = userId.match(/_session_(.+)$/);
|
|
2666
|
+
return {
|
|
2667
|
+
safetyIdentifier,
|
|
2668
|
+
promptCacheKey: sessionMatch ? sessionMatch[1] : null
|
|
2669
|
+
};
|
|
2670
|
+
};
|
|
2671
|
+
const convertToolResultContent = (content) => {
|
|
2672
|
+
if (typeof content === "string") return content;
|
|
2673
|
+
if (Array.isArray(content)) {
|
|
2674
|
+
const result = [];
|
|
2675
|
+
for (const block of content) switch (block.type) {
|
|
2676
|
+
case "text":
|
|
2677
|
+
result.push(createTextContent(block.text));
|
|
2678
|
+
break;
|
|
2679
|
+
case "image":
|
|
2680
|
+
result.push(createImageContent(block));
|
|
2681
|
+
break;
|
|
2682
|
+
default: break;
|
|
2683
|
+
}
|
|
2684
|
+
return result;
|
|
2685
|
+
}
|
|
2686
|
+
return "";
|
|
2687
|
+
};
|
|
2688
|
+
|
|
2689
|
+
//#endregion
|
|
2690
|
+
//#region src/routes/messages/responses-stream-translation.ts
|
|
2691
|
+
const MAX_CONSECUTIVE_FUNCTION_CALL_WHITESPACE = 20;
|
|
2692
|
+
var FunctionCallArgumentsValidationError = class extends Error {
|
|
2693
|
+
constructor(message) {
|
|
2694
|
+
super(message);
|
|
2695
|
+
this.name = "FunctionCallArgumentsValidationError";
|
|
2696
|
+
}
|
|
2697
|
+
};
|
|
2698
|
+
const updateWhitespaceRunState = (previousCount, chunk) => {
|
|
2699
|
+
let count = previousCount;
|
|
2700
|
+
for (const char of chunk) {
|
|
2701
|
+
if (char === "\r" || char === "\n" || char === " ") {
|
|
2702
|
+
count += 1;
|
|
2703
|
+
if (count > MAX_CONSECUTIVE_FUNCTION_CALL_WHITESPACE) return {
|
|
2704
|
+
nextCount: count,
|
|
2705
|
+
exceeded: true
|
|
2706
|
+
};
|
|
2707
|
+
continue;
|
|
2708
|
+
}
|
|
2709
|
+
if (char !== " ") count = 0;
|
|
2710
|
+
}
|
|
2711
|
+
return {
|
|
2712
|
+
nextCount: count,
|
|
2713
|
+
exceeded: false
|
|
2714
|
+
};
|
|
2715
|
+
};
|
|
2716
|
+
const createResponsesStreamState = () => ({
|
|
2717
|
+
messageStartSent: false,
|
|
2718
|
+
messageCompleted: false,
|
|
2719
|
+
nextContentBlockIndex: 0,
|
|
2720
|
+
blockIndexByKey: /* @__PURE__ */ new Map(),
|
|
2721
|
+
openBlocks: /* @__PURE__ */ new Set(),
|
|
2722
|
+
blockHasDelta: /* @__PURE__ */ new Set(),
|
|
2723
|
+
functionCallStateByOutputIndex: /* @__PURE__ */ new Map()
|
|
2724
|
+
});
|
|
2725
|
+
const translateResponsesStreamEvent = (rawEvent, state$1) => {
|
|
2726
|
+
switch (rawEvent.type) {
|
|
2727
|
+
case "response.created": return handleResponseCreated(rawEvent, state$1);
|
|
2728
|
+
case "response.output_item.added": return handleOutputItemAdded$1(rawEvent, state$1);
|
|
2729
|
+
case "response.reasoning_summary_text.delta": return handleReasoningSummaryTextDelta(rawEvent, state$1);
|
|
2730
|
+
case "response.output_text.delta": return handleOutputTextDelta(rawEvent, state$1);
|
|
2731
|
+
case "response.reasoning_summary_text.done": return handleReasoningSummaryTextDone(rawEvent, state$1);
|
|
2732
|
+
case "response.output_text.done": return handleOutputTextDone(rawEvent, state$1);
|
|
2733
|
+
case "response.output_item.done": return handleOutputItemDone$1(rawEvent, state$1);
|
|
2734
|
+
case "response.function_call_arguments.delta": return handleFunctionCallArgumentsDelta(rawEvent, state$1);
|
|
2735
|
+
case "response.function_call_arguments.done": return handleFunctionCallArgumentsDone(rawEvent, state$1);
|
|
2736
|
+
case "response.completed":
|
|
2737
|
+
case "response.incomplete": return handleResponseCompleted(rawEvent, state$1);
|
|
2738
|
+
case "response.failed": return handleResponseFailed(rawEvent, state$1);
|
|
2739
|
+
case "error": return handleErrorEvent(rawEvent, state$1);
|
|
2740
|
+
default: return [];
|
|
2741
|
+
}
|
|
2742
|
+
};
|
|
2743
|
+
const handleResponseCreated = (rawEvent, state$1) => {
|
|
2744
|
+
return messageStart(state$1, rawEvent.response);
|
|
2745
|
+
};
|
|
2746
|
+
const handleOutputItemAdded$1 = (rawEvent, state$1) => {
|
|
2747
|
+
const events$1 = new Array();
|
|
2748
|
+
const functionCallDetails = extractFunctionCallDetails(rawEvent);
|
|
2749
|
+
if (!functionCallDetails) return events$1;
|
|
2750
|
+
const { outputIndex, toolCallId, name, initialArguments } = functionCallDetails;
|
|
2751
|
+
const blockIndex = openFunctionCallBlock(state$1, {
|
|
2752
|
+
outputIndex,
|
|
2753
|
+
toolCallId,
|
|
2754
|
+
name,
|
|
2755
|
+
events: events$1
|
|
2756
|
+
});
|
|
2757
|
+
if (initialArguments !== void 0 && initialArguments.length > 0) {
|
|
2758
|
+
events$1.push({
|
|
2759
|
+
type: "content_block_delta",
|
|
2760
|
+
index: blockIndex,
|
|
2761
|
+
delta: {
|
|
2762
|
+
type: "input_json_delta",
|
|
2763
|
+
partial_json: initialArguments
|
|
2764
|
+
}
|
|
2765
|
+
});
|
|
2766
|
+
state$1.blockHasDelta.add(blockIndex);
|
|
2767
|
+
}
|
|
2768
|
+
return events$1;
|
|
2769
|
+
};
|
|
2770
|
+
const handleOutputItemDone$1 = (rawEvent, state$1) => {
|
|
2771
|
+
const events$1 = new Array();
|
|
2772
|
+
const item = rawEvent.item;
|
|
2773
|
+
if (item.type !== "reasoning") return events$1;
|
|
2774
|
+
const outputIndex = rawEvent.output_index;
|
|
2775
|
+
const blockIndex = openThinkingBlockIfNeeded(state$1, outputIndex, events$1);
|
|
2776
|
+
const signature = (item.encrypted_content ?? "") + "@" + item.id;
|
|
2777
|
+
if (signature) {
|
|
2778
|
+
if (!item.summary || item.summary.length === 0) events$1.push({
|
|
2779
|
+
type: "content_block_delta",
|
|
2780
|
+
index: blockIndex,
|
|
2781
|
+
delta: {
|
|
2782
|
+
type: "thinking_delta",
|
|
2783
|
+
thinking: THINKING_TEXT
|
|
2784
|
+
}
|
|
2785
|
+
});
|
|
2786
|
+
events$1.push({
|
|
2787
|
+
type: "content_block_delta",
|
|
2788
|
+
index: blockIndex,
|
|
2789
|
+
delta: {
|
|
2790
|
+
type: "signature_delta",
|
|
2791
|
+
signature
|
|
2792
|
+
}
|
|
2793
|
+
});
|
|
2794
|
+
state$1.blockHasDelta.add(blockIndex);
|
|
2795
|
+
}
|
|
2796
|
+
return events$1;
|
|
2797
|
+
};
|
|
2798
|
+
const handleFunctionCallArgumentsDelta = (rawEvent, state$1) => {
|
|
2799
|
+
const events$1 = new Array();
|
|
2800
|
+
const outputIndex = rawEvent.output_index;
|
|
2801
|
+
const deltaText = rawEvent.delta;
|
|
2802
|
+
if (!deltaText) return events$1;
|
|
2803
|
+
const blockIndex = openFunctionCallBlock(state$1, {
|
|
2804
|
+
outputIndex,
|
|
2805
|
+
events: events$1
|
|
2806
|
+
});
|
|
2807
|
+
const functionCallState = state$1.functionCallStateByOutputIndex.get(outputIndex);
|
|
2808
|
+
if (!functionCallState) return handleFunctionCallArgumentsValidationError(new FunctionCallArgumentsValidationError("Received function call arguments delta without an open tool call block."), state$1, events$1);
|
|
2809
|
+
const { nextCount, exceeded } = updateWhitespaceRunState(functionCallState.consecutiveWhitespaceCount, deltaText);
|
|
2810
|
+
if (exceeded) return handleFunctionCallArgumentsValidationError(new FunctionCallArgumentsValidationError("Received function call arguments delta containing more than 20 consecutive whitespace characters."), state$1, events$1);
|
|
2811
|
+
functionCallState.consecutiveWhitespaceCount = nextCount;
|
|
2812
|
+
events$1.push({
|
|
2813
|
+
type: "content_block_delta",
|
|
2814
|
+
index: blockIndex,
|
|
2815
|
+
delta: {
|
|
2816
|
+
type: "input_json_delta",
|
|
2817
|
+
partial_json: deltaText
|
|
2818
|
+
}
|
|
2819
|
+
});
|
|
2820
|
+
state$1.blockHasDelta.add(blockIndex);
|
|
2821
|
+
return events$1;
|
|
2822
|
+
};
|
|
2823
|
+
const handleFunctionCallArgumentsDone = (rawEvent, state$1) => {
|
|
2824
|
+
const events$1 = new Array();
|
|
2825
|
+
const outputIndex = rawEvent.output_index;
|
|
2826
|
+
const blockIndex = openFunctionCallBlock(state$1, {
|
|
2827
|
+
outputIndex,
|
|
2828
|
+
events: events$1
|
|
2829
|
+
});
|
|
2830
|
+
const finalArguments = typeof rawEvent.arguments === "string" ? rawEvent.arguments : void 0;
|
|
2831
|
+
if (!state$1.blockHasDelta.has(blockIndex) && finalArguments) {
|
|
2832
|
+
events$1.push({
|
|
2833
|
+
type: "content_block_delta",
|
|
2834
|
+
index: blockIndex,
|
|
2835
|
+
delta: {
|
|
2836
|
+
type: "input_json_delta",
|
|
2837
|
+
partial_json: finalArguments
|
|
2838
|
+
}
|
|
2839
|
+
});
|
|
2840
|
+
state$1.blockHasDelta.add(blockIndex);
|
|
2841
|
+
}
|
|
2842
|
+
state$1.functionCallStateByOutputIndex.delete(outputIndex);
|
|
2843
|
+
return events$1;
|
|
2844
|
+
};
|
|
2845
|
+
const handleOutputTextDelta = (rawEvent, state$1) => {
|
|
2846
|
+
const events$1 = new Array();
|
|
2847
|
+
const outputIndex = rawEvent.output_index;
|
|
2848
|
+
const contentIndex = rawEvent.content_index;
|
|
2849
|
+
const deltaText = rawEvent.delta;
|
|
2850
|
+
if (!deltaText) return events$1;
|
|
2851
|
+
const blockIndex = openTextBlockIfNeeded(state$1, {
|
|
2852
|
+
outputIndex,
|
|
2853
|
+
contentIndex,
|
|
2854
|
+
events: events$1
|
|
2855
|
+
});
|
|
2856
|
+
events$1.push({
|
|
2857
|
+
type: "content_block_delta",
|
|
2858
|
+
index: blockIndex,
|
|
2859
|
+
delta: {
|
|
2860
|
+
type: "text_delta",
|
|
2861
|
+
text: deltaText
|
|
2862
|
+
}
|
|
2863
|
+
});
|
|
2864
|
+
state$1.blockHasDelta.add(blockIndex);
|
|
2865
|
+
return events$1;
|
|
2866
|
+
};
|
|
2867
|
+
const handleReasoningSummaryTextDelta = (rawEvent, state$1) => {
|
|
2868
|
+
const outputIndex = rawEvent.output_index;
|
|
2869
|
+
const deltaText = rawEvent.delta;
|
|
2870
|
+
const events$1 = new Array();
|
|
2871
|
+
const blockIndex = openThinkingBlockIfNeeded(state$1, outputIndex, events$1);
|
|
2872
|
+
events$1.push({
|
|
2873
|
+
type: "content_block_delta",
|
|
2874
|
+
index: blockIndex,
|
|
2875
|
+
delta: {
|
|
2876
|
+
type: "thinking_delta",
|
|
2877
|
+
thinking: deltaText
|
|
2878
|
+
}
|
|
2879
|
+
});
|
|
2880
|
+
state$1.blockHasDelta.add(blockIndex);
|
|
2881
|
+
return events$1;
|
|
2882
|
+
};
|
|
2883
|
+
const handleReasoningSummaryTextDone = (rawEvent, state$1) => {
|
|
2884
|
+
const outputIndex = rawEvent.output_index;
|
|
2885
|
+
const text = rawEvent.text;
|
|
2886
|
+
const events$1 = new Array();
|
|
2887
|
+
const blockIndex = openThinkingBlockIfNeeded(state$1, outputIndex, events$1);
|
|
2888
|
+
if (text && !state$1.blockHasDelta.has(blockIndex)) events$1.push({
|
|
2889
|
+
type: "content_block_delta",
|
|
2890
|
+
index: blockIndex,
|
|
2891
|
+
delta: {
|
|
2892
|
+
type: "thinking_delta",
|
|
2893
|
+
thinking: text
|
|
2894
|
+
}
|
|
2895
|
+
});
|
|
2896
|
+
return events$1;
|
|
2897
|
+
};
|
|
2898
|
+
const handleOutputTextDone = (rawEvent, state$1) => {
|
|
2899
|
+
const events$1 = new Array();
|
|
2900
|
+
const outputIndex = rawEvent.output_index;
|
|
2901
|
+
const contentIndex = rawEvent.content_index;
|
|
2902
|
+
const text = rawEvent.text;
|
|
2903
|
+
const blockIndex = openTextBlockIfNeeded(state$1, {
|
|
2904
|
+
outputIndex,
|
|
2905
|
+
contentIndex,
|
|
2906
|
+
events: events$1
|
|
2907
|
+
});
|
|
2908
|
+
if (text && !state$1.blockHasDelta.has(blockIndex)) events$1.push({
|
|
2909
|
+
type: "content_block_delta",
|
|
2910
|
+
index: blockIndex,
|
|
2911
|
+
delta: {
|
|
2912
|
+
type: "text_delta",
|
|
2913
|
+
text
|
|
2914
|
+
}
|
|
2915
|
+
});
|
|
2916
|
+
return events$1;
|
|
2917
|
+
};
|
|
2918
|
+
const handleResponseCompleted = (rawEvent, state$1) => {
|
|
2919
|
+
const response = rawEvent.response;
|
|
2920
|
+
const events$1 = new Array();
|
|
2921
|
+
closeAllOpenBlocks(state$1, events$1);
|
|
2922
|
+
const anthropic = translateResponsesResultToAnthropic(response);
|
|
2923
|
+
events$1.push({
|
|
2924
|
+
type: "message_delta",
|
|
2925
|
+
delta: {
|
|
2926
|
+
stop_reason: anthropic.stop_reason,
|
|
2927
|
+
stop_sequence: anthropic.stop_sequence
|
|
2928
|
+
},
|
|
2929
|
+
usage: anthropic.usage
|
|
2930
|
+
}, { type: "message_stop" });
|
|
2931
|
+
state$1.messageCompleted = true;
|
|
2932
|
+
return events$1;
|
|
2933
|
+
};
|
|
2934
|
+
const handleResponseFailed = (rawEvent, state$1) => {
|
|
2935
|
+
const response = rawEvent.response;
|
|
2936
|
+
const events$1 = new Array();
|
|
2937
|
+
closeAllOpenBlocks(state$1, events$1);
|
|
2938
|
+
const message = response.error?.message ?? "The response failed due to an unknown error.";
|
|
2939
|
+
events$1.push(buildErrorEvent(message));
|
|
2940
|
+
state$1.messageCompleted = true;
|
|
2941
|
+
return events$1;
|
|
2942
|
+
};
|
|
2943
|
+
const handleErrorEvent = (rawEvent, state$1) => {
|
|
2944
|
+
const message = typeof rawEvent.message === "string" ? rawEvent.message : "An unexpected error occurred during streaming.";
|
|
2945
|
+
state$1.messageCompleted = true;
|
|
2946
|
+
return [buildErrorEvent(message)];
|
|
2947
|
+
};
|
|
2948
|
+
const handleFunctionCallArgumentsValidationError = (error, state$1, events$1 = []) => {
|
|
2949
|
+
const reason = error.message;
|
|
2950
|
+
closeAllOpenBlocks(state$1, events$1);
|
|
2951
|
+
state$1.messageCompleted = true;
|
|
2952
|
+
events$1.push(buildErrorEvent(reason));
|
|
2953
|
+
return events$1;
|
|
2954
|
+
};
|
|
2955
|
+
const messageStart = (state$1, response) => {
|
|
2956
|
+
state$1.messageStartSent = true;
|
|
2957
|
+
const inputCachedTokens = response.usage?.input_tokens_details?.cached_tokens;
|
|
2958
|
+
const inputTokens = (response.usage?.input_tokens ?? 0) - (inputCachedTokens ?? 0);
|
|
2959
|
+
return [{
|
|
2960
|
+
type: "message_start",
|
|
2961
|
+
message: {
|
|
2962
|
+
id: response.id,
|
|
2963
|
+
type: "message",
|
|
2964
|
+
role: "assistant",
|
|
2965
|
+
content: [],
|
|
2966
|
+
model: response.model,
|
|
2967
|
+
stop_reason: null,
|
|
2968
|
+
stop_sequence: null,
|
|
2969
|
+
usage: {
|
|
2970
|
+
input_tokens: inputTokens,
|
|
2971
|
+
output_tokens: 0,
|
|
2972
|
+
cache_read_input_tokens: inputCachedTokens ?? 0
|
|
2973
|
+
}
|
|
2974
|
+
}
|
|
2975
|
+
}];
|
|
2976
|
+
};
|
|
2977
|
+
const openTextBlockIfNeeded = (state$1, params) => {
|
|
2978
|
+
const { outputIndex, contentIndex, events: events$1 } = params;
|
|
2979
|
+
const key = getBlockKey(outputIndex, contentIndex);
|
|
2980
|
+
let blockIndex = state$1.blockIndexByKey.get(key);
|
|
2981
|
+
if (blockIndex === void 0) {
|
|
2982
|
+
blockIndex = state$1.nextContentBlockIndex;
|
|
2983
|
+
state$1.nextContentBlockIndex += 1;
|
|
2984
|
+
state$1.blockIndexByKey.set(key, blockIndex);
|
|
2985
|
+
}
|
|
2986
|
+
if (!state$1.openBlocks.has(blockIndex)) {
|
|
2987
|
+
closeOpenBlocks(state$1, events$1);
|
|
2988
|
+
events$1.push({
|
|
2989
|
+
type: "content_block_start",
|
|
2990
|
+
index: blockIndex,
|
|
2991
|
+
content_block: {
|
|
2992
|
+
type: "text",
|
|
2993
|
+
text: ""
|
|
2994
|
+
}
|
|
2995
|
+
});
|
|
2996
|
+
state$1.openBlocks.add(blockIndex);
|
|
2997
|
+
}
|
|
2998
|
+
return blockIndex;
|
|
2999
|
+
};
|
|
3000
|
+
const openThinkingBlockIfNeeded = (state$1, outputIndex, events$1) => {
|
|
3001
|
+
const key = getBlockKey(outputIndex, 0);
|
|
3002
|
+
let blockIndex = state$1.blockIndexByKey.get(key);
|
|
3003
|
+
if (blockIndex === void 0) {
|
|
3004
|
+
blockIndex = state$1.nextContentBlockIndex;
|
|
3005
|
+
state$1.nextContentBlockIndex += 1;
|
|
3006
|
+
state$1.blockIndexByKey.set(key, blockIndex);
|
|
3007
|
+
}
|
|
3008
|
+
if (!state$1.openBlocks.has(blockIndex)) {
|
|
3009
|
+
closeOpenBlocks(state$1, events$1);
|
|
3010
|
+
events$1.push({
|
|
3011
|
+
type: "content_block_start",
|
|
3012
|
+
index: blockIndex,
|
|
3013
|
+
content_block: {
|
|
3014
|
+
type: "thinking",
|
|
3015
|
+
thinking: ""
|
|
3016
|
+
}
|
|
3017
|
+
});
|
|
3018
|
+
state$1.openBlocks.add(blockIndex);
|
|
3019
|
+
}
|
|
3020
|
+
return blockIndex;
|
|
3021
|
+
};
|
|
3022
|
+
const closeBlockIfOpen = (state$1, blockIndex, events$1) => {
|
|
3023
|
+
if (!state$1.openBlocks.has(blockIndex)) return;
|
|
3024
|
+
events$1.push({
|
|
3025
|
+
type: "content_block_stop",
|
|
3026
|
+
index: blockIndex
|
|
3027
|
+
});
|
|
3028
|
+
state$1.openBlocks.delete(blockIndex);
|
|
3029
|
+
state$1.blockHasDelta.delete(blockIndex);
|
|
3030
|
+
};
|
|
3031
|
+
const closeOpenBlocks = (state$1, events$1) => {
|
|
3032
|
+
for (const blockIndex of state$1.openBlocks) closeBlockIfOpen(state$1, blockIndex, events$1);
|
|
3033
|
+
};
|
|
3034
|
+
const closeAllOpenBlocks = (state$1, events$1) => {
|
|
3035
|
+
closeOpenBlocks(state$1, events$1);
|
|
3036
|
+
state$1.functionCallStateByOutputIndex.clear();
|
|
3037
|
+
};
|
|
3038
|
+
const buildErrorEvent = (message) => ({
|
|
3039
|
+
type: "error",
|
|
3040
|
+
error: {
|
|
3041
|
+
type: "api_error",
|
|
3042
|
+
message
|
|
3043
|
+
}
|
|
3044
|
+
});
|
|
3045
|
+
const getBlockKey = (outputIndex, contentIndex) => `${outputIndex}:${contentIndex}`;
|
|
3046
|
+
const openFunctionCallBlock = (state$1, params) => {
|
|
3047
|
+
const { outputIndex, toolCallId, name, events: events$1 } = params;
|
|
3048
|
+
let functionCallState = state$1.functionCallStateByOutputIndex.get(outputIndex);
|
|
3049
|
+
if (!functionCallState) {
|
|
3050
|
+
const blockIndex$1 = state$1.nextContentBlockIndex;
|
|
3051
|
+
state$1.nextContentBlockIndex += 1;
|
|
3052
|
+
functionCallState = {
|
|
3053
|
+
blockIndex: blockIndex$1,
|
|
3054
|
+
toolCallId: toolCallId ?? `tool_call_${blockIndex$1}`,
|
|
3055
|
+
name: name ?? "function",
|
|
3056
|
+
consecutiveWhitespaceCount: 0
|
|
3057
|
+
};
|
|
3058
|
+
state$1.functionCallStateByOutputIndex.set(outputIndex, functionCallState);
|
|
3059
|
+
}
|
|
3060
|
+
const { blockIndex } = functionCallState;
|
|
3061
|
+
if (!state$1.openBlocks.has(blockIndex)) {
|
|
3062
|
+
closeOpenBlocks(state$1, events$1);
|
|
3063
|
+
events$1.push({
|
|
3064
|
+
type: "content_block_start",
|
|
3065
|
+
index: blockIndex,
|
|
3066
|
+
content_block: {
|
|
3067
|
+
type: "tool_use",
|
|
3068
|
+
id: functionCallState.toolCallId,
|
|
3069
|
+
name: functionCallState.name,
|
|
3070
|
+
input: {}
|
|
3071
|
+
}
|
|
3072
|
+
});
|
|
3073
|
+
state$1.openBlocks.add(blockIndex);
|
|
3074
|
+
}
|
|
3075
|
+
return blockIndex;
|
|
3076
|
+
};
|
|
3077
|
+
const extractFunctionCallDetails = (rawEvent) => {
|
|
3078
|
+
const item = rawEvent.item;
|
|
3079
|
+
if (item.type !== "function_call") return;
|
|
3080
|
+
return {
|
|
3081
|
+
outputIndex: rawEvent.output_index,
|
|
3082
|
+
toolCallId: item.call_id,
|
|
3083
|
+
name: item.name,
|
|
3084
|
+
initialArguments: item.arguments
|
|
3085
|
+
};
|
|
3086
|
+
};
|
|
3087
|
+
|
|
3088
|
+
//#endregion
|
|
3089
|
+
//#region src/routes/responses/utils.ts
|
|
3090
|
+
const getResponsesRequestOptions = (payload) => {
|
|
3091
|
+
return {
|
|
3092
|
+
vision: hasVisionInput(payload),
|
|
3093
|
+
initiator: hasAgentInitiator(payload) ? "agent" : "user"
|
|
3094
|
+
};
|
|
3095
|
+
};
|
|
3096
|
+
const hasAgentInitiator = (payload) => {
|
|
3097
|
+
const lastItem = getPayloadItems(payload).at(-1);
|
|
3098
|
+
if (!lastItem) return false;
|
|
3099
|
+
if (!("role" in lastItem) || !lastItem.role) return true;
|
|
3100
|
+
return (typeof lastItem.role === "string" ? lastItem.role.toLowerCase() : "") === "assistant";
|
|
3101
|
+
};
|
|
3102
|
+
const hasVisionInput = (payload) => {
|
|
3103
|
+
return getPayloadItems(payload).some((item) => containsVisionContent(item));
|
|
3104
|
+
};
|
|
3105
|
+
const getPayloadItems = (payload) => {
|
|
3106
|
+
const result = [];
|
|
3107
|
+
const { input } = payload;
|
|
3108
|
+
if (Array.isArray(input)) result.push(...input);
|
|
3109
|
+
return result;
|
|
3110
|
+
};
|
|
3111
|
+
const containsVisionContent = (value) => {
|
|
3112
|
+
if (!value) return false;
|
|
3113
|
+
if (Array.isArray(value)) return value.some((entry) => containsVisionContent(entry));
|
|
3114
|
+
if (typeof value !== "object") return false;
|
|
3115
|
+
const record = value;
|
|
3116
|
+
if ((typeof record.type === "string" ? record.type.toLowerCase() : void 0) === "input_image") return true;
|
|
3117
|
+
if (Array.isArray(record.content)) return record.content.some((entry) => containsVisionContent(entry));
|
|
3118
|
+
return false;
|
|
3119
|
+
};
|
|
3120
|
+
|
|
3121
|
+
//#endregion
|
|
3122
|
+
//#region src/services/copilot/create-messages.ts
|
|
3123
|
+
const createMessages = async (payload, anthropicBetaHeader, options) => {
|
|
3124
|
+
if (!state.copilotToken) throw new Error("Copilot token not found");
|
|
3125
|
+
const enableVision = payload.messages.some((message) => Array.isArray(message.content) && message.content.some((block) => block.type === "image"));
|
|
3126
|
+
let isInitiateRequest = false;
|
|
3127
|
+
const lastMessage = payload.messages.at(-1);
|
|
3128
|
+
if (lastMessage?.role === "user") isInitiateRequest = Array.isArray(lastMessage.content) ? lastMessage.content.some((block) => block.type !== "tool_result") : true;
|
|
3129
|
+
const initiator = options?.initiator ?? (isInitiateRequest ? "user" : "agent");
|
|
3130
|
+
const headers = {
|
|
3131
|
+
...copilotHeaders(state, enableVision),
|
|
3132
|
+
"X-Initiator": initiator
|
|
3133
|
+
};
|
|
3134
|
+
if (anthropicBetaHeader) {
|
|
3135
|
+
const filteredBeta = anthropicBetaHeader.split(",").map((item) => item.trim()).filter((item) => item !== "claude-code-20250219").join(",");
|
|
3136
|
+
if (filteredBeta) headers["anthropic-beta"] = filteredBeta;
|
|
3137
|
+
} else if (payload.thinking?.budget_tokens) headers["anthropic-beta"] = "interleaved-thinking-2025-05-14";
|
|
3138
|
+
const response = await fetch(`${copilotBaseUrl(state)}/v1/messages`, {
|
|
3139
|
+
method: "POST",
|
|
3140
|
+
headers,
|
|
3141
|
+
body: JSON.stringify(payload)
|
|
3142
|
+
});
|
|
3143
|
+
if (!response.ok) {
|
|
3144
|
+
consola.error("Failed to create messages", response);
|
|
3145
|
+
throw new HTTPError("Failed to create messages", response);
|
|
3146
|
+
}
|
|
3147
|
+
if (payload.stream) return events(response);
|
|
3148
|
+
return await response.json();
|
|
3149
|
+
};
|
|
3150
|
+
|
|
2104
3151
|
//#endregion
|
|
2105
3152
|
//#region src/routes/messages/stream-translation.ts
|
|
2106
3153
|
function isToolBlockOpen(state$1) {
|
|
@@ -2108,32 +3155,22 @@ function isToolBlockOpen(state$1) {
|
|
|
2108
3155
|
return Object.values(state$1.toolCalls).some((tc) => tc.anthropicBlockIndex === state$1.contentBlockIndex);
|
|
2109
3156
|
}
|
|
2110
3157
|
function createMessageDeltaEvents(finishReason, usage) {
|
|
3158
|
+
const stopReason = mapOpenAIStopReasonToAnthropic(finishReason);
|
|
3159
|
+
const inputTokens = usage.prompt_tokens - usage.cached_tokens;
|
|
2111
3160
|
return [{
|
|
2112
3161
|
type: "message_delta",
|
|
2113
3162
|
delta: {
|
|
2114
|
-
stop_reason:
|
|
3163
|
+
stop_reason: stopReason,
|
|
2115
3164
|
stop_sequence: null
|
|
2116
3165
|
},
|
|
2117
3166
|
usage: {
|
|
2118
|
-
input_tokens:
|
|
3167
|
+
input_tokens: inputTokens,
|
|
2119
3168
|
output_tokens: usage.completion_tokens,
|
|
2120
3169
|
cache_creation_input_tokens: 0,
|
|
2121
3170
|
cache_read_input_tokens: usage.cached_tokens
|
|
2122
3171
|
}
|
|
2123
3172
|
}, { type: "message_stop" }];
|
|
2124
3173
|
}
|
|
2125
|
-
function createFallbackMessageDeltaEvents(state$1) {
|
|
2126
|
-
if (state$1.messageDeltaSent) return [];
|
|
2127
|
-
if (state$1.pendingFinishReason) {
|
|
2128
|
-
const usage = state$1.pendingUsage ?? {
|
|
2129
|
-
prompt_tokens: 0,
|
|
2130
|
-
completion_tokens: 0,
|
|
2131
|
-
cached_tokens: 0
|
|
2132
|
-
};
|
|
2133
|
-
return createMessageDeltaEvents(state$1.pendingFinishReason, usage);
|
|
2134
|
-
}
|
|
2135
|
-
return [];
|
|
2136
|
-
}
|
|
2137
3174
|
function translateChunkToAnthropicEvents(chunk, state$1, originalModel) {
|
|
2138
3175
|
const events$1 = [];
|
|
2139
3176
|
if (chunk.usage) {
|
|
@@ -2156,6 +3193,7 @@ function translateChunkToAnthropicEvents(chunk, state$1, originalModel) {
|
|
|
2156
3193
|
completion_tokens: 0,
|
|
2157
3194
|
cached_tokens: 0
|
|
2158
3195
|
};
|
|
3196
|
+
const inputTokens = usage.prompt_tokens - usage.cached_tokens;
|
|
2159
3197
|
events$1.push({
|
|
2160
3198
|
type: "message_start",
|
|
2161
3199
|
message: {
|
|
@@ -2167,8 +3205,8 @@ function translateChunkToAnthropicEvents(chunk, state$1, originalModel) {
|
|
|
2167
3205
|
stop_reason: null,
|
|
2168
3206
|
stop_sequence: null,
|
|
2169
3207
|
usage: {
|
|
2170
|
-
input_tokens:
|
|
2171
|
-
output_tokens:
|
|
3208
|
+
input_tokens: inputTokens,
|
|
3209
|
+
output_tokens: 0,
|
|
2172
3210
|
cache_creation_input_tokens: 0,
|
|
2173
3211
|
cache_read_input_tokens: usage.cached_tokens
|
|
2174
3212
|
}
|
|
@@ -2267,108 +3305,264 @@ function translateChunkToAnthropicEvents(chunk, state$1, originalModel) {
|
|
|
2267
3305
|
}
|
|
2268
3306
|
|
|
2269
3307
|
//#endregion
|
|
2270
|
-
//#region src/routes/messages/
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
const
|
|
2274
|
-
|
|
2275
|
-
for
|
|
2276
|
-
if (
|
|
3308
|
+
//#region src/routes/messages/subagent-marker.ts
|
|
3309
|
+
const subagentMarkerPrefix = "__SUBAGENT_MARKER__";
|
|
3310
|
+
const parseSubagentMarkerFromFirstUser = (payload) => {
|
|
3311
|
+
const firstUserMessage = payload.messages.find((msg) => msg.role === "user");
|
|
3312
|
+
if (!firstUserMessage || !Array.isArray(firstUserMessage.content)) return null;
|
|
3313
|
+
for (const block of firstUserMessage.content) {
|
|
3314
|
+
if (block.type !== "text") continue;
|
|
3315
|
+
const marker = parseSubagentMarkerFromSystemReminder(block.text);
|
|
3316
|
+
if (marker) return marker;
|
|
3317
|
+
}
|
|
3318
|
+
return null;
|
|
3319
|
+
};
|
|
3320
|
+
const parseSubagentMarkerFromSystemReminder = (text) => {
|
|
3321
|
+
const startTag = "<system-reminder>";
|
|
3322
|
+
const endTag = "</system-reminder>";
|
|
3323
|
+
let searchFrom = 0;
|
|
3324
|
+
while (true) {
|
|
3325
|
+
const reminderStart = text.indexOf(startTag, searchFrom);
|
|
3326
|
+
if (reminderStart === -1) break;
|
|
3327
|
+
const contentStart = reminderStart + 17;
|
|
3328
|
+
const reminderEnd = text.indexOf(endTag, contentStart);
|
|
3329
|
+
if (reminderEnd === -1) break;
|
|
3330
|
+
const reminderContent = text.slice(contentStart, reminderEnd);
|
|
3331
|
+
const markerIndex = reminderContent.indexOf(subagentMarkerPrefix);
|
|
3332
|
+
if (markerIndex === -1) {
|
|
3333
|
+
searchFrom = reminderEnd + 18;
|
|
3334
|
+
continue;
|
|
3335
|
+
}
|
|
3336
|
+
const markerJson = reminderContent.slice(markerIndex + 19).trim();
|
|
2277
3337
|
try {
|
|
2278
|
-
const
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
3338
|
+
const parsed = JSON.parse(markerJson);
|
|
3339
|
+
if (!parsed.session_id || !parsed.agent_id || !parsed.agent_type) {
|
|
3340
|
+
searchFrom = reminderEnd + 18;
|
|
3341
|
+
continue;
|
|
3342
|
+
}
|
|
3343
|
+
return parsed;
|
|
3344
|
+
} catch {
|
|
3345
|
+
searchFrom = reminderEnd + 18;
|
|
3346
|
+
continue;
|
|
2287
3347
|
}
|
|
2288
3348
|
}
|
|
2289
|
-
return
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
3349
|
+
return null;
|
|
3350
|
+
};
|
|
3351
|
+
|
|
3352
|
+
//#endregion
|
|
3353
|
+
//#region src/routes/messages/handler.ts
|
|
3354
|
+
const logger$1 = createHandlerLogger("messages-handler");
|
|
3355
|
+
const compactSystemPromptStart = "You are a helpful AI assistant tasked with summarizing conversations";
|
|
2294
3356
|
async function handleCompletion(c) {
|
|
2295
3357
|
await checkRateLimit(state);
|
|
2296
3358
|
const anthropicPayload = await c.req.json();
|
|
2297
|
-
|
|
2298
|
-
const
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
3359
|
+
logger$1.debug("Anthropic request payload:", JSON.stringify(anthropicPayload));
|
|
3360
|
+
const subagentMarker = parseSubagentMarkerFromFirstUser(anthropicPayload);
|
|
3361
|
+
const initiatorOverride = subagentMarker ? "agent" : void 0;
|
|
3362
|
+
if (subagentMarker) logger$1.debug("Detected Subagent marker:", JSON.stringify(subagentMarker));
|
|
3363
|
+
const isCompact = isCompactRequest(anthropicPayload);
|
|
3364
|
+
const anthropicBeta = c.req.header("anthropic-beta");
|
|
3365
|
+
logger$1.debug("Anthropic Beta header:", anthropicBeta);
|
|
3366
|
+
const noTools = !anthropicPayload.tools || anthropicPayload.tools.length === 0;
|
|
3367
|
+
if (anthropicBeta && noTools && !isCompact) anthropicPayload.model = getSmallModel();
|
|
3368
|
+
if (isCompact) {
|
|
3369
|
+
logger$1.debug("Is compact request:", isCompact);
|
|
3370
|
+
if (shouldCompactUseSmallModel()) anthropicPayload.model = getSmallModel();
|
|
3371
|
+
} else mergeToolResultForClaude(anthropicPayload);
|
|
2304
3372
|
if (state.manualApprove) await awaitApproval();
|
|
2305
|
-
const
|
|
2306
|
-
if (
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
model: openAIPayload.model
|
|
2311
|
-
});
|
|
2312
|
-
} else setRequestContext(c, {
|
|
2313
|
-
provider: "Copilot",
|
|
2314
|
-
model: openAIPayload.model
|
|
3373
|
+
const selectedModel = state.models?.data.find((m) => m.id === anthropicPayload.model);
|
|
3374
|
+
if (shouldUseMessagesApi(selectedModel)) return await handleWithMessagesApi(c, anthropicPayload, {
|
|
3375
|
+
anthropicBetaHeader: anthropicBeta,
|
|
3376
|
+
initiatorOverride,
|
|
3377
|
+
selectedModel
|
|
2315
3378
|
});
|
|
2316
|
-
if (anthropicPayload
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
3379
|
+
if (shouldUseResponsesApi(selectedModel)) return await handleWithResponsesApi(c, anthropicPayload, initiatorOverride);
|
|
3380
|
+
return await handleWithChatCompletions(c, anthropicPayload, initiatorOverride);
|
|
3381
|
+
}
|
|
3382
|
+
const RESPONSES_ENDPOINT$1 = "/responses";
|
|
3383
|
+
const MESSAGES_ENDPOINT = "/v1/messages";
|
|
3384
|
+
const handleWithChatCompletions = async (c, anthropicPayload, initiatorOverride) => {
|
|
3385
|
+
let finalPayload = await applyReplacementsToPayload(translateToOpenAI(anthropicPayload));
|
|
3386
|
+
finalPayload = {
|
|
3387
|
+
...finalPayload,
|
|
3388
|
+
model: normalizeModelName(finalPayload.model)
|
|
3389
|
+
};
|
|
3390
|
+
logger$1.debug("Translated OpenAI request payload:", JSON.stringify(finalPayload));
|
|
3391
|
+
const response = isAzureOpenAIModel(finalPayload.model) && state.azureOpenAIConfig ? await createAzureOpenAIChatCompletions(state.azureOpenAIConfig, finalPayload) : await createChatCompletions(finalPayload, { initiator: initiatorOverride });
|
|
3392
|
+
if (isNonStreaming(response)) {
|
|
3393
|
+
logger$1.debug("Non-streaming response from Copilot:", JSON.stringify(response).slice(-400));
|
|
3394
|
+
const anthropicResponse = translateToAnthropic(response);
|
|
3395
|
+
logger$1.debug("Translated Anthropic response:", JSON.stringify(anthropicResponse));
|
|
3396
|
+
return c.json(anthropicResponse);
|
|
3397
|
+
}
|
|
3398
|
+
logger$1.debug("Streaming response from Copilot");
|
|
3399
|
+
return streamSSE(c, async (stream) => {
|
|
3400
|
+
const streamState = {
|
|
3401
|
+
messageStartSent: false,
|
|
3402
|
+
contentBlockIndex: 0,
|
|
3403
|
+
contentBlockOpen: false,
|
|
3404
|
+
toolCalls: {}
|
|
2321
3405
|
};
|
|
2322
|
-
const
|
|
2323
|
-
|
|
3406
|
+
for await (const rawEvent of response) {
|
|
3407
|
+
logger$1.debug("Copilot raw stream event:", JSON.stringify(rawEvent));
|
|
3408
|
+
if (rawEvent.data === "[DONE]") break;
|
|
3409
|
+
if (!rawEvent.data) continue;
|
|
3410
|
+
const events$1 = translateChunkToAnthropicEvents(JSON.parse(rawEvent.data), streamState);
|
|
3411
|
+
for (const event of events$1) {
|
|
3412
|
+
logger$1.debug("Translated Anthropic event:", JSON.stringify(event));
|
|
3413
|
+
await stream.writeSSE({
|
|
3414
|
+
event: event.type,
|
|
3415
|
+
data: JSON.stringify(event)
|
|
3416
|
+
});
|
|
3417
|
+
}
|
|
3418
|
+
}
|
|
3419
|
+
});
|
|
3420
|
+
};
|
|
3421
|
+
const handleWithResponsesApi = async (c, anthropicPayload, initiatorOverride) => {
|
|
3422
|
+
const responsesPayload = translateAnthropicMessagesToResponsesPayload(anthropicPayload);
|
|
3423
|
+
logger$1.debug("Translated Responses payload:", JSON.stringify(responsesPayload));
|
|
3424
|
+
const { vision, initiator } = getResponsesRequestOptions(responsesPayload);
|
|
3425
|
+
const response = await createResponses(responsesPayload, {
|
|
3426
|
+
vision,
|
|
3427
|
+
initiator: initiatorOverride ?? initiator
|
|
3428
|
+
});
|
|
3429
|
+
if (responsesPayload.stream && isAsyncIterable$1(response)) {
|
|
3430
|
+
logger$1.debug("Streaming response from Copilot (Responses API)");
|
|
2324
3431
|
return streamSSE(c, async (stream) => {
|
|
2325
|
-
const
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
inputTokens: usage.prompt_tokens,
|
|
2329
|
-
outputTokens: usage.completion_tokens
|
|
2330
|
-
});
|
|
2331
|
-
const streamState = {
|
|
2332
|
-
messageStartSent: false,
|
|
2333
|
-
contentBlockOpen: false,
|
|
2334
|
-
contentBlockIndex: 0,
|
|
2335
|
-
toolCalls: {},
|
|
2336
|
-
pendingUsage: usage ?? void 0
|
|
2337
|
-
};
|
|
2338
|
-
for (const chunk of chunks) {
|
|
2339
|
-
const events$1 = translateChunkToAnthropicEvents(chunk, streamState, anthropicPayload.model);
|
|
2340
|
-
for (const evt of events$1) {
|
|
2341
|
-
consola.debug(`[stream] Emitting event: ${evt.type}`);
|
|
3432
|
+
const streamState = createResponsesStreamState();
|
|
3433
|
+
for await (const chunk of response) {
|
|
3434
|
+
if (chunk.event === "ping") {
|
|
2342
3435
|
await stream.writeSSE({
|
|
2343
|
-
event:
|
|
2344
|
-
data:
|
|
3436
|
+
event: "ping",
|
|
3437
|
+
data: "{\"type\":\"ping\"}"
|
|
2345
3438
|
});
|
|
3439
|
+
continue;
|
|
3440
|
+
}
|
|
3441
|
+
const data = chunk.data;
|
|
3442
|
+
if (!data) continue;
|
|
3443
|
+
logger$1.debug("Responses raw stream event:", data);
|
|
3444
|
+
const events$1 = translateResponsesStreamEvent(JSON.parse(data), streamState);
|
|
3445
|
+
for (const event of events$1) {
|
|
3446
|
+
const eventData = JSON.stringify(event);
|
|
3447
|
+
logger$1.debug("Translated Anthropic event:", eventData);
|
|
3448
|
+
await stream.writeSSE({
|
|
3449
|
+
event: event.type,
|
|
3450
|
+
data: eventData
|
|
3451
|
+
});
|
|
3452
|
+
}
|
|
3453
|
+
if (streamState.messageCompleted) {
|
|
3454
|
+
logger$1.debug("Message completed, ending stream");
|
|
3455
|
+
break;
|
|
2346
3456
|
}
|
|
2347
3457
|
}
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
consola.debug(`[stream] Emitting fallback event: ${evt.type}`);
|
|
3458
|
+
if (!streamState.messageCompleted) {
|
|
3459
|
+
logger$1.warn("Responses stream ended without completion; sending error event");
|
|
3460
|
+
const errorEvent = buildErrorEvent("Responses stream ended without completion");
|
|
2352
3461
|
await stream.writeSSE({
|
|
2353
|
-
event:
|
|
2354
|
-
data: JSON.stringify(
|
|
3462
|
+
event: errorEvent.type,
|
|
3463
|
+
data: JSON.stringify(errorEvent)
|
|
2355
3464
|
});
|
|
2356
3465
|
}
|
|
2357
3466
|
});
|
|
2358
3467
|
}
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
};
|
|
2363
|
-
const azureConfigNonStream = state.azureOpenAIConfig;
|
|
2364
|
-
const response = isAzureModel && azureConfigNonStream ? await createAzureOpenAIChatCompletions(azureConfigNonStream, nonStreamPayload) : await createChatCompletions(nonStreamPayload);
|
|
2365
|
-
if (response.usage) setRequestContext(c, {
|
|
2366
|
-
inputTokens: response.usage.prompt_tokens,
|
|
2367
|
-
outputTokens: response.usage.completion_tokens
|
|
2368
|
-
});
|
|
2369
|
-
const anthropicResponse = translateToAnthropic(response, anthropicPayload.model);
|
|
3468
|
+
logger$1.debug("Non-streaming Responses result:", JSON.stringify(response).slice(-400));
|
|
3469
|
+
const anthropicResponse = translateResponsesResultToAnthropic(response);
|
|
3470
|
+
logger$1.debug("Translated Anthropic response:", JSON.stringify(anthropicResponse));
|
|
2370
3471
|
return c.json(anthropicResponse);
|
|
2371
|
-
}
|
|
3472
|
+
};
|
|
3473
|
+
const handleWithMessagesApi = async (c, anthropicPayload, options) => {
|
|
3474
|
+
const { anthropicBetaHeader, initiatorOverride, selectedModel } = options ?? {};
|
|
3475
|
+
for (const msg of anthropicPayload.messages) if (msg.role === "assistant" && Array.isArray(msg.content)) msg.content = msg.content.filter((block) => {
|
|
3476
|
+
if (block.type !== "thinking") return true;
|
|
3477
|
+
return block.thinking && block.thinking !== "Thinking..." && block.signature && !block.signature.includes("@");
|
|
3478
|
+
});
|
|
3479
|
+
if (selectedModel?.capabilities.supports.adaptive_thinking) {
|
|
3480
|
+
anthropicPayload.thinking = { type: "adaptive" };
|
|
3481
|
+
anthropicPayload.output_config = { effort: getAnthropicEffortForModel(anthropicPayload.model) };
|
|
3482
|
+
}
|
|
3483
|
+
logger$1.debug("Translated Messages payload:", JSON.stringify(anthropicPayload));
|
|
3484
|
+
const response = await createMessages(anthropicPayload, anthropicBetaHeader, { initiator: initiatorOverride });
|
|
3485
|
+
if (isAsyncIterable$1(response)) {
|
|
3486
|
+
logger$1.debug("Streaming response from Copilot (Messages API)");
|
|
3487
|
+
return streamSSE(c, async (stream) => {
|
|
3488
|
+
for await (const event of response) {
|
|
3489
|
+
const eventName = event.event;
|
|
3490
|
+
const data = event.data ?? "";
|
|
3491
|
+
logger$1.debug("Messages raw stream event:", data);
|
|
3492
|
+
await stream.writeSSE({
|
|
3493
|
+
event: eventName,
|
|
3494
|
+
data
|
|
3495
|
+
});
|
|
3496
|
+
}
|
|
3497
|
+
});
|
|
3498
|
+
}
|
|
3499
|
+
logger$1.debug("Non-streaming Messages result:", JSON.stringify(response).slice(-400));
|
|
3500
|
+
return c.json(response);
|
|
3501
|
+
};
|
|
3502
|
+
const shouldUseResponsesApi = (selectedModel) => {
|
|
3503
|
+
return selectedModel?.supported_endpoints?.includes(RESPONSES_ENDPOINT$1) ?? false;
|
|
3504
|
+
};
|
|
3505
|
+
const shouldUseMessagesApi = (selectedModel) => {
|
|
3506
|
+
return selectedModel?.supported_endpoints?.includes(MESSAGES_ENDPOINT) ?? false;
|
|
3507
|
+
};
|
|
3508
|
+
const isNonStreaming = (response) => Object.hasOwn(response, "choices");
|
|
3509
|
+
const isAsyncIterable$1 = (value) => Boolean(value) && typeof value[Symbol.asyncIterator] === "function";
|
|
3510
|
+
const getAnthropicEffortForModel = (model) => {
|
|
3511
|
+
const reasoningEffort = getReasoningEffortForModel(model);
|
|
3512
|
+
if (reasoningEffort === "xhigh") return "max";
|
|
3513
|
+
if (reasoningEffort === "none" || reasoningEffort === "minimal") return "low";
|
|
3514
|
+
return reasoningEffort;
|
|
3515
|
+
};
|
|
3516
|
+
const isCompactRequest = (anthropicPayload) => {
|
|
3517
|
+
const system = anthropicPayload.system;
|
|
3518
|
+
if (typeof system === "string") return system.startsWith(compactSystemPromptStart);
|
|
3519
|
+
if (!Array.isArray(system)) return false;
|
|
3520
|
+
return system.some((msg) => typeof msg.text === "string" && msg.text.startsWith(compactSystemPromptStart));
|
|
3521
|
+
};
|
|
3522
|
+
const mergeContentWithText = (tr, textBlock) => {
|
|
3523
|
+
if (typeof tr.content === "string") return {
|
|
3524
|
+
...tr,
|
|
3525
|
+
content: `${tr.content}\n\n${textBlock.text}`
|
|
3526
|
+
};
|
|
3527
|
+
return {
|
|
3528
|
+
...tr,
|
|
3529
|
+
content: [...tr.content, textBlock]
|
|
3530
|
+
};
|
|
3531
|
+
};
|
|
3532
|
+
const mergeContentWithTexts = (tr, textBlocks) => {
|
|
3533
|
+
if (typeof tr.content === "string") {
|
|
3534
|
+
const appendedTexts = textBlocks.map((tb) => tb.text).join("\n\n");
|
|
3535
|
+
return {
|
|
3536
|
+
...tr,
|
|
3537
|
+
content: `${tr.content}\n\n${appendedTexts}`
|
|
3538
|
+
};
|
|
3539
|
+
}
|
|
3540
|
+
return {
|
|
3541
|
+
...tr,
|
|
3542
|
+
content: [...tr.content, ...textBlocks]
|
|
3543
|
+
};
|
|
3544
|
+
};
|
|
3545
|
+
const mergeToolResultForClaude = (anthropicPayload) => {
|
|
3546
|
+
for (const msg of anthropicPayload.messages) {
|
|
3547
|
+
if (msg.role !== "user" || !Array.isArray(msg.content)) continue;
|
|
3548
|
+
const toolResults = [];
|
|
3549
|
+
const textBlocks = [];
|
|
3550
|
+
let valid = true;
|
|
3551
|
+
for (const block of msg.content) if (block.type === "tool_result") toolResults.push(block);
|
|
3552
|
+
else if (block.type === "text") textBlocks.push(block);
|
|
3553
|
+
else {
|
|
3554
|
+
valid = false;
|
|
3555
|
+
break;
|
|
3556
|
+
}
|
|
3557
|
+
if (!valid || toolResults.length === 0 || textBlocks.length === 0) continue;
|
|
3558
|
+
msg.content = mergeToolResult(toolResults, textBlocks);
|
|
3559
|
+
}
|
|
3560
|
+
};
|
|
3561
|
+
const mergeToolResult = (toolResults, textBlocks) => {
|
|
3562
|
+
if (toolResults.length === textBlocks.length) return toolResults.map((tr, i) => mergeContentWithText(tr, textBlocks[i]));
|
|
3563
|
+
const lastIndex = toolResults.length - 1;
|
|
3564
|
+
return toolResults.map((tr, i) => i === lastIndex ? mergeContentWithTexts(tr, textBlocks) : tr);
|
|
3565
|
+
};
|
|
2372
3566
|
|
|
2373
3567
|
//#endregion
|
|
2374
3568
|
//#region src/routes/messages/route.ts
|
|
@@ -2439,20 +3633,16 @@ replacementsRoute.post("/", async (c) => {
|
|
|
2439
3633
|
return c.json(rule, 201);
|
|
2440
3634
|
});
|
|
2441
3635
|
replacementsRoute.delete("/:id", async (c) => {
|
|
2442
|
-
|
|
2443
|
-
if (!await removeReplacement(id)) return c.json({ error: "Replacement not found or is a system rule" }, 404);
|
|
3636
|
+
if (!await removeReplacement(c.req.param("id"))) return c.json({ error: "Replacement not found or is a system rule" }, 404);
|
|
2444
3637
|
return c.json({ success: true });
|
|
2445
3638
|
});
|
|
2446
3639
|
replacementsRoute.patch("/:id", async (c) => {
|
|
2447
|
-
const
|
|
2448
|
-
const body = await c.req.json();
|
|
2449
|
-
const rule = await updateReplacement(id, body);
|
|
3640
|
+
const rule = await updateReplacement(c.req.param("id"), await c.req.json());
|
|
2450
3641
|
if (!rule) return c.json({ error: "Replacement not found or is a system rule" }, 404);
|
|
2451
3642
|
return c.json(rule);
|
|
2452
3643
|
});
|
|
2453
3644
|
replacementsRoute.patch("/:id/toggle", async (c) => {
|
|
2454
|
-
const
|
|
2455
|
-
const rule = await toggleReplacement(id);
|
|
3645
|
+
const rule = await toggleReplacement(c.req.param("id"));
|
|
2456
3646
|
if (!rule) return c.json({ error: "Replacement not found or is a system rule" }, 404);
|
|
2457
3647
|
return c.json(rule);
|
|
2458
3648
|
});
|
|
@@ -2461,6 +3651,130 @@ replacementsRoute.delete("/", async (c) => {
|
|
|
2461
3651
|
return c.json({ success: true });
|
|
2462
3652
|
});
|
|
2463
3653
|
|
|
3654
|
+
//#endregion
|
|
3655
|
+
//#region src/routes/responses/stream-id-sync.ts
|
|
3656
|
+
const createStreamIdTracker = () => ({ outputItems: /* @__PURE__ */ new Map() });
|
|
3657
|
+
const fixStreamIds = (data, event, tracker) => {
|
|
3658
|
+
if (!data) return data;
|
|
3659
|
+
const parsed = JSON.parse(data);
|
|
3660
|
+
switch (event) {
|
|
3661
|
+
case "response.output_item.added": return handleOutputItemAdded(parsed, tracker);
|
|
3662
|
+
case "response.output_item.done": return handleOutputItemDone(parsed, tracker);
|
|
3663
|
+
default: return handleItemId(parsed, tracker);
|
|
3664
|
+
}
|
|
3665
|
+
};
|
|
3666
|
+
const handleOutputItemAdded = (parsed, tracker) => {
|
|
3667
|
+
if (!parsed.item.id) {
|
|
3668
|
+
let randomSuffix = "";
|
|
3669
|
+
while (randomSuffix.length < 16) randomSuffix += Math.random().toString(36).slice(2);
|
|
3670
|
+
parsed.item.id = `oi_${parsed.output_index}_${randomSuffix.slice(0, 16)}`;
|
|
3671
|
+
}
|
|
3672
|
+
const outputIndex = parsed.output_index;
|
|
3673
|
+
tracker.outputItems.set(outputIndex, parsed.item.id);
|
|
3674
|
+
return JSON.stringify(parsed);
|
|
3675
|
+
};
|
|
3676
|
+
const handleOutputItemDone = (parsed, tracker) => {
|
|
3677
|
+
const outputIndex = parsed.output_index;
|
|
3678
|
+
const originalId = tracker.outputItems.get(outputIndex);
|
|
3679
|
+
if (originalId) parsed.item.id = originalId;
|
|
3680
|
+
return JSON.stringify(parsed);
|
|
3681
|
+
};
|
|
3682
|
+
const handleItemId = (parsed, tracker) => {
|
|
3683
|
+
const outputIndex = parsed.output_index;
|
|
3684
|
+
if (outputIndex !== void 0) {
|
|
3685
|
+
const itemId = tracker.outputItems.get(outputIndex);
|
|
3686
|
+
if (itemId) parsed.item_id = itemId;
|
|
3687
|
+
}
|
|
3688
|
+
return JSON.stringify(parsed);
|
|
3689
|
+
};
|
|
3690
|
+
|
|
3691
|
+
//#endregion
|
|
3692
|
+
//#region src/routes/responses/handler.ts
|
|
3693
|
+
const logger = createHandlerLogger("responses-handler");
|
|
3694
|
+
const RESPONSES_ENDPOINT = "/responses";
|
|
3695
|
+
const handleResponses = async (c) => {
|
|
3696
|
+
await checkRateLimit(state);
|
|
3697
|
+
const payload = await c.req.json();
|
|
3698
|
+
setRequestContext(c, {
|
|
3699
|
+
provider: "Copilot (Responses)",
|
|
3700
|
+
model: payload.model
|
|
3701
|
+
});
|
|
3702
|
+
logger.debug("Responses request payload:", JSON.stringify(payload));
|
|
3703
|
+
useFunctionApplyPatch(payload);
|
|
3704
|
+
removeWebSearchTool(payload);
|
|
3705
|
+
if (!((state.models?.data.find((model) => model.id === payload.model))?.supported_endpoints?.includes(RESPONSES_ENDPOINT) ?? false)) return c.json({ error: {
|
|
3706
|
+
message: "This model does not support the responses endpoint. Please choose a different model.",
|
|
3707
|
+
type: "invalid_request_error"
|
|
3708
|
+
} }, 400);
|
|
3709
|
+
const { vision, initiator } = getResponsesRequestOptions(payload);
|
|
3710
|
+
if (state.manualApprove) await awaitApproval();
|
|
3711
|
+
const response = await createResponses(payload, {
|
|
3712
|
+
vision,
|
|
3713
|
+
initiator
|
|
3714
|
+
});
|
|
3715
|
+
if (isStreamingRequested(payload) && isAsyncIterable(response)) {
|
|
3716
|
+
logger.debug("Forwarding native Responses stream");
|
|
3717
|
+
return streamSSE(c, async (stream) => {
|
|
3718
|
+
const idTracker = createStreamIdTracker();
|
|
3719
|
+
for await (const chunk of response) {
|
|
3720
|
+
logger.debug("Responses stream chunk:", JSON.stringify(chunk));
|
|
3721
|
+
const processedData = fixStreamIds(chunk.data ?? "", chunk.event, idTracker);
|
|
3722
|
+
await stream.writeSSE({
|
|
3723
|
+
id: chunk.id,
|
|
3724
|
+
event: chunk.event,
|
|
3725
|
+
data: processedData
|
|
3726
|
+
});
|
|
3727
|
+
}
|
|
3728
|
+
});
|
|
3729
|
+
}
|
|
3730
|
+
logger.debug("Forwarding native Responses result:", JSON.stringify(response).slice(-400));
|
|
3731
|
+
return c.json(response);
|
|
3732
|
+
};
|
|
3733
|
+
const isAsyncIterable = (value) => Boolean(value) && typeof value[Symbol.asyncIterator] === "function";
|
|
3734
|
+
const isStreamingRequested = (payload) => Boolean(payload.stream);
|
|
3735
|
+
const useFunctionApplyPatch = (payload) => {
|
|
3736
|
+
if (getConfig().useFunctionApplyPatch ?? true) {
|
|
3737
|
+
logger.debug("Using function tool apply_patch for responses");
|
|
3738
|
+
if (Array.isArray(payload.tools)) {
|
|
3739
|
+
const toolsArr = payload.tools;
|
|
3740
|
+
for (let i = 0; i < toolsArr.length; i++) {
|
|
3741
|
+
const t = toolsArr[i];
|
|
3742
|
+
if (t.type === "custom" && t.name === "apply_patch") toolsArr[i] = {
|
|
3743
|
+
type: "function",
|
|
3744
|
+
name: t.name,
|
|
3745
|
+
description: "Use the `apply_patch` tool to edit files",
|
|
3746
|
+
parameters: {
|
|
3747
|
+
type: "object",
|
|
3748
|
+
properties: { input: {
|
|
3749
|
+
type: "string",
|
|
3750
|
+
description: "The entire contents of the apply_patch command"
|
|
3751
|
+
} },
|
|
3752
|
+
required: ["input"]
|
|
3753
|
+
},
|
|
3754
|
+
strict: false
|
|
3755
|
+
};
|
|
3756
|
+
}
|
|
3757
|
+
}
|
|
3758
|
+
}
|
|
3759
|
+
};
|
|
3760
|
+
const removeWebSearchTool = (payload) => {
|
|
3761
|
+
if (!Array.isArray(payload.tools) || payload.tools.length === 0) return;
|
|
3762
|
+
payload.tools = payload.tools.filter((t) => {
|
|
3763
|
+
return t.type !== "web_search";
|
|
3764
|
+
});
|
|
3765
|
+
};
|
|
3766
|
+
|
|
3767
|
+
//#endregion
|
|
3768
|
+
//#region src/routes/responses/route.ts
|
|
3769
|
+
const responsesRoutes = new Hono();
|
|
3770
|
+
responsesRoutes.post("/", async (c) => {
|
|
3771
|
+
try {
|
|
3772
|
+
return await handleResponses(c);
|
|
3773
|
+
} catch (error) {
|
|
3774
|
+
return await forwardError(c, error);
|
|
3775
|
+
}
|
|
3776
|
+
});
|
|
3777
|
+
|
|
2464
3778
|
//#endregion
|
|
2465
3779
|
//#region src/routes/token/route.ts
|
|
2466
3780
|
const tokenRoute = new Hono();
|
|
@@ -2494,6 +3808,7 @@ usageRoute.get("/", async (c) => {
|
|
|
2494
3808
|
const server = new Hono();
|
|
2495
3809
|
server.use(requestLogger);
|
|
2496
3810
|
server.use(cors());
|
|
3811
|
+
server.use("*", createAuthMiddleware());
|
|
2497
3812
|
server.get("/", (c) => c.text("Server running"));
|
|
2498
3813
|
server.route("/chat/completions", completionRoutes);
|
|
2499
3814
|
server.route("/models", modelRoutes);
|
|
@@ -2501,15 +3816,17 @@ server.route("/embeddings", embeddingRoutes);
|
|
|
2501
3816
|
server.route("/usage", usageRoute);
|
|
2502
3817
|
server.route("/token", tokenRoute);
|
|
2503
3818
|
server.route("/replacements", replacementsRoute);
|
|
3819
|
+
server.route("/responses", responsesRoutes);
|
|
2504
3820
|
server.route("/v1/chat/completions", completionRoutes);
|
|
2505
3821
|
server.route("/v1/models", modelRoutes);
|
|
2506
3822
|
server.route("/v1/embeddings", embeddingRoutes);
|
|
3823
|
+
server.route("/v1/responses", responsesRoutes);
|
|
2507
3824
|
server.route("/v1/messages", messageRoutes);
|
|
2508
3825
|
|
|
2509
3826
|
//#endregion
|
|
2510
3827
|
//#region src/start.ts
|
|
2511
3828
|
async function runServer(options) {
|
|
2512
|
-
consola.info(`copilot-api v${
|
|
3829
|
+
consola.info(`copilot-api v${version}`);
|
|
2513
3830
|
if (options.insecure) {
|
|
2514
3831
|
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
|
|
2515
3832
|
consola.warn("SSL certificate verification disabled (insecure mode)");
|
|
@@ -2526,8 +3843,10 @@ async function runServer(options) {
|
|
|
2526
3843
|
state.rateLimitWait = options.rateLimitWait;
|
|
2527
3844
|
state.showToken = options.showToken;
|
|
2528
3845
|
state.debug = options.debug;
|
|
3846
|
+
state.verbose = options.verbose;
|
|
2529
3847
|
if (options.debug) consola.info("Debug mode enabled - raw HTTP requests will be logged");
|
|
2530
3848
|
await ensurePaths();
|
|
3849
|
+
mergeConfigWithDefaults();
|
|
2531
3850
|
await cacheVSCodeVersion();
|
|
2532
3851
|
if (options.githubToken) {
|
|
2533
3852
|
state.githubToken = options.githubToken;
|
|
@@ -2671,10 +3990,10 @@ const start = defineCommand({
|
|
|
2671
3990
|
|
|
2672
3991
|
//#endregion
|
|
2673
3992
|
//#region src/main.ts
|
|
2674
|
-
|
|
3993
|
+
await runMain(defineCommand({
|
|
2675
3994
|
meta: {
|
|
2676
3995
|
name: "copilot-api",
|
|
2677
|
-
version
|
|
3996
|
+
version,
|
|
2678
3997
|
description: "A wrapper around GitHub Copilot API to make it OpenAI compatible, making it usable for other tools."
|
|
2679
3998
|
},
|
|
2680
3999
|
subCommands: {
|
|
@@ -2684,8 +4003,7 @@ const main = defineCommand({
|
|
|
2684
4003
|
debug,
|
|
2685
4004
|
config
|
|
2686
4005
|
}
|
|
2687
|
-
});
|
|
2688
|
-
await runMain(main);
|
|
4006
|
+
}));
|
|
2689
4007
|
|
|
2690
4008
|
//#endregion
|
|
2691
4009
|
export { };
|