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