@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
package/src/OpenRouterError.ts
CHANGED
|
@@ -42,51 +42,43 @@ export type OpenRouterRateLimitMetadata = OpenRouterErrorMetadata & {
|
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
declare module "effect/unstable/ai/AiError" {
|
|
45
|
-
export interface
|
|
46
|
-
readonly
|
|
47
|
-
readonly openrouter?: OpenRouterRateLimitMetadata | null
|
|
48
|
-
}
|
|
45
|
+
export interface RateLimitErrorMetadata {
|
|
46
|
+
readonly openrouter?: OpenRouterRateLimitMetadata | null
|
|
49
47
|
}
|
|
50
48
|
|
|
51
|
-
export interface
|
|
52
|
-
readonly
|
|
53
|
-
readonly openrouter?: OpenRouterErrorMetadata | null
|
|
54
|
-
}
|
|
49
|
+
export interface QuotaExhaustedErrorMetadata {
|
|
50
|
+
readonly openrouter?: OpenRouterErrorMetadata | null
|
|
55
51
|
}
|
|
56
52
|
|
|
57
|
-
export interface
|
|
58
|
-
readonly
|
|
59
|
-
readonly openrouter?: OpenRouterErrorMetadata | null
|
|
60
|
-
}
|
|
53
|
+
export interface AuthenticationErrorMetadata {
|
|
54
|
+
readonly openrouter?: OpenRouterErrorMetadata | null
|
|
61
55
|
}
|
|
62
56
|
|
|
63
|
-
export interface
|
|
64
|
-
readonly
|
|
65
|
-
readonly openrouter?: OpenRouterErrorMetadata | null
|
|
66
|
-
}
|
|
57
|
+
export interface ContentPolicyErrorMetadata {
|
|
58
|
+
readonly openrouter?: OpenRouterErrorMetadata | null
|
|
67
59
|
}
|
|
68
60
|
|
|
69
|
-
export interface
|
|
70
|
-
readonly
|
|
71
|
-
readonly openrouter?: OpenRouterErrorMetadata | null
|
|
72
|
-
}
|
|
61
|
+
export interface InvalidRequestErrorMetadata {
|
|
62
|
+
readonly openrouter?: OpenRouterErrorMetadata | null
|
|
73
63
|
}
|
|
74
64
|
|
|
75
|
-
export interface
|
|
76
|
-
readonly
|
|
77
|
-
readonly openrouter?: OpenRouterErrorMetadata | null
|
|
78
|
-
}
|
|
65
|
+
export interface InternalProviderErrorMetadata {
|
|
66
|
+
readonly openrouter?: OpenRouterErrorMetadata | null
|
|
79
67
|
}
|
|
80
68
|
|
|
81
|
-
export interface
|
|
82
|
-
readonly
|
|
83
|
-
readonly openrouter?: OpenRouterErrorMetadata | null
|
|
84
|
-
}
|
|
69
|
+
export interface InvalidOutputErrorMetadata {
|
|
70
|
+
readonly openrouter?: OpenRouterErrorMetadata | null
|
|
85
71
|
}
|
|
86
72
|
|
|
87
|
-
export interface
|
|
88
|
-
readonly
|
|
89
|
-
|
|
90
|
-
|
|
73
|
+
export interface StructuredOutputErrorMetadata {
|
|
74
|
+
readonly openrouter?: OpenRouterErrorMetadata | null
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export interface UnsupportedSchemaErrorMetadata {
|
|
78
|
+
readonly openrouter?: OpenRouterErrorMetadata | null
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export interface UnknownErrorMetadata {
|
|
82
|
+
readonly openrouter?: OpenRouterErrorMetadata | null
|
|
91
83
|
}
|
|
92
84
|
}
|
|
@@ -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"
|
|
@@ -233,7 +233,7 @@ export const model = (
|
|
|
233
233
|
model: string,
|
|
234
234
|
config?: Omit<typeof Config.Service, "model">
|
|
235
235
|
): AiModel.Model<"openai", LanguageModel.LanguageModel, OpenRouterClient> =>
|
|
236
|
-
AiModel.make("openai", layer({ model, config }))
|
|
236
|
+
AiModel.make("openai", model, layer({ model, config }))
|
|
237
237
|
|
|
238
238
|
/**
|
|
239
239
|
* Creates an OpenRouter language model service.
|
|
@@ -273,6 +273,7 @@ export const make = Effect.fnUntraced(function*({ model, config: providerConfig
|
|
|
273
273
|
)
|
|
274
274
|
|
|
275
275
|
return yield* LanguageModel.make({
|
|
276
|
+
codecTransformer: toCodecOpenAI,
|
|
276
277
|
generateText: Effect.fnUntraced(
|
|
277
278
|
function*(options) {
|
|
278
279
|
const config = yield* makeConfig
|
|
@@ -300,10 +301,7 @@ export const make = Effect.fnUntraced(function*({ model, config: providerConfig
|
|
|
300
301
|
})
|
|
301
302
|
)
|
|
302
303
|
)
|
|
303
|
-
})
|
|
304
|
-
LanguageModel.CurrentCodecTransformer,
|
|
305
|
-
codecTransformer
|
|
306
|
-
))
|
|
304
|
+
})
|
|
307
305
|
})
|
|
308
306
|
|
|
309
307
|
/**
|
|
@@ -451,7 +449,7 @@ const prepareMessages = Effect.fnUntraced(
|
|
|
451
449
|
url: part.data instanceof URL
|
|
452
450
|
? part.data.toString()
|
|
453
451
|
: part.data instanceof Uint8Array
|
|
454
|
-
? `data:${mediaType};base64,${
|
|
452
|
+
? `data:${mediaType};base64,${Encoding.encodeBase64(part.data)}`
|
|
455
453
|
: part.data
|
|
456
454
|
},
|
|
457
455
|
...(Predicate.isNotNull(partCacheControl) ? { cache_control: partCacheControl } : undefined)
|
|
@@ -470,7 +468,7 @@ const prepareMessages = Effect.fnUntraced(
|
|
|
470
468
|
file_data: part.data instanceof URL
|
|
471
469
|
? part.data.toString()
|
|
472
470
|
: part.data instanceof Uint8Array
|
|
473
|
-
? `data:${part.mediaType};base64,${
|
|
471
|
+
? `data:${part.mediaType};base64,${Encoding.encodeBase64(part.data)}`
|
|
474
472
|
: part.data
|
|
475
473
|
},
|
|
476
474
|
...(Predicate.isNotNull(partCacheControl) ? { cache_control: partCacheControl } : undefined)
|
|
@@ -885,283 +883,275 @@ const makeStreamResponse = Effect.fnUntraced(
|
|
|
885
883
|
}
|
|
886
884
|
|
|
887
885
|
const choice = event.choices[0]
|
|
888
|
-
if (Predicate.
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
reason: new AiError.InvalidOutputError({
|
|
893
|
-
description: "Received response with empty choices"
|
|
894
|
-
})
|
|
895
|
-
})
|
|
896
|
-
}
|
|
897
|
-
|
|
898
|
-
if (Predicate.isNotNull(choice.finish_reason)) {
|
|
899
|
-
finishReason = resolveFinishReason(choice.finish_reason)
|
|
900
|
-
}
|
|
886
|
+
if (Predicate.isNotUndefined(choice)) {
|
|
887
|
+
if (Predicate.isNotNullish(choice.finish_reason)) {
|
|
888
|
+
finishReason = resolveFinishReason(choice.finish_reason)
|
|
889
|
+
}
|
|
901
890
|
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
891
|
+
const delta = choice.delta
|
|
892
|
+
if (Predicate.isNullish(delta)) {
|
|
893
|
+
return parts
|
|
894
|
+
}
|
|
906
895
|
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
896
|
+
const emitReasoning = Effect.fnUntraced(
|
|
897
|
+
function*(delta: string, metadata?: Response.ReasoningDeltaPart["metadata"] | undefined) {
|
|
898
|
+
if (!reasoningStarted) {
|
|
899
|
+
activeReasoningId = openRouterResponseId ?? (yield* idGenerator.generateId())
|
|
900
|
+
parts.push({
|
|
901
|
+
type: "reasoning-start",
|
|
902
|
+
id: activeReasoningId,
|
|
903
|
+
metadata
|
|
904
|
+
})
|
|
905
|
+
reasoningStarted = true
|
|
906
|
+
}
|
|
911
907
|
parts.push({
|
|
912
|
-
type: "reasoning-
|
|
913
|
-
id: activeReasoningId
|
|
908
|
+
type: "reasoning-delta",
|
|
909
|
+
id: activeReasoningId!,
|
|
910
|
+
delta,
|
|
914
911
|
metadata
|
|
915
912
|
})
|
|
916
|
-
reasoningStarted = true
|
|
917
913
|
}
|
|
918
|
-
|
|
919
|
-
type: "reasoning-delta",
|
|
920
|
-
id: activeReasoningId!,
|
|
921
|
-
delta,
|
|
922
|
-
metadata
|
|
923
|
-
})
|
|
924
|
-
}
|
|
925
|
-
)
|
|
914
|
+
)
|
|
926
915
|
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
916
|
+
const reasoningDetails = delta.reasoning_details
|
|
917
|
+
if (Predicate.isNotUndefined(reasoningDetails) && reasoningDetails.length > 0) {
|
|
918
|
+
// Accumulate reasoning_details to preserve for multi-turn conversations
|
|
919
|
+
// Merge consecutive reasoning.text items into a single entry
|
|
920
|
+
for (const detail of reasoningDetails) {
|
|
921
|
+
if (detail.type === "reasoning.text") {
|
|
922
|
+
const lastDetail = accumulatedReasoningDetails[accumulatedReasoningDetails.length - 1]
|
|
923
|
+
if (Predicate.isNotUndefined(lastDetail) && lastDetail.type === "reasoning.text") {
|
|
924
|
+
// Merge with the previous text detail
|
|
925
|
+
lastDetail.text = (lastDetail.text ?? "") + (detail.text ?? "")
|
|
926
|
+
lastDetail.signature = lastDetail.signature ?? detail.signature ?? null
|
|
927
|
+
lastDetail.format = lastDetail.format ?? detail.format ?? null
|
|
928
|
+
} else {
|
|
929
|
+
// Start a new text detail
|
|
930
|
+
accumulatedReasoningDetails.push({ ...detail })
|
|
931
|
+
}
|
|
939
932
|
} else {
|
|
940
|
-
//
|
|
941
|
-
accumulatedReasoningDetails.push(
|
|
933
|
+
// Non-text details (encrypted, summary) are pushed as-is
|
|
934
|
+
accumulatedReasoningDetails.push(detail)
|
|
942
935
|
}
|
|
943
|
-
} else {
|
|
944
|
-
// Non-text details (encrypted, summary) are pushed as-is
|
|
945
|
-
accumulatedReasoningDetails.push(detail)
|
|
946
936
|
}
|
|
947
|
-
}
|
|
948
937
|
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
938
|
+
// Emit reasoning_details in providerMetadata for each delta chunk
|
|
939
|
+
// so users can accumulate them on their end before sending back
|
|
940
|
+
const metadata: Response.ReasoningDeltaPart["metadata"] = {
|
|
941
|
+
openrouter: {
|
|
942
|
+
reasoningDetails
|
|
943
|
+
}
|
|
954
944
|
}
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
945
|
+
for (const detail of reasoningDetails) {
|
|
946
|
+
switch (detail.type) {
|
|
947
|
+
case "reasoning.text": {
|
|
948
|
+
if (Predicate.isNotNullish(detail.text)) {
|
|
949
|
+
yield* emitReasoning(detail.text, metadata)
|
|
950
|
+
}
|
|
951
|
+
break
|
|
961
952
|
}
|
|
962
|
-
break
|
|
963
|
-
}
|
|
964
953
|
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
954
|
+
case "reasoning.summary": {
|
|
955
|
+
if (Predicate.isNotNullish(detail.summary)) {
|
|
956
|
+
yield* emitReasoning(detail.summary, metadata)
|
|
957
|
+
}
|
|
958
|
+
break
|
|
968
959
|
}
|
|
969
|
-
break
|
|
970
|
-
}
|
|
971
960
|
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
961
|
+
case "reasoning.encrypted": {
|
|
962
|
+
if (Predicate.isNotNullish(detail.data)) {
|
|
963
|
+
yield* emitReasoning("[REDACTED]", metadata)
|
|
964
|
+
}
|
|
965
|
+
break
|
|
975
966
|
}
|
|
976
|
-
break
|
|
977
967
|
}
|
|
978
968
|
}
|
|
969
|
+
} else if (Predicate.isNotNullish(delta.reasoning)) {
|
|
970
|
+
yield* emitReasoning(delta.reasoning)
|
|
979
971
|
}
|
|
980
|
-
} else if (Predicate.isNotNullish(delta.reasoning)) {
|
|
981
|
-
yield* emitReasoning(delta.reasoning)
|
|
982
|
-
}
|
|
983
972
|
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
973
|
+
const content = delta.content
|
|
974
|
+
if (Predicate.isNotNullish(content)) {
|
|
975
|
+
// If reasoning was previously active and now we're starting text content,
|
|
976
|
+
// we should end the reasoning first to maintain proper order
|
|
977
|
+
if (reasoningStarted && !textStarted) {
|
|
978
|
+
parts.push({
|
|
979
|
+
type: "reasoning-end",
|
|
980
|
+
id: activeReasoningId!,
|
|
981
|
+
// Include accumulated reasoning_details so the we can update the
|
|
982
|
+
// reasoning part's provider metadata with the correct signature.
|
|
983
|
+
// The signature typically arrives in the last reasoning delta,
|
|
984
|
+
// but reasoning-start only carries the first delta's metadata.
|
|
985
|
+
metadata: accumulatedReasoningDetails.length > 0
|
|
986
|
+
? { openRouter: { reasoningDetails: accumulatedReasoningDetails } }
|
|
987
|
+
: undefined
|
|
988
|
+
})
|
|
989
|
+
reasoningStarted = false
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
if (!textStarted) {
|
|
993
|
+
activeTextId = openRouterResponseId ?? (yield* idGenerator.generateId())
|
|
994
|
+
parts.push({
|
|
995
|
+
type: "text-start",
|
|
996
|
+
id: activeTextId
|
|
997
|
+
})
|
|
998
|
+
textStarted = true
|
|
999
|
+
}
|
|
1002
1000
|
|
|
1003
|
-
if (!textStarted) {
|
|
1004
|
-
activeTextId = openRouterResponseId ?? (yield* idGenerator.generateId())
|
|
1005
1001
|
parts.push({
|
|
1006
|
-
type: "text-
|
|
1007
|
-
id: activeTextId
|
|
1002
|
+
type: "text-delta",
|
|
1003
|
+
id: activeTextId!,
|
|
1004
|
+
delta: content
|
|
1008
1005
|
})
|
|
1009
|
-
textStarted = true
|
|
1010
1006
|
}
|
|
1011
1007
|
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
...(Predicate.isNotUndefined(annotation.url_citation.start_index)
|
|
1035
|
-
? { startIndex: annotation.url_citation.start_index }
|
|
1036
|
-
: undefined),
|
|
1037
|
-
...(Predicate.isNotUndefined(annotation.url_citation.end_index)
|
|
1038
|
-
? { startIndex: annotation.url_citation.end_index }
|
|
1039
|
-
: undefined)
|
|
1008
|
+
const annotations = delta.annotations
|
|
1009
|
+
if (Predicate.isNotNullish(annotations)) {
|
|
1010
|
+
for (const annotation of annotations) {
|
|
1011
|
+
if (annotation.type === "url_citation") {
|
|
1012
|
+
parts.push({
|
|
1013
|
+
type: "source",
|
|
1014
|
+
sourceType: "url",
|
|
1015
|
+
id: annotation.url_citation.url,
|
|
1016
|
+
url: annotation.url_citation.url,
|
|
1017
|
+
title: annotation.url_citation.title ?? "",
|
|
1018
|
+
metadata: {
|
|
1019
|
+
openrouter: {
|
|
1020
|
+
...(Predicate.isNotUndefined(annotation.url_citation.content)
|
|
1021
|
+
? { content: annotation.url_citation.content }
|
|
1022
|
+
: undefined),
|
|
1023
|
+
...(Predicate.isNotUndefined(annotation.url_citation.start_index)
|
|
1024
|
+
? { startIndex: annotation.url_citation.start_index }
|
|
1025
|
+
: undefined),
|
|
1026
|
+
...(Predicate.isNotUndefined(annotation.url_citation.end_index)
|
|
1027
|
+
? { startIndex: annotation.url_citation.end_index }
|
|
1028
|
+
: undefined)
|
|
1029
|
+
}
|
|
1040
1030
|
}
|
|
1041
|
-
}
|
|
1042
|
-
})
|
|
1043
|
-
|
|
1044
|
-
|
|
1031
|
+
})
|
|
1032
|
+
} else if (annotation.type === "file") {
|
|
1033
|
+
accumulatedFileAnnotations.push(annotation)
|
|
1034
|
+
}
|
|
1045
1035
|
}
|
|
1046
1036
|
}
|
|
1047
|
-
}
|
|
1048
1037
|
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1038
|
+
const toolCalls = delta.tool_calls
|
|
1039
|
+
if (Predicate.isNotNullish(toolCalls)) {
|
|
1040
|
+
for (const toolCall of toolCalls) {
|
|
1041
|
+
const index = toolCall.index ?? toolCalls.length - 1
|
|
1042
|
+
let activeToolCall = activeToolCalls[index]
|
|
1043
|
+
|
|
1044
|
+
// Tool call start - OpenRouter returns all information except the
|
|
1045
|
+
// tool call parameters in the first chunk
|
|
1046
|
+
if (Predicate.isUndefined(activeToolCall)) {
|
|
1047
|
+
if (toolCall.type !== "function") {
|
|
1048
|
+
return yield* AiError.make({
|
|
1049
|
+
module: "OpenRouterLanguageModel",
|
|
1050
|
+
method: "makeStreamResponse",
|
|
1051
|
+
reason: new AiError.InvalidOutputError({
|
|
1052
|
+
description: "Received tool call delta that was not of type: 'function'"
|
|
1053
|
+
})
|
|
1064
1054
|
})
|
|
1065
|
-
}
|
|
1066
|
-
}
|
|
1055
|
+
}
|
|
1067
1056
|
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1057
|
+
if (Predicate.isNullish(toolCall.id)) {
|
|
1058
|
+
return yield* AiError.make({
|
|
1059
|
+
module: "OpenRouterLanguageModel",
|
|
1060
|
+
method: "makeStreamResponse",
|
|
1061
|
+
reason: new AiError.InvalidOutputError({
|
|
1062
|
+
description: "Received tool call delta without a tool call identifier"
|
|
1063
|
+
})
|
|
1074
1064
|
})
|
|
1075
|
-
}
|
|
1076
|
-
}
|
|
1065
|
+
}
|
|
1077
1066
|
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1067
|
+
if (Predicate.isNullish(toolCall.function?.name)) {
|
|
1068
|
+
return yield* AiError.make({
|
|
1069
|
+
module: "OpenRouterLanguageModel",
|
|
1070
|
+
method: "makeStreamResponse",
|
|
1071
|
+
reason: new AiError.InvalidOutputError({
|
|
1072
|
+
description: "Received tool call delta without a tool call name"
|
|
1073
|
+
})
|
|
1084
1074
|
})
|
|
1085
|
-
}
|
|
1086
|
-
}
|
|
1075
|
+
}
|
|
1087
1076
|
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1077
|
+
activeToolCall = {
|
|
1078
|
+
id: toolCall.id,
|
|
1079
|
+
type: "function",
|
|
1080
|
+
name: toolCall.function.name,
|
|
1081
|
+
params: toolCall.function.arguments ?? ""
|
|
1082
|
+
}
|
|
1094
1083
|
|
|
1095
|
-
|
|
1084
|
+
activeToolCalls[index] = activeToolCall
|
|
1096
1085
|
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1086
|
+
parts.push({
|
|
1087
|
+
type: "tool-params-start",
|
|
1088
|
+
id: activeToolCall.id,
|
|
1089
|
+
name: activeToolCall.name
|
|
1090
|
+
})
|
|
1102
1091
|
|
|
1103
|
-
|
|
1104
|
-
|
|
1092
|
+
// Emit a tool call delta part if parameters were also sent
|
|
1093
|
+
if (activeToolCall.params.length > 0) {
|
|
1094
|
+
parts.push({
|
|
1095
|
+
type: "tool-params-delta",
|
|
1096
|
+
id: activeToolCall.id,
|
|
1097
|
+
delta: activeToolCall.params
|
|
1098
|
+
})
|
|
1099
|
+
}
|
|
1100
|
+
} else {
|
|
1101
|
+
// If an active tool call was found, update and emit the delta for
|
|
1102
|
+
// the tool call's parameters
|
|
1103
|
+
activeToolCall.params += toolCall.function?.arguments ?? ""
|
|
1105
1104
|
parts.push({
|
|
1106
1105
|
type: "tool-params-delta",
|
|
1107
1106
|
id: activeToolCall.id,
|
|
1108
1107
|
delta: activeToolCall.params
|
|
1109
1108
|
})
|
|
1110
1109
|
}
|
|
1111
|
-
} else {
|
|
1112
|
-
// If an active tool call was found, update and emit the delta for
|
|
1113
|
-
// the tool call's parameters
|
|
1114
|
-
activeToolCall.params += toolCall.function?.arguments ?? ""
|
|
1115
|
-
parts.push({
|
|
1116
|
-
type: "tool-params-delta",
|
|
1117
|
-
id: activeToolCall.id,
|
|
1118
|
-
delta: activeToolCall.params
|
|
1119
|
-
})
|
|
1120
|
-
}
|
|
1121
1110
|
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1111
|
+
// Check if the tool call is complete
|
|
1112
|
+
// @effect-diagnostics-next-line tryCatchInEffectGen:off
|
|
1113
|
+
try {
|
|
1114
|
+
const params = Tool.unsafeSecureJsonParse(activeToolCall.params)
|
|
1126
1115
|
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1116
|
+
parts.push({
|
|
1117
|
+
type: "tool-params-end",
|
|
1118
|
+
id: activeToolCall.id
|
|
1119
|
+
})
|
|
1131
1120
|
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1121
|
+
parts.push({
|
|
1122
|
+
type: "tool-call",
|
|
1123
|
+
id: activeToolCall.id,
|
|
1124
|
+
name: activeToolCall.name,
|
|
1125
|
+
params,
|
|
1126
|
+
// Only attach reasoning_details to the first tool call to avoid
|
|
1127
|
+
// duplicating thinking blocks for parallel tool calls (Claude)
|
|
1128
|
+
metadata: reasoningDetailsAttachedToToolCall ? undefined : {
|
|
1129
|
+
openrouter: { reasoningDetails: accumulatedReasoningDetails }
|
|
1130
|
+
}
|
|
1131
|
+
})
|
|
1143
1132
|
|
|
1144
|
-
|
|
1133
|
+
reasoningDetailsAttachedToToolCall = true
|
|
1145
1134
|
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1135
|
+
// Increment the total tool calls emitted by the stream and
|
|
1136
|
+
// remove the active tool call
|
|
1137
|
+
totalToolCalls += 1
|
|
1138
|
+
delete activeToolCalls[toolCall.index]
|
|
1139
|
+
} catch {
|
|
1140
|
+
// Tool call incomplete, continue parsing
|
|
1141
|
+
continue
|
|
1142
|
+
}
|
|
1153
1143
|
}
|
|
1154
1144
|
}
|
|
1155
|
-
}
|
|
1156
1145
|
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1146
|
+
const images = delta.images
|
|
1147
|
+
if (Predicate.isNotNullish(images)) {
|
|
1148
|
+
for (const image of images) {
|
|
1149
|
+
parts.push({
|
|
1150
|
+
type: "file",
|
|
1151
|
+
mediaType: getMediaType(image.image_url.url, "image/jpeg"),
|
|
1152
|
+
data: getBase64FromDataUrl(image.image_url.url)
|
|
1153
|
+
})
|
|
1154
|
+
}
|
|
1165
1155
|
}
|
|
1166
1156
|
}
|
|
1167
1157
|
|