llm-messages 0.5.0 → 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 +125 -1
- package/README.md +80 -35
- 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 +12 -4
package/dist/index.cjs
CHANGED
|
@@ -31,7 +31,8 @@ __export(index_exports, {
|
|
|
31
31
|
responseFromOpenAIResponses: () => responseFromOpenAIResponses,
|
|
32
32
|
toAnthropic: () => toAnthropic,
|
|
33
33
|
toDataUrl: () => toDataUrl,
|
|
34
|
-
toGemini: () => toGemini
|
|
34
|
+
toGemini: () => toGemini,
|
|
35
|
+
warningCodes: () => warningCodes
|
|
35
36
|
});
|
|
36
37
|
module.exports = __toCommonJS(index_exports);
|
|
37
38
|
|
|
@@ -54,6 +55,7 @@ function textOf(content) {
|
|
|
54
55
|
return content.map((part) => {
|
|
55
56
|
if (typeof part === "string") return part;
|
|
56
57
|
if (isRecord(part) && typeof part.text === "string") return part.text;
|
|
58
|
+
if (isRecord(part) && part.type === "refusal" && typeof part.refusal === "string") return part.refusal;
|
|
57
59
|
return "";
|
|
58
60
|
}).join("");
|
|
59
61
|
}
|
|
@@ -75,17 +77,83 @@ function parseArguments(args, reporter, fnName) {
|
|
|
75
77
|
);
|
|
76
78
|
return {};
|
|
77
79
|
}
|
|
80
|
+
function stringifyArgumentsObject(value, reporter, provider, part, fnName) {
|
|
81
|
+
if (value === void 0) return "{}";
|
|
82
|
+
if (isRecord(value)) {
|
|
83
|
+
try {
|
|
84
|
+
return JSON.stringify(value);
|
|
85
|
+
} catch {
|
|
86
|
+
reporter.warn(
|
|
87
|
+
"invalid-json-arguments",
|
|
88
|
+
`${provider} ${part} '${fnName}' had arguments that could not be serialized as JSON; used an empty object instead.`
|
|
89
|
+
);
|
|
90
|
+
return "{}";
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
reporter.warn(
|
|
94
|
+
"invalid-json-arguments",
|
|
95
|
+
`${provider} ${part} '${fnName}' had arguments that were not an object; used an empty object instead.`
|
|
96
|
+
);
|
|
97
|
+
return "{}";
|
|
98
|
+
}
|
|
99
|
+
var OPENAI_FUNCTION_NAME = /^[a-zA-Z0-9_-]{1,64}$/;
|
|
100
|
+
function isProviderFunctionName(value) {
|
|
101
|
+
return typeof value === "string" && OPENAI_FUNCTION_NAME.test(value);
|
|
102
|
+
}
|
|
103
|
+
function providerFunctionName(value, reporter, provider, part) {
|
|
104
|
+
if (isProviderFunctionName(value)) return value;
|
|
105
|
+
reporter.warn(
|
|
106
|
+
"dropped-metadata",
|
|
107
|
+
`${provider} ${part} had a missing or invalid function name; used 'unknown_function'.`
|
|
108
|
+
);
|
|
109
|
+
return "unknown_function";
|
|
110
|
+
}
|
|
111
|
+
function createToolCallIdGenerator(reservedIds = []) {
|
|
112
|
+
const reserved = new Set(reservedIds);
|
|
113
|
+
const used = /* @__PURE__ */ new Set();
|
|
114
|
+
let counter = 0;
|
|
115
|
+
const generate = (name) => {
|
|
116
|
+
let id;
|
|
117
|
+
do {
|
|
118
|
+
id = `call_${name.replace(/[^a-zA-Z0-9_-]/g, "_")}_${counter++}`;
|
|
119
|
+
} while (used.has(id) || reserved.has(id));
|
|
120
|
+
used.add(id);
|
|
121
|
+
return id;
|
|
122
|
+
};
|
|
123
|
+
return {
|
|
124
|
+
claim(id, name) {
|
|
125
|
+
if (used.has(id)) return generate(name);
|
|
126
|
+
used.add(id);
|
|
127
|
+
reserved.delete(id);
|
|
128
|
+
return id;
|
|
129
|
+
},
|
|
130
|
+
generate(name) {
|
|
131
|
+
return generate(name);
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
}
|
|
78
135
|
function wrapResponse(content) {
|
|
79
136
|
const parsed = tryParseJson(content);
|
|
80
137
|
if (parsed.ok && isRecord(parsed.value)) return parsed.value;
|
|
81
138
|
return { result: content };
|
|
82
139
|
}
|
|
83
|
-
function unwrapResponse(response) {
|
|
84
|
-
|
|
85
|
-
if (
|
|
86
|
-
|
|
140
|
+
function unwrapResponse(response, reporter, provider = "Provider", part = "response") {
|
|
141
|
+
if (response === void 0) return "{}";
|
|
142
|
+
if (isRecord(response)) {
|
|
143
|
+
const keys = Object.keys(response);
|
|
144
|
+
if (keys.length === 1 && keys[0] === "result" && typeof response.result === "string") {
|
|
145
|
+
return response.result;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
try {
|
|
149
|
+
return JSON.stringify(response) ?? "{}";
|
|
150
|
+
} catch {
|
|
151
|
+
reporter?.warn(
|
|
152
|
+
"dropped-content",
|
|
153
|
+
`${provider} ${part} response could not be serialized as JSON; used an empty object instead.`
|
|
154
|
+
);
|
|
155
|
+
return "{}";
|
|
87
156
|
}
|
|
88
|
-
return JSON.stringify(response);
|
|
89
157
|
}
|
|
90
158
|
|
|
91
159
|
// src/image.ts
|
|
@@ -304,6 +372,7 @@ function splitSystem(messages, reporter) {
|
|
|
304
372
|
}
|
|
305
373
|
|
|
306
374
|
// src/providers/anthropic.ts
|
|
375
|
+
var nonEmptyString = (value) => typeof value === "string" && value.length > 0;
|
|
307
376
|
function toAnthropic(messages, options = {}) {
|
|
308
377
|
const reporter = new Reporter(options);
|
|
309
378
|
const { system, rest } = splitSystem(messages, reporter);
|
|
@@ -365,7 +434,7 @@ function userContent(content, reporter) {
|
|
|
365
434
|
}
|
|
366
435
|
reporter.warn("dropped-content", "Dropped an unsupported user content part.");
|
|
367
436
|
}
|
|
368
|
-
return blocks;
|
|
437
|
+
return blocks.length > 0 ? blocks : "";
|
|
369
438
|
}
|
|
370
439
|
function assistantContent(message, reporter) {
|
|
371
440
|
warnDroppedName("Assistant", message.name, "Anthropic", reporter);
|
|
@@ -405,17 +474,59 @@ function mergeConsecutive(messages, reporter) {
|
|
|
405
474
|
return result;
|
|
406
475
|
}
|
|
407
476
|
function asBlocks(content) {
|
|
408
|
-
if (
|
|
477
|
+
if (Array.isArray(content)) return content.filter(isRecord);
|
|
478
|
+
if (typeof content !== "string") return [];
|
|
409
479
|
return content ? [{ type: "text", text: content }] : [];
|
|
410
480
|
}
|
|
481
|
+
function warnMalformedBlocks(content, reporter) {
|
|
482
|
+
if (Array.isArray(content)) {
|
|
483
|
+
for (const block of content) {
|
|
484
|
+
if (!isRecord(block)) {
|
|
485
|
+
reporter.warn("dropped-content", "Dropped a malformed Anthropic content block.");
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
return;
|
|
489
|
+
}
|
|
490
|
+
if (typeof content !== "string") {
|
|
491
|
+
reporter.warn("dropped-content", "Dropped malformed Anthropic message content.");
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
function isSupportedUserBlock(block) {
|
|
495
|
+
return block.type === "text" && typeof block.text === "string" || block.type === "tool_result" || imageFromAnthropic(block) !== null || mediaFromAnthropic(block) !== null;
|
|
496
|
+
}
|
|
497
|
+
function isSupportedAssistantBlock(block) {
|
|
498
|
+
return block.type === "text" && typeof block.text === "string" || block.type === "tool_use";
|
|
499
|
+
}
|
|
500
|
+
function warnUnsupportedBlocks(blocks, role, reporter) {
|
|
501
|
+
const isSupported = role === "user" ? isSupportedUserBlock : isSupportedAssistantBlock;
|
|
502
|
+
for (const block of blocks) {
|
|
503
|
+
if (isSupported(block)) continue;
|
|
504
|
+
reporter.warn("dropped-content", `Dropped unsupported Anthropic ${role} content block '${String(block.type)}'.`);
|
|
505
|
+
}
|
|
506
|
+
}
|
|
411
507
|
function fromAnthropic(conversation, options = {}) {
|
|
412
508
|
const reporter = new Reporter(options);
|
|
413
509
|
const out = [];
|
|
414
|
-
|
|
415
|
-
|
|
510
|
+
const root = isRecord(conversation) ? conversation : {};
|
|
511
|
+
const system = textOf(root.system);
|
|
512
|
+
if (system) {
|
|
513
|
+
out.push({ role: "system", content: system });
|
|
416
514
|
}
|
|
417
|
-
|
|
515
|
+
const messages = Array.isArray(root.messages) ? root.messages : [];
|
|
516
|
+
const ids = createToolCallIdGenerator(anthropicProviderIds(root));
|
|
517
|
+
const pendingToolUseIds = /* @__PURE__ */ new Map();
|
|
518
|
+
for (const message of messages) {
|
|
519
|
+
if (!isRecord(message)) {
|
|
520
|
+
reporter.warn("dropped-content", "Dropped a malformed Anthropic message.");
|
|
521
|
+
continue;
|
|
522
|
+
}
|
|
523
|
+
if (message.role !== "user" && message.role !== "assistant") {
|
|
524
|
+
reporter.warn("dropped-content", `Dropped an Anthropic message with unsupported role '${String(message.role)}'.`);
|
|
525
|
+
continue;
|
|
526
|
+
}
|
|
527
|
+
warnMalformedBlocks(message.content, reporter);
|
|
418
528
|
const blocks = asBlocks(message.content);
|
|
529
|
+
warnUnsupportedBlocks(blocks, message.role, reporter);
|
|
419
530
|
if (message.role === "user") {
|
|
420
531
|
if (blocks.length === 0) {
|
|
421
532
|
out.push({ role: "user", content: "" });
|
|
@@ -434,9 +545,28 @@ function fromAnthropic(conversation, options = {}) {
|
|
|
434
545
|
continue;
|
|
435
546
|
}
|
|
436
547
|
flushContent();
|
|
548
|
+
const rawToolUseId = block.tool_use_id;
|
|
549
|
+
const hasToolUseId = nonEmptyString(rawToolUseId);
|
|
550
|
+
const matchedToolCallId = hasToolUseId ? takePendingToolUseId(pendingToolUseIds, rawToolUseId) : void 0;
|
|
551
|
+
const toolCallId = matchedToolCallId ?? (hasToolUseId ? ids.claim(rawToolUseId, "tool_result") : ids.generate("tool_result"));
|
|
552
|
+
if (!hasToolUseId) {
|
|
553
|
+
reporter.warn(
|
|
554
|
+
"unmapped-tool-result",
|
|
555
|
+
`Anthropic tool_result had no usable tool_use_id; generated '${toolCallId}'.`
|
|
556
|
+
);
|
|
557
|
+
} else if (!matchedToolCallId) {
|
|
558
|
+
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}'.`;
|
|
559
|
+
reporter.warn("unmapped-tool-result", unmappedMessage);
|
|
560
|
+
if (toolCallId !== rawToolUseId) {
|
|
561
|
+
reporter.warn(
|
|
562
|
+
"generated-id",
|
|
563
|
+
`Anthropic tool_result '${rawToolUseId}' reused an existing id; generated '${toolCallId}'.`
|
|
564
|
+
);
|
|
565
|
+
}
|
|
566
|
+
}
|
|
437
567
|
out.push({
|
|
438
568
|
role: "tool",
|
|
439
|
-
tool_call_id:
|
|
569
|
+
tool_call_id: toolCallId,
|
|
440
570
|
content: textOf(block.content),
|
|
441
571
|
...typeof block.is_error === "boolean" ? { is_error: block.is_error } : {}
|
|
442
572
|
});
|
|
@@ -450,13 +580,57 @@ function fromAnthropic(conversation, options = {}) {
|
|
|
450
580
|
if (toolUses.length > 0) {
|
|
451
581
|
assistant.tool_calls = toolUses.map((block) => {
|
|
452
582
|
const b = block;
|
|
453
|
-
|
|
583
|
+
const name = providerFunctionName(b.name, reporter, "Anthropic", "tool_use");
|
|
584
|
+
const providedId = nonEmptyString(b.id) ? b.id : void 0;
|
|
585
|
+
const id = providedId ? ids.claim(providedId, name) : ids.generate(name);
|
|
586
|
+
if (!providedId) {
|
|
587
|
+
reporter.warn("generated-id", `Anthropic tool_use '${name}' had no id; generated '${id}'.`);
|
|
588
|
+
} else if (id !== providedId) {
|
|
589
|
+
reporter.warn("generated-id", `Anthropic tool_use '${name}' reused id '${providedId}'; generated '${id}'.`);
|
|
590
|
+
}
|
|
591
|
+
if (providedId) pushPendingToolUseId(pendingToolUseIds, providedId, id);
|
|
592
|
+
return {
|
|
593
|
+
id,
|
|
594
|
+
type: "function",
|
|
595
|
+
function: {
|
|
596
|
+
name,
|
|
597
|
+
arguments: stringifyArgumentsObject(b.input, reporter, "Anthropic", "tool_use", name)
|
|
598
|
+
}
|
|
599
|
+
};
|
|
454
600
|
});
|
|
455
601
|
}
|
|
456
602
|
out.push(assistant);
|
|
457
603
|
}
|
|
458
604
|
return out;
|
|
459
605
|
}
|
|
606
|
+
function pushPendingToolUseId(pending, providerId, normalizedId) {
|
|
607
|
+
const ids = pending.get(providerId);
|
|
608
|
+
if (ids) ids.push(normalizedId);
|
|
609
|
+
else pending.set(providerId, [normalizedId]);
|
|
610
|
+
}
|
|
611
|
+
function takePendingToolUseId(pending, providerId) {
|
|
612
|
+
const ids = pending.get(providerId);
|
|
613
|
+
const id = ids?.shift();
|
|
614
|
+
if (ids?.length === 0) pending.delete(providerId);
|
|
615
|
+
return id;
|
|
616
|
+
}
|
|
617
|
+
function anthropicProviderIds(conversation) {
|
|
618
|
+
const ids = [];
|
|
619
|
+
const root = isRecord(conversation) ? conversation : {};
|
|
620
|
+
const messages = Array.isArray(root.messages) ? root.messages : [];
|
|
621
|
+
for (const message of messages) {
|
|
622
|
+
if (!isRecord(message)) continue;
|
|
623
|
+
for (const block of asBlocks(message.content)) {
|
|
624
|
+
if (block.type === "tool_use" && nonEmptyString(block.id)) {
|
|
625
|
+
ids.push(block.id);
|
|
626
|
+
}
|
|
627
|
+
if (block.type === "tool_result" && nonEmptyString(block.tool_use_id)) {
|
|
628
|
+
ids.push(block.tool_use_id);
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
return ids;
|
|
633
|
+
}
|
|
460
634
|
function userContentToOpenAI(blocks, reporter) {
|
|
461
635
|
const hasMedia = blocks.some((block) => imageFromAnthropic(block) !== null || mediaFromAnthropic(block) !== null);
|
|
462
636
|
if (!hasMedia) return textOf(blocks);
|
|
@@ -478,10 +652,11 @@ function userContentToOpenAI(blocks, reporter) {
|
|
|
478
652
|
parts.push({ type: "text", text: block.text });
|
|
479
653
|
}
|
|
480
654
|
}
|
|
481
|
-
return parts;
|
|
655
|
+
return parts.length > 0 ? parts : "";
|
|
482
656
|
}
|
|
483
657
|
|
|
484
658
|
// src/providers/gemini.ts
|
|
659
|
+
var nonEmptyString2 = (value) => typeof value === "string" && value.length > 0;
|
|
485
660
|
function toGemini(messages, options = {}) {
|
|
486
661
|
const reporter = new Reporter(options);
|
|
487
662
|
const { system, rest } = splitSystem(messages, reporter);
|
|
@@ -512,8 +687,9 @@ function toGemini(messages, options = {}) {
|
|
|
512
687
|
`Tool message name '${tool.name}' differs from matching tool call '${matchingName}'; used the tool-call function name for Gemini.`
|
|
513
688
|
);
|
|
514
689
|
}
|
|
515
|
-
const
|
|
516
|
-
|
|
690
|
+
const hasStandaloneName = nonEmptyString2(tool.name);
|
|
691
|
+
const name = matchingName ?? (hasStandaloneName ? tool.name : tool.tool_call_id);
|
|
692
|
+
if (!matchingName && !hasStandaloneName) {
|
|
517
693
|
reporter.warn(
|
|
518
694
|
"unmapped-tool-result",
|
|
519
695
|
`Tool result '${tool.tool_call_id}' has no matching call; used the id as the function name.`
|
|
@@ -601,31 +777,51 @@ function mergeConsecutive2(contents, reporter) {
|
|
|
601
777
|
function fromGemini(conversation, options = {}) {
|
|
602
778
|
const reporter = new Reporter(options);
|
|
603
779
|
const out = [];
|
|
604
|
-
|
|
605
|
-
|
|
780
|
+
const root = isRecord(conversation) ? conversation : {};
|
|
781
|
+
const systemInstruction = isRecord(root.systemInstruction) ? root.systemInstruction : void 0;
|
|
782
|
+
if (systemInstruction) {
|
|
783
|
+
const text = textOf(systemInstruction.parts);
|
|
606
784
|
if (text) out.push({ role: "system", content: text });
|
|
607
785
|
}
|
|
608
786
|
const pending = [];
|
|
609
|
-
|
|
610
|
-
const
|
|
611
|
-
for (const content of
|
|
612
|
-
|
|
787
|
+
const contents = Array.isArray(root.contents) ? root.contents : [];
|
|
788
|
+
const ids = createToolCallIdGenerator(geminiProviderIds(root));
|
|
789
|
+
for (const content of contents) {
|
|
790
|
+
if (!isRecord(content)) {
|
|
791
|
+
reporter.warn("dropped-content", "Dropped a malformed Gemini content entry.");
|
|
792
|
+
continue;
|
|
793
|
+
}
|
|
613
794
|
if (content.role === "model") {
|
|
795
|
+
const parts2 = geminiParts(content, reporter);
|
|
614
796
|
const textPieces = [];
|
|
615
797
|
const toolCalls = [];
|
|
616
|
-
for (const part of
|
|
798
|
+
for (const part of parts2) {
|
|
617
799
|
if (isRecord(part) && isRecord(part.functionCall)) {
|
|
618
800
|
const fc = part.functionCall;
|
|
619
|
-
const
|
|
620
|
-
|
|
801
|
+
const name = providerFunctionName(fc.name, reporter, "Gemini", "functionCall");
|
|
802
|
+
const providedId = nonEmptyString2(fc.id) ? fc.id : void 0;
|
|
803
|
+
const id = providedId ? ids.claim(providedId, name) : ids.generate(name);
|
|
804
|
+
if (!providedId) {
|
|
805
|
+
reporter.warn("generated-id", `Gemini functionCall '${name}' had no id; generated '${id}'.`);
|
|
806
|
+
} else if (id !== providedId) {
|
|
807
|
+
reporter.warn(
|
|
808
|
+
"generated-id",
|
|
809
|
+
`Gemini functionCall '${name}' reused id '${providedId}'; generated '${id}'.`
|
|
810
|
+
);
|
|
811
|
+
}
|
|
621
812
|
toolCalls.push({
|
|
622
813
|
id,
|
|
623
814
|
type: "function",
|
|
624
|
-
function: {
|
|
815
|
+
function: {
|
|
816
|
+
name,
|
|
817
|
+
arguments: stringifyArgumentsObject(fc.args, reporter, "Gemini", "functionCall", name)
|
|
818
|
+
}
|
|
625
819
|
});
|
|
626
|
-
pending.push({ id, name:
|
|
820
|
+
pending.push({ id, name, ...providedId ? { providerId: providedId } : {} });
|
|
627
821
|
} else if (isRecord(part) && typeof part.text === "string") {
|
|
628
822
|
textPieces.push(part.text);
|
|
823
|
+
} else {
|
|
824
|
+
reporter.warn("dropped-content", "Dropped an unsupported Gemini model content part.");
|
|
629
825
|
}
|
|
630
826
|
}
|
|
631
827
|
const text = textPieces.join("");
|
|
@@ -634,17 +830,45 @@ function fromGemini(conversation, options = {}) {
|
|
|
634
830
|
out.push(assistant);
|
|
635
831
|
continue;
|
|
636
832
|
}
|
|
637
|
-
|
|
833
|
+
if (content.role !== void 0 && content.role !== "user") {
|
|
834
|
+
reporter.warn(
|
|
835
|
+
"dropped-content",
|
|
836
|
+
`Dropped a Gemini content entry with unsupported role '${String(content.role)}'.`
|
|
837
|
+
);
|
|
838
|
+
continue;
|
|
839
|
+
}
|
|
840
|
+
const parts = geminiParts(content, reporter);
|
|
841
|
+
let contentParts = [];
|
|
638
842
|
let hasMedia = false;
|
|
843
|
+
let sawUserContent = false;
|
|
844
|
+
const flushContent = () => {
|
|
845
|
+
if (!sawUserContent) return;
|
|
846
|
+
if (contentParts.length === 0) {
|
|
847
|
+
out.push({ role: "user", content: "" });
|
|
848
|
+
} else if (hasMedia) {
|
|
849
|
+
out.push({ role: "user", content: contentParts });
|
|
850
|
+
} else {
|
|
851
|
+
const text = textOf(contentParts);
|
|
852
|
+
out.push({ role: "user", content: text });
|
|
853
|
+
}
|
|
854
|
+
contentParts = [];
|
|
855
|
+
hasMedia = false;
|
|
856
|
+
sawUserContent = false;
|
|
857
|
+
};
|
|
639
858
|
for (const part of parts) {
|
|
640
859
|
if (isRecord(part) && isRecord(part.functionResponse)) {
|
|
641
860
|
const fr = part.functionResponse;
|
|
642
|
-
const
|
|
861
|
+
const response = Object.prototype.hasOwnProperty.call(fr, "response") ? fr.response : {};
|
|
862
|
+
const responseId = nonEmptyString2(fr.id) ? fr.id : void 0;
|
|
863
|
+
const matchedName = responseId ? pending.find((pendingCall) => pendingCall.providerId === responseId || pendingCall.id === responseId)?.name : void 0;
|
|
864
|
+
const name = geminiFunctionResponseName(fr.name, matchedName, reporter);
|
|
865
|
+
const { id, matched } = resolveResponseId({ id: fr.id, name }, pending, reporter, ids);
|
|
866
|
+
flushContent();
|
|
643
867
|
out.push({
|
|
644
868
|
role: "tool",
|
|
645
869
|
tool_call_id: id,
|
|
646
|
-
content: unwrapResponse(
|
|
647
|
-
...matched ? {} : { name
|
|
870
|
+
content: unwrapResponse(response, reporter, "Gemini", "functionResponse"),
|
|
871
|
+
...matched ? {} : { name }
|
|
648
872
|
});
|
|
649
873
|
continue;
|
|
650
874
|
}
|
|
@@ -652,6 +876,7 @@ function fromGemini(conversation, options = {}) {
|
|
|
652
876
|
if (image) {
|
|
653
877
|
contentParts.push(imageToOpenAI(image));
|
|
654
878
|
hasMedia = true;
|
|
879
|
+
sawUserContent = true;
|
|
655
880
|
continue;
|
|
656
881
|
}
|
|
657
882
|
const media = mediaFromGemini(part);
|
|
@@ -660,29 +885,74 @@ function fromGemini(conversation, options = {}) {
|
|
|
660
885
|
if (openaiPart) {
|
|
661
886
|
contentParts.push(openaiPart);
|
|
662
887
|
hasMedia = true;
|
|
888
|
+
} else {
|
|
889
|
+
reporter.warn(
|
|
890
|
+
"dropped-content",
|
|
891
|
+
`A Gemini ${media.modality} ${media.source.kind} has no OpenAI Chat Completions equivalent; dropped.`
|
|
892
|
+
);
|
|
663
893
|
}
|
|
894
|
+
sawUserContent = true;
|
|
664
895
|
continue;
|
|
665
896
|
}
|
|
666
897
|
if (isRecord(part) && typeof part.text === "string") {
|
|
667
898
|
contentParts.push({ type: "text", text: part.text });
|
|
899
|
+
sawUserContent = true;
|
|
900
|
+
continue;
|
|
668
901
|
}
|
|
902
|
+
reporter.warn("dropped-content", "Dropped an unsupported Gemini user content part.");
|
|
669
903
|
}
|
|
670
|
-
|
|
671
|
-
if (hasMedia) {
|
|
672
|
-
out.push({ role: "user", content: contentParts });
|
|
673
|
-
} else {
|
|
674
|
-
const text = textOf(contentParts);
|
|
675
|
-
out.push({ role: "user", content: text });
|
|
676
|
-
}
|
|
677
|
-
}
|
|
904
|
+
flushContent();
|
|
678
905
|
}
|
|
679
906
|
return out;
|
|
680
907
|
}
|
|
681
|
-
function
|
|
682
|
-
if (
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
908
|
+
function geminiParts(content, reporter) {
|
|
909
|
+
if (Array.isArray(content.parts)) return content.parts;
|
|
910
|
+
reporter.warn("dropped-content", "Dropped malformed Gemini content parts.");
|
|
911
|
+
return [];
|
|
912
|
+
}
|
|
913
|
+
function geminiFunctionResponseName(value, matchedName, reporter) {
|
|
914
|
+
if (matchedName && value === void 0) return matchedName;
|
|
915
|
+
if (matchedName && !isProviderFunctionName(value)) {
|
|
916
|
+
reporter.warn(
|
|
917
|
+
"dropped-metadata",
|
|
918
|
+
`Gemini functionResponse had a missing or invalid function name; used matching functionCall '${matchedName}'.`
|
|
919
|
+
);
|
|
920
|
+
return matchedName;
|
|
921
|
+
}
|
|
922
|
+
return providerFunctionName(value, reporter, "Gemini", "functionResponse");
|
|
923
|
+
}
|
|
924
|
+
function resolveResponseId(response, pending, reporter, ids) {
|
|
925
|
+
const responseId = nonEmptyString2(response.id) ? response.id : void 0;
|
|
926
|
+
if (response.id !== void 0 && typeof response.id !== "string") {
|
|
927
|
+
reporter.warn(
|
|
928
|
+
"dropped-metadata",
|
|
929
|
+
`Gemini functionResponse for '${response.name}' had a non-string id; ignored it.`
|
|
930
|
+
);
|
|
931
|
+
}
|
|
932
|
+
if (responseId) {
|
|
933
|
+
const index2 = pending.findIndex((p) => p.providerId === responseId || p.id === responseId);
|
|
934
|
+
if (index2 >= 0) {
|
|
935
|
+
const [match] = pending.splice(index2, 1);
|
|
936
|
+
if (response.name !== match.name) {
|
|
937
|
+
reporter.warn(
|
|
938
|
+
"dropped-metadata",
|
|
939
|
+
`Gemini functionResponse '${responseId}' name '${response.name}' differed from matching functionCall '${match.name}'; used the call id mapping.`
|
|
940
|
+
);
|
|
941
|
+
}
|
|
942
|
+
return { id: match.id, matched: true };
|
|
943
|
+
}
|
|
944
|
+
reporter.warn(
|
|
945
|
+
"unmapped-tool-result",
|
|
946
|
+
`Gemini functionResponse '${responseId}' for '${response.name}' had no matching call; kept the response id.`
|
|
947
|
+
);
|
|
948
|
+
const id2 = ids.claim(responseId, response.name);
|
|
949
|
+
if (id2 !== responseId) {
|
|
950
|
+
reporter.warn(
|
|
951
|
+
"generated-id",
|
|
952
|
+
`Gemini functionResponse '${responseId}' for '${response.name}' reused an existing id; generated '${id2}'.`
|
|
953
|
+
);
|
|
954
|
+
}
|
|
955
|
+
return { id: id2, matched: false };
|
|
686
956
|
}
|
|
687
957
|
const index = pending.findIndex((p) => p.name === response.name);
|
|
688
958
|
if (index >= 0) {
|
|
@@ -690,13 +960,31 @@ function resolveResponseId(response, pending, reporter, generateId) {
|
|
|
690
960
|
pending.splice(index, 1);
|
|
691
961
|
return { id: id2, matched: true };
|
|
692
962
|
}
|
|
693
|
-
const id =
|
|
963
|
+
const id = ids.generate(response.name);
|
|
694
964
|
reporter.warn(
|
|
695
965
|
"unmapped-tool-result",
|
|
696
966
|
`Gemini functionResponse for '${response.name}' had no matching call; generated '${id}'.`
|
|
697
967
|
);
|
|
698
968
|
return { id, matched: false };
|
|
699
969
|
}
|
|
970
|
+
function geminiProviderIds(conversation) {
|
|
971
|
+
const ids = [];
|
|
972
|
+
const root = isRecord(conversation) ? conversation : {};
|
|
973
|
+
const contents = Array.isArray(root.contents) ? root.contents : [];
|
|
974
|
+
for (const content of contents) {
|
|
975
|
+
if (!isRecord(content)) continue;
|
|
976
|
+
const parts = Array.isArray(content.parts) ? content.parts : [];
|
|
977
|
+
for (const part of parts) {
|
|
978
|
+
if (isRecord(part) && isRecord(part.functionCall) && nonEmptyString2(part.functionCall.id)) {
|
|
979
|
+
ids.push(part.functionCall.id);
|
|
980
|
+
}
|
|
981
|
+
if (isRecord(part) && isRecord(part.functionResponse) && nonEmptyString2(part.functionResponse.id)) {
|
|
982
|
+
ids.push(part.functionResponse.id);
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
return ids;
|
|
987
|
+
}
|
|
700
988
|
|
|
701
989
|
// src/convert.ts
|
|
702
990
|
function convert(conversation, route, options = {}) {
|
|
@@ -730,6 +1018,42 @@ function fromCanonical(canonical, to, options) {
|
|
|
730
1018
|
|
|
731
1019
|
// src/response.ts
|
|
732
1020
|
var num = (value) => typeof value === "number" ? value : 0;
|
|
1021
|
+
var nonEmptyString3 = (value) => typeof value === "string" && value.length > 0;
|
|
1022
|
+
function responseRoot(body, reporter, provider) {
|
|
1023
|
+
if (isRecord(body)) return body;
|
|
1024
|
+
reporter.warn("dropped-content", `Dropped malformed ${provider} response body; expected an object.`);
|
|
1025
|
+
return {};
|
|
1026
|
+
}
|
|
1027
|
+
function responseArrayField(root, field, reporter, context, noun) {
|
|
1028
|
+
const value = root[field];
|
|
1029
|
+
if (value === void 0) {
|
|
1030
|
+
reporter.warn("dropped-content", `${context} missing ${field} array; no ${noun} were read.`);
|
|
1031
|
+
return [];
|
|
1032
|
+
}
|
|
1033
|
+
if (!Array.isArray(value)) {
|
|
1034
|
+
reporter.warn("dropped-content", `Dropped malformed ${context} ${field}; expected an array.`);
|
|
1035
|
+
return [];
|
|
1036
|
+
}
|
|
1037
|
+
return value;
|
|
1038
|
+
}
|
|
1039
|
+
function firstResponseRecord(items, reporter, provider, noun) {
|
|
1040
|
+
if (items.length === 0) return {};
|
|
1041
|
+
if (isRecord(items[0])) return items[0];
|
|
1042
|
+
reporter.warn("dropped-content", `Dropped malformed ${provider} response ${noun}; expected an object.`);
|
|
1043
|
+
return {};
|
|
1044
|
+
}
|
|
1045
|
+
function responseRecordField(root, field, reporter, context, noun) {
|
|
1046
|
+
const value = root[field];
|
|
1047
|
+
if (value === void 0) {
|
|
1048
|
+
reporter.warn("dropped-content", `${context} missing ${field} object; no ${noun} were read.`);
|
|
1049
|
+
return {};
|
|
1050
|
+
}
|
|
1051
|
+
if (!isRecord(value)) {
|
|
1052
|
+
reporter.warn("dropped-content", `Dropped malformed ${context} ${field}; expected an object.`);
|
|
1053
|
+
return {};
|
|
1054
|
+
}
|
|
1055
|
+
return value;
|
|
1056
|
+
}
|
|
733
1057
|
function buildMessage(text, toolCalls) {
|
|
734
1058
|
const message = { role: "assistant", content: text ? text : null };
|
|
735
1059
|
if (toolCalls.length > 0) message.tool_calls = toolCalls;
|
|
@@ -738,6 +1062,97 @@ function buildMessage(text, toolCalls) {
|
|
|
738
1062
|
function finalReason(mapped, toolCalls) {
|
|
739
1063
|
return toolCalls.length > 0 ? "tool_calls" : mapped;
|
|
740
1064
|
}
|
|
1065
|
+
function normalizeProviderArguments(value, reporter, provider, part, fnName) {
|
|
1066
|
+
if (typeof value !== "string") return stringifyArgumentsObject(value, reporter, provider, part, fnName);
|
|
1067
|
+
const parsed = tryParseJson(value);
|
|
1068
|
+
if (parsed.ok && isRecord(parsed.value)) return value;
|
|
1069
|
+
reporter.warn(
|
|
1070
|
+
"invalid-json-arguments",
|
|
1071
|
+
`${provider} ${part} '${fnName}' had arguments that were not a JSON object string; used an empty object instead.`
|
|
1072
|
+
);
|
|
1073
|
+
return "{}";
|
|
1074
|
+
}
|
|
1075
|
+
function normalizeOpenAIToolCalls(value, reporter) {
|
|
1076
|
+
if (value === void 0) return [];
|
|
1077
|
+
if (!Array.isArray(value)) {
|
|
1078
|
+
reporter.warn("dropped-content", "Dropped malformed OpenAI Chat Completions tool_calls; expected an array.");
|
|
1079
|
+
return [];
|
|
1080
|
+
}
|
|
1081
|
+
const toolCalls = [];
|
|
1082
|
+
const ids = createToolCallIdGenerator(
|
|
1083
|
+
value.flatMap(
|
|
1084
|
+
(call) => isRecord(call) && (call.type === void 0 || call.type === "function") && nonEmptyString3(call.id) ? [call.id] : []
|
|
1085
|
+
)
|
|
1086
|
+
);
|
|
1087
|
+
for (const call of value) {
|
|
1088
|
+
if (!isRecord(call)) {
|
|
1089
|
+
reporter.warn("dropped-content", "Dropped malformed OpenAI Chat Completions tool_call; expected an object.");
|
|
1090
|
+
continue;
|
|
1091
|
+
}
|
|
1092
|
+
if (call.type !== void 0 && call.type !== "function") {
|
|
1093
|
+
reporter.warn(
|
|
1094
|
+
"dropped-content",
|
|
1095
|
+
`OpenAI Chat Completions tool_call type '${String(call.type)}' is not supported; dropped.`
|
|
1096
|
+
);
|
|
1097
|
+
continue;
|
|
1098
|
+
}
|
|
1099
|
+
const fn = isRecord(call.function) ? call.function : {};
|
|
1100
|
+
const name = providerFunctionName(fn.name, reporter, "OpenAI Chat Completions", "tool_call.function");
|
|
1101
|
+
const providedId = nonEmptyString3(call.id) ? call.id : void 0;
|
|
1102
|
+
const id = providedId ? ids.claim(providedId, name) : ids.generate(name);
|
|
1103
|
+
if (!providedId) {
|
|
1104
|
+
reporter.warn("generated-id", `OpenAI Chat Completions tool_call '${name}' had no id; generated '${id}'.`);
|
|
1105
|
+
} else if (id !== providedId) {
|
|
1106
|
+
reporter.warn(
|
|
1107
|
+
"generated-id",
|
|
1108
|
+
`OpenAI Chat Completions tool_call '${name}' reused id '${providedId}'; generated '${id}'.`
|
|
1109
|
+
);
|
|
1110
|
+
}
|
|
1111
|
+
const args = normalizeProviderArguments(
|
|
1112
|
+
fn.arguments,
|
|
1113
|
+
reporter,
|
|
1114
|
+
"OpenAI Chat Completions",
|
|
1115
|
+
"tool_call.function",
|
|
1116
|
+
name
|
|
1117
|
+
);
|
|
1118
|
+
toolCalls.push({ id, type: "function", function: { name, arguments: args } });
|
|
1119
|
+
}
|
|
1120
|
+
return toolCalls;
|
|
1121
|
+
}
|
|
1122
|
+
function normalizeOpenAIFunctionCall(value, reporter) {
|
|
1123
|
+
if (!isRecord(value)) return [];
|
|
1124
|
+
const name = providerFunctionName(value.name, reporter, "OpenAI Chat Completions", "function_call");
|
|
1125
|
+
const id = `call_${name.replace(/[^a-zA-Z0-9_-]/g, "_")}_0`;
|
|
1126
|
+
reporter.warn("generated-id", `OpenAI Chat Completions function_call '${name}' had no id; generated '${id}'.`);
|
|
1127
|
+
const args = normalizeProviderArguments(value.arguments, reporter, "OpenAI Chat Completions", "function_call", name);
|
|
1128
|
+
return [{ id, type: "function", function: { name, arguments: args } }];
|
|
1129
|
+
}
|
|
1130
|
+
function openAIChatContentText(content, reporter) {
|
|
1131
|
+
if (typeof content === "string") return content;
|
|
1132
|
+
if (content === null || content === void 0) return "";
|
|
1133
|
+
if (!Array.isArray(content)) {
|
|
1134
|
+
reporter.warn("dropped-content", "Dropped malformed OpenAI Chat Completions response content.");
|
|
1135
|
+
return "";
|
|
1136
|
+
}
|
|
1137
|
+
const pieces = [];
|
|
1138
|
+
for (const part of content) {
|
|
1139
|
+
if (typeof part === "string") {
|
|
1140
|
+
pieces.push(part);
|
|
1141
|
+
} else if (isRecord(part) && typeof part.text === "string") {
|
|
1142
|
+
pieces.push(part.text);
|
|
1143
|
+
} else if (isRecord(part) && part.type === "refusal" && typeof part.refusal === "string") {
|
|
1144
|
+
pieces.push(part.refusal);
|
|
1145
|
+
} else {
|
|
1146
|
+
reporter.warn("dropped-content", "Dropped unsupported OpenAI Chat Completions response content part.");
|
|
1147
|
+
}
|
|
1148
|
+
}
|
|
1149
|
+
return pieces.join("");
|
|
1150
|
+
}
|
|
1151
|
+
function openAIChatMessageText(message, reporter) {
|
|
1152
|
+
const content = openAIChatContentText(message.content, reporter);
|
|
1153
|
+
if (content) return content;
|
|
1154
|
+
return typeof message.refusal === "string" ? message.refusal : content;
|
|
1155
|
+
}
|
|
741
1156
|
var OPENAI_FINISH = {
|
|
742
1157
|
stop: "stop",
|
|
743
1158
|
length: "length",
|
|
@@ -745,16 +1160,27 @@ var OPENAI_FINISH = {
|
|
|
745
1160
|
content_filter: "content_filter",
|
|
746
1161
|
function_call: "tool_calls"
|
|
747
1162
|
};
|
|
748
|
-
function responseFromOpenAI(body) {
|
|
749
|
-
const
|
|
750
|
-
const
|
|
751
|
-
const
|
|
752
|
-
const
|
|
753
|
-
const
|
|
1163
|
+
function responseFromOpenAI(body, options = {}) {
|
|
1164
|
+
const reporter = new Reporter(options);
|
|
1165
|
+
const isObjectBody = isRecord(body);
|
|
1166
|
+
const root = isObjectBody ? body : responseRoot(body, reporter, "OpenAI Chat Completions");
|
|
1167
|
+
const choices = isObjectBody ? responseArrayField(root, "choices", reporter, "OpenAI Chat Completions response", "choices") : [];
|
|
1168
|
+
const hasChoiceRecord = choices.length > 0 && isRecord(choices[0]);
|
|
1169
|
+
const choice = firstResponseRecord(choices, reporter, "OpenAI Chat Completions", "choice");
|
|
1170
|
+
const message = hasChoiceRecord ? responseRecordField(
|
|
1171
|
+
choice,
|
|
1172
|
+
"message",
|
|
1173
|
+
reporter,
|
|
1174
|
+
"OpenAI Chat Completions response choice",
|
|
1175
|
+
"message content or tool calls"
|
|
1176
|
+
) : {};
|
|
1177
|
+
const text = openAIChatMessageText(message, reporter);
|
|
1178
|
+
const toolCalls = normalizeOpenAIToolCalls(message.tool_calls, reporter);
|
|
1179
|
+
const normalizedToolCalls = toolCalls.length > 0 ? toolCalls : normalizeOpenAIFunctionCall(message.function_call, reporter);
|
|
754
1180
|
const usage = isRecord(root.usage) ? root.usage : {};
|
|
755
1181
|
return {
|
|
756
|
-
message: buildMessage(text,
|
|
757
|
-
finishReason: finalReason(OPENAI_FINISH[String(choice.finish_reason)] ?? "unknown",
|
|
1182
|
+
message: buildMessage(text, normalizedToolCalls),
|
|
1183
|
+
finishReason: finalReason(OPENAI_FINISH[String(choice.finish_reason)] ?? "unknown", normalizedToolCalls),
|
|
758
1184
|
usage: { inputTokens: num(usage.prompt_tokens), outputTokens: num(usage.completion_tokens) }
|
|
759
1185
|
};
|
|
760
1186
|
}
|
|
@@ -762,35 +1188,85 @@ var OPENAI_RESPONSES_INCOMPLETE = {
|
|
|
762
1188
|
max_output_tokens: "length",
|
|
763
1189
|
content_filter: "content_filter"
|
|
764
1190
|
};
|
|
1191
|
+
function openAIResponsesContentText(content, reporter) {
|
|
1192
|
+
if (content === void 0) return "";
|
|
1193
|
+
if (!Array.isArray(content)) {
|
|
1194
|
+
reporter.warn("dropped-content", "Dropped malformed OpenAI Responses message content.");
|
|
1195
|
+
return "";
|
|
1196
|
+
}
|
|
1197
|
+
const pieces = [];
|
|
1198
|
+
for (const part of content) {
|
|
1199
|
+
if (!isRecord(part)) {
|
|
1200
|
+
reporter.warn("dropped-content", "Dropped malformed OpenAI Responses message content part.");
|
|
1201
|
+
} else if (typeof part.text === "string" && (part.type === "output_text" || part.type === "text")) {
|
|
1202
|
+
pieces.push(part.text);
|
|
1203
|
+
} else if (part.type === "refusal" && typeof part.refusal === "string") {
|
|
1204
|
+
pieces.push(part.refusal);
|
|
1205
|
+
} else {
|
|
1206
|
+
reporter.warn("dropped-content", "Dropped unsupported OpenAI Responses message content part.");
|
|
1207
|
+
}
|
|
1208
|
+
}
|
|
1209
|
+
return pieces.join("");
|
|
1210
|
+
}
|
|
765
1211
|
function responseApiFinishReason(root) {
|
|
766
1212
|
if (root.status === "completed") return "stop";
|
|
767
1213
|
if (root.status !== "incomplete") return "unknown";
|
|
768
1214
|
const details = isRecord(root.incomplete_details) ? root.incomplete_details : {};
|
|
769
1215
|
return OPENAI_RESPONSES_INCOMPLETE[String(details.reason)] ?? "unknown";
|
|
770
1216
|
}
|
|
771
|
-
function
|
|
772
|
-
|
|
773
|
-
|
|
1217
|
+
function openAIResponsesOutput(root, reporter) {
|
|
1218
|
+
if (root.output === void 0) {
|
|
1219
|
+
reporter.warn(
|
|
1220
|
+
"dropped-content",
|
|
1221
|
+
"OpenAI Responses response missing top-level output array; no output items were read."
|
|
1222
|
+
);
|
|
1223
|
+
return [];
|
|
1224
|
+
}
|
|
1225
|
+
if (!Array.isArray(root.output)) {
|
|
1226
|
+
reporter.warn("dropped-content", "Dropped malformed OpenAI Responses top-level output; expected an array.");
|
|
1227
|
+
return [];
|
|
1228
|
+
}
|
|
1229
|
+
return root.output;
|
|
1230
|
+
}
|
|
1231
|
+
function responseFromOpenAIResponses(body, options = {}) {
|
|
1232
|
+
const reporter = new Reporter(options);
|
|
1233
|
+
const isObjectBody = isRecord(body);
|
|
1234
|
+
const root = isObjectBody ? body : responseRoot(body, reporter, "OpenAI Responses");
|
|
1235
|
+
const output = isObjectBody ? openAIResponsesOutput(root, reporter) : [];
|
|
774
1236
|
const textPieces = [];
|
|
775
1237
|
const toolCalls = [];
|
|
776
|
-
|
|
1238
|
+
const ids = createToolCallIdGenerator(
|
|
1239
|
+
output.flatMap((item) => {
|
|
1240
|
+
if (!isRecord(item) || item.type !== "function_call") return [];
|
|
1241
|
+
if (nonEmptyString3(item.call_id)) return [item.call_id];
|
|
1242
|
+
return nonEmptyString3(item.id) ? [item.id] : [];
|
|
1243
|
+
})
|
|
1244
|
+
);
|
|
777
1245
|
for (const item of output) {
|
|
778
|
-
if (!isRecord(item))
|
|
1246
|
+
if (!isRecord(item)) {
|
|
1247
|
+
reporter.warn("dropped-content", "Dropped malformed OpenAI Responses output item.");
|
|
1248
|
+
continue;
|
|
1249
|
+
}
|
|
779
1250
|
if (item.type === "message") {
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
1251
|
+
textPieces.push(openAIResponsesContentText(item.content, reporter));
|
|
1252
|
+
} else if (item.type === "function_call") {
|
|
1253
|
+
const name = providerFunctionName(item.name, reporter, "OpenAI Responses", "function_call");
|
|
1254
|
+
const callId = nonEmptyString3(item.call_id) ? item.call_id : void 0;
|
|
1255
|
+
const itemId = nonEmptyString3(item.id) ? item.id : void 0;
|
|
1256
|
+
const id = callId ?? itemId;
|
|
1257
|
+
const toolCallId = id ? ids.claim(id, name) : ids.generate(name);
|
|
1258
|
+
if (!callId && !itemId) {
|
|
1259
|
+
reporter.warn("generated-id", `OpenAI Responses function_call '${name}' had no id; generated '${toolCallId}'.`);
|
|
1260
|
+
} else if (toolCallId !== id) {
|
|
1261
|
+
reporter.warn(
|
|
1262
|
+
"generated-id",
|
|
1263
|
+
`OpenAI Responses function_call '${name}' reused id '${id}'; generated '${toolCallId}'.`
|
|
1264
|
+
);
|
|
788
1265
|
}
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
toolCalls.push({ id, type: "function", function: { name, arguments: args } });
|
|
1266
|
+
const args = normalizeProviderArguments(item.arguments, reporter, "OpenAI Responses", "function_call", name);
|
|
1267
|
+
toolCalls.push({ id: toolCallId, type: "function", function: { name, arguments: args } });
|
|
1268
|
+
} else {
|
|
1269
|
+
reporter.warn("dropped-content", `Dropped unsupported OpenAI Responses output item '${String(item.type)}'.`);
|
|
794
1270
|
}
|
|
795
1271
|
}
|
|
796
1272
|
const usage = isRecord(root.usage) ? root.usage : {};
|
|
@@ -808,21 +1284,47 @@ var ANTHROPIC_FINISH = {
|
|
|
808
1284
|
refusal: "content_filter",
|
|
809
1285
|
pause_turn: "unknown"
|
|
810
1286
|
};
|
|
811
|
-
function responseFromAnthropic(body) {
|
|
812
|
-
const
|
|
813
|
-
const
|
|
1287
|
+
function responseFromAnthropic(body, options = {}) {
|
|
1288
|
+
const reporter = new Reporter(options);
|
|
1289
|
+
const isObjectBody = isRecord(body);
|
|
1290
|
+
const root = isObjectBody ? body : responseRoot(body, reporter, "Anthropic");
|
|
1291
|
+
const blocks = isObjectBody ? responseArrayField(root, "content", reporter, "Anthropic response", "content blocks") : [];
|
|
814
1292
|
const textPieces = [];
|
|
815
1293
|
const toolCalls = [];
|
|
1294
|
+
const ids = createToolCallIdGenerator(
|
|
1295
|
+
blocks.flatMap(
|
|
1296
|
+
(block) => isRecord(block) && block.type === "tool_use" && nonEmptyString3(block.id) ? [block.id] : []
|
|
1297
|
+
)
|
|
1298
|
+
);
|
|
816
1299
|
for (const block of blocks) {
|
|
817
|
-
if (!isRecord(block))
|
|
1300
|
+
if (!isRecord(block)) {
|
|
1301
|
+
reporter.warn("dropped-content", "Dropped a malformed Anthropic response content block.");
|
|
1302
|
+
continue;
|
|
1303
|
+
}
|
|
818
1304
|
if (block.type === "text" && typeof block.text === "string") {
|
|
819
1305
|
textPieces.push(block.text);
|
|
820
|
-
} else if (block.type === "tool_use"
|
|
1306
|
+
} else if (block.type === "tool_use") {
|
|
1307
|
+
const name = providerFunctionName(block.name, reporter, "Anthropic", "tool_use");
|
|
1308
|
+
const providedId = nonEmptyString3(block.id) ? block.id : void 0;
|
|
1309
|
+
const id = providedId ? ids.claim(providedId, name) : ids.generate(name);
|
|
1310
|
+
if (!providedId) {
|
|
1311
|
+
reporter.warn("generated-id", `Anthropic tool_use '${name}' had no id; generated '${id}'.`);
|
|
1312
|
+
} else if (id !== providedId) {
|
|
1313
|
+
reporter.warn("generated-id", `Anthropic tool_use '${name}' reused id '${providedId}'; generated '${id}'.`);
|
|
1314
|
+
}
|
|
821
1315
|
toolCalls.push({
|
|
822
|
-
id
|
|
1316
|
+
id,
|
|
823
1317
|
type: "function",
|
|
824
|
-
function: {
|
|
1318
|
+
function: {
|
|
1319
|
+
name,
|
|
1320
|
+
arguments: stringifyArgumentsObject(block.input, reporter, "Anthropic", "tool_use", name)
|
|
1321
|
+
}
|
|
825
1322
|
});
|
|
1323
|
+
} else {
|
|
1324
|
+
reporter.warn(
|
|
1325
|
+
"dropped-content",
|
|
1326
|
+
`Anthropic response content block '${String(block.type)}' is not supported; dropped.`
|
|
1327
|
+
);
|
|
826
1328
|
}
|
|
827
1329
|
}
|
|
828
1330
|
const usage = isRecord(root.usage) ? root.usage : {};
|
|
@@ -837,30 +1339,63 @@ var GEMINI_FINISH = {
|
|
|
837
1339
|
MAX_TOKENS: "length",
|
|
838
1340
|
SAFETY: "content_filter",
|
|
839
1341
|
RECITATION: "content_filter",
|
|
840
|
-
|
|
1342
|
+
BLOCKLIST: "content_filter",
|
|
1343
|
+
PROHIBITED_CONTENT: "content_filter",
|
|
1344
|
+
SPII: "content_filter",
|
|
1345
|
+
MALFORMED_FUNCTION_CALL: "content_filter",
|
|
1346
|
+
MODEL_ARMOR: "content_filter",
|
|
1347
|
+
IMAGE_SAFETY: "content_filter",
|
|
1348
|
+
IMAGE_PROHIBITED_CONTENT: "content_filter",
|
|
1349
|
+
IMAGE_RECITATION: "content_filter"
|
|
841
1350
|
};
|
|
842
1351
|
function responseFromGemini(body, options = {}) {
|
|
843
1352
|
const reporter = new Reporter(options);
|
|
844
|
-
const
|
|
845
|
-
const
|
|
1353
|
+
const isObjectBody = isRecord(body);
|
|
1354
|
+
const root = isObjectBody ? body : responseRoot(body, reporter, "Gemini");
|
|
1355
|
+
const candidates = isObjectBody ? responseArrayField(root, "candidates", reporter, "Gemini response", "candidates") : [];
|
|
1356
|
+
const hasCandidateRecord = candidates.length > 0 && isRecord(candidates[0]);
|
|
1357
|
+
const candidate = firstResponseRecord(candidates, reporter, "Gemini", "candidate");
|
|
846
1358
|
const content = isRecord(candidate.content) ? candidate.content : {};
|
|
847
|
-
|
|
1359
|
+
if (hasCandidateRecord && !isRecord(candidate.content)) {
|
|
1360
|
+
reporter.warn("dropped-content", "Dropped malformed Gemini response candidate content; expected an object.");
|
|
1361
|
+
}
|
|
1362
|
+
const parts = hasCandidateRecord && isRecord(candidate.content) ? responseArrayField(content, "parts", reporter, "Gemini response candidate content", "content parts") : [];
|
|
848
1363
|
const textPieces = [];
|
|
849
1364
|
const toolCalls = [];
|
|
850
|
-
|
|
1365
|
+
const ids = createToolCallIdGenerator(
|
|
1366
|
+
parts.flatMap(
|
|
1367
|
+
(part) => isRecord(part) && isRecord(part.functionCall) && nonEmptyString3(part.functionCall.id) ? [part.functionCall.id] : []
|
|
1368
|
+
)
|
|
1369
|
+
);
|
|
851
1370
|
for (const part of parts) {
|
|
852
|
-
if (!isRecord(part))
|
|
1371
|
+
if (!isRecord(part)) {
|
|
1372
|
+
reporter.warn("dropped-content", "Dropped a malformed Gemini response content part.");
|
|
1373
|
+
continue;
|
|
1374
|
+
}
|
|
853
1375
|
if (isRecord(part.functionCall)) {
|
|
854
1376
|
const call = part.functionCall;
|
|
855
|
-
const name =
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
1377
|
+
const name = providerFunctionName(call.name, reporter, "Gemini", "functionCall");
|
|
1378
|
+
const providedId = nonEmptyString3(call.id) ? call.id : void 0;
|
|
1379
|
+
const id = providedId ? ids.claim(providedId, name) : ids.generate(name);
|
|
1380
|
+
if (!providedId) {
|
|
859
1381
|
reporter.warn("generated-id", `Gemini functionCall '${name}' had no id; generated '${id}'.`);
|
|
1382
|
+
} else if (id !== providedId) {
|
|
1383
|
+
reporter.warn("generated-id", `Gemini functionCall '${name}' reused id '${providedId}'; generated '${id}'.`);
|
|
860
1384
|
}
|
|
861
|
-
toolCalls.push({
|
|
1385
|
+
toolCalls.push({
|
|
1386
|
+
id,
|
|
1387
|
+
type: "function",
|
|
1388
|
+
function: {
|
|
1389
|
+
name,
|
|
1390
|
+
arguments: stringifyArgumentsObject(call.args, reporter, "Gemini", "functionCall", name)
|
|
1391
|
+
}
|
|
1392
|
+
});
|
|
1393
|
+
} else if (part.thought === true) {
|
|
1394
|
+
reporter.warn("dropped-content", "Dropped a Gemini thought (reasoning) response part.");
|
|
862
1395
|
} else if (typeof part.text === "string") {
|
|
863
1396
|
textPieces.push(part.text);
|
|
1397
|
+
} else {
|
|
1398
|
+
reporter.warn("dropped-content", "Dropped an unsupported Gemini response content part.");
|
|
864
1399
|
}
|
|
865
1400
|
}
|
|
866
1401
|
const usage = isRecord(root.usageMetadata) ? root.usageMetadata : {};
|
|
@@ -873,17 +1408,31 @@ function responseFromGemini(body, options = {}) {
|
|
|
873
1408
|
function normalizeResponse(body, route, options = {}) {
|
|
874
1409
|
switch (route.from) {
|
|
875
1410
|
case "openai":
|
|
876
|
-
return responseFromOpenAI(body);
|
|
1411
|
+
return responseFromOpenAI(body, options);
|
|
877
1412
|
case "openai-responses":
|
|
878
|
-
return responseFromOpenAIResponses(body);
|
|
1413
|
+
return responseFromOpenAIResponses(body, options);
|
|
879
1414
|
case "anthropic":
|
|
880
|
-
return responseFromAnthropic(body);
|
|
1415
|
+
return responseFromAnthropic(body, options);
|
|
881
1416
|
case "gemini":
|
|
882
1417
|
return responseFromGemini(body, options);
|
|
883
1418
|
default:
|
|
884
1419
|
throw new Error(`Unknown source provider: ${String(route.from)}`);
|
|
885
1420
|
}
|
|
886
1421
|
}
|
|
1422
|
+
|
|
1423
|
+
// src/types.ts
|
|
1424
|
+
var warningCodes = Object.freeze([
|
|
1425
|
+
"generated-id",
|
|
1426
|
+
"unmapped-tool-result",
|
|
1427
|
+
"merged-role",
|
|
1428
|
+
"dropped-content",
|
|
1429
|
+
"dropped-metadata",
|
|
1430
|
+
"invalid-json-arguments",
|
|
1431
|
+
"system-midstream",
|
|
1432
|
+
"gemini-url-image",
|
|
1433
|
+
"gemini-url-media",
|
|
1434
|
+
"unsupported-modality"
|
|
1435
|
+
]);
|
|
887
1436
|
// Annotate the CommonJS export names for ESM import in node:
|
|
888
1437
|
0 && (module.exports = {
|
|
889
1438
|
convert,
|
|
@@ -897,6 +1446,7 @@ function normalizeResponse(body, route, options = {}) {
|
|
|
897
1446
|
responseFromOpenAIResponses,
|
|
898
1447
|
toAnthropic,
|
|
899
1448
|
toDataUrl,
|
|
900
|
-
toGemini
|
|
1449
|
+
toGemini,
|
|
1450
|
+
warningCodes
|
|
901
1451
|
});
|
|
902
1452
|
//# sourceMappingURL=index.cjs.map
|