@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,23 +4,25 @@
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 * as AiError from "effect/unstable/ai/AiError";
21
22
  import * as IdGenerator from "effect/unstable/ai/IdGenerator";
22
23
  import * as LanguageModel from "effect/unstable/ai/LanguageModel";
23
24
  import * as AiModel from "effect/unstable/ai/Model";
25
+ import { toCodecOpenAI } from "effect/unstable/ai/OpenAiStructuredOutput";
24
26
  import * as Tool from "effect/unstable/ai/Tool";
25
27
  import * as Generated from "./Generated.js";
26
28
  import * as InternalUtilities from "./internal/utilities.js";
@@ -34,36 +36,38 @@ const SharedModelIds = Generated.ModelIdsShared.members[1];
34
36
  /**
35
37
  * Service definition for OpenAI language model configuration.
36
38
  *
37
- * @since 1.0.0
38
39
  * @category services
40
+ * @since 4.0.0
39
41
  */
40
- export class Config extends /*#__PURE__*/ServiceMap.Service()("@effect/ai-openai/OpenAiLanguageModel/Config") {}
42
+ export class Config extends /*#__PURE__*/Context.Service()("@effect/ai-openai/OpenAiLanguageModel/Config") {}
41
43
  // =============================================================================
42
44
  // Language Model
43
45
  // =============================================================================
44
46
  /**
45
- * @since 1.0.0
47
+ * Creates an OpenAI language model that can be used with `AiModel.provide`.
48
+ *
46
49
  * @category constructors
50
+ * @since 4.0.0
47
51
  */
