@effect/ai-openai-compat 4.0.0-beta.7 → 4.0.0-beta.71

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.
Files changed (38) hide show
  1. package/dist/OpenAiClient.d.ts +250 -51
  2. package/dist/OpenAiClient.d.ts.map +1 -1
  3. package/dist/OpenAiClient.js +108 -9
  4. package/dist/OpenAiClient.js.map +1 -1
  5. package/dist/OpenAiConfig.d.ts +83 -10
  6. package/dist/OpenAiConfig.d.ts.map +1 -1
  7. package/dist/OpenAiConfig.js +51 -7
  8. package/dist/OpenAiConfig.js.map +1 -1
  9. package/dist/OpenAiEmbeddingModel.d.ts +214 -0
  10. package/dist/OpenAiEmbeddingModel.d.ts.map +1 -0
  11. package/dist/OpenAiEmbeddingModel.js +218 -0
  12. package/dist/OpenAiEmbeddingModel.js.map +1 -0
  13. package/dist/OpenAiError.d.ts +109 -35
  14. package/dist/OpenAiError.d.ts.map +1 -1
  15. package/dist/OpenAiError.js +14 -1
  16. package/dist/OpenAiLanguageModel.d.ts +326 -18
  17. package/dist/OpenAiLanguageModel.d.ts.map +1 -1
  18. package/dist/OpenAiLanguageModel.js +126 -25
  19. package/dist/OpenAiLanguageModel.js.map +1 -1
  20. package/dist/OpenAiTelemetry.d.ts +72 -22
  21. package/dist/OpenAiTelemetry.d.ts.map +1 -1
  22. package/dist/OpenAiTelemetry.js +47 -8
  23. package/dist/OpenAiTelemetry.js.map +1 -1
  24. package/dist/index.d.ts +10 -17
  25. package/dist/index.d.ts.map +1 -1
  26. package/dist/index.js +10 -17
  27. package/dist/index.js.map +1 -1
  28. package/dist/internal/errors.js +4 -4
  29. package/dist/internal/errors.js.map +1 -1
  30. package/package.json +3 -3
  31. package/src/OpenAiClient.ts +283 -49
  32. package/src/OpenAiConfig.ts +84 -11
  33. package/src/OpenAiEmbeddingModel.ts +360 -0
  34. package/src/OpenAiError.ts +111 -35
  35. package/src/OpenAiLanguageModel.ts +409 -40
  36. package/src/OpenAiTelemetry.ts +103 -27
  37. package/src/index.ts +11 -17
  38. package/src/internal/errors.ts +4 -4
@@ -1,21 +1,48 @@
1
1
  /**
2
- * OpenAI Language Model implementation.
2
+ * The `OpenAiLanguageModel` module adapts OpenAI-compatible chat-completions
3
+ * providers to the shared Effect AI `LanguageModel` interface. It translates
4
+ * provider-neutral prompts, tools, structured output schemas, and streaming
5
+ * responses into the request and response shapes used by `OpenAiClient`.
3
6
  *
4
- * Provides a LanguageModel implementation for OpenAI's chat completions API,
5
- * supporting text generation, structured output, tool calling, and streaming.
7
+ * Use this module when an application wants to talk to OpenAI-compatible
8
+ * endpoints through Effect AI abstractions rather than constructing provider
9
+ * payloads directly. The exported constructors build a language model service
10
+ * from a model id, while `Config` and {@link withConfigOverride} provide scoped
11
+ * defaults for request fields such as temperature, reasoning options, text
12
+ * format, and provider-specific file handling.
6
13
  *
7
- * @since 1.0.0
14
+ * **Common tasks**
15
+ *
16
+ * - Create a model descriptor with {@link model}
17
+ * - Build or provide the `LanguageModel` service with {@link make} or
18
+ * {@link layer}
19
+ * - Scope request defaults with {@link Config} and {@link withConfigOverride}
20
+ * - Send tool calls, structured output schemas, images, files, and reasoning
21
+ * metadata through the provider-neutral Effect AI prompt types
22
+ *
23
+ * **Gotchas**
24
+ *
25
+ * - The module requires an `OpenAiClient` service; configure authentication,
26
+ * base URL, and HTTP behavior through that client layer.
27
+ * - Compatibility depends on the provider supporting the OpenAI request fields
28
+ * being used. Optional capabilities such as strict JSON schemas, reasoning
29
+ * metadata, and tool status fields may vary across providers.
30
+ * - `fileIdPrefixes` tells the prompt conversion which file references are
31
+ * provider file IDs instead of base64 file contents.
32
+ *
33
+ * @since 4.0.0
8
34
  */
