@effect/ai-openai 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 (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 +167 -27
  6. package/dist/OpenAiClient.d.ts.map +1 -1
  7. package/dist/OpenAiClient.js +337 -44
  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 +114 -10
  14. package/dist/OpenAiConfig.d.ts.map +1 -1
  15. package/dist/OpenAiConfig.js +68 -7
  16. package/dist/OpenAiConfig.js.map +1 -1
  17. package/dist/OpenAiEmbeddingModel.d.ts +213 -0
  18. package/dist/OpenAiEmbeddingModel.d.ts.map +1 -0
  19. package/dist/OpenAiEmbeddingModel.js +219 -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 +384 -62
  25. package/dist/OpenAiLanguageModel.d.ts.map +1 -1
  26. package/dist/OpenAiLanguageModel.js +416 -166
  27. package/dist/OpenAiLanguageModel.js.map +1 -1
  28. package/dist/OpenAiSchema.d.ts +2298 -0
  29. package/dist/OpenAiSchema.d.ts.map +1 -0
  30. package/dist/OpenAiSchema.js +814 -0
  31. package/dist/OpenAiSchema.js.map +1 -0
  32. package/dist/OpenAiTelemetry.d.ts +59 -18
  33. package/dist/OpenAiTelemetry.d.ts.map +1 -1
  34. package/dist/OpenAiTelemetry.js +35 -8
  35. package/dist/OpenAiTelemetry.js.map +1 -1
  36. package/dist/OpenAiTool.d.ts +157 -62
  37. package/dist/OpenAiTool.d.ts.map +1 -1
  38. package/dist/OpenAiTool.js +134 -39
  39. package/dist/OpenAiTool.js.map +1 -1
  40. package/dist/index.d.ts +19 -33
  41. package/dist/index.d.ts.map +1 -1
  42. package/dist/index.js +19 -33
  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 +513 -95
  49. package/src/OpenAiClientGenerated.ts +202 -0
  50. package/src/OpenAiConfig.ts +115 -11
  51. package/src/OpenAiEmbeddingModel.ts +357 -0
  52. package/src/OpenAiError.ts +170 -35
  53. package/src/OpenAiLanguageModel.ts +802 -167
  54. package/src/OpenAiSchema.ts +1289 -0
  55. package/src/OpenAiTelemetry.ts +81 -23
  56. package/src/OpenAiTool.ts +135 -40
  57. package/src/index.ts +22 -33
  58. package/src/internal/errors.ts +6 -4
@@ -1,26 +1,58 @@
1
1
  /**
2
- * OpenAI Language Model implementation.
2
+ * The `OpenAiLanguageModel` module provides the OpenAI Responses API
3
+ * implementation of Effect AI's `LanguageModel` service. It translates Effect
4
+ * AI prompts, files, tools, structured output requests, reasoning metadata, and
5
+ * provider options into OpenAI response requests, then converts OpenAI
6
+ * responses and streams back into Effect AI response parts.
3
7
  *
4
- * Provides a LanguageModel implementation for OpenAI's responses API,
5
- * supporting text generation, structured output, tool calling, and streaming.
8
+ * **Mental model**
6
9
  *
7
- * @since 1.0.0
10
+ * `OpenAiClient` owns HTTP transport and provider calls. This module owns
11
+ * protocol translation: request assembly, tool choice conversion, structured
12
+ * output codecs, streaming event handling, response metadata, and GenAI
13
+ * telemetry annotations. {@link model}, {@link layer}, and {@link make} all
14
+ * build the same OpenAI-backed `LanguageModel.LanguageModel` service from a
15
+ * model id and optional request defaults.
16
+ *
17
+ * **Common tasks**
18
+ *
19
+ * - Provide an OpenAI-backed `LanguageModel.LanguageModel` from an existing
20
+ * `OpenAiClient`
21
+ * - Generate text or stream text through Effect AI's provider-neutral language
22
+ * model API
23
+ * - Use OpenAI provider metadata for files, reasoning items, tool calls, and
24
+ * response parts
25
+ * - Scope Responses API defaults with {@link Config} and
26
+ * {@link withConfigOverride}
27
+ *
28
+ * **Gotchas**
29
+ *
30
+ * - Some OpenAI model families receive system instructions as developer
31
+ * messages because the Responses API treats them differently.
32
+ * - File prompt parts are sent either as provider file ids or as base64
33
+ * content, depending on `fileIdPrefixes`.
34
+ * - Structured output and tool-call behavior depends on both Effect AI tool
35
+ * definitions and OpenAI model capabilities.
36
+ *
37
+ * @since 4.0.0
8
38
  */
39
+ import * as Context from "effect/Context";
9
40
  import * as DateTime from "effect/DateTime";
10
41
  import * as Effect from "effect/Effect";
11
42
  import * as Encoding from "effect/Encoding";
12
43
  import { dual } from "effect/Function";
13
44
  import * as Layer from "effect/Layer";
45
+ import * as Option from "effect/Option";
14
46
  import * as Predicate from "effect/Predicate";
15
47
  import * as Redactable from "effect/Redactable";
16
48
  import * as Schema from "effect/Schema";
17
49
  import * as AST from "effect/SchemaAST";
18
- import * as ServiceMap from "effect/ServiceMap";
19
50
  import * as Stream from "effect/Stream";
20
51
  import * as AiError from "effect/unstable/ai/AiError";
21
52
  import * as IdGenerator from "effect/unstable/ai/IdGenerator";
22
53
  import * as LanguageModel from "effect/unstable/ai/LanguageModel";
23
54
  import * as AiModel from "effect/unstable/ai/Model";
55
+ import { toCodecOpenAI } from "effect/unstable/ai/OpenAiStructuredOutput";
24
56
  import * as Tool from "effect/unstable/ai/Tool";
25
57
  import * as Generated from "./Generated.js";
26
58
  import * as InternalUtilities from "./internal/utilities.js";
@@ -34,36 +66,75 @@ const SharedModelIds = Generated.ModelIdsShared.members[1];
34
66
  /**
35
67
  * Service definition for OpenAI language model configuration.
36
68
  *
37
- * @since 1.0.0
69
+ * **When to use**
70
+ *
71
+ * Use when you need to provide OpenAI Responses API request defaults through
72
+ * Effect context for language model operations.
73
+ *
74
+ * **Details**
75
+ *
76
+ * Config values are merged with the config object passed to `model`, `make`, or
77
+ * `layer`, with scoped context values taking precedence.
78
+ *
79
+ * @see {@link withConfigOverride} for scoping language model request overrides
80
+ *
38
81
  * @category services
82
+ * @since 4.0.0
39
83
  */
40
- export class Config extends /*#__PURE__*/ServiceMap.Service()("@effect/ai-openai/OpenAiLanguageModel/Config") {}
84
+ export class Config extends /*#__PURE__*/Context.Service()("@effect/ai-openai/OpenAiLanguageModel/Config") {}
41
85
  // =============================================================================
42
86
  // Language Model
43
87
  // =============================================================================
44
88
  /**
45
- * @since 1.0.0
89
+ * Creates an OpenAI model descriptor that can be provided with
90
+ * `Effect.provide`.
91
+ *
92
+ * **When to use**
93
+ *
94
+ * Use when you want an OpenAI language model value that carries provider and
95
+ * model metadata and can be supplied directly to an Effect program.
96
+ *
97
+ * @see {@link layer} for creating a `LanguageModel.LanguageModel` layer directly
98
+ * @see {@link make} for constructing the language model service effectfully
99
+ *
46
100
  * @category constructors
101
+ * @since 4.0.0
47
102
  */
48
- export const model = (model, config) => AiModel.make("openai", layer({
103
+ export const model = (model, config) => AiModel.make("openai", model, layer({
49
104
  model,
50
105
  config
51
106
  }));
52
107
  // TODO
53
108
  // /**
54
- // * @since 1.0.0
109
+ // * @since 4.0.0
55
110
  // * @category constructors
56
111
  // */
57
112
  // export const modelWithTokenizer = (
58
113
  // model: (string & {}) | Model,
59
114
  // config?: Omit<typeof Config.Service, "model">
60
115
  // ): AiModel.Model<"openai", LanguageModel.LanguageModel | Tokenizer.Tokenizer, OpenAiClient> =>
61
- // AiModel.make("openai", layerWithTokenizer({ model, config }))
116
+ // AiModel.make("openai", model, layerWithTokenizer({ model, config }))
62
117
  /**
63
- * Creates an OpenAI language model service.
118
+ * Creates an OpenAI `LanguageModel` service from a model identifier and
119
+ * optional request defaults.
120
+ *
121
+ * **When to use**
122
+ *
123
+ * Use when an Effect needs to construct a `LanguageModel.Service` value backed
124
+ * by `OpenAiClient`.
125
+ *
126
+ * **Details**
127
+ *
128
+ * The returned effect requires `OpenAiClient`. Request defaults from the
129
+ * `config` option are merged with any `Config` service in the context, with
130
+ * context values taking precedence. The service supports both `generateText`
131
+ * and `streamText`.
132
+ *
133
+ * @see {@link layer} for providing the service as a `Layer`
134
+ * @see {@link model} for creating a model descriptor for `Effect.provide`
64
135
  *
65
- * @since 1.0.0
66
136
  * @category constructors
137
+ * @since 4.0.0
67
138
  */
68
139
  export const make = /*#__PURE__*/Effect.fnUntraced(function* ({
69
140
  model,
@@ -71,7 +142,7 @@ export const make = /*#__PURE__*/Effect.fnUntraced(function* ({
71
142
  }) {
72
143
  const client = yield* OpenAiClient;
73
144
  const makeConfig = Effect.gen(function* () {
74
- const services = yield* Effect.services();
145
+ const services = yield* Effect.context();
75
146
  return {
76
147
  model,
77
148
  ...providerConfig,
@@ -100,28 +171,31 @@ export const make = /*#__PURE__*/Effect.fnUntraced(function* ({
100
171
  options,
101
172
  toolNameMapper
102
173
  });
103
- const responseFormat = prepareResponseFormat({
174
+ const responseFormat = yield* prepareResponseFormat({
104
175
  config,
105
176
  options
106
177
  });
178
+ const {
179
+ fileIdPrefixes: _fip,
180
+ strictJsonSchema: _sjs,
181
+ ...apiConfig
182
+ } = config;
107
183
  const request = {
108
- ...config,
184
+ ...apiConfig,
109
185
  input: messages,
110
- include: include.size > 0 ? Array.from(include) : null,
186
+ include: include.size > 0 ? Array.from(include) : undefined,
111
187
  text: {
112
- verbosity: config.text?.verbosity ?? null,
188
+ verbosity: config.text?.verbosity ?? undefined,
113
189
  format: responseFormat
114
- },
115
- ...(Predicate.isNotUndefined(tools) ? {
116
- tools
117
- } : undefined),
118
- ...(Predicate.isNotUndefined(toolChoice) ? {
119
- tool_choice: toolChoice
120
- } : undefined)
190
+ }
121
191
  };
192
+ if (tools) request.tools = tools;
193
+ if (toolChoice) request.tool_choice = toolChoice;
194
+ if (options.previousResponseId) request.previous_response_id = options.previousResponseId;
122
195
  return request;
123
196
  });
124
197
  return yield* LanguageModel.make({
198
+ codecTransformer: toCodecOpenAI,
125
199
  generateText: Effect.fnUntraced(function* (options) {
126
200
  const config = yield* makeConfig;
127
201
  const toolNameMapper = new Tool.NameMapper(options.tools);
@@ -164,17 +238,49 @@ export const make = /*#__PURE__*/Effect.fnUntraced(function* ({
164
238
  });
165
239
  });
166
240
  /**
167
- * Creates a layer for the OpenAI language model.
241
+ * Creates a layer that provides the OpenAI `LanguageModel.LanguageModel`
242
+ * service.
243
+ *
244
+ * **When to use**
245
+ *
246
+ * Use when composing application layers and you want OpenAI to satisfy
247
+ * `LanguageModel.LanguageModel` while supplying `OpenAiClient` from another
248
+ * layer.
249
+ *
250
+ * **Details**
251
+ *
252
+ * The `config` option supplies request defaults for the selected model. Scoped
253
+ * values from `withConfigOverride` are merged when each request is built and
254
+ * take precedence over these defaults.
255
+ *
256
+ * @see {@link make} for constructing the language model service effectfully
257
+ * @see {@link model} for creating a model descriptor for `Effect.provide`
258
+ * @see {@link withConfigOverride} for scoped request configuration overrides
168
259
  *
169
- * @since 1.0.0
170
260
  * @category layers
261
+ * @since 4.0.0
171
262
  */
172
263
  export const layer = options => Layer.effect(LanguageModel.LanguageModel, make(options));
173
264
  /**
174
- * Provides config overrides for OpenAI language model operations.
265
+ * Provides scoped config overrides for OpenAI language model operations.
266
+ *
267
+ * **When to use**
268
+ *
269
+ * Use to apply OpenAI Responses API config overrides around one or more
270
+ * language model operations without changing the defaults passed to `model`,
271
+ * `make`, or `layer`.
272
+ *
273
+ * **Details**
274
+ *
275
+ * The override is dual, so it can be used in pipe form or as
276
+ * `withConfigOverride(effect, overrides)`. Overrides are merged with any
277
+ * existing `Config` service in the current context, and the override values take
278
+ * precedence.
279
+ *
280
+ * @see {@link Config} for the scoped configuration service consumed by this function
175
281
  *
176
- * @since 1.0.0
177
282
  * @category configuration
283
+ * @since 4.0.0
178
284
  */
179
285
  export const withConfigOverride = /*#__PURE__*/dual(2, (self, overrides) => Effect.flatMap(Effect.serviceOption(Config), config => Effect.provideService(self, Config, {
180
286
  ...(config._tag === "Some" ? config.value : {}),
@@ -207,14 +313,15 @@ const prepareMessages = /*#__PURE__*/Effect.fnUntraced(function* ({
207
313
  if (config.store === false && capabilities.isReasoningModel) {
208
314
  include.add("reasoning.encrypted_content");
209
315
  }
210
- if (Predicate.isNotUndefined(codeInterpreterTool)) {
316
+ if (codeInterpreterTool) {
211
317
  include.add("code_interpreter_call.outputs");
212
318
  }
213
- if (Predicate.isNotUndefined(webSearchTool) || Predicate.isNotUndefined(webSearchPreviewTool)) {
319
+ if (webSearchTool || webSearchPreviewTool) {
214
320
  include.add("web_search_call.action.sources");
215
321
  }
216
322
  const messages = [];
217
- for (const message of options.prompt.content) {
323
+ const prompt = options.incrementalPrompt ?? options.prompt;
324
+ for (const message of prompt.content) {
218
325
  switch (message.role) {
219
326
  case "system":
220
327
  {
@@ -378,7 +485,9 @@ const prepareMessages = /*#__PURE__*/Effect.fnUntraced(function* ({
378
485
  type: "reasoning",
379
486
  id,
380
487
  summary: summaryParts,
381
- encrypted_content: encryptedContent ?? null
488
+ ...(Predicate.isNotNull(encryptedContent) ? {
489
+ encrypted_content: encryptedContent
490
+ } : undefined)
382
491
  };
383
492
  messages.push(reasoningMessages[id]);
384
493
  } else {
@@ -569,13 +678,15 @@ const buildHttpRequestDetails = request => ({
569
678
  method: request.method,
570
679
  url: request.url,
571
680
  urlParams: Array.from(request.urlParams),
572
- hash: request.hash,
681
+ hash: Option.getOrUndefined(request.hash),
573
682
  headers: Redactable.redact(request.headers)
574
683
  });
575
684
  const buildHttpResponseDetails = response => ({
576
685
  status: response.status,
577
686
  headers: Redactable.redact(response.headers)
578
687
  });
688
+ const knownResponseStreamEventTypes = /*#__PURE__*/new Set(["response.created", "response.completed", "response.incomplete", "response.failed", "response.output_item.added", "response.output_item.done", "response.output_text.delta", "response.output_text.annotation.added", "response.reasoning_summary_part.added", "response.reasoning_summary_part.done", "response.reasoning_summary_text.delta", "response.function_call_arguments.delta", "response.function_call_arguments.done", "response.code_interpreter_call_code.delta", "response.code_interpreter_call_code.done", "response.apply_patch_call_operation_diff.delta", "response.apply_patch_call_operation_diff.done", "response.image_generation_call.partial_image", "error"]);
689
+ const isKnownResponseStreamEvent = event => knownResponseStreamEventTypes.has(event.type);
579
690
  const makeResponse = /*#__PURE__*/Effect.fnUntraced(function* ({
580
691
  options,
581
692
  rawResponse,
@@ -609,9 +720,7 @@ const makeResponse = /*#__PURE__*/Effect.fnUntraced(function* ({
609
720
  operation: part.operation
610
721
  },
611
722
  metadata: {
612
- openai: {
613
- ...makeItemIdMetadata(part.id)
614
- }
723
+ openai: makeItemIdMetadata(part.id)
615
724
  }
616
725
  });
617
726
  break;
@@ -669,9 +778,8 @@ const makeResponse = /*#__PURE__*/Effect.fnUntraced(function* ({
669
778
  {
670
779
  hasToolCalls = true;
671
780
  const toolName = part.name;
672
- const toolParams = part.arguments;
673
- const params = yield* Effect.try({
674
- try: () => Tool.unsafeSecureJsonParse(toolParams),
781
+ const toolParams = yield* Effect.try({
782
+ try: () => Tool.unsafeSecureJsonParse(part.arguments),
675
783
  catch: cause => AiError.make({
676
784
  module: "OpenAiLanguageModel",
677
785
  method: "makeResponse",
@@ -682,15 +790,14 @@ const makeResponse = /*#__PURE__*/Effect.fnUntraced(function* ({
682
790
  })
683
791
  })
684
792
  });
793
+ const params = yield* transformToolCallParams(options.tools, part.name, toolParams);
685
794
  parts.push({
686
795
  type: "tool-call",
687
796
  id: part.call_id,
688
797
  name: toolName,
689
798
  params,
690
799
  metadata: {
691
- openai: {
692
- ...makeItemIdMetadata(part.id)
693
- }
800
+ openai: makeItemIdMetadata(part.id)
694
801
  }
695
802
  });
696
803
  break;
@@ -727,9 +834,7 @@ const makeResponse = /*#__PURE__*/Effect.fnUntraced(function* ({
727
834
  action: part.action
728
835
  },
729
836
  metadata: {
730
- openai: {
731
- ...makeItemIdMetadata(part.id)
732
- }
837
+ openai: makeItemIdMetadata(part.id)
733
838
  }
734
839
  });
735
840
  break;
@@ -737,12 +842,19 @@ const makeResponse = /*#__PURE__*/Effect.fnUntraced(function* ({
737
842
  case "mcp_call":
738
843
  {
739
844
  const toolId = Predicate.isNotNullish(part.approval_request_id) ? approvalRequests.get(part.approval_request_id) ?? part.id : part.id;
740
- const toolName = `mcp.${part.name}`;
845
+ const {
846
+ toolName,
847
+ params
848
+ } = yield* normalizeMcpToolCall({
849
+ toolNameMapper,
850
+ toolParams: part.arguments,
851
+ method: "makeResponse"
852
+ });
741
853
  parts.push({
742
854
  type: "tool-call",
743
855
  id: toolId,
744
856
  name: toolName,
745
- params: part.arguments,
857
+ params,
746
858
  providerExecuted: true
747
859
  });
748
860
  parts.push({
@@ -752,7 +864,7 @@ const makeResponse = /*#__PURE__*/Effect.fnUntraced(function* ({
752
864
  isFailure: false,
753
865
  providerExecuted: true,
754
866
  result: {
755
- type: "call",
867
+ type: "mcp_call",
756
868
  name: part.name,
757
869
  arguments: part.arguments,
758
870
  server_label: part.server_label,
@@ -764,9 +876,7 @@ const makeResponse = /*#__PURE__*/Effect.fnUntraced(function* ({
764
876
  } : undefined)
765
877
  },
766
878
  metadata: {
767
- openai: {
768
- ...makeItemIdMetadata(part.id)
769
- }
879
+ openai: makeItemIdMetadata(part.id)
770
880
  }
771
881
  });
772
882
  break;
@@ -780,18 +890,13 @@ const makeResponse = /*#__PURE__*/Effect.fnUntraced(function* ({
780
890
  {
781
891
  const approvalRequestId = part.approval_request_id ?? part.id;
782
892
  const toolId = yield* idGenerator.generateId();
783
- const toolName = `mcp.${part.name}`;
784
- const params = yield* Effect.try({
785
- try: () => Tool.unsafeSecureJsonParse(part.arguments),
786
- catch: cause => AiError.make({
787
- module: "OpenAiLanguageModel",
788
- method: "makeResponse",
789
- reason: new AiError.ToolParameterValidationError({
790
- toolName,
791
- toolParams: {},
792
- description: `Failed securely JSON parse tool parameters: ${cause}`
793
- })
794
- })
893
+ const {
894
+ toolName,
895
+ params
896
+ } = yield* normalizeMcpToolCall({
897
+ toolNameMapper,
898
+ toolParams: part.arguments,
899
+ method: "makeResponse"
795
900
  });
796
901
  parts.push({
797
902
  type: "tool-call",
@@ -952,9 +1057,7 @@ const makeResponse = /*#__PURE__*/Effect.fnUntraced(function* ({
952
1057
  action: part.action
953
1058
  },
954
1059
  metadata: {
955
- openai: {
956
- ...makeItemIdMetadata(part.id)
957
- }
1060
+ openai: makeItemIdMetadata(part.id)
958
1061
  }
959
1062
  });
960
1063
  break;
@@ -990,13 +1093,7 @@ const makeResponse = /*#__PURE__*/Effect.fnUntraced(function* ({
990
1093
  reason: finishReason,
991
1094
  usage: getUsage(rawResponse.usage),
992
1095
  response: buildHttpResponseDetails(response),
993
- ...(rawResponse.service_tier && {
994
- metadata: {
995
- openai: {
996
- serviceTier: rawResponse.service_tier
997
- }
998
- }
999
- })
1096
+ ...toServiceTier(rawResponse.service_tier)
1000
1097
  });
1001
1098
  return parts;
1002
1099
  });
@@ -1015,11 +1112,29 @@ const makeStreamResponse = /*#__PURE__*/Effect.fnUntraced(function* ({
1015
1112
  const activeAnnotations = [];
1016
1113
  // Track active reasoning items with state machine for proper concluding logic
1017
1114
  const activeReasoning = {};
1115
+ const getOrCreateReasoningPart = (itemId, encryptedContent) => {
1116
+ const activePart = activeReasoning[itemId];
1117
+ if (Predicate.isNotUndefined(activePart)) {
1118
+ if (Predicate.isNotNullish(encryptedContent)) {
1119
+ activePart.encryptedContent = encryptedContent;
1120
+ }
1121
+ return activePart;
1122
+ }
1123
+ const reasoningPart = {
1124
+ encryptedContent: Predicate.isNotNullish(encryptedContent) ? encryptedContent : undefined,
1125
+ summaryParts: {}
1126
+ };
1127
+ activeReasoning[itemId] = reasoningPart;
1128
+ return reasoningPart;
1129
+ };
1018
1130
  // Track active tool calls with optional provider-specific state
1019
1131
  const activeToolCalls = {};
1020
1132
  const webSearchTool = options.tools.find(tool => Tool.isProviderDefined(tool) && (tool.name === "OpenAiWebSearch" || tool.name === "OpenAiWebSearchPreview"));
1021
1133
  return stream.pipe(Stream.mapEffect(Effect.fnUntraced(function* (event) {
1022
1134
  const parts = [];
1135
+ if (!isKnownResponseStreamEvent(event)) {
1136
+ return parts;
1137
+ }
1023
1138
  switch (event.type) {
1024
1139
  case "response.created":
1025
1140
  {
@@ -1050,13 +1165,7 @@ const makeStreamResponse = /*#__PURE__*/Effect.fnUntraced(function* ({
1050
1165
  reason: InternalUtilities.resolveFinishReason(event.response.incomplete_details?.reason, hasToolCalls),
1051
1166
  usage: getUsage(event.response.usage),
1052
1167
  response: buildHttpResponseDetails(response),
1053
- ...(event.response.service_tier && {
1054
- metadata: {
1055
- openai: {
1056
- serviceTier: event.response.service_tier
1057
- }
1058
- }
1059
- })
1168
+ ...toServiceTier(event.response.service_tier)
1060
1169
  });
1061
1170
  break;
1062
1171
  }
@@ -1157,7 +1266,10 @@ const makeStreamResponse = /*#__PURE__*/Effect.fnUntraced(function* ({
1157
1266
  {
1158
1267
  activeToolCalls[event.output_index] = {
1159
1268
  id: event.item.call_id,
1160
- name: event.item.name
1269
+ name: event.item.name,
1270
+ functionCall: {
1271
+ emitted: false
1272
+ }
1161
1273
  };
1162
1274
  parts.push({
1163
1275
  type: "tool-params-start",
@@ -1195,39 +1307,34 @@ const makeStreamResponse = /*#__PURE__*/Effect.fnUntraced(function* ({
1195
1307
  type: "text-start",
1196
1308
  id: event.item.id,
1197
1309
  metadata: {
1198
- openai: {
1199
- ...makeItemIdMetadata(event.item.id)
1200
- }
1310
+ openai: makeItemIdMetadata(event.item.id)
1201
1311
  }
1202
1312
  });
1203
1313
  break;
1204
1314
  }
1205
1315
  case "reasoning":
1206
1316
  {
1207
- const encryptedContent = event.item.encrypted_content ?? undefined;
1208
- activeReasoning[event.item.id] = {
1209
- encryptedContent,
1210
- summaryParts: {
1211
- 0: "active"
1212
- }
1213
- };
1214
- parts.push({
1215
- type: "reasoning-start",
1216
- id: `${event.item.id}:0`,
1217
- metadata: {
1218
- openai: {
1219
- ...makeItemIdMetadata(event.item.id),
1220
- ...makeEncryptedContentMetadata(event.item.encrypted_content)
1317
+ const reasoningPart = getOrCreateReasoningPart(event.item.id, event.item.encrypted_content);
1318
+ if (Predicate.isUndefined(reasoningPart.summaryParts[0])) {
1319
+ reasoningPart.summaryParts[0] = "active";
1320
+ parts.push({
1321
+ type: "reasoning-start",
1322
+ id: `${event.item.id}:0`,
1323
+ metadata: {
1324
+ openai: {
1325
+ ...makeItemIdMetadata(event.item.id),
1326
+ ...makeEncryptedContentMetadata(reasoningPart.encryptedContent)
1327
+ }
1221
1328
  }
1222
- }
1223
- });
1329
+ });
1330
+ }
1224
1331
  break;
1225
1332
  }
1226
1333
  case "shell_call":
1227
1334
  {
1228
1335
  const toolName = toolNameMapper.getCustomName("shell");
1229
1336
  activeToolCalls[event.output_index] = {
1230
- id: event.item.id,
1337
+ id: event.item.id ?? event.item.call_id,
1231
1338
  name: toolName
1232
1339
  };
1233
1340
  break;
@@ -1272,7 +1379,7 @@ const makeStreamResponse = /*#__PURE__*/Effect.fnUntraced(function* ({
1272
1379
  parts.push({
1273
1380
  type: "tool-params-delta",
1274
1381
  id: toolCall.id,
1275
- delta: InternalUtilities.escapeJSONDelta(event.item.operation.diff)
1382
+ delta: InternalUtilities.escapeJSONDelta(event.item.operation.diff ?? "")
1276
1383
  });
1277
1384
  }
1278
1385
  parts.push({
@@ -1298,9 +1405,7 @@ const makeStreamResponse = /*#__PURE__*/Effect.fnUntraced(function* ({
1298
1405
  operation: event.item.operation
1299
1406
  },
1300
1407
  metadata: {
1301
- openai: {
1302
- ...makeItemIdMetadata(event.item.id)
1303
- }
1408
+ openai: makeItemIdMetadata(event.item.id)
1304
1409
  }
1305
1410
  });
1306
1411
  }
@@ -1372,12 +1477,17 @@ const makeStreamResponse = /*#__PURE__*/Effect.fnUntraced(function* ({
1372
1477
  }
1373
1478
  case "function_call":
1374
1479
  {
1480
+ const toolCall = activeToolCalls[event.output_index];
1481
+ if (Predicate.isNotUndefined(toolCall?.functionCall?.emitted) && toolCall.functionCall.emitted) {
1482
+ delete activeToolCalls[event.output_index];
1483
+ break;
1484
+ }
1375
1485
  delete activeToolCalls[event.output_index];
1376
1486
  hasToolCalls = true;
1377
1487
  const toolName = event.item.name;
1378
- const toolParams = event.item.arguments;
1379
- const params = yield* Effect.try({
1380
- try: () => Tool.unsafeSecureJsonParse(toolParams),
1488
+ const toolArgs = event.item.arguments;
1489
+ const toolParams = yield* Effect.try({
1490
+ try: () => Tool.unsafeSecureJsonParse(toolArgs),
1381
1491
  catch: cause => AiError.make({
1382
1492
  module: "OpenAiLanguageModel",
1383
1493
  method: "makeStreamResponse",
@@ -1388,6 +1498,7 @@ const makeStreamResponse = /*#__PURE__*/Effect.fnUntraced(function* ({
1388
1498
  })
1389
1499
  })
1390
1500
  });
1501
+ const params = yield* transformToolCallParams(options.tools, toolName, toolParams);
1391
1502
  parts.push({
1392
1503
  type: "tool-params-end",
1393
1504
  id: event.item.call_id
@@ -1398,9 +1509,7 @@ const makeStreamResponse = /*#__PURE__*/Effect.fnUntraced(function* ({
1398
1509
  name: toolName,
1399
1510
  params,
1400
1511
  metadata: {
1401
- openai: {
1402
- ...makeItemIdMetadata(event.item.id)
1403
- }
1512
+ openai: makeItemIdMetadata(event.item.id)
1404
1513
  }
1405
1514
  });
1406
1515
  break;
@@ -1431,9 +1540,7 @@ const makeStreamResponse = /*#__PURE__*/Effect.fnUntraced(function* ({
1431
1540
  action: event.item.action
1432
1541
  },
1433
1542
  metadata: {
1434
- openai: {
1435
- ...makeItemIdMetadata(event.item.id)
1436
- }
1543
+ openai: makeItemIdMetadata(event.item.id)
1437
1544
  }
1438
1545
  });
1439
1546
  break;
@@ -1443,12 +1550,19 @@ const makeStreamResponse = /*#__PURE__*/Effect.fnUntraced(function* ({
1443
1550
  const approvalRequestId = event.item.approval_request_id;
1444
1551
  // Track approval with our own tool call identifiers
1445
1552
  const toolId = Predicate.isNotNullish(approvalRequestId) ? streamApprovalRequests.get(approvalRequestId) ?? approvalRequests.get(approvalRequestId) ?? event.item.id : event.item.id;
1446
- const toolName = `mcp.${event.item.name}`;
1553
+ const {
1554
+ toolName,
1555
+ params
1556
+ } = yield* normalizeMcpToolCall({
1557
+ toolNameMapper,
1558
+ toolParams: event.item.arguments,
1559
+ method: "makeStreamResponse"
1560
+ });
1447
1561
  parts.push({
1448
1562
  type: "tool-call",
1449
1563
  id: toolId,
1450
1564
  name: toolName,
1451
- params: event.item.arguments,
1565
+ params,
1452
1566
  providerExecuted: true
1453
1567
  });
1454
1568
  parts.push({
@@ -1458,7 +1572,7 @@ const makeStreamResponse = /*#__PURE__*/Effect.fnUntraced(function* ({
1458
1572
  isFailure: false,
1459
1573
  providerExecuted: true,
1460
1574
  result: {
1461
- type: "call",
1575
+ type: "mcp_call",
1462
1576
  name: event.item.name,
1463
1577
  arguments: event.item.arguments,
1464
1578
  server_label: event.item.server_label,
@@ -1470,9 +1584,7 @@ const makeStreamResponse = /*#__PURE__*/Effect.fnUntraced(function* ({
1470
1584
  } : undefined)
1471
1585
  },
1472
1586
  metadata: {
1473
- openai: {
1474
- ...makeItemIdMetadata(event.item.id)
1475
- }
1587
+ openai: makeItemIdMetadata(event.item.id)
1476
1588
  }
1477
1589
  });
1478
1590
  break;
@@ -1487,12 +1599,19 @@ const makeStreamResponse = /*#__PURE__*/Effect.fnUntraced(function* ({
1487
1599
  const toolId = yield* idGenerator.generateId();
1488
1600
  const approvalRequestId = event.item.approval_request_id ?? event.item.id;
1489
1601
  streamApprovalRequests.set(approvalRequestId, toolId);
1490
- const toolName = `mcp.${event.item.name}`;
1602
+ const {
1603
+ toolName,
1604
+ params
1605
+ } = yield* normalizeMcpToolCall({
1606
+ toolNameMapper,
1607
+ toolParams: event.item.arguments,
1608
+ method: "makeStreamResponse"
1609
+ });
1491
1610
  parts.push({
1492
1611
  type: "tool-call",
1493
1612
  id: toolId,
1494
1613
  name: toolName,
1495
- params: event.item.arguments,
1614
+ params,
1496
1615
  providerExecuted: true
1497
1616
  });
1498
1617
  parts.push({
@@ -1521,7 +1640,7 @@ const makeStreamResponse = /*#__PURE__*/Effect.fnUntraced(function* ({
1521
1640
  }
1522
1641
  case "reasoning":
1523
1642
  {
1524
- const reasoningPart = activeReasoning[event.item.id];
1643
+ const reasoningPart = getOrCreateReasoningPart(event.item.id, event.item.encrypted_content);
1525
1644
  for (const [summaryIndex, status] of Object.entries(reasoningPart.summaryParts)) {
1526
1645
  if (status === "active" || status === "can-conclude") {
1527
1646
  parts.push({
@@ -1530,7 +1649,7 @@ const makeStreamResponse = /*#__PURE__*/Effect.fnUntraced(function* ({
1530
1649
  metadata: {
1531
1650
  openai: {
1532
1651
  ...makeItemIdMetadata(event.item.id),
1533
- ...makeEncryptedContentMetadata(event.item.encrypted_content)
1652
+ ...makeEncryptedContentMetadata(reasoningPart.encryptedContent)
1534
1653
  }
1535
1654
  }
1536
1655
  });
@@ -1545,15 +1664,13 @@ const makeStreamResponse = /*#__PURE__*/Effect.fnUntraced(function* ({
1545
1664
  const toolName = toolNameMapper.getCustomName("shell");
1546
1665
  parts.push({
1547
1666
  type: "tool-call",
1548
- id: event.item.id,
1667
+ id: event.item.id ?? event.item.call_id,
1549
1668
  name: toolName,
1550
1669
  params: {
1551
1670
  action: event.item.action
1552
1671
  },
1553
1672
  metadata: {
1554
- openai: {
1555
- ...makeItemIdMetadata(event.item.id)
1556
- }
1673
+ openai: makeItemIdMetadata(event.item.id)
1557
1674
  }
1558
1675
  });
1559
1676
  break;
@@ -1670,6 +1787,41 @@ const makeStreamResponse = /*#__PURE__*/Effect.fnUntraced(function* ({
1670
1787
  }
1671
1788
  break;
1672
1789
  }
1790
+ case "response.function_call_arguments.done":
1791
+ {
1792
+ const toolCall = activeToolCalls[event.output_index];
1793
+ if (Predicate.isNotUndefined(toolCall?.functionCall) && !toolCall.functionCall.emitted) {
1794
+ hasToolCalls = true;
1795
+ const toolParams = yield* Effect.try({
1796
+ try: () => Tool.unsafeSecureJsonParse(event.arguments),
1797
+ catch: cause => AiError.make({
1798
+ module: "OpenAiLanguageModel",
1799
+ method: "makeStreamResponse",
1800
+ reason: new AiError.ToolParameterValidationError({
1801
+ toolName: toolCall.name,
1802
+ toolParams: {},
1803
+ description: `Failed securely JSON parse tool parameters: ${cause}`
1804
+ })
1805
+ })
1806
+ });
1807
+ const params = yield* transformToolCallParams(options.tools, toolCall.name, toolParams);
1808
+ parts.push({
1809
+ type: "tool-params-end",
1810
+ id: toolCall.id
1811
+ });
1812
+ parts.push({
1813
+ type: "tool-call",
1814
+ id: toolCall.id,
1815
+ name: toolCall.name,
1816
+ params,
1817
+ metadata: {
1818
+ openai: makeItemIdMetadata(event.item_id)
1819
+ }
1820
+ });
1821
+ toolCall.functionCall.emitted = true;
1822
+ }
1823
+ break;
1824
+ }
1673
1825
  case "response.apply_patch_call_operation_diff.delta":
1674
1826
  {
1675
1827
  const toolCall = activeToolCalls[event.output_index];
@@ -1765,28 +1917,27 @@ const makeStreamResponse = /*#__PURE__*/Effect.fnUntraced(function* ({
1765
1917
  }
1766
1918
  case "response.reasoning_summary_part.added":
1767
1919
  {
1768
- // The first reasoning start is pushed in the `response.output_item.added` block
1920
+ const reasoningPart = getOrCreateReasoningPart(event.item_id);
1769
1921
  if (event.summary_index > 0) {
1770
- const reasoningPart = activeReasoning[event.item_id];
1771
- if (Predicate.isNotUndefined(reasoningPart)) {
1772
- // Conclude all can-conclude parts before starting new one
1773
- for (const [summaryIndex, status] of Object.entries(reasoningPart.summaryParts)) {
1774
- if (status === "can-conclude") {
1775
- parts.push({
1776
- type: "reasoning-end",
1777
- id: `${event.item_id}:${summaryIndex}`,
1778
- metadata: {
1779
- openai: {
1780
- ...makeItemIdMetadata(event.item_id),
1781
- ...makeEncryptedContentMetadata(reasoningPart.encryptedContent)
1782
- }
1922
+ // Conclude all can-conclude parts before starting new one
1923
+ for (const [summaryIndex, status] of Object.entries(reasoningPart.summaryParts)) {
1924
+ if (status === "can-conclude") {
1925
+ parts.push({
1926
+ type: "reasoning-end",
1927
+ id: `${event.item_id}:${summaryIndex}`,
1928
+ metadata: {
1929
+ openai: {
1930
+ ...makeItemIdMetadata(event.item_id),
1931
+ ...makeEncryptedContentMetadata(reasoningPart.encryptedContent)
1783
1932
  }
1784
- });
1785
- reasoningPart.summaryParts[Number(summaryIndex)] = "concluded";
1786
- }
1933
+ }
1934
+ });
1935
+ reasoningPart.summaryParts[Number(summaryIndex)] = "concluded";
1787
1936
  }
1788
- reasoningPart.summaryParts[event.summary_index] = "active";
1789
1937
  }
1938
+ }
1939
+ if (Predicate.isUndefined(reasoningPart.summaryParts[event.summary_index])) {
1940
+ reasoningPart.summaryParts[event.summary_index] = "active";
1790
1941
  parts.push({
1791
1942
  type: "reasoning-start",
1792
1943
  id: `${event.item_id}:${event.summary_index}`,
@@ -1807,15 +1958,14 @@ const makeStreamResponse = /*#__PURE__*/Effect.fnUntraced(function* ({
1807
1958
  id: `${event.item_id}:${event.summary_index}`,
1808
1959
  delta: event.delta,
1809
1960
  metadata: {
1810
- openai: {
1811
- ...makeItemIdMetadata(event.item_id)
1812
- }
1961
+ openai: makeItemIdMetadata(event.item_id)
1813
1962
  }
1814
1963
  });
1815
1964
  break;
1816
1965
  }
1817
1966
  case "response.reasoning_summary_part.done":
1818
1967
  {
1968
+ const reasoningPart = getOrCreateReasoningPart(event.item_id);
1819
1969
  // When OpenAI stores message data, we can immediately conclude the
1820
1970
  // reasoning part given that we do not need the encrypted content
1821
1971
  if (config.store === true) {
@@ -1823,17 +1973,15 @@ const makeStreamResponse = /*#__PURE__*/Effect.fnUntraced(function* ({
1823
1973
  type: "reasoning-end",
1824
1974
  id: `${event.item_id}:${event.summary_index}`,
1825
1975
  metadata: {
1826
- openai: {
1827
- ...makeItemIdMetadata(event.item_id)
1828
- }
1976
+ openai: makeItemIdMetadata(event.item_id)
1829
1977
  }
1830
1978
  });
1831
1979
  // Mark the summary part concluded
1832
- activeReasoning[event.item_id].summaryParts[event.summary_index] = "concluded";
1980
+ reasoningPart.summaryParts[event.summary_index] = "concluded";
1833
1981
  } else {
1834
1982
  // Mark the summary part as can-conclude given we still need a
1835
1983
  // final summary part with the encrypted content
1836
- activeReasoning[event.item_id].summaryParts[event.summary_index] = "can-conclude";
1984
+ reasoningPart.summaryParts[event.summary_index] = "can-conclude";
1837
1985
  }
1838
1986
  break;
1839
1987
  }
@@ -1936,14 +2084,18 @@ const prepareTools = /*#__PURE__*/Effect.fnUntraced(function* ({
1936
2084
  }
1937
2085
  // Convert the tools in the toolkit to the provider-defined format
1938
2086
  for (const tool of allowedTools) {
1939
- if (Tool.isUserDefined(tool)) {
2087
+ if (Tool.isUserDefined(tool) || Tool.isDynamic(tool)) {
1940
2088
  const strict = Tool.getStrictMode(tool) ?? config.strictJsonSchema ?? true;
2089
+ const description = Tool.getDescription(tool);
2090
+ const parameters = yield* tryToolJsonSchema(tool, "prepareTools");
1941
2091
  tools.push({
1942
2092
  type: "function",
1943
2093
  name: tool.name,
1944
- description: Tool.getDescription(tool) ?? null,
1945
- parameters: Tool.getJsonSchema(tool),
1946
- strict
2094
+ parameters,
2095
+ strict,
2096
+ ...(Predicate.isNotUndefined(description) ? {
2097
+ description
2098
+ } : undefined)
1947
2099
  });
1948
2100
  }
1949
2101
  if (Tool.isProviderDefined(tool)) {
@@ -2111,29 +2263,53 @@ const getEncryptedContent = part => part.options.openai?.encryptedContent ?? nul
2111
2263
  const getImageDetail = part => part.options.openai?.imageDetail ?? "auto";
2112
2264
  const makeItemIdMetadata = itemId => Predicate.isNotUndefined(itemId) ? {
2113
2265
  itemId
2114
- } : undefined;
2266
+ } : {};
2115
2267
  const makeEncryptedContentMetadata = encryptedContent => Predicate.isNotNullish(encryptedContent) ? {
2116
2268
  encryptedContent
2117
2269
  } : undefined;
2118
- const prepareResponseFormat = ({
2270
+ const unsupportedSchemaError = (error, method) => AiError.make({
2271
+ module: "OpenAiLanguageModel",
2272
+ method,
2273
+ reason: new AiError.UnsupportedSchemaError({
2274
+ description: error instanceof Error ? error.message : String(error)
2275
+ })
2276
+ });
2277
+ const tryCodecTransform = (schema, method) => Effect.try({
2278
+ try: () => toCodecOpenAI(schema),
2279
+ catch: error => unsupportedSchemaError(error, method)
2280
+ });
2281
+ const tryJsonSchema = (schema, method) => Effect.try({
2282
+ try: () => Tool.getJsonSchemaFromSchema(schema, {
2283
+ transformer: toCodecOpenAI
2284
+ }),
2285
+ catch: error => unsupportedSchemaError(error, method)
2286
+ });
2287
+ const tryToolJsonSchema = (tool, method) => Effect.try({
2288
+ try: () => Tool.getJsonSchema(tool, {
2289
+ transformer: toCodecOpenAI
2290
+ }),
2291
+ catch: error => unsupportedSchemaError(error, method)
2292
+ });
2293
+ const prepareResponseFormat = /*#__PURE__*/Effect.fnUntraced(function* ({
2119
2294
  config,
2120
2295
  options
2121
- }) => {
2296
+ }) {
2122
2297
  if (options.responseFormat.type === "json") {
2123
2298
  const name = options.responseFormat.objectName;
2124
2299
  const schema = options.responseFormat.schema;
2300
+ const jsonSchema = yield* tryJsonSchema(schema, "prepareResponseFormat");
2125
2301
  return {
2126
2302
  type: "json_schema",
2127
2303
  name,
2128
2304
  description: AST.resolveDescription(schema.ast) ?? "Response with a JSON object",
2129
- schema: Tool.getJsonSchemaFromSchema(schema),
2305
+ schema: jsonSchema,
2130
2306
  strict: config.strictJsonSchema ?? true
2131
2307
  };
2132
2308
  }
2133
2309
  return {
2134
2310
  type: "text"
2135
2311
  };
2136
- };
2312
+ });
2137
2313
  const getModelCapabilities = modelId => {
2138
2314
  const supportsFlexProcessing = modelId.startsWith("o3") || modelId.startsWith("o4-mini") || modelId.startsWith("gpt-5") && !modelId.startsWith("gpt-5-chat");
2139
2315
  const supportsPriorityProcessing = modelId.startsWith("gpt-4") || modelId.startsWith("gpt-5-mini") || modelId.startsWith("gpt-5") && !modelId.startsWith("gpt-5-nano") && !modelId.startsWith("gpt-5-chat") || modelId.startsWith("o3") || modelId.startsWith("o4-mini");
@@ -2170,6 +2346,35 @@ const getApprovalRequestIdMapping = prompt => {
2170
2346
  }
2171
2347
  return mapping;
2172
2348
  };
2349
+ const normalizeMcpToolCall = /*#__PURE__*/Effect.fnUntraced(function* ({
2350
+ toolNameMapper,
2351
+ toolParams,
2352
+ method
2353
+ }) {
2354
+ const toolName = toolNameMapper.getCustomName("mcp");
2355
+ if (typeof toolParams !== "string") {
2356
+ return {
2357
+ toolName,
2358
+ params: toolParams
2359
+ };
2360
+ }
2361
+ const params = yield* Effect.try({
2362
+ try: () => Tool.unsafeSecureJsonParse(toolParams),
2363
+ catch: cause => AiError.make({
2364
+ module: "OpenAiLanguageModel",
2365
+ method,
2366
+ reason: new AiError.ToolParameterValidationError({
2367
+ toolName,
2368
+ toolParams,
2369
+ description: `Failed to securely JSON parse tool parameters: ${cause}`
2370
+ })
2371
+ })
2372
+ });
2373
+ return {
2374
+ toolName,
2375
+ params
2376
+ };
2377
+ });
2173
2378
  const getUsage = usage => {
2174
2379
  if (Predicate.isNullish(usage)) {
2175
2380
  return {
@@ -2188,8 +2393,8 @@ const getUsage = usage => {
2188
2393
  }
2189
2394
  const inputTokens = usage.input_tokens;
2190
2395
  const outputTokens = usage.output_tokens;
2191
- const cachedTokens = usage.input_tokens_details.cached_tokens;
2192
- const reasoningTokens = usage.output_tokens_details.reasoning_tokens;
2396
+ const cachedTokens = getUsageTokenDetail(usage.input_tokens_details, "cached_tokens");
2397
+ const reasoningTokens = getUsageTokenDetail(usage.output_tokens_details, "reasoning_tokens");
2193
2398
  return {
2194
2399
  inputTokens: {
2195
2400
  uncached: inputTokens - cachedTokens,
@@ -2204,4 +2409,49 @@ const getUsage = usage => {
2204
2409
  }
2205
2410
  };
2206
2411
  };
2412
+ const toServiceTier = value => {
2413
+ switch (value) {
2414
+ case "default":
2415
+ case "auto":
2416
+ case "flex":
2417
+ case "scale":
2418
+ case "priority":
2419
+ return {
2420
+ metadata: {
2421
+ openai: {
2422
+ serviceTier: value
2423
+ }
2424
+ }
2425
+ };
2426
+ default:
2427
+ return undefined;
2428
+ }
2429
+ };
2430
+ const getUsageTokenDetail = (details, key) => Predicate.hasProperty(details, key) && typeof details[key] === "number" ? details[key] : 0;
2431
+ const transformToolCallParams = /*#__PURE__*/Effect.fnUntraced(function* (tools, toolName, toolParams) {
2432
+ const tool = tools.find(tool => tool.name === toolName);
2433
+ if (Predicate.isUndefined(tool)) {
2434
+ return yield* AiError.make({
2435
+ module: "OpenAiLanguageModel",
2436
+ method: "makeResponse",
2437
+ reason: new AiError.ToolNotFoundError({
2438
+ toolName,
2439
+ availableTools: tools.map(tool => tool.name)
2440
+ })
2441
+ });
2442
+ }
2443
+ const {
2444
+ codec
2445
+ } = yield* tryCodecTransform(tool.parametersSchema, "makeResponse");
2446
+ const transform = Schema.decodeEffect(codec);
2447
+ return yield* transform(toolParams).pipe(Effect.mapError(error => AiError.make({
2448
+ module: "OpenAiLanguageModel",
2449
+ method: "makeResponse",
2450
+ reason: new AiError.ToolParameterValidationError({
2451
+ toolName,
2452
+ toolParams,
2453
+ description: error.issue.toString()
2454
+ })
2455
+ })));
2456
+ });
2207
2457
  //# sourceMappingURL=OpenAiLanguageModel.js.map