@effect/ai-openrouter 4.0.0-beta.7 → 4.0.0-beta.9
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 +59 -30
- package/dist/Generated.js.map +1 -1
- package/dist/OpenRouterLanguageModel.d.ts +8 -0
- package/dist/OpenRouterLanguageModel.d.ts.map +1 -1
- package/dist/OpenRouterLanguageModel.js +221 -228
- package/dist/OpenRouterLanguageModel.js.map +1 -1
- package/package.json +3 -3
- package/src/Generated.ts +122 -52
- package/src/OpenRouterLanguageModel.ts +216 -224
|
@@ -885,283 +885,275 @@ const makeStreamResponse = Effect.fnUntraced(
|
|
|
885
885
|
}
|
|
886
886
|
|
|
887
887
|
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
|
-
}
|
|
888
|
+
if (Predicate.isNotUndefined(choice)) {
|
|
889
|
+
if (Predicate.isNotNullish(choice.finish_reason)) {
|
|
890
|
+
finishReason = resolveFinishReason(choice.finish_reason)
|
|
891
|
+
}
|
|
901
892
|
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
893
|
+
const delta = choice.delta
|
|
894
|
+
if (Predicate.isNullish(delta)) {
|
|
895
|
+
return parts
|
|
896
|
+
}
|
|
906
897
|
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
898
|
+
const emitReasoning = Effect.fnUntraced(
|
|
899
|
+
function*(delta: string, metadata?: Response.ReasoningDeltaPart["metadata"] | undefined) {
|
|
900
|
+
if (!reasoningStarted) {
|
|
901
|
+
activeReasoningId = openRouterResponseId ?? (yield* idGenerator.generateId())
|
|
902
|
+
parts.push({
|
|
903
|
+
type: "reasoning-start",
|
|
904
|
+
id: activeReasoningId,
|
|
905
|
+
metadata
|
|
906
|
+
})
|
|
907
|
+
reasoningStarted = true
|
|
908
|
+
}
|
|
911
909
|
parts.push({
|
|
912
|
-
type: "reasoning-
|
|
913
|
-
id: activeReasoningId
|
|
910
|
+
type: "reasoning-delta",
|
|
911
|
+
id: activeReasoningId!,
|
|
912
|
+
delta,
|
|
914
913
|
metadata
|
|
915
914
|
})
|
|
916
|
-
reasoningStarted = true
|
|
917
915
|
}
|
|
918
|
-
|
|
919
|
-
type: "reasoning-delta",
|
|
920
|
-
id: activeReasoningId!,
|
|
921
|
-
delta,
|
|
922
|
-
metadata
|
|
923
|
-
})
|
|
924
|
-
}
|
|
925
|
-
)
|
|
916
|
+
)
|
|
926
917
|
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
918
|
+
const reasoningDetails = delta.reasoning_details
|
|
919
|
+
if (Predicate.isNotUndefined(reasoningDetails) && reasoningDetails.length > 0) {
|
|
920
|
+
// Accumulate reasoning_details to preserve for multi-turn conversations
|
|
921
|
+
// Merge consecutive reasoning.text items into a single entry
|
|
922
|
+
for (const detail of reasoningDetails) {
|
|
923
|
+
if (detail.type === "reasoning.text") {
|
|
924
|
+
const lastDetail = accumulatedReasoningDetails[accumulatedReasoningDetails.length - 1]
|
|
925
|
+
if (Predicate.isNotUndefined(lastDetail) && lastDetail.type === "reasoning.text") {
|
|
926
|
+
// Merge with the previous text detail
|
|
927
|
+
lastDetail.text = (lastDetail.text ?? "") + (detail.text ?? "")
|
|
928
|
+
lastDetail.signature = lastDetail.signature ?? detail.signature ?? null
|
|
929
|
+
lastDetail.format = lastDetail.format ?? detail.format ?? null
|
|
930
|
+
} else {
|
|
931
|
+
// Start a new text detail
|
|
932
|
+
accumulatedReasoningDetails.push({ ...detail })
|
|
933
|
+
}
|
|
939
934
|
} else {
|
|
940
|
-
//
|
|
941
|
-
accumulatedReasoningDetails.push(
|
|
935
|
+
// Non-text details (encrypted, summary) are pushed as-is
|
|
936
|
+
accumulatedReasoningDetails.push(detail)
|
|
942
937
|
}
|
|
943
|
-
} else {
|
|
944
|
-
// Non-text details (encrypted, summary) are pushed as-is
|
|
945
|
-
accumulatedReasoningDetails.push(detail)
|
|
946
938
|
}
|
|
947
|
-
}
|
|
948
939
|
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
940
|
+
// Emit reasoning_details in providerMetadata for each delta chunk
|
|
941
|
+
// so users can accumulate them on their end before sending back
|
|
942
|
+
const metadata: Response.ReasoningDeltaPart["metadata"] = {
|
|
943
|
+
openrouter: {
|
|
944
|
+
reasoningDetails
|
|
945
|
+
}
|
|
954
946
|
}
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
947
|
+
for (const detail of reasoningDetails) {
|
|
948
|
+
switch (detail.type) {
|
|
949
|
+
case "reasoning.text": {
|
|
950
|
+
if (Predicate.isNotNullish(detail.text)) {
|
|
951
|
+
yield* emitReasoning(detail.text, metadata)
|
|
952
|
+
}
|
|
953
|
+
break
|
|
961
954
|
}
|
|
962
|
-
break
|
|
963
|
-
}
|
|
964
955
|
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
956
|
+
case "reasoning.summary": {
|
|
957
|
+
if (Predicate.isNotNullish(detail.summary)) {
|
|
958
|
+
yield* emitReasoning(detail.summary, metadata)
|
|
959
|
+
}
|
|
960
|
+
break
|
|
968
961
|
}
|
|
969
|
-
break
|
|
970
|
-
}
|
|
971
962
|
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
963
|
+
case "reasoning.encrypted": {
|
|
964
|
+
if (Predicate.isNotNullish(detail.data)) {
|
|
965
|
+
yield* emitReasoning("[REDACTED]", metadata)
|
|
966
|
+
}
|
|
967
|
+
break
|
|
975
968
|
}
|
|
976
|
-
break
|
|
977
969
|
}
|
|
978
970
|
}
|
|
971
|
+
} else if (Predicate.isNotNullish(delta.reasoning)) {
|
|
972
|
+
yield* emitReasoning(delta.reasoning)
|
|
979
973
|
}
|
|
980
|
-
} else if (Predicate.isNotNullish(delta.reasoning)) {
|
|
981
|
-
yield* emitReasoning(delta.reasoning)
|
|
982
|
-
}
|
|
983
974
|
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
975
|
+
const content = delta.content
|
|
976
|
+
if (Predicate.isNotNullish(content)) {
|
|
977
|
+
// If reasoning was previously active and now we're starting text content,
|
|
978
|
+
// we should end the reasoning first to maintain proper order
|
|
979
|
+
if (reasoningStarted && !textStarted) {
|
|
980
|
+
parts.push({
|
|
981
|
+
type: "reasoning-end",
|
|
982
|
+
id: activeReasoningId!,
|
|
983
|
+
// Include accumulated reasoning_details so the we can update the
|
|
984
|
+
// reasoning part's provider metadata with the correct signature.
|
|
985
|
+
// The signature typically arrives in the last reasoning delta,
|
|
986
|
+
// but reasoning-start only carries the first delta's metadata.
|
|
987
|
+
metadata: accumulatedReasoningDetails.length > 0
|
|
988
|
+
? { openRouter: { reasoningDetails: accumulatedReasoningDetails } }
|
|
989
|
+
: undefined
|
|
990
|
+
})
|
|
991
|
+
reasoningStarted = false
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
if (!textStarted) {
|
|
995
|
+
activeTextId = openRouterResponseId ?? (yield* idGenerator.generateId())
|
|
996
|
+
parts.push({
|
|
997
|
+
type: "text-start",
|
|
998
|
+
id: activeTextId
|
|
999
|
+
})
|
|
1000
|
+
textStarted = true
|
|
1001
|
+
}
|
|
1002
1002
|
|
|
1003
|
-
if (!textStarted) {
|
|
1004
|
-
activeTextId = openRouterResponseId ?? (yield* idGenerator.generateId())
|
|
1005
1003
|
parts.push({
|
|
1006
|
-
type: "text-
|
|
1007
|
-
id: activeTextId
|
|
1004
|
+
type: "text-delta",
|
|
1005
|
+
id: activeTextId!,
|
|
1006
|
+
delta: content
|
|
1008
1007
|
})
|
|
1009
|
-
textStarted = true
|
|
1010
1008
|
}
|
|
1011
1009
|
|
|
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)
|
|
1010
|
+
const annotations = delta.annotations
|
|
1011
|
+
if (Predicate.isNotNullish(annotations)) {
|
|
1012
|
+
for (const annotation of annotations) {
|
|
1013
|
+
if (annotation.type === "url_citation") {
|
|
1014
|
+
parts.push({
|
|
1015
|
+
type: "source",
|
|
1016
|
+
sourceType: "url",
|
|
1017
|
+
id: annotation.url_citation.url,
|
|
1018
|
+
url: annotation.url_citation.url,
|
|
1019
|
+
title: annotation.url_citation.title ?? "",
|
|
1020
|
+
metadata: {
|
|
1021
|
+
openrouter: {
|
|
1022
|
+
...(Predicate.isNotUndefined(annotation.url_citation.content)
|
|
1023
|
+
? { content: annotation.url_citation.content }
|
|
1024
|
+
: undefined),
|
|
1025
|
+
...(Predicate.isNotUndefined(annotation.url_citation.start_index)
|
|
1026
|
+
? { startIndex: annotation.url_citation.start_index }
|
|
1027
|
+
: undefined),
|
|
1028
|
+
...(Predicate.isNotUndefined(annotation.url_citation.end_index)
|
|
1029
|
+
? { startIndex: annotation.url_citation.end_index }
|
|
1030
|
+
: undefined)
|
|
1031
|
+
}
|
|
1040
1032
|
}
|
|
1041
|
-
}
|
|
1042
|
-
})
|
|
1043
|
-
|
|
1044
|
-
|
|
1033
|
+
})
|
|
1034
|
+
} else if (annotation.type === "file") {
|
|
1035
|
+
accumulatedFileAnnotations.push(annotation)
|
|
1036
|
+
}
|
|
1045
1037
|
}
|
|
1046
1038
|
}
|
|
1047
|
-
}
|
|
1048
1039
|
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1040
|
+
const toolCalls = delta.tool_calls
|
|
1041
|
+
if (Predicate.isNotNullish(toolCalls)) {
|
|
1042
|
+
for (const toolCall of toolCalls) {
|
|
1043
|
+
const index = toolCall.index ?? toolCalls.length - 1
|
|
1044
|
+
let activeToolCall = activeToolCalls[index]
|
|
1045
|
+
|
|
1046
|
+
// Tool call start - OpenRouter returns all information except the
|
|
1047
|
+
// tool call parameters in the first chunk
|
|
1048
|
+
if (Predicate.isUndefined(activeToolCall)) {
|
|
1049
|
+
if (toolCall.type !== "function") {
|
|
1050
|
+
return yield* AiError.make({
|
|
1051
|
+
module: "OpenRouterLanguageModel",
|
|
1052
|
+
method: "makeStreamResponse",
|
|
1053
|
+
reason: new AiError.InvalidOutputError({
|
|
1054
|
+
description: "Received tool call delta that was not of type: 'function'"
|
|
1055
|
+
})
|
|
1064
1056
|
})
|
|
1065
|
-
}
|
|
1066
|
-
}
|
|
1057
|
+
}
|
|
1067
1058
|
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1059
|
+
if (Predicate.isNullish(toolCall.id)) {
|
|
1060
|
+
return yield* AiError.make({
|
|
1061
|
+
module: "OpenRouterLanguageModel",
|
|
1062
|
+
method: "makeStreamResponse",
|
|
1063
|
+
reason: new AiError.InvalidOutputError({
|
|
1064
|
+
description: "Received tool call delta without a tool call identifier"
|
|
1065
|
+
})
|
|
1074
1066
|
})
|
|
1075
|
-
}
|
|
1076
|
-
}
|
|
1067
|
+
}
|
|
1077
1068
|
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1069
|
+
if (Predicate.isNullish(toolCall.function?.name)) {
|
|
1070
|
+
return yield* AiError.make({
|
|
1071
|
+
module: "OpenRouterLanguageModel",
|
|
1072
|
+
method: "makeStreamResponse",
|
|
1073
|
+
reason: new AiError.InvalidOutputError({
|
|
1074
|
+
description: "Received tool call delta without a tool call name"
|
|
1075
|
+
})
|
|
1084
1076
|
})
|
|
1085
|
-
}
|
|
1086
|
-
}
|
|
1077
|
+
}
|
|
1087
1078
|
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1079
|
+
activeToolCall = {
|
|
1080
|
+
id: toolCall.id,
|
|
1081
|
+
type: "function",
|
|
1082
|
+
name: toolCall.function.name,
|
|
1083
|
+
params: toolCall.function.arguments ?? ""
|
|
1084
|
+
}
|
|
1094
1085
|
|
|
1095
|
-
|
|
1086
|
+
activeToolCalls[index] = activeToolCall
|
|
1096
1087
|
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1088
|
+
parts.push({
|
|
1089
|
+
type: "tool-params-start",
|
|
1090
|
+
id: activeToolCall.id,
|
|
1091
|
+
name: activeToolCall.name
|
|
1092
|
+
})
|
|
1102
1093
|
|
|
1103
|
-
|
|
1104
|
-
|
|
1094
|
+
// Emit a tool call delta part if parameters were also sent
|
|
1095
|
+
if (activeToolCall.params.length > 0) {
|
|
1096
|
+
parts.push({
|
|
1097
|
+
type: "tool-params-delta",
|
|
1098
|
+
id: activeToolCall.id,
|
|
1099
|
+
delta: activeToolCall.params
|
|
1100
|
+
})
|
|
1101
|
+
}
|
|
1102
|
+
} else {
|
|
1103
|
+
// If an active tool call was found, update and emit the delta for
|
|
1104
|
+
// the tool call's parameters
|
|
1105
|
+
activeToolCall.params += toolCall.function?.arguments ?? ""
|
|
1105
1106
|
parts.push({
|
|
1106
1107
|
type: "tool-params-delta",
|
|
1107
1108
|
id: activeToolCall.id,
|
|
1108
1109
|
delta: activeToolCall.params
|
|
1109
1110
|
})
|
|
1110
1111
|
}
|
|
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
1112
|
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1113
|
+
// Check if the tool call is complete
|
|
1114
|
+
// @effect-diagnostics-next-line tryCatchInEffectGen:off
|
|
1115
|
+
try {
|
|
1116
|
+
const params = Tool.unsafeSecureJsonParse(activeToolCall.params)
|
|
1126
1117
|
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1118
|
+
parts.push({
|
|
1119
|
+
type: "tool-params-end",
|
|
1120
|
+
id: activeToolCall.id
|
|
1121
|
+
})
|
|
1131
1122
|
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1123
|
+
parts.push({
|
|
1124
|
+
type: "tool-call",
|
|
1125
|
+
id: activeToolCall.id,
|
|
1126
|
+
name: activeToolCall.name,
|
|
1127
|
+
params,
|
|
1128
|
+
// Only attach reasoning_details to the first tool call to avoid
|
|
1129
|
+
// duplicating thinking blocks for parallel tool calls (Claude)
|
|
1130
|
+
metadata: reasoningDetailsAttachedToToolCall ? undefined : {
|
|
1131
|
+
openrouter: { reasoningDetails: accumulatedReasoningDetails }
|
|
1132
|
+
}
|
|
1133
|
+
})
|
|
1143
1134
|
|
|
1144
|
-
|
|
1135
|
+
reasoningDetailsAttachedToToolCall = true
|
|
1145
1136
|
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1137
|
+
// Increment the total tool calls emitted by the stream and
|
|
1138
|
+
// remove the active tool call
|
|
1139
|
+
totalToolCalls += 1
|
|
1140
|
+
delete activeToolCalls[toolCall.index]
|
|
1141
|
+
} catch {
|
|
1142
|
+
// Tool call incomplete, continue parsing
|
|
1143
|
+
continue
|
|
1144
|
+
}
|
|
1153
1145
|
}
|
|
1154
1146
|
}
|
|
1155
|
-
}
|
|
1156
1147
|
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1148
|
+
const images = delta.images
|
|
1149
|
+
if (Predicate.isNotNullish(images)) {
|
|
1150
|
+
for (const image of images) {
|
|
1151
|
+
parts.push({
|
|
1152
|
+
type: "file",
|
|
1153
|
+
mediaType: getMediaType(image.image_url.url, "image/jpeg"),
|
|
1154
|
+
data: getBase64FromDataUrl(image.image_url.url)
|
|
1155
|
+
})
|
|
1156
|
+
}
|
|
1165
1157
|
}
|
|
1166
1158
|
}
|
|
1167
1159
|
|