35
+ import * as Context from "effect/Context"
9
36
  import * as DateTime from "effect/DateTime"
10
37
  import * as Effect from "effect/Effect"
11
38
  import * as Encoding from "effect/Encoding"
12
39
  import { dual } from "effect/Function"
13
40
  import * as Layer from "effect/Layer"
41
+ import * as Option from "effect/Option"
14
42
  import * as Predicate from "effect/Predicate"
15
43
  import * as Redactable from "effect/Redactable"
16
44
  import type * as Schema from "effect/Schema"
17
45
  import * as AST from "effect/SchemaAST"
18
- import * as ServiceMap from "effect/ServiceMap"
19
46
  import * as Stream from "effect/Stream"
20
47
  import type { Span } from "effect/Tracer"
21
48
  import type { DeepMutable, Simplify } from "effect/Types"
@@ -60,10 +87,18 @@ type ImageDetail = "auto" | "low" | "high"
60
87
  /**
61
88
  * Service definition for OpenAI language model configuration.
62
89
  *
63
- * @since 1.0.0
90
+ * **When to use**
91
+ *
92
+ * Use as the context service for OpenAI-compatible language model request
93
+ * configuration, especially when a scoped operation should override the defaults
94
+ * supplied to `model`, `make`, or `layer`.
95
+ *
96
+ * @see {@link withConfigOverride} for scoping language model request overrides
97
+ *
64
98
  * @category context
99
+ * @since 4.0.0
65
100
  */
66
- export class Config extends ServiceMap.Service<
101
+ export class Config extends Context.Service<
67
102
  Config,
68
103
  Simplify<
69
104
  & Partial<
@@ -101,6 +136,7 @@ export class Config extends ServiceMap.Service<
101
136
  * Defaults to `true`.
102
137
  */
103
138
  readonly strictJsonSchema?: boolean | undefined
139
+ readonly [x: string]: unknown
104
140
  }
105
141
  >
106
142
  >()("@effect/ai-openai-compat/OpenAiLanguageModel/Config") {}
@@ -110,7 +146,16 @@ export class Config extends ServiceMap.Service<
110
146
  // =============================================================================
111
147
 
