llm-messages 0.5.1 → 0.5.2
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/CHANGELOG.md +119 -1
- package/README.md +79 -34
- package/ROADMAP.md +3 -3
- package/SECURITY.md +24 -0
- package/dist/index.cjs +643 -93
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +11 -9
- package/dist/index.d.ts +11 -9
- package/dist/index.js +641 -92
- package/dist/index.js.map +1 -1
- package/docs/adoption-guide.md +7 -0
- package/docs/conformance-fixtures.md +113 -13
- package/examples/commonjs.cjs +34 -0
- package/examples/usage.mjs +19 -4
- package/package.json +8 -2
package/dist/index.js
CHANGED
|
@@ -17,6 +17,7 @@ function textOf(content) {
|
|
|
17
17
|
return content.map((part) => {
|
|
18
18
|
if (typeof part === "string") return part;
|
|
19
19
|
if (isRecord(part) && typeof part.text === "string") return part.text;
|
|
20
|
+
if (isRecord(part) && part.type === "refusal" && typeof part.refusal === "string") return part.refusal;
|
|
20
21
|
return "";
|
|
21
22
|
}).join("");
|
|
22
23
|
}
|
|
@@ -38,17 +39,83 @@ function parseArguments(args, reporter, fnName) {
|
|
|
38
39
|
);
|
|
39
40
|
return {};
|
|
40
41
|
}
|
|
42
|
+
function stringifyArgumentsObject(value, reporter, provider, part, fnName) {
|
|
43
|
+
if (value === void 0) return "{}";
|
|
44
|
+
if (isRecord(value)) {
|
|
45
|
+
try {
|
|
46
|
+
return JSON.stringify(value);
|
|
47
|
+
} catch {
|
|
48
|
+
reporter.warn(
|
|
49
|
+
"invalid-json-arguments",
|
|
50
|
+
`${provider} ${part} '${fnName}' had arguments that could not be serialized as JSON; used an empty object instead.`
|
|
51
|
+
);
|
|
52
|
+
return "{}";
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
reporter.warn(
|
|
56
|
+
"invalid-json-arguments",
|
|
57
|
+
`${provider} ${part} '${fnName}' had arguments that were not an object; used an empty object instead.`
|
|
58
|
+
);
|
|
59
|
+
return "{}";
|
|
60
|
+
}
|
|
61
|
+
var OPENAI_FUNCTION_NAME = /^[a-zA-Z0-9_-]{1,64}$/;
|
|
62
|
+
function isProviderFunctionName(value) {
|
|
63
|
+
return typeof value === "string" && OPENAI_FUNCTION_NAME.test(value);
|
|
64
|
+
}
|
|
65
|
+
function providerFunctionName(value, reporter, provider, part) {
|
|
66
|
+
if (isProviderFunctionName(value)) return value;
|
|
67
|
+
reporter.warn(
|
|
68
|
+
"dropped-metadata",
|
|
69
|
+
`${provider} ${part} had a missing or invalid function name; used 'unknown_function'.`
|
|
70
|
+
);
|
|
71
|
+
return "unknown_function";
|
|
72
|
+
}
|
|
73
|
+
function createToolCallIdGenerator(reservedIds = []) {
|
|
74
|
+
const reserved = new Set(reservedIds);
|
|
75
|
+
const used = /* @__PURE__ */ new Set();
|
|
76
|
+
let counter = 0;
|
|
77
|
+
const generate = (name) => {
|
|
78
|
+
let id;
|
|
79
|
+
do {
|
|
80
|
+
id = `call_${name.replace(/[^a-zA-Z0-9_-]/g, "_")}_${counter++}`;
|
|
81
|
+
} while (used.has(id) || reserved.has(id));
|
|
82
|
+
used.add(id);
|
|
83
|
+
return id;
|
|
84
|
+
};
|
|
85
|
+
return {
|
|
86
|
+
claim(id, name) {
|
|
87
|
+
if (used.has(id)) return generate(name);
|
|
88
|
+
used.add(id);
|
|
89
|
+
reserved.delete(id);
|
|
90
|
+
return id;
|
|
91
|
+
},
|
|
92
|
+
generate(name) {
|
|
93
|
+
return generate(name);
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
}
|
|
41
97
|
function wrapResponse(content) {
|
|
42
98
|
const parsed = tryParseJson(content);
|
|
43
99
|
if (parsed.ok && isRecord(parsed.value)) return parsed.value;
|
|
44
100
|
return { result: content };
|
|
45
101
|
}
|
|
46
|
-
function unwrapResponse(response) {
|
|
47
|
-
|
|
48
|
-
if (
|
|
49
|
-
|
|
102
|
+
function unwrapResponse(response, reporter, provider = "Provider", part = "response") {
|
|
103
|
+
if (response === void 0) return "{}";
|
|
104
|
+
if (isRecord(response)) {
|
|
105
|
+
const keys = Object.keys(response);
|
|
106
|
+
if (keys.length === 1 && keys[0] === "result" && typeof response.result === "string") {
|
|
107
|
+
return response.result;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
try {
|
|
111
|
+
return JSON.stringify(response) ?? "{}";
|
|
112
|
+
} catch {
|
|
113
|
+
reporter?.warn(
|
|
114
|
+
"dropped-content",
|
|
115
|
+
`${provider} ${part} response could not be serialized as JSON; used an empty object instead.`
|
|
116
|
+
);
|
|
117
|
+
return "{}";
|
|
50
118
|
}
|
|
51
|
-
return JSON.stringify(response);
|
|
52
119
|
}
|
|
53
120
|
|
|
54
121
|
// src/image.ts
|
|
@@ -267,6 +334,7 @@ function splitSystem(messages, reporter) {
|
|
|
267
334
|
}
|
|
268
335
|
|
|
269
336
|
// src/providers/anthropic.ts
|
|
337
|
+
var nonEmptyString = (value) => typeof value === "string" && value.length > 0;
|
|
270
338
|
function toAnthropic(messages, options = {}) {
|
|
271
339
|
const reporter = new Reporter(options);
|
|
272
340
|
const { system, rest } = splitSystem(messages, reporter);
|
|
@@ -328,7 +396,7 @@ function userContent(content, reporter) {
|
|
|
328
396
|
}
|
|
329
397
|
reporter.warn("dropped-content", "Dropped an unsupported user content part.");
|
|
330
398
|
}
|
|
331
|
-
return blocks;
|
|
399
|
+
return blocks.length > 0 ? blocks : "";
|
|
332
400
|
}
|
|
333
401
|
function assistantContent(message, reporter) {
|
|
334
402
|
warnDroppedName("Assistant", message.name, "Anthropic", reporter);
|
|
@@ -368,17 +436,59 @@ function mergeConsecutive(messages, reporter) {
|
|
|
368
436
|
return result;
|
|
369
437
|
}
|
|
370
438
|
function asBlocks(content) {
|
|
371
|
-
if (
|
|
439
|
+
if (Array.isArray(content)) return content.filter(isRecord);
|
|
440
|
+
if (typeof content !== "string") return [];
|
|
372
441
|
return content ? [{ type: "text", text: content }] : [];
|
|
373
442
|
}
|
|
443
|
+
function warnMalformedBlocks(content, reporter) {
|
|
444
|
+
if (Array.isArray(content)) {
|
|
445
|
+
for (const block of content) {
|
|
446
|
+
if (!isRecord(block)) {
|
|
447
|
+
reporter.warn("dropped-content", "Dropped a malformed Anthropic content block.");
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
return;
|
|
451
|
+
}
|
|
452
|
+
if (typeof content !== "string") {
|
|
453
|
+
reporter.warn("dropped-content", "Dropped malformed Anthropic message content.");
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
function isSupportedUserBlock(block) {
|
|
457
|
+
return block.type === "text" && typeof block.text === "string" || block.type === "tool_result" || imageFromAnthropic(block) !== null || mediaFromAnthropic(block) !== null;
|
|
458
|
+
}
|
|
459
|
+
function isSupportedAssistantBlock(block) {
|
|
460
|
+
return block.type === "text" && typeof block.text === "string" || block.type === "tool_use";
|
|
461
|
+
}
|
|
462
|
+
function warnUnsupportedBlocks(blocks, role, reporter) {
|
|
463
|
+
const isSupported = role === "user" ? isSupportedUserBlock : isSupportedAssistantBlock;
|
|
464
|
+
for (const block of blocks) {
|
|
465
|
+
if (isSupported(block)) continue;
|
|
466
|
+
reporter.warn("dropped-content", `Dropped unsupported Anthropic ${role} content block '${String(block.type)}'.`);
|
|
467
|
+
}
|
|
468
|
+
}
|
|
374
469
|
function fromAnthropic(conversation, options = {}) {
|
|
375
470
|
const reporter = new Reporter(options);
|
|
376
471
|
const out = [];
|
|
377
|
-
|
|
378
|
-
|
|
472
|
+
const root = isRecord(conversation) ? conversation : {};
|
|
473
|
+
const system = textOf(root.system);
|
|
474
|
+
if (system) {
|
|
475
|
+
out.push({ role: "system", content: system });
|
|
379
476
|
}
|
|
380
|
-
|
|
477
|
+
const messages = Array.isArray(root.messages) ? root.messages : [];
|
|
478
|
+
const ids = createToolCallIdGenerator(anthropicProviderIds(root));
|
|
479
|
+
const pendingToolUseIds = /* @__PURE__ */ new Map();
|
|
480
|
+
for (const message of messages) {
|
|
481
|
+
if (!isRecord(message)) {
|
|
482
|
+
reporter.warn("dropped-content", "Dropped a malformed Anthropic message.");
|
|
483
|
+
continue;
|
|
484
|
+
}
|
|
485
|
+
if (message.role !== "user" && message.role !== "assistant") {
|
|
486
|
+
reporter.warn("dropped-content", `Dropped an Anthropic message with unsupported role '${String(message.role)}'.`);
|
|
487
|
+
continue;
|
|
488
|
+
}
|
|
489
|
+
warnMalformedBlocks(message.content, reporter);
|
|
381
490
|
const blocks = asBlocks(message.content);
|
|
491
|
+
warnUnsupportedBlocks(blocks, message.role, reporter);
|
|
382
492
|
if (message.role === "user") {
|
|
383
493
|
if (blocks.length === 0) {
|
|
384
494
|
out.push({ role: "user", content: "" });
|
|
@@ -397,9 +507,28 @@ function fromAnthropic(conversation, options = {}) {
|
|
|
397
507
|
continue;
|
|
398
508
|
}
|
|
399
509
|
flushContent();
|
|
510
|
+
const rawToolUseId = block.tool_use_id;
|
|
511
|
+
const hasToolUseId = nonEmptyString(rawToolUseId);
|
|
512
|
+
const matchedToolCallId = hasToolUseId ? takePendingToolUseId(pendingToolUseIds, rawToolUseId) : void 0;
|
|
513
|
+
const toolCallId = matchedToolCallId ?? (hasToolUseId ? ids.claim(rawToolUseId, "tool_result") : ids.generate("tool_result"));
|
|
514
|
+
if (!hasToolUseId) {
|
|
515
|
+
reporter.warn(
|
|
516
|
+
"unmapped-tool-result",
|
|
517
|
+
`Anthropic tool_result had no usable tool_use_id; generated '${toolCallId}'.`
|
|
518
|
+
);
|
|
519
|
+
} else if (!matchedToolCallId) {
|
|
520
|
+
const unmappedMessage = toolCallId === rawToolUseId ? `Anthropic tool_result '${rawToolUseId}' had no matching tool_use; kept the result id.` : `Anthropic tool_result '${rawToolUseId}' had no matching tool_use; generated '${toolCallId}'.`;
|
|
521
|
+
reporter.warn("unmapped-tool-result", unmappedMessage);
|
|
522
|
+
if (toolCallId !== rawToolUseId) {
|
|
523
|
+
reporter.warn(
|
|
524
|
+
"generated-id",
|
|
525
|
+
`Anthropic tool_result '${rawToolUseId}' reused an existing id; generated '${toolCallId}'.`
|
|
526
|
+
);
|
|
527
|
+
}
|
|
528
|
+
}
|
|
400
529
|
out.push({
|
|
401
530
|
role: "tool",
|
|
402
|
-
tool_call_id:
|
|
531
|
+
tool_call_id: toolCallId,
|
|
403
532
|
content: textOf(block.content),
|
|
404
533
|
...typeof block.is_error === "boolean" ? { is_error: block.is_error } : {}
|
|
405
534
|
});
|
|
@@ -413,13 +542,57 @@ function fromAnthropic(conversation, options = {}) {
|
|
|
413
542
|
if (toolUses.length > 0) {
|
|
414
543
|
assistant.tool_calls = toolUses.map((block) => {
|
|
415
544
|
const b = block;
|
|
416
|
-
|
|
545
|
+
const name = providerFunctionName(b.name, reporter, "Anthropic", "tool_use");
|
|
546
|
+
const providedId = nonEmptyString(b.id) ? b.id : void 0;
|
|
547
|
+
const id = providedId ? ids.claim(providedId, name) : ids.generate(name);
|
|
548
|
+
if (!providedId) {
|
|
549
|
+
reporter.warn("generated-id", `Anthropic tool_use '${name}' had no id; generated '${id}'.`);
|
|
550
|
+
} else if (id !== providedId) {
|
|
551
|
+
reporter.warn("generated-id", `Anthropic tool_use '${name}' reused id '${providedId}'; generated '${id}'.`);
|
|
552
|
+
}
|
|
553
|
+
if (providedId) pushPendingToolUseId(pendingToolUseIds, providedId, id);
|
|
554
|
+
return {
|
|
555
|
+
id,
|
|
556
|
+
type: "function",
|
|
557
|
+
function: {
|
|
558
|
+
name,
|
|
559
|
+
arguments: stringifyArgumentsObject(b.input, reporter, "Anthropic", "tool_use", name)
|
|
560
|
+
}
|
|
561
|
+
};
|
|
417
562
|
});
|
|
418
563
|
}
|
|
419
564
|
out.push(assistant);
|
|
420
565
|
}
|
|
421
566
|
return out;
|
|
422
567
|
}
|
|
568
|
+
function pushPendingToolUseId(pending, providerId, normalizedId) {
|
|
569
|
+
const ids = pending.get(providerId);
|
|
570
|
+
if (ids) ids.push(normalizedId);
|
|
571
|
+
else pending.set(providerId, [normalizedId]);
|
|
572
|
+
}
|
|
573
|
+
function takePendingToolUseId(pending, providerId) {
|
|
574
|
+
const ids = pending.get(providerId);
|
|
575
|
+
const id = ids?.shift();
|
|
576
|
+
if (ids?.length === 0) pending.delete(providerId);
|
|
577
|
+
return id;
|
|
578
|
+
}
|
|
579
|
+
function anthropicProviderIds(conversation) {
|
|
580
|
+
const ids = [];
|
|
581
|
+
const root = isRecord(conversation) ? conversation : {};
|
|
582
|
+
const messages = Array.isArray(root.messages) ? root.messages : [];
|
|
583
|
+
for (const message of messages) {
|
|
584
|
+
if (!isRecord(message)) continue;
|
|
585
|
+
for (const block of asBlocks(message.content)) {
|
|
586
|
+
if (block.type === "tool_use" && nonEmptyString(block.id)) {
|
|
587
|
+
ids.push(block.id);
|
|
588
|
+
}
|
|
589
|
+
if (block.type === "tool_result" && nonEmptyString(block.tool_use_id)) {
|
|
590
|
+
ids.push(block.tool_use_id);
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
return ids;
|
|
595
|
+
}
|
|
423
596
|
function userContentToOpenAI(blocks, reporter) {
|
|
424
597
|
const hasMedia = blocks.some((block) => imageFromAnthropic(block) !== null || mediaFromAnthropic(block) !== null);
|
|
425
598
|
if (!hasMedia) return textOf(blocks);
|
|
@@ -441,10 +614,11 @@ function userContentToOpenAI(blocks, reporter) {
|
|
|
441
614
|
parts.push({ type: "text", text: block.text });
|
|
442
615
|
}
|
|
443
616
|
}
|
|
444
|
-
return parts;
|
|
617
|
+
return parts.length > 0 ? parts : "";
|
|
445
618
|
}
|
|
446
619
|
|
|
447
620
|
// src/providers/gemini.ts
|
|
621
|
+
var nonEmptyString2 = (value) => typeof value === "string" && value.length > 0;
|
|
448
622
|
function toGemini(messages, options = {}) {
|
|
449
623
|
const reporter = new Reporter(options);
|
|
450
624
|
const { system, rest } = splitSystem(messages, reporter);
|
|
@@ -475,8 +649,9 @@ function toGemini(messages, options = {}) {
|
|
|
475
649
|
`Tool message name '${tool.name}' differs from matching tool call '${matchingName}'; used the tool-call function name for Gemini.`
|
|
476
650
|
);
|
|
477
651
|
}
|
|
478
|
-
const
|
|
479
|
-
|
|
652
|
+
const hasStandaloneName = nonEmptyString2(tool.name);
|
|
653
|
+
const name = matchingName ?? (hasStandaloneName ? tool.name : tool.tool_call_id);
|
|
654
|
+
if (!matchingName && !hasStandaloneName) {
|
|
480
655
|
reporter.warn(
|
|
481
656
|
"unmapped-tool-result",
|
|
482
657
|
`Tool result '${tool.tool_call_id}' has no matching call; used the id as the function name.`
|
|
@@ -564,31 +739,51 @@ function mergeConsecutive2(contents, reporter) {
|
|
|
564
739
|
function fromGemini(conversation, options = {}) {
|
|
565
740
|
const reporter = new Reporter(options);
|
|
566
741
|
const out = [];
|
|
567
|
-
|
|
568
|
-
|
|
742
|
+
const root = isRecord(conversation) ? conversation : {};
|
|
743
|
+
const systemInstruction = isRecord(root.systemInstruction) ? root.systemInstruction : void 0;
|
|
744
|
+
if (systemInstruction) {
|
|
745
|
+
const text = textOf(systemInstruction.parts);
|
|
569
746
|
if (text) out.push({ role: "system", content: text });
|
|
570
747
|
}
|
|
571
748
|
const pending = [];
|
|
572
|
-
|
|
573
|
-
const
|
|
574
|
-
for (const content of
|
|
575
|
-
|
|
749
|
+
const contents = Array.isArray(root.contents) ? root.contents : [];
|
|
750
|
+
const ids = createToolCallIdGenerator(geminiProviderIds(root));
|
|
751
|
+
for (const content of contents) {
|
|
752
|
+
if (!isRecord(content)) {
|
|
753
|
+
reporter.warn("dropped-content", "Dropped a malformed Gemini content entry.");
|
|
754
|
+
continue;
|
|
755
|
+
}
|
|
576
756
|
if (content.role === "model") {
|
|
757
|
+
const parts2 = geminiParts(content, reporter);
|
|
577
758
|
const textPieces = [];
|
|
578
759
|
const toolCalls = [];
|
|
579
|
-
for (const part of
|
|
760
|
+
for (const part of parts2) {
|
|
580
761
|
if (isRecord(part) && isRecord(part.functionCall)) {
|
|
581
762
|
const fc = part.functionCall;
|
|
582
|
-
const
|
|
583
|
-
|
|
763
|
+
const name = providerFunctionName(fc.name, reporter, "Gemini", "functionCall");
|
|
764
|
+
const providedId = nonEmptyString2(fc.id) ? fc.id : void 0;
|
|
765
|
+
const id = providedId ? ids.claim(providedId, name) : ids.generate(name);
|
|
766
|
+
if (!providedId) {
|
|
767
|
+
reporter.warn("generated-id", `Gemini functionCall '${name}' had no id; generated '${id}'.`);
|
|
768
|
+
} else if (id !== providedId) {
|
|
769
|
+
reporter.warn(
|
|
770
|
+
"generated-id",
|
|
771
|
+
`Gemini functionCall '${name}' reused id '${providedId}'; generated '${id}'.`
|
|
772
|
+
);
|
|
773
|
+
}
|
|
584
774
|
toolCalls.push({
|
|
585
775
|
id,
|
|
586
776
|
type: "function",
|
|
587
|
-
function: {
|
|
777
|
+
function: {
|
|
778
|
+
name,
|
|
779
|
+
arguments: stringifyArgumentsObject(fc.args, reporter, "Gemini", "functionCall", name)
|
|
780
|
+
}
|
|
588
781
|
});
|
|
589
|
-
pending.push({ id, name:
|
|
782
|
+
pending.push({ id, name, ...providedId ? { providerId: providedId } : {} });
|
|
590
783
|
} else if (isRecord(part) && typeof part.text === "string") {
|
|
591
784
|
textPieces.push(part.text);
|
|
785
|
+
} else {
|
|
786
|
+
reporter.warn("dropped-content", "Dropped an unsupported Gemini model content part.");
|
|
592
787
|
}
|
|
593
788
|
}
|
|
594
789
|
const text = textPieces.join("");
|
|
@@ -597,17 +792,45 @@ function fromGemini(conversation, options = {}) {
|
|
|
597
792
|
out.push(assistant);
|
|
598
793
|
continue;
|
|
599
794
|
}
|
|
600
|
-
|
|
795
|
+
if (content.role !== void 0 && content.role !== "user") {
|
|
796
|
+
reporter.warn(
|
|
797
|
+
"dropped-content",
|
|
798
|
+
`Dropped a Gemini content entry with unsupported role '${String(content.role)}'.`
|
|
799
|
+
);
|
|
800
|
+
continue;
|
|
801
|
+
}
|
|
802
|
+
const parts = geminiParts(content, reporter);
|
|
803
|
+
let contentParts = [];
|
|
601
804
|
let hasMedia = false;
|
|
805
|
+
let sawUserContent = false;
|
|
806
|
+
const flushContent = () => {
|
|
807
|
+
if (!sawUserContent) return;
|
|
808
|
+
if (contentParts.length === 0) {
|
|
809
|
+
out.push({ role: "user", content: "" });
|
|
810
|
+
} else if (hasMedia) {
|
|
811
|
+
out.push({ role: "user", content: contentParts });
|
|
812
|
+
} else {
|
|
813
|
+
const text = textOf(contentParts);
|
|
814
|
+
out.push({ role: "user", content: text });
|
|
815
|
+
}
|
|
816
|
+
contentParts = [];
|
|
817
|
+
hasMedia = false;
|
|
818
|
+
sawUserContent = false;
|
|
819
|
+
};
|
|
602
820
|
for (const part of parts) {
|
|
603
821
|
if (isRecord(part) && isRecord(part.functionResponse)) {
|
|
604
822
|
const fr = part.functionResponse;
|
|
605
|
-
const
|
|
823
|
+
const response = Object.prototype.hasOwnProperty.call(fr, "response") ? fr.response : {};
|
|
824
|
+
const responseId = nonEmptyString2(fr.id) ? fr.id : void 0;
|
|
825
|
+
const matchedName = responseId ? pending.find((pendingCall) => pendingCall.providerId === responseId || pendingCall.id === responseId)?.name : void 0;
|
|
826
|
+
const name = geminiFunctionResponseName(fr.name, matchedName, reporter);
|
|
827
|
+
const { id, matched } = resolveResponseId({ id: fr.id, name }, pending, reporter, ids);
|
|
828
|
+
flushContent();
|
|
606
829
|
out.push({
|
|
607
830
|
role: "tool",
|
|
608
831
|
tool_call_id: id,
|
|
609
|
-
content: unwrapResponse(
|
|
610
|
-
...matched ? {} : { name
|
|
832
|
+
content: unwrapResponse(response, reporter, "Gemini", "functionResponse"),
|
|
833
|
+
...matched ? {} : { name }
|
|
611
834
|
});
|
|
612
835
|
continue;
|
|
613
836
|
}
|
|
@@ -615,6 +838,7 @@ function fromGemini(conversation, options = {}) {
|
|
|
615
838
|
if (image) {
|
|
616
839
|
contentParts.push(imageToOpenAI(image));
|
|
617
840
|
hasMedia = true;
|
|
841
|
+
sawUserContent = true;
|
|
618
842
|
continue;
|
|
619
843
|
}
|
|
620
844
|
const media = mediaFromGemini(part);
|
|
@@ -623,29 +847,74 @@ function fromGemini(conversation, options = {}) {
|
|
|
623
847
|
if (openaiPart) {
|
|
624
848
|
contentParts.push(openaiPart);
|
|
625
849
|
hasMedia = true;
|
|
850
|
+
} else {
|
|
851
|
+
reporter.warn(
|
|
852
|
+
"dropped-content",
|
|
853
|
+
`A Gemini ${media.modality} ${media.source.kind} has no OpenAI Chat Completions equivalent; dropped.`
|
|
854
|
+
);
|
|
626
855
|
}
|
|
856
|
+
sawUserContent = true;
|
|
627
857
|
continue;
|
|
628
858
|
}
|
|
629
859
|
if (isRecord(part) && typeof part.text === "string") {
|
|
630
860
|
contentParts.push({ type: "text", text: part.text });
|
|
861
|
+
sawUserContent = true;
|
|
862
|
+
continue;
|
|
631
863
|
}
|
|
864
|
+
reporter.warn("dropped-content", "Dropped an unsupported Gemini user content part.");
|
|
632
865
|
}
|
|
633
|
-
|
|
634
|
-
if (hasMedia) {
|
|
635
|
-
out.push({ role: "user", content: contentParts });
|
|
636
|
-
} else {
|
|
637
|
-
const text = textOf(contentParts);
|
|
638
|
-
out.push({ role: "user", content: text });
|
|
639
|
-
}
|
|
640
|
-
}
|
|
866
|
+
flushContent();
|
|
641
867
|
}
|
|
642
868
|
return out;
|
|
643
869
|
}
|
|
644
|
-
function
|
|
645
|
-
if (
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
870
|
+
function geminiParts(content, reporter) {
|
|
871
|
+
if (Array.isArray(content.parts)) return content.parts;
|
|
872
|
+
reporter.warn("dropped-content", "Dropped malformed Gemini content parts.");
|
|
873
|
+
return [];
|
|
874
|
+
}
|
|
875
|
+
function geminiFunctionResponseName(value, matchedName, reporter) {
|
|
876
|
+
if (matchedName && value === void 0) return matchedName;
|
|
877
|
+
if (matchedName && !isProviderFunctionName(value)) {
|
|
878
|
+
reporter.warn(
|
|
879
|
+
"dropped-metadata",
|
|
880
|
+
`Gemini functionResponse had a missing or invalid function name; used matching functionCall '${matchedName}'.`
|
|
881
|
+
);
|
|
882
|
+
return matchedName;
|
|
883
|
+
}
|
|
884
|
+
return providerFunctionName(value, reporter, "Gemini", "functionResponse");
|
|
885
|
+
}
|
|
886
|
+
function resolveResponseId(response, pending, reporter, ids) {
|
|
887
|
+
const responseId = nonEmptyString2(response.id) ? response.id : void 0;
|
|
888
|
+
if (response.id !== void 0 && typeof response.id !== "string") {
|
|
889
|
+
reporter.warn(
|
|
890
|
+
"dropped-metadata",
|
|
891
|
+
`Gemini functionResponse for '${response.name}' had a non-string id; ignored it.`
|
|
892
|
+
);
|
|
893
|
+
}
|
|
894
|
+
if (responseId) {
|
|
895
|
+
const index2 = pending.findIndex((p) => p.providerId === responseId || p.id === responseId);
|
|
896
|
+
if (index2 >= 0) {
|
|
897
|
+
const [match] = pending.splice(index2, 1);
|
|
898
|
+
if (response.name !== match.name) {
|
|
899
|
+
reporter.warn(
|
|
900
|
+
"dropped-metadata",
|
|
901
|
+
`Gemini functionResponse '${responseId}' name '${response.name}' differed from matching functionCall '${match.name}'; used the call id mapping.`
|
|
902
|
+
);
|
|
903
|
+
}
|
|
904
|
+
return { id: match.id, matched: true };
|
|
905
|
+
}
|
|
906
|
+
reporter.warn(
|
|
907
|
+
"unmapped-tool-result",
|
|
908
|
+
`Gemini functionResponse '${responseId}' for '${response.name}' had no matching call; kept the response id.`
|
|
909
|
+
);
|
|
910
|
+
const id2 = ids.claim(responseId, response.name);
|
|
911
|
+
if (id2 !== responseId) {
|
|
912
|
+
reporter.warn(
|
|
913
|
+
"generated-id",
|
|
914
|
+
`Gemini functionResponse '${responseId}' for '${response.name}' reused an existing id; generated '${id2}'.`
|
|
915
|
+
);
|
|
916
|
+
}
|
|
917
|
+
return { id: id2, matched: false };
|
|
649
918
|
}
|
|
650
919
|
const index = pending.findIndex((p) => p.name === response.name);
|
|
651
920
|
if (index >= 0) {
|
|
@@ -653,13 +922,31 @@ function resolveResponseId(response, pending, reporter, generateId) {
|
|
|
653
922
|
pending.splice(index, 1);
|
|
654
923
|
return { id: id2, matched: true };
|
|
655
924
|
}
|
|
656
|
-
const id =
|
|
925
|
+
const id = ids.generate(response.name);
|
|
657
926
|
reporter.warn(
|
|
658
927
|
"unmapped-tool-result",
|
|
659
928
|
`Gemini functionResponse for '${response.name}' had no matching call; generated '${id}'.`
|
|
660
929
|
);
|
|
661
930
|
return { id, matched: false };
|
|
662
931
|
}
|
|
932
|
+
function geminiProviderIds(conversation) {
|
|
933
|
+
const ids = [];
|
|
934
|
+
const root = isRecord(conversation) ? conversation : {};
|
|
935
|
+
const contents = Array.isArray(root.contents) ? root.contents : [];
|
|
936
|
+
for (const content of contents) {
|
|
937
|
+
if (!isRecord(content)) continue;
|
|
938
|
+
const parts = Array.isArray(content.parts) ? content.parts : [];
|
|
939
|
+
for (const part of parts) {
|
|
940
|
+
if (isRecord(part) && isRecord(part.functionCall) && nonEmptyString2(part.functionCall.id)) {
|
|
941
|
+
ids.push(part.functionCall.id);
|
|
942
|
+
}
|
|
943
|
+
if (isRecord(part) && isRecord(part.functionResponse) && nonEmptyString2(part.functionResponse.id)) {
|
|
944
|
+
ids.push(part.functionResponse.id);
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
return ids;
|
|
949
|
+
}
|
|
663
950
|
|
|
664
951
|
// src/convert.ts
|
|
665
952
|
function convert(conversation, route, options = {}) {
|
|
@@ -693,6 +980,42 @@ function fromCanonical(canonical, to, options) {
|
|
|
693
980
|
|
|
694
981
|
// src/response.ts
|
|
695
982
|
var num = (value) => typeof value === "number" ? value : 0;
|
|
983
|
+
var nonEmptyString3 = (value) => typeof value === "string" && value.length > 0;
|
|
984
|
+
function responseRoot(body, reporter, provider) {
|
|
985
|
+
if (isRecord(body)) return body;
|
|
986
|
+
reporter.warn("dropped-content", `Dropped malformed ${provider} response body; expected an object.`);
|
|
987
|
+
return {};
|
|
988
|
+
}
|
|
989
|
+
function responseArrayField(root, field, reporter, context, noun) {
|
|
990
|
+
const value = root[field];
|
|
991
|
+
if (value === void 0) {
|
|
992
|
+
reporter.warn("dropped-content", `${context} missing ${field} array; no ${noun} were read.`);
|
|
993
|
+
return [];
|
|
994
|
+
}
|
|
995
|
+
if (!Array.isArray(value)) {
|
|
996
|
+
reporter.warn("dropped-content", `Dropped malformed ${context} ${field}; expected an array.`);
|
|
997
|
+
return [];
|
|
998
|
+
}
|
|
999
|
+
return value;
|
|
1000
|
+
}
|
|
1001
|
+
function firstResponseRecord(items, reporter, provider, noun) {
|
|
1002
|
+
if (items.length === 0) return {};
|
|
1003
|
+
if (isRecord(items[0])) return items[0];
|
|
1004
|
+
reporter.warn("dropped-content", `Dropped malformed ${provider} response ${noun}; expected an object.`);
|
|
1005
|
+
return {};
|
|
1006
|
+
}
|
|
1007
|
+
function responseRecordField(root, field, reporter, context, noun) {
|
|
1008
|
+
const value = root[field];
|
|
1009
|
+
if (value === void 0) {
|
|
1010
|
+
reporter.warn("dropped-content", `${context} missing ${field} object; no ${noun} were read.`);
|
|
1011
|
+
return {};
|
|
1012
|
+
}
|
|
1013
|
+
if (!isRecord(value)) {
|
|
1014
|
+
reporter.warn("dropped-content", `Dropped malformed ${context} ${field}; expected an object.`);
|
|
1015
|
+
return {};
|
|
1016
|
+
}
|
|
1017
|
+
return value;
|
|
1018
|
+
}
|
|
696
1019
|
function buildMessage(text, toolCalls) {
|
|
697
1020
|
const message = { role: "assistant", content: text ? text : null };
|
|
698
1021
|
if (toolCalls.length > 0) message.tool_calls = toolCalls;
|
|
@@ -701,6 +1024,97 @@ function buildMessage(text, toolCalls) {
|
|
|
701
1024
|
function finalReason(mapped, toolCalls) {
|
|
702
1025
|
return toolCalls.length > 0 ? "tool_calls" : mapped;
|
|
703
1026
|
}
|
|
1027
|
+
function normalizeProviderArguments(value, reporter, provider, part, fnName) {
|
|
1028
|
+
if (typeof value !== "string") return stringifyArgumentsObject(value, reporter, provider, part, fnName);
|
|
1029
|
+
const parsed = tryParseJson(value);
|
|
1030
|
+
if (parsed.ok && isRecord(parsed.value)) return value;
|
|
1031
|
+
reporter.warn(
|
|
1032
|
+
"invalid-json-arguments",
|
|
1033
|
+
`${provider} ${part} '${fnName}' had arguments that were not a JSON object string; used an empty object instead.`
|
|
1034
|
+
);
|
|
1035
|
+
return "{}";
|
|
1036
|
+
}
|
|
1037
|
+
function normalizeOpenAIToolCalls(value, reporter) {
|
|
1038
|
+
if (value === void 0) return [];
|
|
1039
|
+
if (!Array.isArray(value)) {
|
|
1040
|
+
reporter.warn("dropped-content", "Dropped malformed OpenAI Chat Completions tool_calls; expected an array.");
|
|
1041
|
+
return [];
|
|
1042
|
+
}
|
|
1043
|
+
const toolCalls = [];
|
|
1044
|
+
const ids = createToolCallIdGenerator(
|
|
1045
|
+
value.flatMap(
|
|
1046
|
+
(call) => isRecord(call) && (call.type === void 0 || call.type === "function") && nonEmptyString3(call.id) ? [call.id] : []
|
|
1047
|
+
)
|
|
1048
|
+
);
|
|
1049
|
+
for (const call of value) {
|
|
1050
|
+
if (!isRecord(call)) {
|
|
1051
|
+
reporter.warn("dropped-content", "Dropped malformed OpenAI Chat Completions tool_call; expected an object.");
|
|
1052
|
+
continue;
|
|
1053
|
+
}
|
|
1054
|
+
if (call.type !== void 0 && call.type !== "function") {
|
|
1055
|
+
reporter.warn(
|
|
1056
|
+
"dropped-content",
|
|
1057
|
+
`OpenAI Chat Completions tool_call type '${String(call.type)}' is not supported; dropped.`
|
|
1058
|
+
);
|
|
1059
|
+
continue;
|
|
1060
|
+
}
|
|
1061
|
+
const fn = isRecord(call.function) ? call.function : {};
|
|
1062
|
+
const name = providerFunctionName(fn.name, reporter, "OpenAI Chat Completions", "tool_call.function");
|
|
1063
|
+
const providedId = nonEmptyString3(call.id) ? call.id : void 0;
|
|
1064
|
+
const id = providedId ? ids.claim(providedId, name) : ids.generate(name);
|
|
1065
|
+
if (!providedId) {
|
|
1066
|
+
reporter.warn("generated-id", `OpenAI Chat Completions tool_call '${name}' had no id; generated '${id}'.`);
|
|
1067
|
+
} else if (id !== providedId) {
|
|
1068
|
+
reporter.warn(
|
|
1069
|
+
"generated-id",
|
|
1070
|
+
`OpenAI Chat Completions tool_call '${name}' reused id '${providedId}'; generated '${id}'.`
|
|
1071
|
+
);
|
|
1072
|
+
}
|
|
1073
|
+
const args = normalizeProviderArguments(
|
|
1074
|
+
fn.arguments,
|
|
1075
|
+
reporter,
|
|
1076
|
+
"OpenAI Chat Completions",
|
|
1077
|
+
"tool_call.function",
|
|
1078
|
+
name
|
|
1079
|
+
);
|
|
1080
|
+
toolCalls.push({ id, type: "function", function: { name, arguments: args } });
|
|
1081
|
+
}
|
|
1082
|
+
return toolCalls;
|
|
1083
|
+
}
|
|
1084
|
+
function normalizeOpenAIFunctionCall(value, reporter) {
|
|
1085
|
+
if (!isRecord(value)) return [];
|
|
1086
|
+
const name = providerFunctionName(value.name, reporter, "OpenAI Chat Completions", "function_call");
|
|
1087
|
+
const id = `call_${name.replace(/[^a-zA-Z0-9_-]/g, "_")}_0`;
|
|
1088
|
+
reporter.warn("generated-id", `OpenAI Chat Completions function_call '${name}' had no id; generated '${id}'.`);
|
|
1089
|
+
const args = normalizeProviderArguments(value.arguments, reporter, "OpenAI Chat Completions", "function_call", name);
|
|
1090
|
+
return [{ id, type: "function", function: { name, arguments: args } }];
|
|
1091
|
+
}
|
|
1092
|
+
function openAIChatContentText(content, reporter) {
|
|
1093
|
+
if (typeof content === "string") return content;
|
|
1094
|
+
if (content === null || content === void 0) return "";
|
|
1095
|
+
if (!Array.isArray(content)) {
|
|
1096
|
+
reporter.warn("dropped-content", "Dropped malformed OpenAI Chat Completions response content.");
|
|
1097
|
+
return "";
|
|
1098
|
+
}
|
|
1099
|
+
const pieces = [];
|
|
1100
|
+
for (const part of content) {
|
|
1101
|
+
if (typeof part === "string") {
|
|
1102
|
+
pieces.push(part);
|
|
1103
|
+
} else if (isRecord(part) && typeof part.text === "string") {
|
|
1104
|
+
pieces.push(part.text);
|
|
1105
|
+
} else if (isRecord(part) && part.type === "refusal" && typeof part.refusal === "string") {
|
|
1106
|
+
pieces.push(part.refusal);
|
|
1107
|
+
} else {
|
|
1108
|
+
reporter.warn("dropped-content", "Dropped unsupported OpenAI Chat Completions response content part.");
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
return pieces.join("");
|
|
1112
|
+
}
|
|
1113
|
+
function openAIChatMessageText(message, reporter) {
|
|
1114
|
+
const content = openAIChatContentText(message.content, reporter);
|
|
1115
|
+
if (content) return content;
|
|
1116
|
+
return typeof message.refusal === "string" ? message.refusal : content;
|
|
1117
|
+
}
|
|
704
1118
|
var OPENAI_FINISH = {
|
|
705
1119
|
stop: "stop",
|
|
706
1120
|
length: "length",
|
|
@@ -708,16 +1122,27 @@ var OPENAI_FINISH = {
|
|
|
708
1122
|
content_filter: "content_filter",
|
|
709
1123
|
function_call: "tool_calls"
|
|
710
1124
|
};
|
|
711
|
-
function responseFromOpenAI(body) {
|
|
712
|
-
const
|
|
713
|
-
const
|
|
714
|
-
const
|
|
715
|
-
const
|
|
716
|
-
const
|
|
1125
|
+
function responseFromOpenAI(body, options = {}) {
|
|
1126
|
+
const reporter = new Reporter(options);
|
|
1127
|
+
const isObjectBody = isRecord(body);
|
|
1128
|
+
const root = isObjectBody ? body : responseRoot(body, reporter, "OpenAI Chat Completions");
|
|
1129
|
+
const choices = isObjectBody ? responseArrayField(root, "choices", reporter, "OpenAI Chat Completions response", "choices") : [];
|
|
1130
|
+
const hasChoiceRecord = choices.length > 0 && isRecord(choices[0]);
|
|
1131
|
+
const choice = firstResponseRecord(choices, reporter, "OpenAI Chat Completions", "choice");
|
|
1132
|
+
const message = hasChoiceRecord ? responseRecordField(
|
|
1133
|
+
choice,
|
|
1134
|
+
"message",
|
|
1135
|
+
reporter,
|
|
1136
|
+
"OpenAI Chat Completions response choice",
|
|
1137
|
+
"message content or tool calls"
|
|
1138
|
+
) : {};
|
|
1139
|
+
const text = openAIChatMessageText(message, reporter);
|
|
1140
|
+
const toolCalls = normalizeOpenAIToolCalls(message.tool_calls, reporter);
|
|
1141
|
+
const normalizedToolCalls = toolCalls.length > 0 ? toolCalls : normalizeOpenAIFunctionCall(message.function_call, reporter);
|
|
717
1142
|
const usage = isRecord(root.usage) ? root.usage : {};
|
|
718
1143
|
return {
|
|
719
|
-
message: buildMessage(text,
|
|
720
|
-
finishReason: finalReason(OPENAI_FINISH[String(choice.finish_reason)] ?? "unknown",
|
|
1144
|
+
message: buildMessage(text, normalizedToolCalls),
|
|
1145
|
+
finishReason: finalReason(OPENAI_FINISH[String(choice.finish_reason)] ?? "unknown", normalizedToolCalls),
|
|
721
1146
|
usage: { inputTokens: num(usage.prompt_tokens), outputTokens: num(usage.completion_tokens) }
|
|
722
1147
|
};
|
|
723
1148
|
}
|
|
@@ -725,35 +1150,85 @@ var OPENAI_RESPONSES_INCOMPLETE = {
|
|
|
725
1150
|
max_output_tokens: "length",
|
|
726
1151
|
content_filter: "content_filter"
|
|
727
1152
|
};
|
|
1153
|
+
function openAIResponsesContentText(content, reporter) {
|
|
1154
|
+
if (content === void 0) return "";
|
|
1155
|
+
if (!Array.isArray(content)) {
|
|
1156
|
+
reporter.warn("dropped-content", "Dropped malformed OpenAI Responses message content.");
|
|
1157
|
+
return "";
|
|
1158
|
+
}
|
|
1159
|
+
const pieces = [];
|
|
1160
|
+
for (const part of content) {
|
|
1161
|
+
if (!isRecord(part)) {
|
|
1162
|
+
reporter.warn("dropped-content", "Dropped malformed OpenAI Responses message content part.");
|
|
1163
|
+
} else if (typeof part.text === "string" && (part.type === "output_text" || part.type === "text")) {
|
|
1164
|
+
pieces.push(part.text);
|
|
1165
|
+
} else if (part.type === "refusal" && typeof part.refusal === "string") {
|
|
1166
|
+
pieces.push(part.refusal);
|
|
1167
|
+
} else {
|
|
1168
|
+
reporter.warn("dropped-content", "Dropped unsupported OpenAI Responses message content part.");
|
|
1169
|
+
}
|
|
1170
|
+
}
|
|
1171
|
+
return pieces.join("");
|
|
1172
|
+
}
|
|
728
1173
|
function responseApiFinishReason(root) {
|
|
729
1174
|
if (root.status === "completed") return "stop";
|
|
730
1175
|
if (root.status !== "incomplete") return "unknown";
|
|
731
1176
|
const details = isRecord(root.incomplete_details) ? root.incomplete_details : {};
|
|
732
1177
|
return OPENAI_RESPONSES_INCOMPLETE[String(details.reason)] ?? "unknown";
|
|
733
1178
|
}
|
|
734
|
-
function
|
|
735
|
-
|
|
736
|
-
|
|
1179
|
+
function openAIResponsesOutput(root, reporter) {
|
|
1180
|
+
if (root.output === void 0) {
|
|
1181
|
+
reporter.warn(
|
|
1182
|
+
"dropped-content",
|
|
1183
|
+
"OpenAI Responses response missing top-level output array; no output items were read."
|
|
1184
|
+
);
|
|
1185
|
+
return [];
|
|
1186
|
+
}
|
|
1187
|
+
if (!Array.isArray(root.output)) {
|
|
1188
|
+
reporter.warn("dropped-content", "Dropped malformed OpenAI Responses top-level output; expected an array.");
|
|
1189
|
+
return [];
|
|
1190
|
+
}
|
|
1191
|
+
return root.output;
|
|
1192
|
+
}
|
|
1193
|
+
function responseFromOpenAIResponses(body, options = {}) {
|
|
1194
|
+
const reporter = new Reporter(options);
|
|
1195
|
+
const isObjectBody = isRecord(body);
|
|
1196
|
+
const root = isObjectBody ? body : responseRoot(body, reporter, "OpenAI Responses");
|
|
1197
|
+
const output = isObjectBody ? openAIResponsesOutput(root, reporter) : [];
|
|
737
1198
|
const textPieces = [];
|
|
738
1199
|
const toolCalls = [];
|
|
739
|
-
|
|
1200
|
+
const ids = createToolCallIdGenerator(
|
|
1201
|
+
output.flatMap((item) => {
|
|
1202
|
+
if (!isRecord(item) || item.type !== "function_call") return [];
|
|
1203
|
+
if (nonEmptyString3(item.call_id)) return [item.call_id];
|
|
1204
|
+
return nonEmptyString3(item.id) ? [item.id] : [];
|
|
1205
|
+
})
|
|
1206
|
+
);
|
|
740
1207
|
for (const item of output) {
|
|
741
|
-
if (!isRecord(item))
|
|
1208
|
+
if (!isRecord(item)) {
|
|
1209
|
+
reporter.warn("dropped-content", "Dropped malformed OpenAI Responses output item.");
|
|
1210
|
+
continue;
|
|
1211
|
+
}
|
|
742
1212
|
if (item.type === "message") {
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
1213
|
+
textPieces.push(openAIResponsesContentText(item.content, reporter));
|
|
1214
|
+
} else if (item.type === "function_call") {
|
|
1215
|
+
const name = providerFunctionName(item.name, reporter, "OpenAI Responses", "function_call");
|
|
1216
|
+
const callId = nonEmptyString3(item.call_id) ? item.call_id : void 0;
|
|
1217
|
+
const itemId = nonEmptyString3(item.id) ? item.id : void 0;
|
|
1218
|
+
const id = callId ?? itemId;
|
|
1219
|
+
const toolCallId = id ? ids.claim(id, name) : ids.generate(name);
|
|
1220
|
+
if (!callId && !itemId) {
|
|
1221
|
+
reporter.warn("generated-id", `OpenAI Responses function_call '${name}' had no id; generated '${toolCallId}'.`);
|
|
1222
|
+
} else if (toolCallId !== id) {
|
|
1223
|
+
reporter.warn(
|
|
1224
|
+
"generated-id",
|
|
1225
|
+
`OpenAI Responses function_call '${name}' reused id '${id}'; generated '${toolCallId}'.`
|
|
1226
|
+
);
|
|
751
1227
|
}
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
toolCalls.push({ id, type: "function", function: { name, arguments: args } });
|
|
1228
|
+
const args = normalizeProviderArguments(item.arguments, reporter, "OpenAI Responses", "function_call", name);
|
|
1229
|
+
toolCalls.push({ id: toolCallId, type: "function", function: { name, arguments: args } });
|
|
1230
|
+
} else {
|
|
1231
|
+
reporter.warn("dropped-content", `Dropped unsupported OpenAI Responses output item '${String(item.type)}'.`);
|
|
757
1232
|
}
|
|
758
1233
|
}
|
|
759
1234
|
const usage = isRecord(root.usage) ? root.usage : {};
|
|
@@ -771,21 +1246,47 @@ var ANTHROPIC_FINISH = {
|
|
|
771
1246
|
refusal: "content_filter",
|
|
772
1247
|
pause_turn: "unknown"
|
|
773
1248
|
};
|
|
774
|
-
function responseFromAnthropic(body) {
|
|
775
|
-
const
|
|
776
|
-
const
|
|
1249
|
+
function responseFromAnthropic(body, options = {}) {
|
|
1250
|
+
const reporter = new Reporter(options);
|
|
1251
|
+
const isObjectBody = isRecord(body);
|
|
1252
|
+
const root = isObjectBody ? body : responseRoot(body, reporter, "Anthropic");
|
|
1253
|
+
const blocks = isObjectBody ? responseArrayField(root, "content", reporter, "Anthropic response", "content blocks") : [];
|
|
777
1254
|
const textPieces = [];
|
|
778
1255
|
const toolCalls = [];
|
|
1256
|
+
const ids = createToolCallIdGenerator(
|
|
1257
|
+
blocks.flatMap(
|
|
1258
|
+
(block) => isRecord(block) && block.type === "tool_use" && nonEmptyString3(block.id) ? [block.id] : []
|
|
1259
|
+
)
|
|
1260
|
+
);
|
|
779
1261
|
for (const block of blocks) {
|
|
780
|
-
if (!isRecord(block))
|
|
1262
|
+
if (!isRecord(block)) {
|
|
1263
|
+
reporter.warn("dropped-content", "Dropped a malformed Anthropic response content block.");
|
|
1264
|
+
continue;
|
|
1265
|
+
}
|
|
781
1266
|
if (block.type === "text" && typeof block.text === "string") {
|
|
782
1267
|
textPieces.push(block.text);
|
|
783
|
-
} else if (block.type === "tool_use"
|
|
1268
|
+
} else if (block.type === "tool_use") {
|
|
1269
|
+
const name = providerFunctionName(block.name, reporter, "Anthropic", "tool_use");
|
|
1270
|
+
const providedId = nonEmptyString3(block.id) ? block.id : void 0;
|
|
1271
|
+
const id = providedId ? ids.claim(providedId, name) : ids.generate(name);
|
|
1272
|
+
if (!providedId) {
|
|
1273
|
+
reporter.warn("generated-id", `Anthropic tool_use '${name}' had no id; generated '${id}'.`);
|
|
1274
|
+
} else if (id !== providedId) {
|
|
1275
|
+
reporter.warn("generated-id", `Anthropic tool_use '${name}' reused id '${providedId}'; generated '${id}'.`);
|
|
1276
|
+
}
|
|
784
1277
|
toolCalls.push({
|
|
785
|
-
id
|
|
1278
|
+
id,
|
|
786
1279
|
type: "function",
|
|
787
|
-
function: {
|
|
1280
|
+
function: {
|
|
1281
|
+
name,
|
|
1282
|
+
arguments: stringifyArgumentsObject(block.input, reporter, "Anthropic", "tool_use", name)
|
|
1283
|
+
}
|
|
788
1284
|
});
|
|
1285
|
+
} else {
|
|
1286
|
+
reporter.warn(
|
|
1287
|
+
"dropped-content",
|
|
1288
|
+
`Anthropic response content block '${String(block.type)}' is not supported; dropped.`
|
|
1289
|
+
);
|
|
789
1290
|
}
|
|
790
1291
|
}
|
|
791
1292
|
const usage = isRecord(root.usage) ? root.usage : {};
|
|
@@ -800,30 +1301,63 @@ var GEMINI_FINISH = {
|
|
|
800
1301
|
MAX_TOKENS: "length",
|
|
801
1302
|
SAFETY: "content_filter",
|
|
802
1303
|
RECITATION: "content_filter",
|
|
803
|
-
|
|
1304
|
+
BLOCKLIST: "content_filter",
|
|
1305
|
+
PROHIBITED_CONTENT: "content_filter",
|
|
1306
|
+
SPII: "content_filter",
|
|
1307
|
+
MALFORMED_FUNCTION_CALL: "content_filter",
|
|
1308
|
+
MODEL_ARMOR: "content_filter",
|
|
1309
|
+
IMAGE_SAFETY: "content_filter",
|
|
1310
|
+
IMAGE_PROHIBITED_CONTENT: "content_filter",
|
|
1311
|
+
IMAGE_RECITATION: "content_filter"
|
|
804
1312
|
};
|
|
805
1313
|
function responseFromGemini(body, options = {}) {
|
|
806
1314
|
const reporter = new Reporter(options);
|
|
807
|
-
const
|
|
808
|
-
const
|
|
1315
|
+
const isObjectBody = isRecord(body);
|
|
1316
|
+
const root = isObjectBody ? body : responseRoot(body, reporter, "Gemini");
|
|
1317
|
+
const candidates = isObjectBody ? responseArrayField(root, "candidates", reporter, "Gemini response", "candidates") : [];
|
|
1318
|
+
const hasCandidateRecord = candidates.length > 0 && isRecord(candidates[0]);
|
|
1319
|
+
const candidate = firstResponseRecord(candidates, reporter, "Gemini", "candidate");
|
|
809
1320
|
const content = isRecord(candidate.content) ? candidate.content : {};
|
|
810
|
-
|
|
1321
|
+
if (hasCandidateRecord && !isRecord(candidate.content)) {
|
|
1322
|
+
reporter.warn("dropped-content", "Dropped malformed Gemini response candidate content; expected an object.");
|
|
1323
|
+
}
|
|
1324
|
+
const parts = hasCandidateRecord && isRecord(candidate.content) ? responseArrayField(content, "parts", reporter, "Gemini response candidate content", "content parts") : [];
|
|
811
1325
|
const textPieces = [];
|
|
812
1326
|
const toolCalls = [];
|
|
813
|
-
|
|
1327
|
+
const ids = createToolCallIdGenerator(
|
|
1328
|
+
parts.flatMap(
|
|
1329
|
+
(part) => isRecord(part) && isRecord(part.functionCall) && nonEmptyString3(part.functionCall.id) ? [part.functionCall.id] : []
|
|
1330
|
+
)
|
|
1331
|
+
);
|
|
814
1332
|
for (const part of parts) {
|
|
815
|
-
if (!isRecord(part))
|
|
1333
|
+
if (!isRecord(part)) {
|
|
1334
|
+
reporter.warn("dropped-content", "Dropped a malformed Gemini response content part.");
|
|
1335
|
+
continue;
|
|
1336
|
+
}
|
|
816
1337
|
if (isRecord(part.functionCall)) {
|
|
817
1338
|
const call = part.functionCall;
|
|
818
|
-
const name =
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
1339
|
+
const name = providerFunctionName(call.name, reporter, "Gemini", "functionCall");
|
|
1340
|
+
const providedId = nonEmptyString3(call.id) ? call.id : void 0;
|
|
1341
|
+
const id = providedId ? ids.claim(providedId, name) : ids.generate(name);
|
|
1342
|
+
if (!providedId) {
|
|
822
1343
|
reporter.warn("generated-id", `Gemini functionCall '${name}' had no id; generated '${id}'.`);
|
|
1344
|
+
} else if (id !== providedId) {
|
|
1345
|
+
reporter.warn("generated-id", `Gemini functionCall '${name}' reused id '${providedId}'; generated '${id}'.`);
|
|
823
1346
|
}
|
|
824
|
-
toolCalls.push({
|
|
1347
|
+
toolCalls.push({
|
|
1348
|
+
id,
|
|
1349
|
+
type: "function",
|
|
1350
|
+
function: {
|
|
1351
|
+
name,
|
|
1352
|
+
arguments: stringifyArgumentsObject(call.args, reporter, "Gemini", "functionCall", name)
|
|
1353
|
+
}
|
|
1354
|
+
});
|
|
1355
|
+
} else if (part.thought === true) {
|
|
1356
|
+
reporter.warn("dropped-content", "Dropped a Gemini thought (reasoning) response part.");
|
|
825
1357
|
} else if (typeof part.text === "string") {
|
|
826
1358
|
textPieces.push(part.text);
|
|
1359
|
+
} else {
|
|
1360
|
+
reporter.warn("dropped-content", "Dropped an unsupported Gemini response content part.");
|
|
827
1361
|
}
|
|
828
1362
|
}
|
|
829
1363
|
const usage = isRecord(root.usageMetadata) ? root.usageMetadata : {};
|
|
@@ -836,17 +1370,31 @@ function responseFromGemini(body, options = {}) {
|
|
|
836
1370
|
function normalizeResponse(body, route, options = {}) {
|
|
837
1371
|
switch (route.from) {
|
|
838
1372
|
case "openai":
|
|
839
|
-
return responseFromOpenAI(body);
|
|
1373
|
+
return responseFromOpenAI(body, options);
|
|
840
1374
|
case "openai-responses":
|
|
841
|
-
return responseFromOpenAIResponses(body);
|
|
1375
|
+
return responseFromOpenAIResponses(body, options);
|
|
842
1376
|
case "anthropic":
|
|
843
|
-
return responseFromAnthropic(body);
|
|
1377
|
+
return responseFromAnthropic(body, options);
|
|
844
1378
|
case "gemini":
|
|
845
1379
|
return responseFromGemini(body, options);
|
|
846
1380
|
default:
|
|
847
1381
|
throw new Error(`Unknown source provider: ${String(route.from)}`);
|
|
848
1382
|
}
|
|
849
1383
|
}
|
|
1384
|
+
|
|
1385
|
+
// src/types.ts
|
|
1386
|
+
var warningCodes = Object.freeze([
|
|
1387
|
+
"generated-id",
|
|
1388
|
+
"unmapped-tool-result",
|
|
1389
|
+
"merged-role",
|
|
1390
|
+
"dropped-content",
|
|
1391
|
+
"dropped-metadata",
|
|
1392
|
+
"invalid-json-arguments",
|
|
1393
|
+
"system-midstream",
|
|
1394
|
+
"gemini-url-image",
|
|
1395
|
+
"gemini-url-media",
|
|
1396
|
+
"unsupported-modality"
|
|
1397
|
+
]);
|
|
850
1398
|
export {
|
|
851
1399
|
convert,
|
|
852
1400
|
fromAnthropic,
|
|
@@ -859,6 +1407,7 @@ export {
|
|
|
859
1407
|
responseFromOpenAIResponses,
|
|
860
1408
|
toAnthropic,
|
|
861
1409
|
toDataUrl,
|
|
862
|
-
toGemini
|
|
1410
|
+
toGemini,
|
|
1411
|
+
warningCodes
|
|
863
1412
|
};
|
|
864
1413
|
//# sourceMappingURL=index.js.map
|