@effect/ai-openrouter 4.0.0-beta.7 → 4.0.0-beta.70
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 +171 -92
- package/dist/Generated.d.ts.map +1 -1
- package/dist/Generated.js +60 -31
- package/dist/Generated.js.map +1 -1
- package/dist/OpenRouterClient.d.ts +46 -13
- package/dist/OpenRouterClient.d.ts.map +1 -1
- package/dist/OpenRouterClient.js +8 -8
- package/dist/OpenRouterClient.js.map +1 -1
- package/dist/OpenRouterConfig.d.ts +47 -10
- package/dist/OpenRouterConfig.d.ts.map +1 -1
- package/dist/OpenRouterConfig.js +33 -7
- package/dist/OpenRouterConfig.js.map +1 -1
- package/dist/OpenRouterError.d.ts +166 -35
- package/dist/OpenRouterError.d.ts.map +1 -1
- package/dist/OpenRouterError.js +1 -1
- package/dist/OpenRouterLanguageModel.d.ts +272 -19
- package/dist/OpenRouterLanguageModel.d.ts.map +1 -1
- package/dist/OpenRouterLanguageModel.js +257 -240
- package/dist/OpenRouterLanguageModel.js.map +1 -1
- package/dist/index.d.ts +66 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +66 -6
- package/dist/index.js.map +1 -1
- package/dist/internal/errors.js +4 -4
- package/dist/internal/errors.js.map +1 -1
- package/dist/internal/utilities.js +0 -1
- package/dist/internal/utilities.js.map +1 -1
- package/package.json +3 -3
- package/src/Generated.ts +123 -53
- package/src/OpenRouterClient.ts +47 -14
- package/src/OpenRouterConfig.ts +56 -13
- package/src/OpenRouterError.ts +168 -35
- package/src/OpenRouterLanguageModel.ts +501 -245
- package/src/index.ts +66 -6
- package/src/internal/errors.ts +6 -4
- package/src/internal/utilities.ts +0 -1
|
@@ -1,18 +1,39 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* The `OpenRouterLanguageModel` module provides constructors for using
|
|
3
|
+
* OpenRouter chat completion models through the Effect AI `LanguageModel`
|
|
4
|
+
* interface. It adapts Effect prompts, tools, structured output schemas, file
|
|
5
|
+
* parts, reasoning details, cache-control hints, and telemetry annotations into
|
|
6
|
+
* the OpenRouter request and response formats.
|
|
7
|
+
*
|
|
8
|
+
* Use this module when an application wants to select an OpenRouter model by
|
|
9
|
+
* name while keeping the rest of its AI workflow provider-agnostic. The
|
|
10
|
+
* exported layer and model constructors install a `LanguageModel` service backed
|
|
11
|
+
* by `OpenRouterClient`, and `withConfigOverride` can scope per-request
|
|
12
|
+
* OpenRouter options such as sampling, routing, tool use, or JSON schema
|
|
13
|
+
* behavior.
|
|
14
|
+
*
|
|
15
|
+
* OpenRouter routes requests to many underlying providers, so model support for
|
|
16
|
+
* images, files, tools, structured outputs, caching, and reasoning metadata can
|
|
17
|
+
* vary. Provider-specific prompt and response metadata is preserved under the
|
|
18
|
+
* `openrouter` option namespace so multi-turn conversations can round-trip
|
|
19
|
+
* details such as reasoning blocks and file annotations when the selected model
|
|
20
|
+
* supports them.
|
|
21
|
+
*
|
|
22
|
+
* @since 4.0.0
|
|
3
23
|
*/
|
|
4
24
|
/** @effect-diagnostics preferSchemaOverJson:skip-file */
|
|
5
25
|
import * as Arr from "effect/Array"
|
|
26
|
+
import * as Context from "effect/Context"
|
|
6
27
|
import * as DateTime from "effect/DateTime"
|
|
7
28
|
import * as Effect from "effect/Effect"
|
|
8
29
|
import * as Encoding from "effect/Encoding"
|
|
9
30
|
import { dual } from "effect/Function"
|
|
10
31
|
import * as Layer from "effect/Layer"
|
|
32
|
+
import * as Option from "effect/Option"
|
|
11
33
|
import * as Predicate from "effect/Predicate"
|
|
12
34
|
import * as Redactable from "effect/Redactable"
|
|
13
35
|
import type * as Schema from "effect/Schema"
|
|
14
36
|
import * as SchemaAST from "effect/SchemaAST"
|
|
15
|
-
import * as ServiceMap from "effect/ServiceMap"
|
|
16
37
|
import * as Stream from "effect/Stream"
|
|
17
38
|
import type { Span } from "effect/Tracer"
|
|
18
39
|
import type { DeepMutable, Mutable, Simplify } from "effect/Types"
|
|
@@ -39,10 +60,10 @@ import { type ChatStreamingResponseChunkData, OpenRouterClient } from "./OpenRou
|
|
|
39
60
|
/**
|
|
40
61
|
* Service definition for OpenRouter language model configuration.
|
|
41
62
|
*
|
|
42
|
-
* @since 1.0.0
|
|
43
63
|
* @category services
|
|
64
|
+
* @since 4.0.0
|
|
44
65
|
*/
|
|
45
|
-
export class Config extends
|
|
66
|
+
export class Config extends Context.Service<
|
|
46
67
|
Config,
|
|
47
68
|
Simplify<
|
|
48
69
|
& Partial<
|
|
@@ -68,14 +89,20 @@ export class Config extends ServiceMap.Service<
|
|
|
68
89
|
// =============================================================================
|
|
69
90
|
|
|
70
91
|
/**
|
|
71
|
-
*
|
|
92
|
+
* OpenRouter assistant reasoning detail blocks preserved for multi-turn
|
|
93
|
+
* conversations.
|
|
94
|
+
*
|
|
72
95
|
* @category models
|
|
96
|
+
* @since 4.0.0
|
|
73
97
|
*/
|
|
74
98
|
export type ReasoningDetails = Exclude<typeof Generated.AssistantMessage.Encoded["reasoning_details"], undefined>
|
|
75
99
|
|
|
76
100
|
/**
|
|
77
|
-
*
|
|
101
|
+
* File annotations emitted on OpenRouter assistant messages and exposed in
|
|
102
|
+
* finish metadata.
|
|
103
|
+
*
|
|
78
104
|
* @category models
|
|
105
|
+
* @since 4.0.0
|
|
79
106
|
*/
|
|
80
107
|
export type FileAnnotation = Extract<
|
|
81
108
|
NonNullable<typeof Generated.AssistantMessage.fields.annotations.Type>[number],
|
|
@@ -83,7 +110,21 @@ export type FileAnnotation = Extract<
|
|
|
83
110
|
>
|
|
84
111
|
|
|
85
112
|
declare module "effect/unstable/ai/Prompt" {
|
|
113
|
+
/**
|
|
114
|
+
* OpenRouter-specific options for system messages.
|
|
115
|
+
*
|
|
116
|
+
* **Details**
|
|
117
|
+
*
|
|
118
|
+
* These options are used when translating system instructions into
|
|
119
|
+
* OpenRouter chat messages.
|
|
120
|
+
*
|
|
121
|
+
* @category request
|
|
122
|
+
* @since 4.0.0
|
|
123
|
+
*/
|
|
86
124
|
export interface SystemMessageOptions extends ProviderOptions {
|
|
125
|
+
/**
|
|
126
|
+
* Provider-specific options sent to OpenRouter for the system message.
|
|
127
|
+
*/
|
|
87
128
|
readonly openrouter?: {
|
|
88
129
|
/**
|
|
89
130
|
* A breakpoint which marks the end of reusable content eligible for caching.
|
|
@@ -92,7 +133,21 @@ declare module "effect/unstable/ai/Prompt" {
|
|
|
92
133
|
} | null
|
|
93
134
|
}
|
|
94
135
|
|
|
136
|
+
/**
|
|
137
|
+
* OpenRouter-specific options for user messages.
|
|
138
|
+
*
|
|
139
|
+
* **Details**
|
|
140
|
+
*
|
|
141
|
+
* These options are used when translating user content into OpenRouter chat
|
|
142
|
+
* messages.
|
|
143
|
+
*
|
|
144
|
+
* @category request
|
|
145
|
+
* @since 4.0.0
|
|
146
|
+
*/
|
|
95
147
|
export interface UserMessageOptions extends ProviderOptions {
|
|
148
|
+
/**
|
|
149
|
+
* Provider-specific options sent to OpenRouter for the user message.
|
|
150
|
+
*/
|
|
96
151
|
readonly openrouter?: {
|
|
97
152
|
/**
|
|
98
153
|
* A breakpoint which marks the end of reusable content eligible for caching.
|
|
@@ -101,7 +156,21 @@ declare module "effect/unstable/ai/Prompt" {
|
|
|
101
156
|
} | null
|
|
102
157
|
}
|
|
103
158
|
|
|
159
|
+
/**
|
|
160
|
+
* OpenRouter-specific options for assistant messages.
|
|
161
|
+
*
|
|
162
|
+
* **Details**
|
|
163
|
+
*
|
|
164
|
+
* Preserves reasoning metadata when assistant messages are replayed in later
|
|
165
|
+
* OpenRouter requests.
|
|
166
|
+
*
|
|
167
|
+
* @category request
|
|
168
|
+
* @since 4.0.0
|
|
169
|
+
*/
|
|
104
170
|
export interface AssistantMessageOptions extends ProviderOptions {
|
|
171
|
+
/**
|
|
172
|
+
* Provider-specific options sent to OpenRouter for the assistant message.
|
|
173
|
+
*/
|
|
105
174
|
readonly openrouter?: {
|
|
106
175
|
/**
|
|
107
176
|
* A breakpoint which marks the end of reusable content eligible for caching.
|
|
@@ -114,7 +183,21 @@ declare module "effect/unstable/ai/Prompt" {
|
|
|
114
183
|
} | null
|
|
115
184
|
}
|
|
116
185
|
|
|
186
|
+
/**
|
|
187
|
+
* OpenRouter-specific options for tool messages.
|
|
188
|
+
*
|
|
189
|
+
* **Details**
|
|
190
|
+
*
|
|
191
|
+
* These options are used when converting tool results into OpenRouter chat
|
|
192
|
+
* messages.
|
|
193
|
+
*
|
|
194
|
+
* @category request
|
|
195
|
+
* @since 4.0.0
|
|
196
|
+
*/
|
|
117
197
|
export interface ToolMessageOptions extends ProviderOptions {
|
|
198
|
+
/**
|
|
199
|
+
* Provider-specific options sent to OpenRouter for the tool message.
|
|
200
|
+
*/
|
|
118
201
|
readonly openrouter?: {
|
|
119
202
|
/**
|
|
120
203
|
* A breakpoint which marks the end of reusable content eligible for caching.
|
|
@@ -123,7 +206,20 @@ declare module "effect/unstable/ai/Prompt" {
|
|
|
123
206
|
} | null
|
|
124
207
|
}
|
|
125
208
|
|
|
209
|
+
/**
|
|
210
|
+
* OpenRouter-specific options for text prompt parts.
|
|
211
|
+
*
|
|
212
|
+
* **When to use**
|
|
213
|
+
*
|
|
214
|
+
* Use these options to control how text content is sent to OpenRouter.
|
|
215
|
+
*
|
|
216
|
+
* @category request
|
|
217
|
+
* @since 4.0.0
|
|
218
|
+
*/
|
|
126
219
|
export interface TextPartOptions extends ProviderOptions {
|
|
220
|
+
/**
|
|
221
|
+
* Provider-specific options sent to OpenRouter for the text part.
|
|
222
|
+
*/
|
|
127
223
|
readonly openrouter?: {
|
|
128
224
|
/**
|
|
129
225
|
* A breakpoint which marks the end of reusable content eligible for caching.
|
|
@@ -132,7 +228,21 @@ declare module "effect/unstable/ai/Prompt" {
|
|
|
132
228
|
} | null
|
|
133
229
|
}
|
|
134
230
|
|
|
231
|
+
/**
|
|
232
|
+
* OpenRouter-specific options for reasoning prompt parts.
|
|
233
|
+
*
|
|
234
|
+
* **Details**
|
|
235
|
+
*
|
|
236
|
+
* Preserves provider reasoning blocks so reasoning-aware conversations can
|
|
237
|
+
* continue across OpenRouter requests.
|
|
238
|
+
*
|
|
239
|
+
* @category request
|
|
240
|
+
* @since 4.0.0
|
|
241
|
+
*/
|
|
135
242
|
export interface ReasoningPartOptions extends ProviderOptions {
|
|
243
|
+
/**
|
|
244
|
+
* Provider-specific options sent to OpenRouter for the reasoning part.
|
|
245
|
+
*/
|
|
136
246
|
readonly openrouter?: {
|
|
137
247
|
/**
|
|
138
248
|
* A breakpoint which marks the end of reusable content eligible for caching.
|
|
@@ -145,7 +255,20 @@ declare module "effect/unstable/ai/Prompt" {
|
|
|
145
255
|
} | null
|
|
146
256
|
}
|
|
147
257
|
|
|
258
|
+
/**
|
|
259
|
+
* OpenRouter-specific options for file prompt parts.
|
|
260
|
+
*
|
|
261
|
+
* **Details**
|
|
262
|
+
*
|
|
263
|
+
* Controls file naming and prompt caching for files sent to OpenRouter.
|
|
264
|
+
*
|
|
265
|
+
* @category request
|
|
266
|
+
* @since 4.0.0
|
|
267
|
+
*/
|
|
148
268
|
export interface FilePartOptions extends ProviderOptions {
|
|
269
|
+
/**
|
|
270
|
+
* Provider-specific options sent to OpenRouter for the file part.
|
|
271
|
+
*/
|
|
149
272
|
readonly openrouter?: {
|
|
150
273
|
/**
|
|
151
274
|
* The name to give to the file. Will be prioritized over the file name
|
|
@@ -159,7 +282,21 @@ declare module "effect/unstable/ai/Prompt" {
|
|
|
159
282
|
} | null
|
|
160
283
|
}
|
|
161
284
|
|
|
285
|
+
/**
|
|
286
|
+
* OpenRouter-specific options for tool call prompt parts.
|
|
287
|
+
*
|
|
288
|
+
* **Details**
|
|
289
|
+
*
|
|
290
|
+
* Preserves reasoning details associated with tool calls when a conversation
|
|
291
|
+
* is sent back to OpenRouter.
|
|
292
|
+
*
|
|
293
|
+
* @category request
|
|
294
|
+
* @since 4.0.0
|
|
295
|
+
*/
|
|
162
296
|
export interface ToolCallPartOptions extends ProviderOptions {
|
|
297
|
+
/**
|
|
298
|
+
* Provider-specific options sent to OpenRouter for the tool call part.
|
|
299
|
+
*/
|
|
163
300
|
readonly openrouter?: {
|
|
164
301
|
/**
|
|
165
302
|
* Reasoning details associated with the tool call part.
|
|
@@ -168,7 +305,20 @@ declare module "effect/unstable/ai/Prompt" {
|
|
|
168
305
|
} | null
|
|
169
306
|
}
|
|
170
307
|
|
|
308
|
+
/**
|
|
309
|
+
* OpenRouter-specific options for tool result prompt parts.
|
|
310
|
+
*
|
|
311
|
+
* **Details**
|
|
312
|
+
*
|
|
313
|
+
* Controls prompt caching for tool results sent to OpenRouter.
|
|
314
|
+
*
|
|
315
|
+
* @category request
|
|
316
|
+
* @since 4.0.0
|
|
317
|
+
*/
|
|
171
318
|
export interface ToolResultPartOptions extends ProviderOptions {
|
|
319
|
+
/**
|
|
320
|
+
* Provider-specific options sent to OpenRouter for the tool result part.
|
|
321
|
+
*/
|
|
172
322
|
readonly openrouter?: {
|
|
173
323
|
/**
|
|
174
324
|
* A breakpoint which marks the end of reusable content eligible for caching.
|
|
@@ -179,43 +329,157 @@ declare module "effect/unstable/ai/Prompt" {
|
|
|
179
329
|
}
|
|
180
330
|
|
|
181
331
|
declare module "effect/unstable/ai/Response" {
|
|
332
|
+
/**
|
|
333
|
+
* OpenRouter metadata attached to completed reasoning response parts.
|
|
334
|
+
*
|
|
335
|
+
* **Details**
|
|
336
|
+
*
|
|
337
|
+
* Preserves provider reasoning details that can be sent back in later turns.
|
|
338
|
+
*
|
|
339
|
+
* @category response
|
|
340
|
+
* @since 4.0.0
|
|
341
|
+
*/
|
|
182
342
|
export interface ReasoningPartMetadata extends ProviderMetadata {
|
|
343
|
+
/**
|
|
344
|
+
* Provider-specific metadata returned for the reasoning part.
|
|
345
|
+
*/
|
|
183
346
|
readonly openrouter?: {
|
|
347
|
+
/**
|
|
348
|
+
* Reasoning details emitted by the underlying provider for this part.
|
|
349
|
+
*/
|
|
184
350
|
readonly reasoningDetails?: ReasoningDetails | null
|
|
185
351
|
} | null
|
|
186
352
|
}
|
|
187
353
|
|
|
354
|
+
/**
|
|
355
|
+
* OpenRouter metadata emitted when a streamed reasoning part starts.
|
|
356
|
+
*
|
|
357
|
+
* **Details**
|
|
358
|
+
*
|
|
359
|
+
* Carries the first reasoning detail chunk when OpenRouter exposes one.
|
|
360
|
+
*
|
|
361
|
+
* @category response
|
|
362
|
+
* @since 4.0.0
|
|
363
|
+
*/
|
|
188
364
|
export interface ReasoningStartPartMetadata extends ProviderMetadata {
|
|
365
|
+
/**
|
|
366
|
+
* Provider-specific metadata returned for the streamed reasoning start.
|
|
367
|
+
*/
|
|
189
368
|
readonly openrouter?: {
|
|
369
|
+
/**
|
|
370
|
+
* Reasoning details emitted by the underlying provider for this part.
|
|
371
|
+
*/
|
|
190
372
|
readonly reasoningDetails?: ReasoningDetails | null
|
|
191
373
|
} | null
|
|
192
374
|
}
|
|
193
375
|
|
|
376
|
+
/**
|
|
377
|
+
* OpenRouter metadata emitted for streamed reasoning deltas.
|
|
378
|
+
*
|
|
379
|
+
* **Details**
|
|
380
|
+
*
|
|
381
|
+
* Carries provider reasoning detail chunks as they arrive from OpenRouter.
|
|
382
|
+
*
|
|
383
|
+
* @category response
|
|
384
|
+
* @since 4.0.0
|
|
385
|
+
*/
|
|
194
386
|
export interface ReasoningDeltaPartMetadata extends ProviderMetadata {
|
|
387
|
+
/**
|
|
388
|
+
* Provider-specific metadata returned for the streamed reasoning delta.
|
|
389
|
+
*/
|
|
195
390
|
readonly openrouter?: {
|
|
391
|
+
/**
|
|
392
|
+
* Reasoning details emitted by the underlying provider for this delta.
|
|
393
|
+
*/
|
|
196
394
|
readonly reasoningDetails?: ReasoningDetails | null
|
|
197
395
|
} | null
|
|
198
396
|
}
|
|
199
397
|
|
|
398
|
+
/**
|
|
399
|
+
* OpenRouter metadata attached to tool-call response parts.
|
|
400
|
+
*
|
|
401
|
+
* **Details**
|
|
402
|
+
*
|
|
403
|
+
* Associates tool calls with provider reasoning details when the model emits
|
|
404
|
+
* reasoning and tool calls together.
|
|
405
|
+
*
|
|
406
|
+
* @category response
|
|
407
|
+
* @since 4.0.0
|
|
408
|
+
*/
|
|
200
409
|
export interface ToolCallPartMetadata extends ProviderMetadata {
|
|
410
|
+
/**
|
|
411
|
+
* Provider-specific metadata returned for the tool call.
|
|
412
|
+
*/
|
|
201
413
|
readonly openrouter?: {
|
|
414
|
+
/**
|
|
415
|
+
* Reasoning details associated with this tool call.
|
|
416
|
+
*/
|
|
202
417
|
readonly reasoningDetails?: ReasoningDetails | null
|
|
203
418
|
} | null
|
|
204
419
|
}
|
|
205
420
|
|
|
421
|
+
/**
|
|
422
|
+
* OpenRouter metadata attached to URL source citations.
|
|
423
|
+
*
|
|
424
|
+
* **Details**
|
|
425
|
+
*
|
|
426
|
+
* Includes citation text and offsets returned by providers that support URL
|
|
427
|
+
* annotations.
|
|
428
|
+
*
|
|
429
|
+
* @category response
|
|
430
|
+
* @since 4.0.0
|
|
431
|
+
*/
|
|
206
432
|
export interface UrlSourcePartMetadata extends ProviderMetadata {
|
|
433
|
+
/**
|
|
434
|
+
* Provider-specific citation metadata returned for the URL source.
|
|
435
|
+
*/
|
|
207
436
|
readonly openrouter?: {
|
|
437
|
+
/**
|
|
438
|
+
* The cited source content returned by the provider.
|
|
439
|
+
*/
|
|
208
440
|
readonly content?: string | null
|
|
441
|
+
/**
|
|
442
|
+
* The zero-based start index of the citation in the generated text.
|
|
443
|
+
*/
|
|
209
444
|
readonly startIndex?: number | null
|
|
445
|
+
/**
|
|
446
|
+
* The zero-based end index of the citation in the generated text.
|
|
447
|
+
*/
|
|
210
448
|
readonly endIndex?: number | null
|
|
211
449
|
} | null
|
|
212
450
|
}
|
|
213
451
|
|
|
452
|
+
/**
|
|
453
|
+
* OpenRouter metadata attached to finish response parts.
|
|
454
|
+
*
|
|
455
|
+
* **Details**
|
|
456
|
+
*
|
|
457
|
+
* Exposes provider response details that are not represented by the common
|
|
458
|
+
* Effect AI finish part fields.
|
|
459
|
+
*
|
|
460
|
+
* @category response
|
|
461
|
+
* @since 4.0.0
|
|
462
|
+
*/
|
|
214
463
|
export interface FinishPartMetadata extends ProviderMetadata {
|
|
464
|
+
/**
|
|
465
|
+
* Provider-specific metadata returned when the OpenRouter response finishes.
|
|
466
|
+
*/
|
|
215
467
|
readonly openrouter?: {
|
|
468
|
+
/**
|
|
469
|
+
* Provider fingerprint for the backend configuration that served the request.
|
|
470
|
+
*/
|
|
216
471
|
readonly systemFingerprint?: string | null
|
|
472
|
+
/**
|
|
473
|
+
* Raw token usage reported by OpenRouter.
|
|
474
|
+
*/
|
|
217
475
|
readonly usage?: typeof Generated.ChatGenerationTokenUsage.Encoded | null
|
|
476
|
+
/**
|
|
477
|
+
* File annotations returned by the provider.
|
|
478
|
+
*/
|
|
218
479
|
readonly annotations?: ReadonlyArray<FileAnnotation> | null
|
|
480
|
+
/**
|
|
481
|
+
* The OpenRouter provider that served the request, when reported.
|
|
482
|
+
*/
|
|
219
483
|
readonly provider?: string | null
|
|
220
484
|
} | null
|
|
221
485
|
}
|
|
@@ -226,20 +490,22 @@ declare module "effect/unstable/ai/Response" {
|
|
|
226
490
|
// =============================================================================
|
|
227
491
|
|
|
228
492
|
/**
|
|
229
|
-
*
|
|
493
|
+
* Creates an AI model descriptor for an OpenRouter language model.
|
|
494
|
+
*
|
|
230
495
|
* @category constructors
|
|
496
|
+
* @since 4.0.0
|
|
231
497
|
*/
|
|
232
498
|
export const model = (
|
|
233
499
|
model: string,
|
|
234
500
|
config?: Omit<typeof Config.Service, "model">
|
|
235
501
|
): AiModel.Model<"openai", LanguageModel.LanguageModel, OpenRouterClient> =>
|
|
236
|
-
AiModel.make("openai", layer({ model, config }))
|
|
502
|
+
AiModel.make("openai", model, layer({ model, config }))
|
|
237
503
|
|
|
238
504
|
/**
|
|
239
505
|
* Creates an OpenRouter language model service.
|
|
240
506
|
*
|
|
241
|
-
* @since 1.0.0
|
|
242
507
|
* @category constructors
|
|
508
|
+
* @since 4.0.0
|
|
243
509
|
*/
|
|
244
510
|
export const make = Effect.fnUntraced(function*({ model, config: providerConfig }: {
|
|
245
511
|
readonly model: string
|
|
@@ -249,7 +515,7 @@ export const make = Effect.fnUntraced(function*({ model, config: providerConfig
|
|
|
249
515
|
const codecTransformer = getCodecTransformer(model)
|
|
250
516
|
|
|
251
517
|
const makeConfig = Effect.gen(function*() {
|
|
252
|
-
const services = yield* Effect.
|
|
518
|
+
const services = yield* Effect.context<never>()
|
|
253
519
|
return { model, ...providerConfig, ...services.mapUnsafe.get(Config.key) }
|
|
254
520
|
})
|
|
255
521
|
|
|
@@ -273,6 +539,7 @@ export const make = Effect.fnUntraced(function*({ model, config: providerConfig
|
|
|
273
539
|
)
|
|
274
540
|
|
|
275
541
|
return yield* LanguageModel.make({
|
|
542
|
+
codecTransformer: toCodecOpenAI,
|
|
276
543
|
generateText: Effect.fnUntraced(
|
|
277
544
|
function*(options) {
|
|
278
545
|
const config = yield* makeConfig
|
|
@@ -300,17 +567,14 @@ export const make = Effect.fnUntraced(function*({ model, config: providerConfig
|
|
|
300
567
|
})
|
|
301
568
|
)
|
|
302
569
|
)
|
|
303
|
-
})
|
|
304
|
-
LanguageModel.CurrentCodecTransformer,
|
|
305
|
-
codecTransformer
|
|
306
|
-
))
|
|
570
|
+
})
|
|
307
571
|
})
|
|
308
572
|
|
|
309
573
|
/**
|
|
310
574
|
* Creates a layer for the OpenRouter language model.
|
|
311
575
|
*
|
|
312
|
-
* @since 1.0.0
|
|
313
576
|
* @category layers
|
|
577
|
+
* @since 4.0.0
|
|
314
578
|
*/
|
|
315
579
|
export const layer = (options: {
|
|
316
580
|
readonly model: string
|
|
@@ -321,37 +585,37 @@ export const layer = (options: {
|
|
|
321
585
|
/**
|
|
322
586
|
* Provides config overrides for OpenRouter language model operations.
|
|
323
587
|
*
|
|
324
|
-
* @since 1.0.0
|
|
325
588
|
* @category configuration
|
|
589
|
+
* @since 4.0.0
|
|
326
590
|
*/
|
|
327
591
|
export const withConfigOverride: {
|
|
328
592
|
/**
|
|
329
593
|
* Provides config overrides for OpenRouter language model operations.
|
|
330
594
|
*
|
|
331
|
-
* @since 1.0.0
|
|
332
595
|
* @category configuration
|
|
596
|
+
* @since 4.0.0
|
|
333
597
|
*/
|
|
334
598
|
(overrides: typeof Config.Service): <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, E, Exclude<R, Config>>
|
|
335
599
|
/**
|
|
336
600
|
* Provides config overrides for OpenRouter language model operations.
|
|
337
601
|
*
|
|
338
|
-
* @since 1.0.0
|
|
339
602
|
* @category configuration
|
|
603
|
+
* @since 4.0.0
|
|
340
604
|
*/
|
|
341
605
|
<A, E, R>(self: Effect.Effect<A, E, R>, overrides: typeof Config.Service): Effect.Effect<A, E, Exclude<R, Config>>
|
|
342
606
|
} = dual<
|
|
343
607
|
/**
|
|
344
608
|
* Provides config overrides for OpenRouter language model operations.
|
|
345
609
|
*
|
|
346
|
-
* @since 1.0.0
|
|
347
610
|
* @category configuration
|
|
611
|
+
* @since 4.0.0
|
|
348
612
|
*/
|
|
349
613
|
(overrides: typeof Config.Service) => <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, E, Exclude<R, Config>>,
|
|
350
614
|
/**
|
|
351
615
|
* Provides config overrides for OpenRouter language model operations.
|
|
352
616
|
*
|
|
353
|
-
* @since 1.0.0
|
|
354
617
|
* @category configuration
|
|
618
|
+
* @since 4.0.0
|
|
355
619
|
*/
|
|
356
620
|
<A, E, R>(self: Effect.Effect<A, E, R>, overrides: typeof Config.Service) => Effect.Effect<A, E, Exclude<R, Config>>
|
|
357
621
|
>(2, (self, overrides) =>
|
|
@@ -588,7 +852,7 @@ const buildHttpRequestDetails = (
|
|
|
588
852
|
method: request.method,
|
|
589
853
|
url: request.url,
|
|
590
854
|
urlParams: Array.from(request.urlParams),
|
|
591
|
-
hash: request.hash,
|
|
855
|
+
hash: Option.getOrUndefined(request.hash),
|
|
592
856
|
headers: Redactable.redact(request.headers) as Record<string, string>
|
|
593
857
|
})
|
|
594
858
|
|
|
@@ -885,283 +1149,275 @@ const makeStreamResponse = Effect.fnUntraced(
|
|
|
885
1149
|
}
|
|
886
1150
|
|
|
887
1151
|
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
|
-
}
|
|
1152
|
+
if (Predicate.isNotUndefined(choice)) {
|
|
1153
|
+
if (Predicate.isNotNullish(choice.finish_reason)) {
|
|
1154
|
+
finishReason = resolveFinishReason(choice.finish_reason)
|
|
1155
|
+
}
|
|
901
1156
|
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
1157
|
+
const delta = choice.delta
|
|
1158
|
+
if (Predicate.isNullish(delta)) {
|
|
1159
|
+
return parts
|
|
1160
|
+
}
|
|
906
1161
|
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
1162
|
+
const emitReasoning = Effect.fnUntraced(
|
|
1163
|
+
function*(delta: string, metadata?: Response.ReasoningDeltaPart["metadata"] | undefined) {
|
|
1164
|
+
if (!reasoningStarted) {
|
|
1165
|
+
activeReasoningId = openRouterResponseId ?? (yield* idGenerator.generateId())
|
|
1166
|
+
parts.push({
|
|
1167
|
+
type: "reasoning-start",
|
|
1168
|
+
id: activeReasoningId,
|
|
1169
|
+
metadata
|
|
1170
|
+
})
|
|
1171
|
+
reasoningStarted = true
|
|
1172
|
+
}
|
|
911
1173
|
parts.push({
|
|
912
|
-
type: "reasoning-
|
|
913
|
-
id: activeReasoningId
|
|
1174
|
+
type: "reasoning-delta",
|
|
1175
|
+
id: activeReasoningId!,
|
|
1176
|
+
delta,
|
|
914
1177
|
metadata
|
|
915
1178
|
})
|
|
916
|
-
reasoningStarted = true
|
|
917
1179
|
}
|
|
918
|
-
|
|
919
|
-
type: "reasoning-delta",
|
|
920
|
-
id: activeReasoningId!,
|
|
921
|
-
delta,
|
|
922
|
-
metadata
|
|
923
|
-
})
|
|
924
|
-
}
|
|
925
|
-
)
|
|
1180
|
+
)
|
|
926
1181
|
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
1182
|
+
const reasoningDetails = delta.reasoning_details
|
|
1183
|
+
if (Predicate.isNotUndefined(reasoningDetails) && reasoningDetails.length > 0) {
|
|
1184
|
+
// Accumulate reasoning_details to preserve for multi-turn conversations
|
|
1185
|
+
// Merge consecutive reasoning.text items into a single entry
|
|
1186
|
+
for (const detail of reasoningDetails) {
|
|
1187
|
+
if (detail.type === "reasoning.text") {
|
|
1188
|
+
const lastDetail = accumulatedReasoningDetails[accumulatedReasoningDetails.length - 1]
|
|
1189
|
+
if (Predicate.isNotUndefined(lastDetail) && lastDetail.type === "reasoning.text") {
|
|
1190
|
+
// Merge with the previous text detail
|
|
1191
|
+
lastDetail.text = (lastDetail.text ?? "") + (detail.text ?? "")
|
|
1192
|
+
lastDetail.signature = lastDetail.signature ?? detail.signature ?? null
|
|
1193
|
+
lastDetail.format = lastDetail.format ?? detail.format ?? null
|
|
1194
|
+
} else {
|
|
1195
|
+
// Start a new text detail
|
|
1196
|
+
accumulatedReasoningDetails.push({ ...detail })
|
|
1197
|
+
}
|
|
939
1198
|
} else {
|
|
940
|
-
//
|
|
941
|
-
accumulatedReasoningDetails.push(
|
|
1199
|
+
// Non-text details (encrypted, summary) are pushed as-is
|
|
1200
|
+
accumulatedReasoningDetails.push(detail)
|
|
942
1201
|
}
|
|
943
|
-
} else {
|
|
944
|
-
// Non-text details (encrypted, summary) are pushed as-is
|
|
945
|
-
accumulatedReasoningDetails.push(detail)
|
|
946
1202
|
}
|
|
947
|
-
}
|
|
948
1203
|
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
1204
|
+
// Emit reasoning_details in providerMetadata for each delta chunk
|
|
1205
|
+
// so users can accumulate them on their end before sending back
|
|
1206
|
+
const metadata: Response.ReasoningDeltaPart["metadata"] = {
|
|
1207
|
+
openrouter: {
|
|
1208
|
+
reasoningDetails
|
|
1209
|
+
}
|
|
954
1210
|
}
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
1211
|
+
for (const detail of reasoningDetails) {
|
|
1212
|
+
switch (detail.type) {
|
|
1213
|
+
case "reasoning.text": {
|
|
1214
|
+
if (Predicate.isNotNullish(detail.text)) {
|
|
1215
|
+
yield* emitReasoning(detail.text, metadata)
|
|
1216
|
+
}
|
|
1217
|
+
break
|
|
961
1218
|
}
|
|
962
|
-
break
|
|
963
|
-
}
|
|
964
1219
|
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
1220
|
+
case "reasoning.summary": {
|
|
1221
|
+
if (Predicate.isNotNullish(detail.summary)) {
|
|
1222
|
+
yield* emitReasoning(detail.summary, metadata)
|
|
1223
|
+
}
|
|
1224
|
+
break
|
|
968
1225
|
}
|
|
969
|
-
break
|
|
970
|
-
}
|
|
971
1226
|
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
1227
|
+
case "reasoning.encrypted": {
|
|
1228
|
+
if (Predicate.isNotNullish(detail.data)) {
|
|
1229
|
+
yield* emitReasoning("[REDACTED]", metadata)
|
|
1230
|
+
}
|
|
1231
|
+
break
|
|
975
1232
|
}
|
|
976
|
-
break
|
|
977
1233
|
}
|
|
978
1234
|
}
|
|
1235
|
+
} else if (Predicate.isNotNullish(delta.reasoning)) {
|
|
1236
|
+
yield* emitReasoning(delta.reasoning)
|
|
979
1237
|
}
|
|
980
|
-
} else if (Predicate.isNotNullish(delta.reasoning)) {
|
|
981
|
-
yield* emitReasoning(delta.reasoning)
|
|
982
|
-
}
|
|
983
1238
|
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1239
|
+
const content = delta.content
|
|
1240
|
+
if (Predicate.isNotNullish(content)) {
|
|
1241
|
+
// If reasoning was previously active and now we're starting text content,
|
|
1242
|
+
// we should end the reasoning first to maintain proper order
|
|
1243
|
+
if (reasoningStarted && !textStarted) {
|
|
1244
|
+
parts.push({
|
|
1245
|
+
type: "reasoning-end",
|
|
1246
|
+
id: activeReasoningId!,
|
|
1247
|
+
// Include accumulated reasoning_details so the we can update the
|
|
1248
|
+
// reasoning part's provider metadata with the correct signature.
|
|
1249
|
+
// The signature typically arrives in the last reasoning delta,
|
|
1250
|
+
// but reasoning-start only carries the first delta's metadata.
|
|
1251
|
+
metadata: accumulatedReasoningDetails.length > 0
|
|
1252
|
+
? { openRouter: { reasoningDetails: accumulatedReasoningDetails } }
|
|
1253
|
+
: undefined
|
|
1254
|
+
})
|
|
1255
|
+
reasoningStarted = false
|
|
1256
|
+
}
|
|
1257
|
+
|
|
1258
|
+
if (!textStarted) {
|
|
1259
|
+
activeTextId = openRouterResponseId ?? (yield* idGenerator.generateId())
|
|
1260
|
+
parts.push({
|
|
1261
|
+
type: "text-start",
|
|
1262
|
+
id: activeTextId
|
|
1263
|
+
})
|
|
1264
|
+
textStarted = true
|
|
1265
|
+
}
|
|
1002
1266
|
|
|
1003
|
-
if (!textStarted) {
|
|
1004
|
-
activeTextId = openRouterResponseId ?? (yield* idGenerator.generateId())
|
|
1005
1267
|
parts.push({
|
|
1006
|
-
type: "text-
|
|
1007
|
-
id: activeTextId
|
|
1268
|
+
type: "text-delta",
|
|
1269
|
+
id: activeTextId!,
|
|
1270
|
+
delta: content
|
|
1008
1271
|
})
|
|
1009
|
-
textStarted = true
|
|
1010
1272
|
}
|
|
1011
1273
|
|
|
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)
|
|
1274
|
+
const annotations = delta.annotations
|
|
1275
|
+
if (Predicate.isNotNullish(annotations)) {
|
|
1276
|
+
for (const annotation of annotations) {
|
|
1277
|
+
if (annotation.type === "url_citation") {
|
|
1278
|
+
parts.push({
|
|
1279
|
+
type: "source",
|
|
1280
|
+
sourceType: "url",
|
|
1281
|
+
id: annotation.url_citation.url,
|
|
1282
|
+
url: annotation.url_citation.url,
|
|
1283
|
+
title: annotation.url_citation.title ?? "",
|
|
1284
|
+
metadata: {
|
|
1285
|
+
openrouter: {
|
|
1286
|
+
...(Predicate.isNotUndefined(annotation.url_citation.content)
|
|
1287
|
+
? { content: annotation.url_citation.content }
|
|
1288
|
+
: undefined),
|
|
1289
|
+
...(Predicate.isNotUndefined(annotation.url_citation.start_index)
|
|
1290
|
+
? { startIndex: annotation.url_citation.start_index }
|
|
1291
|
+
: undefined),
|
|
1292
|
+
...(Predicate.isNotUndefined(annotation.url_citation.end_index)
|
|
1293
|
+
? { startIndex: annotation.url_citation.end_index }
|
|
1294
|
+
: undefined)
|
|
1295
|
+
}
|
|
1040
1296
|
}
|
|
1041
|
-
}
|
|
1042
|
-
})
|
|
1043
|
-
|
|
1044
|
-
|
|
1297
|
+
})
|
|
1298
|
+
} else if (annotation.type === "file") {
|
|
1299
|
+
accumulatedFileAnnotations.push(annotation)
|
|
1300
|
+
}
|
|
1045
1301
|
}
|
|
1046
1302
|
}
|
|
1047
|
-
}
|
|
1048
1303
|
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1304
|
+
const toolCalls = delta.tool_calls
|
|
1305
|
+
if (Predicate.isNotNullish(toolCalls)) {
|
|
1306
|
+
for (const toolCall of toolCalls) {
|
|
1307
|
+
const index = toolCall.index ?? toolCalls.length - 1
|
|
1308
|
+
let activeToolCall = activeToolCalls[index]
|
|
1309
|
+
|
|
1310
|
+
// Tool call start - OpenRouter returns all information except the
|
|
1311
|
+
// tool call parameters in the first chunk
|
|
1312
|
+
if (Predicate.isUndefined(activeToolCall)) {
|
|
1313
|
+
if (toolCall.type !== "function") {
|
|
1314
|
+
return yield* AiError.make({
|
|
1315
|
+
module: "OpenRouterLanguageModel",
|
|
1316
|
+
method: "makeStreamResponse",
|
|
1317
|
+
reason: new AiError.InvalidOutputError({
|
|
1318
|
+
description: "Received tool call delta that was not of type: 'function'"
|
|
1319
|
+
})
|
|
1064
1320
|
})
|
|
1065
|
-
}
|
|
1066
|
-
}
|
|
1321
|
+
}
|
|
1067
1322
|
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1323
|
+
if (Predicate.isNullish(toolCall.id)) {
|
|
1324
|
+
return yield* AiError.make({
|
|
1325
|
+
module: "OpenRouterLanguageModel",
|
|
1326
|
+
method: "makeStreamResponse",
|
|
1327
|
+
reason: new AiError.InvalidOutputError({
|
|
1328
|
+
description: "Received tool call delta without a tool call identifier"
|
|
1329
|
+
})
|
|
1074
1330
|
})
|
|
1075
|
-
}
|
|
1076
|
-
}
|
|
1331
|
+
}
|
|
1077
1332
|
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1333
|
+
if (Predicate.isNullish(toolCall.function?.name)) {
|
|
1334
|
+
return yield* AiError.make({
|
|
1335
|
+
module: "OpenRouterLanguageModel",
|
|
1336
|
+
method: "makeStreamResponse",
|
|
1337
|
+
reason: new AiError.InvalidOutputError({
|
|
1338
|
+
description: "Received tool call delta without a tool call name"
|
|
1339
|
+
})
|
|
1084
1340
|
})
|
|
1085
|
-
}
|
|
1086
|
-
}
|
|
1341
|
+
}
|
|
1087
1342
|
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1343
|
+
activeToolCall = {
|
|
1344
|
+
id: toolCall.id,
|
|
1345
|
+
type: "function",
|
|
1346
|
+
name: toolCall.function.name,
|
|
1347
|
+
params: toolCall.function.arguments ?? ""
|
|
1348
|
+
}
|
|
1094
1349
|
|
|
1095
|
-
|
|
1350
|
+
activeToolCalls[index] = activeToolCall
|
|
1096
1351
|
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1352
|
+
parts.push({
|
|
1353
|
+
type: "tool-params-start",
|
|
1354
|
+
id: activeToolCall.id,
|
|
1355
|
+
name: activeToolCall.name
|
|
1356
|
+
})
|
|
1102
1357
|
|
|
1103
|
-
|
|
1104
|
-
|
|
1358
|
+
// Emit a tool call delta part if parameters were also sent
|
|
1359
|
+
if (activeToolCall.params.length > 0) {
|
|
1360
|
+
parts.push({
|
|
1361
|
+
type: "tool-params-delta",
|
|
1362
|
+
id: activeToolCall.id,
|
|
1363
|
+
delta: activeToolCall.params
|
|
1364
|
+
})
|
|
1365
|
+
}
|
|
1366
|
+
} else {
|
|
1367
|
+
// If an active tool call was found, update and emit the delta for
|
|
1368
|
+
// the tool call's parameters
|
|
1369
|
+
activeToolCall.params += toolCall.function?.arguments ?? ""
|
|
1105
1370
|
parts.push({
|
|
1106
1371
|
type: "tool-params-delta",
|
|
1107
1372
|
id: activeToolCall.id,
|
|
1108
1373
|
delta: activeToolCall.params
|
|
1109
1374
|
})
|
|
1110
1375
|
}
|
|
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
1376
|
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1377
|
+
// Check if the tool call is complete
|
|
1378
|
+
// @effect-diagnostics-next-line tryCatchInEffectGen:off
|
|
1379
|
+
try {
|
|
1380
|
+
const params = Tool.unsafeSecureJsonParse(activeToolCall.params)
|
|
1126
1381
|
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1382
|
+
parts.push({
|
|
1383
|
+
type: "tool-params-end",
|
|
1384
|
+
id: activeToolCall.id
|
|
1385
|
+
})
|
|
1131
1386
|
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1387
|
+
parts.push({
|
|
1388
|
+
type: "tool-call",
|
|
1389
|
+
id: activeToolCall.id,
|
|
1390
|
+
name: activeToolCall.name,
|
|
1391
|
+
params,
|
|
1392
|
+
// Only attach reasoning_details to the first tool call to avoid
|
|
1393
|
+
// duplicating thinking blocks for parallel tool calls (Claude)
|
|
1394
|
+
metadata: reasoningDetailsAttachedToToolCall ? undefined : {
|
|
1395
|
+
openrouter: { reasoningDetails: accumulatedReasoningDetails }
|
|
1396
|
+
}
|
|
1397
|
+
})
|
|
1143
1398
|
|
|
1144
|
-
|
|
1399
|
+
reasoningDetailsAttachedToToolCall = true
|
|
1145
1400
|
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1401
|
+
// Increment the total tool calls emitted by the stream and
|
|
1402
|
+
// remove the active tool call
|
|
1403
|
+
totalToolCalls += 1
|
|
1404
|
+
delete activeToolCalls[toolCall.index]
|
|
1405
|
+
} catch {
|
|
1406
|
+
// Tool call incomplete, continue parsing
|
|
1407
|
+
continue
|
|
1408
|
+
}
|
|
1153
1409
|
}
|
|
1154
1410
|
}
|
|
1155
|
-
}
|
|
1156
1411
|
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1412
|
+
const images = delta.images
|
|
1413
|
+
if (Predicate.isNotNullish(images)) {
|
|
1414
|
+
for (const image of images) {
|
|
1415
|
+
parts.push({
|
|
1416
|
+
type: "file",
|
|
1417
|
+
mediaType: getMediaType(image.image_url.url, "image/jpeg"),
|
|
1418
|
+
data: getBase64FromDataUrl(image.image_url.url)
|
|
1419
|
+
})
|
|
1420
|
+
}
|
|
1165
1421
|
}
|
|
1166
1422
|
}
|
|
1167
1423
|
|