@jeffusion/bungee-core 5.19.0 → 5.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/gateway/contracts.d.ts +2 -0
- package/dist/hooks/plugin-hooks.d.ts +3 -0
- package/dist/main.js +38 -18
- package/dist/plugins/ai-transformer/index.js +11 -1
- package/dist/plugins/ai-transformer/index.js.map +5 -3
- package/dist/plugins/codex-router/control.js +673 -2
- package/dist/plugins/codex-router/control.js.map +19 -4
- package/dist/plugins/codex-router/index.js +3765 -8
- package/dist/plugins/codex-router/index.js.map +22 -6
- package/dist/plugins/codex-router/manifest.json +15 -1
- package/dist/plugins/openai-messages-to-chat/index.js +691 -543
- package/dist/plugins/openai-messages-to-chat/index.js.map +8 -4
- package/dist/plugins/token-metering/index.js +652 -71
- package/dist/plugins/token-metering/index.js.map +5 -3
- package/dist/scoped-plugin-registry.d.ts +4 -2
- package/package.json +2 -2
|
@@ -163,12 +163,583 @@ class ProtocolTransformerRegistry {
|
|
|
163
163
|
}
|
|
164
164
|
}
|
|
165
165
|
// packages/llms/src/providers/openai/protocol-conversion.ts
|
|
166
|
+
var THINKING_TAG_PATTERN = /<thinking>([\s\S]*?)<\/thinking>/gi;
|
|
166
167
|
var TOOL_CALL_CONTENT_TYPES = new Set([
|
|
167
168
|
"tool_use",
|
|
168
169
|
"tool_call",
|
|
169
170
|
"function_call",
|
|
170
171
|
"output_tool_call"
|
|
171
172
|
]);
|
|
173
|
+
function isRecord(value) {
|
|
174
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
175
|
+
}
|
|
176
|
+
function asString(value, trimWhitespace) {
|
|
177
|
+
if (typeof value !== "string") {
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
return trimWhitespace ? value.trim() : value;
|
|
181
|
+
}
|
|
182
|
+
function normalizePath(pathname) {
|
|
183
|
+
if (pathname.length > 1 && pathname.endsWith("/")) {
|
|
184
|
+
return pathname.slice(0, -1);
|
|
185
|
+
}
|
|
186
|
+
return pathname;
|
|
187
|
+
}
|
|
188
|
+
function isEnabledLike(value) {
|
|
189
|
+
if (value === true) {
|
|
190
|
+
return true;
|
|
191
|
+
}
|
|
192
|
+
if (typeof value === "string") {
|
|
193
|
+
const normalized = value.trim().toLowerCase();
|
|
194
|
+
return normalized === "enabled" || normalized === "enable" || normalized === "true" || normalized === "on" || normalized === "1";
|
|
195
|
+
}
|
|
196
|
+
if (!isRecord(value)) {
|
|
197
|
+
return false;
|
|
198
|
+
}
|
|
199
|
+
const type = typeof value.type === "string" ? value.type.trim().toLowerCase() : "";
|
|
200
|
+
if (type === "disabled" || type === "disable" || type === "off") {
|
|
201
|
+
return false;
|
|
202
|
+
}
|
|
203
|
+
if (value.enabled === false) {
|
|
204
|
+
return false;
|
|
205
|
+
}
|
|
206
|
+
if (value.enabled === true) {
|
|
207
|
+
return true;
|
|
208
|
+
}
|
|
209
|
+
if (type === "enabled" || type === "enable") {
|
|
210
|
+
return true;
|
|
211
|
+
}
|
|
212
|
+
return Object.keys(value).length > 0;
|
|
213
|
+
}
|
|
214
|
+
function isToolCallContentBlock(item) {
|
|
215
|
+
const itemType = typeof item.type === "string" ? item.type.trim().toLowerCase() : "";
|
|
216
|
+
if (TOOL_CALL_CONTENT_TYPES.has(itemType) || itemType.endsWith("_call")) {
|
|
217
|
+
return true;
|
|
218
|
+
}
|
|
219
|
+
const hasCallIdentifier = typeof item.id === "string" || typeof item.call_id === "string" || typeof item.name === "string";
|
|
220
|
+
const hasCallPayload = item.input !== undefined || item.arguments !== undefined || item.function !== undefined;
|
|
221
|
+
return hasCallIdentifier && hasCallPayload;
|
|
222
|
+
}
|
|
223
|
+
function hasToolCalls(value) {
|
|
224
|
+
if (Array.isArray(value)) {
|
|
225
|
+
return value.length > 0;
|
|
226
|
+
}
|
|
227
|
+
if (isRecord(value)) {
|
|
228
|
+
return Object.keys(value).length > 0;
|
|
229
|
+
}
|
|
230
|
+
if (typeof value !== "string") {
|
|
231
|
+
return false;
|
|
232
|
+
}
|
|
233
|
+
const trimmed = value.trim();
|
|
234
|
+
if (trimmed.length === 0) {
|
|
235
|
+
return false;
|
|
236
|
+
}
|
|
237
|
+
try {
|
|
238
|
+
const parsed = JSON.parse(trimmed);
|
|
239
|
+
if (Array.isArray(parsed)) {
|
|
240
|
+
return parsed.length > 0;
|
|
241
|
+
}
|
|
242
|
+
return isRecord(parsed);
|
|
243
|
+
} catch {
|
|
244
|
+
return true;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
function hasToolCallLikeContent(content) {
|
|
248
|
+
if (!Array.isArray(content)) {
|
|
249
|
+
return false;
|
|
250
|
+
}
|
|
251
|
+
for (const item of content) {
|
|
252
|
+
if (!isRecord(item)) {
|
|
253
|
+
continue;
|
|
254
|
+
}
|
|
255
|
+
if (isToolCallContentBlock(item)) {
|
|
256
|
+
return true;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
return false;
|
|
260
|
+
}
|
|
261
|
+
function isAssistantToolCallMessage(message) {
|
|
262
|
+
if (message.role !== "assistant") {
|
|
263
|
+
return false;
|
|
264
|
+
}
|
|
265
|
+
return hasToolCalls(message.tool_calls) || hasToolCallLikeContent(message.content);
|
|
266
|
+
}
|
|
267
|
+
function extractThinkingTagContent(content, trimWhitespace) {
|
|
268
|
+
THINKING_TAG_PATTERN.lastIndex = 0;
|
|
269
|
+
const extracted = [];
|
|
270
|
+
let matched = THINKING_TAG_PATTERN.exec(content);
|
|
271
|
+
while (matched !== null) {
|
|
272
|
+
const block = asString(matched[1], trimWhitespace);
|
|
273
|
+
if (block) {
|
|
274
|
+
extracted.push(block);
|
|
275
|
+
}
|
|
276
|
+
matched = THINKING_TAG_PATTERN.exec(content);
|
|
277
|
+
}
|
|
278
|
+
if (extracted.length === 0) {
|
|
279
|
+
return "";
|
|
280
|
+
}
|
|
281
|
+
return extracted.join(`
|
|
282
|
+
|
|
283
|
+
`);
|
|
284
|
+
}
|
|
285
|
+
function extractReasoningFromArrayContent(content, trimWhitespace) {
|
|
286
|
+
const reasoningTexts = [];
|
|
287
|
+
for (const item of content) {
|
|
288
|
+
if (!isRecord(item)) {
|
|
289
|
+
continue;
|
|
290
|
+
}
|
|
291
|
+
const type = typeof item.type === "string" ? item.type : "";
|
|
292
|
+
if (type !== "reasoning" && type !== "thinking" && type !== "reasoning_content") {
|
|
293
|
+
continue;
|
|
294
|
+
}
|
|
295
|
+
const candidates = [
|
|
296
|
+
item.text,
|
|
297
|
+
item.content,
|
|
298
|
+
item.reasoning,
|
|
299
|
+
item.thinking
|
|
300
|
+
];
|
|
301
|
+
for (const candidate of candidates) {
|
|
302
|
+
const text = asString(candidate, trimWhitespace);
|
|
303
|
+
if (text && text.length > 0) {
|
|
304
|
+
reasoningTexts.push(text);
|
|
305
|
+
break;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
if (reasoningTexts.length === 0) {
|
|
310
|
+
return "";
|
|
311
|
+
}
|
|
312
|
+
return reasoningTexts.join(`
|
|
313
|
+
|
|
314
|
+
`);
|
|
315
|
+
}
|
|
316
|
+
function extractReasoningContent(content, trimWhitespace) {
|
|
317
|
+
if (typeof content === "string") {
|
|
318
|
+
return extractThinkingTagContent(content, trimWhitespace);
|
|
319
|
+
}
|
|
320
|
+
if (Array.isArray(content)) {
|
|
321
|
+
return extractReasoningFromArrayContent(content, trimWhitespace);
|
|
322
|
+
}
|
|
323
|
+
return "";
|
|
324
|
+
}
|
|
325
|
+
function normalizeAnthropicUsage(usage) {
|
|
326
|
+
if (!isRecord(usage)) {
|
|
327
|
+
return {
|
|
328
|
+
input_tokens: 0,
|
|
329
|
+
output_tokens: 0
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
return {
|
|
333
|
+
input_tokens: typeof usage.input_tokens === "number" ? usage.input_tokens : 0,
|
|
334
|
+
output_tokens: typeof usage.output_tokens === "number" ? usage.output_tokens : 0
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
function normalizeAssistantToolCallMessage(message, trimWhitespace) {
|
|
338
|
+
if (typeof message.reasoning_content === "string") {
|
|
339
|
+
const normalized = trimWhitespace ? message.reasoning_content.trim() : message.reasoning_content;
|
|
340
|
+
if (normalized === message.reasoning_content) {
|
|
341
|
+
return message;
|
|
342
|
+
}
|
|
343
|
+
return {
|
|
344
|
+
...message,
|
|
345
|
+
reasoning_content: normalized
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
return {
|
|
349
|
+
...message,
|
|
350
|
+
reasoning_content: extractReasoningContent(message.content, trimWhitespace)
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
function convertChatUserContentToResponsesContent(content) {
|
|
354
|
+
if (typeof content === "string") {
|
|
355
|
+
const text = content.trim();
|
|
356
|
+
return text ? [{ type: "input_text", text }] : [{ type: "input_text", text: "" }];
|
|
357
|
+
}
|
|
358
|
+
if (!Array.isArray(content)) {
|
|
359
|
+
return [{ type: "input_text", text: "" }];
|
|
360
|
+
}
|
|
361
|
+
const converted = [];
|
|
362
|
+
for (const part of content) {
|
|
363
|
+
if (typeof part === "string") {
|
|
364
|
+
const text = part.trim();
|
|
365
|
+
if (text) {
|
|
366
|
+
converted.push({ type: "input_text", text });
|
|
367
|
+
}
|
|
368
|
+
continue;
|
|
369
|
+
}
|
|
370
|
+
if (!isRecord(part)) {
|
|
371
|
+
continue;
|
|
372
|
+
}
|
|
373
|
+
if (part.type === "text" && typeof part.text === "string") {
|
|
374
|
+
converted.push({ type: "input_text", text: part.text });
|
|
375
|
+
continue;
|
|
376
|
+
}
|
|
377
|
+
if (part.type === "image_url" && isRecord(part.image_url) && typeof part.image_url.url === "string") {
|
|
378
|
+
converted.push({ type: "input_image", image_url: part.image_url.url });
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
return converted.length > 0 ? converted : [{ type: "input_text", text: "" }];
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
class OpenAIProtocolConversion {
|
|
385
|
+
trimWhitespace;
|
|
386
|
+
constructor(options) {
|
|
387
|
+
this.trimWhitespace = options?.trimWhitespace !== false;
|
|
388
|
+
}
|
|
389
|
+
isResponsesPath(pathname) {
|
|
390
|
+
const normalizedPath = normalizePath(pathname);
|
|
391
|
+
return normalizedPath === "/v1/responses" || normalizedPath === "/responses";
|
|
392
|
+
}
|
|
393
|
+
isReasoningContext(body) {
|
|
394
|
+
if (body.reasoning !== undefined && body.reasoning !== null) {
|
|
395
|
+
return true;
|
|
396
|
+
}
|
|
397
|
+
if (typeof body.reasoning_effort === "string" && body.reasoning_effort.trim().length > 0) {
|
|
398
|
+
return true;
|
|
399
|
+
}
|
|
400
|
+
if (isEnabledLike(body.enable_thinking)) {
|
|
401
|
+
return true;
|
|
402
|
+
}
|
|
403
|
+
return isEnabledLike(body.thinking);
|
|
404
|
+
}
|
|
405
|
+
normalizeResponsesReasoningBody(body) {
|
|
406
|
+
let changed = false;
|
|
407
|
+
let normalizedBody = body;
|
|
408
|
+
for (const key of ["input", "messages"]) {
|
|
409
|
+
const normalizedCollection = this.normalizeMessageCollection(normalizedBody, key);
|
|
410
|
+
if (normalizedCollection.changed) {
|
|
411
|
+
normalizedBody = normalizedCollection.body;
|
|
412
|
+
changed = true;
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
if (!changed) {
|
|
416
|
+
return body;
|
|
417
|
+
}
|
|
418
|
+
return normalizedBody;
|
|
419
|
+
}
|
|
420
|
+
ensureAssistantToolUseReasoningContent(body) {
|
|
421
|
+
if (!isRecord(body) || body.type !== "message" || body.role !== "assistant" || !Array.isArray(body.content)) {
|
|
422
|
+
return body;
|
|
423
|
+
}
|
|
424
|
+
if (!body.content.some((block) => isRecord(block) && block.type === "tool_use") || typeof body.reasoning_content === "string") {
|
|
425
|
+
return body;
|
|
426
|
+
}
|
|
427
|
+
return {
|
|
428
|
+
...body,
|
|
429
|
+
reasoning_content: this.extractThinkingTextFromAnthropicContent(body.content)
|
|
430
|
+
};
|
|
431
|
+
}
|
|
432
|
+
ensureMessagesStreamCompatibility(events, ctx) {
|
|
433
|
+
const withUsage = this.ensureMessageDeltaUsage(events, ctx);
|
|
434
|
+
return this.ensureMessageStartReasoningContent(withUsage);
|
|
435
|
+
}
|
|
436
|
+
convertChatMessagesToResponsesInput(messages) {
|
|
437
|
+
if (!Array.isArray(messages)) {
|
|
438
|
+
return { input: [] };
|
|
439
|
+
}
|
|
440
|
+
const input = [];
|
|
441
|
+
const instructionParts = [];
|
|
442
|
+
for (const msg of messages) {
|
|
443
|
+
if (!isRecord(msg)) {
|
|
444
|
+
continue;
|
|
445
|
+
}
|
|
446
|
+
if (msg.role === "system") {
|
|
447
|
+
const text = typeof msg.content === "string" ? msg.content.trim() : "";
|
|
448
|
+
if (text)
|
|
449
|
+
instructionParts.push(text);
|
|
450
|
+
continue;
|
|
451
|
+
}
|
|
452
|
+
if (msg.role === "user") {
|
|
453
|
+
input.push({
|
|
454
|
+
role: "user",
|
|
455
|
+
content: convertChatUserContentToResponsesContent(msg.content)
|
|
456
|
+
});
|
|
457
|
+
continue;
|
|
458
|
+
}
|
|
459
|
+
if (msg.role === "assistant") {
|
|
460
|
+
const assistantText = typeof msg.content === "string" ? msg.content.trim() : "";
|
|
461
|
+
if (assistantText) {
|
|
462
|
+
input.push({
|
|
463
|
+
role: "assistant",
|
|
464
|
+
content: [{ type: "output_text", text: assistantText }]
|
|
465
|
+
});
|
|
466
|
+
}
|
|
467
|
+
if (Array.isArray(msg.tool_calls)) {
|
|
468
|
+
for (const toolCall of msg.tool_calls) {
|
|
469
|
+
if (!isRecord(toolCall)) {
|
|
470
|
+
continue;
|
|
471
|
+
}
|
|
472
|
+
const callId = typeof toolCall.id === "string" ? toolCall.id : "";
|
|
473
|
+
const functionObject = isRecord(toolCall.function) ? toolCall.function : undefined;
|
|
474
|
+
if (!functionObject) {
|
|
475
|
+
continue;
|
|
476
|
+
}
|
|
477
|
+
const functionName = typeof functionObject?.name === "string" ? functionObject.name : "";
|
|
478
|
+
if (!callId || !functionName) {
|
|
479
|
+
continue;
|
|
480
|
+
}
|
|
481
|
+
input.push({
|
|
482
|
+
type: "function_call",
|
|
483
|
+
call_id: callId,
|
|
484
|
+
name: functionName,
|
|
485
|
+
arguments: functionObject.arguments || "{}"
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
continue;
|
|
490
|
+
}
|
|
491
|
+
if (msg.role === "tool") {
|
|
492
|
+
const callId = typeof msg.tool_call_id === "string" ? msg.tool_call_id : "";
|
|
493
|
+
if (!callId) {
|
|
494
|
+
continue;
|
|
495
|
+
}
|
|
496
|
+
input.push({
|
|
497
|
+
type: "function_call_output",
|
|
498
|
+
call_id: callId,
|
|
499
|
+
output: msg.content ?? ""
|
|
500
|
+
});
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
const instructions = instructionParts.length > 0 ? instructionParts.join(`
|
|
504
|
+
`) : undefined;
|
|
505
|
+
return { input, instructions };
|
|
506
|
+
}
|
|
507
|
+
mapChatToolsToResponsesTools(chatTools) {
|
|
508
|
+
if (!Array.isArray(chatTools)) {
|
|
509
|
+
return [];
|
|
510
|
+
}
|
|
511
|
+
const mapped = [];
|
|
512
|
+
for (const tool of chatTools) {
|
|
513
|
+
if (!isRecord(tool) || tool.type !== "function" || !isRecord(tool.function)) {
|
|
514
|
+
continue;
|
|
515
|
+
}
|
|
516
|
+
const name = typeof tool.function.name === "string" ? tool.function.name : "";
|
|
517
|
+
if (!name) {
|
|
518
|
+
continue;
|
|
519
|
+
}
|
|
520
|
+
const parameters = isRecord(tool.function.parameters) ? tool.function.parameters : { type: "object", properties: {} };
|
|
521
|
+
mapped.push({
|
|
522
|
+
type: "function",
|
|
523
|
+
name,
|
|
524
|
+
description: typeof tool.function.description === "string" ? tool.function.description : "",
|
|
525
|
+
parameters
|
|
526
|
+
});
|
|
527
|
+
}
|
|
528
|
+
return mapped;
|
|
529
|
+
}
|
|
530
|
+
convertResponsesInputToMessages(input) {
|
|
531
|
+
if (typeof input === "string") {
|
|
532
|
+
const text = input.trim();
|
|
533
|
+
return text ? [{ role: "user", content: text }] : [];
|
|
534
|
+
}
|
|
535
|
+
if (!Array.isArray(input)) {
|
|
536
|
+
return [];
|
|
537
|
+
}
|
|
538
|
+
const messages = [];
|
|
539
|
+
const pendingTopLevelInputParts = [];
|
|
540
|
+
const flushPendingTopLevelInputParts = () => {
|
|
541
|
+
if (pendingTopLevelInputParts.length === 0) {
|
|
542
|
+
return;
|
|
543
|
+
}
|
|
544
|
+
messages.push({
|
|
545
|
+
role: "user",
|
|
546
|
+
content: this.normalizeResponsesMessageContent([...pendingTopLevelInputParts])
|
|
547
|
+
});
|
|
548
|
+
pendingTopLevelInputParts.length = 0;
|
|
549
|
+
};
|
|
550
|
+
for (const item of input) {
|
|
551
|
+
if (typeof item === "string") {
|
|
552
|
+
if (item.trim()) {
|
|
553
|
+
pendingTopLevelInputParts.push({ type: "input_text", text: item });
|
|
554
|
+
}
|
|
555
|
+
continue;
|
|
556
|
+
}
|
|
557
|
+
if (!isRecord(item)) {
|
|
558
|
+
continue;
|
|
559
|
+
}
|
|
560
|
+
const itemType = item.type;
|
|
561
|
+
if (itemType === "input_text" || itemType === "output_text" || itemType === "input_image" || itemType === "text" || itemType === "image_url") {
|
|
562
|
+
pendingTopLevelInputParts.push(item);
|
|
563
|
+
continue;
|
|
564
|
+
}
|
|
565
|
+
if (itemType === "function_call") {
|
|
566
|
+
flushPendingTopLevelInputParts();
|
|
567
|
+
const rawArgs = item.arguments;
|
|
568
|
+
const argumentsString = typeof rawArgs === "string" ? rawArgs : JSON.stringify(rawArgs || {});
|
|
569
|
+
messages.push({
|
|
570
|
+
role: "assistant",
|
|
571
|
+
content: null,
|
|
572
|
+
tool_calls: [{
|
|
573
|
+
id: item.call_id || item.id || "",
|
|
574
|
+
type: "function",
|
|
575
|
+
function: {
|
|
576
|
+
name: item.name || "",
|
|
577
|
+
arguments: argumentsString
|
|
578
|
+
}
|
|
579
|
+
}]
|
|
580
|
+
});
|
|
581
|
+
continue;
|
|
582
|
+
}
|
|
583
|
+
if (itemType === "function_call_output") {
|
|
584
|
+
flushPendingTopLevelInputParts();
|
|
585
|
+
messages.push({
|
|
586
|
+
role: "tool",
|
|
587
|
+
tool_call_id: item.call_id || "",
|
|
588
|
+
content: item.output ?? "",
|
|
589
|
+
is_error: item.is_error === true || item.status === "error" || item.status === "failed"
|
|
590
|
+
});
|
|
591
|
+
continue;
|
|
592
|
+
}
|
|
593
|
+
if (itemType === "message") {
|
|
594
|
+
flushPendingTopLevelInputParts();
|
|
595
|
+
const role = item.role || "user";
|
|
596
|
+
if (typeof role === "string") {
|
|
597
|
+
messages.push({
|
|
598
|
+
role,
|
|
599
|
+
content: this.normalizeResponsesMessageContent(item.content)
|
|
600
|
+
});
|
|
601
|
+
}
|
|
602
|
+
continue;
|
|
603
|
+
}
|
|
604
|
+
if (typeof item.role === "string") {
|
|
605
|
+
flushPendingTopLevelInputParts();
|
|
606
|
+
messages.push({
|
|
607
|
+
role: item.role,
|
|
608
|
+
content: this.normalizeResponsesMessageContent(item.content)
|
|
609
|
+
});
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
flushPendingTopLevelInputParts();
|
|
613
|
+
return messages;
|
|
614
|
+
}
|
|
615
|
+
normalizeResponsesMessageContent(content) {
|
|
616
|
+
if (typeof content === "string") {
|
|
617
|
+
return content;
|
|
618
|
+
}
|
|
619
|
+
if (!Array.isArray(content)) {
|
|
620
|
+
return content;
|
|
621
|
+
}
|
|
622
|
+
const converted = content.map((part) => {
|
|
623
|
+
if (typeof part === "string") {
|
|
624
|
+
return { type: "text", text: part };
|
|
625
|
+
}
|
|
626
|
+
if (!isRecord(part)) {
|
|
627
|
+
return null;
|
|
628
|
+
}
|
|
629
|
+
if (part.type === "input_text" || part.type === "output_text") {
|
|
630
|
+
return { type: "text", text: part.text || "" };
|
|
631
|
+
}
|
|
632
|
+
if (part.type === "input_image") {
|
|
633
|
+
const imageUrl = typeof part.image_url === "string" ? part.image_url : isRecord(part.image_url) ? part.image_url.url : undefined;
|
|
634
|
+
if (typeof imageUrl === "string" && imageUrl) {
|
|
635
|
+
return {
|
|
636
|
+
type: "image_url",
|
|
637
|
+
image_url: { url: imageUrl }
|
|
638
|
+
};
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
return part;
|
|
642
|
+
}).filter((part) => part !== null);
|
|
643
|
+
return converted;
|
|
644
|
+
}
|
|
645
|
+
normalizeMessageCollection(body, key) {
|
|
646
|
+
const collection = body[key];
|
|
647
|
+
if (!Array.isArray(collection)) {
|
|
648
|
+
return { body, changed: false };
|
|
649
|
+
}
|
|
650
|
+
let changed = false;
|
|
651
|
+
const normalizedCollection = collection.map((item) => {
|
|
652
|
+
const normalizedItem = this.normalizeAssistantToolCallCandidate(item);
|
|
653
|
+
if (normalizedItem.changed) {
|
|
654
|
+
changed = true;
|
|
655
|
+
}
|
|
656
|
+
return normalizedItem.normalized;
|
|
657
|
+
});
|
|
658
|
+
if (!changed) {
|
|
659
|
+
return { body, changed: false };
|
|
660
|
+
}
|
|
661
|
+
return {
|
|
662
|
+
body: {
|
|
663
|
+
...body,
|
|
664
|
+
[key]: normalizedCollection
|
|
665
|
+
},
|
|
666
|
+
changed: true
|
|
667
|
+
};
|
|
668
|
+
}
|
|
669
|
+
normalizeAssistantToolCallCandidate(value) {
|
|
670
|
+
if (!isRecord(value)) {
|
|
671
|
+
return { normalized: value, changed: false };
|
|
672
|
+
}
|
|
673
|
+
let changed = false;
|
|
674
|
+
let current = value;
|
|
675
|
+
if (isRecord(current.message)) {
|
|
676
|
+
const nested = this.normalizeAssistantToolCallCandidate(current.message);
|
|
677
|
+
if (nested.changed) {
|
|
678
|
+
current = {
|
|
679
|
+
...current,
|
|
680
|
+
message: nested.normalized
|
|
681
|
+
};
|
|
682
|
+
changed = true;
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
if (!isAssistantToolCallMessage(current)) {
|
|
686
|
+
return { normalized: current, changed };
|
|
687
|
+
}
|
|
688
|
+
const normalizedMessage = normalizeAssistantToolCallMessage(current, this.trimWhitespace);
|
|
689
|
+
if (normalizedMessage !== current) {
|
|
690
|
+
current = normalizedMessage;
|
|
691
|
+
changed = true;
|
|
692
|
+
}
|
|
693
|
+
return { normalized: current, changed };
|
|
694
|
+
}
|
|
695
|
+
extractThinkingTextFromAnthropicContent(content) {
|
|
696
|
+
const segments = [];
|
|
697
|
+
for (const block of content) {
|
|
698
|
+
if (!isRecord(block) || block.type !== "thinking") {
|
|
699
|
+
continue;
|
|
700
|
+
}
|
|
701
|
+
if (typeof block.thinking === "string" && block.thinking.trim().length > 0) {
|
|
702
|
+
segments.push(block.thinking.trim());
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
return segments.join(`
|
|
706
|
+
|
|
707
|
+
`);
|
|
708
|
+
}
|
|
709
|
+
ensureMessageStartReasoningContent(events) {
|
|
710
|
+
return events.map((event) => {
|
|
711
|
+
if (!isRecord(event) || event.type !== "message_start" || !isRecord(event.message)) {
|
|
712
|
+
return event;
|
|
713
|
+
}
|
|
714
|
+
const existingReasoningContent = event.message.reasoning_content;
|
|
715
|
+
if (typeof existingReasoningContent === "string") {
|
|
716
|
+
return event;
|
|
717
|
+
}
|
|
718
|
+
return {
|
|
719
|
+
...event,
|
|
720
|
+
message: {
|
|
721
|
+
...event.message,
|
|
722
|
+
reasoning_content: ""
|
|
723
|
+
}
|
|
724
|
+
};
|
|
725
|
+
});
|
|
726
|
+
}
|
|
727
|
+
ensureMessageDeltaUsage(events, ctx) {
|
|
728
|
+
const lastUsageKey = "messages_compat_last_usage";
|
|
729
|
+
const cachedUsage = normalizeAnthropicUsage(ctx.streamState.get(lastUsageKey));
|
|
730
|
+
return events.map((event) => {
|
|
731
|
+
if (!isRecord(event) || event.type !== "message_delta") {
|
|
732
|
+
return event;
|
|
733
|
+
}
|
|
734
|
+
const normalizedUsage = normalizeAnthropicUsage(event.usage ?? cachedUsage);
|
|
735
|
+
ctx.streamState.set(lastUsageKey, normalizedUsage);
|
|
736
|
+
return {
|
|
737
|
+
...event,
|
|
738
|
+
usage: normalizedUsage
|
|
739
|
+
};
|
|
740
|
+
});
|
|
741
|
+
}
|
|
742
|
+
}
|
|
172
743
|
// packages/llms/src/providers/openai/messages-compat-normalization.ts
|
|
173
744
|
var SUPPORTED_ROLES = new Set(["system", "developer", "user", "assistant", "tool"]);
|
|
174
745
|
// packages/llms/src/token-accounting/types.ts
|
|
@@ -441,10 +1012,10 @@ var CANONICAL_EVENT_KEYS = new Set([
|
|
|
441
1012
|
"outcome",
|
|
442
1013
|
"countedAt"
|
|
443
1014
|
]);
|
|
444
|
-
function
|
|
1015
|
+
function isRecord2(value) {
|
|
445
1016
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
446
1017
|
}
|
|
447
|
-
function
|
|
1018
|
+
function asString2(value) {
|
|
448
1019
|
return typeof value === "string" && value.length > 0 ? value : undefined;
|
|
449
1020
|
}
|
|
450
1021
|
function readNumber(record, key) {
|
|
@@ -459,7 +1030,7 @@ function readRecord(record, key) {
|
|
|
459
1030
|
return;
|
|
460
1031
|
}
|
|
461
1032
|
const value = record[key];
|
|
462
|
-
return
|
|
1033
|
+
return isRecord2(value) ? value : undefined;
|
|
463
1034
|
}
|
|
464
1035
|
function countTextTokens(text) {
|
|
465
1036
|
if (!text) {
|
|
@@ -514,7 +1085,7 @@ function countJsonLikeTokens(value) {
|
|
|
514
1085
|
if (Array.isArray(value)) {
|
|
515
1086
|
return value.reduce((sum, entry) => sum + countJsonLikeTokens(entry), 0);
|
|
516
1087
|
}
|
|
517
|
-
if (
|
|
1088
|
+
if (isRecord2(value)) {
|
|
518
1089
|
return Object.entries(value).reduce((sum, [key, entry]) => {
|
|
519
1090
|
return sum + countTextTokens(key) + countJsonLikeTokens(entry);
|
|
520
1091
|
}, 0);
|
|
@@ -560,7 +1131,7 @@ function finalizeTokenAccountingEvent(state, overrides = {}, outcome = "complete
|
|
|
560
1131
|
});
|
|
561
1132
|
}
|
|
562
1133
|
function assertCanonicalTokenAccountingEventV2(event) {
|
|
563
|
-
if (!
|
|
1134
|
+
if (!isRecord2(event)) {
|
|
564
1135
|
throw new Error("Canonical token accounting event must be an object.");
|
|
565
1136
|
}
|
|
566
1137
|
for (const key of Object.keys(event)) {
|
|
@@ -719,7 +1290,7 @@ function isDeferred(state) {
|
|
|
719
1290
|
return state.deferFinalization === true;
|
|
720
1291
|
}
|
|
721
1292
|
function setSessionModel(state, value) {
|
|
722
|
-
const model =
|
|
1293
|
+
const model = asString2(value) ?? state.model ?? "unknown";
|
|
723
1294
|
if (isDeferred(state) && model.length > MAX_DEFERRED_MODEL_CHARS) {
|
|
724
1295
|
state.model = model.slice(0, MAX_DEFERRED_MODEL_CHARS);
|
|
725
1296
|
state.requestEstimateTruncated = true;
|
|
@@ -814,11 +1385,11 @@ function stripDeferredMedia(value, key = "") {
|
|
|
814
1385
|
}
|
|
815
1386
|
return { value: result, sawMedia, hasContent };
|
|
816
1387
|
}
|
|
817
|
-
if (!
|
|
1388
|
+
if (!isRecord2(value))
|
|
818
1389
|
return { value, sawMedia: false, hasContent: false };
|
|
819
|
-
if (
|
|
1390
|
+
if (asString2(value.type) === "tool_use" || asString2(value.type) === "function_call")
|
|
820
1391
|
return { value, sawMedia: false, hasContent: true };
|
|
821
|
-
if (DEFERRED_MEDIA_TYPES.has(
|
|
1392
|
+
if (DEFERRED_MEDIA_TYPES.has(asString2(value.type) ?? ""))
|
|
822
1393
|
return { value: undefined, sawMedia: true, hasContent: false };
|
|
823
1394
|
const result = {};
|
|
824
1395
|
let sawMedia = false;
|
|
@@ -894,7 +1465,7 @@ function boundedRequestSnapshot(body, maxNodes = MAX_INPUT_ESTIMATE_NODES) {
|
|
|
894
1465
|
truncated = true;
|
|
895
1466
|
return result;
|
|
896
1467
|
}
|
|
897
|
-
if (!
|
|
1468
|
+
if (!isRecord2(value))
|
|
898
1469
|
return;
|
|
899
1470
|
const result = {};
|
|
900
1471
|
for (const key in value) {
|
|
@@ -962,7 +1533,7 @@ function hasOpenAIOutput(body) {
|
|
|
962
1533
|
if (Array.isArray(body.output) && body.output.length > 0)
|
|
963
1534
|
return true;
|
|
964
1535
|
return Array.isArray(body.choices) && body.choices.some((choice) => {
|
|
965
|
-
if (!
|
|
1536
|
+
if (!isRecord2(choice))
|
|
966
1537
|
return false;
|
|
967
1538
|
const message = readRecord(choice, "message") ?? readRecord(choice, "delta");
|
|
968
1539
|
return !!message && (message.content !== undefined && message.content !== null || Array.isArray(message.tool_calls) && message.tool_calls.length > 0);
|
|
@@ -986,13 +1557,13 @@ function hasGeminiOutput(body) {
|
|
|
986
1557
|
});
|
|
987
1558
|
}
|
|
988
1559
|
function isErrorResponse(body) {
|
|
989
|
-
const status =
|
|
990
|
-
const type =
|
|
1560
|
+
const status = asString2(body.status)?.toLowerCase();
|
|
1561
|
+
const type = asString2(body.type)?.toLowerCase();
|
|
991
1562
|
return body.error !== undefined || status === "error" || status === "failed" || type === "error" || type?.endsWith(".error") === true || type?.endsWith(".failed") === true;
|
|
992
1563
|
}
|
|
993
1564
|
function isDeferredFailure(body) {
|
|
994
|
-
const status =
|
|
995
|
-
const type =
|
|
1565
|
+
const status = asString2(body.status)?.toLowerCase();
|
|
1566
|
+
const type = asString2(body.type)?.toLowerCase();
|
|
996
1567
|
return isErrorResponse(body) || status === "incomplete" || type === "incomplete" || type?.endsWith(".incomplete") === true;
|
|
997
1568
|
}
|
|
998
1569
|
function observeOpenAIOutput(state, chunk, includeResponseSnapshot = true, event) {
|
|
@@ -1003,7 +1574,7 @@ function observeOpenAIOutput(state, chunk, includeResponseSnapshot = true, event
|
|
|
1003
1574
|
if (isDeferred(state))
|
|
1004
1575
|
state.estimateTruncated = true;
|
|
1005
1576
|
};
|
|
1006
|
-
const type = event ??
|
|
1577
|
+
const type = event ?? asString2(chunk.type);
|
|
1007
1578
|
if (type === "response.output_text.delta") {
|
|
1008
1579
|
if (isDeferred(state))
|
|
1009
1580
|
observeBoundedOutput(state, chunk.delta);
|
|
@@ -1035,7 +1606,7 @@ function observeOpenAIOutput(state, chunk, includeResponseSnapshot = true, event
|
|
|
1035
1606
|
const choiceLimit = Math.min(choices.length, nodes);
|
|
1036
1607
|
for (let index = 0;index < choiceLimit; index += 1, nodes -= 1) {
|
|
1037
1608
|
const choice = choices[index];
|
|
1038
|
-
if (!
|
|
1609
|
+
if (!isRecord2(choice))
|
|
1039
1610
|
continue;
|
|
1040
1611
|
const delta = readRecord(choice, "delta") ?? (isDeferred(state) ? readRecord(choice, "message") : undefined);
|
|
1041
1612
|
if (delta?.content !== undefined) {
|
|
@@ -1081,7 +1652,7 @@ function observeGeminiOutput(state, chunk) {
|
|
|
1081
1652
|
const partLimit = Math.min(parts.length, nodes);
|
|
1082
1653
|
for (let partIndex = 0;partIndex < partLimit; partIndex += 1, nodes -= 1) {
|
|
1083
1654
|
const part = parts[partIndex];
|
|
1084
|
-
if (!
|
|
1655
|
+
if (!isRecord2(part))
|
|
1085
1656
|
continue;
|
|
1086
1657
|
if (isDeferred(state) && (part.inlineData !== undefined || part.inline_data !== undefined || part.fileData !== undefined || part.file_data !== undefined)) {
|
|
1087
1658
|
state.estimateMediaSeen = true;
|
|
@@ -1106,7 +1677,7 @@ function observeGeminiOutput(state, chunk) {
|
|
|
1106
1677
|
state.estimateTruncated = true;
|
|
1107
1678
|
}
|
|
1108
1679
|
function countOpenAIStringTokens(value, model) {
|
|
1109
|
-
return countModelTextTokens(
|
|
1680
|
+
return countModelTextTokens(asString2(value) ?? "", model);
|
|
1110
1681
|
}
|
|
1111
1682
|
function countOpenAIJsonLikeTokens(value, model) {
|
|
1112
1683
|
if (typeof value === "string") {
|
|
@@ -1121,7 +1692,7 @@ function countOpenAIJsonLikeTokens(value, model) {
|
|
|
1121
1692
|
if (Array.isArray(value)) {
|
|
1122
1693
|
return value.reduce((total, item) => total + countOpenAIJsonLikeTokens(item, model), 0);
|
|
1123
1694
|
}
|
|
1124
|
-
if (
|
|
1695
|
+
if (isRecord2(value)) {
|
|
1125
1696
|
return Object.entries(value).reduce((total, [key, item]) => {
|
|
1126
1697
|
return total + countOpenAIStringTokens(key, model) + countOpenAIJsonLikeTokens(item, model);
|
|
1127
1698
|
}, 0);
|
|
@@ -1130,21 +1701,21 @@ function countOpenAIJsonLikeTokens(value, model) {
|
|
|
1130
1701
|
}
|
|
1131
1702
|
function estimateOpenAIImageTokens(part) {
|
|
1132
1703
|
const imageUrl = readRecord(part, "image_url");
|
|
1133
|
-
const detail =
|
|
1704
|
+
const detail = asString2(part.detail) ?? asString2(imageUrl?.detail);
|
|
1134
1705
|
if (detail === "low") {
|
|
1135
1706
|
return 85;
|
|
1136
1707
|
}
|
|
1137
1708
|
return 170;
|
|
1138
1709
|
}
|
|
1139
1710
|
function estimateOpenAIToolCallTokens(toolCall, model) {
|
|
1140
|
-
if (!
|
|
1711
|
+
if (!isRecord2(toolCall)) {
|
|
1141
1712
|
return countOpenAIJsonLikeTokens(toolCall, model);
|
|
1142
1713
|
}
|
|
1143
1714
|
const toolFunction = readRecord(toolCall, "function");
|
|
1144
1715
|
return 12 + countOpenAIStringTokens(toolCall.id, model) + countOpenAIStringTokens(toolCall.type, model) + countOpenAIStringTokens(toolFunction?.name, model) + countOpenAIStringTokens(toolFunction?.arguments, model) + countOpenAIJsonLikeTokens(toolCall, model);
|
|
1145
1716
|
}
|
|
1146
1717
|
function estimateOpenAIMessageTokens(message, model) {
|
|
1147
|
-
if (!
|
|
1718
|
+
if (!isRecord2(message)) {
|
|
1148
1719
|
return countOpenAIJsonLikeTokens(message, model);
|
|
1149
1720
|
}
|
|
1150
1721
|
const content = message.content;
|
|
@@ -1156,7 +1727,7 @@ function safeTokenSum(...values) {
|
|
|
1156
1727
|
return Number.isSafeInteger(total) && total >= 0 ? total : undefined;
|
|
1157
1728
|
}
|
|
1158
1729
|
function estimateGeminiInlineDataTokens(inlineData) {
|
|
1159
|
-
const mimeType =
|
|
1730
|
+
const mimeType = asString2(inlineData.mimeType)?.toLowerCase() ?? "";
|
|
1160
1731
|
if (mimeType.startsWith("image/")) {
|
|
1161
1732
|
return 224;
|
|
1162
1733
|
}
|
|
@@ -1169,10 +1740,10 @@ function estimateGeminiInlineDataTokens(inlineData) {
|
|
|
1169
1740
|
return 96;
|
|
1170
1741
|
}
|
|
1171
1742
|
function estimateGeminiFunctionCallTokens(functionCall) {
|
|
1172
|
-
return 12 + countTextTokens(
|
|
1743
|
+
return 12 + countTextTokens(asString2(functionCall.name) ?? "") + countJsonLikeTokens(functionCall.args);
|
|
1173
1744
|
}
|
|
1174
1745
|
function estimateGeminiFunctionResponseTokens(functionResponse) {
|
|
1175
|
-
return 12 + countTextTokens(
|
|
1746
|
+
return 12 + countTextTokens(asString2(functionResponse.name) ?? "") + countJsonLikeTokens(functionResponse.response);
|
|
1176
1747
|
}
|
|
1177
1748
|
function buildAnthropicStreamingEvent(state, overrides = {}) {
|
|
1178
1749
|
return createPartialTokenAccountingEvent(state, {
|
|
@@ -1193,10 +1764,10 @@ function estimateOpenAIMessageContent(content, model) {
|
|
|
1193
1764
|
return countOpenAIJsonLikeTokens(content, model);
|
|
1194
1765
|
}
|
|
1195
1766
|
return content.reduce((total, part) => {
|
|
1196
|
-
if (!
|
|
1767
|
+
if (!isRecord2(part)) {
|
|
1197
1768
|
return total + countOpenAIJsonLikeTokens(part, model);
|
|
1198
1769
|
}
|
|
1199
|
-
const type =
|
|
1770
|
+
const type = asString2(part.type);
|
|
1200
1771
|
if (type === "text") {
|
|
1201
1772
|
return total + countOpenAIStringTokens(part.text, model);
|
|
1202
1773
|
}
|
|
@@ -1207,7 +1778,7 @@ function estimateOpenAIMessageContent(content, model) {
|
|
|
1207
1778
|
}, 0);
|
|
1208
1779
|
}
|
|
1209
1780
|
function estimateOpenAIInputTokens(body) {
|
|
1210
|
-
const model =
|
|
1781
|
+
const model = asString2(body.model);
|
|
1211
1782
|
if (body.input !== undefined) {
|
|
1212
1783
|
return clampTokens(countOpenAIJsonLikeTokens(body.input, model) + 8) ?? 0;
|
|
1213
1784
|
}
|
|
@@ -1220,13 +1791,13 @@ function estimateOpenAIInputTokens(body) {
|
|
|
1220
1791
|
function estimateOpenAIResponseOutputTokens(body, model) {
|
|
1221
1792
|
const choices = Array.isArray(body.choices) ? body.choices : [];
|
|
1222
1793
|
return clampTokens(choices.reduce((total, choice) => {
|
|
1223
|
-
if (!
|
|
1794
|
+
if (!isRecord2(choice)) {
|
|
1224
1795
|
return total + countOpenAIJsonLikeTokens(choice, model);
|
|
1225
1796
|
}
|
|
1226
1797
|
const message = readRecord(choice, "message");
|
|
1227
1798
|
const delta = readRecord(choice, "delta");
|
|
1228
1799
|
const toolCalls = Array.isArray(message?.tool_calls) ? message.tool_calls : Array.isArray(delta?.tool_calls) ? delta.tool_calls : [];
|
|
1229
|
-
return total + countOpenAIStringTokens(message?.role ?? delta?.role, model) + countOpenAIStringTokens(message?.tool_call_id, model) + estimateOpenAIMessageContent(message?.content ?? delta?.content, model) + toolCalls.reduce((toolTotal, toolCall) => toolTotal + estimateOpenAIToolCallTokens(toolCall, model), 0) + (
|
|
1800
|
+
return total + countOpenAIStringTokens(message?.role ?? delta?.role, model) + countOpenAIStringTokens(message?.tool_call_id, model) + estimateOpenAIMessageContent(message?.content ?? delta?.content, model) + toolCalls.reduce((toolTotal, toolCall) => toolTotal + estimateOpenAIToolCallTokens(toolCall, model), 0) + (asString2(choice.finish_reason) === "tool_calls" ? 4 : 0);
|
|
1230
1801
|
}, 0)) ?? 0;
|
|
1231
1802
|
}
|
|
1232
1803
|
function openAIUsage(body) {
|
|
@@ -1235,11 +1806,11 @@ function openAIUsage(body) {
|
|
|
1235
1806
|
function estimateOpenAIResponsesOutputTokens(response, model) {
|
|
1236
1807
|
const output = Array.isArray(response.output) ? response.output : [];
|
|
1237
1808
|
return clampTokens(output.reduce((total, item) => {
|
|
1238
|
-
if (!
|
|
1809
|
+
if (!isRecord2(item))
|
|
1239
1810
|
return total + countOpenAIJsonLikeTokens(item, model);
|
|
1240
1811
|
const content = Array.isArray(item.content) ? item.content : [];
|
|
1241
1812
|
return total + content.reduce((contentTotal, part) => {
|
|
1242
|
-
if (
|
|
1813
|
+
if (isRecord2(part) && typeof part.text === "string")
|
|
1243
1814
|
return contentTotal + countOpenAIStringTokens(part.text, model);
|
|
1244
1815
|
return contentTotal + countOpenAIJsonLikeTokens(part, model);
|
|
1245
1816
|
}, 0) + countOpenAIStringTokens(item.arguments, model);
|
|
@@ -1249,7 +1820,7 @@ function isDoneSentinel(chunk) {
|
|
|
1249
1820
|
return chunk.data === "[DONE]" || chunk.type === "[DONE]";
|
|
1250
1821
|
}
|
|
1251
1822
|
function estimateAnthropicInputTokens(body) {
|
|
1252
|
-
const system =
|
|
1823
|
+
const system = asString2(body.system) ?? "";
|
|
1253
1824
|
const messages = Array.isArray(body.messages) ? body.messages : [];
|
|
1254
1825
|
const tools = Array.isArray(body.tools) ? body.tools : [];
|
|
1255
1826
|
return clampTokens(countTextTokens(system) + messages.reduce((total, message) => total + countJsonLikeTokens(message), 0) + tools.reduce((total, tool) => total + 16 + countJsonLikeTokens(tool), 0) + 8) ?? 0;
|
|
@@ -1259,19 +1830,19 @@ function estimateAnthropicOutputTokens(body) {
|
|
|
1259
1830
|
return clampTokens(content.reduce((total, block) => total + countJsonLikeTokens(block), 0)) ?? 0;
|
|
1260
1831
|
}
|
|
1261
1832
|
function estimateGeminiPartTokens(part) {
|
|
1262
|
-
if (!
|
|
1833
|
+
if (!isRecord2(part)) {
|
|
1263
1834
|
return countJsonLikeTokens(part);
|
|
1264
1835
|
}
|
|
1265
1836
|
if (typeof part.text === "string") {
|
|
1266
1837
|
return countTextTokens(part.text);
|
|
1267
1838
|
}
|
|
1268
|
-
if (
|
|
1839
|
+
if (isRecord2(part.inlineData)) {
|
|
1269
1840
|
return estimateGeminiInlineDataTokens(part.inlineData);
|
|
1270
1841
|
}
|
|
1271
|
-
if (
|
|
1842
|
+
if (isRecord2(part.functionCall)) {
|
|
1272
1843
|
return estimateGeminiFunctionCallTokens(part.functionCall);
|
|
1273
1844
|
}
|
|
1274
|
-
if (
|
|
1845
|
+
if (isRecord2(part.functionResponse)) {
|
|
1275
1846
|
return estimateGeminiFunctionResponseTokens(part.functionResponse);
|
|
1276
1847
|
}
|
|
1277
1848
|
return countJsonLikeTokens(part);
|
|
@@ -1281,7 +1852,7 @@ function estimateGeminiInputTokens(body) {
|
|
|
1281
1852
|
const systemParts = Array.isArray(systemInstruction?.parts) ? systemInstruction.parts : [];
|
|
1282
1853
|
const contents = Array.isArray(body.contents) ? body.contents : [];
|
|
1283
1854
|
return clampTokens(systemParts.reduce((total, part) => total + estimateGeminiPartTokens(part), 0) + contents.reduce((total, item) => {
|
|
1284
|
-
if (!
|
|
1855
|
+
if (!isRecord2(item)) {
|
|
1285
1856
|
return total + countJsonLikeTokens(item);
|
|
1286
1857
|
}
|
|
1287
1858
|
const parts = Array.isArray(item.parts) ? item.parts : [];
|
|
@@ -1291,7 +1862,7 @@ function estimateGeminiInputTokens(body) {
|
|
|
1291
1862
|
function estimateGeminiOutputTokens(body) {
|
|
1292
1863
|
const candidates = Array.isArray(body.candidates) ? body.candidates : [];
|
|
1293
1864
|
return clampTokens(candidates.reduce((total, candidate) => {
|
|
1294
|
-
if (!
|
|
1865
|
+
if (!isRecord2(candidate)) {
|
|
1295
1866
|
return total + countJsonLikeTokens(candidate);
|
|
1296
1867
|
}
|
|
1297
1868
|
const content = readRecord(candidate, "content");
|
|
@@ -1311,9 +1882,9 @@ function createOpenAIAdapter() {
|
|
|
1311
1882
|
const usage = openAIUsage(body);
|
|
1312
1883
|
const promptTokens = readNumber(usage, "prompt_tokens") ?? readNumber(usage, "input_tokens");
|
|
1313
1884
|
const completionTokens = readNumber(usage, "completion_tokens") ?? readNumber(usage, "output_tokens");
|
|
1314
|
-
const responseStatus =
|
|
1315
|
-
const isIncomplete = responseStatus === "incomplete" ||
|
|
1316
|
-
const isFailed = responseStatus === "failed" ||
|
|
1885
|
+
const responseStatus = asString2(readRecord(body, "response")?.status) ?? asString2(body.status);
|
|
1886
|
+
const isIncomplete = responseStatus === "incomplete" || asString2(body.type) === "response.incomplete";
|
|
1887
|
+
const isFailed = responseStatus === "failed" || asString2(body.type) === "response.failed";
|
|
1317
1888
|
const promptDetails = readRecord(usage, "prompt_tokens_details") ?? readRecord(usage, "input_tokens_details");
|
|
1318
1889
|
const cacheReadTokens = readNumber(promptDetails, "cached_tokens");
|
|
1319
1890
|
if (isDeferred(state)) {
|
|
@@ -1387,12 +1958,12 @@ function createOpenAIAdapter() {
|
|
|
1387
1958
|
const reportedCache = readNumber(promptDetails, "cached_tokens");
|
|
1388
1959
|
if (reportedCache !== undefined)
|
|
1389
1960
|
state.cacheReadTokens = reportedCache;
|
|
1390
|
-
const responseType = event ??
|
|
1391
|
-
const responseStatus =
|
|
1961
|
+
const responseType = event ?? asString2(chunk.type);
|
|
1962
|
+
const responseStatus = asString2(response?.status);
|
|
1392
1963
|
const responseCompleted = responseType === "response.completed" || responseStatus === "completed";
|
|
1393
1964
|
observeOpenAIOutput(state, chunk, !responseCompleted, event);
|
|
1394
1965
|
const choices = Array.isArray(chunk.choices) ? chunk.choices : [];
|
|
1395
|
-
if (choices.some((choice) =>
|
|
1966
|
+
if (choices.some((choice) => isRecord2(choice) && asString2(choice.finish_reason) !== undefined && choice.finish_reason !== null)) {
|
|
1396
1967
|
state.protocolTerminalSeen = true;
|
|
1397
1968
|
if (isDeferred(state))
|
|
1398
1969
|
state.deferredOutcome = "completed";
|
|
@@ -1561,7 +2132,7 @@ function createAnthropicAdapter() {
|
|
|
1561
2132
|
consumeStreamChunk(state, chunk, event) {
|
|
1562
2133
|
if (state.finalReceived || isDoneSentinel(chunk))
|
|
1563
2134
|
return null;
|
|
1564
|
-
const type = event ??
|
|
2135
|
+
const type = event ?? asString2(chunk.type);
|
|
1565
2136
|
if (isDeferred(state) && type === "error") {
|
|
1566
2137
|
state.protocolTerminalSeen = true;
|
|
1567
2138
|
state.deferredOutcome = "failed";
|
|
@@ -1607,7 +2178,7 @@ function createAnthropicAdapter() {
|
|
|
1607
2178
|
}
|
|
1608
2179
|
if (type === "content_block_delta") {
|
|
1609
2180
|
const delta = readRecord(chunk, "delta");
|
|
1610
|
-
const deltaType =
|
|
2181
|
+
const deltaType = asString2(delta?.type);
|
|
1611
2182
|
if (deltaType === "text_delta") {
|
|
1612
2183
|
if (isDeferred(state))
|
|
1613
2184
|
observeBoundedOutput(state, delta?.text);
|
|
@@ -1782,7 +2353,7 @@ function createGeminiAdapter() {
|
|
|
1782
2353
|
const toolUsePromptTokenCount = readNumber(usageMetadata, "toolUsePromptTokenCount") ?? 0;
|
|
1783
2354
|
const reportedCacheRead = readNumber(usageMetadata, "cachedContentTokenCount");
|
|
1784
2355
|
const candidates = Array.isArray(chunk.candidates) ? chunk.candidates : [];
|
|
1785
|
-
if (candidates.some((candidate) =>
|
|
2356
|
+
if (candidates.some((candidate) => isRecord2(candidate) && asString2(candidate.finishReason) !== undefined)) {
|
|
1786
2357
|
state.protocolTerminalSeen = true;
|
|
1787
2358
|
if (isDeferred(state))
|
|
1788
2359
|
state.deferredOutcome ??= "completed";
|
|
@@ -1924,18 +2495,28 @@ function createTokenAccountingSession(input, options = {}) {
|
|
|
1924
2495
|
}
|
|
1925
2496
|
};
|
|
1926
2497
|
}
|
|
2498
|
+
// packages/llms/src/responses-codec/common.ts
|
|
2499
|
+
var DEFAULT_RESPONSES_CODEC_LIMITS = Object.freeze({
|
|
2500
|
+
maxRequestBytes: 8 * 1024 * 1024,
|
|
2501
|
+
maxOutputBytes: 8 * 1024 * 1024,
|
|
2502
|
+
maxArgumentBytes: 1024 * 1024,
|
|
2503
|
+
maxItems: 1024
|
|
2504
|
+
});
|
|
2505
|
+
|
|
2506
|
+
// packages/llms/src/responses-codec/request.ts
|
|
2507
|
+
var contentNormalizer = new OpenAIProtocolConversion({ trimWhitespace: false });
|
|
1927
2508
|
// plugins/token-metering/server/classifier.ts
|
|
1928
|
-
function
|
|
2509
|
+
function record2(value) {
|
|
1929
2510
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1930
2511
|
}
|
|
1931
2512
|
function messages(value) {
|
|
1932
|
-
return Array.isArray(value) && value.length > 0 && value.every((message) =>
|
|
2513
|
+
return Array.isArray(value) && value.length > 0 && value.every((message) => record2(message) && typeof message.role === "string" && ["system", "developer", "user", "assistant", "tool", "function"].includes(message.role) && (typeof message.content === "string" || Array.isArray(message.content) || message.role === "assistant" && (message.content === null || Array.isArray(message.tool_calls))));
|
|
1933
2514
|
}
|
|
1934
2515
|
function input(value) {
|
|
1935
|
-
return typeof value === "string" || Array.isArray(value) && value.every((item) => typeof item === "string" ||
|
|
2516
|
+
return typeof value === "string" || Array.isArray(value) && value.every((item) => typeof item === "string" || record2(item) && (typeof item.type === "string" || typeof item.role === "string" && (typeof item.content === "string" || Array.isArray(item.content))));
|
|
1936
2517
|
}
|
|
1937
2518
|
function contents(value) {
|
|
1938
|
-
return Array.isArray(value) && value.length > 0 && value.every((content) =>
|
|
2519
|
+
return Array.isArray(value) && value.length > 0 && value.every((content) => record2(content) && Array.isArray(content.parts));
|
|
1939
2520
|
}
|
|
1940
2521
|
function classifyRequest(url, body) {
|
|
1941
2522
|
const path = url.pathname.toLowerCase().replace(/\/+$/, "");
|
|
@@ -1944,7 +2525,7 @@ function classifyRequest(url, body) {
|
|
|
1944
2525
|
const hasInput = input(body.input);
|
|
1945
2526
|
const hasPrompt = typeof body.prompt === "string" || Array.isArray(body.prompt) && body.prompt.length > 0 && body.prompt.every((item) => typeof item === "string");
|
|
1946
2527
|
const hasContents = contents(body.contents);
|
|
1947
|
-
if (hasContents && (/\/models\/[^/]+:(?:stream)?generatecontent$/.test(path) || hasModel ||
|
|
2528
|
+
if (hasContents && (/\/models\/[^/]+:(?:stream)?generatecontent$/.test(path) || hasModel || record2(body.generationConfig))) {
|
|
1948
2529
|
return { llm: true, provider: "gemini" };
|
|
1949
2530
|
}
|
|
1950
2531
|
if (hasModel && hasMessages && (path.endsWith("/messages") || typeof body.anthropic_version === "string")) {
|
|
@@ -1960,25 +2541,25 @@ function classifyRequest(url, body) {
|
|
|
1960
2541
|
}
|
|
1961
2542
|
function classifyResponse(body, knownLlmRequest, event) {
|
|
1962
2543
|
const eventType = event ?? (typeof body.type === "string" ? body.type : "");
|
|
1963
|
-
if (Array.isArray(body.candidates) && body.candidates.some((candidate) =>
|
|
2544
|
+
if (Array.isArray(body.candidates) && body.candidates.some((candidate) => record2(candidate) && record2(candidate.content) && Array.isArray(candidate.content.parts)))
|
|
1964
2545
|
return "gemini";
|
|
1965
2546
|
if (eventType === "message" && body.role === "assistant" && Array.isArray(body.content))
|
|
1966
2547
|
return "anthropic";
|
|
1967
|
-
if (eventType === "message_start" &&
|
|
2548
|
+
if (eventType === "message_start" && record2(body.message) && body.message.type === "message" && Array.isArray(body.message.content))
|
|
1968
2549
|
return "anthropic";
|
|
1969
|
-
if (eventType === "content_block_delta" &&
|
|
2550
|
+
if (eventType === "content_block_delta" && record2(body.delta) && ["text_delta", "input_json_delta", "thinking_delta", "signature_delta"].includes(String(body.delta.type)))
|
|
1970
2551
|
return "anthropic";
|
|
1971
|
-
if (Array.isArray(body.choices) && (body.object === "chat.completion" || body.object === "chat.completion.chunk" || body.object === "text_completion" || body.choices.some((choice) =>
|
|
2552
|
+
if (Array.isArray(body.choices) && (body.object === "chat.completion" || body.object === "chat.completion.chunk" || body.object === "text_completion" || body.choices.some((choice) => record2(choice) && (record2(choice.message) && choice.message.role === "assistant" && (typeof choice.message.content === "string" || Array.isArray(choice.message.content) || Array.isArray(choice.message.tool_calls)) || record2(choice.delta) && (choice.delta.role === "assistant" || typeof choice.delta.content === "string" || typeof choice.delta.reasoning_content === "string" || Array.isArray(choice.delta.tool_calls) || record2(choice.delta.function_call)) || typeof choice.text === "string" && typeof choice.index === "number"))))
|
|
1972
2553
|
return "openai";
|
|
1973
2554
|
if (body.object === "response" && Array.isArray(body.output))
|
|
1974
2555
|
return "openai";
|
|
1975
|
-
if (eventType.startsWith("response.") && (
|
|
2556
|
+
if (eventType.startsWith("response.") && (record2(body.response) || record2(body.item) || typeof body.delta === "string" || typeof body.output_index === "number"))
|
|
1976
2557
|
return "openai";
|
|
1977
2558
|
if (!knownLlmRequest)
|
|
1978
2559
|
return;
|
|
1979
|
-
if (
|
|
2560
|
+
if (record2(body.usageMetadata))
|
|
1980
2561
|
return "gemini";
|
|
1981
|
-
const usage =
|
|
2562
|
+
const usage = record2(body.usage) ? body.usage : undefined;
|
|
1982
2563
|
if (eventType.startsWith("message_") || eventType.startsWith("content_block_") || usage && (("cache_creation_input_tokens" in usage) || ("cache_read_input_tokens" in usage)))
|
|
1983
2564
|
return "anthropic";
|
|
1984
2565
|
if (usage && (("prompt_tokens" in usage) || ("completion_tokens" in usage)))
|
|
@@ -1992,7 +2573,7 @@ var MAX_CONNECTIONS = 128;
|
|
|
1992
2573
|
var MAX_RESPONSES = 1024;
|
|
1993
2574
|
var MAX_CONNECTION_RESPONSES = 64;
|
|
1994
2575
|
var MAX_TOMBSTONES = 256;
|
|
1995
|
-
var
|
|
2576
|
+
var record3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1996
2577
|
var identifier = (value) => typeof value === "string" && value.length > 0 && value.length <= 256 ? value : undefined;
|
|
1997
2578
|
var count = (value) => typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
|
|
1998
2579
|
function metadata(event) {
|
|
@@ -2006,7 +2587,7 @@ function responsesUrl(value) {
|
|
|
2006
2587
|
}
|
|
2007
2588
|
}
|
|
2008
2589
|
function stream(body) {
|
|
2009
|
-
return identifier(body.stream_id) ?? (
|
|
2590
|
+
return identifier(body.stream_id) ?? (record3(body.response) ? identifier(body.response.stream_id) : undefined);
|
|
2010
2591
|
}
|
|
2011
2592
|
|
|
2012
2593
|
class ResponsesWebSocketMetering {
|
|
@@ -2056,7 +2637,7 @@ class ResponsesWebSocketMetering {
|
|
|
2056
2637
|
if (connection.disabled || event.phase !== "message" || event.message.kind !== "text")
|
|
2057
2638
|
return;
|
|
2058
2639
|
const body = event.message.json();
|
|
2059
|
-
if (!
|
|
2640
|
+
if (!record3(body))
|
|
2060
2641
|
return;
|
|
2061
2642
|
if (event.direction === "client") {
|
|
2062
2643
|
if (body.type !== "response.create" || !(typeof body.input === "string" || Array.isArray(body.input) || identifier(body.previous_response_id) || identifier(body.model)))
|
|
@@ -2073,7 +2654,7 @@ class ResponsesWebSocketMetering {
|
|
|
2073
2654
|
const type = identifier(body.type);
|
|
2074
2655
|
if (!type?.startsWith("response."))
|
|
2075
2656
|
return;
|
|
2076
|
-
const envelope =
|
|
2657
|
+
const envelope = record3(body.response) ? body.response : undefined;
|
|
2077
2658
|
const terminal = ["response.completed", "response.failed", "response.incomplete"].includes(type);
|
|
2078
2659
|
if (terminal && (!envelope || envelope.object !== "response" || !Array.isArray(envelope.output)))
|
|
2079
2660
|
return;
|
|
@@ -2101,7 +2682,7 @@ class ResponsesWebSocketMetering {
|
|
|
2101
2682
|
return;
|
|
2102
2683
|
connection.active.set(responseId, response);
|
|
2103
2684
|
}
|
|
2104
|
-
const usage =
|
|
2685
|
+
const usage = record3(envelope?.usage) ? envelope.usage : undefined;
|
|
2105
2686
|
if (!response.bound && !await this.bind(response, event, responseId))
|
|
2106
2687
|
return;
|
|
2107
2688
|
if (!await this.emit(response, event, { phase: "response", status: 200, protocol: "sse", body }))
|
|
@@ -2193,11 +2774,11 @@ class ResponsesWebSocketMetering {
|
|
|
2193
2774
|
var MAX_ACTIVE_ATTEMPTS = 1024;
|
|
2194
2775
|
var LOG_INTERVAL_MS = 60000;
|
|
2195
2776
|
var lastCapacityWarningAt = 0;
|
|
2196
|
-
function
|
|
2777
|
+
function isRecord3(value) {
|
|
2197
2778
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2198
2779
|
}
|
|
2199
2780
|
function parseRequestBody(value) {
|
|
2200
|
-
return
|
|
2781
|
+
return isRecord3(value) ? value : undefined;
|
|
2201
2782
|
}
|
|
2202
2783
|
function detectModel(body, url) {
|
|
2203
2784
|
if (typeof body.model === "string" && body.model.length > 0)
|
|
@@ -2567,7 +3148,7 @@ var TokenMeteringPlugin = definePlugin(class {
|
|
|
2567
3148
|
if (event.phase === "response") {
|
|
2568
3149
|
attempt.responseSeen = true;
|
|
2569
3150
|
const body = event.body;
|
|
2570
|
-
if (websocket &&
|
|
3151
|
+
if (websocket && isRecord3(body.response)) {
|
|
2571
3152
|
const model = detectModel(body.response);
|
|
2572
3153
|
if (model) {
|
|
2573
3154
|
attempt.model = model;
|
|
@@ -2579,7 +3160,7 @@ var TokenMeteringPlugin = definePlugin(class {
|
|
|
2579
3160
|
if (!provider)
|
|
2580
3161
|
return;
|
|
2581
3162
|
if (!attempt.llm) {
|
|
2582
|
-
attempt.model = detectModel(body) ?? (
|
|
3163
|
+
attempt.model = detectModel(body) ?? (isRecord3(body.response) ? detectModel(body.response) : undefined);
|
|
2583
3164
|
}
|
|
2584
3165
|
attempt.llm = true;
|
|
2585
3166
|
if (!attempt.session) {
|
|
@@ -2664,4 +3245,4 @@ export {
|
|
|
2664
3245
|
server_default as default
|
|
2665
3246
|
};
|
|
2666
3247
|
|
|
2667
|
-
//# debugId=
|
|
3248
|
+
//# debugId=BE427C2BCB66F77664756E2164756E21
|