@effect/ai-openrouter 4.0.0-beta.3 → 4.0.0-beta.30
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/Generated.d.ts +1233 -1154
- package/dist/Generated.d.ts.map +1 -1
- package/dist/Generated.js +61 -32
- package/dist/Generated.js.map +1 -1
- package/dist/OpenRouterClient.d.ts +1 -1
- package/dist/OpenRouterClient.d.ts.map +1 -1
- package/dist/OpenRouterError.d.ts +22 -32
- package/dist/OpenRouterError.d.ts.map +1 -1
- package/dist/OpenRouterLanguageModel.d.ts +8 -0
- package/dist/OpenRouterLanguageModel.d.ts.map +1 -1
- package/dist/OpenRouterLanguageModel.js +227 -233
- package/dist/OpenRouterLanguageModel.js.map +1 -1
- package/package.json +3 -3
- package/src/Generated.ts +124 -54
- package/src/OpenRouterClient.ts +1 -1
- package/src/OpenRouterError.ts +24 -32
- package/src/OpenRouterLanguageModel.ts +222 -232
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import * as Arr from "effect/Array";
|
|
6
6
|
import * as DateTime from "effect/DateTime";
|
|
7
7
|
import * as Effect from "effect/Effect";
|
|
8
|
-
import * as
|
|
8
|
+
import * as Encoding from "effect/Encoding";
|
|
9
9
|
import { dual } from "effect/Function";
|
|
10
10
|
import * as Layer from "effect/Layer";
|
|
11
11
|
import * as Predicate from "effect/Predicate";
|
|
@@ -40,7 +40,7 @@ export class Config extends /*#__PURE__*/ServiceMap.Service()("@effect/ai-openro
|
|
|
40
40
|
* @since 1.0.0
|
|
41
41
|
* @category constructors
|
|
42
42
|
*/
|
|
43
|
-
export const model = (model, config) => AiModel.make("openai", layer({
|
|
43
|
+
export const model = (model, config) => AiModel.make("openai", model, layer({
|
|
44
44
|
model,
|
|
45
45
|
config
|
|
46
46
|
}));
|
|
@@ -99,6 +99,7 @@ export const make = /*#__PURE__*/Effect.fnUntraced(function* ({
|
|
|
99
99
|
return request;
|
|
100
100
|
});
|
|
101
101
|
return yield* LanguageModel.make({
|
|
102
|
+
codecTransformer: toCodecOpenAI,
|
|
102
103
|
generateText: Effect.fnUntraced(function* (options) {
|
|
103
104
|
const config = yield* makeConfig;
|
|
104
105
|
const request = yield* makeRequest({
|
|
@@ -129,7 +130,7 @@ export const make = /*#__PURE__*/Effect.fnUntraced(function* ({
|
|
|
129
130
|
annotateStreamResponse(options.span, response);
|
|
130
131
|
return response;
|
|
131
132
|
})))
|
|
132
|
-
})
|
|
133
|
+
});
|
|
133
134
|
});
|
|
134
135
|
/**
|
|
135
136
|
* Creates a layer for the OpenRouter language model.
|
|
@@ -221,7 +222,7 @@ const prepareMessages = /*#__PURE__*/Effect.fnUntraced(function* ({
|
|
|
221
222
|
content.push({
|
|
222
223
|
type: "image_url",
|
|
223
224
|
image_url: {
|
|
224
|
-
url: part.data instanceof URL ? part.data.toString() : part.data instanceof Uint8Array ? `data:${mediaType};base64,${
|
|
225
|
+
url: part.data instanceof URL ? part.data.toString() : part.data instanceof Uint8Array ? `data:${mediaType};base64,${Encoding.encodeBase64(part.data)}` : part.data
|
|
225
226
|
},
|
|
226
227
|
...(Predicate.isNotNull(partCacheControl) ? {
|
|
227
228
|
cache_control: partCacheControl
|
|
@@ -235,7 +236,7 @@ const prepareMessages = /*#__PURE__*/Effect.fnUntraced(function* ({
|
|
|
235
236
|
type: "file",
|
|
236
237
|
file: {
|
|
237
238
|
filename: fileName,
|
|
238
|
-
file_data: part.data instanceof URL ? part.data.toString() : part.data instanceof Uint8Array ? `data:${part.mediaType};base64,${
|
|
239
|
+
file_data: part.data instanceof URL ? part.data.toString() : part.data instanceof Uint8Array ? `data:${part.mediaType};base64,${Encoding.encodeBase64(part.data)}` : part.data
|
|
239
240
|
},
|
|
240
241
|
...(Predicate.isNotNull(partCacheControl) ? {
|
|
241
242
|
cache_control: partCacheControl
|
|
@@ -636,267 +637,260 @@ const makeStreamResponse = /*#__PURE__*/Effect.fnUntraced(function* ({
|
|
|
636
637
|
usage.outputTokens = computed.outputTokens;
|
|
637
638
|
}
|
|
638
639
|
const choice = event.choices[0];
|
|
639
|
-
if (Predicate.
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
}
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
activeReasoningId = openRouterResponseId ?? (yield* idGenerator.generateId());
|
|
640
|
+
if (Predicate.isNotUndefined(choice)) {
|
|
641
|
+
if (Predicate.isNotNullish(choice.finish_reason)) {
|
|
642
|
+
finishReason = resolveFinishReason(choice.finish_reason);
|
|
643
|
+
}
|
|
644
|
+
const delta = choice.delta;
|
|
645
|
+
if (Predicate.isNullish(delta)) {
|
|
646
|
+
return parts;
|
|
647
|
+
}
|
|
648
|
+
const emitReasoning = Effect.fnUntraced(function* (delta, metadata) {
|
|
649
|
+
if (!reasoningStarted) {
|
|
650
|
+
activeReasoningId = openRouterResponseId ?? (yield* idGenerator.generateId());
|
|
651
|
+
parts.push({
|
|
652
|
+
type: "reasoning-start",
|
|
653
|
+
id: activeReasoningId,
|
|
654
|
+
metadata
|
|
655
|
+
});
|
|
656
|
+
reasoningStarted = true;
|
|
657
|
+
}
|
|
658
658
|
parts.push({
|
|
659
|
-
type: "reasoning-
|
|
659
|
+
type: "reasoning-delta",
|
|
660
660
|
id: activeReasoningId,
|
|
661
|
+
delta,
|
|
661
662
|
metadata
|
|
662
663
|
});
|
|
663
|
-
reasoningStarted = true;
|
|
664
|
-
}
|
|
665
|
-
parts.push({
|
|
666
|
-
type: "reasoning-delta",
|
|
667
|
-
id: activeReasoningId,
|
|
668
|
-
delta,
|
|
669
|
-
metadata
|
|
670
664
|
});
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
665
|
+
const reasoningDetails = delta.reasoning_details;
|
|
666
|
+
if (Predicate.isNotUndefined(reasoningDetails) && reasoningDetails.length > 0) {
|
|
667
|
+
// Accumulate reasoning_details to preserve for multi-turn conversations
|
|
668
|
+
// Merge consecutive reasoning.text items into a single entry
|
|
669
|
+
for (const detail of reasoningDetails) {
|
|
670
|
+
if (detail.type === "reasoning.text") {
|
|
671
|
+
const lastDetail = accumulatedReasoningDetails[accumulatedReasoningDetails.length - 1];
|
|
672
|
+
if (Predicate.isNotUndefined(lastDetail) && lastDetail.type === "reasoning.text") {
|
|
673
|
+
// Merge with the previous text detail
|
|
674
|
+
lastDetail.text = (lastDetail.text ?? "") + (detail.text ?? "");
|
|
675
|
+
lastDetail.signature = lastDetail.signature ?? detail.signature ?? null;
|
|
676
|
+
lastDetail.format = lastDetail.format ?? detail.format ?? null;
|
|
677
|
+
} else {
|
|
678
|
+
// Start a new text detail
|
|
679
|
+
accumulatedReasoningDetails.push({
|
|
680
|
+
...detail
|
|
681
|
+
});
|
|
682
|
+
}
|
|
684
683
|
} else {
|
|
685
|
-
//
|
|
686
|
-
accumulatedReasoningDetails.push(
|
|
687
|
-
...detail
|
|
688
|
-
});
|
|
684
|
+
// Non-text details (encrypted, summary) are pushed as-is
|
|
685
|
+
accumulatedReasoningDetails.push(detail);
|
|
689
686
|
}
|
|
690
|
-
} else {
|
|
691
|
-
// Non-text details (encrypted, summary) are pushed as-is
|
|
692
|
-
accumulatedReasoningDetails.push(detail);
|
|
693
|
-
}
|
|
694
|
-
}
|
|
695
|
-
// Emit reasoning_details in providerMetadata for each delta chunk
|
|
696
|
-
// so users can accumulate them on their end before sending back
|
|
697
|
-
const metadata = {
|
|
698
|
-
openrouter: {
|
|
699
|
-
reasoningDetails
|
|
700
687
|
}
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
688
|
+
// Emit reasoning_details in providerMetadata for each delta chunk
|
|
689
|
+
// so users can accumulate them on their end before sending back
|
|
690
|
+
const metadata = {
|
|
691
|
+
openrouter: {
|
|
692
|
+
reasoningDetails
|
|
693
|
+
}
|
|
694
|
+
};
|
|
695
|
+
for (const detail of reasoningDetails) {
|
|
696
|
+
switch (detail.type) {
|
|
697
|
+
case "reasoning.text":
|
|
698
|
+
{
|
|
699
|
+
if (Predicate.isNotNullish(detail.text)) {
|
|
700
|
+
yield* emitReasoning(detail.text, metadata);
|
|
701
|
+
}
|
|
702
|
+
break;
|
|
708
703
|
}
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
704
|
+
case "reasoning.summary":
|
|
705
|
+
{
|
|
706
|
+
if (Predicate.isNotNullish(detail.summary)) {
|
|
707
|
+
yield* emitReasoning(detail.summary, metadata);
|
|
708
|
+
}
|
|
709
|
+
break;
|
|
715
710
|
}
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
711
|
+
case "reasoning.encrypted":
|
|
712
|
+
{
|
|
713
|
+
if (Predicate.isNotNullish(detail.data)) {
|
|
714
|
+
yield* emitReasoning("[REDACTED]", metadata);
|
|
715
|
+
}
|
|
716
|
+
break;
|
|
722
717
|
}
|
|
723
|
-
|
|
724
|
-
}
|
|
718
|
+
}
|
|
725
719
|
}
|
|
720
|
+
} else if (Predicate.isNotNullish(delta.reasoning)) {
|
|
721
|
+
yield* emitReasoning(delta.reasoning);
|
|
726
722
|
}
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
// If reasoning was previously active and now we're starting text content,
|
|
733
|
-
// we should end the reasoning first to maintain proper order
|
|
734
|
-
if (reasoningStarted && !textStarted) {
|
|
735
|
-
parts.push({
|
|
736
|
-
type: "reasoning-end",
|
|
737
|
-
id: activeReasoningId,
|
|
738
|
-
// Include accumulated reasoning_details so the we can update the
|
|
739
|
-
// reasoning part's provider metadata with the correct signature.
|
|
740
|
-
// The signature typically arrives in the last reasoning delta,
|
|
741
|
-
// but reasoning-start only carries the first delta's metadata.
|
|
742
|
-
metadata: accumulatedReasoningDetails.length > 0 ? {
|
|
743
|
-
openRouter: {
|
|
744
|
-
reasoningDetails: accumulatedReasoningDetails
|
|
745
|
-
}
|
|
746
|
-
} : undefined
|
|
747
|
-
});
|
|
748
|
-
reasoningStarted = false;
|
|
749
|
-
}
|
|
750
|
-
if (!textStarted) {
|
|
751
|
-
activeTextId = openRouterResponseId ?? (yield* idGenerator.generateId());
|
|
752
|
-
parts.push({
|
|
753
|
-
type: "text-start",
|
|
754
|
-
id: activeTextId
|
|
755
|
-
});
|
|
756
|
-
textStarted = true;
|
|
757
|
-
}
|
|
758
|
-
parts.push({
|
|
759
|
-
type: "text-delta",
|
|
760
|
-
id: activeTextId,
|
|
761
|
-
delta: content
|
|
762
|
-
});
|
|
763
|
-
}
|
|
764
|
-
const annotations = delta.annotations;
|
|
765
|
-
if (Predicate.isNotNullish(annotations)) {
|
|
766
|
-
for (const annotation of annotations) {
|
|
767
|
-
if (annotation.type === "url_citation") {
|
|
723
|
+
const content = delta.content;
|
|
724
|
+
if (Predicate.isNotNullish(content)) {
|
|
725
|
+
// If reasoning was previously active and now we're starting text content,
|
|
726
|
+
// we should end the reasoning first to maintain proper order
|
|
727
|
+
if (reasoningStarted && !textStarted) {
|
|
768
728
|
parts.push({
|
|
769
|
-
type: "
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
metadata
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
} : undefined),
|
|
779
|
-
...(Predicate.isNotUndefined(annotation.url_citation.start_index) ? {
|
|
780
|
-
startIndex: annotation.url_citation.start_index
|
|
781
|
-
} : undefined),
|
|
782
|
-
...(Predicate.isNotUndefined(annotation.url_citation.end_index) ? {
|
|
783
|
-
startIndex: annotation.url_citation.end_index
|
|
784
|
-
} : undefined)
|
|
729
|
+
type: "reasoning-end",
|
|
730
|
+
id: activeReasoningId,
|
|
731
|
+
// Include accumulated reasoning_details so the we can update the
|
|
732
|
+
// reasoning part's provider metadata with the correct signature.
|
|
733
|
+
// The signature typically arrives in the last reasoning delta,
|
|
734
|
+
// but reasoning-start only carries the first delta's metadata.
|
|
735
|
+
metadata: accumulatedReasoningDetails.length > 0 ? {
|
|
736
|
+
openRouter: {
|
|
737
|
+
reasoningDetails: accumulatedReasoningDetails
|
|
785
738
|
}
|
|
786
|
-
}
|
|
739
|
+
} : undefined
|
|
740
|
+
});
|
|
741
|
+
reasoningStarted = false;
|
|
742
|
+
}
|
|
743
|
+
if (!textStarted) {
|
|
744
|
+
activeTextId = openRouterResponseId ?? (yield* idGenerator.generateId());
|
|
745
|
+
parts.push({
|
|
746
|
+
type: "text-start",
|
|
747
|
+
id: activeTextId
|
|
787
748
|
});
|
|
788
|
-
|
|
789
|
-
accumulatedFileAnnotations.push(annotation);
|
|
749
|
+
textStarted = true;
|
|
790
750
|
}
|
|
751
|
+
parts.push({
|
|
752
|
+
type: "text-delta",
|
|
753
|
+
id: activeTextId,
|
|
754
|
+
delta: content
|
|
755
|
+
});
|
|
791
756
|
}
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
description: "Received tool call delta without a tool call identifier"
|
|
816
|
-
})
|
|
757
|
+
const annotations = delta.annotations;
|
|
758
|
+
if (Predicate.isNotNullish(annotations)) {
|
|
759
|
+
for (const annotation of annotations) {
|
|
760
|
+
if (annotation.type === "url_citation") {
|
|
761
|
+
parts.push({
|
|
762
|
+
type: "source",
|
|
763
|
+
sourceType: "url",
|
|
764
|
+
id: annotation.url_citation.url,
|
|
765
|
+
url: annotation.url_citation.url,
|
|
766
|
+
title: annotation.url_citation.title ?? "",
|
|
767
|
+
metadata: {
|
|
768
|
+
openrouter: {
|
|
769
|
+
...(Predicate.isNotUndefined(annotation.url_citation.content) ? {
|
|
770
|
+
content: annotation.url_citation.content
|
|
771
|
+
} : undefined),
|
|
772
|
+
...(Predicate.isNotUndefined(annotation.url_citation.start_index) ? {
|
|
773
|
+
startIndex: annotation.url_citation.start_index
|
|
774
|
+
} : undefined),
|
|
775
|
+
...(Predicate.isNotUndefined(annotation.url_citation.end_index) ? {
|
|
776
|
+
startIndex: annotation.url_citation.end_index
|
|
777
|
+
} : undefined)
|
|
778
|
+
}
|
|
779
|
+
}
|
|
817
780
|
});
|
|
781
|
+
} else if (annotation.type === "file") {
|
|
782
|
+
accumulatedFileAnnotations.push(annotation);
|
|
818
783
|
}
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
const toolCalls = delta.tool_calls;
|
|
787
|
+
if (Predicate.isNotNullish(toolCalls)) {
|
|
788
|
+
for (const toolCall of toolCalls) {
|
|
789
|
+
const index = toolCall.index ?? toolCalls.length - 1;
|
|
790
|
+
let activeToolCall = activeToolCalls[index];
|
|
791
|
+
// Tool call start - OpenRouter returns all information except the
|
|
792
|
+
// tool call parameters in the first chunk
|
|
793
|
+
if (Predicate.isUndefined(activeToolCall)) {
|
|
794
|
+
if (toolCall.type !== "function") {
|
|
795
|
+
return yield* AiError.make({
|
|
796
|
+
module: "OpenRouterLanguageModel",
|
|
797
|
+
method: "makeStreamResponse",
|
|
798
|
+
reason: new AiError.InvalidOutputError({
|
|
799
|
+
description: "Received tool call delta that was not of type: 'function'"
|
|
800
|
+
})
|
|
801
|
+
});
|
|
802
|
+
}
|
|
803
|
+
if (Predicate.isNullish(toolCall.id)) {
|
|
804
|
+
return yield* AiError.make({
|
|
805
|
+
module: "OpenRouterLanguageModel",
|
|
806
|
+
method: "makeStreamResponse",
|
|
807
|
+
reason: new AiError.InvalidOutputError({
|
|
808
|
+
description: "Received tool call delta without a tool call identifier"
|
|
809
|
+
})
|
|
810
|
+
});
|
|
811
|
+
}
|
|
812
|
+
if (Predicate.isNullish(toolCall.function?.name)) {
|
|
813
|
+
return yield* AiError.make({
|
|
814
|
+
module: "OpenRouterLanguageModel",
|
|
815
|
+
method: "makeStreamResponse",
|
|
816
|
+
reason: new AiError.InvalidOutputError({
|
|
817
|
+
description: "Received tool call delta without a tool call name"
|
|
818
|
+
})
|
|
819
|
+
});
|
|
820
|
+
}
|
|
821
|
+
activeToolCall = {
|
|
822
|
+
id: toolCall.id,
|
|
823
|
+
type: "function",
|
|
824
|
+
name: toolCall.function.name,
|
|
825
|
+
params: toolCall.function.arguments ?? ""
|
|
826
|
+
};
|
|
827
|
+
activeToolCalls[index] = activeToolCall;
|
|
828
|
+
parts.push({
|
|
829
|
+
type: "tool-params-start",
|
|
830
|
+
id: activeToolCall.id,
|
|
831
|
+
name: activeToolCall.name
|
|
826
832
|
});
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
});
|
|
840
|
-
// Emit a tool call delta part if parameters were also sent
|
|
841
|
-
if (activeToolCall.params.length > 0) {
|
|
833
|
+
// Emit a tool call delta part if parameters were also sent
|
|
834
|
+
if (activeToolCall.params.length > 0) {
|
|
835
|
+
parts.push({
|
|
836
|
+
type: "tool-params-delta",
|
|
837
|
+
id: activeToolCall.id,
|
|
838
|
+
delta: activeToolCall.params
|
|
839
|
+
});
|
|
840
|
+
}
|
|
841
|
+
} else {
|
|
842
|
+
// If an active tool call was found, update and emit the delta for
|
|
843
|
+
// the tool call's parameters
|
|
844
|
+
activeToolCall.params += toolCall.function?.arguments ?? "";
|
|
842
845
|
parts.push({
|
|
843
846
|
type: "tool-params-delta",
|
|
844
847
|
id: activeToolCall.id,
|
|
845
848
|
delta: activeToolCall.params
|
|
846
849
|
});
|
|
847
850
|
}
|
|
848
|
-
|
|
849
|
-
//
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
851
|
+
// Check if the tool call is complete
|
|
852
|
+
// @effect-diagnostics-next-line tryCatchInEffectGen:off
|
|
853
|
+
try {
|
|
854
|
+
const params = Tool.unsafeSecureJsonParse(activeToolCall.params);
|
|
855
|
+
parts.push({
|
|
856
|
+
type: "tool-params-end",
|
|
857
|
+
id: activeToolCall.id
|
|
858
|
+
});
|
|
859
|
+
parts.push({
|
|
860
|
+
type: "tool-call",
|
|
861
|
+
id: activeToolCall.id,
|
|
862
|
+
name: activeToolCall.name,
|
|
863
|
+
params,
|
|
864
|
+
// Only attach reasoning_details to the first tool call to avoid
|
|
865
|
+
// duplicating thinking blocks for parallel tool calls (Claude)
|
|
866
|
+
metadata: reasoningDetailsAttachedToToolCall ? undefined : {
|
|
867
|
+
openrouter: {
|
|
868
|
+
reasoningDetails: accumulatedReasoningDetails
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
});
|
|
872
|
+
reasoningDetailsAttachedToToolCall = true;
|
|
873
|
+
// Increment the total tool calls emitted by the stream and
|
|
874
|
+
// remove the active tool call
|
|
875
|
+
totalToolCalls += 1;
|
|
876
|
+
delete activeToolCalls[toolCall.index];
|
|
877
|
+
} catch {
|
|
878
|
+
// Tool call incomplete, continue parsing
|
|
879
|
+
continue;
|
|
880
|
+
}
|
|
857
881
|
}
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
parts.push({
|
|
863
|
-
type: "tool-params-end",
|
|
864
|
-
id: activeToolCall.id
|
|
865
|
-
});
|
|
882
|
+
}
|
|
883
|
+
const images = delta.images;
|
|
884
|
+
if (Predicate.isNotNullish(images)) {
|
|
885
|
+
for (const image of images) {
|
|
866
886
|
parts.push({
|
|
867
|
-
type: "
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
params,
|
|
871
|
-
// Only attach reasoning_details to the first tool call to avoid
|
|
872
|
-
// duplicating thinking blocks for parallel tool calls (Claude)
|
|
873
|
-
metadata: reasoningDetailsAttachedToToolCall ? undefined : {
|
|
874
|
-
openrouter: {
|
|
875
|
-
reasoningDetails: accumulatedReasoningDetails
|
|
876
|
-
}
|
|
877
|
-
}
|
|
887
|
+
type: "file",
|
|
888
|
+
mediaType: getMediaType(image.image_url.url, "image/jpeg"),
|
|
889
|
+
data: getBase64FromDataUrl(image.image_url.url)
|
|
878
890
|
});
|
|
879
|
-
reasoningDetailsAttachedToToolCall = true;
|
|
880
|
-
// Increment the total tool calls emitted by the stream and
|
|
881
|
-
// remove the active tool call
|
|
882
|
-
totalToolCalls += 1;
|
|
883
|
-
delete activeToolCalls[toolCall.index];
|
|
884
|
-
} catch {
|
|
885
|
-
// Tool call incomplete, continue parsing
|
|
886
|
-
continue;
|
|
887
891
|
}
|
|
888
892
|
}
|
|
889
893
|
}
|
|
890
|
-
const images = delta.images;
|
|
891
|
-
if (Predicate.isNotNullish(images)) {
|
|
892
|
-
for (const image of images) {
|
|
893
|
-
parts.push({
|
|
894
|
-
type: "file",
|
|
895
|
-
mediaType: getMediaType(image.image_url.url, "image/jpeg"),
|
|
896
|
-
data: getBase64FromDataUrl(image.image_url.url)
|
|
897
|
-
});
|
|
898
|
-
}
|
|
899
|
-
}
|
|
900
894
|
// Usage is only emitted by the last part of the stream, so we need to
|
|
901
895
|
// handle flushing any remaining text / reasoning / tool calls
|
|
902
896
|
if (Predicate.isNotUndefined(event.usage)) {
|