@effect/ai-openai 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.
Files changed (58) hide show
  1. package/dist/Generated.d.ts +66734 -37723
  2. package/dist/Generated.d.ts.map +1 -1
  3. package/dist/Generated.js +1 -1
  4. package/dist/Generated.js.map +1 -1
  5. package/dist/OpenAiClient.d.ts +81 -25
  6. package/dist/OpenAiClient.d.ts.map +1 -1
  7. package/dist/OpenAiClient.js +220 -39
  8. package/dist/OpenAiClient.js.map +1 -1
  9. package/dist/OpenAiClientGenerated.d.ts +91 -0
  10. package/dist/OpenAiClientGenerated.d.ts.map +1 -0
  11. package/dist/OpenAiClientGenerated.js +84 -0
  12. package/dist/OpenAiClientGenerated.js.map +1 -0
  13. package/dist/OpenAiConfig.d.ts +45 -10
  14. package/dist/OpenAiConfig.d.ts.map +1 -1
  15. package/dist/OpenAiConfig.js +31 -7
  16. package/dist/OpenAiConfig.js.map +1 -1
  17. package/dist/OpenAiEmbeddingModel.d.ts +89 -0
  18. package/dist/OpenAiEmbeddingModel.d.ts.map +1 -0
  19. package/dist/OpenAiEmbeddingModel.js +121 -0
  20. package/dist/OpenAiEmbeddingModel.js.map +1 -0
  21. package/dist/OpenAiError.d.ts +168 -35
  22. package/dist/OpenAiError.d.ts.map +1 -1
  23. package/dist/OpenAiError.js +1 -1
  24. package/dist/OpenAiLanguageModel.d.ts +250 -57
  25. package/dist/OpenAiLanguageModel.d.ts.map +1 -1
  26. package/dist/OpenAiLanguageModel.js +311 -160
  27. package/dist/OpenAiLanguageModel.js.map +1 -1
  28. package/dist/OpenAiSchema.d.ts +2029 -0
  29. package/dist/OpenAiSchema.d.ts.map +1 -0
  30. package/dist/OpenAiSchema.js +591 -0
  31. package/dist/OpenAiSchema.js.map +1 -0
  32. package/dist/OpenAiTelemetry.d.ts +31 -18
  33. package/dist/OpenAiTelemetry.d.ts.map +1 -1
  34. package/dist/OpenAiTelemetry.js +6 -4
  35. package/dist/OpenAiTelemetry.js.map +1 -1
  36. package/dist/OpenAiTool.d.ts +56 -67
  37. package/dist/OpenAiTool.d.ts.map +1 -1
  38. package/dist/OpenAiTool.js +33 -44
  39. package/dist/OpenAiTool.js.map +1 -1
  40. package/dist/index.d.ts +42 -8
  41. package/dist/index.d.ts.map +1 -1
  42. package/dist/index.js +42 -8
  43. package/dist/index.js.map +1 -1
  44. package/dist/internal/errors.js +4 -4
  45. package/dist/internal/errors.js.map +1 -1
  46. package/package.json +3 -3
  47. package/src/Generated.ts +9858 -5044
  48. package/src/OpenAiClient.ts +396 -90
  49. package/src/OpenAiClientGenerated.ts +202 -0
  50. package/src/OpenAiConfig.ts +46 -11
  51. package/src/OpenAiEmbeddingModel.ts +207 -0
  52. package/src/OpenAiError.ts +170 -35
  53. package/src/OpenAiLanguageModel.ts +633 -157
  54. package/src/OpenAiSchema.ts +984 -0
  55. package/src/OpenAiTelemetry.ts +32 -19
  56. package/src/OpenAiTool.ts +34 -45
  57. package/src/index.ts +45 -8
  58. package/src/internal/errors.ts +6 -4
@@ -4,25 +4,27 @@
4
4
  * Provides a LanguageModel implementation for OpenAI's responses API,
5
5
  * supporting text generation, structured output, tool calling, and streaming.
6
6
  *
7
- * @since 1.0.0
7
+ * @since 4.0.0
8
8
  */
9
+ import * as Context from "effect/Context"
9
10
  import * as DateTime from "effect/DateTime"
10
11
  import * as Effect from "effect/Effect"
11
12
  import * as Encoding from "effect/Encoding"
12
13
  import { dual } from "effect/Function"
13
14
  import * as Layer from "effect/Layer"
15
+ import * as Option from "effect/Option"
14
16
  import * as Predicate from "effect/Predicate"
15
17
  import * as Redactable from "effect/Redactable"
16
18
  import * as Schema from "effect/Schema"
17
19
  import * as AST from "effect/SchemaAST"
18
- import * as ServiceMap from "effect/ServiceMap"
19
20
  import * as Stream from "effect/Stream"
20
21
  import type { Span } from "effect/Tracer"
21
- import type { DeepMutable, Simplify } from "effect/Types"
22
+ import type { DeepMutable, Mutable, Simplify } from "effect/Types"
22
23
  import * as AiError from "effect/unstable/ai/AiError"
23
24
  import * as IdGenerator from "effect/unstable/ai/IdGenerator"
24
25
  import * as LanguageModel from "effect/unstable/ai/LanguageModel"
25
26
  import * as AiModel from "effect/unstable/ai/Model"
27
+ import { toCodecOpenAI } from "effect/unstable/ai/OpenAiStructuredOutput"
26
28
  import type * as Prompt from "effect/unstable/ai/Prompt"
27
29
  import type * as Response from "effect/unstable/ai/Response"
28
30
  import * as Tool from "effect/unstable/ai/Tool"
@@ -31,6 +33,7 @@ import type * as HttpClientResponse from "effect/unstable/http/HttpClientRespons
31
33
  import * as Generated from "./Generated.ts"
32
34
  import * as InternalUtilities from "./internal/utilities.ts"
33
35
  import { OpenAiClient } from "./OpenAiClient.ts"
36
+ import type * as OpenAiSchema from "./OpenAiSchema.ts"
34
37
  import { addGenAIAnnotations } from "./OpenAiTelemetry.ts"
35
38
  import type * as OpenAiTool from "./OpenAiTool.ts"
36
39
 
@@ -38,8 +41,10 @@ const ResponseModelIds = Generated.ModelIdsResponses.members[1]
38
41
  const SharedModelIds = Generated.ModelIdsShared.members[1]
39
42
 
40
43
  /**
41
- * @since 1.0.0
44
+ * OpenAI model identifiers supported by the Responses API language model.
45
+ *
42
46
  * @category models
47
+ * @since 4.0.0
43
48
  */
44
49
  export type Model = typeof ResponseModelIds.Encoded | typeof SharedModelIds.Encoded
45
50
 
@@ -55,15 +60,15 @@ type ImageDetail = "auto" | "low" | "high"
55
60
  /**
56
61
  * Service definition for OpenAI language model configuration.
57
62
  *
58
- * @since 1.0.0
59
63
  * @category services
64
+ * @since 4.0.0
60
65
  */
61
- export class Config extends ServiceMap.Service<
66
+ export class Config extends Context.Service<
62
67
  Config,
63
68
  Simplify<
64
69
  & Partial<
65
70
  Omit<
66
- typeof Generated.CreateResponse.Encoded,
71
+ typeof OpenAiSchema.CreateResponse.Encoded,
67
72
  "input" | "tools" | "tool_choice" | "stream" | "text"
68
73
  >
69
74
  >
@@ -105,7 +110,16 @@ export class Config extends ServiceMap.Service<
105
110
  // =============================================================================
106
111
 
