@effect/ai-openai 0.37.2 → 4.0.0-beta.1

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 (117) hide show
  1. package/dist/Generated.d.ts +70887 -0
  2. package/dist/Generated.d.ts.map +1 -0
  3. package/dist/Generated.js +4 -0
  4. package/dist/Generated.js.map +1 -0
  5. package/dist/OpenAiClient.d.ts +124 -0
  6. package/dist/OpenAiClient.d.ts.map +1 -0
  7. package/dist/OpenAiClient.js +128 -0
  8. package/dist/OpenAiClient.js.map +1 -0
  9. package/dist/{dts/OpenAiConfig.d.ts → OpenAiConfig.d.ts} +9 -9
  10. package/dist/OpenAiConfig.d.ts.map +1 -0
  11. package/dist/{esm/OpenAiConfig.js → OpenAiConfig.js} +8 -5
  12. package/dist/OpenAiConfig.js.map +1 -0
  13. package/dist/OpenAiError.d.ts +98 -0
  14. package/dist/OpenAiError.d.ts.map +1 -0
  15. package/dist/OpenAiError.js +10 -0
  16. package/dist/OpenAiError.js.map +1 -0
  17. package/dist/OpenAiLanguageModel.d.ts +318 -0
  18. package/dist/OpenAiLanguageModel.d.ts.map +1 -0
  19. package/dist/OpenAiLanguageModel.js +2207 -0
  20. package/dist/OpenAiLanguageModel.js.map +1 -0
  21. package/dist/{dts/OpenAiTelemetry.d.ts → OpenAiTelemetry.d.ts} +31 -13
  22. package/dist/OpenAiTelemetry.d.ts.map +1 -0
  23. package/dist/{esm/OpenAiTelemetry.js → OpenAiTelemetry.js} +11 -6
  24. package/dist/OpenAiTelemetry.js.map +1 -0
  25. package/dist/OpenAiTool.d.ts +479 -0
  26. package/dist/OpenAiTool.d.ts.map +1 -0
  27. package/dist/OpenAiTool.js +231 -0
  28. package/dist/OpenAiTool.js.map +1 -0
  29. package/dist/index.d.ts +58 -0
  30. package/dist/index.d.ts.map +1 -0
  31. package/dist/index.js +59 -0
  32. package/dist/index.js.map +1 -0
  33. package/dist/internal/errors.d.ts +2 -0
  34. package/dist/internal/errors.d.ts.map +1 -0
  35. package/dist/internal/errors.js +316 -0
  36. package/dist/internal/errors.js.map +1 -0
  37. package/dist/{dts/internal → internal}/utilities.d.ts.map +1 -1
  38. package/dist/{esm/internal → internal}/utilities.js +4 -3
  39. package/dist/internal/utilities.js.map +1 -0
  40. package/package.json +45 -97
  41. package/src/Generated.ts +28521 -20036
  42. package/src/OpenAiClient.ts +220 -1816
  43. package/src/OpenAiConfig.ts +20 -34
  44. package/src/OpenAiError.ts +107 -0
  45. package/src/OpenAiLanguageModel.ts +1807 -638
  46. package/src/OpenAiTelemetry.ts +24 -19
  47. package/src/OpenAiTool.ts +216 -70
  48. package/src/index.ts +35 -8
  49. package/src/internal/errors.ts +347 -0
  50. package/src/internal/utilities.ts +7 -5
  51. package/Generated/package.json +0 -6
  52. package/OpenAiClient/package.json +0 -6
  53. package/OpenAiConfig/package.json +0 -6
  54. package/OpenAiEmbeddingModel/package.json +0 -6
  55. package/OpenAiLanguageModel/package.json +0 -6
  56. package/OpenAiTelemetry/package.json +0 -6
  57. package/OpenAiTokenizer/package.json +0 -6
  58. package/OpenAiTool/package.json +0 -6
  59. package/README.md +0 -5
  60. package/dist/cjs/Generated.js +0 -7150
  61. package/dist/cjs/Generated.js.map +0 -1
  62. package/dist/cjs/OpenAiClient.js +0 -1567
  63. package/dist/cjs/OpenAiClient.js.map +0 -1
  64. package/dist/cjs/OpenAiConfig.js +0 -30
  65. package/dist/cjs/OpenAiConfig.js.map +0 -1
  66. package/dist/cjs/OpenAiEmbeddingModel.js +0 -155
  67. package/dist/cjs/OpenAiEmbeddingModel.js.map +0 -1
  68. package/dist/cjs/OpenAiLanguageModel.js +0 -1147
  69. package/dist/cjs/OpenAiLanguageModel.js.map +0 -1
  70. package/dist/cjs/OpenAiTelemetry.js +0 -38
  71. package/dist/cjs/OpenAiTelemetry.js.map +0 -1
  72. package/dist/cjs/OpenAiTokenizer.js +0 -83
  73. package/dist/cjs/OpenAiTokenizer.js.map +0 -1
  74. package/dist/cjs/OpenAiTool.js +0 -93
  75. package/dist/cjs/OpenAiTool.js.map +0 -1
  76. package/dist/cjs/index.js +0 -24
  77. package/dist/cjs/index.js.map +0 -1
  78. package/dist/cjs/internal/utilities.js +0 -32
  79. package/dist/cjs/internal/utilities.js.map +0 -1
  80. package/dist/dts/Generated.d.ts +0 -40661
  81. package/dist/dts/Generated.d.ts.map +0 -1
  82. package/dist/dts/OpenAiClient.d.ts +0 -3120
  83. package/dist/dts/OpenAiClient.d.ts.map +0 -1
  84. package/dist/dts/OpenAiConfig.d.ts.map +0 -1
  85. package/dist/dts/OpenAiEmbeddingModel.d.ts +0 -109
  86. package/dist/dts/OpenAiEmbeddingModel.d.ts.map +0 -1
  87. package/dist/dts/OpenAiLanguageModel.d.ts +0 -235
  88. package/dist/dts/OpenAiLanguageModel.d.ts.map +0 -1
  89. package/dist/dts/OpenAiTelemetry.d.ts.map +0 -1
  90. package/dist/dts/OpenAiTokenizer.d.ts +0 -17
  91. package/dist/dts/OpenAiTokenizer.d.ts.map +0 -1
  92. package/dist/dts/OpenAiTool.d.ts +0 -200
  93. package/dist/dts/OpenAiTool.d.ts.map +0 -1
  94. package/dist/dts/index.d.ts +0 -33
  95. package/dist/dts/index.d.ts.map +0 -1
  96. package/dist/esm/Generated.js +0 -7150
  97. package/dist/esm/Generated.js.map +0 -1
  98. package/dist/esm/OpenAiClient.js +0 -1504
  99. package/dist/esm/OpenAiClient.js.map +0 -1
  100. package/dist/esm/OpenAiConfig.js.map +0 -1
  101. package/dist/esm/OpenAiEmbeddingModel.js +0 -143
  102. package/dist/esm/OpenAiEmbeddingModel.js.map +0 -1
  103. package/dist/esm/OpenAiLanguageModel.js +0 -1134
  104. package/dist/esm/OpenAiLanguageModel.js.map +0 -1
  105. package/dist/esm/OpenAiTelemetry.js.map +0 -1
  106. package/dist/esm/OpenAiTokenizer.js +0 -73
  107. package/dist/esm/OpenAiTokenizer.js.map +0 -1
  108. package/dist/esm/OpenAiTool.js +0 -84
  109. package/dist/esm/OpenAiTool.js.map +0 -1
  110. package/dist/esm/index.js +0 -33
  111. package/dist/esm/index.js.map +0 -1
  112. package/dist/esm/internal/utilities.js.map +0 -1
  113. package/dist/esm/package.json +0 -4
  114. package/index/package.json +0 -6
  115. package/src/OpenAiEmbeddingModel.ts +0 -243
  116. package/src/OpenAiTokenizer.ts +0 -70
  117. /package/dist/{dts/internal → internal}/utilities.d.ts +0 -0
