@effect/ai-openrouter 4.0.0-beta.6 → 4.0.0-beta.62
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 +170 -91
- 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 +3 -3
- package/dist/OpenRouterClient.d.ts.map +1 -1
- package/dist/OpenRouterClient.js +3 -3
- package/dist/OpenRouterClient.js.map +1 -1
- package/dist/OpenRouterConfig.d.ts +2 -2
- package/dist/OpenRouterConfig.d.ts.map +1 -1
- package/dist/OpenRouterConfig.js +3 -3
- package/dist/OpenRouterConfig.js.map +1 -1
- package/dist/OpenRouterError.d.ts +22 -32
- package/dist/OpenRouterError.d.ts.map +1 -1
- package/dist/OpenRouterLanguageModel.d.ts +10 -2
- 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 +4 -4
- package/src/OpenRouterConfig.ts +3 -3
- package/src/OpenRouterError.ts +24 -32
- package/src/OpenRouterLanguageModel.ts +224 -233
- package/src/internal/errors.ts +6 -4
|
@@ -3,16 +3,17 @@
|
|
|
3
3
|
*/
|
|
4
4
|
/** @effect-diagnostics preferSchemaOverJson:skip-file */
|
|
5
5
|
import * as Arr from "effect/Array"
|
|
6
|
+
import * as Context from "effect/Context"
|
|
6
7
|
import * as DateTime from "effect/DateTime"
|
|
7
8
|
import * as Effect from "effect/Effect"
|
|
8
9
|
import * as Encoding from "effect/Encoding"
|
|
9
10
|
import { dual } from "effect/Function"
|
|
10
11
|
import * as Layer from "effect/Layer"
|
|
12
|
+
import * as Option from "effect/Option"
|
|
11
13
|
import * as Predicate from "effect/Predicate"
|
|
12
14
|
import * as Redactable from "effect/Redactable"
|
|
13
15
|
import type * as Schema from "effect/Schema"
|
|
14
16
|
import * as SchemaAST from "effect/SchemaAST"
|
|
15
|
-
import * as ServiceMap from "effect/ServiceMap"
|
|
16
17
|
import * as Stream from "effect/Stream"
|
|
17
18
|
import type { Span } from "effect/Tracer"
|
|
18
19
|
import type { DeepMutable, Mutable, Simplify } from "effect/Types"
|
|
@@ -42,7 +43,7 @@ import { type ChatStreamingResponseChunkData, OpenRouterClient } from "./OpenRou
|
|
|
42
43
|
* @since 1.0.0
|
|
43
44
|
* @category services
|
|
44
45
|
*/
|
|
45
|
-
export class Config extends
|
|
46
|
+
export class Config extends Context.Service<
|
|
46
47
|
Config,
|
|
47
48
|
Simplify<
|
|
48
49
|
& Partial<
|
|
@@ -233,7 +234,7 @@ export const model = (
|
|
|
233
234
|
model: string,
|
|
234
235
|
config?: Omit<typeof Config.Service, "model">
|
|
235
236
|
): AiModel.Model<"openai", LanguageModel.LanguageModel, OpenRouterClient> =>
|
|
236
|
-
AiModel.make("openai", layer({ model, config }))
|
|
237
|
+
AiModel.make("openai", model, layer({ model, config }))
|
|
237
238
|
|
|
238
239
|
/**
|
|
239
240
|
* Creates an OpenRouter language model service.
|
|
@@ -249,7 +250,7 @@ export const make = Effect.fnUntraced(function*({ model, config: providerConfig
|
|
|
249
250
|
const codecTransformer = getCodecTransformer(model)
|
|
250
251
|
|
|
251
252
|
const makeConfig = Effect.gen(function*() {
|
|
252
|
-
const services = yield* Effect.
|
|
253
|
+
const services = yield* Effect.context<never>()
|
|
253
254
|
return { model, ...providerConfig, ...services.mapUnsafe.get(Config.key) }
|
|
254
255
|
})
|
|
255
256
|
|
|
@@ -273,6 +274,7 @@ export const make = Effect.fnUntraced(function*({ model, config: providerConfig
|
|
|
273
274
|
)
|
|
274
275
|
|
|
275
276
|
return yield* LanguageModel.make({
|
|
277
|
+
codecTransformer: toCodecOpenAI,
|
|
276
278
|
generateText: Effect.fnUntraced(
|
|
277
279
|
function*(options) {
|
|
278
280
|
const config = yield* makeConfig
|
|
@@ -300,10 +302,7 @@ export const make = Effect.fnUntraced(function*({ model, config: providerConfig
|
|
|
300
302
|
})
|
|
301
303
|
)
|
|
302
304
|
)
|
|
303
|
-
})
|
|
304
|
-
LanguageModel.CurrentCodecTransformer,
|
|
305
|
-
codecTransformer
|
|
306
|
-
))
|
|
305
|
+
})
|
|
307
306
|
})
|
|
308
307
|
|
|
309
308
|
/**
|
|
@@ -588,7 +587,7 @@ const buildHttpRequestDetails = (
|
|
|
588
587
|
method: request.method,
|
|
589
588
|
url: request.url,
|
|
590
589
|
urlParams: Array.from(request.urlParams),
|
|
591
|
-
hash: request.hash,
|
|
590
|
+
hash: Option.getOrUndefined(request.hash),
|
|
592
591
|
headers: Redactable.redact(request.headers) as Record<string, string>
|
|
593
592
|
})
|
|
594
593
|
|
|
@@ -885,283 +884,275 @@ const makeStreamResponse = Effect.fnUntraced(
|
|
|
885
884
|
}
|
|
886
885
|
|
|
887
886
|
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
|
-
}
|
|
887
|
+
if (Predicate.isNotUndefined(choice)) {
|
|
888
|
+
if (Predicate.isNotNullish(choice.finish_reason)) {
|
|
889
|
+
finishReason = resolveFinishReason(choice.finish_reason)
|
|
890
|
+
}
|
|
901
891
|
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
892
|
+
const delta = choice.delta
|
|
893
|
+
if (Predicate.isNullish(delta)) {
|
|
894
|
+
return parts
|
|
895
|
+
}
|
|
906
896
|
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
897
|
+
const emitReasoning = Effect.fnUntraced(
|
|
898
|
+
function*(delta: string, metadata?: Response.ReasoningDeltaPart["metadata"] | undefined) {
|
|
899
|
+
if (!reasoningStarted) {
|
|
900
|
+
activeReasoningId = openRouterResponseId ?? (yield* idGenerator.generateId())
|
|
901
|
+
parts.push({
|
|
902
|
+
type: "reasoning-start",
|
|
903
|
+
id: activeReasoningId,
|
|
904
|
+
metadata
|
|
905
|
+
})
|
|
906
|
+
reasoningStarted = true
|
|
907
|
+
}
|
|
911
908
|
parts.push({
|
|
912
|
-
type: "reasoning-
|
|
913
|
-
id: activeReasoningId
|
|
909
|
+
type: "reasoning-delta",
|
|
910
|
+
id: activeReasoningId!,
|
|
911
|
+
delta,
|
|
914
912
|
metadata
|
|
915
913
|
})
|
|
916
|
-
reasoningStarted = true
|
|
917
914
|
}
|
|
918
|
-
|
|
919
|
-
type: "reasoning-delta",
|
|
920
|
-
id: activeReasoningId!,
|
|
921
|
-
delta,
|
|
922
|
-
metadata
|
|
923
|
-
})
|
|
924
|
-
}
|
|
925
|
-
)
|
|
915
|
+
)
|
|
926
916
|
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
917
|
+
const reasoningDetails = delta.reasoning_details
|
|
918
|
+
if (Predicate.isNotUndefined(reasoningDetails) && reasoningDetails.length > 0) {
|
|
919
|
+
// Accumulate reasoning_details to preserve for multi-turn conversations
|
|
920
|
+
// Merge consecutive reasoning.text items into a single entry
|
|
921
|
+
for (const detail of reasoningDetails) {
|
|
922
|
+
if (detail.type === "reasoning.text") {
|
|
923
|
+
const lastDetail = accumulatedReasoningDetails[accumulatedReasoningDetails.length - 1]
|
|
924
|
+
if (Predicate.isNotUndefined(lastDetail) && lastDetail.type === "reasoning.text") {
|
|
925
|
+
// Merge with the previous text detail
|
|
926
|
+
lastDetail.text = (lastDetail.text ?? "") + (detail.text ?? "")
|
|
927
|
+
lastDetail.signature = lastDetail.signature ?? detail.signature ?? null
|
|
928
|
+
lastDetail.format = lastDetail.format ?? detail.format ?? null
|
|
929
|
+
} else {
|
|
930
|
+
// Start a new text detail
|
|
931
|
+
accumulatedReasoningDetails.push({ ...detail })
|
|
932
|
+
}
|
|
939
933
|
} else {
|
|
940
|
-
//
|
|
941
|
-
accumulatedReasoningDetails.push(
|
|
934
|
+
// Non-text details (encrypted, summary) are pushed as-is
|
|
935
|
+
accumulatedReasoningDetails.push(detail)
|
|
942
936
|
}
|
|
943
|
-
} else {
|
|
944
|
-
// Non-text details (encrypted, summary) are pushed as-is
|
|
945
|
-
accumulatedReasoningDetails.push(detail)
|
|
946
937
|
}
|
|
947
|
-
}
|
|
948
938
|
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
939
|
+
// Emit reasoning_details in providerMetadata for each delta chunk
|
|
940
|
+
// so users can accumulate them on their end before sending back
|
|
941
|
+
const metadata: Response.ReasoningDeltaPart["metadata"] = {
|
|
942
|
+
openrouter: {
|
|
943
|
+
reasoningDetails
|
|
944
|
+
}
|
|
954
945
|
}
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
946
|
+
for (const detail of reasoningDetails) {
|
|
947
|
+
switch (detail.type) {
|
|
948
|
+
case "reasoning.text": {
|
|
949
|
+
if (Predicate.isNotNullish(detail.text)) {
|
|
950
|
+
yield* emitReasoning(detail.text, metadata)
|
|
951
|
+
}
|
|
952
|
+
break
|
|
961
953
|
}
|
|
962
|
-
break
|
|
963
|
-
}
|
|
964
954
|
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
955
|
+
case "reasoning.summary": {
|
|
956
|
+
if (Predicate.isNotNullish(detail.summary)) {
|
|
957
|
+
yield* emitReasoning(detail.summary, metadata)
|
|
958
|
+
}
|
|
959
|
+
break
|
|
968
960
|
}
|
|
969
|
-
break
|
|
970
|
-
}
|
|
971
961
|
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
962
|
+
case "reasoning.encrypted": {
|
|
963
|
+
if (Predicate.isNotNullish(detail.data)) {
|
|
964
|
+
yield* emitReasoning("[REDACTED]", metadata)
|
|
965
|
+
}
|
|
966
|
+
break
|
|
975
967
|
}
|
|
976
|
-
break
|
|
977
968
|
}
|
|
978
969
|
}
|
|
970
|
+
} else if (Predicate.isNotNullish(delta.reasoning)) {
|
|
971
|
+
yield* emitReasoning(delta.reasoning)
|
|
979
972
|
}
|
|
980
|
-
} else if (Predicate.isNotNullish(delta.reasoning)) {
|
|
981
|
-
yield* emitReasoning(delta.reasoning)
|
|
982
|
-
}
|
|
983
973
|
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
974
|
+
const content = delta.content
|
|
975
|
+
if (Predicate.isNotNullish(content)) {
|
|
976
|
+
// If reasoning was previously active and now we're starting text content,
|
|
977
|
+
// we should end the reasoning first to maintain proper order
|
|
978
|
+
if (reasoningStarted && !textStarted) {
|
|
979
|
+
parts.push({
|
|
980
|
+
type: "reasoning-end",
|
|
981
|
+
id: activeReasoningId!,
|
|
982
|
+
// Include accumulated reasoning_details so the we can update the
|
|
983
|
+
// reasoning part's provider metadata with the correct signature.
|
|
984
|
+
// The signature typically arrives in the last reasoning delta,
|
|
985
|
+
// but reasoning-start only carries the first delta's metadata.
|
|
986
|
+
metadata: accumulatedReasoningDetails.length > 0
|
|
987
|
+
? { openRouter: { reasoningDetails: accumulatedReasoningDetails } }
|
|
988
|
+
: undefined
|
|
989
|
+
})
|
|
990
|
+
reasoningStarted = false
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
if (!textStarted) {
|
|
994
|
+
activeTextId = openRouterResponseId ?? (yield* idGenerator.generateId())
|
|
995
|
+
parts.push({
|
|
996
|
+
type: "text-start",
|
|
997
|
+
id: activeTextId
|
|
998
|
+
})
|
|
999
|
+
textStarted = true
|
|
1000
|
+
}
|
|
1002
1001
|
|
|
1003
|
-
if (!textStarted) {
|
|
1004
|
-
activeTextId = openRouterResponseId ?? (yield* idGenerator.generateId())
|
|
1005
1002
|
parts.push({
|
|
1006
|
-
type: "text-
|
|
1007
|
-
id: activeTextId
|
|
1003
|
+
type: "text-delta",
|
|
1004
|
+
id: activeTextId!,
|
|
1005
|
+
delta: content
|
|
1008
1006
|
})
|
|
1009
|
-
textStarted = true
|
|
1010
1007
|
}
|
|
1011
1008
|
|
|
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)
|
|
1009
|
+
const annotations = delta.annotations
|
|
1010
|
+
if (Predicate.isNotNullish(annotations)) {
|
|
1011
|
+
for (const annotation of annotations) {
|
|
1012
|
+
if (annotation.type === "url_citation") {
|
|
1013
|
+
parts.push({
|
|
1014
|
+
type: "source",
|
|
1015
|
+
sourceType: "url",
|
|
1016
|
+
id: annotation.url_citation.url,
|
|
1017
|
+
url: annotation.url_citation.url,
|
|
1018
|
+
title: annotation.url_citation.title ?? "",
|
|
1019
|
+
metadata: {
|
|
1020
|
+
openrouter: {
|
|
1021
|
+
...(Predicate.isNotUndefined(annotation.url_citation.content)
|
|
1022
|
+
? { content: annotation.url_citation.content }
|
|
1023
|
+
: undefined),
|
|
1024
|
+
...(Predicate.isNotUndefined(annotation.url_citation.start_index)
|
|
1025
|
+
? { startIndex: annotation.url_citation.start_index }
|
|
1026
|
+
: undefined),
|
|
1027
|
+
...(Predicate.isNotUndefined(annotation.url_citation.end_index)
|
|
1028
|
+
? { startIndex: annotation.url_citation.end_index }
|
|
1029
|
+
: undefined)
|
|
1030
|
+
}
|
|
1040
1031
|
}
|
|
1041
|
-
}
|
|
1042
|
-
})
|
|
1043
|
-
|
|
1044
|
-
|
|
1032
|
+
})
|
|
1033
|
+
} else if (annotation.type === "file") {
|
|
1034
|
+
accumulatedFileAnnotations.push(annotation)
|
|
1035
|
+
}
|
|
1045
1036
|
}
|
|
1046
1037
|
}
|
|
1047
|
-
}
|
|
1048
1038
|
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1039
|
+
const toolCalls = delta.tool_calls
|
|
1040
|
+
if (Predicate.isNotNullish(toolCalls)) {
|
|
1041
|
+
for (const toolCall of toolCalls) {
|
|
1042
|
+
const index = toolCall.index ?? toolCalls.length - 1
|
|
1043
|
+
let activeToolCall = activeToolCalls[index]
|
|
1044
|
+
|
|
1045
|
+
// Tool call start - OpenRouter returns all information except the
|
|
1046
|
+
// tool call parameters in the first chunk
|
|
1047
|
+
if (Predicate.isUndefined(activeToolCall)) {
|
|
1048
|
+
if (toolCall.type !== "function") {
|
|
1049
|
+
return yield* AiError.make({
|
|
1050
|
+
module: "OpenRouterLanguageModel",
|
|
1051
|
+
method: "makeStreamResponse",
|
|
1052
|
+
reason: new AiError.InvalidOutputError({
|
|
1053
|
+
description: "Received tool call delta that was not of type: 'function'"
|
|
1054
|
+
})
|
|
1064
1055
|
})
|
|
1065
|
-
}
|
|
1066
|
-
}
|
|
1056
|
+
}
|
|
1067
1057
|
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1058
|
+
if (Predicate.isNullish(toolCall.id)) {
|
|
1059
|
+
return yield* AiError.make({
|
|
1060
|
+
module: "OpenRouterLanguageModel",
|
|
1061
|
+
method: "makeStreamResponse",
|
|
1062
|
+
reason: new AiError.InvalidOutputError({
|
|
1063
|
+
description: "Received tool call delta without a tool call identifier"
|
|
1064
|
+
})
|
|
1074
1065
|
})
|
|
1075
|
-
}
|
|
1076
|
-
}
|
|
1066
|
+
}
|
|
1077
1067
|
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1068
|
+
if (Predicate.isNullish(toolCall.function?.name)) {
|
|
1069
|
+
return yield* AiError.make({
|
|
1070
|
+
module: "OpenRouterLanguageModel",
|
|
1071
|
+
method: "makeStreamResponse",
|
|
1072
|
+
reason: new AiError.InvalidOutputError({
|
|
1073
|
+
description: "Received tool call delta without a tool call name"
|
|
1074
|
+
})
|
|
1084
1075
|
})
|
|
1085
|
-
}
|
|
1086
|
-
}
|
|
1076
|
+
}
|
|
1087
1077
|
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1078
|
+
activeToolCall = {
|
|
1079
|
+
id: toolCall.id,
|
|
1080
|
+
type: "function",
|
|
1081
|
+
name: toolCall.function.name,
|
|
1082
|
+
params: toolCall.function.arguments ?? ""
|
|
1083
|
+
}
|
|
1094
1084
|
|
|
1095
|
-
|
|
1085
|
+
activeToolCalls[index] = activeToolCall
|
|
1096
1086
|
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1087
|
+
parts.push({
|
|
1088
|
+
type: "tool-params-start",
|
|
1089
|
+
id: activeToolCall.id,
|
|
1090
|
+
name: activeToolCall.name
|
|
1091
|
+
})
|
|
1102
1092
|
|
|
1103
|
-
|
|
1104
|
-
|
|
1093
|
+
// Emit a tool call delta part if parameters were also sent
|
|
1094
|
+
if (activeToolCall.params.length > 0) {
|
|
1095
|
+
parts.push({
|
|
1096
|
+
type: "tool-params-delta",
|
|
1097
|
+
id: activeToolCall.id,
|
|
1098
|
+
delta: activeToolCall.params
|
|
1099
|
+
})
|
|
1100
|
+
}
|
|
1101
|
+
} else {
|
|
1102
|
+
// If an active tool call was found, update and emit the delta for
|
|
1103
|
+
// the tool call's parameters
|
|
1104
|
+
activeToolCall.params += toolCall.function?.arguments ?? ""
|
|
1105
1105
|
parts.push({
|
|
1106
1106
|
type: "tool-params-delta",
|
|
1107
1107
|
id: activeToolCall.id,
|
|
1108
1108
|
delta: activeToolCall.params
|
|
1109
1109
|
})
|
|
1110
1110
|
}
|
|
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
1111
|
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1112
|
+
// Check if the tool call is complete
|
|
1113
|
+
// @effect-diagnostics-next-line tryCatchInEffectGen:off
|
|
1114
|
+
try {
|
|
1115
|
+
const params = Tool.unsafeSecureJsonParse(activeToolCall.params)
|
|
1126
1116
|
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1117
|
+
parts.push({
|
|
1118
|
+
type: "tool-params-end",
|
|
1119
|
+
id: activeToolCall.id
|
|
1120
|
+
})
|
|
1131
1121
|
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1122
|
+
parts.push({
|
|
1123
|
+
type: "tool-call",
|
|
1124
|
+
id: activeToolCall.id,
|
|
1125
|
+
name: activeToolCall.name,
|
|
1126
|
+
params,
|
|
1127
|
+
// Only attach reasoning_details to the first tool call to avoid
|
|
1128
|
+
// duplicating thinking blocks for parallel tool calls (Claude)
|
|
1129
|
+
metadata: reasoningDetailsAttachedToToolCall ? undefined : {
|
|
1130
|
+
openrouter: { reasoningDetails: accumulatedReasoningDetails }
|
|
1131
|
+
}
|
|
1132
|
+
})
|
|
1143
1133
|
|
|
1144
|
-
|
|
1134
|
+
reasoningDetailsAttachedToToolCall = true
|
|
1145
1135
|
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1136
|
+
// Increment the total tool calls emitted by the stream and
|
|
1137
|
+
// remove the active tool call
|
|
1138
|
+
totalToolCalls += 1
|
|
1139
|
+
delete activeToolCalls[toolCall.index]
|
|
1140
|
+
} catch {
|
|
1141
|
+
// Tool call incomplete, continue parsing
|
|
1142
|
+
continue
|
|
1143
|
+
}
|
|
1153
1144
|
}
|
|
1154
1145
|
}
|
|
1155
|
-
}
|
|
1156
1146
|
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1147
|
+
const images = delta.images
|
|
1148
|
+
if (Predicate.isNotNullish(images)) {
|
|
1149
|
+
for (const image of images) {
|
|
1150
|
+
parts.push({
|
|
1151
|
+
type: "file",
|
|
1152
|
+
mediaType: getMediaType(image.image_url.url, "image/jpeg"),
|
|
1153
|
+
data: getBase64FromDataUrl(image.image_url.url)
|
|
1154
|
+
})
|
|
1155
|
+
}
|
|
1165
1156
|
}
|
|
1166
1157
|
}
|
|
1167
1158
|
|
package/src/internal/errors.ts
CHANGED
|
@@ -192,12 +192,14 @@ export const parseRateLimitHeaders = (headers: Record<string, string>) => {
|
|
|
192
192
|
let retryAfter: Duration.Duration | undefined
|
|
193
193
|
if (Predicate.isNotUndefined(retryAfterRaw)) {
|
|
194
194
|
const parsed = Number.parse(retryAfterRaw)
|
|
195
|
-
if (
|
|
196
|
-
retryAfter = Duration.seconds(parsed)
|
|
195
|
+
if (Option.isSome(parsed)) {
|
|
196
|
+
retryAfter = Duration.seconds(parsed.value)
|
|
197
197
|
}
|
|
198
198
|
}
|
|
199
199
|
const remainingRaw = headers["x-ratelimit-remaining-requests"]
|
|
200
|
-
const remaining = Predicate.isNotUndefined(remainingRaw)
|
|
200
|
+
const remaining = Predicate.isNotUndefined(remainingRaw)
|
|
201
|
+
? Option.getOrNull(Number.parse(remainingRaw))
|
|
202
|
+
: null
|
|
201
203
|
return {
|
|
202
204
|
retryAfter,
|
|
203
205
|
limit: headers["x-ratelimit-limit-requests"] ?? null,
|
|
@@ -218,7 +220,7 @@ export const buildHttpRequestDetails = (
|
|
|
218
220
|
method: request.method,
|
|
219
221
|
url: request.url,
|
|
220
222
|
urlParams: Array.from(request.urlParams),
|
|
221
|
-
hash: request.hash,
|
|
223
|
+
hash: Option.getOrUndefined(request.hash),
|
|
222
224
|
headers: Redactable.redact(request.headers) as Record<string, string>
|
|
223
225
|
})
|
|
224
226
|
|