107
112
  declare module "effect/unstable/ai/Prompt" {
113
+ /**
114
+ * OpenAI-specific options for file prompt parts.
115
+ *
116
+ * @category request
117
+ * @since 4.0.0
118
+ */
108
119
  export interface FilePartOptions extends ProviderOptions {
120
+ /**
121
+ * Provider-specific file options for the OpenAI Responses API.
122
+ */
109
123
  readonly openai?: {
110
124
  /**
111
125
  * The detail level of the image to be sent to the model. One of `high`, `low`, or `auto`. Defaults to `auto`.
@@ -114,7 +128,16 @@ declare module "effect/unstable/ai/Prompt" {
114
128
  } | null
115
129
  }
116
130
 
131
+ /**
132
+ * OpenAI-specific options for reasoning prompt parts.
133
+ *
134
+ * @category request
135
+ * @since 4.0.0
136
+ */
117
137
  export interface ReasoningPartOptions extends ProviderOptions {
138
+ /**
139
+ * Provider-specific reasoning options for the OpenAI Responses API.
140
+ */
118
141
  readonly openai?: {
119
142
  /**
120
143
  * The ID of the item to reference.
@@ -129,7 +152,16 @@ declare module "effect/unstable/ai/Prompt" {
129
152
  } | null
130
153
  }
131
154
 
155
+ /**
156
+ * OpenAI-specific options for assistant tool-call prompt parts.
157
+ *
158
+ * @category request
159
+ * @since 4.0.0
160
+ */
132
161
  export interface ToolCallPartOptions extends ProviderOptions {
162
+ /**
163
+ * Provider-specific tool-call options for the OpenAI Responses API.
164
+ */
133
165
  readonly openai?: {
134
166
  /**
135
167
  * The ID of the item to reference.
@@ -138,7 +170,7 @@ declare module "effect/unstable/ai/Prompt" {
138
170
  /**
139
171
  * The status of item.
140
172
  */
141
- readonly status?: typeof Generated.Message.Encoded["status"] | null
173
+ readonly status?: typeof OpenAiSchema.MessageStatus.Encoded | null
142
174
  /**
143
175
  * The ID of the approval request.
144
176
  */
@@ -146,7 +178,16 @@ declare module "effect/unstable/ai/Prompt" {
146
178
  } | null
147
179
  }
148
180
 
181
+ /**
182
+ * OpenAI-specific options for tool-result prompt parts.
183
+ *
184
+ * @category request
185
+ * @since 4.0.0
186
+ */
149
187
  export interface ToolResultPartOptions extends ProviderOptions {
188
+ /**
189
+ * Provider-specific tool-result options for the OpenAI Responses API.
190
+ */
150
191
  readonly openai?: {
151
192
  /**
152
193
  * The ID of the item to reference.
@@ -155,7 +196,7 @@ declare module "effect/unstable/ai/Prompt" {
155
196
  /**
156
197
  * The status of item.
157
198
  */
158
- readonly status?: typeof Generated.Message.Encoded["status"] | null
199
+ readonly status?: typeof OpenAiSchema.MessageStatus.Encoded | null
159
200
  /**
160
201
  * The ID of the approval request.
161
202
  */
@@ -163,7 +204,16 @@ declare module "effect/unstable/ai/Prompt" {
163
204
  } | null
164
205
  }
165
206
 
207
+ /**
208
+ * OpenAI-specific options for text prompt parts.
209
+ *
210
+ * @category request
211
+ * @since 4.0.0
212
+ */
166
213
  export interface TextPartOptions extends ProviderOptions {
214
+ /**
215
+ * Provider-specific text options for the OpenAI Responses API.
216
+ */
167
217
  readonly openai?: {
168
218
  /**
169
219
  * The ID of the item to reference.
@@ -172,18 +222,30 @@ declare module "effect/unstable/ai/Prompt" {
172
222
  /**
173
223
  * The status of item.
174
224
  */
175
- readonly status?: typeof Generated.Message.Encoded["status"] | null
225
+ readonly status?: typeof OpenAiSchema.MessageStatus.Encoded | null
176
226
  /**
177
227
  * A list of annotations that apply to the output text.
178
228
  */
179
- readonly annotations?: ReadonlyArray<typeof Generated.Annotation.Encoded> | null
229
+ readonly annotations?: ReadonlyArray<typeof OpenAiSchema.Annotation.Encoded> | null
180
230
  } | null
181
231
  }
182
232
  }
183
233
 
184
234
  declare module "effect/unstable/ai/Response" {
235
+ /**
236
+ * OpenAI metadata attached to a complete text response part.
237
+ *
238
+ * @category response
239
+ * @since 4.0.0
240
+ */
185
241
  export interface TextPartMetadata extends ProviderMetadata {
242
+ /**
243
+ * Provider-specific metadata returned for the text part.
244
+ */
186
245
  readonly openai?: {
246
+ /**
247
+ * The OpenAI item ID associated with the text part.
248
+ */
187
249
  readonly itemId?: string | null
188
250
  /**
189
251
  * If the model emits a refusal content part, the refusal explanation
@@ -194,63 +256,171 @@ declare module "effect/unstable/ai/Response" {
194
256
  /**
195
257
  * The status of item.
196
258
  */
197
- readonly status?: typeof Generated.Message.Encoded["status"] | null
259
+ readonly status?: typeof OpenAiSchema.MessageStatus.Encoded | null
198
260
  /**
199
261
  * The text content part annotations.
200
262
  */
201
- readonly annotations?: ReadonlyArray<typeof Generated.Annotation.Encoded> | null
263
+ readonly annotations?: ReadonlyArray<typeof OpenAiSchema.Annotation.Encoded> | null
202
264
  }
203
265
  }
204
266
 
267
+ /**
268
+ * OpenAI metadata emitted when a streamed text part starts.
269
+ *
270
+ * @category response
271
+ * @since 4.0.0
272
+ */
205
273
  export interface TextStartPartMetadata extends ProviderMetadata {
274
+ /**
275
+ * Provider-specific metadata returned for the streamed text start.
276
+ */
206
277
  readonly openai?: {
278
+ /**
279
+ * The OpenAI item ID associated with the streamed text part.
280
+ */
207
281
  readonly itemId?: string | null
208
282
  } | null
209
283
  }
210
284
 
285
+ /**
286
+ * OpenAI metadata emitted when a streamed text part ends.
287
+ *
288
+ * @category response
289
+ * @since 4.0.0
290
+ */
211
291
  export interface TextEndPartMetadata extends ProviderMetadata {
292
+ /**
293
+ * Provider-specific metadata returned for the streamed text end.
294
+ */
212
295
  readonly openai?: {
296
+ /**
297
+ * The OpenAI item ID associated with the streamed text part.
298
+ */
213
299
  readonly itemId?: string | null
214
- readonly annotations?: ReadonlyArray<typeof Generated.Annotation.Encoded> | null
300
+ /**
301
+ * The annotations collected for the completed streamed text part.
302
+ */
303
+ readonly annotations?: ReadonlyArray<typeof OpenAiSchema.Annotation.Encoded> | null
215
304
  } | null
216
305
  }
217
306
 
307
+ /**
308
+ * OpenAI metadata attached to a complete reasoning response part.
309
+ *
310
+ * @category response
311
+ * @since 4.0.0
312
+ */
218
313
  export interface ReasoningPartMetadata extends ProviderMetadata {
314
+ /**
315
+ * Provider-specific metadata returned for the reasoning part.
316
+ */
219
317
  readonly openai?: {
318
+ /**
319
+ * The OpenAI item ID associated with the reasoning part.
320
+ */
220
321
  readonly itemId?: string | null
322
+ /**
323
+ * Encrypted reasoning content that can be sent back in later requests.
324
+ */
221
325
  readonly encryptedContent?: string | null
222
326
  } | null
223
327
  }
224
328
 
329
+ /**
330
+ * OpenAI metadata emitted when a streamed reasoning part starts.
331
+ *
332
+ * @category response
333
+ * @since 4.0.0
334
+ */
225
335
  export interface ReasoningStartPartMetadata extends ProviderMetadata {
336
+ /**
337
+ * Provider-specific metadata returned for the streamed reasoning start.
338
+ */
226
339
  readonly openai?: {
340
+ /**
341
+ * The OpenAI item ID associated with the reasoning part.
342
+ */
227
343
  readonly itemId?: string | null
344
+ /**
345
+ * Encrypted reasoning content that can be sent back in later requests.
346
+ */
228
347
  readonly encryptedContent?: string | null
229
348
  } | null
230
349
  }
231
350
 
351
+ /**
352
+ * OpenAI metadata emitted for a streamed reasoning delta.
353
+ *
354
+ * @category response
355
+ * @since 4.0.0
356
+ */
232
357
  export interface ReasoningDeltaPartMetadata extends ProviderMetadata {
358
+ /**
359
+ * Provider-specific metadata returned for the streamed reasoning delta.
360
+ */
233
361
  readonly openai?: {
362
+ /**
363
+ * The OpenAI item ID associated with the reasoning part.
364
+ */
234
365
  readonly itemId?: string | null
235
366
  } | null
236
367
  }
237
368
 
369
+ /**
370
+ * OpenAI metadata emitted when a streamed reasoning part ends.
371
+ *
372
+ * @category response
373
+ * @since 4.0.0
374
+ */
238
375
  export interface ReasoningEndPartMetadata extends ProviderMetadata {
376
+ /**
377
+ * Provider-specific metadata returned for the streamed reasoning end.
378
+ */
239
379
  readonly openai?: {
380
+ /**
381
+ * The OpenAI item ID associated with the reasoning part.
382
+ */
240
383
  readonly itemId?: string | null
384
+ /**
385
+ * Encrypted reasoning content that can be sent back in later requests.
386
+ */
241
387
  readonly encryptedContent?: string
242
388
  } | null
243
389
  }
244
390
 
391
+ /**
392
+ * OpenAI metadata attached to tool-call response parts.
393
+ *
394
+ * @category response
395
+ * @since 4.0.0
396
+ */
245
397
  export interface ToolCallPartMetadata extends ProviderMetadata {
398
+ /**
399
+ * Provider-specific metadata returned for the tool call.
400
+ */
246
401
  readonly openai?: {
402
+ /**
403
+ * The OpenAI item ID associated with the tool call.
404
+ */
247
405
  readonly itemId?: string | null
248
406
  } | null
249
407
  }
250
408
 
409
+ /**
410
+ * OpenAI metadata attached to document source citations.
411
+ *
412
+ * @category response
413
+ * @since 4.0.0
414
+ */
251
415
  export interface DocumentSourcePartMetadata extends ProviderMetadata {
416
+ /**
417
+ * Provider-specific citation metadata for the OpenAI Responses API.
418
+ */
252
419
  readonly openai?:
253
420
  | {
421
+ /**
422
+ * Identifies a citation to an uploaded file.
423
+ */
254
424
  readonly type: "file_citation"
255
425
  /**
256
426
  * The index of the file in the list of files.
@@ -262,6 +432,9 @@ declare module "effect/unstable/ai/Response" {
262
432
  readonly fileId: string
263
433
  }
264
434
  | {
435
+ /**
436
+ * Identifies a citation to a generated file path.
437
+ */
265
438
  readonly type: "file_path"
266
439
  /**
267
440
  * The index of the file in the list of files.
@@ -273,6 +446,9 @@ declare module "effect/unstable/ai/Response" {
273
446
  readonly fileId: string
274
447
  }
275
448
  | {
449
+ /**
450
+ * Identifies a citation to a file inside a container.
451
+ */
276
452
  readonly type: "container_file_citation"
277
453
  /**
278
454
  * The ID of the file.
@@ -286,8 +462,20 @@ declare module "effect/unstable/ai/Response" {
286
462
  | null
287
463
  }
288
464
 
465
+ /**
466
+ * OpenAI metadata attached to URL source citations.
467
+ *
468
+ * @category response
469
+ * @since 4.0.0
470
+ */
289
471
  export interface UrlSourcePartMetadata extends ProviderMetadata {
472
+ /**
473
+ * Provider-specific URL citation metadata for the OpenAI Responses API.
474
+ */
290
475
  readonly openai?: {
476
+ /**
477
+ * Identifies a citation to a URL.
478
+ */
291
479
  readonly type: "url_citation"
292
480
  /**
293
481
  * The index of the first character of the URL citation in the message.
@@ -300,8 +488,20 @@ declare module "effect/unstable/ai/Response" {
300
488
  } | null
301
489
  }
302
490
 
491
+ /**
492
+ * OpenAI metadata attached to finish response parts.
493
+ *
494
+ * @category response
495
+ * @since 4.0.0
496
+ */
303
497
  export interface FinishPartMetadata extends ProviderMetadata {
498
+ /**
499
+ * Provider-specific metadata returned when generation finishes.
500
+ */
304
501
  readonly openai?: {
502
+ /**
503
+ * The service tier reported by OpenAI for the response.
504
+ */
305
505
  readonly serviceTier?: "default" | "auto" | "flex" | "scale" | "priority" | null
306
506
  } | null
307
507
  }
@@ -312,31 +512,33 @@ declare module "effect/unstable/ai/Response" {
312
512
  // =============================================================================
313
513
 
314
514
  /**
315
- * @since 1.0.0
515
+ * Creates an OpenAI language model that can be used with `AiModel.provide`.
516
+ *
316
517
  * @category constructors
518
+ * @since 4.0.0
317
519
  */
318
520
  export const model = (
319
521
  model: (string & {}) | Model,
320
522
  config?: Omit<typeof Config.Service, "model">
321
523
  ): AiModel.Model<"openai", LanguageModel.LanguageModel, OpenAiClient> =>
322
- AiModel.make("openai", layer({ model, config }))
524
+ AiModel.make("openai", model, layer({ model, config }))
323
525
 
324
526
  // TODO
325
527
  // /**
326
- // * @since 1.0.0
528
+ // * @since 4.0.0
327
529
  // * @category constructors
328
530
  // */
329
531
  // export const modelWithTokenizer = (
330
532
  // model: (string & {}) | Model,
331
533
  // config?: Omit<typeof Config.Service, "model">
332
534
  // ): AiModel.Model<"openai", LanguageModel.LanguageModel | Tokenizer.Tokenizer, OpenAiClient> =>
333
- // AiModel.make("openai", layerWithTokenizer({ model, config }))
535
+ // AiModel.make("openai", model, layerWithTokenizer({ model, config }))
334
536
 
335
537
  /**
336
538
  * Creates an OpenAI language model service.
337
539
  *
338
- * @since 1.0.0
339
540
  * @category constructors
541
+ * @since 4.0.0
340
542
  */
341
543
  export const make = Effect.fnUntraced(function*({ model, config: providerConfig }: {
342
544
  readonly model: (string & {}) | Model
@@ -345,7 +547,7 @@ export const make = Effect.fnUntraced(function*({ model, config: providerConfig
345
547
  const client = yield* OpenAiClient
346
548
 
347
549
  const makeConfig = Effect.gen(function*() {
348
- const services = yield* Effect.services<never>()
550
+ const services = yield* Effect.context<never>()
349
551
  return { model, ...providerConfig, ...services.mapUnsafe.get(Config.key) }
350
552
  })
351
553
 
@@ -354,9 +556,9 @@ export const make = Effect.fnUntraced(function*({ model, config: providerConfig
354
556
  readonly config: typeof Config.Service
355
557
  readonly options: LanguageModel.ProviderOptions
356
558
  readonly toolNameMapper: Tool.NameMapper<Tools>
357
- }): Effect.fn.Return<typeof Generated.CreateResponse.Encoded, AiError.AiError> {
358
- const include = new Set<typeof Generated.IncludeEnum.Encoded>()
359
- const capabilities = getModelCapabilities(config.model!)
559
+ }): Effect.fn.Return<typeof OpenAiSchema.CreateResponse.Encoded, AiError.AiError> {
560
+ const include = new Set<typeof OpenAiSchema.IncludeEnum.Encoded>()
561
+ const capabilities = getModelCapabilities(config.model as string)
360
562
  const messages = yield* prepareMessages({
361
563
  config,
362
564
  options,
@@ -369,26 +571,29 @@ export const make = Effect.fnUntraced(function*({ model, config: providerConfig
369
571
  options,
370
572
  toolNameMapper
371
573
  })
372
- const responseFormat = prepareResponseFormat({
574
+ const responseFormat = yield* prepareResponseFormat({
373
575
  config,
374
576
  options
375
577
  })
376
- const request: typeof Generated.CreateResponse.Encoded = {
377
- ...config,
578
+ const { fileIdPrefixes: _fip, strictJsonSchema: _sjs, ...apiConfig } = config
579
+ const request: Mutable<typeof OpenAiSchema.CreateResponse.Encoded> = {
580
+ ...apiConfig,
378
581
  input: messages,
379
- include: include.size > 0 ? Array.from(include) : null,
582
+ include: include.size > 0 ? Array.from(include) : undefined,
380
583
  text: {
381
- verbosity: config.text?.verbosity ?? null,
584
+ verbosity: config.text?.verbosity ?? undefined,
382
585
  format: responseFormat
383
- },
384
- ...(Predicate.isNotUndefined(tools) ? { tools } : undefined),
385
- ...(Predicate.isNotUndefined(toolChoice) ? { tool_choice: toolChoice } : undefined)
586
+ }
386
587
  }
588
+ if (tools) request.tools = tools
589
+ if (toolChoice) request.tool_choice = toolChoice
590
+ if (options.previousResponseId) request.previous_response_id = options.previousResponseId
387
591
  return request
388
592
  }
389
593
  )
390
594
 
391
595
  return yield* LanguageModel.make({
596
+ codecTransformer: toCodecOpenAI,
392
597
  generateText: Effect.fnUntraced(
393
598
  function*(options) {
394
599
  const config = yield* makeConfig
@@ -435,8 +640,8 @@ export const make = Effect.fnUntraced(function*({ model, config: providerConfig
435
640
  /**
436
641
  * Creates a layer for the OpenAI language model.
437
642
  *
438
- * @since 1.0.0
439
643
  * @category layers
644
+ * @since 4.0.0
440
645
  */
441
646
  export const layer = (options: {
442
647
  readonly model: (string & {}) | Model
@@ -447,37 +652,37 @@ export const layer = (options: {
447
652
  /**
448
653
  * Provides config overrides for OpenAI language model operations.
449
654
  *
450
- * @since 1.0.0
451
655
  * @category configuration
656
+ * @since 4.0.0
452
657
  */
453
658
  export const withConfigOverride: {
454
659
  /**
455
660
  * Provides config overrides for OpenAI language model operations.
456
661
  *
457
- * @since 1.0.0
458
662
  * @category configuration
663
+ * @since 4.0.0
459
664
  */
460
665
  (overrides: typeof Config.Service): <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, E, Exclude<R, Config>>
461
666
  /**
462
667
  * Provides config overrides for OpenAI language model operations.
463
668
  *
464
- * @since 1.0.0
465
669
  * @category configuration
670
+ * @since 4.0.0
466
671
  */
467
672
  <A, E, R>(self: Effect.Effect<A, E, R>, overrides: typeof Config.Service): Effect.Effect<A, E, Exclude<R, Config>>
468
673
  } = dual<
469
674
  /**
470
675
  * Provides config overrides for OpenAI language model operations.
471
676
  *
472
- * @since 1.0.0
473
677
  * @category configuration
678
+ * @since 4.0.0
474
679
  */
475
680
  (overrides: typeof Config.Service) => <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, E, Exclude<R, Config>>,
476
681
  /**
477
682
  * Provides config overrides for OpenAI language model operations.
478
683
  *
479
- * @since 1.0.0
480
684
  * @category configuration
685
+ * @since 4.0.0
481
686
  */
482
687
  <A, E, R>(self: Effect.Effect<A, E, R>, overrides: typeof Config.Service) => Effect.Effect<A, E, Exclude<R, Config>>
483
688
  >(2, (self, overrides) =>
@@ -512,10 +717,10 @@ const prepareMessages = Effect.fnUntraced(
512
717
  }: {
513
718
  readonly config: typeof Config.Service
514
719
  readonly options: LanguageModel.ProviderOptions
515
- readonly include: Set<typeof Generated.IncludeEnum.Encoded>
720
+ readonly include: Set<typeof OpenAiSchema.IncludeEnum.Encoded>
516
721
  readonly capabilities: ModelCapabilities
517
722
  readonly toolNameMapper: Tool.NameMapper<Tools>
518
- }): Effect.fn.Return<ReadonlyArray<typeof Generated.InputItem.Encoded>, AiError.AiError> {
723
+ }): Effect.fn.Return<ReadonlyArray<typeof OpenAiSchema.InputItem.Encoded>, AiError.AiError> {
519
724
  const processedApprovalIds = new Set<string>()
520
725
 
521
726
  const hasConversation = Predicate.isNotNullish(config.conversation)
@@ -547,27 +752,28 @@ const prepareMessages = Effect.fnUntraced(
547
752
  if (config.store === false && capabilities.isReasoningModel) {
548
753
  include.add("reasoning.encrypted_content")
549
754
  }
550
- if (Predicate.isNotUndefined(codeInterpreterTool)) {
755
+ if (codeInterpreterTool) {
551
756
  include.add("code_interpreter_call.outputs")
552
757
  }
553
- if (Predicate.isNotUndefined(webSearchTool) || Predicate.isNotUndefined(webSearchPreviewTool)) {
758
+ if (webSearchTool || webSearchPreviewTool) {
554
759
  include.add("web_search_call.action.sources")
555
760
  }
556
761
 
557
- const messages: Array<typeof Generated.InputItem.Encoded> = []
762
+ const messages: Array<typeof OpenAiSchema.InputItem.Encoded> = []
763
+ const prompt = options.incrementalPrompt ?? options.prompt
558
764
 
559
- for (const message of options.prompt.content) {
765
+ for (const message of prompt.content) {
560
766
  switch (message.role) {
561
767
  case "system": {
562
768
  messages.push({
563
- role: getSystemMessageMode(config.model!),
769
+ role: getSystemMessageMode(config.model as string),
564
770
  content: message.content
565
771
  })
566
772
  break
567
773
  }
568
774
 
569
775
  case "user": {
570
- const content: Array<typeof Generated.InputContent.Encoded> = []
776
+ const content: Array<typeof OpenAiSchema.InputContent.Encoded> = []
571
777
 
572
778
  for (let index = 0; index < message.content.length; index++) {
573
779
  const part = message.content[index]
@@ -630,7 +836,7 @@ const prepareMessages = Effect.fnUntraced(
630
836
  }
631
837
 
632
838
  case "assistant": {
633
- const reasoningMessages: Record<string, DeepMutable<typeof Generated.ReasoningItem.Encoded>> = {}
839
+ const reasoningMessages: Record<string, DeepMutable<typeof OpenAiSchema.ReasoningItem.Encoded>> = {}
634
840
 
635
841
  for (const part of message.content) {
636
842
  switch (part.type) {
@@ -689,7 +895,7 @@ const prepareMessages = Effect.fnUntraced(
689
895
  }
690
896
  }
691
897
  } else {
692
- const summaryParts: Array<typeof Generated.SummaryTextContent.Encoded> = []
898
+ const summaryParts: Array<typeof OpenAiSchema.SummaryTextContent.Encoded> = []
693
899
 
694
900
  if (part.text.length > 0) {
695
901
  summaryParts.push({ type: "summary_text", text: part.text })
@@ -700,7 +906,9 @@ const prepareMessages = Effect.fnUntraced(
700
906
  type: "reasoning",
701
907
  id,
702
908
  summary: summaryParts,
703
- encrypted_content: encryptedContent ?? null
909
+ ...(Predicate.isNotNull(encryptedContent)
910
+ ? { encrypted_content: encryptedContent }
911
+ : undefined)
704
912
  }
705
913
 
706
914
  messages.push(reasoningMessages[id])
@@ -921,7 +1129,7 @@ const buildHttpRequestDetails = (
921
1129
  method: request.method,
922
1130
  url: request.url,
923
1131
  urlParams: Array.from(request.urlParams),
924
- hash: request.hash,
1132
+ hash: Option.getOrUndefined(request.hash),
925
1133
  headers: Redactable.redact(request.headers) as Record<string, string>
926
1134
  })
927
1135
 
@@ -936,7 +1144,56 @@ const buildHttpResponseDetails = (
936
1144
  // Response Conversion
937
1145
  // =============================================================================
938
1146
 
939
- type ResponseStreamEvent = typeof Generated.ResponseStreamEvent.Type
1147
+ type ResponseStreamEvent = typeof OpenAiSchema.ResponseStreamEvent.Type
1148
+
1149
+ type KnownResponseStreamEventType =
1150
+ | "response.created"
1151
+ | "response.completed"
1152
+ | "response.incomplete"
1153
+ | "response.failed"
1154
+ | "response.output_item.added"
1155
+ | "response.output_item.done"
1156
+ | "response.output_text.delta"
1157
+ | "response.output_text.annotation.added"
1158
+ | "response.reasoning_summary_part.added"
1159
+ | "response.reasoning_summary_part.done"
1160
+ | "response.reasoning_summary_text.delta"
1161
+ | "response.function_call_arguments.delta"
1162
+ | "response.function_call_arguments.done"
1163
+ | "response.code_interpreter_call_code.delta"
1164
+ | "response.code_interpreter_call_code.done"
1165
+ | "response.apply_patch_call_operation_diff.delta"
1166
+ | "response.apply_patch_call_operation_diff.done"
1167
+ | "response.image_generation_call.partial_image"
1168
+ | "error"
1169
+
1170
+ type KnownResponseStreamEvent = Extract<ResponseStreamEvent, { readonly type: KnownResponseStreamEventType }>
1171
+
1172
+ const knownResponseStreamEventTypes = new Set<KnownResponseStreamEventType>([
1173
+ "response.created",
1174
+ "response.completed",
1175
+ "response.incomplete",
1176
+ "response.failed",
1177
+ "response.output_item.added",
1178
+ "response.output_item.done",
1179
+ "response.output_text.delta",
1180
+ "response.output_text.annotation.added",
1181
+ "response.reasoning_summary_part.added",
1182
+ "response.reasoning_summary_part.done",
1183
+ "response.reasoning_summary_text.delta",
1184
+ "response.function_call_arguments.delta",
1185
+ "response.function_call_arguments.done",
1186
+ "response.code_interpreter_call_code.delta",
1187
+ "response.code_interpreter_call_code.done",
1188
+ "response.apply_patch_call_operation_diff.delta",
1189
+ "response.apply_patch_call_operation_diff.done",
1190
+ "response.image_generation_call.partial_image",
1191
+ "error"
1192
+ ])
1193
+
1194
+ const isKnownResponseStreamEvent = (
1195
+ event: ResponseStreamEvent
1196
+ ): event is KnownResponseStreamEvent => knownResponseStreamEventTypes.has(event.type as KnownResponseStreamEventType)
940
1197
 
941
1198
  const makeResponse = Effect.fnUntraced(
942
1199
  function*<Tools extends ReadonlyArray<Tool.Any>>({
@@ -946,7 +1203,7 @@ const makeResponse = Effect.fnUntraced(
946
1203
  toolNameMapper
947
1204
  }: {
948
1205
  readonly options: LanguageModel.ProviderOptions
949
- readonly rawResponse: Generated.Response
1206
+ readonly rawResponse: OpenAiSchema.Response
950
1207
  readonly response: HttpClientResponse.HttpClientResponse
951
1208
  readonly toolNameMapper: Tool.NameMapper<Tools>
952
1209
  }): Effect.fn.Return<
@@ -985,7 +1242,7 @@ const makeResponse = Effect.fnUntraced(
985
1242
  id: part.call_id,
986
1243
  name: toolName,
987
1244
  params: { call_id: part.call_id, operation: part.operation },
988
- metadata: { openai: { ...makeItemIdMetadata(part.id) } }
1245
+ metadata: { openai: makeItemIdMetadata(part.id) }
989
1246
  })
990
1247
  break
991
1248
  }
@@ -1036,10 +1293,11 @@ const makeResponse = Effect.fnUntraced(
1036
1293
 
1037
1294
  case "function_call": {
1038
1295
  hasToolCalls = true
1296
+
1039
1297
  const toolName = part.name
1040
- const toolParams = part.arguments
1041
- const params = yield* Effect.try({
1042
- try: () => Tool.unsafeSecureJsonParse(toolParams),
1298
+
1299
+ const toolParams = yield* Effect.try({
1300
+ try: () => Tool.unsafeSecureJsonParse(part.arguments),
1043
1301
  catch: (cause) =>
1044
1302
  AiError.make({
1045
1303
  module: "OpenAiLanguageModel",
@@ -1051,12 +1309,15 @@ const makeResponse = Effect.fnUntraced(
1051
1309
  })
1052
1310
  })
1053
1311
  })
1312
+
1313
+ const params = yield* transformToolCallParams(options.tools, part.name, toolParams)
1314
+
1054
1315
  parts.push({
1055
1316
  type: "tool-call",
1056
1317
  id: part.call_id,
1057
1318
  name: toolName,
1058
1319
  params,
1059
- metadata: { openai: { ...makeItemIdMetadata(part.id) } }
1320
+ metadata: { openai: makeItemIdMetadata(part.id) }
1060
1321
  })
1061
1322
  break
1062
1323
  }
@@ -1087,7 +1348,7 @@ const makeResponse = Effect.fnUntraced(
1087
1348
  id: part.call_id,
1088
1349
  name: toolName,
1089
1350
  params: { action: part.action },
1090
- metadata: { openai: { ...makeItemIdMetadata(part.id) } }
1351
+ metadata: { openai: makeItemIdMetadata(part.id) }
1091
1352
  })
1092
1353
  break
1093
1354
  }
@@ -1097,13 +1358,17 @@ const makeResponse = Effect.fnUntraced(
1097
1358
  ? (approvalRequests.get(part.approval_request_id) ?? part.id)
1098
1359
  : part.id
1099
1360
 
1100
- const toolName = `mcp.${part.name}`
1361
+ const { toolName, params } = yield* normalizeMcpToolCall({
1362
+ toolNameMapper,
1363
+ toolParams: part.arguments,
1364
+ method: "makeResponse"
1365
+ })
1101
1366
 
1102
1367
  parts.push({
1103
1368
  type: "tool-call",
1104
1369
  id: toolId,
1105
1370
  name: toolName,
1106
- params: part.arguments,
1371
+ params,
1107
1372
  providerExecuted: true
1108
1373
  })
1109
1374
 
@@ -1114,14 +1379,14 @@ const makeResponse = Effect.fnUntraced(
1114
1379
  isFailure: false,
1115
1380
  providerExecuted: true,
1116
1381
  result: {
1117
- type: "call",
1382
+ type: "mcp_call",
1118
1383
  name: part.name,
1119
1384
  arguments: part.arguments,
1120
1385
  server_label: part.server_label,
1121
1386
  ...(Predicate.isNotNullish(part.output) ? { output: part.output } : undefined),
1122
1387
  ...(Predicate.isNotNullish(part.error) ? { error: part.error } : undefined)
1123
1388
  },
1124
- metadata: { openai: { ...makeItemIdMetadata(part.id) } }
1389
+ metadata: { openai: makeItemIdMetadata(part.id) }
1125
1390
  })
1126
1391
 
1127
1392
  break
@@ -1135,20 +1400,11 @@ const makeResponse = Effect.fnUntraced(
1135
1400
  case "mcp_approval_request": {
1136
1401
  const approvalRequestId = (part as any).approval_request_id ?? part.id
1137
1402
  const toolId = yield* idGenerator.generateId()
1138
- const toolName = `mcp.${part.name}`
1139
1403
 
1140
- const params = yield* Effect.try({
1141
- try: () => Tool.unsafeSecureJsonParse(part.arguments),
1142
- catch: (cause) =>
1143
- AiError.make({
1144
- module: "OpenAiLanguageModel",
1145
- method: "makeResponse",
1146
- reason: new AiError.ToolParameterValidationError({
1147
- toolName,
1148
- toolParams: {},
1149
- description: `Failed securely JSON parse tool parameters: ${cause}`
1150
- })
1151
- })
1404
+ const { toolName, params } = yield* normalizeMcpToolCall({
1405
+ toolNameMapper,
1406
+ toolParams: part.arguments,
1407
+ method: "makeResponse"
1152
1408
  })
1153
1409
 
1154
1410
  parts.push({
@@ -1296,7 +1552,7 @@ const makeResponse = Effect.fnUntraced(
1296
1552
  id: part.call_id,
1297
1553
  name: toolName,
1298
1554
  params: { action: part.action },
1299
- metadata: { openai: { ...makeItemIdMetadata(part.id) } }
1555
+ metadata: { openai: makeItemIdMetadata(part.id) }
1300
1556
  })
1301
1557
  break
1302
1558
  }
@@ -1335,7 +1591,7 @@ const makeResponse = Effect.fnUntraced(
1335
1591
  reason: finishReason,
1336
1592
  usage: getUsage(rawResponse.usage),
1337
1593
  response: buildHttpResponseDetails(response),
1338
- ...(rawResponse.service_tier && { metadata: { openai: { serviceTier: rawResponse.service_tier } } })
1594
+ ...toServiceTier(rawResponse.service_tier)
1339
1595
  })
1340
1596
 
1341
1597
  return parts
@@ -1368,18 +1624,44 @@ const makeStreamResponse = Effect.fnUntraced(
1368
1624
  let hasToolCalls = false
1369
1625
 
1370
1626
  // Track annotations for current message to include in text-end metadata
1371
- const activeAnnotations: Array<typeof Generated.Annotation.Encoded> = []
1627
+ const activeAnnotations: Array<typeof OpenAiSchema.Annotation.Encoded> = []
1628
+
1629
+ type ReasoningSummaryPartStatus = "active" | "can-conclude" | "concluded"
1630
+ type ReasoningPart = {
1631
+ encryptedContent: string | undefined
1632
+ summaryParts: Record<number, ReasoningSummaryPartStatus>
1633
+ }
1372
1634
 
1373
1635
  // Track active reasoning items with state machine for proper concluding logic
1374
- const activeReasoning: Record<string, {
1375
- readonly encryptedContent: string | undefined
1376
- readonly summaryParts: Record<number, "active" | "can-conclude" | "concluded">
1377
- }> = {}
1636
+ const activeReasoning: Record<string, ReasoningPart> = {}
1637
+
1638
+ const getOrCreateReasoningPart = (
1639
+ itemId: string,
1640
+ encryptedContent?: string | null
1641
+ ): ReasoningPart => {
1642
+ const activePart = activeReasoning[itemId]
1643
+ if (Predicate.isNotUndefined(activePart)) {
1644
+ if (Predicate.isNotNullish(encryptedContent)) {
1645
+ activePart.encryptedContent = encryptedContent
1646
+ }
1647
+ return activePart
1648
+ }
1649
+
1650
+ const reasoningPart: ReasoningPart = {
1651
+ encryptedContent: Predicate.isNotNullish(encryptedContent) ? encryptedContent : undefined,
1652
+ summaryParts: {}
1653
+ }
1654
+ activeReasoning[itemId] = reasoningPart
1655
+ return reasoningPart
1656
+ }
1378
1657
 
1379
1658
  // Track active tool calls with optional provider-specific state
1380
1659
  const activeToolCalls: Record<number, {
1381
1660
  readonly id: string
1382
1661
  readonly name: string
1662
+ readonly functionCall?: {
1663
+ emitted: boolean
1664
+ }
1383
1665
  readonly applyPatch?: {
1384
1666
  hasDiff: boolean
1385
1667
  endEmitted: boolean
@@ -1399,6 +1681,10 @@ const makeStreamResponse = Effect.fnUntraced(
1399
1681
  Stream.mapEffect(Effect.fnUntraced(function*(event) {
1400
1682
  const parts: Array<Response.StreamPartEncoded> = []
1401
1683
 
1684
+ if (!isKnownResponseStreamEvent(event)) {
1685
+ return parts
1686
+ }
1687
+
1402
1688
  switch (event.type) {
1403
1689
  case "response.created": {
1404
1690
  const createdAt = new Date(event.response.created_at * 1000)
@@ -1428,7 +1714,7 @@ const makeStreamResponse = Effect.fnUntraced(
1428
1714
  ),
1429
1715
  usage: getUsage(event.response.usage),
1430
1716
  response: buildHttpResponseDetails(response),
1431
- ...(event.response.service_tier && { metadata: { openai: { serviceTier: event.response.service_tier } } })
1717
+ ...toServiceTier(event.response.service_tier)
1432
1718
  })
1433
1719
  break
1434
1720
  }
@@ -1529,7 +1815,8 @@ const makeStreamResponse = Effect.fnUntraced(
1529
1815
  case "function_call": {
1530
1816
  activeToolCalls[event.output_index] = {
1531
1817
  id: event.item.call_id,
1532
- name: event.item.name
1818
+ name: event.item.name,
1819
+ functionCall: { emitted: false }
1533
1820
  }
1534
1821
  parts.push({
1535
1822
  type: "tool-params-start",
@@ -1566,34 +1853,33 @@ const makeStreamResponse = Effect.fnUntraced(
1566
1853
  parts.push({
1567
1854
  type: "text-start",
1568
1855
  id: event.item.id,
1569
- metadata: { openai: { ...makeItemIdMetadata(event.item.id) } }
1856
+ metadata: { openai: makeItemIdMetadata(event.item.id) }
1570
1857
  })
1571
1858
  break
1572
1859
  }
1573
1860
 
1574
1861
  case "reasoning": {
1575
- const encryptedContent = event.item.encrypted_content ?? undefined
1576
- activeReasoning[event.item.id] = {
1577
- encryptedContent,
1578
- summaryParts: { 0: "active" }
1579
- }
1580
- parts.push({
1581
- type: "reasoning-start",
1582
- id: `${event.item.id}:0`,
1583
- metadata: {
1584
- openai: {
1585
- ...makeItemIdMetadata(event.item.id),
1586
- ...makeEncryptedContentMetadata(event.item.encrypted_content)
1862
+ const reasoningPart = getOrCreateReasoningPart(event.item.id, event.item.encrypted_content)
1863
+ if (Predicate.isUndefined(reasoningPart.summaryParts[0])) {
1864
+ reasoningPart.summaryParts[0] = "active"
1865
+ parts.push({
1866
+ type: "reasoning-start",
1867
+ id: `${event.item.id}:0`,
1868
+ metadata: {
1869
+ openai: {
1870
+ ...makeItemIdMetadata(event.item.id),
1871
+ ...makeEncryptedContentMetadata(reasoningPart.encryptedContent)
1872
+ }
1587
1873
  }
1588
- }
1589
- })
1874
+ })
1875
+ }
1590
1876
  break
1591
1877
  }
1592
1878
 
1593
1879
  case "shell_call": {
1594
1880
  const toolName = toolNameMapper.getCustomName("shell")
1595
1881
  activeToolCalls[event.output_index] = {
1596
- id: event.item.id,
1882
+ id: event.item.id ?? event.item.call_id,
1597
1883
  name: toolName
1598
1884
  }
1599
1885
  break
@@ -1644,7 +1930,7 @@ const makeStreamResponse = Effect.fnUntraced(
1644
1930
  parts.push({
1645
1931
  type: "tool-params-delta",
1646
1932
  id: toolCall.id,
1647
- delta: InternalUtilities.escapeJSONDelta(event.item.operation.diff)
1933
+ delta: InternalUtilities.escapeJSONDelta(event.item.operation.diff ?? "")
1648
1934
  })
1649
1935
  }
1650
1936
  parts.push({
@@ -1666,7 +1952,7 @@ const makeStreamResponse = Effect.fnUntraced(
1666
1952
  id: toolCall.id,
1667
1953
  name: toolName,
1668
1954
  params: { call_id: event.item.call_id, operation: event.item.operation },
1669
- metadata: { openai: { ...makeItemIdMetadata(event.item.id) } }
1955
+ metadata: { openai: makeItemIdMetadata(event.item.id) }
1670
1956
  })
1671
1957
  }
1672
1958
  delete activeToolCalls[event.output_index]
@@ -1729,12 +2015,20 @@ const makeStreamResponse = Effect.fnUntraced(
1729
2015
  }
1730
2016
 
1731
2017
  case "function_call": {
2018
+ const toolCall = activeToolCalls[event.output_index]
2019
+ if (Predicate.isNotUndefined(toolCall?.functionCall?.emitted) && toolCall.functionCall.emitted) {
2020
+ delete activeToolCalls[event.output_index]
2021
+ break
2022
+ }
1732
2023
  delete activeToolCalls[event.output_index]
2024
+
1733
2025
  hasToolCalls = true
2026
+
1734
2027
  const toolName = event.item.name
1735
- const toolParams = event.item.arguments
1736
- const params = yield* Effect.try({
1737
- try: () => Tool.unsafeSecureJsonParse(toolParams),
2028
+ const toolArgs = event.item.arguments
2029
+
2030
+ const toolParams = yield* Effect.try({
2031
+ try: () => Tool.unsafeSecureJsonParse(toolArgs),
1738
2032
  catch: (cause) =>
1739
2033
  AiError.make({
1740
2034
  module: "OpenAiLanguageModel",
@@ -1746,17 +2040,22 @@ const makeStreamResponse = Effect.fnUntraced(
1746
2040
  })
1747
2041
  })
1748
2042
  })
2043
+
2044
+ const params = yield* transformToolCallParams(options.tools, toolName, toolParams)
2045
+
1749
2046
  parts.push({
1750
2047
  type: "tool-params-end",
1751
2048
  id: event.item.call_id
1752
2049
  })
2050
+
1753
2051
  parts.push({
1754
2052
  type: "tool-call",
1755
2053
  id: event.item.call_id,
1756
2054
  name: toolName,
1757
2055
  params,
1758
- metadata: { openai: { ...makeItemIdMetadata(event.item.id) } }
2056
+ metadata: { openai: makeItemIdMetadata(event.item.id) }
1759
2057
  })
2058
+
1760
2059
  break
1761
2060
  }
1762
2061
 
@@ -1780,7 +2079,7 @@ const makeStreamResponse = Effect.fnUntraced(
1780
2079
  id: event.item.call_id,
1781
2080
  name: toolName,
1782
2081
  params: { action: event.item.action },
1783
- metadata: { openai: { ...makeItemIdMetadata(event.item.id) } }
2082
+ metadata: { openai: makeItemIdMetadata(event.item.id) }
1784
2083
  })
1785
2084
  break
1786
2085
  }
@@ -1794,13 +2093,17 @@ const makeStreamResponse = Effect.fnUntraced(
1794
2093
  event.item.id)
1795
2094
  : event.item.id
1796
2095
 
1797
- const toolName = `mcp.${event.item.name}`
2096
+ const { toolName, params } = yield* normalizeMcpToolCall({
2097
+ toolNameMapper,
2098
+ toolParams: event.item.arguments,
2099
+ method: "makeStreamResponse"
2100
+ })
1798
2101
 
1799
2102
  parts.push({
1800
2103
  type: "tool-call",
1801
2104
  id: toolId,
1802
2105
  name: toolName,
1803
- params: event.item.arguments,
2106
+ params,
1804
2107
  providerExecuted: true
1805
2108
  })
1806
2109
 
@@ -1811,14 +2114,14 @@ const makeStreamResponse = Effect.fnUntraced(
1811
2114
  isFailure: false,
1812
2115
  providerExecuted: true,
1813
2116
  result: {
1814
- type: "call",
2117
+ type: "mcp_call",
1815
2118
  name: event.item.name,
1816
2119
  arguments: event.item.arguments,
1817
2120
  server_label: event.item.server_label,
1818
2121
  ...(Predicate.isNotNullish(event.item.output) ? { output: event.item.output } : undefined),
1819
2122
  ...(Predicate.isNotNullish(event.item.error) ? { error: event.item.error } : undefined)
1820
2123
  },
1821
- metadata: { openai: { ...makeItemIdMetadata(event.item.id) } }
2124
+ metadata: { openai: makeItemIdMetadata(event.item.id) }
1822
2125
  })
1823
2126
 
1824
2127
  break
@@ -1833,12 +2136,16 @@ const makeStreamResponse = Effect.fnUntraced(
1833
2136
  const toolId = yield* idGenerator.generateId()
1834
2137
  const approvalRequestId = (event.item as any).approval_request_id ?? event.item.id
1835
2138
  streamApprovalRequests.set(approvalRequestId, toolId)
1836
- const toolName = `mcp.${event.item.name}`
2139
+ const { toolName, params } = yield* normalizeMcpToolCall({
2140
+ toolNameMapper,
2141
+ toolParams: event.item.arguments,
2142
+ method: "makeStreamResponse"
2143
+ })
1837
2144
  parts.push({
1838
2145
  type: "tool-call",
1839
2146
  id: toolId,
1840
2147
  name: toolName,
1841
- params: event.item.arguments,
2148
+ params,
1842
2149
  providerExecuted: true
1843
2150
  })
1844
2151
  parts.push({
@@ -1862,7 +2169,7 @@ const makeStreamResponse = Effect.fnUntraced(
1862
2169
  }
1863
2170
 
1864
2171
  case "reasoning": {
1865
- const reasoningPart = activeReasoning[event.item.id]
2172
+ const reasoningPart = getOrCreateReasoningPart(event.item.id, event.item.encrypted_content)
1866
2173
  for (const [summaryIndex, status] of Object.entries(reasoningPart.summaryParts)) {
1867
2174
  if (status === "active" || status === "can-conclude") {
1868
2175
  parts.push({
@@ -1871,7 +2178,7 @@ const makeStreamResponse = Effect.fnUntraced(
1871
2178
  metadata: {
1872
2179
  openai: {
1873
2180
  ...makeItemIdMetadata(event.item.id),
1874
- ...makeEncryptedContentMetadata(event.item.encrypted_content)
2181
+ ...makeEncryptedContentMetadata(reasoningPart.encryptedContent)
1875
2182
  }
1876
2183
  }
1877
2184
  })
@@ -1886,10 +2193,10 @@ const makeStreamResponse = Effect.fnUntraced(
1886
2193
  const toolName = toolNameMapper.getCustomName("shell")
1887
2194
  parts.push({
1888
2195
  type: "tool-call",
1889
- id: event.item.id,
2196
+ id: event.item.id ?? event.item.call_id,
1890
2197
  name: toolName,
1891
2198
  params: { action: event.item.action },
1892
- metadata: { openai: { ...makeItemIdMetadata(event.item.id) } }
2199
+ metadata: { openai: makeItemIdMetadata(event.item.id) }
1893
2200
  })
1894
2201
  break
1895
2202
  }
@@ -1924,7 +2231,7 @@ const makeStreamResponse = Effect.fnUntraced(
1924
2231
  }
1925
2232
 
1926
2233
  case "response.output_text.annotation.added": {
1927
- const annotation = event.annotation as typeof Generated.Annotation.Encoded
2234
+ const annotation = event.annotation as typeof OpenAiSchema.Annotation.Encoded
1928
2235
  // Track annotation for text-end metadata
1929
2236
  activeAnnotations.push(annotation)
1930
2237
  if (annotation.type === "container_file_citation") {
@@ -2006,6 +2313,48 @@ const makeStreamResponse = Effect.fnUntraced(
2006
2313
  break
2007
2314
  }
2008
2315
 
2316
+ case "response.function_call_arguments.done": {
2317
+ const toolCall = activeToolCalls[event.output_index]
2318
+ if (
2319
+ Predicate.isNotUndefined(toolCall?.functionCall) &&
2320
+ !toolCall.functionCall.emitted
2321
+ ) {
2322
+ hasToolCalls = true
2323
+
2324
+ const toolParams = yield* Effect.try({
2325
+ try: () => Tool.unsafeSecureJsonParse(event.arguments),
2326
+ catch: (cause) =>
2327
+ AiError.make({
2328
+ module: "OpenAiLanguageModel",
2329
+ method: "makeStreamResponse",
2330
+ reason: new AiError.ToolParameterValidationError({
2331
+ toolName: toolCall.name,
2332
+ toolParams: {},
2333
+ description: `Failed securely JSON parse tool parameters: ${cause}`
2334
+ })
2335
+ })
2336
+ })
2337
+
2338
+ const params = yield* transformToolCallParams(options.tools, toolCall.name, toolParams)
2339
+
2340
+ parts.push({
2341
+ type: "tool-params-end",
2342
+ id: toolCall.id
2343
+ })
2344
+
2345
+ parts.push({
2346
+ type: "tool-call",
2347
+ id: toolCall.id,
2348
+ name: toolCall.name,
2349
+ params,
2350
+ metadata: { openai: makeItemIdMetadata(event.item_id) }
2351
+ })
2352
+
2353
+ toolCall.functionCall.emitted = true
2354
+ }
2355
+ break
2356
+ }
2357
+
2009
2358
  case "response.apply_patch_call_operation_diff.delta": {
2010
2359
  const toolCall = activeToolCalls[event.output_index]
2011
2360
  if (Predicate.isNotUndefined(toolCall?.applyPatch)) {
@@ -2095,28 +2444,28 @@ const makeStreamResponse = Effect.fnUntraced(
2095
2444
  }
2096
2445
 
2097
2446
  case "response.reasoning_summary_part.added": {
2098
- // The first reasoning start is pushed in the `response.output_item.added` block
2447
+ const reasoningPart = getOrCreateReasoningPart(event.item_id)
2099
2448
  if (event.summary_index > 0) {
2100
- const reasoningPart = activeReasoning[event.item_id]
2101
- if (Predicate.isNotUndefined(reasoningPart)) {
2102
- // Conclude all can-conclude parts before starting new one
2103
- for (const [summaryIndex, status] of Object.entries(reasoningPart.summaryParts)) {
2104
- if (status === "can-conclude") {
2105
- parts.push({
2106
- type: "reasoning-end",
2107
- id: `${event.item_id}:${summaryIndex}`,
2108
- metadata: {
2109
- openai: {
2110
- ...makeItemIdMetadata(event.item_id),
2111
- ...makeEncryptedContentMetadata(reasoningPart.encryptedContent)
2112
- }
2449
+ // Conclude all can-conclude parts before starting new one
2450
+ for (const [summaryIndex, status] of Object.entries(reasoningPart.summaryParts)) {
2451
+ if (status === "can-conclude") {
2452
+ parts.push({
2453
+ type: "reasoning-end",
2454
+ id: `${event.item_id}:${summaryIndex}`,
2455
+ metadata: {
2456
+ openai: {
2457
+ ...makeItemIdMetadata(event.item_id),
2458
+ ...makeEncryptedContentMetadata(reasoningPart.encryptedContent)
2113
2459
  }
2114
- })
2115
- reasoningPart.summaryParts[Number(summaryIndex)] = "concluded"
2116
- }
2460
+ }
2461
+ })
2462
+ reasoningPart.summaryParts[Number(summaryIndex)] = "concluded"
2117
2463
  }
2118
- reasoningPart.summaryParts[event.summary_index] = "active"
2119
2464
  }
2465
+ }
2466
+
2467
+ if (Predicate.isUndefined(reasoningPart.summaryParts[event.summary_index])) {
2468
+ reasoningPart.summaryParts[event.summary_index] = "active"
2120
2469
  parts.push({
2121
2470
  type: "reasoning-start",
2122
2471
  id: `${event.item_id}:${event.summary_index}`,
@@ -2136,26 +2485,27 @@ const makeStreamResponse = Effect.fnUntraced(
2136
2485
  type: "reasoning-delta",
2137
2486
  id: `${event.item_id}:${event.summary_index}`,
2138
2487
  delta: event.delta,
2139
- metadata: { openai: { ...makeItemIdMetadata(event.item_id) } }
2488
+ metadata: { openai: makeItemIdMetadata(event.item_id) }
2140
2489
  })
2141
2490
  break
2142
2491
  }
2143
2492
 
2144
2493
  case "response.reasoning_summary_part.done": {
2494
+ const reasoningPart = getOrCreateReasoningPart(event.item_id)
2145
2495
  // When OpenAI stores message data, we can immediately conclude the
2146
2496
  // reasoning part given that we do not need the encrypted content
2147
2497
  if (config.store === true) {
2148
2498
  parts.push({
2149
2499
  type: "reasoning-end",
2150
2500
  id: `${event.item_id}:${event.summary_index}`,
2151
- metadata: { openai: { ...makeItemIdMetadata(event.item_id) } }
2501
+ metadata: { openai: makeItemIdMetadata(event.item_id) }
2152
2502
  })
2153
2503
  // Mark the summary part concluded
2154
- activeReasoning[event.item_id].summaryParts[event.summary_index] = "concluded"
2504
+ reasoningPart.summaryParts[event.summary_index] = "concluded"
2155
2505
  } else {
2156
2506
  // Mark the summary part as can-conclude given we still need a
2157
2507
  // final summary part with the encrypted content
2158
- activeReasoning[event.item_id].summaryParts[event.summary_index] = "can-conclude"
2508
+ reasoningPart.summaryParts[event.summary_index] = "can-conclude"
2159
2509
  }
2160
2510
  break
2161
2511
  }
@@ -2174,7 +2524,7 @@ const makeStreamResponse = Effect.fnUntraced(
2174
2524
 
2175
2525
  const annotateRequest = (
2176
2526
  span: Span,
2177
- request: typeof Generated.CreateResponse.Encoded
2527
+ request: typeof OpenAiSchema.CreateResponse.Encoded
2178
2528
  ): void => {
2179
2529
  addGenAIAnnotations(span, {
2180
2530
  system: "openai",
@@ -2194,7 +2544,7 @@ const annotateRequest = (
2194
2544
  })
2195
2545
  }
2196
2546
 
2197
- const annotateResponse = (span: Span, response: Generated.Response): void => {
2547
+ const annotateResponse = (span: Span, response: OpenAiSchema.Response): void => {
2198
2548
  const finishReason = response.incomplete_details?.reason as string | undefined
2199
2549
  addGenAIAnnotations(span, {
2200
2550
  response: {
@@ -2244,7 +2594,7 @@ const annotateStreamResponse = (span: Span, part: Response.StreamPartEncoded) =>
2244
2594
  // Tool Conversion
2245
2595
  // =============================================================================
2246
2596
 
2247
- type OpenAiToolChoice = typeof Generated.CreateResponse.Encoded["tool_choice"]
2597
+ type OpenAiToolChoice = typeof OpenAiSchema.CreateResponse.Encoded["tool_choice"]
2248
2598
 
2249
2599
  const prepareTools = Effect.fnUntraced(function*<Tools extends ReadonlyArray<Tool.Any>>({
2250
2600
  config,
@@ -2255,7 +2605,7 @@ const prepareTools = Effect.fnUntraced(function*<Tools extends ReadonlyArray<Too
2255
2605
  readonly options: LanguageModel.ProviderOptions
2256
2606
  readonly toolNameMapper: Tool.NameMapper<Tools>
2257
2607
  }): Effect.fn.Return<{
2258
- readonly tools: ReadonlyArray<typeof Generated.Tool.Encoded> | undefined
2608
+ readonly tools: ReadonlyArray<typeof OpenAiSchema.Tool.Encoded> | undefined
2259
2609
  readonly toolChoice: OpenAiToolChoice | undefined
2260
2610
  }, AiError.AiError> {
2261
2611
  // Return immediately if no tools are in the toolkit
@@ -2263,7 +2613,7 @@ const prepareTools = Effect.fnUntraced(function*<Tools extends ReadonlyArray<Too
2263
2613
  return { tools: undefined, toolChoice: undefined }
2264
2614
  }
2265
2615
 
2266
- const tools: Array<typeof Generated.Tool.Encoded> = []
2616
+ const tools: Array<typeof OpenAiSchema.Tool.Encoded> = []
2267
2617
  let toolChoice: OpenAiToolChoice | undefined = undefined
2268
2618
 
2269
2619
  // Filter the incoming tools down to the set of allowed tools as indicated by
@@ -2279,14 +2629,16 @@ const prepareTools = Effect.fnUntraced(function*<Tools extends ReadonlyArray<Too
2279
2629
 
2280
2630
  // Convert the tools in the toolkit to the provider-defined format
2281
2631
  for (const tool of allowedTools) {
2282
- if (Tool.isUserDefined(tool)) {
2632
+ if (Tool.isUserDefined(tool) || Tool.isDynamic(tool)) {
2283
2633
  const strict = Tool.getStrictMode(tool) ?? config.strictJsonSchema ?? true
2634
+ const description = Tool.getDescription(tool)
2635
+ const parameters = yield* tryToolJsonSchema(tool, "prepareTools")
2284
2636
  tools.push({
2285
2637
  type: "function",
2286
2638
  name: tool.name,
2287
- description: Tool.getDescription(tool) ?? null,
2288
- parameters: Tool.getJsonSchema(tool) as { readonly [x: string]: Schema.Json },
2289
- strict
2639
+ parameters,
2640
+ strict,
2641
+ ...(Predicate.isNotUndefined(description) ? { description } : undefined)
2290
2642
  })
2291
2643
  }
2292
2644
 
@@ -2468,35 +2820,63 @@ const getStatus = (
2468
2820
  | Prompt.TextPart
2469
2821
  | Prompt.ToolCallPart
2470
2822
  | Prompt.ToolResultPart
2471
- ): typeof Generated.Message.Encoded["status"] | null => part.options.openai?.status ?? null
2823
+ ): typeof OpenAiSchema.MessageStatus.Encoded | null => part.options.openai?.status ?? null
2472
2824
  const getEncryptedContent = (
2473
2825
  part: Prompt.ReasoningPart
2474
2826
  ): string | null => part.options.openai?.encryptedContent ?? null
2475
2827
 
2476
2828
  const getImageDetail = (part: Prompt.FilePart): ImageDetail => part.options.openai?.imageDetail ?? "auto"
2477
2829
 
2478
- const makeItemIdMetadata = (itemId: string | undefined) => Predicate.isNotUndefined(itemId) ? { itemId } : undefined
2830
+ const makeItemIdMetadata = (itemId: string | undefined) => Predicate.isNotUndefined(itemId) ? { itemId } : {}
2479
2831
 
2480
2832
  const makeEncryptedContentMetadata = (encryptedContent: string | null | undefined) =>
2481
2833
  Predicate.isNotNullish(encryptedContent) ? { encryptedContent } : undefined
2482
2834
 
2483
- const prepareResponseFormat = ({ config, options }: {
2835
+ const unsupportedSchemaError = (error: unknown, method: string): AiError.AiError =>
2836
+ AiError.make({
2837
+ module: "OpenAiLanguageModel",
2838
+ method,
2839
+ reason: new AiError.UnsupportedSchemaError({
2840
+ description: error instanceof Error ? error.message : String(error)
2841
+ })
2842
+ })
2843
+
2844
+ const tryCodecTransform = <S extends Schema.Top>(schema: S, method: string) =>
2845
+ Effect.try({
2846
+ try: () => toCodecOpenAI(schema),
2847
+ catch: (error) => unsupportedSchemaError(error, method)
2848
+ })
2849
+
2850
+ const tryJsonSchema = <S extends Schema.Top>(schema: S, method: string) =>
2851
+ Effect.try({
2852
+ try: () => Tool.getJsonSchemaFromSchema(schema, { transformer: toCodecOpenAI }),
2853
+ catch: (error) => unsupportedSchemaError(error, method)
2854
+ })
2855
+
2856
+ const tryToolJsonSchema = <T extends Tool.Any>(tool: T, method: string) =>
2857
+ Effect.try({
2858
+ try: () => Tool.getJsonSchema(tool, { transformer: toCodecOpenAI }),
2859
+ catch: (error) => unsupportedSchemaError(error, method)
2860
+ })
2861
+
2862
+ const prepareResponseFormat = Effect.fnUntraced(function*({ config, options }: {
2484
2863
  readonly config: typeof Config.Service
2485
2864
  readonly options: LanguageModel.ProviderOptions
2486
- }): typeof Generated.TextResponseFormatConfiguration.Encoded => {
2865
+ }): Effect.fn.Return<typeof OpenAiSchema.TextResponseFormatConfiguration.Encoded, AiError.AiError> {
2487
2866
  if (options.responseFormat.type === "json") {
2488
2867
  const name = options.responseFormat.objectName
2489
2868
  const schema = options.responseFormat.schema
2869
+ const jsonSchema = yield* tryJsonSchema(schema, "prepareResponseFormat")
2490
2870
  return {
2491
2871
  type: "json_schema",
2492
2872
  name,
2493
2873
  description: AST.resolveDescription(schema.ast) ?? "Response with a JSON object",
2494
- schema: Tool.getJsonSchemaFromSchema(schema) as any,
2874
+ schema: jsonSchema,
2495
2875
  strict: config.strictJsonSchema ?? true
2496
2876
  }
2497
2877
  }
2498
2878
  return { type: "text" }
2499
- }
2879
+ })
2500
2880
 
2501
2881
  interface ModelCapabilities {
2502
2882
  readonly isReasoningModel: boolean
@@ -2570,7 +2950,42 @@ const getApprovalRequestIdMapping = (prompt: Prompt.Prompt): ReadonlyMap<string,
2570
2950
  return mapping
2571
2951
  }
2572
2952
 
2573
- const getUsage = (usage: Generated.ResponseUsage | null | undefined): Response.Usage => {
2953
+ const normalizeMcpToolCall = Effect.fnUntraced(function*<Tools extends ReadonlyArray<Tool.Any>>({
2954
+ toolNameMapper,
2955
+ toolParams,
2956
+ method
2957
+ }: {
2958
+ readonly toolNameMapper: Tool.NameMapper<Tools>
2959
+ readonly toolParams: unknown
2960
+ readonly method: string
2961
+ }): Effect.fn.Return<{
2962
+ readonly toolName: string
2963
+ readonly params: unknown
2964
+ }, AiError.AiError> {
2965
+ const toolName = toolNameMapper.getCustomName("mcp")
2966
+
2967
+ if (typeof toolParams !== "string") {
2968
+ return { toolName, params: toolParams }
2969
+ }
2970
+
2971
+ const params = yield* Effect.try({
2972
+ try: () => Tool.unsafeSecureJsonParse(toolParams),
2973
+ catch: (cause) =>
2974
+ AiError.make({
2975
+ module: "OpenAiLanguageModel",
2976
+ method,
2977
+ reason: new AiError.ToolParameterValidationError({
2978
+ toolName,
2979
+ toolParams,
2980
+ description: `Failed to securely JSON parse tool parameters: ${cause}`
2981
+ })
2982
+ })
2983
+ })
2984
+
2985
+ return { toolName, params }
2986
+ })
2987
+
2988
+ const getUsage = (usage: OpenAiSchema.ResponseUsage | null | undefined): Response.Usage => {
2574
2989
  if (Predicate.isNullish(usage)) {
2575
2990
  return {
2576
2991
  inputTokens: {
@@ -2589,8 +3004,8 @@ const getUsage = (usage: Generated.ResponseUsage | null | undefined): Response.U
2589
3004
 
2590
3005
  const inputTokens = usage.input_tokens
2591
3006
  const outputTokens = usage.output_tokens
2592
- const cachedTokens = usage.input_tokens_details.cached_tokens
2593
- const reasoningTokens = usage.output_tokens_details.reasoning_tokens
3007
+ const cachedTokens = getUsageTokenDetail(usage.input_tokens_details, "cached_tokens")
3008
+ const reasoningTokens = getUsageTokenDetail(usage.output_tokens_details, "reasoning_tokens")
2594
3009
 
2595
3010
  return {
2596
3011
  inputTokens: {
@@ -2606,3 +3021,64 @@ const getUsage = (usage: Generated.ResponseUsage | null | undefined): Response.U
2606
3021
  }
2607
3022
  }
2608
3023
  }
3024
+
3025
+ type ServiceTier = "default" | "auto" | "flex" | "scale" | "priority" | null
3026
+
3027
+ const toServiceTier = (value: string | undefined): {
3028
+ readonly metadata: {
3029
+ readonly openai: {
3030
+ readonly serviceTier: ServiceTier
3031
+ }
3032
+ }
3033
+ } | undefined => {
3034
+ switch (value) {
3035
+ case "default":
3036
+ case "auto":
3037
+ case "flex":
3038
+ case "scale":
3039
+ case "priority":
3040
+ return { metadata: { openai: { serviceTier: value } } }
3041
+ default:
3042
+ return undefined
3043
+ }
3044
+ }
3045
+
3046
+ const getUsageTokenDetail = (details: unknown, key: string): number =>
3047
+ Predicate.hasProperty(details, key) && typeof details[key] === "number" ? details[key] : 0
3048
+
3049
+ const transformToolCallParams = Effect.fnUntraced(function*<Tools extends ReadonlyArray<Tool.Any>>(
3050
+ tools: Tools,
3051
+ toolName: string,
3052
+ toolParams: unknown
3053
+ ): Effect.fn.Return<unknown, AiError.AiError> {
3054
+ const tool = tools.find((tool) => tool.name === toolName)
3055
+
3056
+ if (Predicate.isUndefined(tool)) {
3057
+ return yield* AiError.make({
3058
+ module: "OpenAiLanguageModel",
3059
+ method: "makeResponse",
3060
+ reason: new AiError.ToolNotFoundError({
3061
+ toolName,
3062
+ availableTools: tools.map((tool) => tool.name)
3063
+ })
3064
+ })
3065
+ }
3066
+
3067
+ const { codec } = yield* tryCodecTransform(tool.parametersSchema, "makeResponse")
3068
+
3069
+ const transform = Schema.decodeEffect(codec)
3070
+
3071
+ return yield* (
3072
+ transform(toolParams) as Effect.Effect<unknown, Schema.SchemaError>
3073
+ ).pipe(Effect.mapError((error) =>
3074
+ AiError.make({
3075
+ module: "OpenAiLanguageModel",
3076
+ method: "makeResponse",
3077
+ reason: new AiError.ToolParameterValidationError({
3078
+ toolName,
3079
+ toolParams,
3080
+ description: error.issue.toString()
3081
+ })
3082
+ })
3083
+ ))
3084
+ })