112
148
  declare module "effect/unstable/ai/Prompt" {
149
+ /**
150
+ * OpenAI-compatible options for file prompt parts.
151
+ *
152
+ * @category request
153
+ * @since 4.0.0
154
+ */
113
155
  export interface FilePartOptions extends ProviderOptions {
156
+ /**
157
+ * Provider-specific file options for OpenAI-compatible APIs.
158
+ */
114
159
  readonly openai?: {
115
160
  /**
116
161
  * The detail level of the image to be sent to the model. One of `high`, `low`, or `auto`. Defaults to `auto`.
@@ -119,7 +164,16 @@ declare module "effect/unstable/ai/Prompt" {
119
164
  } | null
120
165
  }
121
166
 
167
+ /**
168
+ * OpenAI-compatible options for reasoning prompt parts.
169
+ *
170
+ * @category request
171
+ * @since 4.0.0
172
+ */
122
173
  export interface ReasoningPartOptions extends ProviderOptions {
174
+ /**
175
+ * Provider-specific reasoning options for OpenAI-compatible APIs.
176
+ */
123
177
  readonly openai?: {
124
178
  /**
125
179
  * The ID of the item to reference.
@@ -134,40 +188,67 @@ declare module "effect/unstable/ai/Prompt" {
134
188
  } | null
135
189
  }
136
190
 
191
+ /**
192
+ * OpenAI-compatible options for assistant tool-call prompt parts.
193
+ *
194
+ * @category request
195
+ * @since 4.0.0
196
+ */
137
197
  export interface ToolCallPartOptions extends ProviderOptions {
198
+ /**
199
+ * Provider-specific tool-call options for OpenAI-compatible APIs.
200
+ */
138
201
  readonly openai?: {
139
202
  /**
140
203
  * The ID of the item to reference.
141
204
  */
142
205
  readonly itemId?: string | null
143
206
  /**
144
- * The status of item.
207
+ * The status to send for the tool-call item.
145
208
  */
146
209
  readonly status?: MessageStatus | null
147
210
  } | null
148
211
  }
149
212
 
213
+ /**
214
+ * OpenAI-compatible options for tool-result prompt parts.
215
+ *
216
+ * @category request
217
+ * @since 4.0.0
218
+ */
150
219
  export interface ToolResultPartOptions extends ProviderOptions {
220
+ /**
221
+ * Provider-specific tool-result options for OpenAI-compatible APIs.
222
+ */
151
223
  readonly openai?: {
152
224
  /**
153
225
  * The ID of the item to reference.
154
226
  */
155
227
  readonly itemId?: string | null
156
228
  /**
157
- * The status of item.
229
+ * The status to send for the tool-result item.
158
230
  */
159
231
  readonly status?: MessageStatus | null
160
232
  } | null
161
233
  }
162
234
 
235
+ /**
236
+ * OpenAI-compatible options for text prompt parts.
237
+ *
238
+ * @category request
239
+ * @since 4.0.0
240
+ */
163
241
  export interface TextPartOptions extends ProviderOptions {
242
+ /**
243
+ * Provider-specific text options for OpenAI-compatible APIs.
244
+ */
164
245
  readonly openai?: {
165
246
  /**
166
247
  * The ID of the item to reference.
167
248
  */
168
249
  readonly itemId?: string | null
169
250
  /**
170
- * The status of item.
251
+ * The status to send for the text item.
171
252
  */
172
253
  readonly status?: MessageStatus | null
173
254
  /**
@@ -179,8 +260,20 @@ declare module "effect/unstable/ai/Prompt" {
179
260
  }
180
261
 
181
262
  declare module "effect/unstable/ai/Response" {
263
+ /**
264
+ * OpenAI-compatible metadata attached to a complete text response part.
265
+ *
266
+ * @category response
267
+ * @since 4.0.0
268
+ */
182
269
  export interface TextPartMetadata extends ProviderMetadata {
270
+ /**
271
+ * Provider-specific metadata returned for the text part.
272
+ */
183
273
  readonly openai?: {
274
+ /**
275
+ * The OpenAI item ID associated with the text part.
276
+ */
184
277
  readonly itemId?: string | null
185
278
  /**
186
279
  * If the model emits a refusal content part, the refusal explanation
@@ -189,7 +282,7 @@ declare module "effect/unstable/ai/Response" {
189
282
  */
190
283
  readonly refusal?: string | null
191
284
  /**
192
- * The status of item.
285
+ * The status returned for the text item.
193
286
  */
194
287
  readonly status?: MessageStatus | null
195
288
  /**
@@ -199,55 +292,163 @@ declare module "effect/unstable/ai/Response" {
199
292
  }
200
293
  }
201
294
 
295
+ /**
296
+ * OpenAI-compatible metadata emitted when a streamed text part starts.
297
+ *
298
+ * @category response
299
+ * @since 4.0.0
300
+ */
202
301
  export interface TextStartPartMetadata extends ProviderMetadata {
302
+ /**
303
+ * Provider-specific metadata returned for the streamed text start.
304
+ */
203
305
  readonly openai?: {
306
+ /**
307
+ * The OpenAI item ID associated with the streamed text part.
308
+ */
204
309
  readonly itemId?: string | null
205
310
  } | null
206
311
  }
207
312
 
313
+ /**
314
+ * OpenAI-compatible metadata emitted when a streamed text part ends.
315
+ *
316
+ * @category response
317
+ * @since 4.0.0
318
+ */
208
319
  export interface TextEndPartMetadata extends ProviderMetadata {
320
+ /**
321
+ * Provider-specific metadata returned for the streamed text end.
322
+ */
209
323
  readonly openai?: {
324
+ /**
325
+ * The OpenAI item ID associated with the streamed text part.
326
+ */
210
327
  readonly itemId?: string | null
328
+ /**
329
+ * The annotations collected for the completed streamed text part.
330
+ */
211
331
  readonly annotations?: ReadonlyArray<Annotation> | null
212
332
  } | null
213
333
  }
214
334
 
335
+ /**
336
+ * OpenAI-compatible metadata attached to a complete reasoning response part.
337
+ *
338
+ * @category response
339
+ * @since 4.0.0
340
+ */
215
341
  export interface ReasoningPartMetadata extends ProviderMetadata {
342
+ /**
343
+ * Provider-specific metadata returned for the reasoning part.
344
+ */
216
345
  readonly openai?: {
346
+ /**
347
+ * The OpenAI item ID associated with the reasoning part.
348
+ */
217
349
  readonly itemId?: string | null
350
+ /**
351
+ * Encrypted reasoning content that can be sent back in later requests.
352
+ */
218
353
  readonly encryptedContent?: string | null
219
354
  } | null
220
355
  }
221
356
 
357
+ /**
358
+ * OpenAI-compatible metadata emitted when a streamed reasoning part starts.
359
+ *
360
+ * @category response
361
+ * @since 4.0.0
362
+ */
222
363
  export interface ReasoningStartPartMetadata extends ProviderMetadata {
364
+ /**
365
+ * Provider-specific metadata returned for the streamed reasoning start.
366
+ */
223
367
  readonly openai?: {
368
+ /**
369
+ * The OpenAI item ID associated with the reasoning part.
370
+ */
224
371
  readonly itemId?: string | null
372
+ /**
373
+ * Encrypted reasoning content that can be sent back in later requests.
374
+ */
225
375
  readonly encryptedContent?: string | null
226
376
  } | null
227
377
  }
228
378
 
379
+ /**
380
+ * OpenAI-compatible metadata emitted for a streamed reasoning delta.
381
+ *
382
+ * @category response
383
+ * @since 4.0.0
384
+ */
229
385
  export interface ReasoningDeltaPartMetadata extends ProviderMetadata {
386
+ /**
387
+ * Provider-specific metadata returned for the streamed reasoning delta.
388
+ */
230
389
  readonly openai?: {
390
+ /**
391
+ * The OpenAI item ID associated with the reasoning part.
392
+ */
231
393
  readonly itemId?: string | null
232
394
  } | null
233
395
  }
234
396
 
397
+ /**
398
+ * OpenAI-compatible metadata emitted when a streamed reasoning part ends.
399
+ *
400
+ * @category response
401
+ * @since 4.0.0
402
+ */
235
403
  export interface ReasoningEndPartMetadata extends ProviderMetadata {
404
+ /**
405
+ * Provider-specific metadata returned for the streamed reasoning end.
406
+ */
236
407
  readonly openai?: {
408
+ /**
409
+ * The OpenAI item ID associated with the reasoning part.
410
+ */
237
411
  readonly itemId?: string | null
412
+ /**
413
+ * Encrypted reasoning content that can be sent back in later requests.
414
+ */
238
415
  readonly encryptedContent?: string
239
416
  } | null
240
417
  }
241
418
 
419
+ /**
420
+ * OpenAI-compatible metadata attached to tool-call response parts.
421
+ *
422
+ * @category response
423
+ * @since 4.0.0
424
+ */
242
425
  export interface ToolCallPartMetadata extends ProviderMetadata {
426
+ /**
427
+ * Provider-specific metadata returned for the tool call.
428
+ */
243
429
  readonly openai?: {
430
+ /**
431
+ * The OpenAI item ID associated with the tool call.
432
+ */
244
433
  readonly itemId?: string | null
245
434
  } | null
246
435
  }
247
436
 
437
+ /**
438
+ * OpenAI-compatible metadata attached to document source citations.
439
+ *
440
+ * @category response
441
+ * @since 4.0.0
442
+ */
248
443
  export interface DocumentSourcePartMetadata extends ProviderMetadata {
444
+ /**
445
+ * Provider-specific citation metadata for OpenAI-compatible APIs.
446
+ */
249
447
  readonly openai?:
250
448
  | {
449
+ /**
450
+ * Identifies a citation to an uploaded file.
451
+ */
251
452
  readonly type: "file_citation"
252
453
  /**
253
454
  * The index of the file in the list of files.
@@ -259,6 +460,9 @@ declare module "effect/unstable/ai/Response" {
259
460
  readonly fileId: string
260
461
  }
261
462
  | {
463
+ /**
464
+ * Identifies a citation to a generated file path.
465
+ */
262
466
  readonly type: "file_path"
263
467
  /**
264
468
  * The index of the file in the list of files.
@@ -270,6 +474,9 @@ declare module "effect/unstable/ai/Response" {
270
474
  readonly fileId: string
271
475
  }
272
476
  | {
477
+ /**
478
+ * Identifies a citation to a file inside a container.
479
+ */
273
480
  readonly type: "container_file_citation"
274
481
  /**
275
482
  * The ID of the file.
@@ -283,8 +490,20 @@ declare module "effect/unstable/ai/Response" {
283
490
  | null
284
491
  }
285
492
 
493
+ /**
494
+ * OpenAI-compatible metadata attached to URL source citations.
495
+ *
496
+ * @category response
497
+ * @since 4.0.0
498
+ */
286
499
  export interface UrlSourcePartMetadata extends ProviderMetadata {
500
+ /**
501
+ * Provider-specific URL citation metadata for OpenAI-compatible APIs.
502
+ */
287
503
  readonly openai?: {
504
+ /**
505
+ * Identifies a citation to a URL.
506
+ */
288
507
  readonly type: "url_citation"
289
508
  /**
290
509
  * The index of the first character of the URL citation in the message.
@@ -297,8 +516,20 @@ declare module "effect/unstable/ai/Response" {
297
516
  } | null
298
517
  }
299
518
 
519
+ /**
520
+ * OpenAI-compatible metadata attached to finish response parts.
521
+ *
522
+ * @category response
523
+ * @since 4.0.0
524
+ */
300
525
  export interface FinishPartMetadata extends ProviderMetadata {
526
+ /**
527
+ * Provider-specific metadata returned when generation finishes.
528
+ */
301
529
  readonly openai?: {
530
+ /**
531
+ * The service tier reported by the OpenAI-compatible provider.
532
+ */
302
533
  readonly serviceTier?: "default" | "auto" | "flex" | "scale" | "priority" | null
303
534
  } | null
304
535
  }
@@ -309,31 +540,56 @@ declare module "effect/unstable/ai/Response" {
309
540
  // =============================================================================
310
541
 
311
542
  /**
312
- * @since 1.0.0
543
+ * Creates an OpenAI-compatible model descriptor that can be provided with `Effect.provide`.
544
+ *
545
+ * **When to use**
546
+ *
547
+ * Use when you want an OpenAI-compatible language model value that carries
548
+ * provider and model metadata and can be supplied directly to an Effect program.
549
+ *
550
+ * @see {@link layer} for creating a `LanguageModel.LanguageModel` layer directly
551
+ * @see {@link make} for constructing the language model service effectfully
552
+ *
313
553
  * @category constructors
554
+ * @since 4.0.0
314
555
  */
315
556
  export const model = (
316
557
  model: string,
317
558
  config?: Omit<typeof Config.Service, "model">
318
559
  ): AiModel.Model<"openai", LanguageModel.LanguageModel, OpenAiClient> =>
319
- AiModel.make("openai", layer({ model, config }))
560
+ AiModel.make("openai", model, layer({ model, config }))
320
561
 
321
562
  // TODO
322
563
  // /**
323
- // * @since 1.0.0
564
+ // * @since 4.0.0
324
565
  // * @category constructors
325
566
  // */
326
567
  // export const modelWithTokenizer = (
327
568
  // model: string,
328
569
  // config?: Omit<typeof Config.Service, "model">
329
570
  // ): AiModel.Model<"openai", LanguageModel.LanguageModel | Tokenizer.Tokenizer, OpenAiClient> =>
330
- // AiModel.make("openai", layerWithTokenizer({ model, config }))
571
+ // AiModel.make("openai", model, layerWithTokenizer({ model, config }))
331
572
 
332
573
  /**
333
- * Creates an OpenAI language model service.
574
+ * Creates an OpenAI-compatible `LanguageModel` service from a model identifier and optional request defaults.
575
+ *
576
+ * **When to use**
577
+ *
578
+ * Use when an Effect needs to construct a `LanguageModel.Service` value backed
579
+ * by `OpenAiClient`.
580
+ *
581
+ * **Details**
582
+ *
583
+ * The returned effect requires `OpenAiClient`. Request defaults from the
584
+ * `config` option are merged with any `Config` service in the context, with
585
+ * context values taking precedence. The service supports both `generateText` and
586
+ * `streamText`.
587
+ *
588
+ * @see {@link layer} for providing the service as a `Layer`
589
+ * @see {@link model} for creating a model descriptor for `AiModel.provide`
334
590
  *
335
- * @since 1.0.0
336
591
  * @category constructors
592
+ * @since 4.0.0
337
593
  */
338
594
  export const make = Effect.fnUntraced(function*({ model, config: providerConfig }: {
339
595
  readonly model: string
@@ -342,7 +598,7 @@ export const make = Effect.fnUntraced(function*({ model, config: providerConfig
342
598
  const client = yield* OpenAiClient
343
599
 
344
600
  const makeConfig = Effect.gen(function*() {
345
- const services = yield* Effect.services<never>()
601
+ const services = yield* Effect.context<never>()
346
602
  return { model, ...providerConfig, ...services.mapUnsafe.get(Config.key) }
347
603
  })
348
604
 
@@ -370,8 +626,9 @@ export const make = Effect.fnUntraced(function*({ model, config: providerConfig
370
626
  config,
371
627
  options
372
628
  })
629
+ const { fileIdPrefixes: _fip, strictJsonSchema: _sjs, ...apiConfig } = config
373
630
  const request: CreateResponse = {
374
- ...config,
631
+ ...apiConfig,
375
632
  input: messages,
376
633
  include: include.size > 0 ? Array.from(include) : null,
377
634
  text: {
@@ -386,6 +643,7 @@ export const make = Effect.fnUntraced(function*({ model, config: providerConfig
386
643
  )
387
644
 
388
645
  return yield* LanguageModel.make({
646
+ codecTransformer: toCodecOpenAI,
389
647
  generateText: Effect.fnUntraced(
390
648
  function*(options) {
391
649
  const config = yield* makeConfig
@@ -423,17 +681,23 @@ export const make = Effect.fnUntraced(function*({ model, config: providerConfig
423
681
  })
424
682
  )
425
683
  )
426
- }).pipe(Effect.provideService(
427
- LanguageModel.CurrentCodecTransformer,
428
- toCodecOpenAI
429
- ))
684
+ })
430
685
  })
431
686
 
432
687
  /**
433
- * Creates a layer for the OpenAI language model.
688
+ * Creates a layer for the OpenAI-compatible language model.
689
+ *
690
+ * **When to use**
691
+ *
692
+ * Use when composing application layers and you want OpenAI-compatible APIs to
693
+ * satisfy `LanguageModel.LanguageModel` while supplying `OpenAiClient` from
694
+ * another layer.
695
+ *
696
+ * @see {@link make} for constructing the language model service effectfully
697
+ * @see {@link model} for creating an AI model descriptor
434
698
  *
435
- * @since 1.0.0
436
699
  * @category layers
700
+ * @since 4.0.0
437
701
  */
438
702
  export const layer = (options: {
439
703
  readonly model: string
@@ -442,39 +706,99 @@ export const layer = (options: {
442
706
  Layer.effect(LanguageModel.LanguageModel, make(options))
443
707
 
444
708
  /**
445
- * Provides config overrides for OpenAI language model operations.
709
+ * Provides scoped config overrides for OpenAI-compatible language model operations.
710
+ *
711
+ * **When to use**
712
+ *
713
+ * Use to override request configuration for a single language model effect
714
+ * without changing the defaults supplied to `model`, `make`, or `layer`.
715
+ *
716
+ * **Details**
717
+ *
718
+ * Existing `Config` values from the Effect context are merged with `overrides`,
719
+ * and the override values take precedence.
720
+ *
721
+ * @see {@link Config} for the configuration shape
446
722
  *
447
- * @since 1.0.0
448
723
  * @category configuration
724
+ * @since 4.0.0
449
725
  */
450
726
  export const withConfigOverride: {
451
727
  /**
452
- * Provides config overrides for OpenAI language model operations.
728
+ * Provides scoped config overrides for OpenAI-compatible language model operations.
729
+ *
730
+ * **When to use**
731
+ *
732
+ * Use to override request configuration for a single language model effect
733
+ * without changing the defaults supplied to `model`, `make`, or `layer`.
734
+ *
735
+ * **Details**
736
+ *
737
+ * Existing `Config` values from the Effect context are merged with `overrides`,
738
+ * and the override values take precedence.
739
+ *
740
+ * @see {@link Config} for the configuration shape
453
741
  *
454
- * @since 1.0.0
455
742
  * @category configuration
743
+ * @since 4.0.0
456
744
  */
457
745
  (overrides: typeof Config.Service): <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, E, Exclude<R, Config>>
458
746
  /**
459
- * Provides config overrides for OpenAI language model operations.
747
+ * Provides scoped config overrides for OpenAI-compatible language model operations.
748
+ *
749
+ * **When to use**
750
+ *
751
+ * Use to override request configuration for a single language model effect
752
+ * without changing the defaults supplied to `model`, `make`, or `layer`.
753
+ *
754
+ * **Details**
755
+ *
756
+ * Existing `Config` values from the Effect context are merged with `overrides`,
757
+ * and the override values take precedence.
758
+ *
759
+ * @see {@link Config} for the configuration shape
460
760
  *
461
- * @since 1.0.0
462
761
  * @category configuration
762
+ * @since 4.0.0
463
763
  */
464
764
  <A, E, R>(self: Effect.Effect<A, E, R>, overrides: typeof Config.Service): Effect.Effect<A, E, Exclude<R, Config>>
465
765
  } = dual<
466
766
  /**
467
- * Provides config overrides for OpenAI language model operations.
767
+ * Provides scoped config overrides for OpenAI-compatible language model operations.
768
+ *
769
+ * **When to use**
770
+ *
771
+ * Use to override request configuration for a single language model effect
772
+ * without changing the defaults supplied to `model`, `make`, or `layer`.
773
+ *
774
+ * **Details**
775
+ *
776
+ * Existing `Config` values from the Effect context are merged with `overrides`,
777
+ * and the override values take precedence.
778
+ *
779
+ * @see {@link Config} for the configuration shape
468
780
  *
469
- * @since 1.0.0
470
781
  * @category configuration
782
+ * @since 4.0.0
471
783
  */
472
784
  (overrides: typeof Config.Service) => <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, E, Exclude<R, Config>>,
473
785
  /**
474
- * Provides config overrides for OpenAI language model operations.
786
+ * Provides scoped config overrides for OpenAI-compatible language model operations.
787
+ *
788
+ * **When to use**
789
+ *
790
+ * Use to override request configuration for a single language model effect
791
+ * without changing the defaults supplied to `model`, `make`, or `layer`.
792
+ *
793
+ * **Details**
794
+ *
795
+ * Existing `Config` values from the Effect context are merged with `overrides`,
796
+ * and the override values take precedence.
797
+ *
798
+ * @see {@link Config} for the configuration shape
475
799
  *
476
- * @since 1.0.0
477
800
  * @category configuration
801
+ * @since 4.0.0
478
802
  */
479
803
  <A, E, R>(self: Effect.Effect<A, E, R>, overrides: typeof Config.Service) => Effect.Effect<A, E, Exclude<R, Config>>
480
804
  >(2, (self, overrides) =>
@@ -782,7 +1106,7 @@ const buildHttpRequestDetails = (
782
1106
  method: request.method,
783
1107
  url: request.url,
784
1108
  urlParams: Array.from(request.urlParams),
785
- hash: request.hash,
1109
+ hash: Option.getOrUndefined(request.hash),
786
1110
  headers: Redactable.redact(request.headers) as Record<string, string>
787
1111
  })
788
1112
 
@@ -1002,11 +1326,13 @@ const makeStreamResponse = Effect.fnUntraced(
1002
1326
  hasToolCalls = hasToolCalls || choice.delta.tool_calls.length > 0
1003
1327
  choice.delta.tool_calls.forEach((deltaTool, indexInChunk) => {
1004
1328
  const toolIndex = deltaTool.index ?? indexInChunk
1005
- const toolId = deltaTool.id ?? `${event.id}_tool_${toolIndex}`
1329
+ const activeToolCall = activeToolCalls[toolIndex]
1330
+ const toolId = activeToolCall?.id ?? deltaTool.id ?? `${event.id}_tool_${toolIndex}`
1006
1331
  const providerToolName = deltaTool.function?.name
1007
- const toolName = toolNameMapper.getCustomName(providerToolName ?? "unknown_tool")
1332
+ const toolName = providerToolName !== undefined
1333
+ ? toolNameMapper.getCustomName(providerToolName)
1334
+ : activeToolCall?.name ?? toolNameMapper.getCustomName("unknown_tool")
1008
1335
  const argumentsDelta = deltaTool.function?.arguments ?? ""
1009
- const activeToolCall = activeToolCalls[toolIndex]
1010
1336
 
1011
1337
  if (Predicate.isUndefined(activeToolCall)) {
1012
1338
  activeToolCalls[toolIndex] = {
@@ -1169,7 +1495,7 @@ const prepareTools = Effect.fnUntraced(function*<Tools extends ReadonlyArray<Too
1169
1495
 
1170
1496
  // Convert the tools in the toolkit to the provider-defined format
1171
1497
  for (const tool of allowedTools) {
1172
- if (Tool.isUserDefined(tool)) {
1498
+ if (Tool.isUserDefined(tool) || Tool.isDynamic(tool)) {
1173
1499
  const strict = Tool.getStrictMode(tool) ?? config.strictJsonSchema ?? true
1174
1500
  const parameters = yield* tryToolJsonSchema(tool, "prepareTools")
1175
1501
  tools.push({
@@ -1220,6 +1546,7 @@ const toChatCompletionsRequest = (payload: CreateResponse): CreateResponseReques
1220
1546
  const toolChoice = toChatToolChoice(payload.tool_choice)
1221
1547
 
1222
1548
  return {
1549
+ ...extractCustomRequestProperties(payload),
1223
1550
  model: payload.model ?? "",
1224
1551
  messages: messages.length > 0 ? messages : [{ role: "user", content: "" }],
1225
1552
  ...(payload.temperature !== undefined ? { temperature: payload.temperature } : undefined),
@@ -1231,12 +1558,54 @@ const toChatCompletionsRequest = (payload: CreateResponse): CreateResponseReques
1231
1558
  ? { parallel_tool_calls: payload.parallel_tool_calls }
1232
1559
  : undefined),
1233
1560
  ...(payload.service_tier !== undefined ? { service_tier: payload.service_tier } : undefined),
1561
+ ...(payload.reasoning !== undefined ? { reasoning: payload.reasoning } : undefined),
1234
1562
  ...(responseFormat !== undefined ? { response_format: responseFormat } : undefined),
1235
1563
  ...(tools.length > 0 ? { tools } : undefined),
1236
1564
  ...(toolChoice !== undefined ? { tool_choice: toolChoice } : undefined)
1237
1565
  }
1238
1566
  }
1239
1567
 
1568
+ const createResponseKnownProperties = new Set<string>([
1569
+ "metadata",
1570
+ "top_logprobs",
1571
+ "temperature",
1572
+ "top_p",
1573
+ "user",
1574
+ "safety_identifier",
1575
+ "prompt_cache_key",
1576
+ "service_tier",
1577
+ "prompt_cache_retention",
1578
+ "previous_response_id",
1579
+ "model",
1580
+ "reasoning",
1581
+ "background",
1582
+ "max_output_tokens",
1583
+ "max_tool_calls",
1584
+ "text",
1585
+ "tools",
1586
+ "tool_choice",
1587
+ "truncation",
1588
+ "input",
1589
+ "include",
1590
+ "parallel_tool_calls",
1591
+ "store",
1592
+ "instructions",
1593
+ "stream",
1594
+ "conversation",
1595
+ "modalities",
1596
+ "seed"
1597
+ ])
1598
+
1599
+ const extractCustomRequestProperties = (payload: CreateResponse): Record<string, unknown> => {
1600
+ const customProperties: Record<string, unknown> = {}
1601
+ for (const [key, value] of Object.entries(payload)) {
1602
+ if (!createResponseKnownProperties.has(key)) {
1603
+ customProperties[key] = value
1604
+ }
1605
+ }
1606
+ return customProperties
1607
+ }
1608
+
1240
1609
  const toChatResponseFormat = (
1241
1610
  format: TextResponseFormatConfiguration | undefined
1242
1611
  ): CreateResponseRequestJson["response_format"] | undefined => {