@jeffreycao/copilot-api 1.0.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/LICENSE +21 -0
- package/README.md +381 -0
- package/dist/config-BKsHEU7z.js +258 -0
- package/dist/config-BKsHEU7z.js.map +1 -0
- package/dist/main.js +538 -0
- package/dist/main.js.map +1 -0
- package/dist/server-IY-mTdPh.js +2252 -0
- package/dist/server-IY-mTdPh.js.map +1 -0
- package/package.json +68 -0
|
@@ -0,0 +1,2252 @@
|
|
|
1
|
+
import { HTTPError, PATHS, cacheModels, copilotBaseUrl, copilotHeaders, forwardError, getConfig, getCopilotUsage, getExtraPromptForModel, getReasoningEffortForModel, getSmallModel, isNullish, shouldCompactUseSmallModel, sleep, state } from "./config-BKsHEU7z.js";
|
|
2
|
+
import consola from "consola";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import fs from "node:fs";
|
|
5
|
+
import { Hono } from "hono";
|
|
6
|
+
import { cors } from "hono/cors";
|
|
7
|
+
import { logger } from "hono/logger";
|
|
8
|
+
import { streamSSE } from "hono/streaming";
|
|
9
|
+
import util from "node:util";
|
|
10
|
+
import { events } from "fetch-event-stream";
|
|
11
|
+
|
|
12
|
+
//#region src/lib/approval.ts
|
|
13
|
+
const awaitApproval = async () => {
|
|
14
|
+
if (!await consola.prompt(`Accept incoming request?`, { type: "confirm" })) throw new HTTPError("Request rejected", Response.json({ message: "Request rejected" }, { status: 403 }));
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
//#endregion
|
|
18
|
+
//#region src/lib/logger.ts
|
|
19
|
+
const LOG_RETENTION_MS = 10080 * 60 * 1e3;
|
|
20
|
+
const CLEANUP_INTERVAL_MS = 1440 * 60 * 1e3;
|
|
21
|
+
const LOG_DIR = path.join(PATHS.APP_DIR, "logs");
|
|
22
|
+
const FLUSH_INTERVAL_MS = 1e3;
|
|
23
|
+
const MAX_BUFFER_SIZE = 100;
|
|
24
|
+
const logStreams = /* @__PURE__ */ new Map();
|
|
25
|
+
const logBuffers = /* @__PURE__ */ new Map();
|
|
26
|
+
const ensureLogDirectory = () => {
|
|
27
|
+
if (!fs.existsSync(LOG_DIR)) fs.mkdirSync(LOG_DIR, { recursive: true });
|
|
28
|
+
};
|
|
29
|
+
const cleanupOldLogs = () => {
|
|
30
|
+
if (!fs.existsSync(LOG_DIR)) return;
|
|
31
|
+
const now = Date.now();
|
|
32
|
+
for (const entry of fs.readdirSync(LOG_DIR)) {
|
|
33
|
+
const filePath = path.join(LOG_DIR, entry);
|
|
34
|
+
let stats;
|
|
35
|
+
try {
|
|
36
|
+
stats = fs.statSync(filePath);
|
|
37
|
+
} catch {
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
if (!stats.isFile()) continue;
|
|
41
|
+
if (now - stats.mtimeMs > LOG_RETENTION_MS) try {
|
|
42
|
+
fs.rmSync(filePath);
|
|
43
|
+
} catch {
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
const formatArgs = (args) => args.map((arg) => typeof arg === "string" ? arg : util.inspect(arg, {
|
|
49
|
+
depth: null,
|
|
50
|
+
colors: false
|
|
51
|
+
})).join(" ");
|
|
52
|
+
const sanitizeName = (name) => {
|
|
53
|
+
const normalized = name.toLowerCase().replaceAll(/[^a-z0-9]+/g, "-").replaceAll(/^-+|-+$/g, "");
|
|
54
|
+
return normalized === "" ? "handler" : normalized;
|
|
55
|
+
};
|
|
56
|
+
const getLogStream = (filePath) => {
|
|
57
|
+
let stream = logStreams.get(filePath);
|
|
58
|
+
if (!stream || stream.destroyed) {
|
|
59
|
+
stream = fs.createWriteStream(filePath, { flags: "a" });
|
|
60
|
+
logStreams.set(filePath, stream);
|
|
61
|
+
stream.on("error", (error) => {
|
|
62
|
+
console.warn("Log stream error", error);
|
|
63
|
+
logStreams.delete(filePath);
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
return stream;
|
|
67
|
+
};
|
|
68
|
+
const flushBuffer = (filePath) => {
|
|
69
|
+
const buffer = logBuffers.get(filePath);
|
|
70
|
+
if (!buffer || buffer.length === 0) return;
|
|
71
|
+
const stream = getLogStream(filePath);
|
|
72
|
+
const content = buffer.join("\n") + "\n";
|
|
73
|
+
stream.write(content, (error) => {
|
|
74
|
+
if (error) console.warn("Failed to write handler log", error);
|
|
75
|
+
});
|
|
76
|
+
logBuffers.set(filePath, []);
|
|
77
|
+
};
|
|
78
|
+
const flushAllBuffers = () => {
|
|
79
|
+
for (const filePath of logBuffers.keys()) flushBuffer(filePath);
|
|
80
|
+
};
|
|
81
|
+
const appendLine = (filePath, line) => {
|
|
82
|
+
let buffer = logBuffers.get(filePath);
|
|
83
|
+
if (!buffer) {
|
|
84
|
+
buffer = [];
|
|
85
|
+
logBuffers.set(filePath, buffer);
|
|
86
|
+
}
|
|
87
|
+
buffer.push(line);
|
|
88
|
+
if (buffer.length >= MAX_BUFFER_SIZE) flushBuffer(filePath);
|
|
89
|
+
};
|
|
90
|
+
setInterval(flushAllBuffers, FLUSH_INTERVAL_MS);
|
|
91
|
+
const cleanup = () => {
|
|
92
|
+
flushAllBuffers();
|
|
93
|
+
for (const stream of logStreams.values()) stream.end();
|
|
94
|
+
logStreams.clear();
|
|
95
|
+
logBuffers.clear();
|
|
96
|
+
};
|
|
97
|
+
process.on("exit", cleanup);
|
|
98
|
+
process.on("SIGINT", () => {
|
|
99
|
+
cleanup();
|
|
100
|
+
process.exit(0);
|
|
101
|
+
});
|
|
102
|
+
process.on("SIGTERM", () => {
|
|
103
|
+
cleanup();
|
|
104
|
+
process.exit(0);
|
|
105
|
+
});
|
|
106
|
+
let lastCleanup = 0;
|
|
107
|
+
const createHandlerLogger = (name) => {
|
|
108
|
+
ensureLogDirectory();
|
|
109
|
+
const sanitizedName = sanitizeName(name);
|
|
110
|
+
const instance = consola.withTag(name);
|
|
111
|
+
if (state.verbose) instance.level = 5;
|
|
112
|
+
instance.setReporters([]);
|
|
113
|
+
instance.addReporter({ log(logObj) {
|
|
114
|
+
ensureLogDirectory();
|
|
115
|
+
if (Date.now() - lastCleanup > CLEANUP_INTERVAL_MS) {
|
|
116
|
+
cleanupOldLogs();
|
|
117
|
+
lastCleanup = Date.now();
|
|
118
|
+
}
|
|
119
|
+
const date = logObj.date;
|
|
120
|
+
const dateKey = date.toLocaleDateString("sv-SE");
|
|
121
|
+
const timestamp = date.toLocaleString("sv-SE", { hour12: false });
|
|
122
|
+
const filePath = path.join(LOG_DIR, `${sanitizedName}-${dateKey}.log`);
|
|
123
|
+
const message = formatArgs(logObj.args);
|
|
124
|
+
const line = `[${timestamp}] [${logObj.type}] [${logObj.tag || name}]${message ? ` ${message}` : ""}`;
|
|
125
|
+
appendLine(filePath, line);
|
|
126
|
+
} });
|
|
127
|
+
return instance;
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
//#endregion
|
|
131
|
+
//#region src/lib/rate-limit.ts
|
|
132
|
+
async function checkRateLimit(state$1) {
|
|
133
|
+
if (state$1.rateLimitSeconds === void 0) return;
|
|
134
|
+
const now = Date.now();
|
|
135
|
+
if (!state$1.lastRequestTimestamp) {
|
|
136
|
+
state$1.lastRequestTimestamp = now;
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
const elapsedSeconds = (now - state$1.lastRequestTimestamp) / 1e3;
|
|
140
|
+
if (elapsedSeconds > state$1.rateLimitSeconds) {
|
|
141
|
+
state$1.lastRequestTimestamp = now;
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
const waitTimeSeconds = Math.ceil(state$1.rateLimitSeconds - elapsedSeconds);
|
|
145
|
+
if (!state$1.rateLimitWait) {
|
|
146
|
+
consola.warn(`Rate limit exceeded. Need to wait ${waitTimeSeconds} more seconds.`);
|
|
147
|
+
throw new HTTPError("Rate limit exceeded", Response.json({ message: "Rate limit exceeded" }, { status: 429 }));
|
|
148
|
+
}
|
|
149
|
+
const waitTimeMs = waitTimeSeconds * 1e3;
|
|
150
|
+
consola.warn(`Rate limit reached. Waiting ${waitTimeSeconds} seconds before proceeding...`);
|
|
151
|
+
await sleep(waitTimeMs);
|
|
152
|
+
state$1.lastRequestTimestamp = now;
|
|
153
|
+
consola.info("Rate limit wait completed, proceeding with request");
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
//#endregion
|
|
157
|
+
//#region src/lib/tokenizer.ts
|
|
158
|
+
const ENCODING_MAP = {
|
|
159
|
+
o200k_base: () => import("gpt-tokenizer/encoding/o200k_base"),
|
|
160
|
+
cl100k_base: () => import("gpt-tokenizer/encoding/cl100k_base"),
|
|
161
|
+
p50k_base: () => import("gpt-tokenizer/encoding/p50k_base"),
|
|
162
|
+
p50k_edit: () => import("gpt-tokenizer/encoding/p50k_edit"),
|
|
163
|
+
r50k_base: () => import("gpt-tokenizer/encoding/r50k_base")
|
|
164
|
+
};
|
|
165
|
+
const encodingCache = /* @__PURE__ */ new Map();
|
|
166
|
+
/**
|
|
167
|
+
* Calculate tokens for tool calls
|
|
168
|
+
*/
|
|
169
|
+
const calculateToolCallsTokens = (toolCalls, encoder, constants) => {
|
|
170
|
+
let tokens = 0;
|
|
171
|
+
for (const toolCall of toolCalls) {
|
|
172
|
+
tokens += constants.funcInit;
|
|
173
|
+
tokens += encoder.encode(toolCall.id).length;
|
|
174
|
+
tokens += encoder.encode(toolCall.function.name).length;
|
|
175
|
+
tokens += encoder.encode(toolCall.function.arguments).length;
|
|
176
|
+
}
|
|
177
|
+
tokens += constants.funcEnd;
|
|
178
|
+
return tokens;
|
|
179
|
+
};
|
|
180
|
+
/**
|
|
181
|
+
* Calculate tokens for content parts
|
|
182
|
+
*/
|
|
183
|
+
const calculateContentPartsTokens = (contentParts, encoder) => {
|
|
184
|
+
let tokens = 0;
|
|
185
|
+
for (const part of contentParts) if (part.type === "image_url") tokens += encoder.encode(part.image_url.url).length + 85;
|
|
186
|
+
else if (part.text) tokens += encoder.encode(part.text).length;
|
|
187
|
+
return tokens;
|
|
188
|
+
};
|
|
189
|
+
/**
|
|
190
|
+
* Calculate tokens for a single message
|
|
191
|
+
*/
|
|
192
|
+
const calculateMessageTokens = (message, encoder, constants) => {
|
|
193
|
+
const tokensPerMessage = 3;
|
|
194
|
+
const tokensPerName = 1;
|
|
195
|
+
let tokens = tokensPerMessage;
|
|
196
|
+
for (const [key, value] of Object.entries(message)) {
|
|
197
|
+
if (key === "reasoning_opaque") continue;
|
|
198
|
+
if (typeof value === "string") tokens += encoder.encode(value).length;
|
|
199
|
+
if (key === "name") tokens += tokensPerName;
|
|
200
|
+
if (key === "tool_calls") tokens += calculateToolCallsTokens(value, encoder, constants);
|
|
201
|
+
if (key === "content" && Array.isArray(value)) tokens += calculateContentPartsTokens(value, encoder);
|
|
202
|
+
}
|
|
203
|
+
return tokens;
|
|
204
|
+
};
|
|
205
|
+
/**
|
|
206
|
+
* Calculate tokens using custom algorithm
|
|
207
|
+
*/
|
|
208
|
+
const calculateTokens = (messages, encoder, constants) => {
|
|
209
|
+
if (messages.length === 0) return 0;
|
|
210
|
+
let numTokens = 0;
|
|
211
|
+
for (const message of messages) numTokens += calculateMessageTokens(message, encoder, constants);
|
|
212
|
+
numTokens += 3;
|
|
213
|
+
return numTokens;
|
|
214
|
+
};
|
|
215
|
+
/**
|
|
216
|
+
* Get the corresponding encoder module based on encoding type
|
|
217
|
+
*/
|
|
218
|
+
const getEncodeChatFunction = async (encoding) => {
|
|
219
|
+
if (encodingCache.has(encoding)) {
|
|
220
|
+
const cached = encodingCache.get(encoding);
|
|
221
|
+
if (cached) return cached;
|
|
222
|
+
}
|
|
223
|
+
const supportedEncoding = encoding;
|
|
224
|
+
if (!(supportedEncoding in ENCODING_MAP)) {
|
|
225
|
+
const fallbackModule = await ENCODING_MAP.o200k_base();
|
|
226
|
+
encodingCache.set(encoding, fallbackModule);
|
|
227
|
+
return fallbackModule;
|
|
228
|
+
}
|
|
229
|
+
const encodingModule = await ENCODING_MAP[supportedEncoding]();
|
|
230
|
+
encodingCache.set(encoding, encodingModule);
|
|
231
|
+
return encodingModule;
|
|
232
|
+
};
|
|
233
|
+
/**
|
|
234
|
+
* Get tokenizer type from model information
|
|
235
|
+
*/
|
|
236
|
+
const getTokenizerFromModel = (model) => {
|
|
237
|
+
return model.capabilities.tokenizer || "o200k_base";
|
|
238
|
+
};
|
|
239
|
+
/**
|
|
240
|
+
* Get model-specific constants for token calculation
|
|
241
|
+
*/
|
|
242
|
+
const getModelConstants = (model) => {
|
|
243
|
+
return model.id === "gpt-3.5-turbo" || model.id === "gpt-4" ? {
|
|
244
|
+
funcInit: 10,
|
|
245
|
+
propInit: 3,
|
|
246
|
+
propKey: 3,
|
|
247
|
+
enumInit: -3,
|
|
248
|
+
enumItem: 3,
|
|
249
|
+
funcEnd: 12,
|
|
250
|
+
isGpt: true
|
|
251
|
+
} : {
|
|
252
|
+
funcInit: 7,
|
|
253
|
+
propInit: 3,
|
|
254
|
+
propKey: 3,
|
|
255
|
+
enumInit: -3,
|
|
256
|
+
enumItem: 3,
|
|
257
|
+
funcEnd: 12,
|
|
258
|
+
isGpt: model.id.startsWith("gpt-")
|
|
259
|
+
};
|
|
260
|
+
};
|
|
261
|
+
/**
|
|
262
|
+
* Calculate tokens for a single parameter
|
|
263
|
+
*/
|
|
264
|
+
const calculateParameterTokens = (key, prop, context) => {
|
|
265
|
+
const { encoder, constants } = context;
|
|
266
|
+
let tokens = constants.propKey;
|
|
267
|
+
if (typeof prop !== "object" || prop === null) return tokens;
|
|
268
|
+
const param = prop;
|
|
269
|
+
const paramName = key;
|
|
270
|
+
const paramType = param.type || "string";
|
|
271
|
+
let paramDesc = param.description || "";
|
|
272
|
+
if (param.enum && Array.isArray(param.enum)) {
|
|
273
|
+
tokens += constants.enumInit;
|
|
274
|
+
for (const item of param.enum) {
|
|
275
|
+
tokens += constants.enumItem;
|
|
276
|
+
tokens += encoder.encode(String(item)).length;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
if (paramDesc.endsWith(".")) paramDesc = paramDesc.slice(0, -1);
|
|
280
|
+
const line = `${paramName}:${paramType}:${paramDesc}`;
|
|
281
|
+
tokens += encoder.encode(line).length;
|
|
282
|
+
if (param.type === "array" && param["items"]) tokens += calculateParametersTokens(param["items"], encoder, constants);
|
|
283
|
+
const excludedKeys = new Set([
|
|
284
|
+
"type",
|
|
285
|
+
"description",
|
|
286
|
+
"enum",
|
|
287
|
+
"items"
|
|
288
|
+
]);
|
|
289
|
+
for (const propertyName of Object.keys(param)) if (!excludedKeys.has(propertyName)) {
|
|
290
|
+
const propertyValue = param[propertyName];
|
|
291
|
+
const propertyText = typeof propertyValue === "string" ? propertyValue : JSON.stringify(propertyValue);
|
|
292
|
+
tokens += encoder.encode(`${propertyName}:${propertyText}`).length;
|
|
293
|
+
}
|
|
294
|
+
return tokens;
|
|
295
|
+
};
|
|
296
|
+
/**
|
|
297
|
+
* Calculate tokens for properties object
|
|
298
|
+
*/
|
|
299
|
+
const calculatePropertiesTokens = (properties, encoder, constants) => {
|
|
300
|
+
let tokens = 0;
|
|
301
|
+
if (Object.keys(properties).length > 0) {
|
|
302
|
+
tokens += constants.propInit;
|
|
303
|
+
for (const propKey of Object.keys(properties)) tokens += calculateParameterTokens(propKey, properties[propKey], {
|
|
304
|
+
encoder,
|
|
305
|
+
constants
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
return tokens;
|
|
309
|
+
};
|
|
310
|
+
/**
|
|
311
|
+
* Calculate tokens for function parameters
|
|
312
|
+
*/
|
|
313
|
+
const calculateParametersTokens = (parameters, encoder, constants) => {
|
|
314
|
+
if (!parameters || typeof parameters !== "object") return 0;
|
|
315
|
+
const params = parameters;
|
|
316
|
+
let tokens = 0;
|
|
317
|
+
const excludedKeys = new Set(["$schema", "additionalProperties"]);
|
|
318
|
+
for (const [key, value] of Object.entries(params)) {
|
|
319
|
+
if (excludedKeys.has(key)) continue;
|
|
320
|
+
if (key === "properties") tokens += calculatePropertiesTokens(value, encoder, constants);
|
|
321
|
+
else {
|
|
322
|
+
const paramText = typeof value === "string" ? value : JSON.stringify(value);
|
|
323
|
+
tokens += encoder.encode(`${key}:${paramText}`).length;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
return tokens;
|
|
327
|
+
};
|
|
328
|
+
/**
|
|
329
|
+
* Calculate tokens for a single tool
|
|
330
|
+
*/
|
|
331
|
+
const calculateToolTokens = (tool, encoder, constants) => {
|
|
332
|
+
let tokens = constants.funcInit;
|
|
333
|
+
const func = tool.function;
|
|
334
|
+
const fName = func.name;
|
|
335
|
+
let fDesc = func.description || "";
|
|
336
|
+
if (fDesc.endsWith(".")) fDesc = fDesc.slice(0, -1);
|
|
337
|
+
const line = fName + ":" + fDesc;
|
|
338
|
+
tokens += encoder.encode(line).length;
|
|
339
|
+
if (typeof func.parameters === "object" && func.parameters !== null) tokens += calculateParametersTokens(func.parameters, encoder, constants);
|
|
340
|
+
return tokens;
|
|
341
|
+
};
|
|
342
|
+
/**
|
|
343
|
+
* Calculate token count for tools based on model
|
|
344
|
+
*/
|
|
345
|
+
const numTokensForTools = (tools, encoder, constants) => {
|
|
346
|
+
let funcTokenCount = 0;
|
|
347
|
+
if (constants.isGpt) {
|
|
348
|
+
for (const tool of tools) funcTokenCount += calculateToolTokens(tool, encoder, constants);
|
|
349
|
+
funcTokenCount += constants.funcEnd;
|
|
350
|
+
} else for (const tool of tools) funcTokenCount += encoder.encode(JSON.stringify(tool)).length;
|
|
351
|
+
return funcTokenCount;
|
|
352
|
+
};
|
|
353
|
+
/**
|
|
354
|
+
* Calculate the token count of messages, supporting multiple GPT encoders
|
|
355
|
+
*/
|
|
356
|
+
const getTokenCount = async (payload, model) => {
|
|
357
|
+
const tokenizer = getTokenizerFromModel(model);
|
|
358
|
+
const encoder = await getEncodeChatFunction(tokenizer);
|
|
359
|
+
const simplifiedMessages = payload.messages;
|
|
360
|
+
const inputMessages = simplifiedMessages.filter((msg) => msg.role !== "assistant");
|
|
361
|
+
const outputMessages = simplifiedMessages.filter((msg) => msg.role === "assistant");
|
|
362
|
+
const constants = getModelConstants(model);
|
|
363
|
+
let inputTokens = calculateTokens(inputMessages, encoder, constants);
|
|
364
|
+
if (payload.tools && payload.tools.length > 0) inputTokens += numTokensForTools(payload.tools, encoder, constants);
|
|
365
|
+
const outputTokens = calculateTokens(outputMessages, encoder, constants);
|
|
366
|
+
return {
|
|
367
|
+
input: inputTokens,
|
|
368
|
+
output: outputTokens
|
|
369
|
+
};
|
|
370
|
+
};
|
|
371
|
+
|
|
372
|
+
//#endregion
|
|
373
|
+
//#region src/services/copilot/create-chat-completions.ts
|
|
374
|
+
const createChatCompletions = async (payload) => {
|
|
375
|
+
if (!state.copilotToken) throw new Error("Copilot token not found");
|
|
376
|
+
const enableVision = payload.messages.some((x) => typeof x.content !== "string" && x.content?.some((x$1) => x$1.type === "image_url"));
|
|
377
|
+
let isAgentCall = false;
|
|
378
|
+
if (payload.messages.length > 0) {
|
|
379
|
+
const lastMessage = payload.messages.at(-1);
|
|
380
|
+
if (lastMessage) isAgentCall = ["assistant", "tool"].includes(lastMessage.role);
|
|
381
|
+
}
|
|
382
|
+
const headers = {
|
|
383
|
+
...copilotHeaders(state, enableVision),
|
|
384
|
+
"X-Initiator": isAgentCall ? "agent" : "user"
|
|
385
|
+
};
|
|
386
|
+
const response = await fetch(`${copilotBaseUrl(state)}/chat/completions`, {
|
|
387
|
+
method: "POST",
|
|
388
|
+
headers,
|
|
389
|
+
body: JSON.stringify(payload)
|
|
390
|
+
});
|
|
391
|
+
if (!response.ok) {
|
|
392
|
+
consola.error("Failed to create chat completions", response);
|
|
393
|
+
throw new HTTPError("Failed to create chat completions", response);
|
|
394
|
+
}
|
|
395
|
+
if (payload.stream) return events(response);
|
|
396
|
+
return await response.json();
|
|
397
|
+
};
|
|
398
|
+
|
|
399
|
+
//#endregion
|
|
400
|
+
//#region src/routes/chat-completions/handler.ts
|
|
401
|
+
const logger$3 = createHandlerLogger("chat-completions-handler");
|
|
402
|
+
async function handleCompletion$1(c) {
|
|
403
|
+
await checkRateLimit(state);
|
|
404
|
+
let payload = await c.req.json();
|
|
405
|
+
logger$3.debug("Request payload:", JSON.stringify(payload).slice(-400));
|
|
406
|
+
const selectedModel = state.models?.data.find((model) => model.id === payload.model);
|
|
407
|
+
try {
|
|
408
|
+
if (selectedModel) {
|
|
409
|
+
const tokenCount = await getTokenCount(payload, selectedModel);
|
|
410
|
+
logger$3.info("Current token count:", tokenCount);
|
|
411
|
+
} else logger$3.warn("No model selected, skipping token count calculation");
|
|
412
|
+
} catch (error) {
|
|
413
|
+
logger$3.warn("Failed to calculate token count:", error);
|
|
414
|
+
}
|
|
415
|
+
if (state.manualApprove) await awaitApproval();
|
|
416
|
+
if (isNullish(payload.max_tokens)) {
|
|
417
|
+
payload = {
|
|
418
|
+
...payload,
|
|
419
|
+
max_tokens: selectedModel?.capabilities.limits.max_output_tokens
|
|
420
|
+
};
|
|
421
|
+
logger$3.debug("Set max_tokens to:", JSON.stringify(payload.max_tokens));
|
|
422
|
+
}
|
|
423
|
+
const response = await createChatCompletions(payload);
|
|
424
|
+
if (isNonStreaming$1(response)) {
|
|
425
|
+
logger$3.debug("Non-streaming response:", JSON.stringify(response));
|
|
426
|
+
return c.json(response);
|
|
427
|
+
}
|
|
428
|
+
logger$3.debug("Streaming response");
|
|
429
|
+
return streamSSE(c, async (stream) => {
|
|
430
|
+
for await (const chunk of response) {
|
|
431
|
+
logger$3.debug("Streaming chunk:", JSON.stringify(chunk));
|
|
432
|
+
await stream.writeSSE(chunk);
|
|
433
|
+
}
|
|
434
|
+
});
|
|
435
|
+
}
|
|
436
|
+
const isNonStreaming$1 = (response) => Object.hasOwn(response, "choices");
|
|
437
|
+
|
|
438
|
+
//#endregion
|
|
439
|
+
//#region src/routes/chat-completions/route.ts
|
|
440
|
+
const completionRoutes = new Hono();
|
|
441
|
+
completionRoutes.post("/", async (c) => {
|
|
442
|
+
try {
|
|
443
|
+
return await handleCompletion$1(c);
|
|
444
|
+
} catch (error) {
|
|
445
|
+
return await forwardError(c, error);
|
|
446
|
+
}
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
//#endregion
|
|
450
|
+
//#region src/services/copilot/create-embeddings.ts
|
|
451
|
+
const createEmbeddings = async (payload) => {
|
|
452
|
+
if (!state.copilotToken) throw new Error("Copilot token not found");
|
|
453
|
+
const response = await fetch(`${copilotBaseUrl(state)}/embeddings`, {
|
|
454
|
+
method: "POST",
|
|
455
|
+
headers: copilotHeaders(state),
|
|
456
|
+
body: JSON.stringify(payload)
|
|
457
|
+
});
|
|
458
|
+
if (!response.ok) throw new HTTPError("Failed to create embeddings", response);
|
|
459
|
+
return await response.json();
|
|
460
|
+
};
|
|
461
|
+
|
|
462
|
+
//#endregion
|
|
463
|
+
//#region src/routes/embeddings/route.ts
|
|
464
|
+
const embeddingRoutes = new Hono();
|
|
465
|
+
embeddingRoutes.post("/", async (c) => {
|
|
466
|
+
try {
|
|
467
|
+
const paylod = await c.req.json();
|
|
468
|
+
const response = await createEmbeddings(paylod);
|
|
469
|
+
return c.json(response);
|
|
470
|
+
} catch (error) {
|
|
471
|
+
return await forwardError(c, error);
|
|
472
|
+
}
|
|
473
|
+
});
|
|
474
|
+
|
|
475
|
+
//#endregion
|
|
476
|
+
//#region src/routes/messages/utils.ts
|
|
477
|
+
function mapOpenAIStopReasonToAnthropic(finishReason) {
|
|
478
|
+
if (finishReason === null) return null;
|
|
479
|
+
return {
|
|
480
|
+
stop: "end_turn",
|
|
481
|
+
length: "max_tokens",
|
|
482
|
+
tool_calls: "tool_use",
|
|
483
|
+
content_filter: "end_turn"
|
|
484
|
+
}[finishReason];
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
//#endregion
|
|
488
|
+
//#region src/routes/messages/non-stream-translation.ts
|
|
489
|
+
const THINKING_TEXT = "Thinking...";
|
|
490
|
+
function translateToOpenAI(payload) {
|
|
491
|
+
const modelId = translateModelName(payload.model);
|
|
492
|
+
const model = state.models?.data.find((m) => m.id === modelId);
|
|
493
|
+
const thinkingBudget = getThinkingBudget(payload, model);
|
|
494
|
+
return {
|
|
495
|
+
model: modelId,
|
|
496
|
+
messages: translateAnthropicMessagesToOpenAI(payload, modelId, thinkingBudget),
|
|
497
|
+
max_tokens: payload.max_tokens,
|
|
498
|
+
stop: payload.stop_sequences,
|
|
499
|
+
stream: payload.stream,
|
|
500
|
+
temperature: payload.temperature,
|
|
501
|
+
top_p: payload.top_p,
|
|
502
|
+
user: payload.metadata?.user_id,
|
|
503
|
+
tools: translateAnthropicToolsToOpenAI(payload.tools),
|
|
504
|
+
tool_choice: translateAnthropicToolChoiceToOpenAI(payload.tool_choice),
|
|
505
|
+
thinking_budget: thinkingBudget
|
|
506
|
+
};
|
|
507
|
+
}
|
|
508
|
+
function getThinkingBudget(payload, model) {
|
|
509
|
+
const thinking = payload.thinking;
|
|
510
|
+
if (model && thinking) {
|
|
511
|
+
const maxThinkingBudget = Math.min(model.capabilities.supports.max_thinking_budget ?? 0, (model.capabilities.limits.max_output_tokens ?? 0) - 1);
|
|
512
|
+
if (maxThinkingBudget > 0 && thinking.budget_tokens !== void 0) {
|
|
513
|
+
const budgetTokens = Math.min(thinking.budget_tokens, maxThinkingBudget);
|
|
514
|
+
return Math.max(budgetTokens, model.capabilities.supports.min_thinking_budget ?? 1024);
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
function translateModelName(model) {
|
|
519
|
+
if (model.startsWith("claude-sonnet-4-")) return model.replace(/^claude-sonnet-4-.*/, "claude-sonnet-4");
|
|
520
|
+
else if (model.startsWith("claude-opus-4-")) return model.replace(/^claude-opus-4-.*/, "claude-opus-4");
|
|
521
|
+
return model;
|
|
522
|
+
}
|
|
523
|
+
function translateAnthropicMessagesToOpenAI(payload, modelId, thinkingBudget) {
|
|
524
|
+
const systemMessages = handleSystemPrompt(payload.system, modelId, thinkingBudget);
|
|
525
|
+
const otherMessages = payload.messages.flatMap((message) => message.role === "user" ? handleUserMessage(message) : handleAssistantMessage(message, modelId));
|
|
526
|
+
if (modelId.startsWith("claude") && thinkingBudget) {
|
|
527
|
+
const reminder = "<system-reminder>you MUST follow interleaved_thinking_protocol</system-reminder>";
|
|
528
|
+
const firstUserIndex = otherMessages.findIndex((m) => m.role === "user");
|
|
529
|
+
if (firstUserIndex !== -1) {
|
|
530
|
+
const userMessage = otherMessages[firstUserIndex];
|
|
531
|
+
if (typeof userMessage.content === "string") userMessage.content = reminder + "\n\n" + userMessage.content;
|
|
532
|
+
else if (Array.isArray(userMessage.content)) userMessage.content = [{
|
|
533
|
+
type: "text",
|
|
534
|
+
text: reminder
|
|
535
|
+
}, ...userMessage.content];
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
return [...systemMessages, ...otherMessages];
|
|
539
|
+
}
|
|
540
|
+
function handleSystemPrompt(system, modelId, thinkingBudget) {
|
|
541
|
+
if (!system) return [];
|
|
542
|
+
let extraPrompt = "";
|
|
543
|
+
if (modelId.startsWith("claude") && thinkingBudget) extraPrompt = `
|
|
544
|
+
<interleaved_thinking_protocol>
|
|
545
|
+
ABSOLUTE REQUIREMENT - NON-NEGOTIABLE:
|
|
546
|
+
The current thinking_mode is interleaved, Whenever you have the result of a function call, think carefully , MUST output a thinking block
|
|
547
|
+
RULES:
|
|
548
|
+
Tool result → thinking block (ALWAYS, no exceptions)
|
|
549
|
+
This is NOT optional - it is a hard requirement
|
|
550
|
+
The thinking block must contain substantive reasoning (minimum 3-5 sentences)
|
|
551
|
+
Think about: what the results mean, what to do next, how to answer the user
|
|
552
|
+
NEVER skip this step, even if the result seems simple or obvious
|
|
553
|
+
</interleaved_thinking_protocol>`;
|
|
554
|
+
if (typeof system === "string") return [{
|
|
555
|
+
role: "system",
|
|
556
|
+
content: system + extraPrompt
|
|
557
|
+
}];
|
|
558
|
+
else return [{
|
|
559
|
+
role: "system",
|
|
560
|
+
content: system.map((block, index) => {
|
|
561
|
+
if (index === 0) return block.text + extraPrompt;
|
|
562
|
+
return block.text;
|
|
563
|
+
}).join("\n\n")
|
|
564
|
+
}];
|
|
565
|
+
}
|
|
566
|
+
function handleUserMessage(message) {
|
|
567
|
+
const newMessages = [];
|
|
568
|
+
if (Array.isArray(message.content)) {
|
|
569
|
+
const toolResultBlocks = message.content.filter((block) => block.type === "tool_result");
|
|
570
|
+
const otherBlocks = message.content.filter((block) => block.type !== "tool_result");
|
|
571
|
+
for (const block of toolResultBlocks) newMessages.push({
|
|
572
|
+
role: "tool",
|
|
573
|
+
tool_call_id: block.tool_use_id,
|
|
574
|
+
content: mapContent(block.content)
|
|
575
|
+
});
|
|
576
|
+
if (otherBlocks.length > 0) newMessages.push({
|
|
577
|
+
role: "user",
|
|
578
|
+
content: mapContent(otherBlocks)
|
|
579
|
+
});
|
|
580
|
+
} else newMessages.push({
|
|
581
|
+
role: "user",
|
|
582
|
+
content: mapContent(message.content)
|
|
583
|
+
});
|
|
584
|
+
return newMessages;
|
|
585
|
+
}
|
|
586
|
+
function handleAssistantMessage(message, modelId) {
|
|
587
|
+
if (!Array.isArray(message.content)) return [{
|
|
588
|
+
role: "assistant",
|
|
589
|
+
content: mapContent(message.content)
|
|
590
|
+
}];
|
|
591
|
+
const toolUseBlocks = message.content.filter((block) => block.type === "tool_use");
|
|
592
|
+
let thinkingBlocks = message.content.filter((block) => block.type === "thinking");
|
|
593
|
+
if (modelId.startsWith("claude")) thinkingBlocks = thinkingBlocks.filter((b) => b.thinking && b.thinking !== THINKING_TEXT && b.signature && !b.signature.includes("@"));
|
|
594
|
+
const thinkingContents = thinkingBlocks.filter((b) => b.thinking && b.thinking !== THINKING_TEXT).map((b) => b.thinking);
|
|
595
|
+
const allThinkingContent = thinkingContents.length > 0 ? thinkingContents.join("\n\n") : void 0;
|
|
596
|
+
const signature = thinkingBlocks.find((b) => b.signature)?.signature;
|
|
597
|
+
return toolUseBlocks.length > 0 ? [{
|
|
598
|
+
role: "assistant",
|
|
599
|
+
content: mapContent(message.content),
|
|
600
|
+
reasoning_text: allThinkingContent,
|
|
601
|
+
reasoning_opaque: signature,
|
|
602
|
+
tool_calls: toolUseBlocks.map((toolUse) => ({
|
|
603
|
+
id: toolUse.id,
|
|
604
|
+
type: "function",
|
|
605
|
+
function: {
|
|
606
|
+
name: toolUse.name,
|
|
607
|
+
arguments: JSON.stringify(toolUse.input)
|
|
608
|
+
}
|
|
609
|
+
}))
|
|
610
|
+
}] : [{
|
|
611
|
+
role: "assistant",
|
|
612
|
+
content: mapContent(message.content),
|
|
613
|
+
reasoning_text: allThinkingContent,
|
|
614
|
+
reasoning_opaque: signature
|
|
615
|
+
}];
|
|
616
|
+
}
|
|
617
|
+
function mapContent(content) {
|
|
618
|
+
if (typeof content === "string") return content;
|
|
619
|
+
if (!Array.isArray(content)) return null;
|
|
620
|
+
if (!content.some((block) => block.type === "image")) return content.filter((block) => block.type === "text").map((block) => block.text).join("\n\n");
|
|
621
|
+
const contentParts = [];
|
|
622
|
+
for (const block of content) switch (block.type) {
|
|
623
|
+
case "text":
|
|
624
|
+
contentParts.push({
|
|
625
|
+
type: "text",
|
|
626
|
+
text: block.text
|
|
627
|
+
});
|
|
628
|
+
break;
|
|
629
|
+
case "image":
|
|
630
|
+
contentParts.push({
|
|
631
|
+
type: "image_url",
|
|
632
|
+
image_url: { url: `data:${block.source.media_type};base64,${block.source.data}` }
|
|
633
|
+
});
|
|
634
|
+
break;
|
|
635
|
+
}
|
|
636
|
+
return contentParts;
|
|
637
|
+
}
|
|
638
|
+
function translateAnthropicToolsToOpenAI(anthropicTools) {
|
|
639
|
+
if (!anthropicTools) return;
|
|
640
|
+
return anthropicTools.map((tool) => ({
|
|
641
|
+
type: "function",
|
|
642
|
+
function: {
|
|
643
|
+
name: tool.name,
|
|
644
|
+
description: tool.description,
|
|
645
|
+
parameters: tool.input_schema
|
|
646
|
+
}
|
|
647
|
+
}));
|
|
648
|
+
}
|
|
649
|
+
function translateAnthropicToolChoiceToOpenAI(anthropicToolChoice) {
|
|
650
|
+
if (!anthropicToolChoice) return;
|
|
651
|
+
switch (anthropicToolChoice.type) {
|
|
652
|
+
case "auto": return "auto";
|
|
653
|
+
case "any": return "required";
|
|
654
|
+
case "tool":
|
|
655
|
+
if (anthropicToolChoice.name) return {
|
|
656
|
+
type: "function",
|
|
657
|
+
function: { name: anthropicToolChoice.name }
|
|
658
|
+
};
|
|
659
|
+
return;
|
|
660
|
+
case "none": return "none";
|
|
661
|
+
default: return;
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
function translateToAnthropic(response) {
|
|
665
|
+
const assistantContentBlocks = [];
|
|
666
|
+
let stopReason = response.choices[0]?.finish_reason ?? null;
|
|
667
|
+
for (const choice of response.choices) {
|
|
668
|
+
const textBlocks = getAnthropicTextBlocks(choice.message.content);
|
|
669
|
+
const thinkBlocks = getAnthropicThinkBlocks(choice.message.reasoning_text, choice.message.reasoning_opaque);
|
|
670
|
+
const toolUseBlocks = getAnthropicToolUseBlocks(choice.message.tool_calls);
|
|
671
|
+
assistantContentBlocks.push(...thinkBlocks, ...textBlocks, ...toolUseBlocks);
|
|
672
|
+
if (choice.finish_reason === "tool_calls" || stopReason === "stop") stopReason = choice.finish_reason;
|
|
673
|
+
}
|
|
674
|
+
return {
|
|
675
|
+
id: response.id,
|
|
676
|
+
type: "message",
|
|
677
|
+
role: "assistant",
|
|
678
|
+
model: response.model,
|
|
679
|
+
content: assistantContentBlocks,
|
|
680
|
+
stop_reason: mapOpenAIStopReasonToAnthropic(stopReason),
|
|
681
|
+
stop_sequence: null,
|
|
682
|
+
usage: {
|
|
683
|
+
input_tokens: (response.usage?.prompt_tokens ?? 0) - (response.usage?.prompt_tokens_details?.cached_tokens ?? 0),
|
|
684
|
+
output_tokens: response.usage?.completion_tokens ?? 0,
|
|
685
|
+
...response.usage?.prompt_tokens_details?.cached_tokens !== void 0 && { cache_read_input_tokens: response.usage.prompt_tokens_details.cached_tokens }
|
|
686
|
+
}
|
|
687
|
+
};
|
|
688
|
+
}
|
|
689
|
+
function getAnthropicTextBlocks(messageContent) {
|
|
690
|
+
if (typeof messageContent === "string" && messageContent.length > 0) return [{
|
|
691
|
+
type: "text",
|
|
692
|
+
text: messageContent
|
|
693
|
+
}];
|
|
694
|
+
if (Array.isArray(messageContent)) return messageContent.filter((part) => part.type === "text").map((part) => ({
|
|
695
|
+
type: "text",
|
|
696
|
+
text: part.text
|
|
697
|
+
}));
|
|
698
|
+
return [];
|
|
699
|
+
}
|
|
700
|
+
function getAnthropicThinkBlocks(reasoningText, reasoningOpaque) {
|
|
701
|
+
if (reasoningText && reasoningText.length > 0) return [{
|
|
702
|
+
type: "thinking",
|
|
703
|
+
thinking: reasoningText,
|
|
704
|
+
signature: reasoningOpaque || ""
|
|
705
|
+
}];
|
|
706
|
+
if (reasoningOpaque && reasoningOpaque.length > 0) return [{
|
|
707
|
+
type: "thinking",
|
|
708
|
+
thinking: THINKING_TEXT,
|
|
709
|
+
signature: reasoningOpaque
|
|
710
|
+
}];
|
|
711
|
+
return [];
|
|
712
|
+
}
|
|
713
|
+
function getAnthropicToolUseBlocks(toolCalls) {
|
|
714
|
+
if (!toolCalls) return [];
|
|
715
|
+
return toolCalls.map((toolCall) => ({
|
|
716
|
+
type: "tool_use",
|
|
717
|
+
id: toolCall.id,
|
|
718
|
+
name: toolCall.function.name,
|
|
719
|
+
input: JSON.parse(toolCall.function.arguments)
|
|
720
|
+
}));
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
//#endregion
|
|
724
|
+
//#region src/routes/messages/count-tokens-handler.ts
|
|
725
|
+
/**
|
|
726
|
+
* Handles token counting for Anthropic messages
|
|
727
|
+
*/
|
|
728
|
+
async function handleCountTokens(c) {
|
|
729
|
+
try {
|
|
730
|
+
const anthropicBeta = c.req.header("anthropic-beta");
|
|
731
|
+
const anthropicPayload = await c.req.json();
|
|
732
|
+
const openAIPayload = translateToOpenAI(anthropicPayload);
|
|
733
|
+
const selectedModel = state.models?.data.find((model) => model.id === anthropicPayload.model);
|
|
734
|
+
if (!selectedModel) {
|
|
735
|
+
consola.warn("Model not found, returning default token count");
|
|
736
|
+
return c.json({ input_tokens: 1 });
|
|
737
|
+
}
|
|
738
|
+
const tokenCount = await getTokenCount(openAIPayload, selectedModel);
|
|
739
|
+
if (anthropicPayload.tools && anthropicPayload.tools.length > 0) {
|
|
740
|
+
let addToolSystemPromptCount = false;
|
|
741
|
+
if (anthropicBeta) {
|
|
742
|
+
const toolsLength = anthropicPayload.tools.length;
|
|
743
|
+
addToolSystemPromptCount = !anthropicPayload.tools.some((tool) => tool.name.startsWith("mcp__") || tool.name === "Skill" && toolsLength === 1);
|
|
744
|
+
}
|
|
745
|
+
if (addToolSystemPromptCount) {
|
|
746
|
+
if (anthropicPayload.model.startsWith("claude")) tokenCount.input = tokenCount.input + 346;
|
|
747
|
+
else if (anthropicPayload.model.startsWith("grok")) tokenCount.input = tokenCount.input + 120;
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
let finalTokenCount = tokenCount.input + tokenCount.output;
|
|
751
|
+
if (anthropicPayload.model.startsWith("claude")) finalTokenCount = Math.round(finalTokenCount * 1.15);
|
|
752
|
+
consola.info("Token count:", finalTokenCount);
|
|
753
|
+
return c.json({ input_tokens: finalTokenCount });
|
|
754
|
+
} catch (error) {
|
|
755
|
+
consola.error("Error counting tokens:", error);
|
|
756
|
+
return c.json({ input_tokens: 1 });
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
//#endregion
|
|
761
|
+
//#region src/services/copilot/create-responses.ts
|
|
762
|
+
const createResponses = async (payload, { vision, initiator }) => {
|
|
763
|
+
if (!state.copilotToken) throw new Error("Copilot token not found");
|
|
764
|
+
const headers = {
|
|
765
|
+
...copilotHeaders(state, vision),
|
|
766
|
+
"X-Initiator": initiator
|
|
767
|
+
};
|
|
768
|
+
payload.service_tier = null;
|
|
769
|
+
const response = await fetch(`${copilotBaseUrl(state)}/responses`, {
|
|
770
|
+
method: "POST",
|
|
771
|
+
headers,
|
|
772
|
+
body: JSON.stringify(payload)
|
|
773
|
+
});
|
|
774
|
+
if (!response.ok) {
|
|
775
|
+
consola.error("Failed to create responses", response);
|
|
776
|
+
throw new HTTPError("Failed to create responses", response);
|
|
777
|
+
}
|
|
778
|
+
if (payload.stream) return events(response);
|
|
779
|
+
return await response.json();
|
|
780
|
+
};
|
|
781
|
+
|
|
782
|
+
//#endregion
|
|
783
|
+
//#region src/routes/messages/responses-translation.ts
|
|
784
|
+
const MESSAGE_TYPE = "message";
|
|
785
|
+
const THINKING_TEXT$1 = "Thinking...";
|
|
786
|
+
const translateAnthropicMessagesToResponsesPayload = (payload) => {
|
|
787
|
+
const input = [];
|
|
788
|
+
for (const message of payload.messages) input.push(...translateMessage(message));
|
|
789
|
+
const translatedTools = convertAnthropicTools(payload.tools);
|
|
790
|
+
const toolChoice = convertAnthropicToolChoice(payload.tool_choice);
|
|
791
|
+
const { safetyIdentifier, promptCacheKey } = parseUserId(payload.metadata?.user_id);
|
|
792
|
+
return {
|
|
793
|
+
model: payload.model,
|
|
794
|
+
input,
|
|
795
|
+
instructions: translateSystemPrompt(payload.system, payload.model),
|
|
796
|
+
temperature: 1,
|
|
797
|
+
top_p: payload.top_p ?? null,
|
|
798
|
+
max_output_tokens: Math.max(payload.max_tokens, 12800),
|
|
799
|
+
tools: translatedTools,
|
|
800
|
+
tool_choice: toolChoice,
|
|
801
|
+
metadata: payload.metadata ? { ...payload.metadata } : null,
|
|
802
|
+
safety_identifier: safetyIdentifier,
|
|
803
|
+
prompt_cache_key: promptCacheKey,
|
|
804
|
+
stream: payload.stream ?? null,
|
|
805
|
+
store: false,
|
|
806
|
+
parallel_tool_calls: true,
|
|
807
|
+
reasoning: {
|
|
808
|
+
effort: getReasoningEffortForModel(payload.model),
|
|
809
|
+
summary: "detailed"
|
|
810
|
+
},
|
|
811
|
+
include: ["reasoning.encrypted_content"]
|
|
812
|
+
};
|
|
813
|
+
};
|
|
814
|
+
const translateMessage = (message) => {
|
|
815
|
+
if (message.role === "user") return translateUserMessage(message);
|
|
816
|
+
return translateAssistantMessage(message);
|
|
817
|
+
};
|
|
818
|
+
const translateUserMessage = (message) => {
|
|
819
|
+
if (typeof message.content === "string") return [createMessage("user", message.content)];
|
|
820
|
+
if (!Array.isArray(message.content)) return [];
|
|
821
|
+
const items = [];
|
|
822
|
+
const pendingContent = [];
|
|
823
|
+
for (const block of message.content) {
|
|
824
|
+
if (block.type === "tool_result") {
|
|
825
|
+
flushPendingContent("user", pendingContent, items);
|
|
826
|
+
items.push(createFunctionCallOutput(block));
|
|
827
|
+
continue;
|
|
828
|
+
}
|
|
829
|
+
const converted = translateUserContentBlock(block);
|
|
830
|
+
if (converted) pendingContent.push(converted);
|
|
831
|
+
}
|
|
832
|
+
flushPendingContent("user", pendingContent, items);
|
|
833
|
+
return items;
|
|
834
|
+
};
|
|
835
|
+
const translateAssistantMessage = (message) => {
|
|
836
|
+
if (typeof message.content === "string") return [createMessage("assistant", message.content)];
|
|
837
|
+
if (!Array.isArray(message.content)) return [];
|
|
838
|
+
const items = [];
|
|
839
|
+
const pendingContent = [];
|
|
840
|
+
for (const block of message.content) {
|
|
841
|
+
if (block.type === "tool_use") {
|
|
842
|
+
flushPendingContent("assistant", pendingContent, items);
|
|
843
|
+
items.push(createFunctionToolCall(block));
|
|
844
|
+
continue;
|
|
845
|
+
}
|
|
846
|
+
if (block.type === "thinking" && block.signature && block.signature.includes("@")) {
|
|
847
|
+
flushPendingContent("assistant", pendingContent, items);
|
|
848
|
+
items.push(createReasoningContent(block));
|
|
849
|
+
continue;
|
|
850
|
+
}
|
|
851
|
+
const converted = translateAssistantContentBlock(block);
|
|
852
|
+
if (converted) pendingContent.push(converted);
|
|
853
|
+
}
|
|
854
|
+
flushPendingContent("assistant", pendingContent, items);
|
|
855
|
+
return items;
|
|
856
|
+
};
|
|
857
|
+
const translateUserContentBlock = (block) => {
|
|
858
|
+
switch (block.type) {
|
|
859
|
+
case "text": return createTextContent(block.text);
|
|
860
|
+
case "image": return createImageContent(block);
|
|
861
|
+
default: return;
|
|
862
|
+
}
|
|
863
|
+
};
|
|
864
|
+
const translateAssistantContentBlock = (block) => {
|
|
865
|
+
switch (block.type) {
|
|
866
|
+
case "text": return createOutPutTextContent(block.text);
|
|
867
|
+
default: return;
|
|
868
|
+
}
|
|
869
|
+
};
|
|
870
|
+
const flushPendingContent = (role, pendingContent, target) => {
|
|
871
|
+
if (pendingContent.length === 0) return;
|
|
872
|
+
const messageContent = [...pendingContent];
|
|
873
|
+
target.push(createMessage(role, messageContent));
|
|
874
|
+
pendingContent.length = 0;
|
|
875
|
+
};
|
|
876
|
+
const createMessage = (role, content) => ({
|
|
877
|
+
type: MESSAGE_TYPE,
|
|
878
|
+
role,
|
|
879
|
+
content
|
|
880
|
+
});
|
|
881
|
+
const createTextContent = (text) => ({
|
|
882
|
+
type: "input_text",
|
|
883
|
+
text
|
|
884
|
+
});
|
|
885
|
+
const createOutPutTextContent = (text) => ({
|
|
886
|
+
type: "output_text",
|
|
887
|
+
text
|
|
888
|
+
});
|
|
889
|
+
const createImageContent = (block) => ({
|
|
890
|
+
type: "input_image",
|
|
891
|
+
image_url: `data:${block.source.media_type};base64,${block.source.data}`,
|
|
892
|
+
detail: "auto"
|
|
893
|
+
});
|
|
894
|
+
const createReasoningContent = (block) => {
|
|
895
|
+
const array = block.signature.split("@");
|
|
896
|
+
const signature = array[0];
|
|
897
|
+
const id = array[1];
|
|
898
|
+
const thinking = block.thinking === THINKING_TEXT$1 ? "" : block.thinking;
|
|
899
|
+
return {
|
|
900
|
+
id,
|
|
901
|
+
type: "reasoning",
|
|
902
|
+
summary: thinking ? [{
|
|
903
|
+
type: "summary_text",
|
|
904
|
+
text: thinking
|
|
905
|
+
}] : [],
|
|
906
|
+
encrypted_content: signature
|
|
907
|
+
};
|
|
908
|
+
};
|
|
909
|
+
const createFunctionToolCall = (block) => ({
|
|
910
|
+
type: "function_call",
|
|
911
|
+
call_id: block.id,
|
|
912
|
+
name: block.name,
|
|
913
|
+
arguments: JSON.stringify(block.input),
|
|
914
|
+
status: "completed"
|
|
915
|
+
});
|
|
916
|
+
const createFunctionCallOutput = (block) => ({
|
|
917
|
+
type: "function_call_output",
|
|
918
|
+
call_id: block.tool_use_id,
|
|
919
|
+
output: convertToolResultContent(block.content),
|
|
920
|
+
status: block.is_error ? "incomplete" : "completed"
|
|
921
|
+
});
|
|
922
|
+
const translateSystemPrompt = (system, model) => {
|
|
923
|
+
if (!system) return null;
|
|
924
|
+
const extraPrompt = getExtraPromptForModel(model);
|
|
925
|
+
if (typeof system === "string") return system + extraPrompt;
|
|
926
|
+
const text = system.map((block, index) => {
|
|
927
|
+
if (index === 0) return block.text + extraPrompt;
|
|
928
|
+
return block.text;
|
|
929
|
+
}).join(" ");
|
|
930
|
+
return text.length > 0 ? text : null;
|
|
931
|
+
};
|
|
932
|
+
const convertAnthropicTools = (tools) => {
|
|
933
|
+
if (!tools || tools.length === 0) return null;
|
|
934
|
+
return tools.map((tool) => ({
|
|
935
|
+
type: "function",
|
|
936
|
+
name: tool.name,
|
|
937
|
+
parameters: tool.input_schema,
|
|
938
|
+
strict: false,
|
|
939
|
+
...tool.description ? { description: tool.description } : {}
|
|
940
|
+
}));
|
|
941
|
+
};
|
|
942
|
+
const convertAnthropicToolChoice = (choice) => {
|
|
943
|
+
if (!choice) return "auto";
|
|
944
|
+
switch (choice.type) {
|
|
945
|
+
case "auto": return "auto";
|
|
946
|
+
case "any": return "required";
|
|
947
|
+
case "tool": return choice.name ? {
|
|
948
|
+
type: "function",
|
|
949
|
+
name: choice.name
|
|
950
|
+
} : "auto";
|
|
951
|
+
case "none": return "none";
|
|
952
|
+
default: return "auto";
|
|
953
|
+
}
|
|
954
|
+
};
|
|
955
|
+
const translateResponsesResultToAnthropic = (response) => {
|
|
956
|
+
const contentBlocks = mapOutputToAnthropicContent(response.output);
|
|
957
|
+
const usage = mapResponsesUsage(response);
|
|
958
|
+
let anthropicContent = fallbackContentBlocks(response.output_text);
|
|
959
|
+
if (contentBlocks.length > 0) anthropicContent = contentBlocks;
|
|
960
|
+
const stopReason = mapResponsesStopReason(response);
|
|
961
|
+
return {
|
|
962
|
+
id: response.id,
|
|
963
|
+
type: "message",
|
|
964
|
+
role: "assistant",
|
|
965
|
+
content: anthropicContent,
|
|
966
|
+
model: response.model,
|
|
967
|
+
stop_reason: stopReason,
|
|
968
|
+
stop_sequence: null,
|
|
969
|
+
usage
|
|
970
|
+
};
|
|
971
|
+
};
|
|
972
|
+
const mapOutputToAnthropicContent = (output) => {
|
|
973
|
+
const contentBlocks = [];
|
|
974
|
+
for (const item of output) switch (item.type) {
|
|
975
|
+
case "reasoning": {
|
|
976
|
+
const thinkingText = extractReasoningText(item);
|
|
977
|
+
if (thinkingText.length > 0) contentBlocks.push({
|
|
978
|
+
type: "thinking",
|
|
979
|
+
thinking: thinkingText,
|
|
980
|
+
signature: (item.encrypted_content ?? "") + "@" + item.id
|
|
981
|
+
});
|
|
982
|
+
break;
|
|
983
|
+
}
|
|
984
|
+
case "function_call": {
|
|
985
|
+
const toolUseBlock = createToolUseContentBlock(item);
|
|
986
|
+
if (toolUseBlock) contentBlocks.push(toolUseBlock);
|
|
987
|
+
break;
|
|
988
|
+
}
|
|
989
|
+
case "message": {
|
|
990
|
+
const combinedText = combineMessageTextContent(item.content);
|
|
991
|
+
if (combinedText.length > 0) contentBlocks.push({
|
|
992
|
+
type: "text",
|
|
993
|
+
text: combinedText
|
|
994
|
+
});
|
|
995
|
+
break;
|
|
996
|
+
}
|
|
997
|
+
default: {
|
|
998
|
+
const combinedText = combineMessageTextContent(item.content);
|
|
999
|
+
if (combinedText.length > 0) contentBlocks.push({
|
|
1000
|
+
type: "text",
|
|
1001
|
+
text: combinedText
|
|
1002
|
+
});
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
return contentBlocks;
|
|
1006
|
+
};
|
|
1007
|
+
const combineMessageTextContent = (content) => {
|
|
1008
|
+
if (!Array.isArray(content)) return "";
|
|
1009
|
+
let aggregated = "";
|
|
1010
|
+
for (const block of content) {
|
|
1011
|
+
if (isResponseOutputText(block)) {
|
|
1012
|
+
aggregated += block.text;
|
|
1013
|
+
continue;
|
|
1014
|
+
}
|
|
1015
|
+
if (isResponseOutputRefusal(block)) {
|
|
1016
|
+
aggregated += block.refusal;
|
|
1017
|
+
continue;
|
|
1018
|
+
}
|
|
1019
|
+
if (typeof block.text === "string") {
|
|
1020
|
+
aggregated += block.text;
|
|
1021
|
+
continue;
|
|
1022
|
+
}
|
|
1023
|
+
if (typeof block.reasoning === "string") {
|
|
1024
|
+
aggregated += block.reasoning;
|
|
1025
|
+
continue;
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
return aggregated;
|
|
1029
|
+
};
|
|
1030
|
+
const extractReasoningText = (item) => {
|
|
1031
|
+
const segments = [];
|
|
1032
|
+
const collectFromBlocks = (blocks) => {
|
|
1033
|
+
if (!Array.isArray(blocks)) return;
|
|
1034
|
+
for (const block of blocks) if (typeof block.text === "string") {
|
|
1035
|
+
segments.push(block.text);
|
|
1036
|
+
continue;
|
|
1037
|
+
}
|
|
1038
|
+
};
|
|
1039
|
+
if (!item.summary || item.summary.length === 0) return THINKING_TEXT$1;
|
|
1040
|
+
collectFromBlocks(item.summary);
|
|
1041
|
+
return segments.join("").trim();
|
|
1042
|
+
};
|
|
1043
|
+
const createToolUseContentBlock = (call) => {
|
|
1044
|
+
const toolId = call.call_id;
|
|
1045
|
+
if (!call.name || !toolId) return null;
|
|
1046
|
+
const input = parseFunctionCallArguments(call.arguments);
|
|
1047
|
+
return {
|
|
1048
|
+
type: "tool_use",
|
|
1049
|
+
id: toolId,
|
|
1050
|
+
name: call.name,
|
|
1051
|
+
input
|
|
1052
|
+
};
|
|
1053
|
+
};
|
|
1054
|
+
const parseFunctionCallArguments = (rawArguments) => {
|
|
1055
|
+
if (typeof rawArguments !== "string" || rawArguments.trim().length === 0) return {};
|
|
1056
|
+
try {
|
|
1057
|
+
const parsed = JSON.parse(rawArguments);
|
|
1058
|
+
if (Array.isArray(parsed)) return { arguments: parsed };
|
|
1059
|
+
if (parsed && typeof parsed === "object") return parsed;
|
|
1060
|
+
} catch (error) {
|
|
1061
|
+
consola.warn("Failed to parse function call arguments", {
|
|
1062
|
+
error,
|
|
1063
|
+
rawArguments
|
|
1064
|
+
});
|
|
1065
|
+
}
|
|
1066
|
+
return { raw_arguments: rawArguments };
|
|
1067
|
+
};
|
|
1068
|
+
const fallbackContentBlocks = (outputText) => {
|
|
1069
|
+
if (!outputText) return [];
|
|
1070
|
+
return [{
|
|
1071
|
+
type: "text",
|
|
1072
|
+
text: outputText
|
|
1073
|
+
}];
|
|
1074
|
+
};
|
|
1075
|
+
const mapResponsesStopReason = (response) => {
|
|
1076
|
+
const { status, incomplete_details: incompleteDetails } = response;
|
|
1077
|
+
if (status === "completed") {
|
|
1078
|
+
if (response.output.some((item) => item.type === "function_call")) return "tool_use";
|
|
1079
|
+
return "end_turn";
|
|
1080
|
+
}
|
|
1081
|
+
if (status === "incomplete") {
|
|
1082
|
+
if (incompleteDetails?.reason === "max_output_tokens") return "max_tokens";
|
|
1083
|
+
if (incompleteDetails?.reason === "content_filter") return "end_turn";
|
|
1084
|
+
}
|
|
1085
|
+
return null;
|
|
1086
|
+
};
|
|
1087
|
+
const mapResponsesUsage = (response) => {
|
|
1088
|
+
const inputTokens = response.usage?.input_tokens ?? 0;
|
|
1089
|
+
const outputTokens = response.usage?.output_tokens ?? 0;
|
|
1090
|
+
const inputCachedTokens = response.usage?.input_tokens_details?.cached_tokens;
|
|
1091
|
+
return {
|
|
1092
|
+
input_tokens: inputTokens - (inputCachedTokens ?? 0),
|
|
1093
|
+
output_tokens: outputTokens,
|
|
1094
|
+
...response.usage?.input_tokens_details?.cached_tokens !== void 0 && { cache_read_input_tokens: response.usage.input_tokens_details.cached_tokens }
|
|
1095
|
+
};
|
|
1096
|
+
};
|
|
1097
|
+
const isRecord = (value) => typeof value === "object" && value !== null;
|
|
1098
|
+
const isResponseOutputText = (block) => isRecord(block) && "type" in block && block.type === "output_text";
|
|
1099
|
+
const isResponseOutputRefusal = (block) => isRecord(block) && "type" in block && block.type === "refusal";
|
|
1100
|
+
const parseUserId = (userId) => {
|
|
1101
|
+
if (!userId || typeof userId !== "string") return {
|
|
1102
|
+
safetyIdentifier: null,
|
|
1103
|
+
promptCacheKey: null
|
|
1104
|
+
};
|
|
1105
|
+
const userMatch = userId.match(/user_([^_]+)_account/);
|
|
1106
|
+
const safetyIdentifier = userMatch ? userMatch[1] : null;
|
|
1107
|
+
const sessionMatch = userId.match(/_session_(.+)$/);
|
|
1108
|
+
const promptCacheKey = sessionMatch ? sessionMatch[1] : null;
|
|
1109
|
+
return {
|
|
1110
|
+
safetyIdentifier,
|
|
1111
|
+
promptCacheKey
|
|
1112
|
+
};
|
|
1113
|
+
};
|
|
1114
|
+
const convertToolResultContent = (content) => {
|
|
1115
|
+
if (typeof content === "string") return content;
|
|
1116
|
+
if (Array.isArray(content)) {
|
|
1117
|
+
const result = [];
|
|
1118
|
+
for (const block of content) switch (block.type) {
|
|
1119
|
+
case "text":
|
|
1120
|
+
result.push(createTextContent(block.text));
|
|
1121
|
+
break;
|
|
1122
|
+
case "image":
|
|
1123
|
+
result.push(createImageContent(block));
|
|
1124
|
+
break;
|
|
1125
|
+
default: break;
|
|
1126
|
+
}
|
|
1127
|
+
return result;
|
|
1128
|
+
}
|
|
1129
|
+
return "";
|
|
1130
|
+
};
|
|
1131
|
+
|
|
1132
|
+
//#endregion
|
|
1133
|
+
//#region src/routes/messages/responses-stream-translation.ts
|
|
1134
|
+
const MAX_CONSECUTIVE_FUNCTION_CALL_WHITESPACE = 20;
|
|
1135
|
+
var FunctionCallArgumentsValidationError = class extends Error {
|
|
1136
|
+
constructor(message) {
|
|
1137
|
+
super(message);
|
|
1138
|
+
this.name = "FunctionCallArgumentsValidationError";
|
|
1139
|
+
}
|
|
1140
|
+
};
|
|
1141
|
+
const updateWhitespaceRunState = (previousCount, chunk) => {
|
|
1142
|
+
let count = previousCount;
|
|
1143
|
+
for (const char of chunk) {
|
|
1144
|
+
if (char === "\r" || char === "\n" || char === " ") {
|
|
1145
|
+
count += 1;
|
|
1146
|
+
if (count > MAX_CONSECUTIVE_FUNCTION_CALL_WHITESPACE) return {
|
|
1147
|
+
nextCount: count,
|
|
1148
|
+
exceeded: true
|
|
1149
|
+
};
|
|
1150
|
+
continue;
|
|
1151
|
+
}
|
|
1152
|
+
if (char !== " ") count = 0;
|
|
1153
|
+
}
|
|
1154
|
+
return {
|
|
1155
|
+
nextCount: count,
|
|
1156
|
+
exceeded: false
|
|
1157
|
+
};
|
|
1158
|
+
};
|
|
1159
|
+
const createResponsesStreamState = () => ({
|
|
1160
|
+
messageStartSent: false,
|
|
1161
|
+
messageCompleted: false,
|
|
1162
|
+
nextContentBlockIndex: 0,
|
|
1163
|
+
blockIndexByKey: /* @__PURE__ */ new Map(),
|
|
1164
|
+
openBlocks: /* @__PURE__ */ new Set(),
|
|
1165
|
+
blockHasDelta: /* @__PURE__ */ new Set(),
|
|
1166
|
+
functionCallStateByOutputIndex: /* @__PURE__ */ new Map()
|
|
1167
|
+
});
|
|
1168
|
+
const translateResponsesStreamEvent = (rawEvent, state$1) => {
|
|
1169
|
+
switch (rawEvent.type) {
|
|
1170
|
+
case "response.created": return handleResponseCreated(rawEvent, state$1);
|
|
1171
|
+
case "response.output_item.added": return handleOutputItemAdded$1(rawEvent, state$1);
|
|
1172
|
+
case "response.reasoning_summary_text.delta": return handleReasoningSummaryTextDelta(rawEvent, state$1);
|
|
1173
|
+
case "response.output_text.delta": return handleOutputTextDelta(rawEvent, state$1);
|
|
1174
|
+
case "response.reasoning_summary_text.done": return handleReasoningSummaryTextDone(rawEvent, state$1);
|
|
1175
|
+
case "response.output_text.done": return handleOutputTextDone(rawEvent, state$1);
|
|
1176
|
+
case "response.output_item.done": return handleOutputItemDone$1(rawEvent, state$1);
|
|
1177
|
+
case "response.function_call_arguments.delta": return handleFunctionCallArgumentsDelta(rawEvent, state$1);
|
|
1178
|
+
case "response.function_call_arguments.done": return handleFunctionCallArgumentsDone(rawEvent, state$1);
|
|
1179
|
+
case "response.completed":
|
|
1180
|
+
case "response.incomplete": return handleResponseCompleted(rawEvent, state$1);
|
|
1181
|
+
case "response.failed": return handleResponseFailed(rawEvent, state$1);
|
|
1182
|
+
case "error": return handleErrorEvent(rawEvent, state$1);
|
|
1183
|
+
default: return [];
|
|
1184
|
+
}
|
|
1185
|
+
};
|
|
1186
|
+
const handleResponseCreated = (rawEvent, state$1) => {
|
|
1187
|
+
return messageStart(state$1, rawEvent.response);
|
|
1188
|
+
};
|
|
1189
|
+
const handleOutputItemAdded$1 = (rawEvent, state$1) => {
|
|
1190
|
+
const events$1 = new Array();
|
|
1191
|
+
const functionCallDetails = extractFunctionCallDetails(rawEvent);
|
|
1192
|
+
if (!functionCallDetails) return events$1;
|
|
1193
|
+
const { outputIndex, toolCallId, name, initialArguments } = functionCallDetails;
|
|
1194
|
+
const blockIndex = openFunctionCallBlock(state$1, {
|
|
1195
|
+
outputIndex,
|
|
1196
|
+
toolCallId,
|
|
1197
|
+
name,
|
|
1198
|
+
events: events$1
|
|
1199
|
+
});
|
|
1200
|
+
if (initialArguments !== void 0 && initialArguments.length > 0) {
|
|
1201
|
+
events$1.push({
|
|
1202
|
+
type: "content_block_delta",
|
|
1203
|
+
index: blockIndex,
|
|
1204
|
+
delta: {
|
|
1205
|
+
type: "input_json_delta",
|
|
1206
|
+
partial_json: initialArguments
|
|
1207
|
+
}
|
|
1208
|
+
});
|
|
1209
|
+
state$1.blockHasDelta.add(blockIndex);
|
|
1210
|
+
}
|
|
1211
|
+
return events$1;
|
|
1212
|
+
};
|
|
1213
|
+
const handleOutputItemDone$1 = (rawEvent, state$1) => {
|
|
1214
|
+
const events$1 = new Array();
|
|
1215
|
+
const item = rawEvent.item;
|
|
1216
|
+
if (item.type !== "reasoning") return events$1;
|
|
1217
|
+
const outputIndex = rawEvent.output_index;
|
|
1218
|
+
const blockIndex = openThinkingBlockIfNeeded(state$1, outputIndex, events$1);
|
|
1219
|
+
const signature = (item.encrypted_content ?? "") + "@" + item.id;
|
|
1220
|
+
if (signature) {
|
|
1221
|
+
if (!item.summary || item.summary.length === 0) events$1.push({
|
|
1222
|
+
type: "content_block_delta",
|
|
1223
|
+
index: blockIndex,
|
|
1224
|
+
delta: {
|
|
1225
|
+
type: "thinking_delta",
|
|
1226
|
+
thinking: THINKING_TEXT$1
|
|
1227
|
+
}
|
|
1228
|
+
});
|
|
1229
|
+
events$1.push({
|
|
1230
|
+
type: "content_block_delta",
|
|
1231
|
+
index: blockIndex,
|
|
1232
|
+
delta: {
|
|
1233
|
+
type: "signature_delta",
|
|
1234
|
+
signature
|
|
1235
|
+
}
|
|
1236
|
+
});
|
|
1237
|
+
state$1.blockHasDelta.add(blockIndex);
|
|
1238
|
+
}
|
|
1239
|
+
return events$1;
|
|
1240
|
+
};
|
|
1241
|
+
const handleFunctionCallArgumentsDelta = (rawEvent, state$1) => {
|
|
1242
|
+
const events$1 = new Array();
|
|
1243
|
+
const outputIndex = rawEvent.output_index;
|
|
1244
|
+
const deltaText = rawEvent.delta;
|
|
1245
|
+
if (!deltaText) return events$1;
|
|
1246
|
+
const blockIndex = openFunctionCallBlock(state$1, {
|
|
1247
|
+
outputIndex,
|
|
1248
|
+
events: events$1
|
|
1249
|
+
});
|
|
1250
|
+
const functionCallState = state$1.functionCallStateByOutputIndex.get(outputIndex);
|
|
1251
|
+
if (!functionCallState) return handleFunctionCallArgumentsValidationError(new FunctionCallArgumentsValidationError("Received function call arguments delta without an open tool call block."), state$1, events$1);
|
|
1252
|
+
const { nextCount, exceeded } = updateWhitespaceRunState(functionCallState.consecutiveWhitespaceCount, deltaText);
|
|
1253
|
+
if (exceeded) return handleFunctionCallArgumentsValidationError(new FunctionCallArgumentsValidationError("Received function call arguments delta containing more than 20 consecutive whitespace characters."), state$1, events$1);
|
|
1254
|
+
functionCallState.consecutiveWhitespaceCount = nextCount;
|
|
1255
|
+
events$1.push({
|
|
1256
|
+
type: "content_block_delta",
|
|
1257
|
+
index: blockIndex,
|
|
1258
|
+
delta: {
|
|
1259
|
+
type: "input_json_delta",
|
|
1260
|
+
partial_json: deltaText
|
|
1261
|
+
}
|
|
1262
|
+
});
|
|
1263
|
+
state$1.blockHasDelta.add(blockIndex);
|
|
1264
|
+
return events$1;
|
|
1265
|
+
};
|
|
1266
|
+
const handleFunctionCallArgumentsDone = (rawEvent, state$1) => {
|
|
1267
|
+
const events$1 = new Array();
|
|
1268
|
+
const outputIndex = rawEvent.output_index;
|
|
1269
|
+
const blockIndex = openFunctionCallBlock(state$1, {
|
|
1270
|
+
outputIndex,
|
|
1271
|
+
events: events$1
|
|
1272
|
+
});
|
|
1273
|
+
const finalArguments = typeof rawEvent.arguments === "string" ? rawEvent.arguments : void 0;
|
|
1274
|
+
if (!state$1.blockHasDelta.has(blockIndex) && finalArguments) {
|
|
1275
|
+
events$1.push({
|
|
1276
|
+
type: "content_block_delta",
|
|
1277
|
+
index: blockIndex,
|
|
1278
|
+
delta: {
|
|
1279
|
+
type: "input_json_delta",
|
|
1280
|
+
partial_json: finalArguments
|
|
1281
|
+
}
|
|
1282
|
+
});
|
|
1283
|
+
state$1.blockHasDelta.add(blockIndex);
|
|
1284
|
+
}
|
|
1285
|
+
state$1.functionCallStateByOutputIndex.delete(outputIndex);
|
|
1286
|
+
return events$1;
|
|
1287
|
+
};
|
|
1288
|
+
const handleOutputTextDelta = (rawEvent, state$1) => {
|
|
1289
|
+
const events$1 = new Array();
|
|
1290
|
+
const outputIndex = rawEvent.output_index;
|
|
1291
|
+
const contentIndex = rawEvent.content_index;
|
|
1292
|
+
const deltaText = rawEvent.delta;
|
|
1293
|
+
if (!deltaText) return events$1;
|
|
1294
|
+
const blockIndex = openTextBlockIfNeeded(state$1, {
|
|
1295
|
+
outputIndex,
|
|
1296
|
+
contentIndex,
|
|
1297
|
+
events: events$1
|
|
1298
|
+
});
|
|
1299
|
+
events$1.push({
|
|
1300
|
+
type: "content_block_delta",
|
|
1301
|
+
index: blockIndex,
|
|
1302
|
+
delta: {
|
|
1303
|
+
type: "text_delta",
|
|
1304
|
+
text: deltaText
|
|
1305
|
+
}
|
|
1306
|
+
});
|
|
1307
|
+
state$1.blockHasDelta.add(blockIndex);
|
|
1308
|
+
return events$1;
|
|
1309
|
+
};
|
|
1310
|
+
const handleReasoningSummaryTextDelta = (rawEvent, state$1) => {
|
|
1311
|
+
const outputIndex = rawEvent.output_index;
|
|
1312
|
+
const deltaText = rawEvent.delta;
|
|
1313
|
+
const events$1 = new Array();
|
|
1314
|
+
const blockIndex = openThinkingBlockIfNeeded(state$1, outputIndex, events$1);
|
|
1315
|
+
events$1.push({
|
|
1316
|
+
type: "content_block_delta",
|
|
1317
|
+
index: blockIndex,
|
|
1318
|
+
delta: {
|
|
1319
|
+
type: "thinking_delta",
|
|
1320
|
+
thinking: deltaText
|
|
1321
|
+
}
|
|
1322
|
+
});
|
|
1323
|
+
state$1.blockHasDelta.add(blockIndex);
|
|
1324
|
+
return events$1;
|
|
1325
|
+
};
|
|
1326
|
+
const handleReasoningSummaryTextDone = (rawEvent, state$1) => {
|
|
1327
|
+
const outputIndex = rawEvent.output_index;
|
|
1328
|
+
const text = rawEvent.text;
|
|
1329
|
+
const events$1 = new Array();
|
|
1330
|
+
const blockIndex = openThinkingBlockIfNeeded(state$1, outputIndex, events$1);
|
|
1331
|
+
if (text && !state$1.blockHasDelta.has(blockIndex)) events$1.push({
|
|
1332
|
+
type: "content_block_delta",
|
|
1333
|
+
index: blockIndex,
|
|
1334
|
+
delta: {
|
|
1335
|
+
type: "thinking_delta",
|
|
1336
|
+
thinking: text
|
|
1337
|
+
}
|
|
1338
|
+
});
|
|
1339
|
+
return events$1;
|
|
1340
|
+
};
|
|
1341
|
+
const handleOutputTextDone = (rawEvent, state$1) => {
|
|
1342
|
+
const events$1 = new Array();
|
|
1343
|
+
const outputIndex = rawEvent.output_index;
|
|
1344
|
+
const contentIndex = rawEvent.content_index;
|
|
1345
|
+
const text = rawEvent.text;
|
|
1346
|
+
const blockIndex = openTextBlockIfNeeded(state$1, {
|
|
1347
|
+
outputIndex,
|
|
1348
|
+
contentIndex,
|
|
1349
|
+
events: events$1
|
|
1350
|
+
});
|
|
1351
|
+
if (text && !state$1.blockHasDelta.has(blockIndex)) events$1.push({
|
|
1352
|
+
type: "content_block_delta",
|
|
1353
|
+
index: blockIndex,
|
|
1354
|
+
delta: {
|
|
1355
|
+
type: "text_delta",
|
|
1356
|
+
text
|
|
1357
|
+
}
|
|
1358
|
+
});
|
|
1359
|
+
return events$1;
|
|
1360
|
+
};
|
|
1361
|
+
const handleResponseCompleted = (rawEvent, state$1) => {
|
|
1362
|
+
const response = rawEvent.response;
|
|
1363
|
+
const events$1 = new Array();
|
|
1364
|
+
closeAllOpenBlocks(state$1, events$1);
|
|
1365
|
+
const anthropic = translateResponsesResultToAnthropic(response);
|
|
1366
|
+
events$1.push({
|
|
1367
|
+
type: "message_delta",
|
|
1368
|
+
delta: {
|
|
1369
|
+
stop_reason: anthropic.stop_reason,
|
|
1370
|
+
stop_sequence: anthropic.stop_sequence
|
|
1371
|
+
},
|
|
1372
|
+
usage: anthropic.usage
|
|
1373
|
+
}, { type: "message_stop" });
|
|
1374
|
+
state$1.messageCompleted = true;
|
|
1375
|
+
return events$1;
|
|
1376
|
+
};
|
|
1377
|
+
const handleResponseFailed = (rawEvent, state$1) => {
|
|
1378
|
+
const response = rawEvent.response;
|
|
1379
|
+
const events$1 = new Array();
|
|
1380
|
+
closeAllOpenBlocks(state$1, events$1);
|
|
1381
|
+
const message = response.error?.message ?? "The response failed due to an unknown error.";
|
|
1382
|
+
events$1.push(buildErrorEvent(message));
|
|
1383
|
+
state$1.messageCompleted = true;
|
|
1384
|
+
return events$1;
|
|
1385
|
+
};
|
|
1386
|
+
const handleErrorEvent = (rawEvent, state$1) => {
|
|
1387
|
+
const message = typeof rawEvent.message === "string" ? rawEvent.message : "An unexpected error occurred during streaming.";
|
|
1388
|
+
state$1.messageCompleted = true;
|
|
1389
|
+
return [buildErrorEvent(message)];
|
|
1390
|
+
};
|
|
1391
|
+
const handleFunctionCallArgumentsValidationError = (error, state$1, events$1 = []) => {
|
|
1392
|
+
const reason = error.message;
|
|
1393
|
+
closeAllOpenBlocks(state$1, events$1);
|
|
1394
|
+
state$1.messageCompleted = true;
|
|
1395
|
+
events$1.push(buildErrorEvent(reason));
|
|
1396
|
+
return events$1;
|
|
1397
|
+
};
|
|
1398
|
+
const messageStart = (state$1, response) => {
|
|
1399
|
+
state$1.messageStartSent = true;
|
|
1400
|
+
const inputCachedTokens = response.usage?.input_tokens_details?.cached_tokens;
|
|
1401
|
+
const inputTokens = (response.usage?.input_tokens ?? 0) - (inputCachedTokens ?? 0);
|
|
1402
|
+
return [{
|
|
1403
|
+
type: "message_start",
|
|
1404
|
+
message: {
|
|
1405
|
+
id: response.id,
|
|
1406
|
+
type: "message",
|
|
1407
|
+
role: "assistant",
|
|
1408
|
+
content: [],
|
|
1409
|
+
model: response.model,
|
|
1410
|
+
stop_reason: null,
|
|
1411
|
+
stop_sequence: null,
|
|
1412
|
+
usage: {
|
|
1413
|
+
input_tokens: inputTokens,
|
|
1414
|
+
output_tokens: 0,
|
|
1415
|
+
cache_read_input_tokens: inputCachedTokens ?? 0
|
|
1416
|
+
}
|
|
1417
|
+
}
|
|
1418
|
+
}];
|
|
1419
|
+
};
|
|
1420
|
+
const openTextBlockIfNeeded = (state$1, params) => {
|
|
1421
|
+
const { outputIndex, contentIndex, events: events$1 } = params;
|
|
1422
|
+
const key = getBlockKey(outputIndex, contentIndex);
|
|
1423
|
+
let blockIndex = state$1.blockIndexByKey.get(key);
|
|
1424
|
+
if (blockIndex === void 0) {
|
|
1425
|
+
blockIndex = state$1.nextContentBlockIndex;
|
|
1426
|
+
state$1.nextContentBlockIndex += 1;
|
|
1427
|
+
state$1.blockIndexByKey.set(key, blockIndex);
|
|
1428
|
+
}
|
|
1429
|
+
if (!state$1.openBlocks.has(blockIndex)) {
|
|
1430
|
+
closeOpenBlocks(state$1, events$1);
|
|
1431
|
+
events$1.push({
|
|
1432
|
+
type: "content_block_start",
|
|
1433
|
+
index: blockIndex,
|
|
1434
|
+
content_block: {
|
|
1435
|
+
type: "text",
|
|
1436
|
+
text: ""
|
|
1437
|
+
}
|
|
1438
|
+
});
|
|
1439
|
+
state$1.openBlocks.add(blockIndex);
|
|
1440
|
+
}
|
|
1441
|
+
return blockIndex;
|
|
1442
|
+
};
|
|
1443
|
+
const openThinkingBlockIfNeeded = (state$1, outputIndex, events$1) => {
|
|
1444
|
+
const key = getBlockKey(outputIndex, 0);
|
|
1445
|
+
let blockIndex = state$1.blockIndexByKey.get(key);
|
|
1446
|
+
if (blockIndex === void 0) {
|
|
1447
|
+
blockIndex = state$1.nextContentBlockIndex;
|
|
1448
|
+
state$1.nextContentBlockIndex += 1;
|
|
1449
|
+
state$1.blockIndexByKey.set(key, blockIndex);
|
|
1450
|
+
}
|
|
1451
|
+
if (!state$1.openBlocks.has(blockIndex)) {
|
|
1452
|
+
closeOpenBlocks(state$1, events$1);
|
|
1453
|
+
events$1.push({
|
|
1454
|
+
type: "content_block_start",
|
|
1455
|
+
index: blockIndex,
|
|
1456
|
+
content_block: {
|
|
1457
|
+
type: "thinking",
|
|
1458
|
+
thinking: ""
|
|
1459
|
+
}
|
|
1460
|
+
});
|
|
1461
|
+
state$1.openBlocks.add(blockIndex);
|
|
1462
|
+
}
|
|
1463
|
+
return blockIndex;
|
|
1464
|
+
};
|
|
1465
|
+
const closeBlockIfOpen = (state$1, blockIndex, events$1) => {
|
|
1466
|
+
if (!state$1.openBlocks.has(blockIndex)) return;
|
|
1467
|
+
events$1.push({
|
|
1468
|
+
type: "content_block_stop",
|
|
1469
|
+
index: blockIndex
|
|
1470
|
+
});
|
|
1471
|
+
state$1.openBlocks.delete(blockIndex);
|
|
1472
|
+
state$1.blockHasDelta.delete(blockIndex);
|
|
1473
|
+
};
|
|
1474
|
+
const closeOpenBlocks = (state$1, events$1) => {
|
|
1475
|
+
for (const blockIndex of state$1.openBlocks) closeBlockIfOpen(state$1, blockIndex, events$1);
|
|
1476
|
+
};
|
|
1477
|
+
const closeAllOpenBlocks = (state$1, events$1) => {
|
|
1478
|
+
closeOpenBlocks(state$1, events$1);
|
|
1479
|
+
state$1.functionCallStateByOutputIndex.clear();
|
|
1480
|
+
};
|
|
1481
|
+
const buildErrorEvent = (message) => ({
|
|
1482
|
+
type: "error",
|
|
1483
|
+
error: {
|
|
1484
|
+
type: "api_error",
|
|
1485
|
+
message
|
|
1486
|
+
}
|
|
1487
|
+
});
|
|
1488
|
+
const getBlockKey = (outputIndex, contentIndex) => `${outputIndex}:${contentIndex}`;
|
|
1489
|
+
const openFunctionCallBlock = (state$1, params) => {
|
|
1490
|
+
const { outputIndex, toolCallId, name, events: events$1 } = params;
|
|
1491
|
+
let functionCallState = state$1.functionCallStateByOutputIndex.get(outputIndex);
|
|
1492
|
+
if (!functionCallState) {
|
|
1493
|
+
const blockIndex$1 = state$1.nextContentBlockIndex;
|
|
1494
|
+
state$1.nextContentBlockIndex += 1;
|
|
1495
|
+
const resolvedToolCallId = toolCallId ?? `tool_call_${blockIndex$1}`;
|
|
1496
|
+
functionCallState = {
|
|
1497
|
+
blockIndex: blockIndex$1,
|
|
1498
|
+
toolCallId: resolvedToolCallId,
|
|
1499
|
+
name: name ?? "function",
|
|
1500
|
+
consecutiveWhitespaceCount: 0
|
|
1501
|
+
};
|
|
1502
|
+
state$1.functionCallStateByOutputIndex.set(outputIndex, functionCallState);
|
|
1503
|
+
}
|
|
1504
|
+
const { blockIndex } = functionCallState;
|
|
1505
|
+
if (!state$1.openBlocks.has(blockIndex)) {
|
|
1506
|
+
closeOpenBlocks(state$1, events$1);
|
|
1507
|
+
events$1.push({
|
|
1508
|
+
type: "content_block_start",
|
|
1509
|
+
index: blockIndex,
|
|
1510
|
+
content_block: {
|
|
1511
|
+
type: "tool_use",
|
|
1512
|
+
id: functionCallState.toolCallId,
|
|
1513
|
+
name: functionCallState.name,
|
|
1514
|
+
input: {}
|
|
1515
|
+
}
|
|
1516
|
+
});
|
|
1517
|
+
state$1.openBlocks.add(blockIndex);
|
|
1518
|
+
}
|
|
1519
|
+
return blockIndex;
|
|
1520
|
+
};
|
|
1521
|
+
const extractFunctionCallDetails = (rawEvent) => {
|
|
1522
|
+
const item = rawEvent.item;
|
|
1523
|
+
if (item.type !== "function_call") return;
|
|
1524
|
+
const outputIndex = rawEvent.output_index;
|
|
1525
|
+
const toolCallId = item.call_id;
|
|
1526
|
+
const name = item.name;
|
|
1527
|
+
const initialArguments = item.arguments;
|
|
1528
|
+
return {
|
|
1529
|
+
outputIndex,
|
|
1530
|
+
toolCallId,
|
|
1531
|
+
name,
|
|
1532
|
+
initialArguments
|
|
1533
|
+
};
|
|
1534
|
+
};
|
|
1535
|
+
|
|
1536
|
+
//#endregion
|
|
1537
|
+
//#region src/routes/responses/utils.ts
|
|
1538
|
+
const getResponsesRequestOptions = (payload) => {
|
|
1539
|
+
const vision = hasVisionInput(payload);
|
|
1540
|
+
const initiator = hasAgentInitiator(payload) ? "agent" : "user";
|
|
1541
|
+
return {
|
|
1542
|
+
vision,
|
|
1543
|
+
initiator
|
|
1544
|
+
};
|
|
1545
|
+
};
|
|
1546
|
+
const hasAgentInitiator = (payload) => {
|
|
1547
|
+
const lastItem = getPayloadItems(payload).at(-1);
|
|
1548
|
+
if (!lastItem) return false;
|
|
1549
|
+
if (!("role" in lastItem) || !lastItem.role) return true;
|
|
1550
|
+
return (typeof lastItem.role === "string" ? lastItem.role.toLowerCase() : "") === "assistant";
|
|
1551
|
+
};
|
|
1552
|
+
const hasVisionInput = (payload) => {
|
|
1553
|
+
return getPayloadItems(payload).some((item) => containsVisionContent(item));
|
|
1554
|
+
};
|
|
1555
|
+
const getPayloadItems = (payload) => {
|
|
1556
|
+
const result = [];
|
|
1557
|
+
const { input } = payload;
|
|
1558
|
+
if (Array.isArray(input)) result.push(...input);
|
|
1559
|
+
return result;
|
|
1560
|
+
};
|
|
1561
|
+
const containsVisionContent = (value) => {
|
|
1562
|
+
if (!value) return false;
|
|
1563
|
+
if (Array.isArray(value)) return value.some((entry) => containsVisionContent(entry));
|
|
1564
|
+
if (typeof value !== "object") return false;
|
|
1565
|
+
const record = value;
|
|
1566
|
+
if ((typeof record.type === "string" ? record.type.toLowerCase() : void 0) === "input_image") return true;
|
|
1567
|
+
if (Array.isArray(record.content)) return record.content.some((entry) => containsVisionContent(entry));
|
|
1568
|
+
return false;
|
|
1569
|
+
};
|
|
1570
|
+
|
|
1571
|
+
//#endregion
|
|
1572
|
+
//#region src/services/copilot/create-messages.ts
|
|
1573
|
+
const createMessages = async (payload, anthropicBetaHeader) => {
|
|
1574
|
+
if (!state.copilotToken) throw new Error("Copilot token not found");
|
|
1575
|
+
const enableVision = payload.messages.some((message) => Array.isArray(message.content) && message.content.some((block) => block.type === "image"));
|
|
1576
|
+
let isInitiateRequest = false;
|
|
1577
|
+
const lastMessage = payload.messages.at(-1);
|
|
1578
|
+
if (lastMessage?.role === "user") isInitiateRequest = Array.isArray(lastMessage.content) ? lastMessage.content.some((block) => block.type !== "tool_result") : true;
|
|
1579
|
+
const headers = {
|
|
1580
|
+
...copilotHeaders(state, enableVision),
|
|
1581
|
+
"X-Initiator": isInitiateRequest ? "user" : "agent"
|
|
1582
|
+
};
|
|
1583
|
+
if (anthropicBetaHeader) headers["anthropic-beta"] = anthropicBetaHeader;
|
|
1584
|
+
else if (payload.thinking?.budget_tokens) headers["anthropic-beta"] = "interleaved-thinking-2025-05-14";
|
|
1585
|
+
const response = await fetch(`${copilotBaseUrl(state)}/v1/messages`, {
|
|
1586
|
+
method: "POST",
|
|
1587
|
+
headers,
|
|
1588
|
+
body: JSON.stringify(payload)
|
|
1589
|
+
});
|
|
1590
|
+
if (!response.ok) {
|
|
1591
|
+
consola.error("Failed to create messages", response);
|
|
1592
|
+
throw new HTTPError("Failed to create messages", response);
|
|
1593
|
+
}
|
|
1594
|
+
if (payload.stream) return events(response);
|
|
1595
|
+
return await response.json();
|
|
1596
|
+
};
|
|
1597
|
+
|
|
1598
|
+
//#endregion
|
|
1599
|
+
//#region src/routes/messages/stream-translation.ts
|
|
1600
|
+
function isToolBlockOpen(state$1) {
|
|
1601
|
+
if (!state$1.contentBlockOpen) return false;
|
|
1602
|
+
return Object.values(state$1.toolCalls).some((tc) => tc.anthropicBlockIndex === state$1.contentBlockIndex);
|
|
1603
|
+
}
|
|
1604
|
+
function translateChunkToAnthropicEvents(chunk, state$1) {
|
|
1605
|
+
const events$1 = [];
|
|
1606
|
+
if (chunk.choices.length === 0) return events$1;
|
|
1607
|
+
const choice = chunk.choices[0];
|
|
1608
|
+
const { delta } = choice;
|
|
1609
|
+
handleMessageStart(state$1, events$1, chunk);
|
|
1610
|
+
handleThinkingText(delta, state$1, events$1);
|
|
1611
|
+
handleContent(delta, state$1, events$1);
|
|
1612
|
+
handleToolCalls(delta, state$1, events$1);
|
|
1613
|
+
handleFinish(choice, state$1, {
|
|
1614
|
+
events: events$1,
|
|
1615
|
+
chunk
|
|
1616
|
+
});
|
|
1617
|
+
return events$1;
|
|
1618
|
+
}
|
|
1619
|
+
function handleFinish(choice, state$1, context) {
|
|
1620
|
+
const { events: events$1, chunk } = context;
|
|
1621
|
+
if (choice.finish_reason && choice.finish_reason.length > 0) {
|
|
1622
|
+
if (state$1.contentBlockOpen) {
|
|
1623
|
+
const toolBlockOpen = isToolBlockOpen(state$1);
|
|
1624
|
+
context.events.push({
|
|
1625
|
+
type: "content_block_stop",
|
|
1626
|
+
index: state$1.contentBlockIndex
|
|
1627
|
+
});
|
|
1628
|
+
state$1.contentBlockOpen = false;
|
|
1629
|
+
state$1.contentBlockIndex++;
|
|
1630
|
+
if (!toolBlockOpen) handleReasoningOpaque(choice.delta, events$1, state$1);
|
|
1631
|
+
}
|
|
1632
|
+
events$1.push({
|
|
1633
|
+
type: "message_delta",
|
|
1634
|
+
delta: {
|
|
1635
|
+
stop_reason: mapOpenAIStopReasonToAnthropic(choice.finish_reason),
|
|
1636
|
+
stop_sequence: null
|
|
1637
|
+
},
|
|
1638
|
+
usage: {
|
|
1639
|
+
input_tokens: (chunk.usage?.prompt_tokens ?? 0) - (chunk.usage?.prompt_tokens_details?.cached_tokens ?? 0),
|
|
1640
|
+
output_tokens: chunk.usage?.completion_tokens ?? 0,
|
|
1641
|
+
...chunk.usage?.prompt_tokens_details?.cached_tokens !== void 0 && { cache_read_input_tokens: chunk.usage.prompt_tokens_details.cached_tokens }
|
|
1642
|
+
}
|
|
1643
|
+
}, { type: "message_stop" });
|
|
1644
|
+
}
|
|
1645
|
+
}
|
|
1646
|
+
function handleToolCalls(delta, state$1, events$1) {
|
|
1647
|
+
if (delta.tool_calls && delta.tool_calls.length > 0) {
|
|
1648
|
+
closeThinkingBlockIfOpen(state$1, events$1);
|
|
1649
|
+
handleReasoningOpaqueInToolCalls(state$1, events$1, delta);
|
|
1650
|
+
for (const toolCall of delta.tool_calls) {
|
|
1651
|
+
if (toolCall.id && toolCall.function?.name) {
|
|
1652
|
+
if (state$1.contentBlockOpen) {
|
|
1653
|
+
events$1.push({
|
|
1654
|
+
type: "content_block_stop",
|
|
1655
|
+
index: state$1.contentBlockIndex
|
|
1656
|
+
});
|
|
1657
|
+
state$1.contentBlockIndex++;
|
|
1658
|
+
state$1.contentBlockOpen = false;
|
|
1659
|
+
}
|
|
1660
|
+
const anthropicBlockIndex = state$1.contentBlockIndex;
|
|
1661
|
+
state$1.toolCalls[toolCall.index] = {
|
|
1662
|
+
id: toolCall.id,
|
|
1663
|
+
name: toolCall.function.name,
|
|
1664
|
+
anthropicBlockIndex
|
|
1665
|
+
};
|
|
1666
|
+
events$1.push({
|
|
1667
|
+
type: "content_block_start",
|
|
1668
|
+
index: anthropicBlockIndex,
|
|
1669
|
+
content_block: {
|
|
1670
|
+
type: "tool_use",
|
|
1671
|
+
id: toolCall.id,
|
|
1672
|
+
name: toolCall.function.name,
|
|
1673
|
+
input: {}
|
|
1674
|
+
}
|
|
1675
|
+
});
|
|
1676
|
+
state$1.contentBlockOpen = true;
|
|
1677
|
+
}
|
|
1678
|
+
if (toolCall.function?.arguments) {
|
|
1679
|
+
const toolCallInfo = state$1.toolCalls[toolCall.index];
|
|
1680
|
+
if (toolCallInfo) events$1.push({
|
|
1681
|
+
type: "content_block_delta",
|
|
1682
|
+
index: toolCallInfo.anthropicBlockIndex,
|
|
1683
|
+
delta: {
|
|
1684
|
+
type: "input_json_delta",
|
|
1685
|
+
partial_json: toolCall.function.arguments
|
|
1686
|
+
}
|
|
1687
|
+
});
|
|
1688
|
+
}
|
|
1689
|
+
}
|
|
1690
|
+
}
|
|
1691
|
+
}
|
|
1692
|
+
function handleReasoningOpaqueInToolCalls(state$1, events$1, delta) {
|
|
1693
|
+
if (state$1.contentBlockOpen && !isToolBlockOpen(state$1)) {
|
|
1694
|
+
events$1.push({
|
|
1695
|
+
type: "content_block_stop",
|
|
1696
|
+
index: state$1.contentBlockIndex
|
|
1697
|
+
});
|
|
1698
|
+
state$1.contentBlockIndex++;
|
|
1699
|
+
state$1.contentBlockOpen = false;
|
|
1700
|
+
}
|
|
1701
|
+
handleReasoningOpaque(delta, events$1, state$1);
|
|
1702
|
+
}
|
|
1703
|
+
function handleContent(delta, state$1, events$1) {
|
|
1704
|
+
if (delta.content && delta.content.length > 0) {
|
|
1705
|
+
closeThinkingBlockIfOpen(state$1, events$1);
|
|
1706
|
+
if (isToolBlockOpen(state$1)) {
|
|
1707
|
+
events$1.push({
|
|
1708
|
+
type: "content_block_stop",
|
|
1709
|
+
index: state$1.contentBlockIndex
|
|
1710
|
+
});
|
|
1711
|
+
state$1.contentBlockIndex++;
|
|
1712
|
+
state$1.contentBlockOpen = false;
|
|
1713
|
+
}
|
|
1714
|
+
if (!state$1.contentBlockOpen) {
|
|
1715
|
+
events$1.push({
|
|
1716
|
+
type: "content_block_start",
|
|
1717
|
+
index: state$1.contentBlockIndex,
|
|
1718
|
+
content_block: {
|
|
1719
|
+
type: "text",
|
|
1720
|
+
text: ""
|
|
1721
|
+
}
|
|
1722
|
+
});
|
|
1723
|
+
state$1.contentBlockOpen = true;
|
|
1724
|
+
}
|
|
1725
|
+
events$1.push({
|
|
1726
|
+
type: "content_block_delta",
|
|
1727
|
+
index: state$1.contentBlockIndex,
|
|
1728
|
+
delta: {
|
|
1729
|
+
type: "text_delta",
|
|
1730
|
+
text: delta.content
|
|
1731
|
+
}
|
|
1732
|
+
});
|
|
1733
|
+
}
|
|
1734
|
+
if (delta.content === "" && delta.reasoning_opaque && delta.reasoning_opaque.length > 0 && state$1.thinkingBlockOpen) {
|
|
1735
|
+
events$1.push({
|
|
1736
|
+
type: "content_block_delta",
|
|
1737
|
+
index: state$1.contentBlockIndex,
|
|
1738
|
+
delta: {
|
|
1739
|
+
type: "signature_delta",
|
|
1740
|
+
signature: delta.reasoning_opaque
|
|
1741
|
+
}
|
|
1742
|
+
}, {
|
|
1743
|
+
type: "content_block_stop",
|
|
1744
|
+
index: state$1.contentBlockIndex
|
|
1745
|
+
});
|
|
1746
|
+
state$1.contentBlockIndex++;
|
|
1747
|
+
state$1.thinkingBlockOpen = false;
|
|
1748
|
+
}
|
|
1749
|
+
}
|
|
1750
|
+
function handleMessageStart(state$1, events$1, chunk) {
|
|
1751
|
+
if (!state$1.messageStartSent) {
|
|
1752
|
+
events$1.push({
|
|
1753
|
+
type: "message_start",
|
|
1754
|
+
message: {
|
|
1755
|
+
id: chunk.id,
|
|
1756
|
+
type: "message",
|
|
1757
|
+
role: "assistant",
|
|
1758
|
+
content: [],
|
|
1759
|
+
model: chunk.model,
|
|
1760
|
+
stop_reason: null,
|
|
1761
|
+
stop_sequence: null,
|
|
1762
|
+
usage: {
|
|
1763
|
+
input_tokens: (chunk.usage?.prompt_tokens ?? 0) - (chunk.usage?.prompt_tokens_details?.cached_tokens ?? 0),
|
|
1764
|
+
output_tokens: 0,
|
|
1765
|
+
...chunk.usage?.prompt_tokens_details?.cached_tokens !== void 0 && { cache_read_input_tokens: chunk.usage.prompt_tokens_details.cached_tokens }
|
|
1766
|
+
}
|
|
1767
|
+
}
|
|
1768
|
+
});
|
|
1769
|
+
state$1.messageStartSent = true;
|
|
1770
|
+
}
|
|
1771
|
+
}
|
|
1772
|
+
function handleReasoningOpaque(delta, events$1, state$1) {
|
|
1773
|
+
if (delta.reasoning_opaque && delta.reasoning_opaque.length > 0) {
|
|
1774
|
+
events$1.push({
|
|
1775
|
+
type: "content_block_start",
|
|
1776
|
+
index: state$1.contentBlockIndex,
|
|
1777
|
+
content_block: {
|
|
1778
|
+
type: "thinking",
|
|
1779
|
+
thinking: ""
|
|
1780
|
+
}
|
|
1781
|
+
}, {
|
|
1782
|
+
type: "content_block_delta",
|
|
1783
|
+
index: state$1.contentBlockIndex,
|
|
1784
|
+
delta: {
|
|
1785
|
+
type: "thinking_delta",
|
|
1786
|
+
thinking: THINKING_TEXT
|
|
1787
|
+
}
|
|
1788
|
+
}, {
|
|
1789
|
+
type: "content_block_delta",
|
|
1790
|
+
index: state$1.contentBlockIndex,
|
|
1791
|
+
delta: {
|
|
1792
|
+
type: "signature_delta",
|
|
1793
|
+
signature: delta.reasoning_opaque
|
|
1794
|
+
}
|
|
1795
|
+
}, {
|
|
1796
|
+
type: "content_block_stop",
|
|
1797
|
+
index: state$1.contentBlockIndex
|
|
1798
|
+
});
|
|
1799
|
+
state$1.contentBlockIndex++;
|
|
1800
|
+
}
|
|
1801
|
+
}
|
|
1802
|
+
function handleThinkingText(delta, state$1, events$1) {
|
|
1803
|
+
if (delta.reasoning_text && delta.reasoning_text.length > 0) {
|
|
1804
|
+
if (state$1.contentBlockOpen) {
|
|
1805
|
+
delta.content = delta.reasoning_text;
|
|
1806
|
+
delta.reasoning_text = void 0;
|
|
1807
|
+
return;
|
|
1808
|
+
}
|
|
1809
|
+
if (!state$1.thinkingBlockOpen) {
|
|
1810
|
+
events$1.push({
|
|
1811
|
+
type: "content_block_start",
|
|
1812
|
+
index: state$1.contentBlockIndex,
|
|
1813
|
+
content_block: {
|
|
1814
|
+
type: "thinking",
|
|
1815
|
+
thinking: ""
|
|
1816
|
+
}
|
|
1817
|
+
});
|
|
1818
|
+
state$1.thinkingBlockOpen = true;
|
|
1819
|
+
}
|
|
1820
|
+
events$1.push({
|
|
1821
|
+
type: "content_block_delta",
|
|
1822
|
+
index: state$1.contentBlockIndex,
|
|
1823
|
+
delta: {
|
|
1824
|
+
type: "thinking_delta",
|
|
1825
|
+
thinking: delta.reasoning_text
|
|
1826
|
+
}
|
|
1827
|
+
});
|
|
1828
|
+
}
|
|
1829
|
+
}
|
|
1830
|
+
function closeThinkingBlockIfOpen(state$1, events$1) {
|
|
1831
|
+
if (state$1.thinkingBlockOpen) {
|
|
1832
|
+
events$1.push({
|
|
1833
|
+
type: "content_block_delta",
|
|
1834
|
+
index: state$1.contentBlockIndex,
|
|
1835
|
+
delta: {
|
|
1836
|
+
type: "signature_delta",
|
|
1837
|
+
signature: ""
|
|
1838
|
+
}
|
|
1839
|
+
}, {
|
|
1840
|
+
type: "content_block_stop",
|
|
1841
|
+
index: state$1.contentBlockIndex
|
|
1842
|
+
});
|
|
1843
|
+
state$1.contentBlockIndex++;
|
|
1844
|
+
state$1.thinkingBlockOpen = false;
|
|
1845
|
+
}
|
|
1846
|
+
}
|
|
1847
|
+
|
|
1848
|
+
//#endregion
|
|
1849
|
+
//#region src/routes/messages/handler.ts
|
|
1850
|
+
const logger$2 = createHandlerLogger("messages-handler");
|
|
1851
|
+
const compactSystemPromptStart = "You are a helpful AI assistant tasked with summarizing conversations";
|
|
1852
|
+
async function handleCompletion(c) {
|
|
1853
|
+
await checkRateLimit(state);
|
|
1854
|
+
const anthropicPayload = await c.req.json();
|
|
1855
|
+
logger$2.debug("Anthropic request payload:", JSON.stringify(anthropicPayload));
|
|
1856
|
+
const isCompact = isCompactRequest(anthropicPayload);
|
|
1857
|
+
const anthropicBeta = c.req.header("anthropic-beta");
|
|
1858
|
+
logger$2.debug("Anthropic Beta header:", anthropicBeta);
|
|
1859
|
+
const noTools = !anthropicPayload.tools || anthropicPayload.tools.length === 0;
|
|
1860
|
+
if (anthropicBeta && noTools && !isCompact) anthropicPayload.model = getSmallModel();
|
|
1861
|
+
if (isCompact) {
|
|
1862
|
+
logger$2.debug("Is compact request:", isCompact);
|
|
1863
|
+
if (shouldCompactUseSmallModel()) anthropicPayload.model = getSmallModel();
|
|
1864
|
+
} else mergeToolResultForClaude(anthropicPayload);
|
|
1865
|
+
if (state.manualApprove) await awaitApproval();
|
|
1866
|
+
if (shouldUseMessagesApi(anthropicPayload.model)) return await handleWithMessagesApi(c, anthropicPayload, anthropicBeta);
|
|
1867
|
+
if (shouldUseResponsesApi(anthropicPayload.model)) return await handleWithResponsesApi(c, anthropicPayload);
|
|
1868
|
+
return await handleWithChatCompletions(c, anthropicPayload);
|
|
1869
|
+
}
|
|
1870
|
+
const RESPONSES_ENDPOINT$1 = "/responses";
|
|
1871
|
+
const MESSAGES_ENDPOINT = "/v1/messages";
|
|
1872
|
+
const handleWithChatCompletions = async (c, anthropicPayload) => {
|
|
1873
|
+
const openAIPayload = translateToOpenAI(anthropicPayload);
|
|
1874
|
+
logger$2.debug("Translated OpenAI request payload:", JSON.stringify(openAIPayload));
|
|
1875
|
+
const response = await createChatCompletions(openAIPayload);
|
|
1876
|
+
if (isNonStreaming(response)) {
|
|
1877
|
+
logger$2.debug("Non-streaming response from Copilot:", JSON.stringify(response));
|
|
1878
|
+
const anthropicResponse = translateToAnthropic(response);
|
|
1879
|
+
logger$2.debug("Translated Anthropic response:", JSON.stringify(anthropicResponse));
|
|
1880
|
+
return c.json(anthropicResponse);
|
|
1881
|
+
}
|
|
1882
|
+
logger$2.debug("Streaming response from Copilot");
|
|
1883
|
+
return streamSSE(c, async (stream) => {
|
|
1884
|
+
const streamState = {
|
|
1885
|
+
messageStartSent: false,
|
|
1886
|
+
contentBlockIndex: 0,
|
|
1887
|
+
contentBlockOpen: false,
|
|
1888
|
+
toolCalls: {},
|
|
1889
|
+
thinkingBlockOpen: false
|
|
1890
|
+
};
|
|
1891
|
+
for await (const rawEvent of response) {
|
|
1892
|
+
logger$2.debug("Copilot raw stream event:", JSON.stringify(rawEvent));
|
|
1893
|
+
if (rawEvent.data === "[DONE]") break;
|
|
1894
|
+
if (!rawEvent.data) continue;
|
|
1895
|
+
const chunk = JSON.parse(rawEvent.data);
|
|
1896
|
+
const events$1 = translateChunkToAnthropicEvents(chunk, streamState);
|
|
1897
|
+
for (const event of events$1) {
|
|
1898
|
+
logger$2.debug("Translated Anthropic event:", JSON.stringify(event));
|
|
1899
|
+
await stream.writeSSE({
|
|
1900
|
+
event: event.type,
|
|
1901
|
+
data: JSON.stringify(event)
|
|
1902
|
+
});
|
|
1903
|
+
}
|
|
1904
|
+
}
|
|
1905
|
+
});
|
|
1906
|
+
};
|
|
1907
|
+
const handleWithResponsesApi = async (c, anthropicPayload) => {
|
|
1908
|
+
const responsesPayload = translateAnthropicMessagesToResponsesPayload(anthropicPayload);
|
|
1909
|
+
logger$2.debug("Translated Responses payload:", JSON.stringify(responsesPayload));
|
|
1910
|
+
const { vision, initiator } = getResponsesRequestOptions(responsesPayload);
|
|
1911
|
+
const response = await createResponses(responsesPayload, {
|
|
1912
|
+
vision,
|
|
1913
|
+
initiator
|
|
1914
|
+
});
|
|
1915
|
+
if (responsesPayload.stream && isAsyncIterable$1(response)) {
|
|
1916
|
+
logger$2.debug("Streaming response from Copilot (Responses API)");
|
|
1917
|
+
return streamSSE(c, async (stream) => {
|
|
1918
|
+
const streamState = createResponsesStreamState();
|
|
1919
|
+
for await (const chunk of response) {
|
|
1920
|
+
if (chunk.event === "ping") {
|
|
1921
|
+
await stream.writeSSE({
|
|
1922
|
+
event: "ping",
|
|
1923
|
+
data: ""
|
|
1924
|
+
});
|
|
1925
|
+
continue;
|
|
1926
|
+
}
|
|
1927
|
+
const data = chunk.data;
|
|
1928
|
+
if (!data) continue;
|
|
1929
|
+
logger$2.debug("Responses raw stream event:", data);
|
|
1930
|
+
const events$1 = translateResponsesStreamEvent(JSON.parse(data), streamState);
|
|
1931
|
+
for (const event of events$1) {
|
|
1932
|
+
const eventData = JSON.stringify(event);
|
|
1933
|
+
logger$2.debug("Translated Anthropic event:", eventData);
|
|
1934
|
+
await stream.writeSSE({
|
|
1935
|
+
event: event.type,
|
|
1936
|
+
data: eventData
|
|
1937
|
+
});
|
|
1938
|
+
}
|
|
1939
|
+
if (streamState.messageCompleted) {
|
|
1940
|
+
logger$2.debug("Message completed, ending stream");
|
|
1941
|
+
break;
|
|
1942
|
+
}
|
|
1943
|
+
}
|
|
1944
|
+
if (!streamState.messageCompleted) {
|
|
1945
|
+
logger$2.warn("Responses stream ended without completion; sending error event");
|
|
1946
|
+
const errorEvent = buildErrorEvent("Responses stream ended without completion");
|
|
1947
|
+
await stream.writeSSE({
|
|
1948
|
+
event: errorEvent.type,
|
|
1949
|
+
data: JSON.stringify(errorEvent)
|
|
1950
|
+
});
|
|
1951
|
+
}
|
|
1952
|
+
});
|
|
1953
|
+
}
|
|
1954
|
+
logger$2.debug("Non-streaming Responses result:", JSON.stringify(response).slice(-400));
|
|
1955
|
+
const anthropicResponse = translateResponsesResultToAnthropic(response);
|
|
1956
|
+
logger$2.debug("Translated Anthropic response:", JSON.stringify(anthropicResponse));
|
|
1957
|
+
return c.json(anthropicResponse);
|
|
1958
|
+
};
|
|
1959
|
+
const handleWithMessagesApi = async (c, anthropicPayload, anthropicBetaHeader) => {
|
|
1960
|
+
for (const msg of anthropicPayload.messages) if (msg.role === "assistant" && Array.isArray(msg.content)) msg.content = msg.content.filter((block) => {
|
|
1961
|
+
if (block.type !== "thinking") return true;
|
|
1962
|
+
return block.thinking && block.thinking !== "Thinking..." && block.signature && !block.signature.includes("@");
|
|
1963
|
+
});
|
|
1964
|
+
const response = await createMessages(anthropicPayload, anthropicBetaHeader);
|
|
1965
|
+
if (isAsyncIterable$1(response)) {
|
|
1966
|
+
logger$2.debug("Streaming response from Copilot (Messages API)");
|
|
1967
|
+
return streamSSE(c, async (stream) => {
|
|
1968
|
+
for await (const event of response) {
|
|
1969
|
+
const eventName = event.event;
|
|
1970
|
+
const data = event.data ?? "";
|
|
1971
|
+
logger$2.debug("Messages raw stream event:", data);
|
|
1972
|
+
await stream.writeSSE({
|
|
1973
|
+
event: eventName,
|
|
1974
|
+
data
|
|
1975
|
+
});
|
|
1976
|
+
}
|
|
1977
|
+
});
|
|
1978
|
+
}
|
|
1979
|
+
logger$2.debug("Non-streaming Messages result:", JSON.stringify(response).slice(-400));
|
|
1980
|
+
return c.json(response);
|
|
1981
|
+
};
|
|
1982
|
+
const shouldUseResponsesApi = (modelId) => {
|
|
1983
|
+
return (state.models?.data.find((model) => model.id === modelId))?.supported_endpoints?.includes(RESPONSES_ENDPOINT$1) ?? false;
|
|
1984
|
+
};
|
|
1985
|
+
const shouldUseMessagesApi = (modelId) => {
|
|
1986
|
+
return (state.models?.data.find((model) => model.id === modelId))?.supported_endpoints?.includes(MESSAGES_ENDPOINT) ?? false;
|
|
1987
|
+
};
|
|
1988
|
+
const isNonStreaming = (response) => Object.hasOwn(response, "choices");
|
|
1989
|
+
const isAsyncIterable$1 = (value) => Boolean(value) && typeof value[Symbol.asyncIterator] === "function";
|
|
1990
|
+
const isCompactRequest = (anthropicPayload) => {
|
|
1991
|
+
const system = anthropicPayload.system;
|
|
1992
|
+
if (typeof system === "string") return system.startsWith(compactSystemPromptStart);
|
|
1993
|
+
if (!Array.isArray(system)) return false;
|
|
1994
|
+
return system.some((msg) => typeof msg.text === "string" && msg.text.startsWith(compactSystemPromptStart));
|
|
1995
|
+
};
|
|
1996
|
+
const mergeContentWithText = (tr, textBlock) => {
|
|
1997
|
+
if (typeof tr.content === "string") return {
|
|
1998
|
+
...tr,
|
|
1999
|
+
content: `${tr.content}\n\n${textBlock.text}`
|
|
2000
|
+
};
|
|
2001
|
+
return {
|
|
2002
|
+
...tr,
|
|
2003
|
+
content: [...tr.content, textBlock]
|
|
2004
|
+
};
|
|
2005
|
+
};
|
|
2006
|
+
const mergeContentWithTexts = (tr, textBlocks) => {
|
|
2007
|
+
if (typeof tr.content === "string") {
|
|
2008
|
+
const appendedTexts = textBlocks.map((tb) => tb.text).join("\n\n");
|
|
2009
|
+
return {
|
|
2010
|
+
...tr,
|
|
2011
|
+
content: `${tr.content}\n\n${appendedTexts}`
|
|
2012
|
+
};
|
|
2013
|
+
}
|
|
2014
|
+
return {
|
|
2015
|
+
...tr,
|
|
2016
|
+
content: [...tr.content, ...textBlocks]
|
|
2017
|
+
};
|
|
2018
|
+
};
|
|
2019
|
+
const mergeToolResultForClaude = (anthropicPayload) => {
|
|
2020
|
+
for (const msg of anthropicPayload.messages) {
|
|
2021
|
+
if (msg.role !== "user" || !Array.isArray(msg.content)) continue;
|
|
2022
|
+
const toolResults = [];
|
|
2023
|
+
const textBlocks = [];
|
|
2024
|
+
let valid = true;
|
|
2025
|
+
for (const block of msg.content) if (block.type === "tool_result") toolResults.push(block);
|
|
2026
|
+
else if (block.type === "text") textBlocks.push(block);
|
|
2027
|
+
else {
|
|
2028
|
+
valid = false;
|
|
2029
|
+
break;
|
|
2030
|
+
}
|
|
2031
|
+
if (!valid || toolResults.length === 0 || textBlocks.length === 0) continue;
|
|
2032
|
+
msg.content = mergeToolResult(toolResults, textBlocks);
|
|
2033
|
+
}
|
|
2034
|
+
};
|
|
2035
|
+
const mergeToolResult = (toolResults, textBlocks) => {
|
|
2036
|
+
if (toolResults.length === textBlocks.length) return toolResults.map((tr, i) => mergeContentWithText(tr, textBlocks[i]));
|
|
2037
|
+
const lastIndex = toolResults.length - 1;
|
|
2038
|
+
return toolResults.map((tr, i) => i === lastIndex ? mergeContentWithTexts(tr, textBlocks) : tr);
|
|
2039
|
+
};
|
|
2040
|
+
|
|
2041
|
+
//#endregion
|
|
2042
|
+
//#region src/routes/messages/route.ts
|
|
2043
|
+
const messageRoutes = new Hono();
|
|
2044
|
+
messageRoutes.post("/", async (c) => {
|
|
2045
|
+
try {
|
|
2046
|
+
return await handleCompletion(c);
|
|
2047
|
+
} catch (error) {
|
|
2048
|
+
return await forwardError(c, error);
|
|
2049
|
+
}
|
|
2050
|
+
});
|
|
2051
|
+
messageRoutes.post("/count_tokens", async (c) => {
|
|
2052
|
+
try {
|
|
2053
|
+
return await handleCountTokens(c);
|
|
2054
|
+
} catch (error) {
|
|
2055
|
+
return await forwardError(c, error);
|
|
2056
|
+
}
|
|
2057
|
+
});
|
|
2058
|
+
|
|
2059
|
+
//#endregion
|
|
2060
|
+
//#region src/routes/models/route.ts
|
|
2061
|
+
const modelRoutes = new Hono();
|
|
2062
|
+
modelRoutes.get("/", async (c) => {
|
|
2063
|
+
try {
|
|
2064
|
+
if (!state.models) await cacheModels();
|
|
2065
|
+
const models = state.models?.data.map((model) => ({
|
|
2066
|
+
id: model.id,
|
|
2067
|
+
object: "model",
|
|
2068
|
+
type: "model",
|
|
2069
|
+
created: 0,
|
|
2070
|
+
created_at: (/* @__PURE__ */ new Date(0)).toISOString(),
|
|
2071
|
+
owned_by: model.vendor,
|
|
2072
|
+
display_name: model.name
|
|
2073
|
+
}));
|
|
2074
|
+
return c.json({
|
|
2075
|
+
object: "list",
|
|
2076
|
+
data: models,
|
|
2077
|
+
has_more: false
|
|
2078
|
+
});
|
|
2079
|
+
} catch (error) {
|
|
2080
|
+
return await forwardError(c, error);
|
|
2081
|
+
}
|
|
2082
|
+
});
|
|
2083
|
+
|
|
2084
|
+
//#endregion
|
|
2085
|
+
//#region src/routes/responses/stream-id-sync.ts
|
|
2086
|
+
const createStreamIdTracker = () => ({ outputItems: /* @__PURE__ */ new Map() });
|
|
2087
|
+
const fixStreamIds = (data, event, tracker) => {
|
|
2088
|
+
if (!data) return data;
|
|
2089
|
+
const parsed = JSON.parse(data);
|
|
2090
|
+
switch (event) {
|
|
2091
|
+
case "response.output_item.added": return handleOutputItemAdded(parsed, tracker);
|
|
2092
|
+
case "response.output_item.done": return handleOutputItemDone(parsed, tracker);
|
|
2093
|
+
default: return handleItemId(parsed, tracker);
|
|
2094
|
+
}
|
|
2095
|
+
};
|
|
2096
|
+
const handleOutputItemAdded = (parsed, tracker) => {
|
|
2097
|
+
if (!parsed.item.id) {
|
|
2098
|
+
let randomSuffix = "";
|
|
2099
|
+
while (randomSuffix.length < 16) randomSuffix += Math.random().toString(36).slice(2);
|
|
2100
|
+
parsed.item.id = `oi_${parsed.output_index}_${randomSuffix.slice(0, 16)}`;
|
|
2101
|
+
}
|
|
2102
|
+
const outputIndex = parsed.output_index;
|
|
2103
|
+
tracker.outputItems.set(outputIndex, parsed.item.id);
|
|
2104
|
+
return JSON.stringify(parsed);
|
|
2105
|
+
};
|
|
2106
|
+
const handleOutputItemDone = (parsed, tracker) => {
|
|
2107
|
+
const outputIndex = parsed.output_index;
|
|
2108
|
+
const originalId = tracker.outputItems.get(outputIndex);
|
|
2109
|
+
if (originalId) parsed.item.id = originalId;
|
|
2110
|
+
return JSON.stringify(parsed);
|
|
2111
|
+
};
|
|
2112
|
+
const handleItemId = (parsed, tracker) => {
|
|
2113
|
+
const outputIndex = parsed.output_index;
|
|
2114
|
+
if (outputIndex !== void 0) {
|
|
2115
|
+
const itemId = tracker.outputItems.get(outputIndex);
|
|
2116
|
+
if (itemId) parsed.item_id = itemId;
|
|
2117
|
+
}
|
|
2118
|
+
return JSON.stringify(parsed);
|
|
2119
|
+
};
|
|
2120
|
+
|
|
2121
|
+
//#endregion
|
|
2122
|
+
//#region src/routes/responses/handler.ts
|
|
2123
|
+
const logger$1 = createHandlerLogger("responses-handler");
|
|
2124
|
+
const RESPONSES_ENDPOINT = "/responses";
|
|
2125
|
+
const handleResponses = async (c) => {
|
|
2126
|
+
await checkRateLimit(state);
|
|
2127
|
+
const payload = await c.req.json();
|
|
2128
|
+
logger$1.debug("Responses request payload:", JSON.stringify(payload));
|
|
2129
|
+
useFunctionApplyPatch(payload);
|
|
2130
|
+
removeWebSearchTool(payload);
|
|
2131
|
+
if (!((state.models?.data.find((model) => model.id === payload.model))?.supported_endpoints?.includes(RESPONSES_ENDPOINT) ?? false)) return c.json({ error: {
|
|
2132
|
+
message: "This model does not support the responses endpoint. Please choose a different model.",
|
|
2133
|
+
type: "invalid_request_error"
|
|
2134
|
+
} }, 400);
|
|
2135
|
+
const { vision, initiator } = getResponsesRequestOptions(payload);
|
|
2136
|
+
if (state.manualApprove) await awaitApproval();
|
|
2137
|
+
const response = await createResponses(payload, {
|
|
2138
|
+
vision,
|
|
2139
|
+
initiator
|
|
2140
|
+
});
|
|
2141
|
+
if (isStreamingRequested(payload) && isAsyncIterable(response)) {
|
|
2142
|
+
logger$1.debug("Forwarding native Responses stream");
|
|
2143
|
+
return streamSSE(c, async (stream) => {
|
|
2144
|
+
const idTracker = createStreamIdTracker();
|
|
2145
|
+
for await (const chunk of response) {
|
|
2146
|
+
logger$1.debug("Responses stream chunk:", JSON.stringify(chunk));
|
|
2147
|
+
const processedData = fixStreamIds(chunk.data ?? "", chunk.event, idTracker);
|
|
2148
|
+
await stream.writeSSE({
|
|
2149
|
+
id: chunk.id,
|
|
2150
|
+
event: chunk.event,
|
|
2151
|
+
data: processedData
|
|
2152
|
+
});
|
|
2153
|
+
}
|
|
2154
|
+
});
|
|
2155
|
+
}
|
|
2156
|
+
logger$1.debug("Forwarding native Responses result:", JSON.stringify(response).slice(-400));
|
|
2157
|
+
return c.json(response);
|
|
2158
|
+
};
|
|
2159
|
+
const isAsyncIterable = (value) => Boolean(value) && typeof value[Symbol.asyncIterator] === "function";
|
|
2160
|
+
const isStreamingRequested = (payload) => Boolean(payload.stream);
|
|
2161
|
+
const useFunctionApplyPatch = (payload) => {
|
|
2162
|
+
if (getConfig().useFunctionApplyPatch ?? true) {
|
|
2163
|
+
logger$1.debug("Using function tool apply_patch for responses");
|
|
2164
|
+
if (Array.isArray(payload.tools)) {
|
|
2165
|
+
const toolsArr = payload.tools;
|
|
2166
|
+
for (let i = 0; i < toolsArr.length; i++) {
|
|
2167
|
+
const t = toolsArr[i];
|
|
2168
|
+
if (t.type === "custom" && t.name === "apply_patch") toolsArr[i] = {
|
|
2169
|
+
type: "function",
|
|
2170
|
+
name: t.name,
|
|
2171
|
+
description: "Use the `apply_patch` tool to edit files",
|
|
2172
|
+
parameters: {
|
|
2173
|
+
type: "object",
|
|
2174
|
+
properties: { input: {
|
|
2175
|
+
type: "string",
|
|
2176
|
+
description: "The entire contents of the apply_patch command"
|
|
2177
|
+
} },
|
|
2178
|
+
required: ["input"]
|
|
2179
|
+
},
|
|
2180
|
+
strict: false
|
|
2181
|
+
};
|
|
2182
|
+
}
|
|
2183
|
+
}
|
|
2184
|
+
}
|
|
2185
|
+
};
|
|
2186
|
+
const removeWebSearchTool = (payload) => {
|
|
2187
|
+
if (!Array.isArray(payload.tools) || payload.tools.length === 0) return;
|
|
2188
|
+
payload.tools = payload.tools.filter((t) => {
|
|
2189
|
+
return t.type !== "web_search";
|
|
2190
|
+
});
|
|
2191
|
+
};
|
|
2192
|
+
|
|
2193
|
+
//#endregion
|
|
2194
|
+
//#region src/routes/responses/route.ts
|
|
2195
|
+
const responsesRoutes = new Hono();
|
|
2196
|
+
responsesRoutes.post("/", async (c) => {
|
|
2197
|
+
try {
|
|
2198
|
+
return await handleResponses(c);
|
|
2199
|
+
} catch (error) {
|
|
2200
|
+
return await forwardError(c, error);
|
|
2201
|
+
}
|
|
2202
|
+
});
|
|
2203
|
+
|
|
2204
|
+
//#endregion
|
|
2205
|
+
//#region src/routes/token/route.ts
|
|
2206
|
+
const tokenRoute = new Hono();
|
|
2207
|
+
tokenRoute.get("/", (c) => {
|
|
2208
|
+
try {
|
|
2209
|
+
return c.json({ token: state.copilotToken });
|
|
2210
|
+
} catch (error) {
|
|
2211
|
+
console.error("Error fetching token:", error);
|
|
2212
|
+
return c.json({
|
|
2213
|
+
error: "Failed to fetch token",
|
|
2214
|
+
token: null
|
|
2215
|
+
}, 500);
|
|
2216
|
+
}
|
|
2217
|
+
});
|
|
2218
|
+
|
|
2219
|
+
//#endregion
|
|
2220
|
+
//#region src/routes/usage/route.ts
|
|
2221
|
+
const usageRoute = new Hono();
|
|
2222
|
+
usageRoute.get("/", async (c) => {
|
|
2223
|
+
try {
|
|
2224
|
+
const usage = await getCopilotUsage();
|
|
2225
|
+
return c.json(usage);
|
|
2226
|
+
} catch (error) {
|
|
2227
|
+
console.error("Error fetching Copilot usage:", error);
|
|
2228
|
+
return c.json({ error: "Failed to fetch Copilot usage" }, 500);
|
|
2229
|
+
}
|
|
2230
|
+
});
|
|
2231
|
+
|
|
2232
|
+
//#endregion
|
|
2233
|
+
//#region src/server.ts
|
|
2234
|
+
const server = new Hono();
|
|
2235
|
+
server.use(logger());
|
|
2236
|
+
server.use(cors());
|
|
2237
|
+
server.get("/", (c) => c.text("Server running"));
|
|
2238
|
+
server.route("/chat/completions", completionRoutes);
|
|
2239
|
+
server.route("/models", modelRoutes);
|
|
2240
|
+
server.route("/embeddings", embeddingRoutes);
|
|
2241
|
+
server.route("/usage", usageRoute);
|
|
2242
|
+
server.route("/token", tokenRoute);
|
|
2243
|
+
server.route("/responses", responsesRoutes);
|
|
2244
|
+
server.route("/v1/chat/completions", completionRoutes);
|
|
2245
|
+
server.route("/v1/models", modelRoutes);
|
|
2246
|
+
server.route("/v1/embeddings", embeddingRoutes);
|
|
2247
|
+
server.route("/v1/responses", responsesRoutes);
|
|
2248
|
+
server.route("/v1/messages", messageRoutes);
|
|
2249
|
+
|
|
2250
|
+
//#endregion
|
|
2251
|
+
export { server };
|
|
2252
|
+
//# sourceMappingURL=server-IY-mTdPh.js.map
|