@effect/ai-openrouter 4.0.0-beta.2 → 4.0.0-beta.20
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 -234
- 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 +223 -231
|
@@ -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
|
}));
|
|
@@ -221,7 +221,7 @@ const prepareMessages = /*#__PURE__*/Effect.fnUntraced(function* ({
|
|
|
221
221
|
content.push({
|
|
222
222
|
type: "image_url",
|
|
223
223
|
image_url: {
|
|
224
|
-
url: part.data instanceof URL ? part.data.toString() : part.data instanceof Uint8Array ? `data:${mediaType};base64,${
|
|
224
|
+
url: part.data instanceof URL ? part.data.toString() : part.data instanceof Uint8Array ? `data:${mediaType};base64,${Encoding.encodeBase64(part.data)}` : part.data
|
|
225
225
|
},
|
|
226
226
|
...(Predicate.isNotNull(partCacheControl) ? {
|
|
227
227
|
cache_control: partCacheControl
|
|
@@ -235,7 +235,7 @@ const prepareMessages = /*#__PURE__*/Effect.fnUntraced(function* ({
|
|
|
235
235
|
type: "file",
|
|
236
236
|
file: {
|
|
237
237
|
filename: fileName,
|
|
238
|
-
file_data: part.data instanceof URL ? part.data.toString() : part.data instanceof Uint8Array ? `data:${part.mediaType};base64,${
|
|
238
|
+
file_data: part.data instanceof URL ? part.data.toString() : part.data instanceof Uint8Array ? `data:${part.mediaType};base64,${Encoding.encodeBase64(part.data)}` : part.data
|
|
239
239
|
},
|
|
240
240
|
...(Predicate.isNotNull(partCacheControl) ? {
|
|
241
241
|
cache_control: partCacheControl
|
|
@@ -592,7 +592,7 @@ const makeStreamResponse = /*#__PURE__*/Effect.fnUntraced(function* ({
|
|
|
592
592
|
let activeReasoningId = undefined;
|
|
593
593
|
let activeTextId = undefined;
|
|
594
594
|
let totalToolCalls = 0;
|
|
595
|
-
const activeToolCalls =
|
|
595
|
+
const activeToolCalls = {};
|
|
596
596
|
// Track reasoning details to preserve for multi-turn conversations
|
|
597
597
|
const accumulatedReasoningDetails = [];
|
|
598
598
|
// Track file annotations to expose in provider metadata
|
|
@@ -636,267 +636,260 @@ const makeStreamResponse = /*#__PURE__*/Effect.fnUntraced(function* ({
|
|
|
636
636
|
usage.outputTokens = computed.outputTokens;
|
|
637
637
|
}
|
|
638
638
|
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());
|
|
639
|
+
if (Predicate.isNotUndefined(choice)) {
|
|
640
|
+
if (Predicate.isNotNullish(choice.finish_reason)) {
|
|
641
|
+
finishReason = resolveFinishReason(choice.finish_reason);
|
|
642
|
+
}
|
|
643
|
+
const delta = choice.delta;
|
|
644
|
+
if (Predicate.isNullish(delta)) {
|
|
645
|
+
return parts;
|
|
646
|
+
}
|
|
647
|
+
const emitReasoning = Effect.fnUntraced(function* (delta, metadata) {
|
|
648
|
+
if (!reasoningStarted) {
|
|
649
|
+
activeReasoningId = openRouterResponseId ?? (yield* idGenerator.generateId());
|
|
650
|
+
parts.push({
|
|
651
|
+
type: "reasoning-start",
|
|
652
|
+
id: activeReasoningId,
|
|
653
|
+
metadata
|
|
654
|
+
});
|
|
655
|
+
reasoningStarted = true;
|
|
656
|
+
}
|
|
658
657
|
parts.push({
|
|
659
|
-
type: "reasoning-
|
|
658
|
+
type: "reasoning-delta",
|
|
660
659
|
id: activeReasoningId,
|
|
660
|
+
delta,
|
|
661
661
|
metadata
|
|
662
662
|
});
|
|
663
|
-
reasoningStarted = true;
|
|
664
|
-
}
|
|
665
|
-
parts.push({
|
|
666
|
-
type: "reasoning-delta",
|
|
667
|
-
id: activeReasoningId,
|
|
668
|
-
delta,
|
|
669
|
-
metadata
|
|
670
663
|
});
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
664
|
+
const reasoningDetails = delta.reasoning_details;
|
|
665
|
+
if (Predicate.isNotUndefined(reasoningDetails) && reasoningDetails.length > 0) {
|
|
666
|
+
// Accumulate reasoning_details to preserve for multi-turn conversations
|
|
667
|
+
// Merge consecutive reasoning.text items into a single entry
|
|
668
|
+
for (const detail of reasoningDetails) {
|
|
669
|
+
if (detail.type === "reasoning.text") {
|
|
670
|
+
const lastDetail = accumulatedReasoningDetails[accumulatedReasoningDetails.length - 1];
|
|
671
|
+
if (Predicate.isNotUndefined(lastDetail) && lastDetail.type === "reasoning.text") {
|
|
672
|
+
// Merge with the previous text detail
|
|
673
|
+
lastDetail.text = (lastDetail.text ?? "") + (detail.text ?? "");
|
|
674
|
+
lastDetail.signature = lastDetail.signature ?? detail.signature ?? null;
|
|
675
|
+
lastDetail.format = lastDetail.format ?? detail.format ?? null;
|
|
676
|
+
} else {
|
|
677
|
+
// Start a new text detail
|
|
678
|
+
accumulatedReasoningDetails.push({
|
|
679
|
+
...detail
|
|
680
|
+
});
|
|
681
|
+
}
|
|
684
682
|
} else {
|
|
685
|
-
//
|
|
686
|
-
accumulatedReasoningDetails.push(
|
|
687
|
-
...detail
|
|
688
|
-
});
|
|
683
|
+
// Non-text details (encrypted, summary) are pushed as-is
|
|
684
|
+
accumulatedReasoningDetails.push(detail);
|
|
689
685
|
}
|
|
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
686
|
}
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
687
|
+
// Emit reasoning_details in providerMetadata for each delta chunk
|
|
688
|
+
// so users can accumulate them on their end before sending back
|
|
689
|
+
const metadata = {
|
|
690
|
+
openrouter: {
|
|
691
|
+
reasoningDetails
|
|
692
|
+
}
|
|
693
|
+
};
|
|
694
|
+
for (const detail of reasoningDetails) {
|
|
695
|
+
switch (detail.type) {
|
|
696
|
+
case "reasoning.text":
|
|
697
|
+
{
|
|
698
|
+
if (Predicate.isNotNullish(detail.text)) {
|
|
699
|
+
yield* emitReasoning(detail.text, metadata);
|
|
700
|
+
}
|
|
701
|
+
break;
|
|
708
702
|
}
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
703
|
+
case "reasoning.summary":
|
|
704
|
+
{
|
|
705
|
+
if (Predicate.isNotNullish(detail.summary)) {
|
|
706
|
+
yield* emitReasoning(detail.summary, metadata);
|
|
707
|
+
}
|
|
708
|
+
break;
|
|
715
709
|
}
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
710
|
+
case "reasoning.encrypted":
|
|
711
|
+
{
|
|
712
|
+
if (Predicate.isNotNullish(detail.data)) {
|
|
713
|
+
yield* emitReasoning("[REDACTED]", metadata);
|
|
714
|
+
}
|
|
715
|
+
break;
|
|
722
716
|
}
|
|
723
|
-
|
|
724
|
-
}
|
|
717
|
+
}
|
|
725
718
|
}
|
|
719
|
+
} else if (Predicate.isNotNullish(delta.reasoning)) {
|
|
720
|
+
yield* emitReasoning(delta.reasoning);
|
|
726
721
|
}
|
|
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") {
|
|
722
|
+
const content = delta.content;
|
|
723
|
+
if (Predicate.isNotNullish(content)) {
|
|
724
|
+
// If reasoning was previously active and now we're starting text content,
|
|
725
|
+
// we should end the reasoning first to maintain proper order
|
|
726
|
+
if (reasoningStarted && !textStarted) {
|
|
768
727
|
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)
|
|
728
|
+
type: "reasoning-end",
|
|
729
|
+
id: activeReasoningId,
|
|
730
|
+
// Include accumulated reasoning_details so the we can update the
|
|
731
|
+
// reasoning part's provider metadata with the correct signature.
|
|
732
|
+
// The signature typically arrives in the last reasoning delta,
|
|
733
|
+
// but reasoning-start only carries the first delta's metadata.
|
|
734
|
+
metadata: accumulatedReasoningDetails.length > 0 ? {
|
|
735
|
+
openRouter: {
|
|
736
|
+
reasoningDetails: accumulatedReasoningDetails
|
|
785
737
|
}
|
|
786
|
-
}
|
|
738
|
+
} : undefined
|
|
739
|
+
});
|
|
740
|
+
reasoningStarted = false;
|
|
741
|
+
}
|
|
742
|
+
if (!textStarted) {
|
|
743
|
+
activeTextId = openRouterResponseId ?? (yield* idGenerator.generateId());
|
|
744
|
+
parts.push({
|
|
745
|
+
type: "text-start",
|
|
746
|
+
id: activeTextId
|
|
787
747
|
});
|
|
788
|
-
|
|
789
|
-
accumulatedFileAnnotations.push(annotation);
|
|
748
|
+
textStarted = true;
|
|
790
749
|
}
|
|
750
|
+
parts.push({
|
|
751
|
+
type: "text-delta",
|
|
752
|
+
id: activeTextId,
|
|
753
|
+
delta: content
|
|
754
|
+
});
|
|
791
755
|
}
|
|
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
|
-
})
|
|
756
|
+
const annotations = delta.annotations;
|
|
757
|
+
if (Predicate.isNotNullish(annotations)) {
|
|
758
|
+
for (const annotation of annotations) {
|
|
759
|
+
if (annotation.type === "url_citation") {
|
|
760
|
+
parts.push({
|
|
761
|
+
type: "source",
|
|
762
|
+
sourceType: "url",
|
|
763
|
+
id: annotation.url_citation.url,
|
|
764
|
+
url: annotation.url_citation.url,
|
|
765
|
+
title: annotation.url_citation.title ?? "",
|
|
766
|
+
metadata: {
|
|
767
|
+
openrouter: {
|
|
768
|
+
...(Predicate.isNotUndefined(annotation.url_citation.content) ? {
|
|
769
|
+
content: annotation.url_citation.content
|
|
770
|
+
} : undefined),
|
|
771
|
+
...(Predicate.isNotUndefined(annotation.url_citation.start_index) ? {
|
|
772
|
+
startIndex: annotation.url_citation.start_index
|
|
773
|
+
} : undefined),
|
|
774
|
+
...(Predicate.isNotUndefined(annotation.url_citation.end_index) ? {
|
|
775
|
+
startIndex: annotation.url_citation.end_index
|
|
776
|
+
} : undefined)
|
|
777
|
+
}
|
|
778
|
+
}
|
|
817
779
|
});
|
|
780
|
+
} else if (annotation.type === "file") {
|
|
781
|
+
accumulatedFileAnnotations.push(annotation);
|
|
818
782
|
}
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
const toolCalls = delta.tool_calls;
|
|
786
|
+
if (Predicate.isNotNullish(toolCalls)) {
|
|
787
|
+
for (const toolCall of toolCalls) {
|
|
788
|
+
const index = toolCall.index ?? toolCalls.length - 1;
|
|
789
|
+
let activeToolCall = activeToolCalls[index];
|
|
790
|
+
// Tool call start - OpenRouter returns all information except the
|
|
791
|
+
// tool call parameters in the first chunk
|
|
792
|
+
if (Predicate.isUndefined(activeToolCall)) {
|
|
793
|
+
if (toolCall.type !== "function") {
|
|
794
|
+
return yield* AiError.make({
|
|
795
|
+
module: "OpenRouterLanguageModel",
|
|
796
|
+
method: "makeStreamResponse",
|
|
797
|
+
reason: new AiError.InvalidOutputError({
|
|
798
|
+
description: "Received tool call delta that was not of type: 'function'"
|
|
799
|
+
})
|
|
800
|
+
});
|
|
801
|
+
}
|
|
802
|
+
if (Predicate.isNullish(toolCall.id)) {
|
|
803
|
+
return yield* AiError.make({
|
|
804
|
+
module: "OpenRouterLanguageModel",
|
|
805
|
+
method: "makeStreamResponse",
|
|
806
|
+
reason: new AiError.InvalidOutputError({
|
|
807
|
+
description: "Received tool call delta without a tool call identifier"
|
|
808
|
+
})
|
|
809
|
+
});
|
|
810
|
+
}
|
|
811
|
+
if (Predicate.isNullish(toolCall.function?.name)) {
|
|
812
|
+
return yield* AiError.make({
|
|
813
|
+
module: "OpenRouterLanguageModel",
|
|
814
|
+
method: "makeStreamResponse",
|
|
815
|
+
reason: new AiError.InvalidOutputError({
|
|
816
|
+
description: "Received tool call delta without a tool call name"
|
|
817
|
+
})
|
|
818
|
+
});
|
|
819
|
+
}
|
|
820
|
+
activeToolCall = {
|
|
821
|
+
id: toolCall.id,
|
|
822
|
+
type: "function",
|
|
823
|
+
name: toolCall.function.name,
|
|
824
|
+
params: toolCall.function.arguments ?? ""
|
|
825
|
+
};
|
|
826
|
+
activeToolCalls[index] = activeToolCall;
|
|
827
|
+
parts.push({
|
|
828
|
+
type: "tool-params-start",
|
|
829
|
+
id: activeToolCall.id,
|
|
830
|
+
name: activeToolCall.name
|
|
826
831
|
});
|
|
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) {
|
|
832
|
+
// Emit a tool call delta part if parameters were also sent
|
|
833
|
+
if (activeToolCall.params.length > 0) {
|
|
834
|
+
parts.push({
|
|
835
|
+
type: "tool-params-delta",
|
|
836
|
+
id: activeToolCall.id,
|
|
837
|
+
delta: activeToolCall.params
|
|
838
|
+
});
|
|
839
|
+
}
|
|
840
|
+
} else {
|
|
841
|
+
// If an active tool call was found, update and emit the delta for
|
|
842
|
+
// the tool call's parameters
|
|
843
|
+
activeToolCall.params += toolCall.function?.arguments ?? "";
|
|
842
844
|
parts.push({
|
|
843
845
|
type: "tool-params-delta",
|
|
844
846
|
id: activeToolCall.id,
|
|
845
847
|
delta: activeToolCall.params
|
|
846
848
|
});
|
|
847
849
|
}
|
|
848
|
-
|
|
849
|
-
//
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
850
|
+
// Check if the tool call is complete
|
|
851
|
+
// @effect-diagnostics-next-line tryCatchInEffectGen:off
|
|
852
|
+
try {
|
|
853
|
+
const params = Tool.unsafeSecureJsonParse(activeToolCall.params);
|
|
854
|
+
parts.push({
|
|
855
|
+
type: "tool-params-end",
|
|
856
|
+
id: activeToolCall.id
|
|
857
|
+
});
|
|
858
|
+
parts.push({
|
|
859
|
+
type: "tool-call",
|
|
860
|
+
id: activeToolCall.id,
|
|
861
|
+
name: activeToolCall.name,
|
|
862
|
+
params,
|
|
863
|
+
// Only attach reasoning_details to the first tool call to avoid
|
|
864
|
+
// duplicating thinking blocks for parallel tool calls (Claude)
|
|
865
|
+
metadata: reasoningDetailsAttachedToToolCall ? undefined : {
|
|
866
|
+
openrouter: {
|
|
867
|
+
reasoningDetails: accumulatedReasoningDetails
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
});
|
|
871
|
+
reasoningDetailsAttachedToToolCall = true;
|
|
872
|
+
// Increment the total tool calls emitted by the stream and
|
|
873
|
+
// remove the active tool call
|
|
874
|
+
totalToolCalls += 1;
|
|
875
|
+
delete activeToolCalls[toolCall.index];
|
|
876
|
+
} catch {
|
|
877
|
+
// Tool call incomplete, continue parsing
|
|
878
|
+
continue;
|
|
879
|
+
}
|
|
857
880
|
}
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
881
|
+
}
|
|
882
|
+
const images = delta.images;
|
|
883
|
+
if (Predicate.isNotNullish(images)) {
|
|
884
|
+
for (const image of images) {
|
|
862
885
|
parts.push({
|
|
863
|
-
type: "
|
|
864
|
-
|
|
886
|
+
type: "file",
|
|
887
|
+
mediaType: getMediaType(image.image_url.url, "image/jpeg"),
|
|
888
|
+
data: getBase64FromDataUrl(image.image_url.url)
|
|
865
889
|
});
|
|
866
|
-
parts.push({
|
|
867
|
-
type: "tool-call",
|
|
868
|
-
id: activeToolCall.id,
|
|
869
|
-
name: activeToolCall.name,
|
|
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
|
-
}
|
|
878
|
-
});
|
|
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
890
|
}
|
|
888
891
|
}
|
|
889
892
|
}
|
|
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
893
|
// Usage is only emitted by the last part of the stream, so we need to
|
|
901
894
|
// handle flushing any remaining text / reasoning / tool calls
|
|
902
895
|
if (Predicate.isNotUndefined(event.usage)) {
|
|
@@ -909,7 +902,7 @@ const makeStreamResponse = /*#__PURE__*/Effect.fnUntraced(function* ({
|
|
|
909
902
|
}
|
|
910
903
|
// Forward any unsent tool calls if finish reason is 'tool-calls'
|
|
911
904
|
if (finishReason === "tool-calls") {
|
|
912
|
-
for (const toolCall of activeToolCalls) {
|
|
905
|
+
for (const toolCall of Object.values(activeToolCalls)) {
|
|
913
906
|
// Coerce invalid tool call parameters to an empty object
|
|
914
907
|
let params;
|
|
915
908
|
// @effect-diagnostics-next-line tryCatchInEffectGen:off
|