48
- export const model = (model, config) => AiModel.make("openai", layer({
52
+ export const model = (model, config) => AiModel.make("openai", model, layer({
49
53
  model,
50
54
  config
51
55
  }));
52
56
  // TODO
53
57
  // /**
54
- // * @since 1.0.0
58
+ // * @since 4.0.0
55
59
  // * @category constructors
56
60
  // */
57
61
  // export const modelWithTokenizer = (
58
62
  // model: (string & {}) | Model,
59
63
  // config?: Omit<typeof Config.Service, "model">
60
64
  // ): AiModel.Model<"openai", LanguageModel.LanguageModel | Tokenizer.Tokenizer, OpenAiClient> =>
61
- // AiModel.make("openai", layerWithTokenizer({ model, config }))
65
+ // AiModel.make("openai", model, layerWithTokenizer({ model, config }))
62
66
  /**
63
67
  * Creates an OpenAI language model service.
64
68
  *
65
- * @since 1.0.0
66
69
  * @category constructors
70
+ * @since 4.0.0
67
71
  */
68
72
  export const make = /*#__PURE__*/Effect.fnUntraced(function* ({
69
73
  model,
@@ -71,7 +75,7 @@ export const make = /*#__PURE__*/Effect.fnUntraced(function* ({
71
75
  }) {
72
76
  const client = yield* OpenAiClient;
73
77
  const makeConfig = Effect.gen(function* () {
74
- const services = yield* Effect.services();
78
+ const services = yield* Effect.context();
75
79
  return {
76
80
  model,
77
81
  ...providerConfig,
@@ -100,28 +104,31 @@ export const make = /*#__PURE__*/Effect.fnUntraced(function* ({
100
104
  options,
101
105
  toolNameMapper
102
106
  });
103
- const responseFormat = prepareResponseFormat({
107
+ const responseFormat = yield* prepareResponseFormat({
104
108
  config,
105
109
  options
106
110
  });
111
+ const {
112
+ fileIdPrefixes: _fip,
113
+ strictJsonSchema: _sjs,
114
+ ...apiConfig
115
+ } = config;
107
116
  const request = {
108
- ...config,
117
+ ...apiConfig,
109
118
  input: messages,
110
- include: include.size > 0 ? Array.from(include) : null,
119
+ include: include.size > 0 ? Array.from(include) : undefined,
111
120
  text: {
112
- verbosity: config.text?.verbosity ?? null,
121
+ verbosity: config.text?.verbosity ?? undefined,
113
122
  format: responseFormat
114
- },
115
- ...(Predicate.isNotUndefined(tools) ? {
116
- tools
117
- } : undefined),
118
- ...(Predicate.isNotUndefined(toolChoice) ? {
119
- tool_choice: toolChoice
120
- } : undefined)
123
+ }
121
124
  };
125
+ if (tools) request.tools = tools;
126
+ if (toolChoice) request.tool_choice = toolChoice;
127
+ if (options.previousResponseId) request.previous_response_id = options.previousResponseId;
122
128
  return request;
123
129
  });
124
130
  return yield* LanguageModel.make({
131
+ codecTransformer: toCodecOpenAI,
125
132
  generateText: Effect.fnUntraced(function* (options) {
126
133
  const config = yield* makeConfig;
127
134
  const toolNameMapper = new Tool.NameMapper(options.tools);
@@ -166,15 +173,15 @@ export const make = /*#__PURE__*/Effect.fnUntraced(function* ({
166
173
  /**
167
174
  * Creates a layer for the OpenAI language model.
168
175
  *
169
- * @since 1.0.0
170
176
  * @category layers
177
+ * @since 4.0.0
171
178
  */
172
179
  export const layer = options => Layer.effect(LanguageModel.LanguageModel, make(options));
173
180
  /**
174
181
  * Provides config overrides for OpenAI language model operations.
175
182
  *
176
- * @since 1.0.0
177
183
  * @category configuration
184
+ * @since 4.0.0
178
185
  */
179
186
  export const withConfigOverride = /*#__PURE__*/dual(2, (self, overrides) => Effect.flatMap(Effect.serviceOption(Config), config => Effect.provideService(self, Config, {
180
187
  ...(config._tag === "Some" ? config.value : {}),
@@ -207,14 +214,15 @@ const prepareMessages = /*#__PURE__*/Effect.fnUntraced(function* ({
207
214
  if (config.store === false && capabilities.isReasoningModel) {
208
215
  include.add("reasoning.encrypted_content");
209
216
  }
210
- if (Predicate.isNotUndefined(codeInterpreterTool)) {
217
+ if (codeInterpreterTool) {
211
218
  include.add("code_interpreter_call.outputs");
212
219
  }
213
- if (Predicate.isNotUndefined(webSearchTool) || Predicate.isNotUndefined(webSearchPreviewTool)) {
220
+ if (webSearchTool || webSearchPreviewTool) {
214
221
  include.add("web_search_call.action.sources");
215
222
  }
216
223
  const messages = [];
217
- for (const message of options.prompt.content) {
224
+ const prompt = options.incrementalPrompt ?? options.prompt;
225
+ for (const message of prompt.content) {
218
226
  switch (message.role) {
219
227
  case "system":
220
228
  {
@@ -378,7 +386,9 @@ const prepareMessages = /*#__PURE__*/Effect.fnUntraced(function* ({
378
386
  type: "reasoning",
379
387
  id,
380
388
  summary: summaryParts,
381
- encrypted_content: encryptedContent ?? null
389
+ ...(Predicate.isNotNull(encryptedContent) ? {
390
+ encrypted_content: encryptedContent
391
+ } : undefined)
382
392
  };
383
393
  messages.push(reasoningMessages[id]);
384
394
  } else {
@@ -569,13 +579,15 @@ const buildHttpRequestDetails = request => ({
569
579
  method: request.method,
570
580
  url: request.url,
571
581
  urlParams: Array.from(request.urlParams),
572
- hash: request.hash,
582
+ hash: Option.getOrUndefined(request.hash),
573
583
  headers: Redactable.redact(request.headers)
574
584
  });
575
585
  const buildHttpResponseDetails = response => ({
576
586
  status: response.status,
577
587
  headers: Redactable.redact(response.headers)
578
588
  });
589
+ 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"]);
590
+ const isKnownResponseStreamEvent = event => knownResponseStreamEventTypes.has(event.type);
579
591
  const makeResponse = /*#__PURE__*/Effect.fnUntraced(function* ({
580
592
  options,
581
593
  rawResponse,
@@ -609,9 +621,7 @@ const makeResponse = /*#__PURE__*/Effect.fnUntraced(function* ({
609
621
  operation: part.operation
610
622
  },
611
623
  metadata: {
612
- openai: {
613
- ...makeItemIdMetadata(part.id)
614
- }
624
+ openai: makeItemIdMetadata(part.id)
615
625
  }
616
626
  });
617
627
  break;
@@ -669,9 +679,8 @@ const makeResponse = /*#__PURE__*/Effect.fnUntraced(function* ({
669
679
  {
670
680
  hasToolCalls = true;
671
681
  const toolName = part.name;
672
- const toolParams = part.arguments;
673
- const params = yield* Effect.try({
674
- try: () => Tool.unsafeSecureJsonParse(toolParams),
682
+ const toolParams = yield* Effect.try({
683
+ try: () => Tool.unsafeSecureJsonParse(part.arguments),
675
684
  catch: cause => AiError.make({
676
685
  module: "OpenAiLanguageModel",
677
686
  method: "makeResponse",
@@ -682,15 +691,14 @@ const makeResponse = /*#__PURE__*/Effect.fnUntraced(function* ({
682
691
  })
683
692
  })
684
693
  });
694
+ const params = yield* transformToolCallParams(options.tools, part.name, toolParams);
685
695
  parts.push({
686
696
  type: "tool-call",
687
697
  id: part.call_id,
688
698
  name: toolName,
689
699
  params,
690
700
  metadata: {
691
- openai: {
692
- ...makeItemIdMetadata(part.id)
693
- }
701
+ openai: makeItemIdMetadata(part.id)
694
702
  }
695
703
  });
696
704
  break;
@@ -727,9 +735,7 @@ const makeResponse = /*#__PURE__*/Effect.fnUntraced(function* ({
727
735
  action: part.action
728
736
  },
729
737
  metadata: {
730
- openai: {
731
- ...makeItemIdMetadata(part.id)
732
- }
738
+ openai: makeItemIdMetadata(part.id)
733
739
  }
734
740
  });
735
741
  break;
@@ -737,12 +743,19 @@ const makeResponse = /*#__PURE__*/Effect.fnUntraced(function* ({
737
743
  case "mcp_call":
738
744
  {
739
745
  const toolId = Predicate.isNotNullish(part.approval_request_id) ? approvalRequests.get(part.approval_request_id) ?? part.id : part.id;
740
- const toolName = `mcp.${part.name}`;
746
+ const {
747
+ toolName,
748
+ params
749
+ } = yield* normalizeMcpToolCall({
750
+ toolNameMapper,
751
+ toolParams: part.arguments,
752
+ method: "makeResponse"
753
+ });
741
754
  parts.push({
742
755
  type: "tool-call",
743
756
  id: toolId,
744
757
  name: toolName,
745
- params: part.arguments,
758
+ params,
746
759
  providerExecuted: true
747
760
  });
748
761
  parts.push({
@@ -752,7 +765,7 @@ const makeResponse = /*#__PURE__*/Effect.fnUntraced(function* ({
752
765
  isFailure: false,
753
766
  providerExecuted: true,
754
767
  result: {
755
- type: "call",
768
+ type: "mcp_call",
756
769
  name: part.name,
757
770
  arguments: part.arguments,
758
771
  server_label: part.server_label,
@@ -764,9 +777,7 @@ const makeResponse = /*#__PURE__*/Effect.fnUntraced(function* ({
764
777
  } : undefined)
765
778
  },
766
779
  metadata: {
767
- openai: {
768
- ...makeItemIdMetadata(part.id)
769
- }
780
+ openai: makeItemIdMetadata(part.id)
770
781
  }
771
782
  });
772
783
  break;
@@ -780,18 +791,13 @@ const makeResponse = /*#__PURE__*/Effect.fnUntraced(function* ({
780
791
  {
781
792
  const approvalRequestId = part.approval_request_id ?? part.id;
782
793
  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
- })
794
+ const {
795
+ toolName,
796
+ params
797
+ } = yield* normalizeMcpToolCall({
798
+ toolNameMapper,
799
+ toolParams: part.arguments,
800
+ method: "makeResponse"
795
801
  });
796
802
  parts.push({
797
803
  type: "tool-call",
@@ -952,9 +958,7 @@ const makeResponse = /*#__PURE__*/Effect.fnUntraced(function* ({
952
958
  action: part.action
953
959
  },
954
960
  metadata: {
955
- openai: {
956
- ...makeItemIdMetadata(part.id)
957
- }
961
+ openai: makeItemIdMetadata(part.id)
958
962
  }
959
963
  });
960
964
  break;
@@ -990,13 +994,7 @@ const makeResponse = /*#__PURE__*/Effect.fnUntraced(function* ({
990
994
  reason: finishReason,
991
995
  usage: getUsage(rawResponse.usage),
992
996
  response: buildHttpResponseDetails(response),
993
- ...(rawResponse.service_tier && {
994
- metadata: {
995
- openai: {
996
- serviceTier: rawResponse.service_tier
997
- }
998
- }
999
- })
997
+ ...toServiceTier(rawResponse.service_tier)
1000
998
  });
1001
999
  return parts;
1002
1000
  });
@@ -1015,11 +1013,29 @@ const makeStreamResponse = /*#__PURE__*/Effect.fnUntraced(function* ({
1015
1013
  const activeAnnotations = [];
1016
1014
  // Track active reasoning items with state machine for proper concluding logic
1017
1015
  const activeReasoning = {};
1016
+ const getOrCreateReasoningPart = (itemId, encryptedContent) => {
1017
+ const activePart = activeReasoning[itemId];
1018
+ if (Predicate.isNotUndefined(activePart)) {
1019
+ if (Predicate.isNotNullish(encryptedContent)) {
1020
+ activePart.encryptedContent = encryptedContent;
1021
+ }
1022
+ return activePart;
1023
+ }
1024
+ const reasoningPart = {
1025
+ encryptedContent: Predicate.isNotNullish(encryptedContent) ? encryptedContent : undefined,
1026
+ summaryParts: {}
1027
+ };
1028
+ activeReasoning[itemId] = reasoningPart;
1029
+ return reasoningPart;
1030
+ };
1018
1031
  // Track active tool calls with optional provider-specific state
1019
1032
  const activeToolCalls = {};
1020
1033
  const webSearchTool = options.tools.find(tool => Tool.isProviderDefined(tool) && (tool.name === "OpenAiWebSearch" || tool.name === "OpenAiWebSearchPreview"));
1021
1034
  return stream.pipe(Stream.mapEffect(Effect.fnUntraced(function* (event) {
1022
1035
  const parts = [];
1036
+ if (!isKnownResponseStreamEvent(event)) {
1037
+ return parts;
1038
+ }
1023
1039
  switch (event.type) {
1024
1040
  case "response.created":
1025
1041
  {
@@ -1050,13 +1066,7 @@ const makeStreamResponse = /*#__PURE__*/Effect.fnUntraced(function* ({
1050
1066
  reason: InternalUtilities.resolveFinishReason(event.response.incomplete_details?.reason, hasToolCalls),
1051
1067
  usage: getUsage(event.response.usage),
1052
1068
  response: buildHttpResponseDetails(response),
1053
- ...(event.response.service_tier && {
1054
- metadata: {
1055
- openai: {
1056
- serviceTier: event.response.service_tier
1057
- }
1058
- }
1059
- })
1069
+ ...toServiceTier(event.response.service_tier)
1060
1070
  });
1061
1071
  break;
1062
1072
  }
@@ -1157,7 +1167,10 @@ const makeStreamResponse = /*#__PURE__*/Effect.fnUntraced(function* ({
1157
1167
  {
1158
1168
  activeToolCalls[event.output_index] = {
1159
1169
  id: event.item.call_id,
1160
- name: event.item.name
1170
+ name: event.item.name,
1171
+ functionCall: {
1172
+ emitted: false
1173
+ }
1161
1174
  };
1162
1175
  parts.push({
1163
1176
  type: "tool-params-start",
@@ -1195,39 +1208,34 @@ const makeStreamResponse = /*#__PURE__*/Effect.fnUntraced(function* ({
1195
1208
  type: "text-start",
1196
1209
  id: event.item.id,
1197
1210
  metadata: {
1198
- openai: {
1199
- ...makeItemIdMetadata(event.item.id)
1200
- }
1211
+ openai: makeItemIdMetadata(event.item.id)
1201
1212
  }
1202
1213
  });
1203
1214
  break;
1204
1215
  }
1205
1216
  case "reasoning":
1206
1217
  {
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)
1218
+ const reasoningPart = getOrCreateReasoningPart(event.item.id, event.item.encrypted_content);
1219
+ if (Predicate.isUndefined(reasoningPart.summaryParts[0])) {
1220
+ reasoningPart.summaryParts[0] = "active";
1221
+ parts.push({
1222
+ type: "reasoning-start",
1223
+ id: `${event.item.id}:0`,
1224
+ metadata: {
1225
+ openai: {
1226
+ ...makeItemIdMetadata(event.item.id),
1227
+ ...makeEncryptedContentMetadata(reasoningPart.encryptedContent)
1228
+ }
1221
1229
  }
1222
- }
1223
- });
1230
+ });
1231
+ }
1224
1232
  break;
1225
1233
  }
1226
1234
  case "shell_call":
1227
1235
  {
1228
1236
  const toolName = toolNameMapper.getCustomName("shell");
1229
1237
  activeToolCalls[event.output_index] = {
1230
- id: event.item.id,
1238
+ id: event.item.id ?? event.item.call_id,
1231
1239
  name: toolName
1232
1240
  };
1233
1241
  break;
@@ -1272,7 +1280,7 @@ const makeStreamResponse = /*#__PURE__*/Effect.fnUntraced(function* ({
1272
1280
  parts.push({
1273
1281
  type: "tool-params-delta",
1274
1282
  id: toolCall.id,
1275
- delta: InternalUtilities.escapeJSONDelta(event.item.operation.diff)
1283
+ delta: InternalUtilities.escapeJSONDelta(event.item.operation.diff ?? "")
1276
1284
  });
1277
1285
  }
1278
1286
  parts.push({
@@ -1298,9 +1306,7 @@ const makeStreamResponse = /*#__PURE__*/Effect.fnUntraced(function* ({
1298
1306
  operation: event.item.operation
1299
1307
  },
1300
1308
  metadata: {
1301
- openai: {
1302
- ...makeItemIdMetadata(event.item.id)
1303
- }
1309
+ openai: makeItemIdMetadata(event.item.id)
1304
1310
  }
1305
1311
  });
1306
1312
  }
@@ -1372,12 +1378,17 @@ const makeStreamResponse = /*#__PURE__*/Effect.fnUntraced(function* ({
1372
1378
  }
1373
1379
  case "function_call":
1374
1380
  {
1381
+ const toolCall = activeToolCalls[event.output_index];
1382
+ if (Predicate.isNotUndefined(toolCall?.functionCall?.emitted) && toolCall.functionCall.emitted) {
1383
+ delete activeToolCalls[event.output_index];
1384
+ break;
1385
+ }
1375
1386
  delete activeToolCalls[event.output_index];
1376
1387
  hasToolCalls = true;
1377
1388
  const toolName = event.item.name;
1378
- const toolParams = event.item.arguments;
1379
- const params = yield* Effect.try({
1380
- try: () => Tool.unsafeSecureJsonParse(toolParams),
1389
+ const toolArgs = event.item.arguments;
1390
+ const toolParams = yield* Effect.try({
1391
+ try: () => Tool.unsafeSecureJsonParse(toolArgs),
1381
1392
  catch: cause => AiError.make({
1382
1393
  module: "OpenAiLanguageModel",
1383
1394
  method: "makeStreamResponse",
@@ -1388,6 +1399,7 @@ const makeStreamResponse = /*#__PURE__*/Effect.fnUntraced(function* ({
1388
1399
  })
1389
1400
  })
1390
1401
  });
1402
+ const params = yield* transformToolCallParams(options.tools, toolName, toolParams);
1391
1403
  parts.push({
1392
1404
  type: "tool-params-end",
1393
1405
  id: event.item.call_id
@@ -1398,9 +1410,7 @@ const makeStreamResponse = /*#__PURE__*/Effect.fnUntraced(function* ({
1398
1410
  name: toolName,
1399
1411
  params,
1400
1412
  metadata: {
1401
- openai: {
1402
- ...makeItemIdMetadata(event.item.id)
1403
- }
1413
+ openai: makeItemIdMetadata(event.item.id)
1404
1414
  }
1405
1415
  });
1406
1416
  break;
@@ -1431,9 +1441,7 @@ const makeStreamResponse = /*#__PURE__*/Effect.fnUntraced(function* ({
1431
1441
  action: event.item.action
1432
1442
  },
1433
1443
  metadata: {
1434
- openai: {
1435
- ...makeItemIdMetadata(event.item.id)
1436
- }
1444
+ openai: makeItemIdMetadata(event.item.id)
1437
1445
  }
1438
1446
  });
1439
1447
  break;
@@ -1443,12 +1451,19 @@ const makeStreamResponse = /*#__PURE__*/Effect.fnUntraced(function* ({
1443
1451
  const approvalRequestId = event.item.approval_request_id;
1444
1452
  // Track approval with our own tool call identifiers
1445
1453
  const toolId = Predicate.isNotNullish(approvalRequestId) ? streamApprovalRequests.get(approvalRequestId) ?? approvalRequests.get(approvalRequestId) ?? event.item.id : event.item.id;
1446
- const toolName = `mcp.${event.item.name}`;
1454
+ const {
1455
+ toolName,
1456
+ params
1457
+ } = yield* normalizeMcpToolCall({
1458
+ toolNameMapper,
1459
+ toolParams: event.item.arguments,
1460
+ method: "makeStreamResponse"
1461
+ });
1447
1462
  parts.push({
1448
1463
  type: "tool-call",
1449
1464
  id: toolId,
1450
1465
  name: toolName,
1451
- params: event.item.arguments,
1466
+ params,
1452
1467
  providerExecuted: true
1453
1468
  });
1454
1469
  parts.push({
@@ -1458,7 +1473,7 @@ const makeStreamResponse = /*#__PURE__*/Effect.fnUntraced(function* ({
1458
1473
  isFailure: false,
1459
1474
  providerExecuted: true,
1460
1475
  result: {
1461
- type: "call",
1476
+ type: "mcp_call",
1462
1477
  name: event.item.name,
1463
1478
  arguments: event.item.arguments,
1464
1479
  server_label: event.item.server_label,
@@ -1470,9 +1485,7 @@ const makeStreamResponse = /*#__PURE__*/Effect.fnUntraced(function* ({
1470
1485
  } : undefined)
1471
1486
  },
1472
1487
  metadata: {
1473
- openai: {
1474
- ...makeItemIdMetadata(event.item.id)
1475
- }
1488
+ openai: makeItemIdMetadata(event.item.id)
1476
1489
  }
1477
1490
  });
1478
1491
  break;
@@ -1487,12 +1500,19 @@ const makeStreamResponse = /*#__PURE__*/Effect.fnUntraced(function* ({
1487
1500
  const toolId = yield* idGenerator.generateId();
1488
1501
  const approvalRequestId = event.item.approval_request_id ?? event.item.id;
1489
1502
  streamApprovalRequests.set(approvalRequestId, toolId);
1490
- const toolName = `mcp.${event.item.name}`;
1503
+ const {
1504
+ toolName,
1505
+ params
1506
+ } = yield* normalizeMcpToolCall({
1507
+ toolNameMapper,
1508
+ toolParams: event.item.arguments,
1509
+ method: "makeStreamResponse"
1510
+ });
1491
1511
  parts.push({
1492
1512
  type: "tool-call",
1493
1513
  id: toolId,
1494
1514
  name: toolName,
1495
- params: event.item.arguments,
1515
+ params,
1496
1516
  providerExecuted: true
1497
1517
  });
1498
1518
  parts.push({
@@ -1521,7 +1541,7 @@ const makeStreamResponse = /*#__PURE__*/Effect.fnUntraced(function* ({
1521
1541
  }
1522
1542
  case "reasoning":
1523
1543
  {
1524
- const reasoningPart = activeReasoning[event.item.id];
1544
+ const reasoningPart = getOrCreateReasoningPart(event.item.id, event.item.encrypted_content);
1525
1545
  for (const [summaryIndex, status] of Object.entries(reasoningPart.summaryParts)) {
1526
1546
  if (status === "active" || status === "can-conclude") {
1527
1547
  parts.push({
@@ -1530,7 +1550,7 @@ const makeStreamResponse = /*#__PURE__*/Effect.fnUntraced(function* ({
1530
1550
  metadata: {
1531
1551
  openai: {
1532
1552
  ...makeItemIdMetadata(event.item.id),
1533
- ...makeEncryptedContentMetadata(event.item.encrypted_content)
1553
+ ...makeEncryptedContentMetadata(reasoningPart.encryptedContent)
1534
1554
  }
1535
1555
  }
1536
1556
  });
@@ -1545,15 +1565,13 @@ const makeStreamResponse = /*#__PURE__*/Effect.fnUntraced(function* ({
1545
1565
  const toolName = toolNameMapper.getCustomName("shell");
1546
1566
  parts.push({
1547
1567
  type: "tool-call",
1548
- id: event.item.id,
1568
+ id: event.item.id ?? event.item.call_id,
1549
1569
  name: toolName,
1550
1570
  params: {
1551
1571
  action: event.item.action
1552
1572
  },
1553
1573
  metadata: {
1554
- openai: {
1555
- ...makeItemIdMetadata(event.item.id)
1556
- }
1574
+ openai: makeItemIdMetadata(event.item.id)
1557
1575
  }
1558
1576
  });
1559
1577
  break;
@@ -1670,6 +1688,41 @@ const makeStreamResponse = /*#__PURE__*/Effect.fnUntraced(function* ({
1670
1688
  }
1671
1689
  break;
1672
1690
  }
1691
+ case "response.function_call_arguments.done":
1692
+ {
1693
+ const toolCall = activeToolCalls[event.output_index];
1694
+ if (Predicate.isNotUndefined(toolCall?.functionCall) && !toolCall.functionCall.emitted) {
1695
+ hasToolCalls = true;
1696
+ const toolParams = yield* Effect.try({
1697
+ try: () => Tool.unsafeSecureJsonParse(event.arguments),
1698
+ catch: cause => AiError.make({
1699
+ module: "OpenAiLanguageModel",
1700
+ method: "makeStreamResponse",
1701
+ reason: new AiError.ToolParameterValidationError({
1702
+ toolName: toolCall.name,
1703
+ toolParams: {},
1704
+ description: `Failed securely JSON parse tool parameters: ${cause}`
1705
+ })
1706
+ })
1707
+ });
1708
+ const params = yield* transformToolCallParams(options.tools, toolCall.name, toolParams);
1709
+ parts.push({
1710
+ type: "tool-params-end",
1711
+ id: toolCall.id
1712
+ });
1713
+ parts.push({
1714
+ type: "tool-call",
1715
+ id: toolCall.id,
1716
+ name: toolCall.name,
1717
+ params,
1718
+ metadata: {
1719
+ openai: makeItemIdMetadata(event.item_id)
1720
+ }
1721
+ });
1722
+ toolCall.functionCall.emitted = true;
1723
+ }
1724
+ break;
1725
+ }
1673
1726
  case "response.apply_patch_call_operation_diff.delta":
1674
1727
  {
1675
1728
  const toolCall = activeToolCalls[event.output_index];
@@ -1765,28 +1818,27 @@ const makeStreamResponse = /*#__PURE__*/Effect.fnUntraced(function* ({
1765
1818
  }
1766
1819
  case "response.reasoning_summary_part.added":
1767
1820
  {
1768
- // The first reasoning start is pushed in the `response.output_item.added` block
1821
+ const reasoningPart = getOrCreateReasoningPart(event.item_id);
1769
1822
  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
- }
1823
+ // Conclude all can-conclude parts before starting new one
1824
+ for (const [summaryIndex, status] of Object.entries(reasoningPart.summaryParts)) {
1825
+ if (status === "can-conclude") {
1826
+ parts.push({
1827
+ type: "reasoning-end",
1828
+ id: `${event.item_id}:${summaryIndex}`,
1829
+ metadata: {
1830
+ openai: {
1831
+ ...makeItemIdMetadata(event.item_id),
1832
+ ...makeEncryptedContentMetadata(reasoningPart.encryptedContent)
1783
1833
  }
1784
- });
1785
- reasoningPart.summaryParts[Number(summaryIndex)] = "concluded";
1786
- }
1834
+ }
1835
+ });
1836
+ reasoningPart.summaryParts[Number(summaryIndex)] = "concluded";
1787
1837
  }
1788
- reasoningPart.summaryParts[event.summary_index] = "active";
1789
1838
  }
1839
+ }
1840
+ if (Predicate.isUndefined(reasoningPart.summaryParts[event.summary_index])) {
1841
+ reasoningPart.summaryParts[event.summary_index] = "active";
1790
1842
  parts.push({
1791
1843
  type: "reasoning-start",
1792
1844
  id: `${event.item_id}:${event.summary_index}`,
@@ -1807,15 +1859,14 @@ const makeStreamResponse = /*#__PURE__*/Effect.fnUntraced(function* ({
1807
1859
  id: `${event.item_id}:${event.summary_index}`,
1808
1860
  delta: event.delta,
1809
1861
  metadata: {
1810
- openai: {
1811
- ...makeItemIdMetadata(event.item_id)
1812
- }
1862
+ openai: makeItemIdMetadata(event.item_id)
1813
1863
  }
1814
1864
  });
1815
1865
  break;
1816
1866
  }
1817
1867
  case "response.reasoning_summary_part.done":
1818
1868
  {
1869
+ const reasoningPart = getOrCreateReasoningPart(event.item_id);
1819
1870
  // When OpenAI stores message data, we can immediately conclude the
1820
1871
  // reasoning part given that we do not need the encrypted content
1821
1872
  if (config.store === true) {
@@ -1823,17 +1874,15 @@ const makeStreamResponse = /*#__PURE__*/Effect.fnUntraced(function* ({
1823
1874
  type: "reasoning-end",
1824
1875
  id: `${event.item_id}:${event.summary_index}`,
1825
1876
  metadata: {
1826
- openai: {
1827
- ...makeItemIdMetadata(event.item_id)
1828
- }
1877
+ openai: makeItemIdMetadata(event.item_id)
1829
1878
  }
1830
1879
  });
1831
1880
  // Mark the summary part concluded
1832
- activeReasoning[event.item_id].summaryParts[event.summary_index] = "concluded";
1881
+ reasoningPart.summaryParts[event.summary_index] = "concluded";
1833
1882
  } else {
1834
1883
  // Mark the summary part as can-conclude given we still need a
1835
1884
  // final summary part with the encrypted content
1836
- activeReasoning[event.item_id].summaryParts[event.summary_index] = "can-conclude";
1885
+ reasoningPart.summaryParts[event.summary_index] = "can-conclude";
1837
1886
  }
1838
1887
  break;
1839
1888
  }
@@ -1936,14 +1985,18 @@ const prepareTools = /*#__PURE__*/Effect.fnUntraced(function* ({
1936
1985
  }
1937
1986
  // Convert the tools in the toolkit to the provider-defined format
1938
1987
  for (const tool of allowedTools) {
1939
- if (Tool.isUserDefined(tool)) {
1988
+ if (Tool.isUserDefined(tool) || Tool.isDynamic(tool)) {
1940
1989
  const strict = Tool.getStrictMode(tool) ?? config.strictJsonSchema ?? true;
1990
+ const description = Tool.getDescription(tool);
1991
+ const parameters = yield* tryToolJsonSchema(tool, "prepareTools");
1941
1992
  tools.push({
1942
1993
  type: "function",
1943
1994
  name: tool.name,
1944
- description: Tool.getDescription(tool) ?? null,
1945
- parameters: Tool.getJsonSchema(tool),
1946
- strict
1995
+ parameters,
1996
+ strict,
1997
+ ...(Predicate.isNotUndefined(description) ? {
1998
+ description
1999
+ } : undefined)
1947
2000
  });
1948
2001
  }
1949
2002
  if (Tool.isProviderDefined(tool)) {
@@ -2111,29 +2164,53 @@ const getEncryptedContent = part => part.options.openai?.encryptedContent ?? nul
2111
2164
  const getImageDetail = part => part.options.openai?.imageDetail ?? "auto";
2112
2165
  const makeItemIdMetadata = itemId => Predicate.isNotUndefined(itemId) ? {
2113
2166
  itemId
2114
- } : undefined;
2167
+ } : {};
2115
2168
  const makeEncryptedContentMetadata = encryptedContent => Predicate.isNotNullish(encryptedContent) ? {
2116
2169
  encryptedContent
2117
2170
  } : undefined;
2118
- const prepareResponseFormat = ({
2171
+ const unsupportedSchemaError = (error, method) => AiError.make({
2172
+ module: "OpenAiLanguageModel",
2173
+ method,
2174
+ reason: new AiError.UnsupportedSchemaError({
2175
+ description: error instanceof Error ? error.message : String(error)
2176
+ })
2177
+ });
2178
+ const tryCodecTransform = (schema, method) => Effect.try({
2179
+ try: () => toCodecOpenAI(schema),
2180
+ catch: error => unsupportedSchemaError(error, method)
2181
+ });
2182
+ const tryJsonSchema = (schema, method) => Effect.try({
2183
+ try: () => Tool.getJsonSchemaFromSchema(schema, {
2184
+ transformer: toCodecOpenAI
2185
+ }),
2186
+ catch: error => unsupportedSchemaError(error, method)
2187
+ });
2188
+ const tryToolJsonSchema = (tool, method) => Effect.try({
2189
+ try: () => Tool.getJsonSchema(tool, {
2190
+ transformer: toCodecOpenAI
2191
+ }),
2192
+ catch: error => unsupportedSchemaError(error, method)
2193
+ });
2194
+ const prepareResponseFormat = /*#__PURE__*/Effect.fnUntraced(function* ({
2119
2195
  config,
2120
2196
  options
2121
- }) => {
2197
+ }) {
2122
2198
  if (options.responseFormat.type === "json") {
2123
2199
  const name = options.responseFormat.objectName;
2124
2200
  const schema = options.responseFormat.schema;
2201
+ const jsonSchema = yield* tryJsonSchema(schema, "prepareResponseFormat");
2125
2202
  return {
2126
2203
  type: "json_schema",
2127
2204
  name,
2128
2205
  description: AST.resolveDescription(schema.ast) ?? "Response with a JSON object",
2129
- schema: Tool.getJsonSchemaFromSchema(schema),
2206
+ schema: jsonSchema,
2130
2207
  strict: config.strictJsonSchema ?? true
2131
2208
  };
2132
2209
  }
2133
2210
  return {
2134
2211
  type: "text"
2135
2212
  };
2136
- };
2213
+ });
2137
2214
  const getModelCapabilities = modelId => {
2138
2215
  const supportsFlexProcessing = modelId.startsWith("o3") || modelId.startsWith("o4-mini") || modelId.startsWith("gpt-5") && !modelId.startsWith("gpt-5-chat");
2139
2216
  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 +2247,35 @@ const getApprovalRequestIdMapping = prompt => {
2170
2247
  }
2171
2248
  return mapping;
2172
2249
  };
2250
+ const normalizeMcpToolCall = /*#__PURE__*/Effect.fnUntraced(function* ({
2251
+ toolNameMapper,
2252
+ toolParams,
2253
+ method
2254
+ }) {
2255
+ const toolName = toolNameMapper.getCustomName("mcp");
2256
+ if (typeof toolParams !== "string") {
2257
+ return {
2258
+ toolName,
2259
+ params: toolParams
2260
+ };
2261
+ }
2262
+ const params = yield* Effect.try({
2263
+ try: () => Tool.unsafeSecureJsonParse(toolParams),
2264
+ catch: cause => AiError.make({
2265
+ module: "OpenAiLanguageModel",
2266
+ method,
2267
+ reason: new AiError.ToolParameterValidationError({
2268
+ toolName,
2269
+ toolParams,
2270
+ description: `Failed to securely JSON parse tool parameters: ${cause}`
2271
+ })
2272
+ })
2273
+ });
2274
+ return {
2275
+ toolName,
2276
+ params
2277
+ };
2278
+ });
2173
2279
  const getUsage = usage => {
2174
2280
  if (Predicate.isNullish(usage)) {
2175
2281
  return {
@@ -2188,8 +2294,8 @@ const getUsage = usage => {
2188
2294
  }
2189
2295
  const inputTokens = usage.input_tokens;
2190
2296
  const outputTokens = usage.output_tokens;
2191
- const cachedTokens = usage.input_tokens_details.cached_tokens;
2192
- const reasoningTokens = usage.output_tokens_details.reasoning_tokens;
2297
+ const cachedTokens = getUsageTokenDetail(usage.input_tokens_details, "cached_tokens");
2298
+ const reasoningTokens = getUsageTokenDetail(usage.output_tokens_details, "reasoning_tokens");
2193
2299
  return {
2194
2300
  inputTokens: {
2195
2301
  uncached: inputTokens - cachedTokens,
@@ -2204,4 +2310,49 @@ const getUsage = usage => {
2204
2310
  }
2205
2311
  };
2206
2312
  };
2313
+ const toServiceTier = value => {
2314
+ switch (value) {
2315
+ case "default":
2316
+ case "auto":
2317
+ case "flex":
2318
+ case "scale":
2319
+ case "priority":
2320
+ return {
2321
+ metadata: {
2322
+ openai: {
2323
+ serviceTier: value
2324
+ }
2325
+ }
2326
+ };
2327
+ default:
2328
+ return undefined;
2329
+ }
2330
+ };
2331
+ const getUsageTokenDetail = (details, key) => Predicate.hasProperty(details, key) && typeof details[key] === "number" ? details[key] : 0;
2332
+ const transformToolCallParams = /*#__PURE__*/Effect.fnUntraced(function* (tools, toolName, toolParams) {
2333
+ const tool = tools.find(tool => tool.name === toolName);
2334
+ if (Predicate.isUndefined(tool)) {
2335
+ return yield* AiError.make({
2336
+ module: "OpenAiLanguageModel",
2337
+ method: "makeResponse",
2338
+ reason: new AiError.ToolNotFoundError({
2339
+ toolName,
2340
+ availableTools: tools.map(tool => tool.name)
2341
+ })
2342
+ });
2343
+ }
2344
+ const {
2345
+ codec
2346
+ } = yield* tryCodecTransform(tool.parametersSchema, "makeResponse");
2347
+ const transform = Schema.decodeEffect(codec);
2348
+ return yield* transform(toolParams).pipe(Effect.mapError(error => AiError.make({
2349
+ module: "OpenAiLanguageModel",
2350
+ method: "makeResponse",
2351
+ reason: new AiError.ToolParameterValidationError({
2352
+ toolName,
2353
+ toolParams,
2354
+ description: error.issue.toString()
2355
+ })
2356
+ })));
2357
+ });
2207
2358
  //# sourceMappingURL=OpenAiLanguageModel.js.map