@@ -0,0 +1,2207 @@
1
+ /**
2
+ * OpenAI Language Model implementation.
3
+ *
4
+ * Provides a LanguageModel implementation for OpenAI's responses API,
5
+ * supporting text generation, structured output, tool calling, and streaming.
6
+ *
7
+ * @since 1.0.0
8
+ */
9
+ import * as DateTime from "effect/DateTime";
10
+ import * as Effect from "effect/Effect";
11
+ import * as Base64 from "effect/encoding/Base64";
12
+ import { dual } from "effect/Function";
13
+ import * as Layer from "effect/Layer";
14
+ import * as Predicate from "effect/Predicate";
15
+ import * as Redactable from "effect/Redactable";
16
+ import * as Schema from "effect/Schema";
17
+ import * as AST from "effect/SchemaAST";
18
+ import * as ServiceMap from "effect/ServiceMap";
19
+ import * as Stream from "effect/Stream";
20
+ import * as AiError from "effect/unstable/ai/AiError";
21
+ import * as IdGenerator from "effect/unstable/ai/IdGenerator";
22
+ import * as LanguageModel from "effect/unstable/ai/LanguageModel";
23
+ import * as AiModel from "effect/unstable/ai/Model";
24
+ import * as Tool from "effect/unstable/ai/Tool";
25
+ import * as Generated from "./Generated.js";
26
+ import * as InternalUtilities from "./internal/utilities.js";
27
+ import { OpenAiClient } from "./OpenAiClient.js";
28
+ import { addGenAIAnnotations } from "./OpenAiTelemetry.js";
29
+ const ResponseModelIds = Generated.ModelIdsResponses.members[1];
30
+ const SharedModelIds = Generated.ModelIdsShared.members[1];
31
+ // =============================================================================
32
+ // Configuration
33
+ // =============================================================================
34
+ /**
35
+ * Service definition for OpenAI language model configuration.
36
+ *
37
+ * @since 1.0.0
38
+ * @category services
39
+ */
40
+ export class Config extends /*#__PURE__*/ServiceMap.Service()("@effect/ai-openai/OpenAiLanguageModel/Config") {}
41
+ // =============================================================================
42
+ // Language Model
43
+ // =============================================================================
44
+ /**
45
+ * @since 1.0.0
46
+ * @category constructors
47
+ */
48
+ export const model = (model, config) => AiModel.make("openai", layer({
49
+ model,
50
+ config
51
+ }));
52
+ // TODO
53
+ // /**
54
+ // * @since 1.0.0
55
+ // * @category constructors
56
+ // */
57
+ // export const modelWithTokenizer = (
58
+ // model: (string & {}) | Model,
59
+ // config?: Omit<typeof Config.Service, "model">
60
+ // ): AiModel.Model<"openai", LanguageModel.LanguageModel | Tokenizer.Tokenizer, OpenAiClient> =>
61
+ // AiModel.make("openai", layerWithTokenizer({ model, config }))
62
+ /**
63
+ * Creates an OpenAI language model service.
64
+ *
65
+ * @since 1.0.0
66
+ * @category constructors
67
+ */
68
+ export const make = /*#__PURE__*/Effect.fnUntraced(function* ({
69
+ model,
70
+ config: providerConfig
71
+ }) {
72
+ const client = yield* OpenAiClient;
73
+ const makeConfig = Effect.gen(function* () {
74
+ const services = yield* Effect.services();
75
+ return {
76
+ model,
77
+ ...providerConfig,
78
+ ...services.mapUnsafe.get(Config.key)
79
+ };
80
+ });
81
+ const makeRequest = Effect.fnUntraced(function* ({
82
+ config,
83
+ options,
84
+ toolNameMapper
85
+ }) {
86
+ const include = new Set();
87
+ const capabilities = getModelCapabilities(config.model);
88
+ const messages = yield* prepareMessages({
89
+ config,
90
+ options,
91
+ capabilities,
92
+ include,
93
+ toolNameMapper
94
+ });
95
+ const {
96
+ toolChoice,
97
+ tools
98
+ } = yield* prepareTools({
99
+ config,
100
+ options,
101
+ toolNameMapper
102
+ });
103
+ const responseFormat = prepareResponseFormat({
104
+ config,
105
+ options
106
+ });
107
+ const request = {
108
+ ...config,
109
+ input: messages,
110
+ include: include.size > 0 ? Array.from(include) : null,
111
+ text: {
112
+ verbosity: config.text?.verbosity ?? null,
113
+ format: responseFormat
114
+ },
115
+ ...(Predicate.isNotUndefined(tools) ? {
116
+ tools
117
+ } : undefined),
118
+ ...(Predicate.isNotUndefined(toolChoice) ? {
119
+ tool_choice: toolChoice
120
+ } : undefined)
121
+ };
122
+ return request;
123
+ });
124
+ return yield* LanguageModel.make({
125
+ generateText: Effect.fnUntraced(function* (options) {
126
+ const config = yield* makeConfig;
127
+ const toolNameMapper = new Tool.NameMapper(options.tools);
128
+ const request = yield* makeRequest({
129
+ config,
130
+ options,
131
+ toolNameMapper
132
+ });
133
+ annotateRequest(options.span, request);
134
+ const [rawResponse, response] = yield* client.createResponse(request);
135
+ annotateResponse(options.span, rawResponse);
136
+ return yield* makeResponse({
137
+ options,
138
+ rawResponse,
139
+ response,
140
+ toolNameMapper
141
+ });
142
+ }),
143
+ streamText: Effect.fnUntraced(function* (options) {
144
+ const config = yield* makeConfig;
145
+ const toolNameMapper = new Tool.NameMapper(options.tools);
146
+ const request = yield* makeRequest({
147
+ config,
148
+ options,
149
+ toolNameMapper
150
+ });
151
+ annotateRequest(options.span, request);
152
+ const [response, stream] = yield* client.createResponseStream(request);
153
+ return yield* makeStreamResponse({
154
+ stream,
155
+ response,
156
+ config,
157
+ options,
158
+ toolNameMapper
159
+ });
160
+ }, (effect, options) => effect.pipe(Stream.unwrap, Stream.map(response => {
161
+ annotateStreamResponse(options.span, response);
162
+ return response;
163
+ })))
164
+ });
165
+ });
166
+ /**
167
+ * Creates a layer for the OpenAI language model.
168
+ *
169
+ * @since 1.0.0
170
+ * @category layers
171
+ */
172
+ export const layer = options => Layer.effect(LanguageModel.LanguageModel, make(options));
173
+ /**
174
+ * Provides config overrides for OpenAI language model operations.
175
+ *
176
+ * @since 1.0.0
177
+ * @category configuration
178
+ */
179
+ export const withConfigOverride = /*#__PURE__*/dual(2, (self, overrides) => Effect.flatMap(Effect.serviceOption(Config), config => Effect.provideService(self, Config, {
180
+ ...(config._tag === "Some" ? config.value : {}),
181
+ ...overrides
182
+ })));
183
+ // =============================================================================
184
+ // Prompt Conversion
185
+ // =============================================================================
186
+ const getSystemMessageMode = model => model.startsWith("o") || model.startsWith("gpt-5") || model.startsWith("codex-") || model.startsWith("computer-use") ? "developer" : "system";
187
+ const prepareMessages = /*#__PURE__*/Effect.fnUntraced(function* ({
188
+ config,
189
+ options,
190
+ capabilities,
191
+ include,
192
+ toolNameMapper
193
+ }) {
194
+ const processedApprovalIds = new Set();
195
+ const hasConversation = Predicate.isNotNullish(config.conversation);
196
+ // Provider-Defined Tools
197
+ const applyPatchTool = options.tools.find(tool => Tool.isProviderDefined(tool) && tool.name === "OpenAiApplyPatch");
198
+ const codeInterpreterTool = options.tools.find(tool => Tool.isProviderDefined(tool) && tool.name === "OpenAiCodeInterpreter");
199
+ const shellTool = options.tools.find(tool => Tool.isProviderDefined(tool) && tool.name === "OpenAiFunctionShell");
200
+ const localShellTool = options.tools.find(tool => Tool.isProviderDefined(tool) && tool.name === "OpenAiLocalShell");
201
+ const webSearchTool = options.tools.find(tool => Tool.isProviderDefined(tool) && tool.name === "OpenAiWebSearch");
202
+ const webSearchPreviewTool = options.tools.find(tool => Tool.isProviderDefined(tool) && tool.name === "OpenAiWebSearchPreview");
203
+ // Handle Included Features
204
+ if (Predicate.isNotUndefined(config.top_logprobs)) {
205
+ include.add("message.output_text.logprobs");
206
+ }
207
+ if (config.store === false && capabilities.isReasoningModel) {
208
+ include.add("reasoning.encrypted_content");
209
+ }
210
+ if (Predicate.isNotUndefined(codeInterpreterTool)) {
211
+ include.add("code_interpreter_call.outputs");
212
+ }
213
+ if (Predicate.isNotUndefined(webSearchTool) || Predicate.isNotUndefined(webSearchPreviewTool)) {
214
+ include.add("web_search_call.action.sources");
215
+ }
216
+ const messages = [];
217
+ for (const message of options.prompt.content) {
218
+ switch (message.role) {
219
+ case "system":
220
+ {
221
+ messages.push({
222
+ role: getSystemMessageMode(config.model),
223
+ content: message.content
224
+ });
225
+ break;
226
+ }
227
+ case "user":
228
+ {
229
+ const content = [];
230
+ for (let index = 0; index < message.content.length; index++) {
231
+ const part = message.content[index];
232
+ switch (part.type) {
233
+ case "text":
234
+ {
235
+ content.push({
236
+ type: "input_text",
237
+ text: part.text
238
+ });
239
+ break;
240
+ }
241
+ case "file":
242
+ {
243
+ if (part.mediaType.startsWith("image/")) {
244
+ const detail = getImageDetail(part);
245
+ const mediaType = part.mediaType === "image/*" ? "image/jpeg" : part.mediaType;
246
+ if (typeof part.data === "string" && isFileId(part.data, config)) {
247
+ content.push({
248
+ type: "input_image",
249
+ file_id: part.data,
250
+ detail
251
+ });
252
+ }
253
+ if (part.data instanceof URL) {
254
+ content.push({
255
+ type: "input_image",
256
+ image_url: part.data.toString(),
257
+ detail
258
+ });
259
+ }
260
+ if (part.data instanceof Uint8Array) {
261
+ const base64 = Base64.encode(part.data);
262
+ const imageUrl = `data:${mediaType};base64,${base64}`;
263
+ content.push({
264
+ type: "input_image",
265
+ image_url: imageUrl,
266
+ detail
267
+ });
268
+ }
269
+ } else if (part.mediaType === "application/pdf") {
270
+ if (typeof part.data === "string" && isFileId(part.data, config)) {
271
+ content.push({
272
+ type: "input_file",
273
+ file_id: part.data
274
+ });
275
+ }
276
+ if (part.data instanceof URL) {
277
+ content.push({
278
+ type: "input_file",
279
+ file_url: part.data.toString()
280
+ });
281
+ }
282
+ if (part.data instanceof Uint8Array) {
283
+ const base64 = Base64.encode(part.data);
284
+ const fileName = part.fileName ?? `part-${index}.pdf`;
285
+ const fileData = `data:application/pdf;base64,${base64}`;
286
+ content.push({
287
+ type: "input_file",
288
+ filename: fileName,
289
+ file_data: fileData
290
+ });
291
+ }
292
+ } else {
293
+ return yield* AiError.make({
294
+ module: "OpenAiLanguageModel",
295
+ method: "prepareMessages",
296
+ reason: new AiError.InvalidRequestError({
297
+ description: `Detected unsupported media type for file: '${part.mediaType}'`
298
+ })
299
+ });
300
+ }
301
+ }
302
+ }
303
+ }
304
+ messages.push({
305
+ role: "user",
306
+ content
307
+ });
308
+ break;
309
+ }
310
+ case "assistant":
311
+ {
312
+ const reasoningMessages = {};
313
+ for (const part of message.content) {
314
+ switch (part.type) {
315
+ case "text":
316
+ {
317
+ const id = getItemId(part);
318
+ // When in conversation mode, skip items that already exist in the
319
+ // conversation context to avoid "Duplicate item found" errors
320
+ if (hasConversation && Predicate.isNotNull(id)) {
321
+ break;
322
+ }
323
+ if (config.store === true && Predicate.isNotNull(id)) {
324
+ messages.push({
325
+ type: "item_reference",
326
+ id
327
+ });
328
+ break;
329
+ }
330
+ messages.push({
331
+ id: id,
332
+ type: "message",
333
+ role: "assistant",
334
+ status: part.options.openai?.status ?? "completed",
335
+ content: [{
336
+ type: "output_text",
337
+ text: part.text,
338
+ annotations: part.options.openai?.annotations ?? [],
339
+ logprobs: []
340
+ }]
341
+ });
342
+ break;
343
+ }
344
+ case "reasoning":
345
+ {
346
+ const id = getItemId(part);
347
+ const encryptedContent = getEncryptedContent(part);
348
+ if (hasConversation && Predicate.isNotNull(id)) {
349
+ break;
350
+ }
351
+ if (Predicate.isNotNull(id)) {
352
+ const message = reasoningMessages[id];
353
+ if (config.store === true) {
354
+ // Use item references to refer to reasoning (single reference)
355
+ // when the first part is encountered
356
+ if (Predicate.isUndefined(message)) {
357
+ messages.push({
358
+ type: "item_reference",
359
+ id
360
+ });
361
+ // Store unused reasoning message to mark its id as used
362
+ reasoningMessages[id] = {
363
+ type: "reasoning",
364
+ id,
365
+ summary: []
366
+ };
367
+ }
368
+ } else {
369
+ const summaryParts = [];
370
+ if (part.text.length > 0) {
371
+ summaryParts.push({
372
+ type: "summary_text",
373
+ text: part.text
374
+ });
375
+ }
376
+ if (Predicate.isUndefined(message)) {
377
+ reasoningMessages[id] = {
378
+ type: "reasoning",
379
+ id,
380
+ summary: summaryParts,
381
+ encrypted_content: encryptedContent ?? null
382
+ };
383
+ messages.push(reasoningMessages[id]);
384
+ } else {
385
+ message.summary.push(...summaryParts);
386
+ // Update encrypted content to enable setting it in the
387
+ // last summary part
388
+ if (Predicate.isNotNull(encryptedContent)) {
389
+ message.encrypted_content = encryptedContent;
390
+ }
391
+ }
392
+ }
393
+ }
394
+ break;
395
+ }
396
+ case "tool-call":
397
+ {
398
+ const id = getItemId(part);
399
+ const status = getStatus(part);
400
+ if (hasConversation && Predicate.isNotNull(id)) {
401
+ break;
402
+ }
403
+ if (config.store && Predicate.isNotNull(id)) {
404
+ messages.push({
405
+ type: "item_reference",
406
+ id
407
+ });
408
+ break;
409
+ }
410
+ if (part.providerExecuted) {
411
+ break;
412
+ }
413
+ const toolName = toolNameMapper.getProviderName(part.name);
414
+ if (Predicate.isNotUndefined(localShellTool) && toolName === "local_shell") {
415
+ const params = yield* Schema.decodeUnknownEffect(localShellTool.parametersSchema)(part.params).pipe(Effect.mapError(error => AiError.make({
416
+ module: "OpenAiLanguageModel",
417
+ method: "prepareMessages",
418
+ reason: new AiError.ToolParameterValidationError({
419
+ toolName: "local_shell",
420
+ toolParams: part.params,
421
+ description: error.message
422
+ })
423
+ })));
424
+ messages.push({
425
+ id: id,
426
+ type: "local_shell_call",
427
+ call_id: part.id,
428
+ status: status ?? "completed",
429
+ action: params.action
430
+ });
431
+ break;
432
+ }
433
+ if (Predicate.isNotUndefined(shellTool) && toolName === "shell") {
434
+ const params = yield* Schema.decodeUnknownEffect(shellTool.parametersSchema)(part.params).pipe(Effect.mapError(error => AiError.make({
435
+ module: "OpenAiLanguageModel",
436
+ method: "prepareMessages",
437
+ reason: new AiError.ToolParameterValidationError({
438
+ toolName: "shell",
439
+ toolParams: part.params,
440
+ description: error.message
441
+ })
442
+ })));
443
+ messages.push({
444
+ id: id,
445
+ type: "shell_call",
446
+ call_id: part.id,
447
+ status: status ?? "completed",
448
+ action: params.action
449
+ });
450
+ break;
451
+ }
452
+ messages.push({
453
+ type: "function_call",
454
+ name: toolName,
455
+ call_id: part.id,
456
+ arguments: JSON.stringify(part.params),
457
+ ...(Predicate.isNotNull(id) ? {
458
+ id
459
+ } : {}),
460
+ ...(Predicate.isNotNull(status) ? {
461
+ status
462
+ } : {})
463
+ });
464
+ break;
465
+ }
466
+ // Assistant tool-result parts are always provider executed
467
+ case "tool-result":
468
+ {
469
+ // Skip execution denied results - these have no corresponding
470
+ // item in OpenAI's store
471
+ if (Predicate.hasProperty(part.result, "type") && part.result.type === "execution-denied") {
472
+ break;
473
+ }
474
+ if (hasConversation) {
475
+ break;
476
+ }
477
+ if (config.store === true) {
478
+ const id = getItemId(part) ?? part.id;
479
+ messages.push({
480
+ type: "item_reference",
481
+ id
482
+ });
483
+ }
484
+ }
485
+ }
486
+ }
487
+ break;
488
+ }
489
+ case "tool":
490
+ {
491
+ for (const part of message.content) {
492
+ if (part.type === "tool-approval-response") {
493
+ if (processedApprovalIds.has(part.approvalId)) {
494
+ continue;
495
+ }
496
+ processedApprovalIds.add(part.approvalId);
497
+ if (config.store === true) {
498
+ messages.push({
499
+ type: "item_reference",
500
+ id: part.approvalId
501
+ });
502
+ }
503
+ messages.push({
504
+ type: "mcp_approval_response",
505
+ approval_request_id: part.approvalId,
506
+ approve: part.approved
507
+ });
508
+ continue;
509
+ }
510
+ // Skip execution-denied results that already have an approvalId -
511
+ // this indicates that the part was already handled via tool-approval-response
512
+ if (Predicate.hasProperty(part.result, "type") && part.result.type === "execution-denied") {
513
+ if (Predicate.isNotNullish(part.options.openai?.approvalId)) {
514
+ continue;
515
+ }
516
+ }
517
+ const id = getItemId(part) ?? part.id;
518
+ const status = getStatus(part);
519
+ const toolName = toolNameMapper.getProviderName(part.name);
520
+ if (Predicate.isNotUndefined(applyPatchTool) && toolName === "apply_patch") {
521
+ messages.push({
522
+ id,
523
+ type: "apply_patch_call_output",
524
+ call_id: part.id,
525
+ ...part.result
526
+ });
527
+ }
528
+ if (Predicate.isNotUndefined(shellTool) && toolName === "shell") {
529
+ messages.push({
530
+ id,
531
+ type: "shell_call_output",
532
+ call_id: part.id,
533
+ output: part.result,
534
+ ...(Predicate.isNotNull(status) ? {
535
+ status
536
+ } : {})
537
+ });
538
+ }
539
+ if (Predicate.isNotUndefined(localShellTool) && toolName === "local_shell") {
540
+ messages.push({
541
+ id,
542
+ type: "local_shell_call_output",
543
+ call_id: part.id,
544
+ output: part.result,
545
+ ...(Predicate.isNotNull(status) ? {
546
+ status
547
+ } : {})
548
+ });
549
+ }
550
+ messages.push({
551
+ type: "function_call_output",
552
+ call_id: part.id,
553
+ output: JSON.stringify(part.result),
554
+ ...(Predicate.isNotNull(status) ? {
555
+ status
556
+ } : {})
557
+ });
558
+ }
559
+ break;
560
+ }
561
+ }
562
+ }
563
+ return messages;
564
+ });
565
+ // =============================================================================
566
+ // HTTP Details
567
+ // =============================================================================
568
+ const buildHttpRequestDetails = request => ({
569
+ method: request.method,
570
+ url: request.url,
571
+ urlParams: Array.from(request.urlParams),
572
+ hash: request.hash,
573
+ headers: Redactable.redact(request.headers)
574
+ });
575
+ const buildHttpResponseDetails = response => ({
576
+ status: response.status,
577
+ headers: Redactable.redact(response.headers)
578
+ });
579
+ const makeResponse = /*#__PURE__*/Effect.fnUntraced(function* ({
580
+ options,
581
+ rawResponse,
582
+ response,
583
+ toolNameMapper
584
+ }) {
585
+ const idGenerator = yield* IdGenerator.IdGenerator;
586
+ const approvalRequests = getApprovalRequestIdMapping(options.prompt);
587
+ const webSearchTool = options.tools.find(tool => Tool.isProviderDefined(tool) && (tool.name === "OpenAiWebSearch" || tool.name === "OpenAiWebSearchPreview"));
588
+ let hasToolCalls = false;
589
+ const parts = [];
590
+ const createdAt = new Date(rawResponse.created_at * 1000);
591
+ parts.push({
592
+ type: "response-metadata",
593
+ id: rawResponse.id,
594
+ modelId: rawResponse.model,
595
+ timestamp: DateTime.formatIso(DateTime.fromDateUnsafe(createdAt)),
596
+ request: buildHttpRequestDetails(response.request)
597
+ });
598
+ for (const part of rawResponse.output) {
599
+ switch (part.type) {
600
+ case "apply_patch_call":
601
+ {
602
+ const toolName = toolNameMapper.getCustomName("apply_patch");
603
+ parts.push({
604
+ type: "tool-call",
605
+ id: part.call_id,
606
+ name: toolName,
607
+ params: {
608
+ call_id: part.call_id,
609
+ operation: part.operation
610
+ },
611
+ metadata: {
612
+ openai: {
613
+ ...makeItemIdMetadata(part.id)
614
+ }
615
+ }
616
+ });
617
+ break;
618
+ }
619
+ case "code_interpreter_call":
620
+ {
621
+ const toolName = toolNameMapper.getCustomName("code_interpreter");
622
+ parts.push({
623
+ type: "tool-call",
624
+ id: part.id,
625
+ name: toolName,
626
+ params: {
627
+ code: part.code,
628
+ container_id: part.container_id
629
+ },
630
+ providerExecuted: true
631
+ });
632
+ parts.push({
633
+ type: "tool-result",
634
+ id: part.id,
635
+ name: toolName,
636
+ isFailure: false,
637
+ result: {
638
+ outputs: part.outputs
639
+ },
640
+ providerExecuted: true
641
+ });
642
+ break;
643
+ }
644
+ case "file_search_call":
645
+ {
646
+ const toolName = toolNameMapper.getCustomName("file_search");
647
+ parts.push({
648
+ type: "tool-call",
649
+ id: part.id,
650
+ name: toolName,
651
+ params: {},
652
+ providerExecuted: true
653
+ });
654
+ parts.push({
655
+ type: "tool-result",
656
+ id: part.id,
657
+ name: toolName,
658
+ isFailure: false,
659
+ result: {
660
+ status: part.status,
661
+ queries: part.queries,
662
+ results: part.results ?? null
663
+ },
664
+ providerExecuted: true
665
+ });
666
+ break;
667
+ }
668
+ case "function_call":
669
+ {
670
+ hasToolCalls = true;
671
+ const toolName = part.name;
672
+ const toolParams = part.arguments;
673
+ const params = yield* Effect.try({
674
+ try: () => Tool.unsafeSecureJsonParse(toolParams),
675
+ catch: cause => AiError.make({
676
+ module: "OpenAiLanguageModel",
677
+ method: "makeResponse",
678
+ reason: new AiError.ToolParameterValidationError({
679
+ toolName,
680
+ toolParams: {},
681
+ description: `Faled to securely JSON parse tool parameters: ${cause}`
682
+ })
683
+ })
684
+ });
685
+ parts.push({
686
+ type: "tool-call",
687
+ id: part.call_id,
688
+ name: toolName,
689
+ params,
690
+ metadata: {
691
+ openai: {
692
+ ...makeItemIdMetadata(part.id)
693
+ }
694
+ }
695
+ });
696
+ break;
697
+ }
698
+ case "image_generation_call":
699
+ {
700
+ const toolName = toolNameMapper.getCustomName("image_generation");
701
+ parts.push({
702
+ type: "tool-call",
703
+ id: part.id,
704
+ name: toolName,
705
+ params: {},
706
+ providerExecuted: true
707
+ });
708
+ parts.push({
709
+ type: "tool-result",
710
+ id: part.id,
711
+ name: toolName,
712
+ isFailure: false,
713
+ result: {
714
+ result: part.result
715
+ }
716
+ });
717
+ break;
718
+ }
719
+ case "local_shell_call":
720
+ {
721
+ const toolName = toolNameMapper.getCustomName("local_shell");
722
+ parts.push({
723
+ type: "tool-call",
724
+ id: part.call_id,
725
+ name: toolName,
726
+ params: {
727
+ action: part.action
728
+ },
729
+ metadata: {
730
+ openai: {
731
+ ...makeItemIdMetadata(part.id)
732
+ }
733
+ }
734
+ });
735
+ break;
736
+ }
737
+ case "mcp_call":
738
+ {
739
+ const toolId = Predicate.isNotNullish(part.approval_request_id) ? approvalRequests.get(part.approval_request_id) ?? part.id : part.id;
740
+ const toolName = `mcp.${part.name}`;
741
+ parts.push({
742
+ type: "tool-call",
743
+ id: toolId,
744
+ name: toolName,
745
+ params: part.arguments,
746
+ providerExecuted: true
747
+ });
748
+ parts.push({
749
+ type: "tool-result",
750
+ id: toolId,
751
+ name: toolName,
752
+ isFailure: false,
753
+ providerExecuted: true,
754
+ result: {
755
+ type: "call",
756
+ name: part.name,
757
+ arguments: part.arguments,
758
+ server_label: part.server_label,
759
+ ...(Predicate.isNotNullish(part.output) ? {
760
+ output: part.output
761
+ } : undefined),
762
+ ...(Predicate.isNotNullish(part.error) ? {
763
+ error: part.error
764
+ } : undefined)
765
+ },
766
+ metadata: {
767
+ openai: {
768
+ ...makeItemIdMetadata(part.id)
769
+ }
770
+ }
771
+ });
772
+ break;
773
+ }
774
+ case "mcp_list_tools":
775
+ {
776
+ // Skip
777
+ break;
778
+ }
779
+ case "mcp_approval_request":
780
+ {
781
+ const approvalRequestId = part.approval_request_id ?? part.id;
782
+ 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
+ })
795
+ });
796
+ parts.push({
797
+ type: "tool-call",
798
+ id: toolId,
799
+ name: toolName,
800
+ params,
801
+ providerExecuted: true
802
+ });
803
+ parts.push({
804
+ type: "tool-approval-request",
805
+ toolCallId: toolId,
806
+ approvalId: approvalRequestId
807
+ });
808
+ break;
809
+ }
810
+ case "message":
811
+ {
812
+ for (const contentPart of part.content) {
813
+ switch (contentPart.type) {
814
+ case "output_text":
815
+ {
816
+ const annotations = contentPart.annotations.length > 0 ? {
817
+ annotations: contentPart.annotations
818
+ } : undefined;
819
+ parts.push({
820
+ type: "text",
821
+ text: contentPart.text,
822
+ metadata: {
823
+ openai: {
824
+ ...makeItemIdMetadata(part.id),
825
+ ...annotations
826
+ }
827
+ }
828
+ });
829
+ for (const annotation of contentPart.annotations) {
830
+ if (annotation.type === "container_file_citation") {
831
+ parts.push({
832
+ type: "source",
833
+ sourceType: "document",
834
+ id: yield* idGenerator.generateId(),
835
+ mediaType: "text/plain",
836
+ title: annotation.filename,
837
+ fileName: annotation.filename,
838
+ metadata: {
839
+ openai: {
840
+ type: annotation.type,
841
+ fileId: annotation.file_id,
842
+ containerId: annotation.container_id
843
+ }
844
+ }
845
+ });
846
+ }
847
+ if (annotation.type === "file_citation") {
848
+ parts.push({
849
+ type: "source",
850
+ sourceType: "document",
851
+ id: yield* idGenerator.generateId(),
852
+ mediaType: "text/plain",
853
+ title: annotation.filename,
854
+ fileName: annotation.filename,
855
+ metadata: {
856
+ openai: {
857
+ type: annotation.type,
858
+ fileId: annotation.file_id,
859
+ index: annotation.index
860
+ }
861
+ }
862
+ });
863
+ }
864
+ if (annotation.type === "file_path") {
865
+ parts.push({
866
+ type: "source",
867
+ sourceType: "document",
868
+ id: yield* idGenerator.generateId(),
869
+ mediaType: "application/octet-stream",
870
+ title: annotation.file_id,
871
+ fileName: annotation.file_id,
872
+ metadata: {
873
+ openai: {
874
+ type: annotation.type,
875
+ fileId: annotation.file_id,
876
+ index: annotation.index
877
+ }
878
+ }
879
+ });
880
+ }
881
+ if (annotation.type === "url_citation") {
882
+ parts.push({
883
+ type: "source",
884
+ sourceType: "url",
885
+ id: yield* idGenerator.generateId(),
886
+ url: annotation.url,
887
+ title: annotation.title,
888
+ metadata: {
889
+ openai: {
890
+ type: annotation.type,
891
+ startIndex: annotation.start_index,
892
+ endIndex: annotation.end_index
893
+ }
894
+ }
895
+ });
896
+ }
897
+ }
898
+ break;
899
+ }
900
+ case "refusal":
901
+ {
902
+ parts.push({
903
+ type: "text",
904
+ text: "",
905
+ metadata: {
906
+ openai: {
907
+ refusal: contentPart.refusal
908
+ }
909
+ }
910
+ });
911
+ break;
912
+ }
913
+ }
914
+ }
915
+ break;
916
+ }
917
+ case "reasoning":
918
+ {
919
+ const metadata = {
920
+ openai: {
921
+ ...makeItemIdMetadata(part.id),
922
+ ...makeEncryptedContentMetadata(part.encrypted_content)
923
+ }
924
+ };
925
+ // If there are no summary parts, we have to add an empty one to
926
+ // propagate the part identifier and encrypted content
927
+ if (part.summary.length === 0) {
928
+ parts.push({
929
+ type: "reasoning",
930
+ text: "",
931
+ metadata
932
+ });
933
+ } else {
934
+ for (const summary of part.summary) {
935
+ parts.push({
936
+ type: "reasoning",
937
+ text: summary.text,
938
+ metadata
939
+ });
940
+ }
941
+ }
942
+ break;
943
+ }
944
+ case "shell_call":
945
+ {
946
+ const toolName = toolNameMapper.getCustomName("shell");
947
+ parts.push({
948
+ type: "tool-call",
949
+ id: part.call_id,
950
+ name: toolName,
951
+ params: {
952
+ action: part.action
953
+ },
954
+ metadata: {
955
+ openai: {
956
+ ...makeItemIdMetadata(part.id)
957
+ }
958
+ }
959
+ });
960
+ break;
961
+ }
962
+ case "web_search_call":
963
+ {
964
+ const toolName = toolNameMapper.getCustomName(webSearchTool?.name ?? "web_search");
965
+ parts.push({
966
+ type: "tool-call",
967
+ id: part.id,
968
+ name: toolName,
969
+ params: {},
970
+ providerExecuted: true
971
+ });
972
+ parts.push({
973
+ type: "tool-result",
974
+ id: part.id,
975
+ name: toolName,
976
+ isFailure: false,
977
+ result: {
978
+ action: part.action,
979
+ status: part.status
980
+ },
981
+ providerExecuted: true
982
+ });
983
+ break;
984
+ }
985
+ }
986
+ }
987
+ const finishReason = InternalUtilities.resolveFinishReason(rawResponse.incomplete_details?.reason, hasToolCalls);
988
+ parts.push({
989
+ type: "finish",
990
+ reason: finishReason,
991
+ usage: getUsage(rawResponse.usage),
992
+ response: buildHttpResponseDetails(response),
993
+ ...(rawResponse.service_tier && {
994
+ metadata: {
995
+ openai: {
996
+ serviceTier: rawResponse.service_tier
997
+ }
998
+ }
999
+ })
1000
+ });
1001
+ return parts;
1002
+ });
1003
+ const makeStreamResponse = /*#__PURE__*/Effect.fnUntraced(function* ({
1004
+ stream,
1005
+ response,
1006
+ config,
1007
+ options,
1008
+ toolNameMapper
1009
+ }) {
1010
+ const idGenerator = yield* IdGenerator.IdGenerator;
1011
+ const approvalRequests = getApprovalRequestIdMapping(options.prompt);
1012
+ const streamApprovalRequests = new Map();
1013
+ let hasToolCalls = false;
1014
+ // Track annotations for current message to include in text-end metadata
1015
+ const activeAnnotations = [];
1016
+ // Track active reasoning items with state machine for proper concluding logic
1017
+ const activeReasoning = {};
1018
+ // Track active tool calls with optional provider-specific state
1019
+ const activeToolCalls = {};
1020
+ const webSearchTool = options.tools.find(tool => Tool.isProviderDefined(tool) && (tool.name === "OpenAiWebSearch" || tool.name === "OpenAiWebSearchPreview"));
1021
+ return stream.pipe(Stream.mapEffect(Effect.fnUntraced(function* (event) {
1022
+ const parts = [];
1023
+ switch (event.type) {
1024
+ case "response.created":
1025
+ {
1026
+ const createdAt = new Date(event.response.created_at * 1000);
1027
+ parts.push({
1028
+ type: "response-metadata",
1029
+ id: event.response.id,
1030
+ modelId: event.response.model,
1031
+ timestamp: DateTime.formatIso(DateTime.fromDateUnsafe(createdAt)),
1032
+ request: buildHttpRequestDetails(response.request)
1033
+ });
1034
+ break;
1035
+ }
1036
+ case "error":
1037
+ {
1038
+ parts.push({
1039
+ type: "error",
1040
+ error: event
1041
+ });
1042
+ break;
1043
+ }
1044
+ case "response.completed":
1045
+ case "response.incomplete":
1046
+ case "response.failed":
1047
+ {
1048
+ parts.push({
1049
+ type: "finish",
1050
+ reason: InternalUtilities.resolveFinishReason(event.response.incomplete_details?.reason, hasToolCalls),
1051
+ usage: getUsage(event.response.usage),
1052
+ response: buildHttpResponseDetails(response),
1053
+ ...(event.response.service_tier && {
1054
+ metadata: {
1055
+ openai: {
1056
+ serviceTier: event.response.service_tier
1057
+ }
1058
+ }
1059
+ })
1060
+ });
1061
+ break;
1062
+ }
1063
+ case "response.output_item.added":
1064
+ {
1065
+ switch (event.item.type) {
1066
+ case "apply_patch_call":
1067
+ {
1068
+ const toolId = event.item.call_id;
1069
+ const toolName = toolNameMapper.getCustomName("apply_patch");
1070
+ const operation = event.item.operation;
1071
+ activeToolCalls[event.output_index] = {
1072
+ id: toolId,
1073
+ name: toolName,
1074
+ applyPatch: {
1075
+ hasDiff: operation.type !== "delete_file",
1076
+ endEmitted: operation.type === "delete_file"
1077
+ }
1078
+ };
1079
+ parts.push({
1080
+ type: "tool-params-start",
1081
+ id: toolId,
1082
+ name: toolName
1083
+ });
1084
+ if (operation.type === "delete_file") {
1085
+ parts.push({
1086
+ type: "tool-params-delta",
1087
+ id: toolId,
1088
+ delta: JSON.stringify({
1089
+ call_id: toolId,
1090
+ operation: operation
1091
+ })
1092
+ });
1093
+ parts.push({
1094
+ type: "tool-params-end",
1095
+ id: toolId
1096
+ });
1097
+ } else {
1098
+ parts.push({
1099
+ type: "tool-params-delta",
1100
+ id: toolId,
1101
+ delta: `{"call_id":"${InternalUtilities.escapeJSONDelta(toolId)}",` + `"operation":{"type":"${InternalUtilities.escapeJSONDelta(operation.type)}",` + `"path":"${InternalUtilities.escapeJSONDelta(operation.path)}","diff":"`
1102
+ });
1103
+ }
1104
+ break;
1105
+ }
1106
+ case "code_interpreter_call":
1107
+ {
1108
+ const toolName = toolNameMapper.getCustomName("code_interpreter");
1109
+ activeToolCalls[event.output_index] = {
1110
+ id: event.item.id,
1111
+ name: toolName,
1112
+ codeInterpreter: {
1113
+ containerId: event.item.container_id
1114
+ }
1115
+ };
1116
+ parts.push({
1117
+ type: "tool-params-start",
1118
+ id: event.item.id,
1119
+ name: toolName,
1120
+ providerExecuted: true
1121
+ });
1122
+ parts.push({
1123
+ type: "tool-params-delta",
1124
+ id: event.item.id,
1125
+ delta: `{"containerId":"${event.item.container_id}","code":"`
1126
+ });
1127
+ break;
1128
+ }
1129
+ case "computer_call":
1130
+ {
1131
+ const toolName = toolNameMapper.getCustomName("computer_use");
1132
+ activeToolCalls[event.output_index] = {
1133
+ id: event.item.id,
1134
+ name: toolName
1135
+ };
1136
+ parts.push({
1137
+ type: "tool-params-start",
1138
+ id: event.item.id,
1139
+ name: toolName,
1140
+ providerExecuted: true
1141
+ });
1142
+ break;
1143
+ }
1144
+ case "file_search_call":
1145
+ {
1146
+ const toolName = toolNameMapper.getCustomName("file_search");
1147
+ parts.push({
1148
+ type: "tool-call",
1149
+ id: event.item.id,
1150
+ name: toolName,
1151
+ params: {},
1152
+ providerExecuted: true
1153
+ });
1154
+ break;
1155
+ }
1156
+ case "function_call":
1157
+ {
1158
+ activeToolCalls[event.output_index] = {
1159
+ id: event.item.call_id,
1160
+ name: event.item.name
1161
+ };
1162
+ parts.push({
1163
+ type: "tool-params-start",
1164
+ id: event.item.call_id,
1165
+ name: event.item.name
1166
+ });
1167
+ break;
1168
+ }
1169
+ case "image_generation_call":
1170
+ {
1171
+ const toolName = toolNameMapper.getCustomName("image_generation");
1172
+ parts.push({
1173
+ type: "tool-call",
1174
+ id: event.item.id,
1175
+ name: toolName,
1176
+ params: {},
1177
+ providerExecuted: true
1178
+ });
1179
+ break;
1180
+ }
1181
+ case "mcp_call":
1182
+ case "mcp_list_tools":
1183
+ case "mcp_approval_request":
1184
+ {
1185
+ // We emit MCP tool call / approvals on `output_item.done` to facilitate:
1186
+ // - Aliasing tool call identifiers when an approval request id exists
1187
+ // - Emit a proper tool-approval-request part for MCP approvals
1188
+ break;
1189
+ }
1190
+ case "message":
1191
+ {
1192
+ // Clear annotations for new message
1193
+ activeAnnotations.length = 0;
1194
+ parts.push({
1195
+ type: "text-start",
1196
+ id: event.item.id,
1197
+ metadata: {
1198
+ openai: {
1199
+ ...makeItemIdMetadata(event.item.id)
1200
+ }
1201
+ }
1202
+ });
1203
+ break;
1204
+ }
1205
+ case "reasoning":
1206
+ {
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)
1221
+ }
1222
+ }
1223
+ });
1224
+ break;
1225
+ }
1226
+ case "shell_call":
1227
+ {
1228
+ const toolName = toolNameMapper.getCustomName("shell");
1229
+ activeToolCalls[event.output_index] = {
1230
+ id: event.item.id,
1231
+ name: toolName
1232
+ };
1233
+ break;
1234
+ }
1235
+ case "web_search_call":
1236
+ {
1237
+ const toolName = toolNameMapper.getCustomName(webSearchTool?.providerName ?? "web_search");
1238
+ activeToolCalls[event.output_index] = {
1239
+ id: event.item.id,
1240
+ name: toolName
1241
+ };
1242
+ parts.push({
1243
+ type: "tool-params-start",
1244
+ id: event.item.id,
1245
+ name: webSearchTool?.name ?? "OpenAiWebSearch",
1246
+ providerExecuted: true
1247
+ });
1248
+ parts.push({
1249
+ type: "tool-params-end",
1250
+ id: event.item.id
1251
+ });
1252
+ parts.push({
1253
+ type: "tool-call",
1254
+ id: event.item.id,
1255
+ name: toolName,
1256
+ params: {},
1257
+ providerExecuted: true
1258
+ });
1259
+ break;
1260
+ }
1261
+ }
1262
+ break;
1263
+ }
1264
+ case "response.output_item.done":
1265
+ {
1266
+ switch (event.item.type) {
1267
+ case "apply_patch_call":
1268
+ {
1269
+ const toolCall = activeToolCalls[event.output_index];
1270
+ if (Predicate.isNotUndefined(toolCall.applyPatch) && !toolCall.applyPatch.endEmitted && event.item.operation.type !== "delete_file") {
1271
+ if (!toolCall.applyPatch.hasDiff) {
1272
+ parts.push({
1273
+ type: "tool-params-delta",
1274
+ id: toolCall.id,
1275
+ delta: InternalUtilities.escapeJSONDelta(event.item.operation.diff)
1276
+ });
1277
+ }
1278
+ parts.push({
1279
+ type: "tool-params-delta",
1280
+ id: toolCall.id,
1281
+ delta: `"}}`
1282
+ });
1283
+ parts.push({
1284
+ type: "tool-params-end",
1285
+ id: toolCall.id
1286
+ });
1287
+ toolCall.applyPatch.endEmitted = true;
1288
+ }
1289
+ // Emit the final tool call with the complete diff when the status is completed
1290
+ if (Predicate.isNotUndefined(toolCall) && event.item.status === "completed") {
1291
+ const toolName = toolNameMapper.getCustomName("apply_patch");
1292
+ parts.push({
1293
+ type: "tool-call",
1294
+ id: toolCall.id,
1295
+ name: toolName,
1296
+ params: {
1297
+ call_id: event.item.call_id,
1298
+ operation: event.item.operation
1299
+ },
1300
+ metadata: {
1301
+ openai: {
1302
+ ...makeItemIdMetadata(event.item.id)
1303
+ }
1304
+ }
1305
+ });
1306
+ }
1307
+ delete activeToolCalls[event.output_index];
1308
+ break;
1309
+ }
1310
+ case "code_interpreter_call":
1311
+ {
1312
+ delete activeToolCalls[event.output_index];
1313
+ const toolName = toolNameMapper.getCustomName("code_interpreter");
1314
+ parts.push({
1315
+ type: "tool-result",
1316
+ id: event.item.id,
1317
+ name: toolName,
1318
+ isFailure: false,
1319
+ result: {
1320
+ outputs: event.item.outputs
1321
+ },
1322
+ providerExecuted: true
1323
+ });
1324
+ break;
1325
+ }
1326
+ case "computer_call":
1327
+ {
1328
+ delete activeToolCalls[event.output_index];
1329
+ const toolName = toolNameMapper.getCustomName("computer_use");
1330
+ parts.push({
1331
+ type: "tool-params-end",
1332
+ id: event.item.id
1333
+ });
1334
+ parts.push({
1335
+ type: "tool-call",
1336
+ id: event.item.id,
1337
+ name: toolName,
1338
+ params: {},
1339
+ providerExecuted: true
1340
+ });
1341
+ parts.push({
1342
+ type: "tool-result",
1343
+ id: event.item.id,
1344
+ name: toolName,
1345
+ isFailure: false,
1346
+ result: {
1347
+ status: event.item.status ?? "completed"
1348
+ }
1349
+ });
1350
+ break;
1351
+ }
1352
+ case "file_search_call":
1353
+ {
1354
+ delete activeToolCalls[event.output_index];
1355
+ const toolName = toolNameMapper.getCustomName("file_search");
1356
+ const results = Predicate.isNotNullish(event.item.results) ? {
1357
+ results: event.item.results
1358
+ } : undefined;
1359
+ parts.push({
1360
+ type: "tool-result",
1361
+ id: event.item.id,
1362
+ name: toolName,
1363
+ isFailure: false,
1364
+ result: {
1365
+ ...results,
1366
+ status: event.item.status,
1367
+ queries: event.item.queries
1368
+ },
1369
+ providerExecuted: true
1370
+ });
1371
+ break;
1372
+ }
1373
+ case "function_call":
1374
+ {
1375
+ delete activeToolCalls[event.output_index];
1376
+ hasToolCalls = true;
1377
+ const toolName = event.item.name;
1378
+ const toolParams = event.item.arguments;
1379
+ const params = yield* Effect.try({
1380
+ try: () => Tool.unsafeSecureJsonParse(toolParams),
1381
+ catch: cause => AiError.make({
1382
+ module: "OpenAiLanguageModel",
1383
+ method: "makeStreamResponse",
1384
+ reason: new AiError.ToolParameterValidationError({
1385
+ toolName,
1386
+ toolParams: {},
1387
+ description: `Failed securely JSON parse tool parameters: ${cause}`
1388
+ })
1389
+ })
1390
+ });
1391
+ parts.push({
1392
+ type: "tool-params-end",
1393
+ id: event.item.call_id
1394
+ });
1395
+ parts.push({
1396
+ type: "tool-call",
1397
+ id: event.item.call_id,
1398
+ name: toolName,
1399
+ params,
1400
+ metadata: {
1401
+ openai: {
1402
+ ...makeItemIdMetadata(event.item.id)
1403
+ }
1404
+ }
1405
+ });
1406
+ break;
1407
+ }
1408
+ case "image_generation_call":
1409
+ {
1410
+ const toolName = toolNameMapper.getCustomName("image_generation");
1411
+ parts.push({
1412
+ type: "tool-result",
1413
+ id: event.item.id,
1414
+ name: toolName,
1415
+ isFailure: false,
1416
+ result: {
1417
+ result: event.item.result
1418
+ },
1419
+ providerExecuted: true
1420
+ });
1421
+ break;
1422
+ }
1423
+ case "local_shell_call":
1424
+ {
1425
+ const toolName = toolNameMapper.getCustomName("local_shell");
1426
+ parts.push({
1427
+ type: "tool-call",
1428
+ id: event.item.call_id,
1429
+ name: toolName,
1430
+ params: {
1431
+ action: event.item.action
1432
+ },
1433
+ metadata: {
1434
+ openai: {
1435
+ ...makeItemIdMetadata(event.item.id)
1436
+ }
1437
+ }
1438
+ });
1439
+ break;
1440
+ }
1441
+ case "mcp_call":
1442
+ {
1443
+ const approvalRequestId = event.item.approval_request_id;
1444
+ // Track approval with our own tool call identifiers
1445
+ const toolId = Predicate.isNotNullish(approvalRequestId) ? streamApprovalRequests.get(approvalRequestId) ?? approvalRequests.get(approvalRequestId) ?? event.item.id : event.item.id;
1446
+ const toolName = `mcp.${event.item.name}`;
1447
+ parts.push({
1448
+ type: "tool-call",
1449
+ id: toolId,
1450
+ name: toolName,
1451
+ params: event.item.arguments,
1452
+ providerExecuted: true
1453
+ });
1454
+ parts.push({
1455
+ type: "tool-result",
1456
+ id: toolId,
1457
+ name: toolName,
1458
+ isFailure: false,
1459
+ providerExecuted: true,
1460
+ result: {
1461
+ type: "call",
1462
+ name: event.item.name,
1463
+ arguments: event.item.arguments,
1464
+ server_label: event.item.server_label,
1465
+ ...(Predicate.isNotNullish(event.item.output) ? {
1466
+ output: event.item.output
1467
+ } : undefined),
1468
+ ...(Predicate.isNotNullish(event.item.error) ? {
1469
+ error: event.item.error
1470
+ } : undefined)
1471
+ },
1472
+ metadata: {
1473
+ openai: {
1474
+ ...makeItemIdMetadata(event.item.id)
1475
+ }
1476
+ }
1477
+ });
1478
+ break;
1479
+ }
1480
+ case "mcp_list_tools":
1481
+ {
1482
+ // Skip
1483
+ break;
1484
+ }
1485
+ case "mcp_approval_request":
1486
+ {
1487
+ const toolId = yield* idGenerator.generateId();
1488
+ const approvalRequestId = event.item.approval_request_id ?? event.item.id;
1489
+ streamApprovalRequests.set(approvalRequestId, toolId);
1490
+ const toolName = `mcp.${event.item.name}`;
1491
+ parts.push({
1492
+ type: "tool-call",
1493
+ id: toolId,
1494
+ name: toolName,
1495
+ params: event.item.arguments,
1496
+ providerExecuted: true
1497
+ });
1498
+ parts.push({
1499
+ type: "tool-approval-request",
1500
+ approvalId: approvalRequestId,
1501
+ toolCallId: toolId
1502
+ });
1503
+ break;
1504
+ }
1505
+ case "message":
1506
+ {
1507
+ const annotations = activeAnnotations.length > 0 ? {
1508
+ annotations: activeAnnotations.slice()
1509
+ } : undefined;
1510
+ parts.push({
1511
+ type: "text-end",
1512
+ id: event.item.id,
1513
+ metadata: {
1514
+ openai: {
1515
+ ...annotations,
1516
+ ...makeItemIdMetadata(event.item.id)
1517
+ }
1518
+ }
1519
+ });
1520
+ break;
1521
+ }
1522
+ case "reasoning":
1523
+ {
1524
+ const reasoningPart = activeReasoning[event.item.id];
1525
+ for (const [summaryIndex, status] of Object.entries(reasoningPart.summaryParts)) {
1526
+ if (status === "active" || status === "can-conclude") {
1527
+ parts.push({
1528
+ type: "reasoning-end",
1529
+ id: `${event.item.id}:${summaryIndex}`,
1530
+ metadata: {
1531
+ openai: {
1532
+ ...makeItemIdMetadata(event.item.id),
1533
+ ...makeEncryptedContentMetadata(event.item.encrypted_content)
1534
+ }
1535
+ }
1536
+ });
1537
+ }
1538
+ }
1539
+ delete activeReasoning[event.item.id];
1540
+ break;
1541
+ }
1542
+ case "shell_call":
1543
+ {
1544
+ delete activeToolCalls[event.output_index];
1545
+ const toolName = toolNameMapper.getCustomName("shell");
1546
+ parts.push({
1547
+ type: "tool-call",
1548
+ id: event.item.id,
1549
+ name: toolName,
1550
+ params: {
1551
+ action: event.item.action
1552
+ },
1553
+ metadata: {
1554
+ openai: {
1555
+ ...makeItemIdMetadata(event.item.id)
1556
+ }
1557
+ }
1558
+ });
1559
+ break;
1560
+ }
1561
+ case "web_search_call":
1562
+ {
1563
+ delete activeToolCalls[event.output_index];
1564
+ const toolName = toolNameMapper.getCustomName(webSearchTool?.name ?? "web_search");
1565
+ parts.push({
1566
+ type: "tool-result",
1567
+ id: event.item.id,
1568
+ name: toolName,
1569
+ isFailure: false,
1570
+ result: {
1571
+ action: event.item.action,
1572
+ status: event.item.status
1573
+ },
1574
+ providerExecuted: true
1575
+ });
1576
+ break;
1577
+ }
1578
+ }
1579
+ break;
1580
+ }
1581
+ case "response.output_text.delta":
1582
+ {
1583
+ parts.push({
1584
+ type: "text-delta",
1585
+ id: event.item_id,
1586
+ delta: event.delta
1587
+ });
1588
+ break;
1589
+ }
1590
+ case "response.output_text.annotation.added":
1591
+ {
1592
+ const annotation = event.annotation;
1593
+ // Track annotation for text-end metadata
1594
+ activeAnnotations.push(annotation);
1595
+ if (annotation.type === "container_file_citation") {
1596
+ parts.push({
1597
+ type: "source",
1598
+ sourceType: "document",
1599
+ id: yield* idGenerator.generateId(),
1600
+ mediaType: "text/plain",
1601
+ title: annotation.filename,
1602
+ fileName: annotation.filename,
1603
+ metadata: {
1604
+ openai: {
1605
+ type: annotation.type,
1606
+ fileId: annotation.file_id,
1607
+ containerId: annotation.container_id
1608
+ }
1609
+ }
1610
+ });
1611
+ } else if (annotation.type === "file_citation") {
1612
+ parts.push({
1613
+ type: "source",
1614
+ sourceType: "document",
1615
+ id: yield* idGenerator.generateId(),
1616
+ mediaType: "text/plain",
1617
+ title: annotation.filename,
1618
+ fileName: annotation.filename,
1619
+ metadata: {
1620
+ openai: {
1621
+ type: annotation.type,
1622
+ fileId: annotation.file_id,
1623
+ index: annotation.index
1624
+ }
1625
+ }
1626
+ });
1627
+ } else if (annotation.type === "file_path") {
1628
+ parts.push({
1629
+ type: "source",
1630
+ sourceType: "document",
1631
+ id: yield* idGenerator.generateId(),
1632
+ mediaType: "application/octet-stream",
1633
+ title: annotation.file_id,
1634
+ fileName: annotation.file_id,
1635
+ metadata: {
1636
+ openai: {
1637
+ type: annotation.type,
1638
+ fileId: annotation.file_id,
1639
+ index: annotation.index
1640
+ }
1641
+ }
1642
+ });
1643
+ } else if (annotation.type === "url_citation") {
1644
+ parts.push({
1645
+ type: "source",
1646
+ sourceType: "url",
1647
+ id: yield* idGenerator.generateId(),
1648
+ url: annotation.url,
1649
+ title: annotation.title,
1650
+ metadata: {
1651
+ openai: {
1652
+ type: annotation.type,
1653
+ startIndex: annotation.start_index,
1654
+ endIndex: annotation.end_index
1655
+ }
1656
+ }
1657
+ });
1658
+ }
1659
+ break;
1660
+ }
1661
+ case "response.function_call_arguments.delta":
1662
+ {
1663
+ const toolCallPart = activeToolCalls[event.output_index];
1664
+ if (Predicate.isNotUndefined(toolCallPart)) {
1665
+ parts.push({
1666
+ type: "tool-params-delta",
1667
+ id: toolCallPart.id,
1668
+ delta: event.delta
1669
+ });
1670
+ }
1671
+ break;
1672
+ }
1673
+ case "response.apply_patch_call_operation_diff.delta":
1674
+ {
1675
+ const toolCall = activeToolCalls[event.output_index];
1676
+ if (Predicate.isNotUndefined(toolCall?.applyPatch)) {
1677
+ parts.push({
1678
+ type: "tool-params-delta",
1679
+ id: toolCall.id,
1680
+ delta: InternalUtilities.escapeJSONDelta(event.delta)
1681
+ });
1682
+ toolCall.applyPatch.hasDiff = true;
1683
+ }
1684
+ break;
1685
+ }
1686
+ case "response.apply_patch_call_operation_diff.done":
1687
+ {
1688
+ const toolCall = activeToolCalls[event.output_index];
1689
+ if (Predicate.isNotUndefined(toolCall?.applyPatch) && !toolCall.applyPatch.endEmitted) {
1690
+ if (!toolCall.applyPatch.hasDiff && Predicate.isNotUndefined(event.delta)) {
1691
+ parts.push({
1692
+ type: "tool-params-delta",
1693
+ id: toolCall.id,
1694
+ delta: InternalUtilities.escapeJSONDelta(event.delta)
1695
+ });
1696
+ toolCall.applyPatch.hasDiff = true;
1697
+ }
1698
+ parts.push({
1699
+ type: "tool-params-delta",
1700
+ id: toolCall.id,
1701
+ delta: `"}}`
1702
+ });
1703
+ parts.push({
1704
+ type: "tool-params-end",
1705
+ id: toolCall.id
1706
+ });
1707
+ toolCall.applyPatch.endEmitted = true;
1708
+ }
1709
+ break;
1710
+ }
1711
+ case "response.code_interpreter_call_code.delta":
1712
+ {
1713
+ const toolCall = activeToolCalls[event.output_index];
1714
+ if (Predicate.isNotUndefined(toolCall)) {
1715
+ parts.push({
1716
+ type: "tool-params-delta",
1717
+ id: toolCall.id,
1718
+ delta: InternalUtilities.escapeJSONDelta(event.delta)
1719
+ });
1720
+ }
1721
+ break;
1722
+ }
1723
+ case "response.code_interpreter_call_code.done":
1724
+ {
1725
+ const toolCall = activeToolCalls[event.output_index];
1726
+ if (Predicate.isNotUndefined(toolCall) && Predicate.isNotUndefined(toolCall.codeInterpreter)) {
1727
+ const toolName = toolNameMapper.getCustomName("code_interpreter");
1728
+ parts.push({
1729
+ type: "tool-params-delta",
1730
+ id: toolCall.id,
1731
+ delta: "\"}"
1732
+ });
1733
+ parts.push({
1734
+ type: "tool-params-end",
1735
+ id: toolCall.id
1736
+ });
1737
+ parts.push({
1738
+ type: "tool-call",
1739
+ id: toolCall.id,
1740
+ name: toolName,
1741
+ params: {
1742
+ code: event.code,
1743
+ container_id: toolCall.codeInterpreter.containerId
1744
+ },
1745
+ providerExecuted: true
1746
+ });
1747
+ }
1748
+ break;
1749
+ }
1750
+ case "response.image_generation_call.partial_image":
1751
+ {
1752
+ const toolName = toolNameMapper.getCustomName("image_generation");
1753
+ parts.push({
1754
+ type: "tool-result",
1755
+ id: event.item_id,
1756
+ name: toolName,
1757
+ isFailure: false,
1758
+ providerExecuted: false,
1759
+ result: {
1760
+ result: event.partial_image_b64
1761
+ },
1762
+ preliminary: true
1763
+ });
1764
+ break;
1765
+ }
1766
+ case "response.reasoning_summary_part.added":
1767
+ {
1768
+ // The first reasoning start is pushed in the `response.output_item.added` block
1769
+ 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
+ }
1783
+ }
1784
+ });
1785
+ reasoningPart.summaryParts[Number(summaryIndex)] = "concluded";
1786
+ }
1787
+ }
1788
+ reasoningPart.summaryParts[event.summary_index] = "active";
1789
+ }
1790
+ parts.push({
1791
+ type: "reasoning-start",
1792
+ id: `${event.item_id}:${event.summary_index}`,
1793
+ metadata: {
1794
+ openai: {
1795
+ ...makeItemIdMetadata(event.item_id),
1796
+ ...makeEncryptedContentMetadata(reasoningPart.encryptedContent)
1797
+ }
1798
+ }
1799
+ });
1800
+ }
1801
+ break;
1802
+ }
1803
+ case "response.reasoning_summary_text.delta":
1804
+ {
1805
+ parts.push({
1806
+ type: "reasoning-delta",
1807
+ id: `${event.item_id}:${event.summary_index}`,
1808
+ delta: event.delta,
1809
+ metadata: {
1810
+ openai: {
1811
+ ...makeItemIdMetadata(event.item_id)
1812
+ }
1813
+ }
1814
+ });
1815
+ break;
1816
+ }
1817
+ case "response.reasoning_summary_part.done":
1818
+ {
1819
+ // When OpenAI stores message data, we can immediately conclude the
1820
+ // reasoning part given that we do not need the encrypted content
1821
+ if (config.store === true) {
1822
+ parts.push({
1823
+ type: "reasoning-end",
1824
+ id: `${event.item_id}:${event.summary_index}`,
1825
+ metadata: {
1826
+ openai: {
1827
+ ...makeItemIdMetadata(event.item_id)
1828
+ }
1829
+ }
1830
+ });
1831
+ // Mark the summary part concluded
1832
+ activeReasoning[event.item_id].summaryParts[event.summary_index] = "concluded";
1833
+ } else {
1834
+ // Mark the summary part as can-conclude given we still need a
1835
+ // final summary part with the encrypted content
1836
+ activeReasoning[event.item_id].summaryParts[event.summary_index] = "can-conclude";
1837
+ }
1838
+ break;
1839
+ }
1840
+ }
1841
+ return parts;
1842
+ })), Stream.flattenIterable);
1843
+ });
1844
+ // =============================================================================
1845
+ // Telemetry
1846
+ // =============================================================================
1847
+ const annotateRequest = (span, request) => {
1848
+ addGenAIAnnotations(span, {
1849
+ system: "openai",
1850
+ operation: {
1851
+ name: "chat"
1852
+ },
1853
+ request: {
1854
+ model: request.model,
1855
+ temperature: request.temperature,
1856
+ topP: request.top_p,
1857
+ maxTokens: request.max_output_tokens
1858
+ },
1859
+ openai: {
1860
+ request: {
1861
+ responseFormat: request.text?.format?.type,
1862
+ serviceTier: request.service_tier
1863
+ }
1864
+ }
1865
+ });
1866
+ };
1867
+ const annotateResponse = (span, response) => {
1868
+ const finishReason = response.incomplete_details?.reason;
1869
+ addGenAIAnnotations(span, {
1870
+ response: {
1871
+ id: response.id,
1872
+ model: response.model,
1873
+ finishReasons: Predicate.isNotUndefined(finishReason) ? [finishReason] : undefined
1874
+ },
1875
+ usage: {
1876
+ inputTokens: response.usage?.input_tokens,
1877
+ outputTokens: response.usage?.output_tokens
1878
+ },
1879
+ openai: {
1880
+ response: {
1881
+ serviceTier: response.service_tier
1882
+ }
1883
+ }
1884
+ });
1885
+ };
1886
+ const annotateStreamResponse = (span, part) => {
1887
+ if (part.type === "response-metadata") {
1888
+ addGenAIAnnotations(span, {
1889
+ response: {
1890
+ id: part.id,
1891
+ model: part.modelId
1892
+ }
1893
+ });
1894
+ }
1895
+ if (part.type === "finish") {
1896
+ const serviceTier = part.metadata?.openai?.serviceTier;
1897
+ addGenAIAnnotations(span, {
1898
+ response: {
1899
+ finishReasons: [part.reason]
1900
+ },
1901
+ usage: {
1902
+ inputTokens: part.usage.inputTokens.total,
1903
+ outputTokens: part.usage.outputTokens.total
1904
+ },
1905
+ openai: {
1906
+ response: {
1907
+ serviceTier
1908
+ }
1909
+ }
1910
+ });
1911
+ }
1912
+ };
1913
+ const prepareTools = /*#__PURE__*/Effect.fnUntraced(function* ({
1914
+ config,
1915
+ options,
1916
+ toolNameMapper
1917
+ }) {
1918
+ // Return immediately if no tools are in the toolkit
1919
+ if (options.tools.length === 0) {
1920
+ return {
1921
+ tools: undefined,
1922
+ toolChoice: undefined
1923
+ };
1924
+ }
1925
+ const tools = [];
1926
+ let toolChoice = undefined;
1927
+ // Filter the incoming tools down to the set of allowed tools as indicated by
1928
+ // the tool choice. This must be done here given that there is no tool name
1929
+ // in OpenAI's provider-defined tools, so there would be no way to perform
1930
+ // this filter otherwise
1931
+ let allowedTools = options.tools;
1932
+ if (typeof options.toolChoice === "object" && "oneOf" in options.toolChoice) {
1933
+ const allowedToolNames = new Set(options.toolChoice.oneOf);
1934
+ allowedTools = options.tools.filter(tool => allowedToolNames.has(tool.name));
1935
+ toolChoice = options.toolChoice.mode === "required" ? "required" : "auto";
1936
+ }
1937
+ // Convert the tools in the toolkit to the provider-defined format
1938
+ for (const tool of allowedTools) {
1939
+ if (Tool.isUserDefined(tool)) {
1940
+ const strict = Tool.getStrictMode(tool) ?? config.strictJsonSchema ?? true;
1941
+ tools.push({
1942
+ type: "function",
1943
+ name: tool.name,
1944
+ description: Tool.getDescription(tool) ?? null,
1945
+ parameters: Tool.getJsonSchema(tool),
1946
+ strict
1947
+ });
1948
+ }
1949
+ if (Tool.isProviderDefined(tool)) {
1950
+ const openAiTool = tool;
1951
+ switch (openAiTool.name) {
1952
+ case "OpenAiApplyPatch":
1953
+ {
1954
+ tools.push({
1955
+ type: "apply_patch"
1956
+ });
1957
+ break;
1958
+ }
1959
+ case "OpenAiCodeInterpreter":
1960
+ {
1961
+ const args = yield* Schema.decodeUnknownEffect(openAiTool.argsSchema)(tool.args).pipe(Effect.mapError(error => AiError.make({
1962
+ module: "OpenAiLanguageModel",
1963
+ method: "prepareTools",
1964
+ reason: new AiError.ToolConfigurationError({
1965
+ toolName: openAiTool.name,
1966
+ description: error.message
1967
+ })
1968
+ })));
1969
+ tools.push({
1970
+ ...args,
1971
+ type: "code_interpreter"
1972
+ });
1973
+ break;
1974
+ }
1975
+ case "OpenAiFileSearch":
1976
+ {
1977
+ const args = yield* Schema.decodeUnknownEffect(openAiTool.argsSchema)(tool.args).pipe(Effect.mapError(error => AiError.make({
1978
+ module: "OpenAiLanguageModel",
1979
+ method: "prepareTools",
1980
+ reason: new AiError.ToolConfigurationError({
1981
+ toolName: openAiTool.name,
1982
+ description: error.message
1983
+ })
1984
+ })));
1985
+ tools.push({
1986
+ ...args,
1987
+ type: "file_search"
1988
+ });
1989
+ break;
1990
+ }
1991
+ case "OpenAiShell":
1992
+ {
1993
+ tools.push({
1994
+ type: "shell"
1995
+ });
1996
+ break;
1997
+ }
1998
+ case "OpenAiImageGeneration":
1999
+ {
2000
+ const args = yield* Schema.decodeUnknownEffect(openAiTool.argsSchema)(tool.args).pipe(Effect.mapError(error => AiError.make({
2001
+ module: "OpenAiLanguageModel",
2002
+ method: "prepareTools",
2003
+ reason: new AiError.ToolConfigurationError({
2004
+ toolName: openAiTool.name,
2005
+ description: error.message
2006
+ })
2007
+ })));
2008
+ tools.push({
2009
+ ...args,
2010
+ type: "image_generation"
2011
+ });
2012
+ break;
2013
+ }
2014
+ case "OpenAiLocalShell":
2015
+ {
2016
+ tools.push({
2017
+ type: "local_shell"
2018
+ });
2019
+ break;
2020
+ }
2021
+ case "OpenAiMcp":
2022
+ {
2023
+ const args = yield* Schema.decodeUnknownEffect(openAiTool.argsSchema)(tool.args).pipe(Effect.mapError(error => AiError.make({
2024
+ module: "OpenAiLanguageModel",
2025
+ method: "prepareTools",
2026
+ reason: new AiError.ToolConfigurationError({
2027
+ toolName: openAiTool.name,
2028
+ description: error.message
2029
+ })
2030
+ })));
2031
+ tools.push({
2032
+ ...args,
2033
+ type: "mcp"
2034
+ });
2035
+ break;
2036
+ }
2037
+ case "OpenAiWebSearch":
2038
+ {
2039
+ const args = yield* Schema.decodeUnknownEffect(openAiTool.argsSchema)(tool.args).pipe(Effect.mapError(error => AiError.make({
2040
+ module: "OpenAiLanguageModel",
2041
+ method: "prepareTools",
2042
+ reason: new AiError.ToolConfigurationError({
2043
+ toolName: openAiTool.name,
2044
+ description: error.message
2045
+ })
2046
+ })));
2047
+ tools.push({
2048
+ ...args,
2049
+ type: "web_search"
2050
+ });
2051
+ break;
2052
+ }
2053
+ case "OpenAiWebSearchPreview":
2054
+ {
2055
+ const args = yield* Schema.decodeUnknownEffect(openAiTool.argsSchema)(tool.args).pipe(Effect.mapError(error => AiError.make({
2056
+ module: "OpenAiLanguageModel",
2057
+ method: "prepareTools",
2058
+ reason: new AiError.ToolConfigurationError({
2059
+ toolName: openAiTool.name,
2060
+ description: error.message
2061
+ })
2062
+ })));
2063
+ tools.push({
2064
+ ...args,
2065
+ type: "web_search_preview"
2066
+ });
2067
+ break;
2068
+ }
2069
+ default:
2070
+ {
2071
+ return yield* AiError.make({
2072
+ module: "OpenAiLanguageModel",
2073
+ method: "prepareTools",
2074
+ reason: new AiError.InvalidRequestError({
2075
+ description: `Unknown provider-defined tool '${tool.name}'`
2076
+ })
2077
+ });
2078
+ }
2079
+ }
2080
+ }
2081
+ }
2082
+ if (options.toolChoice === "auto" || options.toolChoice === "none" || options.toolChoice === "required") {
2083
+ toolChoice = options.toolChoice;
2084
+ }
2085
+ if (typeof options.toolChoice === "object" && "tool" in options.toolChoice) {
2086
+ const toolName = toolNameMapper.getProviderName(options.toolChoice.tool);
2087
+ const providerNames = toolNameMapper.providerNames;
2088
+ if (providerNames.includes(toolName)) {
2089
+ toolChoice = {
2090
+ type: toolName
2091
+ };
2092
+ } else {
2093
+ toolChoice = {
2094
+ type: "function",
2095
+ name: options.toolChoice.tool
2096
+ };
2097
+ }
2098
+ }
2099
+ return {
2100
+ tools,
2101
+ toolChoice
2102
+ };
2103
+ });
2104
+ // =============================================================================
2105
+ // Utilities
2106
+ // =============================================================================
2107
+ const isFileId = (data, config) => config.fileIdPrefixes != null && config.fileIdPrefixes.some(prefix => data.startsWith(prefix));
2108
+ const getItemId = part => part.options.openai?.itemId ?? null;
2109
+ const getStatus = part => part.options.openai?.status ?? null;
2110
+ const getEncryptedContent = part => part.options.openai?.encryptedContent ?? null;
2111
+ const getImageDetail = part => part.options.openai?.imageDetail ?? "auto";
2112
+ const makeItemIdMetadata = itemId => Predicate.isNotUndefined(itemId) ? {
2113
+ itemId
2114
+ } : undefined;
2115
+ const makeEncryptedContentMetadata = encryptedContent => Predicate.isNotNullish(encryptedContent) ? {
2116
+ encryptedContent
2117
+ } : undefined;
2118
+ const prepareResponseFormat = ({
2119
+ config,
2120
+ options
2121
+ }) => {
2122
+ if (options.responseFormat.type === "json") {
2123
+ const name = options.responseFormat.objectName;
2124
+ const schema = options.responseFormat.schema;
2125
+ return {
2126
+ type: "json_schema",
2127
+ name,
2128
+ description: AST.resolveDescription(schema.ast) ?? "Response with a JSON object",
2129
+ schema: Tool.getJsonSchemaFromSchema(schema),
2130
+ strict: config.strictJsonSchema ?? true
2131
+ };
2132
+ }
2133
+ return {
2134
+ type: "text"
2135
+ };
2136
+ };
2137
+ const getModelCapabilities = modelId => {
2138
+ const supportsFlexProcessing = modelId.startsWith("o3") || modelId.startsWith("o4-mini") || modelId.startsWith("gpt-5") && !modelId.startsWith("gpt-5-chat");
2139
+ 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");
2140
+ // Use allowlist approach: only known reasoning models should use 'developer' role
2141
+ // This prevents issues with fine-tuned models, third-party models, and custom models
2142
+ const isReasoningModel = modelId.startsWith("o1") || modelId.startsWith("o3") || modelId.startsWith("o4-mini") || modelId.startsWith("codex-mini") || modelId.startsWith("computer-use-preview") || modelId.startsWith("gpt-5") && !modelId.startsWith("gpt-5-chat");
2143
+ // https://platform.openai.com/docs/guides/latest-model#gpt-5-1-parameter-compatibility
2144
+ // GPT-5.1 and GPT-5.2 support temperature, topP, logProbs when reasoningEffort is none
2145
+ const supportsNonReasoningParameters = modelId.startsWith("gpt-5.1") || modelId.startsWith("gpt-5.2");
2146
+ const systemMessageMode = isReasoningModel ? "developer" : "system";
2147
+ return {
2148
+ supportsFlexProcessing,
2149
+ supportsPriorityProcessing,
2150
+ isReasoningModel,
2151
+ systemMessageMode,
2152
+ supportsNonReasoningParameters
2153
+ };
2154
+ };
2155
+ const getApprovalRequestIdMapping = prompt => {
2156
+ const mapping = new Map();
2157
+ for (const message of prompt.content) {
2158
+ if (message.role !== "assistant") {
2159
+ continue;
2160
+ }
2161
+ for (const part of message.content) {
2162
+ if (part.type !== "tool-call") {
2163
+ continue;
2164
+ }
2165
+ const approvalRequestId = part.options.openai?.approvalRequestId;
2166
+ if (Predicate.isNotNullish(approvalRequestId)) {
2167
+ mapping.set(approvalRequestId, part.id);
2168
+ }
2169
+ }
2170
+ }
2171
+ return mapping;
2172
+ };
2173
+ const getUsage = usage => {
2174
+ if (Predicate.isNullish(usage)) {
2175
+ return {
2176
+ inputTokens: {
2177
+ uncached: undefined,
2178
+ total: undefined,
2179
+ cacheRead: undefined,
2180
+ cacheWrite: undefined
2181
+ },
2182
+ outputTokens: {
2183
+ total: undefined,
2184
+ text: undefined,
2185
+ reasoning: undefined
2186
+ }
2187
+ };
2188
+ }
2189
+ const inputTokens = usage.input_tokens;
2190
+ const outputTokens = usage.output_tokens;
2191
+ const cachedTokens = usage.input_tokens_details.cached_tokens;
2192
+ const reasoningTokens = usage.output_tokens_details.reasoning_tokens;
2193
+ return {
2194
+ inputTokens: {
2195
+ uncached: inputTokens - cachedTokens,
2196
+ total: inputTokens,
2197
+ cacheRead: cachedTokens,
2198
+ cacheWrite: undefined
2199
+ },
2200
+ outputTokens: {
2201
+ total: outputTokens,
2202
+ text: outputTokens - reasoningTokens,
2203
+ reasoning: reasoningTokens
2204
+ }
2205
+ };
2206
+ };
2207
+ //# sourceMappingURL=OpenAiLanguageModel.js.map