@ashsec/copilot-api 0.7.13 → 0.9.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 +1622 -387
- package/dist/main.js.map +1 -1
- package/package.json +69 -68
package/dist/main.js
CHANGED
|
@@ -5,10 +5,10 @@ import fs from "node:fs/promises";
|
|
|
5
5
|
import os from "node:os";
|
|
6
6
|
import path from "node:path";
|
|
7
7
|
import { randomUUID } from "node:crypto";
|
|
8
|
-
import { events } from "fetch-event-stream";
|
|
9
8
|
import clipboard from "clipboardy";
|
|
10
9
|
import { serve } from "srvx";
|
|
11
10
|
import invariant from "tiny-invariant";
|
|
11
|
+
import fs$1 from "node:fs";
|
|
12
12
|
import { getProxyForUrl } from "proxy-from-env";
|
|
13
13
|
import { Agent, ProxyAgent, setGlobalDispatcher } from "undici";
|
|
14
14
|
import { execSync } from "node:child_process";
|
|
@@ -16,10 +16,12 @@ import process$1 from "node:process";
|
|
|
16
16
|
import { Hono } from "hono";
|
|
17
17
|
import { cors } from "hono/cors";
|
|
18
18
|
import { streamSSE } from "hono/streaming";
|
|
19
|
+
import { events } from "fetch-event-stream";
|
|
20
|
+
import util from "node:util";
|
|
19
21
|
|
|
20
22
|
//#region package.json
|
|
21
23
|
var name = "@ashsec/copilot-api";
|
|
22
|
-
var version = "0.
|
|
24
|
+
var version = "0.9.0";
|
|
23
25
|
var description = "Turn GitHub Copilot into OpenAI/Anthropic API compatible server. Usable with Claude Code!";
|
|
24
26
|
var keywords = [
|
|
25
27
|
"proxy",
|
|
@@ -57,6 +59,7 @@ var dependencies = {
|
|
|
57
59
|
"fetch-event-stream": "^0.1.5",
|
|
58
60
|
"gpt-tokenizer": "^3.0.1",
|
|
59
61
|
"hono": "^4.9.9",
|
|
62
|
+
"ms": "^2.1.3",
|
|
60
63
|
"proxy-from-env": "^1.1.0",
|
|
61
64
|
"srvx": "^0.8.9",
|
|
62
65
|
"tiny-invariant": "^1.3.3",
|
|
@@ -99,18 +102,16 @@ var package_default = {
|
|
|
99
102
|
//#region src/lib/paths.ts
|
|
100
103
|
const APP_DIR = path.join(os.homedir(), ".local", "share", "copilot-api");
|
|
101
104
|
const GITHUB_TOKEN_PATH = path.join(APP_DIR, "github_token");
|
|
102
|
-
const AZURE_OPENAI_CONFIG_PATH = path.join(APP_DIR, "azure_openai_config");
|
|
103
105
|
const REPLACEMENTS_CONFIG_PATH = path.join(APP_DIR, "replacements.json");
|
|
104
106
|
const PATHS = {
|
|
105
107
|
APP_DIR,
|
|
108
|
+
CONFIG_PATH: path.join(APP_DIR, "config.json"),
|
|
106
109
|
GITHUB_TOKEN_PATH,
|
|
107
|
-
AZURE_OPENAI_CONFIG_PATH,
|
|
108
110
|
REPLACEMENTS_CONFIG_PATH
|
|
109
111
|
};
|
|
110
112
|
async function ensurePaths() {
|
|
111
113
|
await fs.mkdir(PATHS.APP_DIR, { recursive: true });
|
|
112
114
|
await ensureFile(PATHS.GITHUB_TOKEN_PATH);
|
|
113
|
-
await ensureFile(PATHS.AZURE_OPENAI_CONFIG_PATH);
|
|
114
115
|
}
|
|
115
116
|
async function ensureFile(filePath) {
|
|
116
117
|
try {
|
|
@@ -128,7 +129,8 @@ const state = {
|
|
|
128
129
|
manualApprove: false,
|
|
129
130
|
rateLimitWait: false,
|
|
130
131
|
showToken: false,
|
|
131
|
-
debug: false
|
|
132
|
+
debug: false,
|
|
133
|
+
verbose: false
|
|
132
134
|
};
|
|
133
135
|
|
|
134
136
|
//#endregion
|
|
@@ -137,10 +139,10 @@ const standardHeaders = () => ({
|
|
|
137
139
|
"content-type": "application/json",
|
|
138
140
|
accept: "application/json"
|
|
139
141
|
});
|
|
140
|
-
const COPILOT_VERSION = "0.
|
|
142
|
+
const COPILOT_VERSION = "0.37.6";
|
|
141
143
|
const EDITOR_PLUGIN_VERSION = `copilot-chat/${COPILOT_VERSION}`;
|
|
142
144
|
const USER_AGENT = `GitHubCopilotChat/${COPILOT_VERSION}`;
|
|
143
|
-
const API_VERSION = "2025-
|
|
145
|
+
const API_VERSION = "2025-10-01";
|
|
144
146
|
const copilotBaseUrl = (state$1) => state$1.accountType === "individual" ? "https://api.githubcopilot.com" : `https://api.${state$1.accountType}.githubcopilot.com`;
|
|
145
147
|
const copilotHeaders = (state$1, vision = false) => {
|
|
146
148
|
const headers = {
|
|
@@ -150,7 +152,7 @@ const copilotHeaders = (state$1, vision = false) => {
|
|
|
150
152
|
"editor-version": `vscode/${state$1.vsCodeVersion}`,
|
|
151
153
|
"editor-plugin-version": EDITOR_PLUGIN_VERSION,
|
|
152
154
|
"user-agent": USER_AGENT,
|
|
153
|
-
"openai-intent": "conversation-
|
|
155
|
+
"openai-intent": "conversation-agent",
|
|
154
156
|
"x-github-api-version": API_VERSION,
|
|
155
157
|
"x-request-id": randomUUID(),
|
|
156
158
|
"x-vscode-user-agent-library-version": "electron-fetch"
|
|
@@ -251,56 +253,6 @@ async function getGitHubUser() {
|
|
|
251
253
|
return await response.json();
|
|
252
254
|
}
|
|
253
255
|
|
|
254
|
-
//#endregion
|
|
255
|
-
//#region src/services/azure-openai/config.ts
|
|
256
|
-
const AZURE_OPENAI_MODEL_PREFIX = "azure_openai_";
|
|
257
|
-
async function loadAzureOpenAIConfig() {
|
|
258
|
-
try {
|
|
259
|
-
const content = await fs.readFile(PATHS.AZURE_OPENAI_CONFIG_PATH, "utf8");
|
|
260
|
-
if (!content.trim()) return null;
|
|
261
|
-
const decoded = Buffer.from(content.trim(), "base64").toString("utf8");
|
|
262
|
-
const config$1 = JSON.parse(decoded);
|
|
263
|
-
if (!config$1.endpoint || !config$1.apiKey) return null;
|
|
264
|
-
return config$1;
|
|
265
|
-
} catch {
|
|
266
|
-
return null;
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
async function saveAzureOpenAIConfig(config$1) {
|
|
270
|
-
const encoded = Buffer.from(JSON.stringify(config$1)).toString("base64");
|
|
271
|
-
await fs.writeFile(PATHS.AZURE_OPENAI_CONFIG_PATH, encoded, "utf8");
|
|
272
|
-
await fs.chmod(PATHS.AZURE_OPENAI_CONFIG_PATH, 384);
|
|
273
|
-
consola.success("Azure OpenAI configuration saved");
|
|
274
|
-
}
|
|
275
|
-
async function promptAzureOpenAISetup() {
|
|
276
|
-
if (!await consola.prompt("Would you like to add a custom Azure OpenAI endpoint?", {
|
|
277
|
-
type: "confirm",
|
|
278
|
-
initial: false
|
|
279
|
-
})) return null;
|
|
280
|
-
const endpoint = await consola.prompt("Enter your Azure OpenAI endpoint URL (e.g., https://your-resource.openai.azure.com):", { type: "text" });
|
|
281
|
-
if (!endpoint || typeof endpoint !== "string" || !endpoint.trim()) {
|
|
282
|
-
consola.warn("No endpoint provided, skipping Azure OpenAI setup");
|
|
283
|
-
return null;
|
|
284
|
-
}
|
|
285
|
-
const apiKey = await consola.prompt("Enter your Azure OpenAI API key:", { type: "text" });
|
|
286
|
-
if (!apiKey || typeof apiKey !== "string" || !apiKey.trim()) {
|
|
287
|
-
consola.warn("No API key provided, skipping Azure OpenAI setup");
|
|
288
|
-
return null;
|
|
289
|
-
}
|
|
290
|
-
const config$1 = {
|
|
291
|
-
endpoint: endpoint.trim().replace(/\/$/, ""),
|
|
292
|
-
apiKey: apiKey.trim()
|
|
293
|
-
};
|
|
294
|
-
await saveAzureOpenAIConfig(config$1);
|
|
295
|
-
return config$1;
|
|
296
|
-
}
|
|
297
|
-
function isAzureOpenAIModel(modelId) {
|
|
298
|
-
return modelId.startsWith(AZURE_OPENAI_MODEL_PREFIX);
|
|
299
|
-
}
|
|
300
|
-
function getAzureDeploymentName(modelId) {
|
|
301
|
-
return modelId.slice(13);
|
|
302
|
-
}
|
|
303
|
-
|
|
304
256
|
//#endregion
|
|
305
257
|
//#region src/lib/retry-fetch.ts
|
|
306
258
|
const RETRY_DELAYS_MS = [
|
|
@@ -370,62 +322,6 @@ async function fetchWithRetry(input, init) {
|
|
|
370
322
|
throw lastError;
|
|
371
323
|
}
|
|
372
324
|
|
|
373
|
-
//#endregion
|
|
374
|
-
//#region src/services/azure-openai/create-chat-completions.ts
|
|
375
|
-
const AZURE_API_VERSION = "2024-10-21";
|
|
376
|
-
async function createAzureOpenAIChatCompletions(config$1, payload) {
|
|
377
|
-
const deploymentName = getAzureDeploymentName(payload.model);
|
|
378
|
-
const { max_tokens,...restPayload } = payload;
|
|
379
|
-
const azurePayload = {
|
|
380
|
-
...restPayload,
|
|
381
|
-
model: deploymentName,
|
|
382
|
-
...max_tokens != null && { max_completion_tokens: max_tokens }
|
|
383
|
-
};
|
|
384
|
-
const response = await fetchWithRetry(`${config$1.endpoint}/openai/deployments/${deploymentName}/chat/completions?api-version=${AZURE_API_VERSION}`, {
|
|
385
|
-
method: "POST",
|
|
386
|
-
headers: {
|
|
387
|
-
"api-key": config$1.apiKey,
|
|
388
|
-
"Content-Type": "application/json"
|
|
389
|
-
},
|
|
390
|
-
body: JSON.stringify(azurePayload)
|
|
391
|
-
});
|
|
392
|
-
if (!response.ok) {
|
|
393
|
-
consola.error("Failed to create Azure OpenAI chat completions:", response);
|
|
394
|
-
throw new HTTPError("Failed to create Azure OpenAI chat completions", response, payload);
|
|
395
|
-
}
|
|
396
|
-
if (payload.stream) return events(response);
|
|
397
|
-
return await response.json();
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
//#endregion
|
|
401
|
-
//#region src/services/azure-openai/get-models.ts
|
|
402
|
-
const AZURE_DEPLOYMENTS_API_VERSION = "2022-12-01";
|
|
403
|
-
async function getAzureOpenAIDeployments(config$1) {
|
|
404
|
-
try {
|
|
405
|
-
const response = await fetchWithRetry(`${config$1.endpoint}/openai/deployments?api-version=${AZURE_DEPLOYMENTS_API_VERSION}`, { headers: {
|
|
406
|
-
"api-key": config$1.apiKey,
|
|
407
|
-
"Content-Type": "application/json"
|
|
408
|
-
} });
|
|
409
|
-
if (!response.ok) {
|
|
410
|
-
const errorText = await response.text().catch(() => "");
|
|
411
|
-
consola.error(`Failed to fetch Azure OpenAI deployments: ${response.status}`, errorText);
|
|
412
|
-
throw new HTTPError("Failed to fetch Azure OpenAI deployments", response);
|
|
413
|
-
}
|
|
414
|
-
return (await response.json()).data.filter((deployment) => deployment.status === "succeeded").map((deployment) => ({
|
|
415
|
-
id: `${AZURE_OPENAI_MODEL_PREFIX}${deployment.id}`,
|
|
416
|
-
deploymentName: deployment.id,
|
|
417
|
-
model: deployment.model,
|
|
418
|
-
created: deployment.created_at,
|
|
419
|
-
object: "deployment",
|
|
420
|
-
owned_by: deployment.owner || "azure-openai"
|
|
421
|
-
}));
|
|
422
|
-
} catch (error) {
|
|
423
|
-
if (error instanceof HTTPError) throw error;
|
|
424
|
-
consola.error("Failed to fetch Azure OpenAI deployments:", error);
|
|
425
|
-
return [];
|
|
426
|
-
}
|
|
427
|
-
}
|
|
428
|
-
|
|
429
325
|
//#endregion
|
|
430
326
|
//#region src/services/copilot/get-models.ts
|
|
431
327
|
const getModels = async () => {
|
|
@@ -489,24 +385,6 @@ const cacheVSCodeVersion = async () => {
|
|
|
489
385
|
state.vsCodeVersion = response;
|
|
490
386
|
consola.info(`Using VSCode version: ${response}`);
|
|
491
387
|
};
|
|
492
|
-
async function setupAzureOpenAI() {
|
|
493
|
-
let config$1 = await loadAzureOpenAIConfig();
|
|
494
|
-
if (!config$1) config$1 = await promptAzureOpenAISetup();
|
|
495
|
-
if (!config$1) {
|
|
496
|
-
consola.info("Azure OpenAI not configured");
|
|
497
|
-
return;
|
|
498
|
-
}
|
|
499
|
-
state.azureOpenAIConfig = config$1;
|
|
500
|
-
consola.info("Azure OpenAI configuration loaded");
|
|
501
|
-
try {
|
|
502
|
-
const deployments = await getAzureOpenAIDeployments(config$1);
|
|
503
|
-
state.azureOpenAIDeployments = deployments;
|
|
504
|
-
if (deployments.length > 0) consola.info(`Loaded ${deployments.length} Azure OpenAI deployment(s):\n${deployments.map((d) => `- ${d.id} (${d.model})`).join("\n")}`);
|
|
505
|
-
else consola.warn("No Azure OpenAI deployments found");
|
|
506
|
-
} catch (error) {
|
|
507
|
-
consola.warn("Failed to fetch Azure OpenAI deployments:", error);
|
|
508
|
-
}
|
|
509
|
-
}
|
|
510
388
|
|
|
511
389
|
//#endregion
|
|
512
390
|
//#region src/services/github/poll-access-token.ts
|
|
@@ -1254,6 +1132,119 @@ const debug = defineCommand({
|
|
|
1254
1132
|
}
|
|
1255
1133
|
});
|
|
1256
1134
|
|
|
1135
|
+
//#endregion
|
|
1136
|
+
//#region src/lib/config.ts
|
|
1137
|
+
const gpt5ExplorationPrompt = `## Exploration and reading files
|
|
1138
|
+
- **Think first.** Before any tool call, decide ALL files/resources you will need.
|
|
1139
|
+
- **Batch everything.** If you need multiple files (even from different places), read them together.
|
|
1140
|
+
- **multi_tool_use.parallel** Use multi_tool_use.parallel to parallelize tool calls and only this.
|
|
1141
|
+
- **Only make sequential calls if you truly cannot know the next file without seeing a result first.**
|
|
1142
|
+
- **Workflow:** (a) plan all needed reads → (b) issue one parallel batch → (c) analyze results → (d) repeat if new, unpredictable reads arise.`;
|
|
1143
|
+
const gpt5CommentaryPrompt = `# Working with the user
|
|
1144
|
+
|
|
1145
|
+
You interact with the user through a terminal. You have 2 ways of communicating with the users:
|
|
1146
|
+
- Share intermediary updates in \`commentary\` channel.
|
|
1147
|
+
- After you have completed all your work, send a message to the \`final\` channel.
|
|
1148
|
+
|
|
1149
|
+
## Intermediary updates
|
|
1150
|
+
|
|
1151
|
+
- Intermediary updates go to the \`commentary\` channel.
|
|
1152
|
+
- User updates are short updates while you are working, they are NOT final answers.
|
|
1153
|
+
- You use 1-2 sentence user updates to communicate progress and new information to the user as you are doing work.
|
|
1154
|
+
- Do not begin responses with conversational interjections or meta commentary. Avoid openers such as acknowledgements ("Done —", "Got it", "Great question, ") or framing phrases.
|
|
1155
|
+
- You provide user updates frequently, every 20s.
|
|
1156
|
+
- 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.
|
|
1157
|
+
- 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.
|
|
1158
|
+
- 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).
|
|
1159
|
+
- Before performing file edits of any kind, you provide updates explaining what edits you are making.
|
|
1160
|
+
- 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.
|
|
1161
|
+
- Tone of your updates MUST match your personality.`;
|
|
1162
|
+
const defaultConfig = {
|
|
1163
|
+
auth: { apiKeys: [] },
|
|
1164
|
+
extraPrompts: {
|
|
1165
|
+
"gpt-5-mini": gpt5ExplorationPrompt,
|
|
1166
|
+
"gpt-5.1-codex-max": gpt5ExplorationPrompt,
|
|
1167
|
+
"gpt-5.3-codex": gpt5CommentaryPrompt
|
|
1168
|
+
},
|
|
1169
|
+
smallModel: "gpt-5-mini",
|
|
1170
|
+
modelReasoningEfforts: { "gpt-5-mini": "low" },
|
|
1171
|
+
useFunctionApplyPatch: true,
|
|
1172
|
+
compactUseSmallModel: true
|
|
1173
|
+
};
|
|
1174
|
+
let cachedConfig = null;
|
|
1175
|
+
function ensureConfigFile() {
|
|
1176
|
+
try {
|
|
1177
|
+
fs$1.accessSync(PATHS.CONFIG_PATH, fs$1.constants.R_OK | fs$1.constants.W_OK);
|
|
1178
|
+
} catch {
|
|
1179
|
+
fs$1.mkdirSync(PATHS.APP_DIR, { recursive: true });
|
|
1180
|
+
fs$1.writeFileSync(PATHS.CONFIG_PATH, `${JSON.stringify(defaultConfig, null, 2)}\n`, "utf8");
|
|
1181
|
+
try {
|
|
1182
|
+
fs$1.chmodSync(PATHS.CONFIG_PATH, 384);
|
|
1183
|
+
} catch {
|
|
1184
|
+
return;
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
1187
|
+
}
|
|
1188
|
+
function readConfigFromDisk() {
|
|
1189
|
+
ensureConfigFile();
|
|
1190
|
+
try {
|
|
1191
|
+
const raw = fs$1.readFileSync(PATHS.CONFIG_PATH, "utf8");
|
|
1192
|
+
if (!raw.trim()) {
|
|
1193
|
+
fs$1.writeFileSync(PATHS.CONFIG_PATH, `${JSON.stringify(defaultConfig, null, 2)}\n`, "utf8");
|
|
1194
|
+
return defaultConfig;
|
|
1195
|
+
}
|
|
1196
|
+
return JSON.parse(raw);
|
|
1197
|
+
} catch (error) {
|
|
1198
|
+
consola.error("Failed to read config file, using default config", error);
|
|
1199
|
+
return defaultConfig;
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1202
|
+
function mergeDefaultExtraPrompts(config$1) {
|
|
1203
|
+
const extraPrompts = config$1.extraPrompts ?? {};
|
|
1204
|
+
const defaultExtraPrompts = defaultConfig.extraPrompts ?? {};
|
|
1205
|
+
if (Object.keys(defaultExtraPrompts).filter((model) => !Object.hasOwn(extraPrompts, model)).length === 0) return {
|
|
1206
|
+
mergedConfig: config$1,
|
|
1207
|
+
changed: false
|
|
1208
|
+
};
|
|
1209
|
+
return {
|
|
1210
|
+
mergedConfig: {
|
|
1211
|
+
...config$1,
|
|
1212
|
+
extraPrompts: {
|
|
1213
|
+
...defaultExtraPrompts,
|
|
1214
|
+
...extraPrompts
|
|
1215
|
+
}
|
|
1216
|
+
},
|
|
1217
|
+
changed: true
|
|
1218
|
+
};
|
|
1219
|
+
}
|
|
1220
|
+
function mergeConfigWithDefaults() {
|
|
1221
|
+
const config$1 = readConfigFromDisk();
|
|
1222
|
+
const { mergedConfig, changed } = mergeDefaultExtraPrompts(config$1);
|
|
1223
|
+
if (changed) try {
|
|
1224
|
+
fs$1.writeFileSync(PATHS.CONFIG_PATH, `${JSON.stringify(mergedConfig, null, 2)}\n`, "utf8");
|
|
1225
|
+
} catch (writeError) {
|
|
1226
|
+
consola.warn("Failed to write merged extraPrompts to config file", writeError);
|
|
1227
|
+
}
|
|
1228
|
+
cachedConfig = mergedConfig;
|
|
1229
|
+
return mergedConfig;
|
|
1230
|
+
}
|
|
1231
|
+
function getConfig() {
|
|
1232
|
+
cachedConfig ??= readConfigFromDisk();
|
|
1233
|
+
return cachedConfig;
|
|
1234
|
+
}
|
|
1235
|
+
function getExtraPromptForModel(model) {
|
|
1236
|
+
return getConfig().extraPrompts?.[model] ?? "";
|
|
1237
|
+
}
|
|
1238
|
+
function getSmallModel() {
|
|
1239
|
+
return getConfig().smallModel ?? "gpt-5-mini";
|
|
1240
|
+
}
|
|
1241
|
+
function getReasoningEffortForModel(model) {
|
|
1242
|
+
return getConfig().modelReasoningEfforts?.[model] ?? "high";
|
|
1243
|
+
}
|
|
1244
|
+
function shouldCompactUseSmallModel() {
|
|
1245
|
+
return getConfig().compactUseSmallModel ?? true;
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1257
1248
|
//#endregion
|
|
1258
1249
|
//#region src/lib/proxy.ts
|
|
1259
1250
|
function initProxyFromEnv() {
|
|
@@ -1353,6 +1344,52 @@ function generateEnvScript(envVars, commandToRun = "") {
|
|
|
1353
1344
|
return commandBlock || commandToRun;
|
|
1354
1345
|
}
|
|
1355
1346
|
|
|
1347
|
+
//#endregion
|
|
1348
|
+
//#region src/lib/request-auth.ts
|
|
1349
|
+
function normalizeApiKeys(apiKeys) {
|
|
1350
|
+
if (!Array.isArray(apiKeys)) {
|
|
1351
|
+
if (apiKeys !== void 0) consola.warn("Invalid auth.apiKeys config. Expected an array of strings.");
|
|
1352
|
+
return [];
|
|
1353
|
+
}
|
|
1354
|
+
const normalizedKeys = apiKeys.filter((key) => typeof key === "string").map((key) => key.trim()).filter((key) => key.length > 0);
|
|
1355
|
+
if (normalizedKeys.length !== apiKeys.length) consola.warn("Invalid auth.apiKeys entries found. Only non-empty strings are allowed.");
|
|
1356
|
+
return [...new Set(normalizedKeys)];
|
|
1357
|
+
}
|
|
1358
|
+
function getConfiguredApiKeys() {
|
|
1359
|
+
const config$1 = getConfig();
|
|
1360
|
+
return normalizeApiKeys(config$1.auth?.apiKeys);
|
|
1361
|
+
}
|
|
1362
|
+
function extractRequestApiKey(c) {
|
|
1363
|
+
const xApiKey = c.req.header("x-api-key")?.trim();
|
|
1364
|
+
if (xApiKey) return xApiKey;
|
|
1365
|
+
const authorization = c.req.header("authorization");
|
|
1366
|
+
if (!authorization) return null;
|
|
1367
|
+
const [scheme, ...rest] = authorization.trim().split(/\s+/);
|
|
1368
|
+
if (scheme.toLowerCase() !== "bearer") return null;
|
|
1369
|
+
return rest.join(" ").trim() || null;
|
|
1370
|
+
}
|
|
1371
|
+
function createUnauthorizedResponse(c) {
|
|
1372
|
+
c.header("WWW-Authenticate", "Bearer realm=\"copilot-api\"");
|
|
1373
|
+
return c.json({ error: {
|
|
1374
|
+
message: "Unauthorized",
|
|
1375
|
+
type: "authentication_error"
|
|
1376
|
+
} }, 401);
|
|
1377
|
+
}
|
|
1378
|
+
function createAuthMiddleware(options = {}) {
|
|
1379
|
+
const getApiKeys = options.getApiKeys ?? getConfiguredApiKeys;
|
|
1380
|
+
const allowUnauthenticatedPaths = options.allowUnauthenticatedPaths ?? ["/"];
|
|
1381
|
+
const allowOptionsBypass = options.allowOptionsBypass ?? true;
|
|
1382
|
+
return async (c, next) => {
|
|
1383
|
+
if (allowOptionsBypass && c.req.method === "OPTIONS") return next();
|
|
1384
|
+
if (allowUnauthenticatedPaths.includes(c.req.path)) return next();
|
|
1385
|
+
const apiKeys = getApiKeys();
|
|
1386
|
+
if (apiKeys.length === 0) return next();
|
|
1387
|
+
const requestApiKey = extractRequestApiKey(c);
|
|
1388
|
+
if (!requestApiKey || !apiKeys.includes(requestApiKey)) return createUnauthorizedResponse(c);
|
|
1389
|
+
return next();
|
|
1390
|
+
};
|
|
1391
|
+
}
|
|
1392
|
+
|
|
1356
1393
|
//#endregion
|
|
1357
1394
|
//#region src/lib/request-logger.ts
|
|
1358
1395
|
const REQUEST_CONTEXT_KEY = "requestContext";
|
|
@@ -1455,7 +1492,7 @@ async function requestLogger(c, next) {
|
|
|
1455
1492
|
const durationStr = `${colors.cyan}${duration}s${colors.reset}`;
|
|
1456
1493
|
lines.push(`${colors.bold}${method}${colors.reset} ${path$1} ${statusBadge} ${durationStr}`);
|
|
1457
1494
|
if (ctx?.provider && ctx.model) {
|
|
1458
|
-
const providerColor =
|
|
1495
|
+
const providerColor = colors.magenta;
|
|
1459
1496
|
lines.push(` ${colors.gray}Provider:${colors.reset} ${providerColor}${ctx.provider}${colors.reset} ${colors.gray}->${colors.reset} ${colors.white}${ctx.model}${colors.reset}`);
|
|
1460
1497
|
}
|
|
1461
1498
|
if (ctx?.inputTokens !== void 0 || ctx?.outputTokens !== void 0) {
|
|
@@ -1467,14 +1504,6 @@ async function requestLogger(c, next) {
|
|
|
1467
1504
|
lines.push(` ${colors.dim}${getTimeString()}${colors.reset}`);
|
|
1468
1505
|
console.log(lines.join("\n"));
|
|
1469
1506
|
}
|
|
1470
|
-
/**
|
|
1471
|
-
* Log token usage (for streaming responses where tokens are known after stream completes)
|
|
1472
|
-
*/
|
|
1473
|
-
function logTokenUsage(inputTokens, outputTokens) {
|
|
1474
|
-
const parts = [];
|
|
1475
|
-
parts.push(` ${colors.gray}Tokens:${colors.reset} ${colors.yellow}${inputTokens.toLocaleString()} in${colors.reset} ${colors.gray}/${colors.reset} ${colors.green}${outputTokens.toLocaleString()} out${colors.reset}`);
|
|
1476
|
-
console.log(parts.join(""));
|
|
1477
|
-
}
|
|
1478
1507
|
|
|
1479
1508
|
//#endregion
|
|
1480
1509
|
//#region src/lib/approval.ts
|
|
@@ -1719,13 +1748,17 @@ const getTokenCount = async (payload, model) => {
|
|
|
1719
1748
|
|
|
1720
1749
|
//#endregion
|
|
1721
1750
|
//#region src/services/copilot/create-chat-completions.ts
|
|
1722
|
-
const createChatCompletions = async (payload) => {
|
|
1751
|
+
const createChatCompletions = async (payload, options) => {
|
|
1723
1752
|
if (!state.copilotToken) throw new Error("Copilot token not found");
|
|
1724
1753
|
const enableVision = payload.messages.some((x) => typeof x.content !== "string" && x.content?.some((x$1) => x$1.type === "image_url"));
|
|
1725
|
-
|
|
1754
|
+
let isAgentCall = false;
|
|
1755
|
+
if (payload.messages.length > 0) {
|
|
1756
|
+
const lastMessage = payload.messages.at(-1);
|
|
1757
|
+
if (lastMessage) isAgentCall = ["assistant", "tool"].includes(lastMessage.role);
|
|
1758
|
+
}
|
|
1726
1759
|
const headers = {
|
|
1727
1760
|
...copilotHeaders(state, enableVision),
|
|
1728
|
-
"X-Initiator": isAgentCall ? "agent" : "user"
|
|
1761
|
+
"X-Initiator": options?.initiator ?? (isAgentCall ? "agent" : "user")
|
|
1729
1762
|
};
|
|
1730
1763
|
const response = await fetchWithRetry(`${copilotBaseUrl(state)}/chat/completions`, {
|
|
1731
1764
|
method: "POST",
|
|
@@ -1751,37 +1784,6 @@ async function handleCompletion$1(c) {
|
|
|
1751
1784
|
model: normalizeModelName(payload.model)
|
|
1752
1785
|
};
|
|
1753
1786
|
consola.debug("Request payload:", JSON.stringify(payload).slice(-400));
|
|
1754
|
-
if (isAzureOpenAIModel(payload.model)) {
|
|
1755
|
-
if (!state.azureOpenAIConfig) return c.json({ error: "Azure OpenAI not configured" }, 500);
|
|
1756
|
-
setRequestContext(c, {
|
|
1757
|
-
provider: "Azure OpenAI",
|
|
1758
|
-
model: payload.model
|
|
1759
|
-
});
|
|
1760
|
-
if (state.manualApprove) await awaitApproval();
|
|
1761
|
-
const response$1 = await createAzureOpenAIChatCompletions(state.azureOpenAIConfig, payload);
|
|
1762
|
-
if (isNonStreaming(response$1)) {
|
|
1763
|
-
consola.debug("Non-streaming response:", JSON.stringify(response$1));
|
|
1764
|
-
if (response$1.usage) setRequestContext(c, {
|
|
1765
|
-
inputTokens: response$1.usage.prompt_tokens,
|
|
1766
|
-
outputTokens: response$1.usage.completion_tokens
|
|
1767
|
-
});
|
|
1768
|
-
return c.json(response$1);
|
|
1769
|
-
}
|
|
1770
|
-
consola.debug("Streaming response");
|
|
1771
|
-
return streamSSE(c, async (stream) => {
|
|
1772
|
-
for await (const chunk of response$1) {
|
|
1773
|
-
consola.debug("Streaming chunk:", JSON.stringify(chunk));
|
|
1774
|
-
if (chunk.data && chunk.data !== "[DONE]") {
|
|
1775
|
-
const parsed = JSON.parse(chunk.data);
|
|
1776
|
-
if (parsed.usage) setRequestContext(c, {
|
|
1777
|
-
inputTokens: parsed.usage.prompt_tokens,
|
|
1778
|
-
outputTokens: parsed.usage.completion_tokens
|
|
1779
|
-
});
|
|
1780
|
-
}
|
|
1781
|
-
await stream.writeSSE(chunk);
|
|
1782
|
-
}
|
|
1783
|
-
});
|
|
1784
|
-
}
|
|
1785
1787
|
setRequestContext(c, {
|
|
1786
1788
|
provider: "Copilot",
|
|
1787
1789
|
model: payload.model
|
|
@@ -1804,7 +1806,7 @@ async function handleCompletion$1(c) {
|
|
|
1804
1806
|
consola.debug("Set max_tokens to:", JSON.stringify(payload.max_tokens));
|
|
1805
1807
|
}
|
|
1806
1808
|
const response = await createChatCompletions(payload);
|
|
1807
|
-
if (isNonStreaming(response)) {
|
|
1809
|
+
if (isNonStreaming$1(response)) {
|
|
1808
1810
|
consola.debug("Non-streaming response:", JSON.stringify(response));
|
|
1809
1811
|
if (response.usage) setRequestContext(c, {
|
|
1810
1812
|
inputTokens: response.usage.prompt_tokens,
|
|
@@ -1827,7 +1829,7 @@ async function handleCompletion$1(c) {
|
|
|
1827
1829
|
}
|
|
1828
1830
|
});
|
|
1829
1831
|
}
|
|
1830
|
-
const isNonStreaming = (response) => Object.hasOwn(response, "choices");
|
|
1832
|
+
const isNonStreaming$1 = (response) => Object.hasOwn(response, "choices");
|
|
1831
1833
|
|
|
1832
1834
|
//#endregion
|
|
1833
1835
|
//#region src/routes/chat-completions/route.ts
|
|
@@ -2111,126 +2113,1088 @@ async function handleCountTokens(c) {
|
|
|
2111
2113
|
}
|
|
2112
2114
|
|
|
2113
2115
|
//#endregion
|
|
2114
|
-
//#region src/
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2116
|
+
//#region src/lib/logger.ts
|
|
2117
|
+
const LOG_RETENTION_MS = 10080 * 60 * 1e3;
|
|
2118
|
+
const CLEANUP_INTERVAL_MS = 1440 * 60 * 1e3;
|
|
2119
|
+
const LOG_DIR = path.join(PATHS.APP_DIR, "logs");
|
|
2120
|
+
const FLUSH_INTERVAL_MS = 1e3;
|
|
2121
|
+
const MAX_BUFFER_SIZE = 100;
|
|
2122
|
+
const logStreams = /* @__PURE__ */ new Map();
|
|
2123
|
+
const logBuffers = /* @__PURE__ */ new Map();
|
|
2124
|
+
const ensureLogDirectory = () => {
|
|
2125
|
+
if (!fs$1.existsSync(LOG_DIR)) fs$1.mkdirSync(LOG_DIR, { recursive: true });
|
|
2126
|
+
};
|
|
2127
|
+
const cleanupOldLogs = () => {
|
|
2128
|
+
if (!fs$1.existsSync(LOG_DIR)) return;
|
|
2129
|
+
const now = Date.now();
|
|
2130
|
+
for (const entry of fs$1.readdirSync(LOG_DIR)) {
|
|
2131
|
+
const filePath = path.join(LOG_DIR, entry);
|
|
2132
|
+
let stats;
|
|
2133
|
+
try {
|
|
2134
|
+
stats = fs$1.statSync(filePath);
|
|
2135
|
+
} catch {
|
|
2136
|
+
continue;
|
|
2133
2137
|
}
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
const usage = state$1.pendingUsage ?? {
|
|
2140
|
-
prompt_tokens: 0,
|
|
2141
|
-
completion_tokens: 0,
|
|
2142
|
-
cached_tokens: 0
|
|
2143
|
-
};
|
|
2144
|
-
return createMessageDeltaEvents(state$1.pendingFinishReason, usage);
|
|
2145
|
-
}
|
|
2146
|
-
return [];
|
|
2147
|
-
}
|
|
2148
|
-
function translateChunkToAnthropicEvents(chunk, state$1, originalModel) {
|
|
2149
|
-
const events$1 = [];
|
|
2150
|
-
if (chunk.usage) {
|
|
2151
|
-
state$1.pendingUsage = {
|
|
2152
|
-
prompt_tokens: chunk.usage.prompt_tokens,
|
|
2153
|
-
completion_tokens: chunk.usage.completion_tokens,
|
|
2154
|
-
cached_tokens: chunk.usage.prompt_tokens_details?.cached_tokens ?? 0
|
|
2155
|
-
};
|
|
2156
|
-
if (state$1.pendingFinishReason && !state$1.messageDeltaSent) {
|
|
2157
|
-
events$1.push(...createMessageDeltaEvents(state$1.pendingFinishReason, state$1.pendingUsage));
|
|
2158
|
-
state$1.messageDeltaSent = true;
|
|
2138
|
+
if (!stats.isFile()) continue;
|
|
2139
|
+
if (now - stats.mtimeMs > LOG_RETENTION_MS) try {
|
|
2140
|
+
fs$1.rmSync(filePath);
|
|
2141
|
+
} catch {
|
|
2142
|
+
continue;
|
|
2159
2143
|
}
|
|
2160
2144
|
}
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
model: originalModel ?? chunk.model,
|
|
2179
|
-
stop_reason: null,
|
|
2180
|
-
stop_sequence: null,
|
|
2181
|
-
usage: {
|
|
2182
|
-
input_tokens: inputTokens,
|
|
2183
|
-
output_tokens: 0,
|
|
2184
|
-
cache_creation_input_tokens: 0,
|
|
2185
|
-
cache_read_input_tokens: usage.cached_tokens
|
|
2186
|
-
}
|
|
2187
|
-
}
|
|
2145
|
+
};
|
|
2146
|
+
const formatArgs = (args) => args.map((arg) => typeof arg === "string" ? arg : util.inspect(arg, {
|
|
2147
|
+
depth: null,
|
|
2148
|
+
colors: false
|
|
2149
|
+
})).join(" ");
|
|
2150
|
+
const sanitizeName = (name$1) => {
|
|
2151
|
+
const normalized = name$1.toLowerCase().replaceAll(/[^a-z0-9]+/g, "-").replaceAll(/^-+|-+$/g, "");
|
|
2152
|
+
return normalized === "" ? "handler" : normalized;
|
|
2153
|
+
};
|
|
2154
|
+
const getLogStream = (filePath) => {
|
|
2155
|
+
let stream = logStreams.get(filePath);
|
|
2156
|
+
if (!stream || stream.destroyed) {
|
|
2157
|
+
stream = fs$1.createWriteStream(filePath, { flags: "a" });
|
|
2158
|
+
logStreams.set(filePath, stream);
|
|
2159
|
+
stream.on("error", (error) => {
|
|
2160
|
+
console.warn("Log stream error", error);
|
|
2161
|
+
logStreams.delete(filePath);
|
|
2188
2162
|
});
|
|
2189
|
-
state$1.messageStartSent = true;
|
|
2190
2163
|
}
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2164
|
+
return stream;
|
|
2165
|
+
};
|
|
2166
|
+
const flushBuffer = (filePath) => {
|
|
2167
|
+
const buffer = logBuffers.get(filePath);
|
|
2168
|
+
if (!buffer || buffer.length === 0) return;
|
|
2169
|
+
const stream = getLogStream(filePath);
|
|
2170
|
+
const content = buffer.join("\n") + "\n";
|
|
2171
|
+
stream.write(content, (error) => {
|
|
2172
|
+
if (error) console.warn("Failed to write handler log", error);
|
|
2173
|
+
});
|
|
2174
|
+
logBuffers.set(filePath, []);
|
|
2175
|
+
};
|
|
2176
|
+
const flushAllBuffers = () => {
|
|
2177
|
+
for (const filePath of logBuffers.keys()) flushBuffer(filePath);
|
|
2178
|
+
};
|
|
2179
|
+
const appendLine = (filePath, line) => {
|
|
2180
|
+
let buffer = logBuffers.get(filePath);
|
|
2181
|
+
if (!buffer) {
|
|
2182
|
+
buffer = [];
|
|
2183
|
+
logBuffers.set(filePath, buffer);
|
|
2184
|
+
}
|
|
2185
|
+
buffer.push(line);
|
|
2186
|
+
if (buffer.length >= MAX_BUFFER_SIZE) flushBuffer(filePath);
|
|
2187
|
+
};
|
|
2188
|
+
setInterval(flushAllBuffers, FLUSH_INTERVAL_MS);
|
|
2189
|
+
const cleanup = () => {
|
|
2190
|
+
flushAllBuffers();
|
|
2191
|
+
for (const stream of logStreams.values()) stream.end();
|
|
2192
|
+
logStreams.clear();
|
|
2193
|
+
logBuffers.clear();
|
|
2194
|
+
};
|
|
2195
|
+
process.on("exit", cleanup);
|
|
2196
|
+
process.on("SIGINT", () => {
|
|
2197
|
+
cleanup();
|
|
2198
|
+
process.exit(0);
|
|
2199
|
+
});
|
|
2200
|
+
process.on("SIGTERM", () => {
|
|
2201
|
+
cleanup();
|
|
2202
|
+
process.exit(0);
|
|
2203
|
+
});
|
|
2204
|
+
let lastCleanup = 0;
|
|
2205
|
+
const createHandlerLogger = (name$1) => {
|
|
2206
|
+
ensureLogDirectory();
|
|
2207
|
+
const sanitizedName = sanitizeName(name$1);
|
|
2208
|
+
const instance = consola.withTag(name$1);
|
|
2209
|
+
if (state.verbose) instance.level = 5;
|
|
2210
|
+
instance.setReporters([]);
|
|
2211
|
+
instance.addReporter({ log(logObj) {
|
|
2212
|
+
ensureLogDirectory();
|
|
2213
|
+
if (Date.now() - lastCleanup > CLEANUP_INTERVAL_MS) {
|
|
2214
|
+
cleanupOldLogs();
|
|
2215
|
+
lastCleanup = Date.now();
|
|
2216
|
+
}
|
|
2217
|
+
const date = logObj.date;
|
|
2218
|
+
const dateKey = date.toLocaleDateString("sv-SE");
|
|
2219
|
+
const timestamp = date.toLocaleString("sv-SE", { hour12: false });
|
|
2220
|
+
const filePath = path.join(LOG_DIR, `${sanitizedName}-${dateKey}.log`);
|
|
2221
|
+
const message = formatArgs(logObj.args);
|
|
2222
|
+
const line = `[${timestamp}] [${logObj.type}] [${logObj.tag || name$1}]${message ? ` ${message}` : ""}`;
|
|
2223
|
+
appendLine(filePath, line);
|
|
2224
|
+
} });
|
|
2225
|
+
return instance;
|
|
2226
|
+
};
|
|
2227
|
+
|
|
2228
|
+
//#endregion
|
|
2229
|
+
//#region src/services/copilot/create-responses.ts
|
|
2230
|
+
const createResponses = async (payload, { vision, initiator }) => {
|
|
2231
|
+
if (!state.copilotToken) throw new Error("Copilot token not found");
|
|
2232
|
+
const headers = {
|
|
2233
|
+
...copilotHeaders(state, vision),
|
|
2234
|
+
"X-Initiator": initiator
|
|
2235
|
+
};
|
|
2236
|
+
payload.service_tier = null;
|
|
2237
|
+
const response = await fetch(`${copilotBaseUrl(state)}/responses`, {
|
|
2238
|
+
method: "POST",
|
|
2239
|
+
headers,
|
|
2240
|
+
body: JSON.stringify(payload)
|
|
2241
|
+
});
|
|
2242
|
+
if (!response.ok) {
|
|
2243
|
+
consola.error("Failed to create responses", response);
|
|
2244
|
+
throw new HTTPError("Failed to create responses", response);
|
|
2245
|
+
}
|
|
2246
|
+
if (payload.stream) return events(response);
|
|
2247
|
+
return await response.json();
|
|
2248
|
+
};
|
|
2249
|
+
|
|
2250
|
+
//#endregion
|
|
2251
|
+
//#region src/routes/messages/responses-translation.ts
|
|
2252
|
+
const MESSAGE_TYPE = "message";
|
|
2253
|
+
const CODEX_PHASE_MODEL = "gpt-5.3-codex";
|
|
2254
|
+
const THINKING_TEXT = "Thinking...";
|
|
2255
|
+
const translateAnthropicMessagesToResponsesPayload = (payload) => {
|
|
2256
|
+
const input = [];
|
|
2257
|
+
for (const message of payload.messages) input.push(...translateMessage(message, payload.model));
|
|
2258
|
+
const translatedTools = convertAnthropicTools(payload.tools);
|
|
2259
|
+
const toolChoice = convertAnthropicToolChoice(payload.tool_choice);
|
|
2260
|
+
const { safetyIdentifier, promptCacheKey } = parseUserId(payload.metadata?.user_id);
|
|
2261
|
+
return {
|
|
2262
|
+
model: payload.model,
|
|
2263
|
+
input,
|
|
2264
|
+
instructions: translateSystemPrompt(payload.system, payload.model),
|
|
2265
|
+
temperature: 1,
|
|
2266
|
+
top_p: payload.top_p ?? null,
|
|
2267
|
+
max_output_tokens: Math.max(payload.max_tokens, 12800),
|
|
2268
|
+
tools: translatedTools,
|
|
2269
|
+
tool_choice: toolChoice,
|
|
2270
|
+
metadata: payload.metadata ? { ...payload.metadata } : null,
|
|
2271
|
+
safety_identifier: safetyIdentifier,
|
|
2272
|
+
prompt_cache_key: promptCacheKey,
|
|
2273
|
+
stream: payload.stream ?? null,
|
|
2274
|
+
store: false,
|
|
2275
|
+
parallel_tool_calls: true,
|
|
2276
|
+
reasoning: {
|
|
2277
|
+
effort: getReasoningEffortForModel(payload.model),
|
|
2278
|
+
summary: "detailed"
|
|
2279
|
+
},
|
|
2280
|
+
include: ["reasoning.encrypted_content"]
|
|
2281
|
+
};
|
|
2282
|
+
};
|
|
2283
|
+
const translateMessage = (message, model) => {
|
|
2284
|
+
if (message.role === "user") return translateUserMessage(message);
|
|
2285
|
+
return translateAssistantMessage(message, model);
|
|
2286
|
+
};
|
|
2287
|
+
const translateUserMessage = (message) => {
|
|
2288
|
+
if (typeof message.content === "string") return [createMessage("user", message.content)];
|
|
2289
|
+
if (!Array.isArray(message.content)) return [];
|
|
2290
|
+
const items = [];
|
|
2291
|
+
const pendingContent = [];
|
|
2292
|
+
for (const block of message.content) {
|
|
2293
|
+
if (block.type === "tool_result") {
|
|
2294
|
+
flushPendingContent(pendingContent, items, { role: "user" });
|
|
2295
|
+
items.push(createFunctionCallOutput(block));
|
|
2296
|
+
continue;
|
|
2297
|
+
}
|
|
2298
|
+
const converted = translateUserContentBlock(block);
|
|
2299
|
+
if (converted) pendingContent.push(converted);
|
|
2300
|
+
}
|
|
2301
|
+
flushPendingContent(pendingContent, items, { role: "user" });
|
|
2302
|
+
return items;
|
|
2303
|
+
};
|
|
2304
|
+
const translateAssistantMessage = (message, model) => {
|
|
2305
|
+
const assistantPhase = resolveAssistantPhase(model, message.content);
|
|
2306
|
+
if (typeof message.content === "string") return [createMessage("assistant", message.content, assistantPhase)];
|
|
2307
|
+
if (!Array.isArray(message.content)) return [];
|
|
2308
|
+
const items = [];
|
|
2309
|
+
const pendingContent = [];
|
|
2310
|
+
for (const block of message.content) {
|
|
2311
|
+
if (block.type === "tool_use") {
|
|
2312
|
+
flushPendingContent(pendingContent, items, {
|
|
2313
|
+
role: "assistant",
|
|
2314
|
+
phase: assistantPhase
|
|
2196
2315
|
});
|
|
2197
|
-
|
|
2198
|
-
|
|
2316
|
+
items.push(createFunctionToolCall(block));
|
|
2317
|
+
continue;
|
|
2199
2318
|
}
|
|
2200
|
-
if (
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
content_block: {
|
|
2205
|
-
type: "text",
|
|
2206
|
-
text: ""
|
|
2207
|
-
}
|
|
2319
|
+
if (block.type === "thinking" && block.signature && block.signature.includes("@")) {
|
|
2320
|
+
flushPendingContent(pendingContent, items, {
|
|
2321
|
+
role: "assistant",
|
|
2322
|
+
phase: assistantPhase
|
|
2208
2323
|
});
|
|
2209
|
-
|
|
2324
|
+
items.push(createReasoningContent(block));
|
|
2325
|
+
continue;
|
|
2210
2326
|
}
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
index: state$1.contentBlockIndex,
|
|
2214
|
-
delta: {
|
|
2215
|
-
type: "text_delta",
|
|
2216
|
-
text: delta.content
|
|
2217
|
-
}
|
|
2218
|
-
});
|
|
2327
|
+
const converted = translateAssistantContentBlock(block);
|
|
2328
|
+
if (converted) pendingContent.push(converted);
|
|
2219
2329
|
}
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2330
|
+
flushPendingContent(pendingContent, items, {
|
|
2331
|
+
role: "assistant",
|
|
2332
|
+
phase: assistantPhase
|
|
2333
|
+
});
|
|
2334
|
+
return items;
|
|
2335
|
+
};
|
|
2336
|
+
const translateUserContentBlock = (block) => {
|
|
2337
|
+
switch (block.type) {
|
|
2338
|
+
case "text": return createTextContent(block.text);
|
|
2339
|
+
case "image": return createImageContent(block);
|
|
2340
|
+
default: return;
|
|
2341
|
+
}
|
|
2342
|
+
};
|
|
2343
|
+
const translateAssistantContentBlock = (block) => {
|
|
2344
|
+
switch (block.type) {
|
|
2345
|
+
case "text": return createOutPutTextContent(block.text);
|
|
2346
|
+
default: return;
|
|
2347
|
+
}
|
|
2348
|
+
};
|
|
2349
|
+
const flushPendingContent = (pendingContent, target, message) => {
|
|
2350
|
+
if (pendingContent.length === 0) return;
|
|
2351
|
+
const messageContent = [...pendingContent];
|
|
2352
|
+
target.push(createMessage(message.role, messageContent, message.phase));
|
|
2353
|
+
pendingContent.length = 0;
|
|
2354
|
+
};
|
|
2355
|
+
const createMessage = (role, content, phase) => ({
|
|
2356
|
+
type: MESSAGE_TYPE,
|
|
2357
|
+
role,
|
|
2358
|
+
content,
|
|
2359
|
+
...role === "assistant" && phase ? { phase } : {}
|
|
2360
|
+
});
|
|
2361
|
+
const resolveAssistantPhase = (model, content) => {
|
|
2362
|
+
if (!shouldApplyCodexPhase(model)) return;
|
|
2363
|
+
if (typeof content === "string") return "final_answer";
|
|
2364
|
+
if (!Array.isArray(content)) return;
|
|
2365
|
+
if (!content.some((block) => block.type === "text")) return;
|
|
2366
|
+
return content.some((block) => block.type === "tool_use") ? "commentary" : "final_answer";
|
|
2367
|
+
};
|
|
2368
|
+
const shouldApplyCodexPhase = (model) => model === CODEX_PHASE_MODEL;
|
|
2369
|
+
const createTextContent = (text) => ({
|
|
2370
|
+
type: "input_text",
|
|
2371
|
+
text
|
|
2372
|
+
});
|
|
2373
|
+
const createOutPutTextContent = (text) => ({
|
|
2374
|
+
type: "output_text",
|
|
2375
|
+
text
|
|
2376
|
+
});
|
|
2377
|
+
const createImageContent = (block) => ({
|
|
2378
|
+
type: "input_image",
|
|
2379
|
+
image_url: `data:${block.source.media_type};base64,${block.source.data}`,
|
|
2380
|
+
detail: "auto"
|
|
2381
|
+
});
|
|
2382
|
+
const createReasoningContent = (block) => {
|
|
2383
|
+
const array = (block.signature ?? "").split("@");
|
|
2384
|
+
const signature = array[0];
|
|
2385
|
+
const id = array[1];
|
|
2386
|
+
const thinking = block.thinking === THINKING_TEXT ? "" : block.thinking;
|
|
2387
|
+
return {
|
|
2388
|
+
id,
|
|
2389
|
+
type: "reasoning",
|
|
2390
|
+
summary: thinking ? [{
|
|
2391
|
+
type: "summary_text",
|
|
2392
|
+
text: thinking
|
|
2393
|
+
}] : [],
|
|
2394
|
+
encrypted_content: signature
|
|
2395
|
+
};
|
|
2396
|
+
};
|
|
2397
|
+
const createFunctionToolCall = (block) => ({
|
|
2398
|
+
type: "function_call",
|
|
2399
|
+
call_id: block.id,
|
|
2400
|
+
name: block.name,
|
|
2401
|
+
arguments: JSON.stringify(block.input),
|
|
2402
|
+
status: "completed"
|
|
2403
|
+
});
|
|
2404
|
+
const createFunctionCallOutput = (block) => ({
|
|
2405
|
+
type: "function_call_output",
|
|
2406
|
+
call_id: block.tool_use_id,
|
|
2407
|
+
output: convertToolResultContent(block.content),
|
|
2408
|
+
status: block.is_error ? "incomplete" : "completed"
|
|
2409
|
+
});
|
|
2410
|
+
const translateSystemPrompt = (system, model) => {
|
|
2411
|
+
if (!system) return null;
|
|
2412
|
+
const extraPrompt = getExtraPromptForModel(model);
|
|
2413
|
+
if (typeof system === "string") return system + extraPrompt;
|
|
2414
|
+
const text = system.map((block, index) => {
|
|
2415
|
+
if (index === 0) return block.text + extraPrompt;
|
|
2416
|
+
return block.text;
|
|
2417
|
+
}).join(" ");
|
|
2418
|
+
return text.length > 0 ? text : null;
|
|
2419
|
+
};
|
|
2420
|
+
const convertAnthropicTools = (tools) => {
|
|
2421
|
+
if (!tools || tools.length === 0) return null;
|
|
2422
|
+
return tools.map((tool) => ({
|
|
2423
|
+
type: "function",
|
|
2424
|
+
name: tool.name,
|
|
2425
|
+
parameters: tool.input_schema,
|
|
2426
|
+
strict: false,
|
|
2427
|
+
...tool.description ? { description: tool.description } : {}
|
|
2428
|
+
}));
|
|
2429
|
+
};
|
|
2430
|
+
const convertAnthropicToolChoice = (choice) => {
|
|
2431
|
+
if (!choice) return "auto";
|
|
2432
|
+
switch (choice.type) {
|
|
2433
|
+
case "auto": return "auto";
|
|
2434
|
+
case "any": return "required";
|
|
2435
|
+
case "tool": return choice.name ? {
|
|
2436
|
+
type: "function",
|
|
2437
|
+
name: choice.name
|
|
2438
|
+
} : "auto";
|
|
2439
|
+
case "none": return "none";
|
|
2440
|
+
default: return "auto";
|
|
2441
|
+
}
|
|
2442
|
+
};
|
|
2443
|
+
const translateResponsesResultToAnthropic = (response) => {
|
|
2444
|
+
const contentBlocks = mapOutputToAnthropicContent(response.output);
|
|
2445
|
+
const usage = mapResponsesUsage(response);
|
|
2446
|
+
let anthropicContent = fallbackContentBlocks(response.output_text);
|
|
2447
|
+
if (contentBlocks.length > 0) anthropicContent = contentBlocks;
|
|
2448
|
+
const stopReason = mapResponsesStopReason(response);
|
|
2449
|
+
return {
|
|
2450
|
+
id: response.id,
|
|
2451
|
+
type: "message",
|
|
2452
|
+
role: "assistant",
|
|
2453
|
+
content: anthropicContent,
|
|
2454
|
+
model: response.model,
|
|
2455
|
+
stop_reason: stopReason,
|
|
2456
|
+
stop_sequence: null,
|
|
2457
|
+
usage
|
|
2458
|
+
};
|
|
2459
|
+
};
|
|
2460
|
+
const mapOutputToAnthropicContent = (output) => {
|
|
2461
|
+
const contentBlocks = [];
|
|
2462
|
+
for (const item of output) switch (item.type) {
|
|
2463
|
+
case "reasoning": {
|
|
2464
|
+
const thinkingText = extractReasoningText(item);
|
|
2465
|
+
if (thinkingText.length > 0) contentBlocks.push({
|
|
2466
|
+
type: "thinking",
|
|
2467
|
+
thinking: thinkingText,
|
|
2468
|
+
signature: (item.encrypted_content ?? "") + "@" + item.id
|
|
2469
|
+
});
|
|
2470
|
+
break;
|
|
2471
|
+
}
|
|
2472
|
+
case "function_call": {
|
|
2473
|
+
const toolUseBlock = createToolUseContentBlock(item);
|
|
2474
|
+
if (toolUseBlock) contentBlocks.push(toolUseBlock);
|
|
2475
|
+
break;
|
|
2476
|
+
}
|
|
2477
|
+
case "message": {
|
|
2478
|
+
const combinedText = combineMessageTextContent(item.content);
|
|
2479
|
+
if (combinedText.length > 0) contentBlocks.push({
|
|
2480
|
+
type: "text",
|
|
2481
|
+
text: combinedText
|
|
2482
|
+
});
|
|
2483
|
+
break;
|
|
2484
|
+
}
|
|
2485
|
+
default: {
|
|
2486
|
+
const combinedText = combineMessageTextContent(item.content);
|
|
2487
|
+
if (combinedText.length > 0) contentBlocks.push({
|
|
2488
|
+
type: "text",
|
|
2489
|
+
text: combinedText
|
|
2490
|
+
});
|
|
2491
|
+
}
|
|
2492
|
+
}
|
|
2493
|
+
return contentBlocks;
|
|
2494
|
+
};
|
|
2495
|
+
const combineMessageTextContent = (content) => {
|
|
2496
|
+
if (!Array.isArray(content)) return "";
|
|
2497
|
+
let aggregated = "";
|
|
2498
|
+
for (const block of content) {
|
|
2499
|
+
if (isResponseOutputText(block)) {
|
|
2500
|
+
aggregated += block.text;
|
|
2501
|
+
continue;
|
|
2502
|
+
}
|
|
2503
|
+
if (isResponseOutputRefusal(block)) {
|
|
2504
|
+
aggregated += block.refusal;
|
|
2505
|
+
continue;
|
|
2506
|
+
}
|
|
2507
|
+
if (typeof block.text === "string") {
|
|
2508
|
+
aggregated += block.text;
|
|
2509
|
+
continue;
|
|
2510
|
+
}
|
|
2511
|
+
if (typeof block.reasoning === "string") {
|
|
2512
|
+
aggregated += block.reasoning;
|
|
2513
|
+
continue;
|
|
2514
|
+
}
|
|
2515
|
+
}
|
|
2516
|
+
return aggregated;
|
|
2517
|
+
};
|
|
2518
|
+
const extractReasoningText = (item) => {
|
|
2519
|
+
const segments = [];
|
|
2520
|
+
const collectFromBlocks = (blocks) => {
|
|
2521
|
+
if (!Array.isArray(blocks)) return;
|
|
2522
|
+
for (const block of blocks) if (typeof block.text === "string") {
|
|
2523
|
+
segments.push(block.text);
|
|
2524
|
+
continue;
|
|
2525
|
+
}
|
|
2526
|
+
};
|
|
2527
|
+
if (!item.summary || item.summary.length === 0) return THINKING_TEXT;
|
|
2528
|
+
collectFromBlocks(item.summary);
|
|
2529
|
+
return segments.join("").trim();
|
|
2530
|
+
};
|
|
2531
|
+
const createToolUseContentBlock = (call) => {
|
|
2532
|
+
const toolId = call.call_id;
|
|
2533
|
+
if (!call.name || !toolId) return null;
|
|
2534
|
+
const input = parseFunctionCallArguments(call.arguments);
|
|
2535
|
+
return {
|
|
2536
|
+
type: "tool_use",
|
|
2537
|
+
id: toolId,
|
|
2538
|
+
name: call.name,
|
|
2539
|
+
input
|
|
2540
|
+
};
|
|
2541
|
+
};
|
|
2542
|
+
const parseFunctionCallArguments = (rawArguments) => {
|
|
2543
|
+
if (typeof rawArguments !== "string" || rawArguments.trim().length === 0) return {};
|
|
2544
|
+
try {
|
|
2545
|
+
const parsed = JSON.parse(rawArguments);
|
|
2546
|
+
if (Array.isArray(parsed)) return { arguments: parsed };
|
|
2547
|
+
if (parsed && typeof parsed === "object") return parsed;
|
|
2548
|
+
} catch (error) {
|
|
2549
|
+
consola.warn("Failed to parse function call arguments", {
|
|
2550
|
+
error,
|
|
2551
|
+
rawArguments
|
|
2552
|
+
});
|
|
2553
|
+
}
|
|
2554
|
+
return { raw_arguments: rawArguments };
|
|
2555
|
+
};
|
|
2556
|
+
const fallbackContentBlocks = (outputText) => {
|
|
2557
|
+
if (!outputText) return [];
|
|
2558
|
+
return [{
|
|
2559
|
+
type: "text",
|
|
2560
|
+
text: outputText
|
|
2561
|
+
}];
|
|
2562
|
+
};
|
|
2563
|
+
const mapResponsesStopReason = (response) => {
|
|
2564
|
+
const { status, incomplete_details: incompleteDetails } = response;
|
|
2565
|
+
if (status === "completed") {
|
|
2566
|
+
if (response.output.some((item) => item.type === "function_call")) return "tool_use";
|
|
2567
|
+
return "end_turn";
|
|
2568
|
+
}
|
|
2569
|
+
if (status === "incomplete") {
|
|
2570
|
+
if (incompleteDetails?.reason === "max_output_tokens") return "max_tokens";
|
|
2571
|
+
if (incompleteDetails?.reason === "content_filter") return "end_turn";
|
|
2572
|
+
}
|
|
2573
|
+
return null;
|
|
2574
|
+
};
|
|
2575
|
+
const mapResponsesUsage = (response) => {
|
|
2576
|
+
const inputTokens = response.usage?.input_tokens ?? 0;
|
|
2577
|
+
const outputTokens = response.usage?.output_tokens ?? 0;
|
|
2578
|
+
const inputCachedTokens = response.usage?.input_tokens_details?.cached_tokens;
|
|
2579
|
+
return {
|
|
2580
|
+
input_tokens: inputTokens - (inputCachedTokens ?? 0),
|
|
2581
|
+
output_tokens: outputTokens,
|
|
2582
|
+
...response.usage?.input_tokens_details?.cached_tokens !== void 0 && { cache_read_input_tokens: response.usage.input_tokens_details.cached_tokens }
|
|
2583
|
+
};
|
|
2584
|
+
};
|
|
2585
|
+
const isRecord = (value) => typeof value === "object" && value !== null;
|
|
2586
|
+
const isResponseOutputText = (block) => isRecord(block) && "type" in block && block.type === "output_text";
|
|
2587
|
+
const isResponseOutputRefusal = (block) => isRecord(block) && "type" in block && block.type === "refusal";
|
|
2588
|
+
const parseUserId = (userId) => {
|
|
2589
|
+
if (!userId || typeof userId !== "string") return {
|
|
2590
|
+
safetyIdentifier: null,
|
|
2591
|
+
promptCacheKey: null
|
|
2592
|
+
};
|
|
2593
|
+
const userMatch = userId.match(/user_([^_]+)_account/);
|
|
2594
|
+
const safetyIdentifier = userMatch ? userMatch[1] : null;
|
|
2595
|
+
const sessionMatch = userId.match(/_session_(.+)$/);
|
|
2596
|
+
const promptCacheKey = sessionMatch ? sessionMatch[1] : null;
|
|
2597
|
+
return {
|
|
2598
|
+
safetyIdentifier,
|
|
2599
|
+
promptCacheKey
|
|
2600
|
+
};
|
|
2601
|
+
};
|
|
2602
|
+
const convertToolResultContent = (content) => {
|
|
2603
|
+
if (typeof content === "string") return content;
|
|
2604
|
+
if (Array.isArray(content)) {
|
|
2605
|
+
const result = [];
|
|
2606
|
+
for (const block of content) switch (block.type) {
|
|
2607
|
+
case "text":
|
|
2608
|
+
result.push(createTextContent(block.text));
|
|
2609
|
+
break;
|
|
2610
|
+
case "image":
|
|
2611
|
+
result.push(createImageContent(block));
|
|
2612
|
+
break;
|
|
2613
|
+
default: break;
|
|
2614
|
+
}
|
|
2615
|
+
return result;
|
|
2616
|
+
}
|
|
2617
|
+
return "";
|
|
2618
|
+
};
|
|
2619
|
+
|
|
2620
|
+
//#endregion
|
|
2621
|
+
//#region src/routes/messages/responses-stream-translation.ts
|
|
2622
|
+
const MAX_CONSECUTIVE_FUNCTION_CALL_WHITESPACE = 20;
|
|
2623
|
+
var FunctionCallArgumentsValidationError = class extends Error {
|
|
2624
|
+
constructor(message) {
|
|
2625
|
+
super(message);
|
|
2626
|
+
this.name = "FunctionCallArgumentsValidationError";
|
|
2627
|
+
}
|
|
2628
|
+
};
|
|
2629
|
+
const updateWhitespaceRunState = (previousCount, chunk) => {
|
|
2630
|
+
let count = previousCount;
|
|
2631
|
+
for (const char of chunk) {
|
|
2632
|
+
if (char === "\r" || char === "\n" || char === " ") {
|
|
2633
|
+
count += 1;
|
|
2634
|
+
if (count > MAX_CONSECUTIVE_FUNCTION_CALL_WHITESPACE) return {
|
|
2635
|
+
nextCount: count,
|
|
2636
|
+
exceeded: true
|
|
2637
|
+
};
|
|
2638
|
+
continue;
|
|
2639
|
+
}
|
|
2640
|
+
if (char !== " ") count = 0;
|
|
2641
|
+
}
|
|
2642
|
+
return {
|
|
2643
|
+
nextCount: count,
|
|
2644
|
+
exceeded: false
|
|
2645
|
+
};
|
|
2646
|
+
};
|
|
2647
|
+
const createResponsesStreamState = () => ({
|
|
2648
|
+
messageStartSent: false,
|
|
2649
|
+
messageCompleted: false,
|
|
2650
|
+
nextContentBlockIndex: 0,
|
|
2651
|
+
blockIndexByKey: /* @__PURE__ */ new Map(),
|
|
2652
|
+
openBlocks: /* @__PURE__ */ new Set(),
|
|
2653
|
+
blockHasDelta: /* @__PURE__ */ new Set(),
|
|
2654
|
+
functionCallStateByOutputIndex: /* @__PURE__ */ new Map()
|
|
2655
|
+
});
|
|
2656
|
+
const translateResponsesStreamEvent = (rawEvent, state$1) => {
|
|
2657
|
+
switch (rawEvent.type) {
|
|
2658
|
+
case "response.created": return handleResponseCreated(rawEvent, state$1);
|
|
2659
|
+
case "response.output_item.added": return handleOutputItemAdded$1(rawEvent, state$1);
|
|
2660
|
+
case "response.reasoning_summary_text.delta": return handleReasoningSummaryTextDelta(rawEvent, state$1);
|
|
2661
|
+
case "response.output_text.delta": return handleOutputTextDelta(rawEvent, state$1);
|
|
2662
|
+
case "response.reasoning_summary_text.done": return handleReasoningSummaryTextDone(rawEvent, state$1);
|
|
2663
|
+
case "response.output_text.done": return handleOutputTextDone(rawEvent, state$1);
|
|
2664
|
+
case "response.output_item.done": return handleOutputItemDone$1(rawEvent, state$1);
|
|
2665
|
+
case "response.function_call_arguments.delta": return handleFunctionCallArgumentsDelta(rawEvent, state$1);
|
|
2666
|
+
case "response.function_call_arguments.done": return handleFunctionCallArgumentsDone(rawEvent, state$1);
|
|
2667
|
+
case "response.completed":
|
|
2668
|
+
case "response.incomplete": return handleResponseCompleted(rawEvent, state$1);
|
|
2669
|
+
case "response.failed": return handleResponseFailed(rawEvent, state$1);
|
|
2670
|
+
case "error": return handleErrorEvent(rawEvent, state$1);
|
|
2671
|
+
default: return [];
|
|
2672
|
+
}
|
|
2673
|
+
};
|
|
2674
|
+
const handleResponseCreated = (rawEvent, state$1) => {
|
|
2675
|
+
return messageStart(state$1, rawEvent.response);
|
|
2676
|
+
};
|
|
2677
|
+
const handleOutputItemAdded$1 = (rawEvent, state$1) => {
|
|
2678
|
+
const events$1 = new Array();
|
|
2679
|
+
const functionCallDetails = extractFunctionCallDetails(rawEvent);
|
|
2680
|
+
if (!functionCallDetails) return events$1;
|
|
2681
|
+
const { outputIndex, toolCallId, name: name$1, initialArguments } = functionCallDetails;
|
|
2682
|
+
const blockIndex = openFunctionCallBlock(state$1, {
|
|
2683
|
+
outputIndex,
|
|
2684
|
+
toolCallId,
|
|
2685
|
+
name: name$1,
|
|
2686
|
+
events: events$1
|
|
2687
|
+
});
|
|
2688
|
+
if (initialArguments !== void 0 && initialArguments.length > 0) {
|
|
2689
|
+
events$1.push({
|
|
2690
|
+
type: "content_block_delta",
|
|
2691
|
+
index: blockIndex,
|
|
2692
|
+
delta: {
|
|
2693
|
+
type: "input_json_delta",
|
|
2694
|
+
partial_json: initialArguments
|
|
2695
|
+
}
|
|
2696
|
+
});
|
|
2697
|
+
state$1.blockHasDelta.add(blockIndex);
|
|
2698
|
+
}
|
|
2699
|
+
return events$1;
|
|
2700
|
+
};
|
|
2701
|
+
const handleOutputItemDone$1 = (rawEvent, state$1) => {
|
|
2702
|
+
const events$1 = new Array();
|
|
2703
|
+
const item = rawEvent.item;
|
|
2704
|
+
if (item.type !== "reasoning") return events$1;
|
|
2705
|
+
const outputIndex = rawEvent.output_index;
|
|
2706
|
+
const blockIndex = openThinkingBlockIfNeeded(state$1, outputIndex, events$1);
|
|
2707
|
+
const signature = (item.encrypted_content ?? "") + "@" + item.id;
|
|
2708
|
+
if (signature) {
|
|
2709
|
+
if (!item.summary || item.summary.length === 0) events$1.push({
|
|
2710
|
+
type: "content_block_delta",
|
|
2711
|
+
index: blockIndex,
|
|
2712
|
+
delta: {
|
|
2713
|
+
type: "thinking_delta",
|
|
2714
|
+
thinking: THINKING_TEXT
|
|
2715
|
+
}
|
|
2716
|
+
});
|
|
2717
|
+
events$1.push({
|
|
2718
|
+
type: "content_block_delta",
|
|
2719
|
+
index: blockIndex,
|
|
2720
|
+
delta: {
|
|
2721
|
+
type: "signature_delta",
|
|
2722
|
+
signature
|
|
2723
|
+
}
|
|
2724
|
+
});
|
|
2725
|
+
state$1.blockHasDelta.add(blockIndex);
|
|
2726
|
+
}
|
|
2727
|
+
return events$1;
|
|
2728
|
+
};
|
|
2729
|
+
const handleFunctionCallArgumentsDelta = (rawEvent, state$1) => {
|
|
2730
|
+
const events$1 = new Array();
|
|
2731
|
+
const outputIndex = rawEvent.output_index;
|
|
2732
|
+
const deltaText = rawEvent.delta;
|
|
2733
|
+
if (!deltaText) return events$1;
|
|
2734
|
+
const blockIndex = openFunctionCallBlock(state$1, {
|
|
2735
|
+
outputIndex,
|
|
2736
|
+
events: events$1
|
|
2737
|
+
});
|
|
2738
|
+
const functionCallState = state$1.functionCallStateByOutputIndex.get(outputIndex);
|
|
2739
|
+
if (!functionCallState) return handleFunctionCallArgumentsValidationError(new FunctionCallArgumentsValidationError("Received function call arguments delta without an open tool call block."), state$1, events$1);
|
|
2740
|
+
const { nextCount, exceeded } = updateWhitespaceRunState(functionCallState.consecutiveWhitespaceCount, deltaText);
|
|
2741
|
+
if (exceeded) return handleFunctionCallArgumentsValidationError(new FunctionCallArgumentsValidationError("Received function call arguments delta containing more than 20 consecutive whitespace characters."), state$1, events$1);
|
|
2742
|
+
functionCallState.consecutiveWhitespaceCount = nextCount;
|
|
2743
|
+
events$1.push({
|
|
2744
|
+
type: "content_block_delta",
|
|
2745
|
+
index: blockIndex,
|
|
2746
|
+
delta: {
|
|
2747
|
+
type: "input_json_delta",
|
|
2748
|
+
partial_json: deltaText
|
|
2749
|
+
}
|
|
2750
|
+
});
|
|
2751
|
+
state$1.blockHasDelta.add(blockIndex);
|
|
2752
|
+
return events$1;
|
|
2753
|
+
};
|
|
2754
|
+
const handleFunctionCallArgumentsDone = (rawEvent, state$1) => {
|
|
2755
|
+
const events$1 = new Array();
|
|
2756
|
+
const outputIndex = rawEvent.output_index;
|
|
2757
|
+
const blockIndex = openFunctionCallBlock(state$1, {
|
|
2758
|
+
outputIndex,
|
|
2759
|
+
events: events$1
|
|
2760
|
+
});
|
|
2761
|
+
const finalArguments = typeof rawEvent.arguments === "string" ? rawEvent.arguments : void 0;
|
|
2762
|
+
if (!state$1.blockHasDelta.has(blockIndex) && finalArguments) {
|
|
2763
|
+
events$1.push({
|
|
2764
|
+
type: "content_block_delta",
|
|
2765
|
+
index: blockIndex,
|
|
2766
|
+
delta: {
|
|
2767
|
+
type: "input_json_delta",
|
|
2768
|
+
partial_json: finalArguments
|
|
2769
|
+
}
|
|
2770
|
+
});
|
|
2771
|
+
state$1.blockHasDelta.add(blockIndex);
|
|
2772
|
+
}
|
|
2773
|
+
state$1.functionCallStateByOutputIndex.delete(outputIndex);
|
|
2774
|
+
return events$1;
|
|
2775
|
+
};
|
|
2776
|
+
const handleOutputTextDelta = (rawEvent, state$1) => {
|
|
2777
|
+
const events$1 = new Array();
|
|
2778
|
+
const outputIndex = rawEvent.output_index;
|
|
2779
|
+
const contentIndex = rawEvent.content_index;
|
|
2780
|
+
const deltaText = rawEvent.delta;
|
|
2781
|
+
if (!deltaText) return events$1;
|
|
2782
|
+
const blockIndex = openTextBlockIfNeeded(state$1, {
|
|
2783
|
+
outputIndex,
|
|
2784
|
+
contentIndex,
|
|
2785
|
+
events: events$1
|
|
2786
|
+
});
|
|
2787
|
+
events$1.push({
|
|
2788
|
+
type: "content_block_delta",
|
|
2789
|
+
index: blockIndex,
|
|
2790
|
+
delta: {
|
|
2791
|
+
type: "text_delta",
|
|
2792
|
+
text: deltaText
|
|
2793
|
+
}
|
|
2794
|
+
});
|
|
2795
|
+
state$1.blockHasDelta.add(blockIndex);
|
|
2796
|
+
return events$1;
|
|
2797
|
+
};
|
|
2798
|
+
const handleReasoningSummaryTextDelta = (rawEvent, state$1) => {
|
|
2799
|
+
const outputIndex = rawEvent.output_index;
|
|
2800
|
+
const deltaText = rawEvent.delta;
|
|
2801
|
+
const events$1 = new Array();
|
|
2802
|
+
const blockIndex = openThinkingBlockIfNeeded(state$1, outputIndex, events$1);
|
|
2803
|
+
events$1.push({
|
|
2804
|
+
type: "content_block_delta",
|
|
2805
|
+
index: blockIndex,
|
|
2806
|
+
delta: {
|
|
2807
|
+
type: "thinking_delta",
|
|
2808
|
+
thinking: deltaText
|
|
2809
|
+
}
|
|
2810
|
+
});
|
|
2811
|
+
state$1.blockHasDelta.add(blockIndex);
|
|
2812
|
+
return events$1;
|
|
2813
|
+
};
|
|
2814
|
+
const handleReasoningSummaryTextDone = (rawEvent, state$1) => {
|
|
2815
|
+
const outputIndex = rawEvent.output_index;
|
|
2816
|
+
const text = rawEvent.text;
|
|
2817
|
+
const events$1 = new Array();
|
|
2818
|
+
const blockIndex = openThinkingBlockIfNeeded(state$1, outputIndex, events$1);
|
|
2819
|
+
if (text && !state$1.blockHasDelta.has(blockIndex)) events$1.push({
|
|
2820
|
+
type: "content_block_delta",
|
|
2821
|
+
index: blockIndex,
|
|
2822
|
+
delta: {
|
|
2823
|
+
type: "thinking_delta",
|
|
2824
|
+
thinking: text
|
|
2825
|
+
}
|
|
2826
|
+
});
|
|
2827
|
+
return events$1;
|
|
2828
|
+
};
|
|
2829
|
+
const handleOutputTextDone = (rawEvent, state$1) => {
|
|
2830
|
+
const events$1 = new Array();
|
|
2831
|
+
const outputIndex = rawEvent.output_index;
|
|
2832
|
+
const contentIndex = rawEvent.content_index;
|
|
2833
|
+
const text = rawEvent.text;
|
|
2834
|
+
const blockIndex = openTextBlockIfNeeded(state$1, {
|
|
2835
|
+
outputIndex,
|
|
2836
|
+
contentIndex,
|
|
2837
|
+
events: events$1
|
|
2838
|
+
});
|
|
2839
|
+
if (text && !state$1.blockHasDelta.has(blockIndex)) events$1.push({
|
|
2840
|
+
type: "content_block_delta",
|
|
2841
|
+
index: blockIndex,
|
|
2842
|
+
delta: {
|
|
2843
|
+
type: "text_delta",
|
|
2844
|
+
text
|
|
2845
|
+
}
|
|
2846
|
+
});
|
|
2847
|
+
return events$1;
|
|
2848
|
+
};
|
|
2849
|
+
const handleResponseCompleted = (rawEvent, state$1) => {
|
|
2850
|
+
const response = rawEvent.response;
|
|
2851
|
+
const events$1 = new Array();
|
|
2852
|
+
closeAllOpenBlocks(state$1, events$1);
|
|
2853
|
+
const anthropic = translateResponsesResultToAnthropic(response);
|
|
2854
|
+
events$1.push({
|
|
2855
|
+
type: "message_delta",
|
|
2856
|
+
delta: {
|
|
2857
|
+
stop_reason: anthropic.stop_reason,
|
|
2858
|
+
stop_sequence: anthropic.stop_sequence
|
|
2859
|
+
},
|
|
2860
|
+
usage: anthropic.usage
|
|
2861
|
+
}, { type: "message_stop" });
|
|
2862
|
+
state$1.messageCompleted = true;
|
|
2863
|
+
return events$1;
|
|
2864
|
+
};
|
|
2865
|
+
const handleResponseFailed = (rawEvent, state$1) => {
|
|
2866
|
+
const response = rawEvent.response;
|
|
2867
|
+
const events$1 = new Array();
|
|
2868
|
+
closeAllOpenBlocks(state$1, events$1);
|
|
2869
|
+
const message = response.error?.message ?? "The response failed due to an unknown error.";
|
|
2870
|
+
events$1.push(buildErrorEvent(message));
|
|
2871
|
+
state$1.messageCompleted = true;
|
|
2872
|
+
return events$1;
|
|
2873
|
+
};
|
|
2874
|
+
const handleErrorEvent = (rawEvent, state$1) => {
|
|
2875
|
+
const message = typeof rawEvent.message === "string" ? rawEvent.message : "An unexpected error occurred during streaming.";
|
|
2876
|
+
state$1.messageCompleted = true;
|
|
2877
|
+
return [buildErrorEvent(message)];
|
|
2878
|
+
};
|
|
2879
|
+
const handleFunctionCallArgumentsValidationError = (error, state$1, events$1 = []) => {
|
|
2880
|
+
const reason = error.message;
|
|
2881
|
+
closeAllOpenBlocks(state$1, events$1);
|
|
2882
|
+
state$1.messageCompleted = true;
|
|
2883
|
+
events$1.push(buildErrorEvent(reason));
|
|
2884
|
+
return events$1;
|
|
2885
|
+
};
|
|
2886
|
+
const messageStart = (state$1, response) => {
|
|
2887
|
+
state$1.messageStartSent = true;
|
|
2888
|
+
const inputCachedTokens = response.usage?.input_tokens_details?.cached_tokens;
|
|
2889
|
+
const inputTokens = (response.usage?.input_tokens ?? 0) - (inputCachedTokens ?? 0);
|
|
2890
|
+
return [{
|
|
2891
|
+
type: "message_start",
|
|
2892
|
+
message: {
|
|
2893
|
+
id: response.id,
|
|
2894
|
+
type: "message",
|
|
2895
|
+
role: "assistant",
|
|
2896
|
+
content: [],
|
|
2897
|
+
model: response.model,
|
|
2898
|
+
stop_reason: null,
|
|
2899
|
+
stop_sequence: null,
|
|
2900
|
+
usage: {
|
|
2901
|
+
input_tokens: inputTokens,
|
|
2902
|
+
output_tokens: 0,
|
|
2903
|
+
cache_read_input_tokens: inputCachedTokens ?? 0
|
|
2904
|
+
}
|
|
2905
|
+
}
|
|
2906
|
+
}];
|
|
2907
|
+
};
|
|
2908
|
+
const openTextBlockIfNeeded = (state$1, params) => {
|
|
2909
|
+
const { outputIndex, contentIndex, events: events$1 } = params;
|
|
2910
|
+
const key = getBlockKey(outputIndex, contentIndex);
|
|
2911
|
+
let blockIndex = state$1.blockIndexByKey.get(key);
|
|
2912
|
+
if (blockIndex === void 0) {
|
|
2913
|
+
blockIndex = state$1.nextContentBlockIndex;
|
|
2914
|
+
state$1.nextContentBlockIndex += 1;
|
|
2915
|
+
state$1.blockIndexByKey.set(key, blockIndex);
|
|
2916
|
+
}
|
|
2917
|
+
if (!state$1.openBlocks.has(blockIndex)) {
|
|
2918
|
+
closeOpenBlocks(state$1, events$1);
|
|
2919
|
+
events$1.push({
|
|
2920
|
+
type: "content_block_start",
|
|
2921
|
+
index: blockIndex,
|
|
2922
|
+
content_block: {
|
|
2923
|
+
type: "text",
|
|
2924
|
+
text: ""
|
|
2925
|
+
}
|
|
2926
|
+
});
|
|
2927
|
+
state$1.openBlocks.add(blockIndex);
|
|
2928
|
+
}
|
|
2929
|
+
return blockIndex;
|
|
2930
|
+
};
|
|
2931
|
+
const openThinkingBlockIfNeeded = (state$1, outputIndex, events$1) => {
|
|
2932
|
+
const key = getBlockKey(outputIndex, 0);
|
|
2933
|
+
let blockIndex = state$1.blockIndexByKey.get(key);
|
|
2934
|
+
if (blockIndex === void 0) {
|
|
2935
|
+
blockIndex = state$1.nextContentBlockIndex;
|
|
2936
|
+
state$1.nextContentBlockIndex += 1;
|
|
2937
|
+
state$1.blockIndexByKey.set(key, blockIndex);
|
|
2938
|
+
}
|
|
2939
|
+
if (!state$1.openBlocks.has(blockIndex)) {
|
|
2940
|
+
closeOpenBlocks(state$1, events$1);
|
|
2941
|
+
events$1.push({
|
|
2942
|
+
type: "content_block_start",
|
|
2943
|
+
index: blockIndex,
|
|
2944
|
+
content_block: {
|
|
2945
|
+
type: "thinking",
|
|
2946
|
+
thinking: ""
|
|
2947
|
+
}
|
|
2948
|
+
});
|
|
2949
|
+
state$1.openBlocks.add(blockIndex);
|
|
2950
|
+
}
|
|
2951
|
+
return blockIndex;
|
|
2952
|
+
};
|
|
2953
|
+
const closeBlockIfOpen = (state$1, blockIndex, events$1) => {
|
|
2954
|
+
if (!state$1.openBlocks.has(blockIndex)) return;
|
|
2955
|
+
events$1.push({
|
|
2956
|
+
type: "content_block_stop",
|
|
2957
|
+
index: blockIndex
|
|
2958
|
+
});
|
|
2959
|
+
state$1.openBlocks.delete(blockIndex);
|
|
2960
|
+
state$1.blockHasDelta.delete(blockIndex);
|
|
2961
|
+
};
|
|
2962
|
+
const closeOpenBlocks = (state$1, events$1) => {
|
|
2963
|
+
for (const blockIndex of state$1.openBlocks) closeBlockIfOpen(state$1, blockIndex, events$1);
|
|
2964
|
+
};
|
|
2965
|
+
const closeAllOpenBlocks = (state$1, events$1) => {
|
|
2966
|
+
closeOpenBlocks(state$1, events$1);
|
|
2967
|
+
state$1.functionCallStateByOutputIndex.clear();
|
|
2968
|
+
};
|
|
2969
|
+
const buildErrorEvent = (message) => ({
|
|
2970
|
+
type: "error",
|
|
2971
|
+
error: {
|
|
2972
|
+
type: "api_error",
|
|
2973
|
+
message
|
|
2974
|
+
}
|
|
2975
|
+
});
|
|
2976
|
+
const getBlockKey = (outputIndex, contentIndex) => `${outputIndex}:${contentIndex}`;
|
|
2977
|
+
const openFunctionCallBlock = (state$1, params) => {
|
|
2978
|
+
const { outputIndex, toolCallId, name: name$1, events: events$1 } = params;
|
|
2979
|
+
let functionCallState = state$1.functionCallStateByOutputIndex.get(outputIndex);
|
|
2980
|
+
if (!functionCallState) {
|
|
2981
|
+
const blockIndex$1 = state$1.nextContentBlockIndex;
|
|
2982
|
+
state$1.nextContentBlockIndex += 1;
|
|
2983
|
+
const resolvedToolCallId = toolCallId ?? `tool_call_${blockIndex$1}`;
|
|
2984
|
+
functionCallState = {
|
|
2985
|
+
blockIndex: blockIndex$1,
|
|
2986
|
+
toolCallId: resolvedToolCallId,
|
|
2987
|
+
name: name$1 ?? "function",
|
|
2988
|
+
consecutiveWhitespaceCount: 0
|
|
2989
|
+
};
|
|
2990
|
+
state$1.functionCallStateByOutputIndex.set(outputIndex, functionCallState);
|
|
2991
|
+
}
|
|
2992
|
+
const { blockIndex } = functionCallState;
|
|
2993
|
+
if (!state$1.openBlocks.has(blockIndex)) {
|
|
2994
|
+
closeOpenBlocks(state$1, events$1);
|
|
2995
|
+
events$1.push({
|
|
2996
|
+
type: "content_block_start",
|
|
2997
|
+
index: blockIndex,
|
|
2998
|
+
content_block: {
|
|
2999
|
+
type: "tool_use",
|
|
3000
|
+
id: functionCallState.toolCallId,
|
|
3001
|
+
name: functionCallState.name,
|
|
3002
|
+
input: {}
|
|
3003
|
+
}
|
|
3004
|
+
});
|
|
3005
|
+
state$1.openBlocks.add(blockIndex);
|
|
3006
|
+
}
|
|
3007
|
+
return blockIndex;
|
|
3008
|
+
};
|
|
3009
|
+
const extractFunctionCallDetails = (rawEvent) => {
|
|
3010
|
+
const item = rawEvent.item;
|
|
3011
|
+
if (item.type !== "function_call") return;
|
|
3012
|
+
const outputIndex = rawEvent.output_index;
|
|
3013
|
+
const toolCallId = item.call_id;
|
|
3014
|
+
const name$1 = item.name;
|
|
3015
|
+
const initialArguments = item.arguments;
|
|
3016
|
+
return {
|
|
3017
|
+
outputIndex,
|
|
3018
|
+
toolCallId,
|
|
3019
|
+
name: name$1,
|
|
3020
|
+
initialArguments
|
|
3021
|
+
};
|
|
3022
|
+
};
|
|
3023
|
+
|
|
3024
|
+
//#endregion
|
|
3025
|
+
//#region src/routes/responses/utils.ts
|
|
3026
|
+
const getResponsesRequestOptions = (payload) => {
|
|
3027
|
+
const vision = hasVisionInput(payload);
|
|
3028
|
+
const initiator = hasAgentInitiator(payload) ? "agent" : "user";
|
|
3029
|
+
return {
|
|
3030
|
+
vision,
|
|
3031
|
+
initiator
|
|
3032
|
+
};
|
|
3033
|
+
};
|
|
3034
|
+
const hasAgentInitiator = (payload) => {
|
|
3035
|
+
const lastItem = getPayloadItems(payload).at(-1);
|
|
3036
|
+
if (!lastItem) return false;
|
|
3037
|
+
if (!("role" in lastItem) || !lastItem.role) return true;
|
|
3038
|
+
return (typeof lastItem.role === "string" ? lastItem.role.toLowerCase() : "") === "assistant";
|
|
3039
|
+
};
|
|
3040
|
+
const hasVisionInput = (payload) => {
|
|
3041
|
+
return getPayloadItems(payload).some((item) => containsVisionContent(item));
|
|
3042
|
+
};
|
|
3043
|
+
const getPayloadItems = (payload) => {
|
|
3044
|
+
const result = [];
|
|
3045
|
+
const { input } = payload;
|
|
3046
|
+
if (Array.isArray(input)) result.push(...input);
|
|
3047
|
+
return result;
|
|
3048
|
+
};
|
|
3049
|
+
const containsVisionContent = (value) => {
|
|
3050
|
+
if (!value) return false;
|
|
3051
|
+
if (Array.isArray(value)) return value.some((entry) => containsVisionContent(entry));
|
|
3052
|
+
if (typeof value !== "object") return false;
|
|
3053
|
+
const record = value;
|
|
3054
|
+
if ((typeof record.type === "string" ? record.type.toLowerCase() : void 0) === "input_image") return true;
|
|
3055
|
+
if (Array.isArray(record.content)) return record.content.some((entry) => containsVisionContent(entry));
|
|
3056
|
+
return false;
|
|
3057
|
+
};
|
|
3058
|
+
|
|
3059
|
+
//#endregion
|
|
3060
|
+
//#region src/services/copilot/create-messages.ts
|
|
3061
|
+
const createMessages = async (payload, anthropicBetaHeader, options) => {
|
|
3062
|
+
if (!state.copilotToken) throw new Error("Copilot token not found");
|
|
3063
|
+
const enableVision = payload.messages.some((message) => Array.isArray(message.content) && message.content.some((block) => block.type === "image"));
|
|
3064
|
+
let isInitiateRequest = false;
|
|
3065
|
+
const lastMessage = payload.messages.at(-1);
|
|
3066
|
+
if (lastMessage?.role === "user") isInitiateRequest = Array.isArray(lastMessage.content) ? lastMessage.content.some((block) => block.type !== "tool_result") : true;
|
|
3067
|
+
const initiator = options?.initiator ?? (isInitiateRequest ? "user" : "agent");
|
|
3068
|
+
const headers = {
|
|
3069
|
+
...copilotHeaders(state, enableVision),
|
|
3070
|
+
"X-Initiator": initiator
|
|
3071
|
+
};
|
|
3072
|
+
if (anthropicBetaHeader) {
|
|
3073
|
+
const filteredBeta = anthropicBetaHeader.split(",").map((item) => item.trim()).filter((item) => item !== "claude-code-20250219").join(",");
|
|
3074
|
+
if (filteredBeta) headers["anthropic-beta"] = filteredBeta;
|
|
3075
|
+
} else if (payload.thinking?.budget_tokens) headers["anthropic-beta"] = "interleaved-thinking-2025-05-14";
|
|
3076
|
+
const response = await fetch(`${copilotBaseUrl(state)}/v1/messages`, {
|
|
3077
|
+
method: "POST",
|
|
3078
|
+
headers,
|
|
3079
|
+
body: JSON.stringify(payload)
|
|
3080
|
+
});
|
|
3081
|
+
if (!response.ok) {
|
|
3082
|
+
consola.error("Failed to create messages", response);
|
|
3083
|
+
throw new HTTPError("Failed to create messages", response);
|
|
3084
|
+
}
|
|
3085
|
+
if (payload.stream) return events(response);
|
|
3086
|
+
return await response.json();
|
|
3087
|
+
};
|
|
3088
|
+
|
|
3089
|
+
//#endregion
|
|
3090
|
+
//#region src/routes/messages/stream-translation.ts
|
|
3091
|
+
function isToolBlockOpen(state$1) {
|
|
3092
|
+
if (!state$1.contentBlockOpen) return false;
|
|
3093
|
+
return Object.values(state$1.toolCalls).some((tc) => tc.anthropicBlockIndex === state$1.contentBlockIndex);
|
|
3094
|
+
}
|
|
3095
|
+
function createMessageDeltaEvents(finishReason, usage) {
|
|
3096
|
+
const stopReason = mapOpenAIStopReasonToAnthropic(finishReason);
|
|
3097
|
+
const inputTokens = usage.prompt_tokens - usage.cached_tokens;
|
|
3098
|
+
return [{
|
|
3099
|
+
type: "message_delta",
|
|
3100
|
+
delta: {
|
|
3101
|
+
stop_reason: stopReason,
|
|
3102
|
+
stop_sequence: null
|
|
3103
|
+
},
|
|
3104
|
+
usage: {
|
|
3105
|
+
input_tokens: inputTokens,
|
|
3106
|
+
output_tokens: usage.completion_tokens,
|
|
3107
|
+
cache_creation_input_tokens: 0,
|
|
3108
|
+
cache_read_input_tokens: usage.cached_tokens
|
|
3109
|
+
}
|
|
3110
|
+
}, { type: "message_stop" }];
|
|
3111
|
+
}
|
|
3112
|
+
function translateChunkToAnthropicEvents(chunk, state$1, originalModel) {
|
|
3113
|
+
const events$1 = [];
|
|
3114
|
+
if (chunk.usage) {
|
|
3115
|
+
state$1.pendingUsage = {
|
|
3116
|
+
prompt_tokens: chunk.usage.prompt_tokens,
|
|
3117
|
+
completion_tokens: chunk.usage.completion_tokens,
|
|
3118
|
+
cached_tokens: chunk.usage.prompt_tokens_details?.cached_tokens ?? 0
|
|
3119
|
+
};
|
|
3120
|
+
if (state$1.pendingFinishReason && !state$1.messageDeltaSent) {
|
|
3121
|
+
events$1.push(...createMessageDeltaEvents(state$1.pendingFinishReason, state$1.pendingUsage));
|
|
3122
|
+
state$1.messageDeltaSent = true;
|
|
3123
|
+
}
|
|
3124
|
+
}
|
|
3125
|
+
if (chunk.choices.length === 0) return events$1;
|
|
3126
|
+
const choice = chunk.choices[0];
|
|
3127
|
+
const { delta } = choice;
|
|
3128
|
+
if (!state$1.messageStartSent) {
|
|
3129
|
+
const usage = state$1.pendingUsage ?? {
|
|
3130
|
+
prompt_tokens: 0,
|
|
3131
|
+
completion_tokens: 0,
|
|
3132
|
+
cached_tokens: 0
|
|
3133
|
+
};
|
|
3134
|
+
const inputTokens = usage.prompt_tokens - usage.cached_tokens;
|
|
3135
|
+
events$1.push({
|
|
3136
|
+
type: "message_start",
|
|
3137
|
+
message: {
|
|
3138
|
+
id: chunk.id,
|
|
3139
|
+
type: "message",
|
|
3140
|
+
role: "assistant",
|
|
3141
|
+
content: [],
|
|
3142
|
+
model: originalModel ?? chunk.model,
|
|
3143
|
+
stop_reason: null,
|
|
3144
|
+
stop_sequence: null,
|
|
3145
|
+
usage: {
|
|
3146
|
+
input_tokens: inputTokens,
|
|
3147
|
+
output_tokens: 0,
|
|
3148
|
+
cache_creation_input_tokens: 0,
|
|
3149
|
+
cache_read_input_tokens: usage.cached_tokens
|
|
3150
|
+
}
|
|
3151
|
+
}
|
|
3152
|
+
});
|
|
3153
|
+
state$1.messageStartSent = true;
|
|
3154
|
+
}
|
|
3155
|
+
if (delta.content) {
|
|
3156
|
+
if (isToolBlockOpen(state$1)) {
|
|
3157
|
+
events$1.push({
|
|
3158
|
+
type: "content_block_stop",
|
|
3159
|
+
index: state$1.contentBlockIndex
|
|
3160
|
+
});
|
|
3161
|
+
state$1.contentBlockIndex++;
|
|
3162
|
+
state$1.contentBlockOpen = false;
|
|
3163
|
+
}
|
|
3164
|
+
if (!state$1.contentBlockOpen) {
|
|
3165
|
+
events$1.push({
|
|
3166
|
+
type: "content_block_start",
|
|
3167
|
+
index: state$1.contentBlockIndex,
|
|
3168
|
+
content_block: {
|
|
3169
|
+
type: "text",
|
|
3170
|
+
text: ""
|
|
3171
|
+
}
|
|
3172
|
+
});
|
|
3173
|
+
state$1.contentBlockOpen = true;
|
|
3174
|
+
}
|
|
3175
|
+
events$1.push({
|
|
3176
|
+
type: "content_block_delta",
|
|
3177
|
+
index: state$1.contentBlockIndex,
|
|
3178
|
+
delta: {
|
|
3179
|
+
type: "text_delta",
|
|
3180
|
+
text: delta.content
|
|
3181
|
+
}
|
|
3182
|
+
});
|
|
3183
|
+
}
|
|
3184
|
+
if (delta.tool_calls) for (const toolCall of delta.tool_calls) {
|
|
3185
|
+
if (toolCall.id && toolCall.function?.name) {
|
|
3186
|
+
if (state$1.contentBlockOpen) {
|
|
3187
|
+
events$1.push({
|
|
3188
|
+
type: "content_block_stop",
|
|
3189
|
+
index: state$1.contentBlockIndex
|
|
3190
|
+
});
|
|
3191
|
+
state$1.contentBlockIndex++;
|
|
3192
|
+
state$1.contentBlockOpen = false;
|
|
3193
|
+
}
|
|
3194
|
+
const anthropicBlockIndex = state$1.contentBlockIndex;
|
|
3195
|
+
state$1.toolCalls[toolCall.index] = {
|
|
3196
|
+
id: toolCall.id,
|
|
3197
|
+
name: toolCall.function.name,
|
|
2234
3198
|
anthropicBlockIndex
|
|
2235
3199
|
};
|
|
2236
3200
|
events$1.push({
|
|
@@ -2279,111 +3243,266 @@ function translateChunkToAnthropicEvents(chunk, state$1, originalModel) {
|
|
|
2279
3243
|
}
|
|
2280
3244
|
|
|
2281
3245
|
//#endregion
|
|
2282
|
-
//#region src/routes/messages/
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
const
|
|
2286
|
-
|
|
2287
|
-
for
|
|
2288
|
-
if (
|
|
3246
|
+
//#region src/routes/messages/subagent-marker.ts
|
|
3247
|
+
const subagentMarkerPrefix = "__SUBAGENT_MARKER__";
|
|
3248
|
+
const parseSubagentMarkerFromFirstUser = (payload) => {
|
|
3249
|
+
const firstUserMessage = payload.messages.find((msg) => msg.role === "user");
|
|
3250
|
+
if (!firstUserMessage || !Array.isArray(firstUserMessage.content)) return null;
|
|
3251
|
+
for (const block of firstUserMessage.content) {
|
|
3252
|
+
if (block.type !== "text") continue;
|
|
3253
|
+
const marker = parseSubagentMarkerFromSystemReminder(block.text);
|
|
3254
|
+
if (marker) return marker;
|
|
3255
|
+
}
|
|
3256
|
+
return null;
|
|
3257
|
+
};
|
|
3258
|
+
const parseSubagentMarkerFromSystemReminder = (text) => {
|
|
3259
|
+
const startTag = "<system-reminder>";
|
|
3260
|
+
const endTag = "</system-reminder>";
|
|
3261
|
+
let searchFrom = 0;
|
|
3262
|
+
while (true) {
|
|
3263
|
+
const reminderStart = text.indexOf(startTag, searchFrom);
|
|
3264
|
+
if (reminderStart === -1) break;
|
|
3265
|
+
const contentStart = reminderStart + 17;
|
|
3266
|
+
const reminderEnd = text.indexOf(endTag, contentStart);
|
|
3267
|
+
if (reminderEnd === -1) break;
|
|
3268
|
+
const reminderContent = text.slice(contentStart, reminderEnd);
|
|
3269
|
+
const markerIndex = reminderContent.indexOf(subagentMarkerPrefix);
|
|
3270
|
+
if (markerIndex === -1) {
|
|
3271
|
+
searchFrom = reminderEnd + 18;
|
|
3272
|
+
continue;
|
|
3273
|
+
}
|
|
3274
|
+
const markerJson = reminderContent.slice(markerIndex + 19).trim();
|
|
2289
3275
|
try {
|
|
2290
|
-
const
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
3276
|
+
const parsed = JSON.parse(markerJson);
|
|
3277
|
+
if (!parsed.session_id || !parsed.agent_id || !parsed.agent_type) {
|
|
3278
|
+
searchFrom = reminderEnd + 18;
|
|
3279
|
+
continue;
|
|
3280
|
+
}
|
|
3281
|
+
return parsed;
|
|
3282
|
+
} catch {
|
|
3283
|
+
searchFrom = reminderEnd + 18;
|
|
3284
|
+
continue;
|
|
2299
3285
|
}
|
|
2300
3286
|
}
|
|
2301
|
-
return
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
3287
|
+
return null;
|
|
3288
|
+
};
|
|
3289
|
+
|
|
3290
|
+
//#endregion
|
|
3291
|
+
//#region src/routes/messages/handler.ts
|
|
3292
|
+
const logger$1 = createHandlerLogger("messages-handler");
|
|
3293
|
+
const compactSystemPromptStart = "You are a helpful AI assistant tasked with summarizing conversations";
|
|
2306
3294
|
async function handleCompletion(c) {
|
|
2307
3295
|
await checkRateLimit(state);
|
|
2308
3296
|
const anthropicPayload = await c.req.json();
|
|
2309
|
-
|
|
2310
|
-
const
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
3297
|
+
logger$1.debug("Anthropic request payload:", JSON.stringify(anthropicPayload));
|
|
3298
|
+
const subagentMarker = parseSubagentMarkerFromFirstUser(anthropicPayload);
|
|
3299
|
+
const initiatorOverride = subagentMarker ? "agent" : void 0;
|
|
3300
|
+
if (subagentMarker) logger$1.debug("Detected Subagent marker:", JSON.stringify(subagentMarker));
|
|
3301
|
+
const isCompact = isCompactRequest(anthropicPayload);
|
|
3302
|
+
const anthropicBeta = c.req.header("anthropic-beta");
|
|
3303
|
+
logger$1.debug("Anthropic Beta header:", anthropicBeta);
|
|
3304
|
+
const noTools = !anthropicPayload.tools || anthropicPayload.tools.length === 0;
|
|
3305
|
+
if (anthropicBeta && noTools && !isCompact) anthropicPayload.model = getSmallModel();
|
|
3306
|
+
if (isCompact) {
|
|
3307
|
+
logger$1.debug("Is compact request:", isCompact);
|
|
3308
|
+
if (shouldCompactUseSmallModel()) anthropicPayload.model = getSmallModel();
|
|
3309
|
+
} else mergeToolResultForClaude(anthropicPayload);
|
|
2316
3310
|
if (state.manualApprove) await awaitApproval();
|
|
2317
|
-
const
|
|
2318
|
-
if (
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
model: openAIPayload.model
|
|
2323
|
-
});
|
|
2324
|
-
} else setRequestContext(c, {
|
|
2325
|
-
provider: "Copilot",
|
|
2326
|
-
model: openAIPayload.model
|
|
3311
|
+
const selectedModel = state.models?.data.find((m) => m.id === anthropicPayload.model);
|
|
3312
|
+
if (shouldUseMessagesApi(selectedModel)) return await handleWithMessagesApi(c, anthropicPayload, {
|
|
3313
|
+
anthropicBetaHeader: anthropicBeta,
|
|
3314
|
+
initiatorOverride,
|
|
3315
|
+
selectedModel
|
|
2327
3316
|
});
|
|
2328
|
-
if (anthropicPayload
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
3317
|
+
if (shouldUseResponsesApi(selectedModel)) return await handleWithResponsesApi(c, anthropicPayload, initiatorOverride);
|
|
3318
|
+
return await handleWithChatCompletions(c, anthropicPayload, initiatorOverride);
|
|
3319
|
+
}
|
|
3320
|
+
const RESPONSES_ENDPOINT$1 = "/responses";
|
|
3321
|
+
const MESSAGES_ENDPOINT = "/v1/messages";
|
|
3322
|
+
const handleWithChatCompletions = async (c, anthropicPayload, initiatorOverride) => {
|
|
3323
|
+
const openAIPayload = translateToOpenAI(anthropicPayload);
|
|
3324
|
+
let finalPayload = await applyReplacementsToPayload(openAIPayload);
|
|
3325
|
+
finalPayload = {
|
|
3326
|
+
...finalPayload,
|
|
3327
|
+
model: normalizeModelName(finalPayload.model)
|
|
3328
|
+
};
|
|
3329
|
+
logger$1.debug("Translated OpenAI request payload:", JSON.stringify(finalPayload));
|
|
3330
|
+
const response = await createChatCompletions(finalPayload, { initiator: initiatorOverride });
|
|
3331
|
+
if (isNonStreaming(response)) {
|
|
3332
|
+
logger$1.debug("Non-streaming response from Copilot:", JSON.stringify(response).slice(-400));
|
|
3333
|
+
const anthropicResponse = translateToAnthropic(response);
|
|
3334
|
+
logger$1.debug("Translated Anthropic response:", JSON.stringify(anthropicResponse));
|
|
3335
|
+
return c.json(anthropicResponse);
|
|
3336
|
+
}
|
|
3337
|
+
logger$1.debug("Streaming response from Copilot");
|
|
3338
|
+
return streamSSE(c, async (stream) => {
|
|
3339
|
+
const streamState = {
|
|
3340
|
+
messageStartSent: false,
|
|
3341
|
+
contentBlockIndex: 0,
|
|
3342
|
+
contentBlockOpen: false,
|
|
3343
|
+
toolCalls: {}
|
|
2333
3344
|
};
|
|
2334
|
-
const
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
3345
|
+
for await (const rawEvent of response) {
|
|
3346
|
+
logger$1.debug("Copilot raw stream event:", JSON.stringify(rawEvent));
|
|
3347
|
+
if (rawEvent.data === "[DONE]") break;
|
|
3348
|
+
if (!rawEvent.data) continue;
|
|
3349
|
+
const chunk = JSON.parse(rawEvent.data);
|
|
3350
|
+
const events$1 = translateChunkToAnthropicEvents(chunk, streamState);
|
|
3351
|
+
for (const event of events$1) {
|
|
3352
|
+
logger$1.debug("Translated Anthropic event:", JSON.stringify(event));
|
|
3353
|
+
await stream.writeSSE({
|
|
3354
|
+
event: event.type,
|
|
3355
|
+
data: JSON.stringify(event)
|
|
2343
3356
|
});
|
|
2344
|
-
logTokenUsage(usage.prompt_tokens, usage.completion_tokens);
|
|
2345
3357
|
}
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
3358
|
+
}
|
|
3359
|
+
});
|
|
3360
|
+
};
|
|
3361
|
+
const handleWithResponsesApi = async (c, anthropicPayload, initiatorOverride) => {
|
|
3362
|
+
const responsesPayload = translateAnthropicMessagesToResponsesPayload(anthropicPayload);
|
|
3363
|
+
logger$1.debug("Translated Responses payload:", JSON.stringify(responsesPayload));
|
|
3364
|
+
const { vision, initiator } = getResponsesRequestOptions(responsesPayload);
|
|
3365
|
+
const response = await createResponses(responsesPayload, {
|
|
3366
|
+
vision,
|
|
3367
|
+
initiator: initiatorOverride ?? initiator
|
|
3368
|
+
});
|
|
3369
|
+
if (responsesPayload.stream && isAsyncIterable$1(response)) {
|
|
3370
|
+
logger$1.debug("Streaming response from Copilot (Responses API)");
|
|
3371
|
+
return streamSSE(c, async (stream) => {
|
|
3372
|
+
const streamState = createResponsesStreamState();
|
|
3373
|
+
for await (const chunk of response) {
|
|
3374
|
+
if (chunk.event === "ping") {
|
|
2357
3375
|
await stream.writeSSE({
|
|
2358
|
-
event:
|
|
2359
|
-
data:
|
|
3376
|
+
event: "ping",
|
|
3377
|
+
data: "{\"type\":\"ping\"}"
|
|
2360
3378
|
});
|
|
3379
|
+
continue;
|
|
3380
|
+
}
|
|
3381
|
+
const data = chunk.data;
|
|
3382
|
+
if (!data) continue;
|
|
3383
|
+
logger$1.debug("Responses raw stream event:", data);
|
|
3384
|
+
const events$1 = translateResponsesStreamEvent(JSON.parse(data), streamState);
|
|
3385
|
+
for (const event of events$1) {
|
|
3386
|
+
const eventData = JSON.stringify(event);
|
|
3387
|
+
logger$1.debug("Translated Anthropic event:", eventData);
|
|
3388
|
+
await stream.writeSSE({
|
|
3389
|
+
event: event.type,
|
|
3390
|
+
data: eventData
|
|
3391
|
+
});
|
|
3392
|
+
}
|
|
3393
|
+
if (streamState.messageCompleted) {
|
|
3394
|
+
logger$1.debug("Message completed, ending stream");
|
|
3395
|
+
break;
|
|
2361
3396
|
}
|
|
2362
3397
|
}
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
consola.debug(`[stream] Emitting fallback event: ${evt.type}`);
|
|
3398
|
+
if (!streamState.messageCompleted) {
|
|
3399
|
+
logger$1.warn("Responses stream ended without completion; sending error event");
|
|
3400
|
+
const errorEvent = buildErrorEvent("Responses stream ended without completion");
|
|
2367
3401
|
await stream.writeSSE({
|
|
2368
|
-
event:
|
|
2369
|
-
data: JSON.stringify(
|
|
3402
|
+
event: errorEvent.type,
|
|
3403
|
+
data: JSON.stringify(errorEvent)
|
|
2370
3404
|
});
|
|
2371
3405
|
}
|
|
2372
3406
|
});
|
|
2373
3407
|
}
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
};
|
|
2378
|
-
const azureConfigNonStream = state.azureOpenAIConfig;
|
|
2379
|
-
const response = isAzureModel && azureConfigNonStream ? await createAzureOpenAIChatCompletions(azureConfigNonStream, nonStreamPayload) : await createChatCompletions(nonStreamPayload);
|
|
2380
|
-
if (response.usage) setRequestContext(c, {
|
|
2381
|
-
inputTokens: response.usage.prompt_tokens,
|
|
2382
|
-
outputTokens: response.usage.completion_tokens
|
|
2383
|
-
});
|
|
2384
|
-
const anthropicResponse = translateToAnthropic(response, anthropicPayload.model);
|
|
3408
|
+
logger$1.debug("Non-streaming Responses result:", JSON.stringify(response).slice(-400));
|
|
3409
|
+
const anthropicResponse = translateResponsesResultToAnthropic(response);
|
|
3410
|
+
logger$1.debug("Translated Anthropic response:", JSON.stringify(anthropicResponse));
|
|
2385
3411
|
return c.json(anthropicResponse);
|
|
2386
|
-
}
|
|
3412
|
+
};
|
|
3413
|
+
const handleWithMessagesApi = async (c, anthropicPayload, options) => {
|
|
3414
|
+
const { anthropicBetaHeader, initiatorOverride, selectedModel } = options ?? {};
|
|
3415
|
+
for (const msg of anthropicPayload.messages) if (msg.role === "assistant" && Array.isArray(msg.content)) msg.content = msg.content.filter((block) => {
|
|
3416
|
+
if (block.type !== "thinking") return true;
|
|
3417
|
+
return block.thinking && block.thinking !== "Thinking..." && block.signature && !block.signature.includes("@");
|
|
3418
|
+
});
|
|
3419
|
+
if (selectedModel?.capabilities.supports.adaptive_thinking) {
|
|
3420
|
+
anthropicPayload.thinking = { type: "adaptive" };
|
|
3421
|
+
anthropicPayload.output_config = { effort: getAnthropicEffortForModel(anthropicPayload.model) };
|
|
3422
|
+
}
|
|
3423
|
+
logger$1.debug("Translated Messages payload:", JSON.stringify(anthropicPayload));
|
|
3424
|
+
const response = await createMessages(anthropicPayload, anthropicBetaHeader, { initiator: initiatorOverride });
|
|
3425
|
+
if (isAsyncIterable$1(response)) {
|
|
3426
|
+
logger$1.debug("Streaming response from Copilot (Messages API)");
|
|
3427
|
+
return streamSSE(c, async (stream) => {
|
|
3428
|
+
for await (const event of response) {
|
|
3429
|
+
const eventName = event.event;
|
|
3430
|
+
const data = event.data ?? "";
|
|
3431
|
+
logger$1.debug("Messages raw stream event:", data);
|
|
3432
|
+
await stream.writeSSE({
|
|
3433
|
+
event: eventName,
|
|
3434
|
+
data
|
|
3435
|
+
});
|
|
3436
|
+
}
|
|
3437
|
+
});
|
|
3438
|
+
}
|
|
3439
|
+
logger$1.debug("Non-streaming Messages result:", JSON.stringify(response).slice(-400));
|
|
3440
|
+
return c.json(response);
|
|
3441
|
+
};
|
|
3442
|
+
const shouldUseResponsesApi = (selectedModel) => {
|
|
3443
|
+
return selectedModel?.supported_endpoints?.includes(RESPONSES_ENDPOINT$1) ?? false;
|
|
3444
|
+
};
|
|
3445
|
+
const shouldUseMessagesApi = (selectedModel) => {
|
|
3446
|
+
return selectedModel?.supported_endpoints?.includes(MESSAGES_ENDPOINT) ?? false;
|
|
3447
|
+
};
|
|
3448
|
+
const isNonStreaming = (response) => Object.hasOwn(response, "choices");
|
|
3449
|
+
const isAsyncIterable$1 = (value) => Boolean(value) && typeof value[Symbol.asyncIterator] === "function";
|
|
3450
|
+
const getAnthropicEffortForModel = (model) => {
|
|
3451
|
+
const reasoningEffort = getReasoningEffortForModel(model);
|
|
3452
|
+
if (reasoningEffort === "xhigh") return "max";
|
|
3453
|
+
if (reasoningEffort === "none" || reasoningEffort === "minimal") return "low";
|
|
3454
|
+
return reasoningEffort;
|
|
3455
|
+
};
|
|
3456
|
+
const isCompactRequest = (anthropicPayload) => {
|
|
3457
|
+
const system = anthropicPayload.system;
|
|
3458
|
+
if (typeof system === "string") return system.startsWith(compactSystemPromptStart);
|
|
3459
|
+
if (!Array.isArray(system)) return false;
|
|
3460
|
+
return system.some((msg) => typeof msg.text === "string" && msg.text.startsWith(compactSystemPromptStart));
|
|
3461
|
+
};
|
|
3462
|
+
const mergeContentWithText = (tr, textBlock) => {
|
|
3463
|
+
if (typeof tr.content === "string") return {
|
|
3464
|
+
...tr,
|
|
3465
|
+
content: `${tr.content}\n\n${textBlock.text}`
|
|
3466
|
+
};
|
|
3467
|
+
return {
|
|
3468
|
+
...tr,
|
|
3469
|
+
content: [...tr.content, textBlock]
|
|
3470
|
+
};
|
|
3471
|
+
};
|
|
3472
|
+
const mergeContentWithTexts = (tr, textBlocks) => {
|
|
3473
|
+
if (typeof tr.content === "string") {
|
|
3474
|
+
const appendedTexts = textBlocks.map((tb) => tb.text).join("\n\n");
|
|
3475
|
+
return {
|
|
3476
|
+
...tr,
|
|
3477
|
+
content: `${tr.content}\n\n${appendedTexts}`
|
|
3478
|
+
};
|
|
3479
|
+
}
|
|
3480
|
+
return {
|
|
3481
|
+
...tr,
|
|
3482
|
+
content: [...tr.content, ...textBlocks]
|
|
3483
|
+
};
|
|
3484
|
+
};
|
|
3485
|
+
const mergeToolResultForClaude = (anthropicPayload) => {
|
|
3486
|
+
for (const msg of anthropicPayload.messages) {
|
|
3487
|
+
if (msg.role !== "user" || !Array.isArray(msg.content)) continue;
|
|
3488
|
+
const toolResults = [];
|
|
3489
|
+
const textBlocks = [];
|
|
3490
|
+
let valid = true;
|
|
3491
|
+
for (const block of msg.content) if (block.type === "tool_result") toolResults.push(block);
|
|
3492
|
+
else if (block.type === "text") textBlocks.push(block);
|
|
3493
|
+
else {
|
|
3494
|
+
valid = false;
|
|
3495
|
+
break;
|
|
3496
|
+
}
|
|
3497
|
+
if (!valid || toolResults.length === 0 || textBlocks.length === 0) continue;
|
|
3498
|
+
msg.content = mergeToolResult(toolResults, textBlocks);
|
|
3499
|
+
}
|
|
3500
|
+
};
|
|
3501
|
+
const mergeToolResult = (toolResults, textBlocks) => {
|
|
3502
|
+
if (toolResults.length === textBlocks.length) return toolResults.map((tr, i) => mergeContentWithText(tr, textBlocks[i]));
|
|
3503
|
+
const lastIndex = toolResults.length - 1;
|
|
3504
|
+
return toolResults.map((tr, i) => i === lastIndex ? mergeContentWithTexts(tr, textBlocks) : tr);
|
|
3505
|
+
};
|
|
2387
3506
|
|
|
2388
3507
|
//#endregion
|
|
2389
3508
|
//#region src/routes/messages/route.ts
|
|
@@ -2418,19 +3537,9 @@ modelRoutes.get("/", async (c) => {
|
|
|
2418
3537
|
owned_by: model.vendor,
|
|
2419
3538
|
display_name: model.name
|
|
2420
3539
|
})) ?? [];
|
|
2421
|
-
const azureModels = state.azureOpenAIDeployments?.map((deployment) => ({
|
|
2422
|
-
id: deployment.id,
|
|
2423
|
-
object: "model",
|
|
2424
|
-
type: "model",
|
|
2425
|
-
created: deployment.created,
|
|
2426
|
-
created_at: (/* @__PURE__ */ new Date(deployment.created * 1e3)).toISOString(),
|
|
2427
|
-
owned_by: deployment.owned_by,
|
|
2428
|
-
display_name: `${deployment.deploymentName} (${deployment.model})`
|
|
2429
|
-
})) ?? [];
|
|
2430
|
-
const allModels = [...copilotModels, ...azureModels];
|
|
2431
3540
|
return c.json({
|
|
2432
3541
|
object: "list",
|
|
2433
|
-
data:
|
|
3542
|
+
data: copilotModels,
|
|
2434
3543
|
has_more: false
|
|
2435
3544
|
});
|
|
2436
3545
|
} catch (error) {
|
|
@@ -2476,6 +3585,130 @@ replacementsRoute.delete("/", async (c) => {
|
|
|
2476
3585
|
return c.json({ success: true });
|
|
2477
3586
|
});
|
|
2478
3587
|
|
|
3588
|
+
//#endregion
|
|
3589
|
+
//#region src/routes/responses/stream-id-sync.ts
|
|
3590
|
+
const createStreamIdTracker = () => ({ outputItems: /* @__PURE__ */ new Map() });
|
|
3591
|
+
const fixStreamIds = (data, event, tracker) => {
|
|
3592
|
+
if (!data) return data;
|
|
3593
|
+
const parsed = JSON.parse(data);
|
|
3594
|
+
switch (event) {
|
|
3595
|
+
case "response.output_item.added": return handleOutputItemAdded(parsed, tracker);
|
|
3596
|
+
case "response.output_item.done": return handleOutputItemDone(parsed, tracker);
|
|
3597
|
+
default: return handleItemId(parsed, tracker);
|
|
3598
|
+
}
|
|
3599
|
+
};
|
|
3600
|
+
const handleOutputItemAdded = (parsed, tracker) => {
|
|
3601
|
+
if (!parsed.item.id) {
|
|
3602
|
+
let randomSuffix = "";
|
|
3603
|
+
while (randomSuffix.length < 16) randomSuffix += Math.random().toString(36).slice(2);
|
|
3604
|
+
parsed.item.id = `oi_${parsed.output_index}_${randomSuffix.slice(0, 16)}`;
|
|
3605
|
+
}
|
|
3606
|
+
const outputIndex = parsed.output_index;
|
|
3607
|
+
tracker.outputItems.set(outputIndex, parsed.item.id);
|
|
3608
|
+
return JSON.stringify(parsed);
|
|
3609
|
+
};
|
|
3610
|
+
const handleOutputItemDone = (parsed, tracker) => {
|
|
3611
|
+
const outputIndex = parsed.output_index;
|
|
3612
|
+
const originalId = tracker.outputItems.get(outputIndex);
|
|
3613
|
+
if (originalId) parsed.item.id = originalId;
|
|
3614
|
+
return JSON.stringify(parsed);
|
|
3615
|
+
};
|
|
3616
|
+
const handleItemId = (parsed, tracker) => {
|
|
3617
|
+
const outputIndex = parsed.output_index;
|
|
3618
|
+
if (outputIndex !== void 0) {
|
|
3619
|
+
const itemId = tracker.outputItems.get(outputIndex);
|
|
3620
|
+
if (itemId) parsed.item_id = itemId;
|
|
3621
|
+
}
|
|
3622
|
+
return JSON.stringify(parsed);
|
|
3623
|
+
};
|
|
3624
|
+
|
|
3625
|
+
//#endregion
|
|
3626
|
+
//#region src/routes/responses/handler.ts
|
|
3627
|
+
const logger = createHandlerLogger("responses-handler");
|
|
3628
|
+
const RESPONSES_ENDPOINT = "/responses";
|
|
3629
|
+
const handleResponses = async (c) => {
|
|
3630
|
+
await checkRateLimit(state);
|
|
3631
|
+
const payload = await c.req.json();
|
|
3632
|
+
setRequestContext(c, {
|
|
3633
|
+
provider: "Copilot (Responses)",
|
|
3634
|
+
model: payload.model
|
|
3635
|
+
});
|
|
3636
|
+
logger.debug("Responses request payload:", JSON.stringify(payload));
|
|
3637
|
+
useFunctionApplyPatch(payload);
|
|
3638
|
+
removeWebSearchTool(payload);
|
|
3639
|
+
if (!((state.models?.data.find((model) => model.id === payload.model))?.supported_endpoints?.includes(RESPONSES_ENDPOINT) ?? false)) return c.json({ error: {
|
|
3640
|
+
message: "This model does not support the responses endpoint. Please choose a different model.",
|
|
3641
|
+
type: "invalid_request_error"
|
|
3642
|
+
} }, 400);
|
|
3643
|
+
const { vision, initiator } = getResponsesRequestOptions(payload);
|
|
3644
|
+
if (state.manualApprove) await awaitApproval();
|
|
3645
|
+
const response = await createResponses(payload, {
|
|
3646
|
+
vision,
|
|
3647
|
+
initiator
|
|
3648
|
+
});
|
|
3649
|
+
if (isStreamingRequested(payload) && isAsyncIterable(response)) {
|
|
3650
|
+
logger.debug("Forwarding native Responses stream");
|
|
3651
|
+
return streamSSE(c, async (stream) => {
|
|
3652
|
+
const idTracker = createStreamIdTracker();
|
|
3653
|
+
for await (const chunk of response) {
|
|
3654
|
+
logger.debug("Responses stream chunk:", JSON.stringify(chunk));
|
|
3655
|
+
const processedData = fixStreamIds(chunk.data ?? "", chunk.event, idTracker);
|
|
3656
|
+
await stream.writeSSE({
|
|
3657
|
+
id: chunk.id,
|
|
3658
|
+
event: chunk.event,
|
|
3659
|
+
data: processedData
|
|
3660
|
+
});
|
|
3661
|
+
}
|
|
3662
|
+
});
|
|
3663
|
+
}
|
|
3664
|
+
logger.debug("Forwarding native Responses result:", JSON.stringify(response).slice(-400));
|
|
3665
|
+
return c.json(response);
|
|
3666
|
+
};
|
|
3667
|
+
const isAsyncIterable = (value) => Boolean(value) && typeof value[Symbol.asyncIterator] === "function";
|
|
3668
|
+
const isStreamingRequested = (payload) => Boolean(payload.stream);
|
|
3669
|
+
const useFunctionApplyPatch = (payload) => {
|
|
3670
|
+
if (getConfig().useFunctionApplyPatch ?? true) {
|
|
3671
|
+
logger.debug("Using function tool apply_patch for responses");
|
|
3672
|
+
if (Array.isArray(payload.tools)) {
|
|
3673
|
+
const toolsArr = payload.tools;
|
|
3674
|
+
for (let i = 0; i < toolsArr.length; i++) {
|
|
3675
|
+
const t = toolsArr[i];
|
|
3676
|
+
if (t.type === "custom" && t.name === "apply_patch") toolsArr[i] = {
|
|
3677
|
+
type: "function",
|
|
3678
|
+
name: t.name,
|
|
3679
|
+
description: "Use the `apply_patch` tool to edit files",
|
|
3680
|
+
parameters: {
|
|
3681
|
+
type: "object",
|
|
3682
|
+
properties: { input: {
|
|
3683
|
+
type: "string",
|
|
3684
|
+
description: "The entire contents of the apply_patch command"
|
|
3685
|
+
} },
|
|
3686
|
+
required: ["input"]
|
|
3687
|
+
},
|
|
3688
|
+
strict: false
|
|
3689
|
+
};
|
|
3690
|
+
}
|
|
3691
|
+
}
|
|
3692
|
+
}
|
|
3693
|
+
};
|
|
3694
|
+
const removeWebSearchTool = (payload) => {
|
|
3695
|
+
if (!Array.isArray(payload.tools) || payload.tools.length === 0) return;
|
|
3696
|
+
payload.tools = payload.tools.filter((t) => {
|
|
3697
|
+
return t.type !== "web_search";
|
|
3698
|
+
});
|
|
3699
|
+
};
|
|
3700
|
+
|
|
3701
|
+
//#endregion
|
|
3702
|
+
//#region src/routes/responses/route.ts
|
|
3703
|
+
const responsesRoutes = new Hono();
|
|
3704
|
+
responsesRoutes.post("/", async (c) => {
|
|
3705
|
+
try {
|
|
3706
|
+
return await handleResponses(c);
|
|
3707
|
+
} catch (error) {
|
|
3708
|
+
return await forwardError(c, error);
|
|
3709
|
+
}
|
|
3710
|
+
});
|
|
3711
|
+
|
|
2479
3712
|
//#endregion
|
|
2480
3713
|
//#region src/routes/token/route.ts
|
|
2481
3714
|
const tokenRoute = new Hono();
|
|
@@ -2509,6 +3742,7 @@ usageRoute.get("/", async (c) => {
|
|
|
2509
3742
|
const server = new Hono();
|
|
2510
3743
|
server.use(requestLogger);
|
|
2511
3744
|
server.use(cors());
|
|
3745
|
+
server.use("*", createAuthMiddleware());
|
|
2512
3746
|
server.get("/", (c) => c.text("Server running"));
|
|
2513
3747
|
server.route("/chat/completions", completionRoutes);
|
|
2514
3748
|
server.route("/models", modelRoutes);
|
|
@@ -2516,9 +3750,11 @@ server.route("/embeddings", embeddingRoutes);
|
|
|
2516
3750
|
server.route("/usage", usageRoute);
|
|
2517
3751
|
server.route("/token", tokenRoute);
|
|
2518
3752
|
server.route("/replacements", replacementsRoute);
|
|
3753
|
+
server.route("/responses", responsesRoutes);
|
|
2519
3754
|
server.route("/v1/chat/completions", completionRoutes);
|
|
2520
3755
|
server.route("/v1/models", modelRoutes);
|
|
2521
3756
|
server.route("/v1/embeddings", embeddingRoutes);
|
|
3757
|
+
server.route("/v1/responses", responsesRoutes);
|
|
2522
3758
|
server.route("/v1/messages", messageRoutes);
|
|
2523
3759
|
|
|
2524
3760
|
//#endregion
|
|
@@ -2541,8 +3777,10 @@ async function runServer(options) {
|
|
|
2541
3777
|
state.rateLimitWait = options.rateLimitWait;
|
|
2542
3778
|
state.showToken = options.showToken;
|
|
2543
3779
|
state.debug = options.debug;
|
|
3780
|
+
state.verbose = options.verbose;
|
|
2544
3781
|
if (options.debug) consola.info("Debug mode enabled - raw HTTP requests will be logged");
|
|
2545
3782
|
await ensurePaths();
|
|
3783
|
+
mergeConfigWithDefaults();
|
|
2546
3784
|
await cacheVSCodeVersion();
|
|
2547
3785
|
if (options.githubToken) {
|
|
2548
3786
|
state.githubToken = options.githubToken;
|
|
@@ -2550,10 +3788,7 @@ async function runServer(options) {
|
|
|
2550
3788
|
} else await setupGitHubToken();
|
|
2551
3789
|
await setupCopilotToken();
|
|
2552
3790
|
await cacheModels();
|
|
2553
|
-
|
|
2554
|
-
const copilotModelIds = state.models?.data.map((model) => model.id) ?? [];
|
|
2555
|
-
const azureModelIds = state.azureOpenAIDeployments?.map((deployment) => deployment.id) ?? [];
|
|
2556
|
-
const allModelIds = [...copilotModelIds, ...azureModelIds];
|
|
3791
|
+
const allModelIds = state.models?.data.map((model) => model.id) ?? [];
|
|
2557
3792
|
consola.info(`Available models: \n${allModelIds.map((id) => `- ${id}`).join("\n")}`);
|
|
2558
3793
|
const serverUrl = `http://localhost:${options.port}`;
|
|
2559
3794
|
if (options.claudeCode) {
|