@effect/ai-openai 0.29.1 → 0.30.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 (62) hide show
  1. package/OpenAiTool/package.json +6 -0
  2. package/dist/cjs/Generated.js +5845 -4262
  3. package/dist/cjs/Generated.js.map +1 -1
  4. package/dist/cjs/OpenAiClient.js +1493 -129
  5. package/dist/cjs/OpenAiClient.js.map +1 -1
  6. package/dist/cjs/OpenAiEmbeddingModel.js +61 -50
  7. package/dist/cjs/OpenAiEmbeddingModel.js.map +1 -1
  8. package/dist/cjs/OpenAiLanguageModel.js +973 -333
  9. package/dist/cjs/OpenAiLanguageModel.js.map +1 -1
  10. package/dist/cjs/OpenAiTelemetry.js +4 -4
  11. package/dist/cjs/OpenAiTelemetry.js.map +1 -1
  12. package/dist/cjs/OpenAiTokenizer.js +46 -14
  13. package/dist/cjs/OpenAiTokenizer.js.map +1 -1
  14. package/dist/cjs/OpenAiTool.js +93 -0
  15. package/dist/cjs/OpenAiTool.js.map +1 -0
  16. package/dist/cjs/index.js +3 -1
  17. package/dist/cjs/internal/utilities.js +11 -3
  18. package/dist/cjs/internal/utilities.js.map +1 -1
  19. package/dist/dts/Generated.d.ts +19662 -11761
  20. package/dist/dts/Generated.d.ts.map +1 -1
  21. package/dist/dts/OpenAiClient.d.ts +3022 -14
  22. package/dist/dts/OpenAiClient.d.ts.map +1 -1
  23. package/dist/dts/OpenAiEmbeddingModel.d.ts +8 -8
  24. package/dist/dts/OpenAiEmbeddingModel.d.ts.map +1 -1
  25. package/dist/dts/OpenAiLanguageModel.d.ts +146 -49
  26. package/dist/dts/OpenAiLanguageModel.d.ts.map +1 -1
  27. package/dist/dts/OpenAiTelemetry.d.ts +4 -4
  28. package/dist/dts/OpenAiTelemetry.d.ts.map +1 -1
  29. package/dist/dts/OpenAiTokenizer.d.ts +1 -1
  30. package/dist/dts/OpenAiTokenizer.d.ts.map +1 -1
  31. package/dist/dts/OpenAiTool.d.ts +176 -0
  32. package/dist/dts/OpenAiTool.d.ts.map +1 -0
  33. package/dist/dts/index.d.ts +4 -0
  34. package/dist/dts/index.d.ts.map +1 -1
  35. package/dist/esm/Generated.js +5846 -12846
  36. package/dist/esm/Generated.js.map +1 -1
  37. package/dist/esm/OpenAiClient.js +1440 -128
  38. package/dist/esm/OpenAiClient.js.map +1 -1
  39. package/dist/esm/OpenAiEmbeddingModel.js +60 -49
  40. package/dist/esm/OpenAiEmbeddingModel.js.map +1 -1
  41. package/dist/esm/OpenAiLanguageModel.js +971 -330
  42. package/dist/esm/OpenAiLanguageModel.js.map +1 -1
  43. package/dist/esm/OpenAiTelemetry.js +4 -4
  44. package/dist/esm/OpenAiTelemetry.js.map +1 -1
  45. package/dist/esm/OpenAiTokenizer.js +46 -14
  46. package/dist/esm/OpenAiTokenizer.js.map +1 -1
  47. package/dist/esm/OpenAiTool.js +84 -0
  48. package/dist/esm/OpenAiTool.js.map +1 -0
  49. package/dist/esm/index.js +4 -0
  50. package/dist/esm/index.js.map +1 -1
  51. package/dist/esm/internal/utilities.js +10 -2
  52. package/dist/esm/internal/utilities.js.map +1 -1
  53. package/package.json +12 -4
  54. package/src/Generated.ts +9691 -5598
  55. package/src/OpenAiClient.ts +1761 -224
  56. package/src/OpenAiEmbeddingModel.ts +70 -62
  57. package/src/OpenAiLanguageModel.ts +1193 -377
  58. package/src/OpenAiTelemetry.ts +9 -9
  59. package/src/OpenAiTokenizer.ts +38 -39
  60. package/src/OpenAiTool.ts +110 -0
  61. package/src/index.ts +5 -0
  62. package/src/internal/utilities.ts +16 -4
@@ -3,33 +3,30 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.withConfigOverride = exports.modelWithTokenizer = exports.model = exports.make = exports.layerWithTokenizer = exports.layer = exports.ProviderMetadata = exports.Config = void 0;
7
- var _AiError = require("@effect/ai/AiError");
8
- var AiLanguageModel = _interopRequireWildcard(require("@effect/ai/AiLanguageModel"));
9
- var AiModel = _interopRequireWildcard(require("@effect/ai/AiModel"));
10
- var AiResponse = _interopRequireWildcard(require("@effect/ai/AiResponse"));
11
- var Arr = _interopRequireWildcard(require("effect/Array"));
6
+ exports.withConfigOverride = exports.modelWithTokenizer = exports.model = exports.make = exports.layerWithTokenizer = exports.layer = exports.Config = void 0;
7
+ var AiError = _interopRequireWildcard(require("@effect/ai/AiError"));
8
+ var IdGenerator = _interopRequireWildcard(require("@effect/ai/IdGenerator"));
9
+ var LanguageModel = _interopRequireWildcard(require("@effect/ai/LanguageModel"));
10
+ var AiModel = _interopRequireWildcard(require("@effect/ai/Model"));
11
+ var Tool = _interopRequireWildcard(require("@effect/ai/Tool"));
12
12
  var Context = _interopRequireWildcard(require("effect/Context"));
13
+ var DateTime = _interopRequireWildcard(require("effect/DateTime"));
13
14
  var Effect = _interopRequireWildcard(require("effect/Effect"));
14
15
  var Encoding = _interopRequireWildcard(require("effect/Encoding"));
15
16
  var _Function = require("effect/Function");
16
17
  var Layer = _interopRequireWildcard(require("effect/Layer"));
17
- var Option = _interopRequireWildcard(require("effect/Option"));
18
18
  var Predicate = _interopRequireWildcard(require("effect/Predicate"));
19
19
  var Stream = _interopRequireWildcard(require("effect/Stream"));
20
- var _utilities = _interopRequireWildcard(require("./internal/utilities.js"));
21
- var InternalUtilities = _utilities;
20
+ var InternalUtilities = _interopRequireWildcard(require("./internal/utilities.js"));
22
21
  var _OpenAiClient = require("./OpenAiClient.js");
23
22
  var _OpenAiTelemetry = require("./OpenAiTelemetry.js");
24
23
  var OpenAiTokenizer = _interopRequireWildcard(require("./OpenAiTokenizer.js"));
24
+ var OpenAiTool = _interopRequireWildcard(require("./OpenAiTool.js"));
25
25
  function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
26
26
  /**
27
27
  * @since 1.0.0
28
28
  */
29
29
 
30
- const constDisableValidation = {
31
- disableValidation: true
32
- };
33
30
  // =============================================================================
34
31
  // Configuration
35
32
  // =============================================================================
@@ -44,32 +41,23 @@ class Config extends /*#__PURE__*/Context.Tag("@effect/ai-openai/OpenAiLanguageM
44
41
  static getOrUndefined = /*#__PURE__*/Effect.map(/*#__PURE__*/Effect.context(), context => context.unsafeMap.get(Config.key));
45
42
  }
46
43
  // =============================================================================
47
- // Anthropic Provider Metadata
44
+ // OpenAI Language Model
48
45
  // =============================================================================
49
46
  /**
50
47
  * @since 1.0.0
51
- * @category Context
48
+ * @category Ai Models
52
49
  */
53
50
  exports.Config = Config;
54
- class ProviderMetadata extends /*#__PURE__*/Context.Tag(InternalUtilities.ProviderMetadataKey)() {}
55
- // =============================================================================
56
- // OpenAi Language Model
57
- // =============================================================================
58
- /**
59
- * @since 1.0.0
60
- * @category AiModel
61
- */
62
- exports.ProviderMetadata = ProviderMetadata;
63
- const model = (model, config) => AiModel.make(layer({
51
+ const model = (model, config) => AiModel.make("openai", layer({
64
52
  model,
65
53
  config
66
54
  }));
67
55
  /**
68
56
  * @since 1.0.0
69
- * @category AiModel
57
+ * @category Ai Models
70
58
  */
71
59
  exports.model = model;
72
- const modelWithTokenizer = (model, config) => AiModel.make(layerWithTokenizer({
60
+ const modelWithTokenizer = (model, config) => AiModel.make("openai", layerWithTokenizer({
73
61
  model,
74
62
  config
75
63
  }));
@@ -80,87 +68,57 @@ const modelWithTokenizer = (model, config) => AiModel.make(layerWithTokenizer({
80
68
  exports.modelWithTokenizer = modelWithTokenizer;
81
69
  const make = exports.make = /*#__PURE__*/Effect.fnUntraced(function* (options) {
82
70
  const client = yield* _OpenAiClient.OpenAiClient;
83
- const makeRequest = Effect.fnUntraced(function* (method, {
84
- prompt,
85
- system,
86
- toolChoice,
87
- tools
88
- }) {
71
+ const makeRequest = Effect.fnUntraced(function* (providerOptions) {
89
72
  const context = yield* Effect.context();
90
- const useStructured = tools.length === 1 && tools[0].structured;
91
- let tool_choice = undefined;
92
- if (Predicate.isNotUndefined(toolChoice) && !useStructured && tools.length > 0) {
93
- if (toolChoice === "auto" || toolChoice === "required") {
94
- tool_choice = toolChoice;
95
- } else if (typeof toolChoice === "object") {
96
- tool_choice = {
97
- type: "function",
98
- function: {
99
- name: toolChoice.tool
100
- }
101
- };
102
- }
103
- }
104
- const messages = yield* makeMessages(method, system, prompt);
105
- return {
73
+ const config = {
106
74
  model: options.model,
107
75
  ...options.config,
108
- ...context.unsafeMap.get(Config.key),
109
- messages,
110
- response_format: useStructured ? {
111
- type: "json_schema",
112
- json_schema: {
113
- strict: true,
114
- name: tools[0].name,
115
- description: tools[0].description,
116
- schema: tools[0].parameters
117
- }
118
- } : undefined,
119
- tools: !useStructured && tools.length > 0 ? tools.map(tool => ({
120
- type: "function",
121
- function: {
122
- name: tool.name,
123
- description: tool.description,
124
- parameters: tool.parameters,
125
- strict: true
126
- }
127
- })) : undefined,
128
- tool_choice
76
+ ...context.unsafeMap.get(Config.key)
77
+ };
78
+ const messages = yield* prepareMessages(providerOptions, config);
79
+ const {
80
+ toolChoice,
81
+ tools
82
+ } = yield* prepareTools(providerOptions);
83
+ const include = prepareInclude(providerOptions, config);
84
+ const responseFormat = prepareResponseFormat(providerOptions);
85
+ const verbosity = config.text?.verbosity;
86
+ const request = {
87
+ ...config,
88
+ input: messages,
89
+ include,
90
+ text: {
91
+ format: responseFormat,
92
+ verbosity
93
+ },
94
+ tools,
95
+ tool_choice: toolChoice
129
96
  };
97
+ return request;
130
98
  });
131
- return yield* AiLanguageModel.make({
99
+ return yield* LanguageModel.make({
132
100
  generateText: Effect.fnUntraced(function* (options) {
133
- const structuredTool = options.tools.length === 1 && options.tools[0].structured ? options.tools[0] : undefined;
134
- const request = yield* makeRequest("generateText", options);
101
+ const request = yield* makeRequest(options);
135
102
  annotateRequest(options.span, request);
136
- const rawResponse = yield* client.client.createChatCompletion(request);
137
- annotateChatResponse(options.span, rawResponse);
138
- const response = yield* makeResponse(rawResponse, "generateText", structuredTool);
103
+ const rawResponse = yield* client.createResponse(request);
104
+ annotateResponse(options.span, rawResponse);
105
+ return yield* makeResponse(rawResponse, options);
106
+ }),
107
+ streamText: Effect.fnUntraced(function* (options) {
108
+ const request = yield* makeRequest(options);
109
+ annotateRequest(options.span, request);
110
+ return client.createResponseStream(request);
111
+ }, (effect, options) => effect.pipe(Effect.flatMap(stream => makeStreamResponse(stream, options)), Stream.unwrap, Stream.map(response => {
112
+ annotateStreamResponse(options.span, response);
139
113
  return response;
140
- }, Effect.catchAll(cause => _AiError.AiError.is(cause) ? cause : new _AiError.AiError({
141
- module: "OpenAiLanguageModel",
142
- method: "generateText",
143
- description: "An error occurred",
144
- cause
145
- }))),
146
- streamText(options) {
147
- return makeRequest("streamText", options).pipe(Effect.tap(request => annotateRequest(options.span, request)), Effect.map(client.stream), Stream.unwrap, Stream.map(response => {
148
- annotateStreamResponse(options.span, response);
149
- return response;
150
- }), Stream.catchAll(cause => _AiError.AiError.is(cause) ? cause : new _AiError.AiError({
151
- module: "OpenAiLanguageModel",
152
- method: "streamText",
153
- description: "An error occurred",
154
- cause
155
- })));
156
- }
114
+ })))
157
115
  });
158
116
  });
159
117
  /**
160
118
  * @since 1.0.0
161
119
  * @category Layers
162
120
  */
163
- const layer = options => Layer.effect(AiLanguageModel.AiLanguageModel, make({
121
+ const layer = options => Layer.effect(LanguageModel.LanguageModel, make({
164
122
  model: options.model,
165
123
  config: options.config
166
124
  }));
@@ -179,262 +137,817 @@ const withConfigOverride = exports.withConfigOverride = /*#__PURE__*/(0, _Functi
179
137
  ...config,
180
138
  ...overrides
181
139
  })));
182
- const makeMessages = /*#__PURE__*/Effect.fnUntraced(function* (method, system, prompt) {
183
- const messages = Option.match(system, {
184
- onNone: () => [],
185
- onSome: content => [{
186
- role: "system",
187
- content
188
- }]
189
- });
190
- for (const message of prompt.messages) {
191
- switch (message._tag) {
192
- case "AssistantMessage":
140
+ // =============================================================================
141
+ // Prompt Conversion
142
+ // =============================================================================
143
+ const getSystemMessageMode = model => model.startsWith("o") || model.startsWith("gpt-5") || model.startsWith("codex-") || model.startsWith("computer-use") ? "developer" : "system";
144
+ const prepareMessages = /*#__PURE__*/Effect.fnUntraced(function* (options, config) {
145
+ const messages = [];
146
+ for (const message of options.prompt.content) {
147
+ switch (message.role) {
148
+ case "system":
149
+ {
150
+ messages.push({
151
+ role: getSystemMessageMode(config.model),
152
+ content: message.content
153
+ });
154
+ break;
155
+ }
156
+ case "user":
193
157
  {
194
- let text = "";
195
- const toolCalls = [];
196
- for (const part of message.parts) {
197
- switch (part._tag) {
198
- case "TextPart":
158
+ const content = [];
159
+ for (let index = 0; index < message.content.length; index++) {
160
+ const part = message.content[index];
161
+ switch (part.type) {
162
+ case "text":
199
163
  {
200
- text += part.text;
164
+ content.push({
165
+ type: "input_text",
166
+ text: part.text
167
+ });
201
168
  break;
202
169
  }
203
- case "ToolCallPart":
170
+ case "file":
204
171
  {
205
- toolCalls.push({
206
- id: part.id,
207
- type: "function",
208
- function: {
209
- name: part.name,
210
- arguments: JSON.stringify(part.params)
172
+ if (part.mediaType.startsWith("image/")) {
173
+ const detail = getImageDetail(part);
174
+ const mediaType = part.mediaType === "image/*" ? "image/jpeg" : part.mediaType;
175
+ if (typeof part.data === "string" && isFileId(part.data, config)) {
176
+ content.push({
177
+ type: "input_image",
178
+ file_id: part.data,
179
+ detail
180
+ });
181
+ }
182
+ if (part.data instanceof URL) {
183
+ content.push({
184
+ type: "input_image",
185
+ image_url: part.data.toString(),
186
+ detail
187
+ });
188
+ }
189
+ if (part.data instanceof Uint8Array) {
190
+ const base64 = Encoding.encodeBase64(part.data);
191
+ const imageUrl = `data:${mediaType};base64,${base64}`;
192
+ content.push({
193
+ type: "input_image",
194
+ image_url: imageUrl,
195
+ detail
196
+ });
211
197
  }
198
+ }
199
+ if (part.mediaType === "application/pdf") {
200
+ if (typeof part.data === "string" && isFileId(part.data, config)) {
201
+ content.push({
202
+ type: "input_file",
203
+ file_id: part.data
204
+ });
205
+ }
206
+ if (part.data instanceof URL) {
207
+ content.push({
208
+ type: "input_file",
209
+ file_url: part.data.toString()
210
+ });
211
+ }
212
+ if (part.data instanceof Uint8Array) {
213
+ const base64 = Encoding.encodeBase64(part.data);
214
+ const fileName = part.fileName ?? `part-${index}.pdf`;
215
+ const fileData = `data:application/pdf;base64,${base64}`;
216
+ content.push({
217
+ type: "input_file",
218
+ filename: fileName,
219
+ file_data: fileData
220
+ });
221
+ }
222
+ }
223
+ return yield* new AiError.MalformedInput({
224
+ module: "OpenAiLanguageModel",
225
+ method: "prepareMessages",
226
+ description: `Detected unsupported media type for file: '${part.mediaType}'`
212
227
  });
213
- break;
214
228
  }
215
229
  }
216
230
  }
217
231
  messages.push({
218
- role: "assistant",
219
- content: text,
220
- tool_calls: toolCalls.length > 0 ? toolCalls : undefined
232
+ role: "user",
233
+ content
221
234
  });
222
235
  break;
223
236
  }
224
- case "ToolMessage":
237
+ case "assistant":
225
238
  {
226
- for (const part of message.parts) {
227
- messages.push({
228
- role: "tool",
229
- tool_call_id: part.id,
230
- content: JSON.stringify(part.result)
231
- });
239
+ const reasoningMessages = {};
240
+ for (const part of message.content) {
241
+ switch (part.type) {
242
+ case "text":
243
+ {
244
+ messages.push({
245
+ role: "assistant",
246
+ content: [{
247
+ type: "output_text",
248
+ text: part.text
249
+ }],
250
+ id: getItemId(part)
251
+ });
252
+ break;
253
+ }
254
+ case "reasoning":
255
+ {
256
+ const options = part.options.openai;
257
+ if (Predicate.isNotUndefined(options?.itemId)) {
258
+ const reasoningMessage = reasoningMessages[options.itemId];
259
+ const summaryParts = [];
260
+ if (part.text.length > 0) {
261
+ summaryParts.push({
262
+ type: "summary_text",
263
+ text: part.text
264
+ });
265
+ }
266
+ if (Predicate.isUndefined(reasoningMessage)) {
267
+ reasoningMessages[options.itemId] = {
268
+ id: options.itemId,
269
+ type: "reasoning",
270
+ summary: summaryParts,
271
+ encrypted_content: options.encryptedContent
272
+ };
273
+ messages.push(reasoningMessages[options.itemId]);
274
+ } else {
275
+ for (const summaryPart of summaryParts) {
276
+ reasoningMessage.summary.push(summaryPart);
277
+ }
278
+ }
279
+ }
280
+ break;
281
+ }
282
+ case "tool-call":
283
+ {
284
+ if (!part.providerExecuted) {
285
+ messages.push({
286
+ id: getItemId(part),
287
+ type: "function_call",
288
+ call_id: part.id,
289
+ name: part.name,
290
+ arguments: JSON.stringify(part.params)
291
+ });
292
+ }
293
+ break;
294
+ }
295
+ }
232
296
  }
233
297
  break;
234
298
  }
235
- case "UserMessage":
299
+ case "tool":
236
300
  {
237
- // Handle the case where the message content is just a single piece of text
238
- if (message.parts.length === 1 && message.parts[0]._tag === "TextPart") {
301
+ for (const part of message.content) {
239
302
  messages.push({
240
- role: "user",
241
- content: message.parts[0].text
303
+ type: "function_call_output",
304
+ call_id: part.id,
305
+ result: JSON.stringify(part.result)
242
306
  });
243
- break;
244
307
  }
245
- const content = [];
246
- for (let index = 0; index < message.parts.length; index++) {
247
- const part = message.parts[index];
248
- switch (part._tag) {
249
- // TODO: review file inputs
250
- case "FilePart":
308
+ break;
309
+ }
310
+ }
311
+ }
312
+ return messages;
313
+ });
314
+ // =============================================================================
315
+ // Response Conversion
316
+ // =============================================================================
317
+ const makeResponse = /*#__PURE__*/Effect.fnUntraced(function* (response, options) {
318
+ const idGenerator = yield* IdGenerator.IdGenerator;
319
+ const webSearchTool = options.tools.find(tool => Tool.isProviderDefined(tool) && (tool.id === "openai.web_search" || tool.id === "openai.web_search_preview"));
320
+ let hasToolCalls = false;
321
+ const parts = [];
322
+ const createdAt = new Date(response.created_at * 1000);
323
+ parts.push({
324
+ type: "response-metadata",
325
+ id: response.id,
326
+ modelId: response.model,
327
+ timestamp: DateTime.formatIso(DateTime.unsafeFromDate(createdAt))
328
+ });
329
+ for (const part of response.output) {
330
+ switch (part.type) {
331
+ case "message":
332
+ {
333
+ for (const contentPart of part.content) {
334
+ switch (contentPart.type) {
335
+ case "output_text":
251
336
  {
252
- const data = Encoding.encodeBase64(part.data);
253
- switch (part.mediaType) {
254
- case "audio/wav":
255
- {
256
- content.push({
257
- type: "input_audio",
258
- input_audio: {
259
- data,
260
- format: "wav"
261
- }
262
- });
263
- break;
264
- }
265
- case "audio/mp3":
266
- case "audio/mpeg":
267
- {
268
- content.push({
269
- type: "input_audio",
270
- input_audio: {
271
- data,
272
- format: "mp3"
273
- }
274
- });
275
- break;
276
- }
277
- case "application/pdf":
278
- {
279
- content.push({
280
- type: "file",
281
- file: {
282
- filename: part.name ?? `part-${index}.pdf`,
283
- file_data: `data:application/pdf;base64,${data}`
284
- }
285
- });
286
- break;
287
- }
288
- default:
289
- {
290
- return yield* new _AiError.AiError({
291
- module: "OpenAiLanguageModel",
292
- method: "",
293
- description: `OpenAi does not support file inputs of type "${part.mediaType}"`
294
- });
337
+ parts.push({
338
+ type: "text",
339
+ text: contentPart.text,
340
+ metadata: {
341
+ openai: {
342
+ itemId: part.id
295
343
  }
344
+ }
345
+ });
346
+ for (const annotation of contentPart.annotations) {
347
+ if (annotation.type === "file_citation") {
348
+ const metadata = {
349
+ type: annotation.type,
350
+ index: annotation.index
351
+ };
352
+ parts.push({
353
+ type: "source",
354
+ sourceType: "document",
355
+ id: yield* idGenerator.generateId(),
356
+ mediaType: "text/plain",
357
+ title: annotation.filename ?? "Untitled Document",
358
+ metadata: {
359
+ openai: metadata
360
+ }
361
+ });
362
+ }
363
+ if (annotation.type === "url_citation") {
364
+ const metadata = {
365
+ type: annotation.type,
366
+ startIndex: annotation.start_index,
367
+ endIndex: annotation.end_index
368
+ };
369
+ parts.push({
370
+ type: "source",
371
+ sourceType: "url",
372
+ id: yield* idGenerator.generateId(),
373
+ url: annotation.url,
374
+ title: annotation.title,
375
+ metadata: {
376
+ openai: metadata
377
+ }
378
+ });
379
+ }
296
380
  }
297
381
  break;
298
382
  }
299
- case "FileUrlPart":
383
+ case "refusal":
300
384
  {
301
- return yield* new _AiError.AiError({
302
- module: "OpenAiLanguageModel",
303
- method,
304
- description: "OpenAi does not support file content parts with URL data"
305
- });
306
- }
307
- case "TextPart":
308
- {
309
- content.push({
385
+ parts.push({
310
386
  type: "text",
311
- text: part.text
312
- });
313
- break;
314
- }
315
- case "ImagePart":
316
- {
317
- const mediaType = part.mediaType ?? "image/jpeg";
318
- const base64 = Encoding.encodeBase64(part.data);
319
- const url = `data:${mediaType};base64,${base64}`;
320
- content.push({
321
- type: "image_url",
322
- image_url: {
323
- url
387
+ text: "",
388
+ metadata: {
389
+ openai: {
390
+ refusal: contentPart.refusal
391
+ }
324
392
  }
325
393
  });
326
394
  break;
327
395
  }
328
- case "ImageUrlPart":
329
- {
330
- // TODO: provider options
331
- // const detail = part.providerOptions?.openai?.imageDetail as any
332
- content.push({
333
- type: "image_url",
334
- image_url: {
335
- url: part.url.toString()
336
- }
337
- });
338
- }
339
396
  }
340
397
  }
341
- if (Arr.isNonEmptyArray(content)) {
342
- messages.push({
343
- role: "user",
344
- name: message.userName,
345
- content
398
+ break;
399
+ }
400
+ case "function_call":
401
+ {
402
+ hasToolCalls = true;
403
+ const toolName = part.name;
404
+ const toolParams = part.arguments;
405
+ const params = yield* Effect.try({
406
+ try: () => Tool.unsafeSecureJsonParse(toolParams),
407
+ catch: cause => new AiError.MalformedOutput({
408
+ module: "OpenAiLanguageModel",
409
+ method: "makeResponse",
410
+ description: "Failed to securely parse tool call parameters " + `for tool '${toolName}':\nParameters: ${toolParams}`,
411
+ cause
412
+ })
413
+ });
414
+ parts.push({
415
+ type: "tool-call",
416
+ id: part.call_id,
417
+ name: toolName,
418
+ params,
419
+ metadata: {
420
+ openai: {
421
+ itemId: part.id
422
+ }
423
+ }
424
+ });
425
+ break;
426
+ }
427
+ case "code_interpreter_call":
428
+ {
429
+ parts.push({
430
+ type: "tool-call",
431
+ id: part.id,
432
+ name: "OpenAiCodeInterpreter",
433
+ params: {
434
+ code: part.code,
435
+ container_id: part.container_id
436
+ },
437
+ providerName: "code_interpreter",
438
+ providerExecuted: true
439
+ });
440
+ parts.push({
441
+ type: "tool-result",
442
+ id: part.id,
443
+ name: "OpenAiCodeInterpreter",
444
+ result: {
445
+ outputs: part.outputs
446
+ },
447
+ providerName: "code_interpreter",
448
+ providerExecuted: true
449
+ });
450
+ break;
451
+ }
452
+ case "file_search_call":
453
+ {
454
+ parts.push({
455
+ type: "tool-call",
456
+ id: part.id,
457
+ name: "OpenAiFileSearch",
458
+ params: {},
459
+ providerName: "file_search",
460
+ providerExecuted: true
461
+ });
462
+ parts.push({
463
+ type: "tool-result",
464
+ id: part.id,
465
+ name: "OpenAiFileSearch",
466
+ result: {
467
+ status: part.status,
468
+ queries: part.queries,
469
+ ...(part.results && {
470
+ results: part.results
471
+ })
472
+ },
473
+ providerName: "file_search",
474
+ providerExecuted: true
475
+ });
476
+ break;
477
+ }
478
+ case "web_search_call":
479
+ {
480
+ parts.push({
481
+ type: "tool-call",
482
+ id: part.id,
483
+ name: webSearchTool?.name ?? "OpenAiWebSearch",
484
+ params: {
485
+ action: part.action
486
+ },
487
+ providerName: webSearchTool?.providerName ?? "web_search",
488
+ providerExecuted: true
489
+ });
490
+ parts.push({
491
+ type: "tool-result",
492
+ id: part.id,
493
+ name: webSearchTool?.name ?? "OpenAiWebSearch",
494
+ result: {
495
+ status: part.status
496
+ },
497
+ providerName: webSearchTool?.providerName ?? "web_search",
498
+ providerExecuted: true
499
+ });
500
+ break;
501
+ }
502
+ // TODO(Max): support computer use
503
+ // case "computer_call": {
504
+ // parts.push({
505
+ // type: "tool-call",
506
+ // id: part.id,
507
+ // name: "OpenAiComputerUse",
508
+ // params: { action: part.action },
509
+ // providerName: webSearchTool?.providerName ?? "web_search",
510
+ // providerExecuted: true
511
+ // })
512
+ //
513
+ // parts.push({
514
+ // type: "tool-result",
515
+ // id: part.id,
516
+ // name: webSearchTool?.name ?? "OpenAiWebSearch",
517
+ // result: { status: part.status },
518
+ // providerName: webSearchTool?.providerName ?? "web_search",
519
+ // providerExecuted: true
520
+ // })
521
+ // break
522
+ // }
523
+ case "reasoning":
524
+ {
525
+ // If there are no summary parts, we have to add an empty one to
526
+ // propagate the part identifier
527
+ if (part.summary.length === 0) {
528
+ parts.push({
529
+ type: "reasoning",
530
+ text: "",
531
+ metadata: {
532
+ openai: {
533
+ itemId: part.id
534
+ }
535
+ }
346
536
  });
537
+ } else {
538
+ for (const summary of part.summary) {
539
+ const metadata = {
540
+ itemId: part.id,
541
+ encryptedContent: part.encrypted_content ?? undefined
542
+ };
543
+ parts.push({
544
+ type: "reasoning",
545
+ text: summary.text,
546
+ metadata: {
547
+ openai: metadata
548
+ }
549
+ });
550
+ }
347
551
  }
348
552
  break;
349
553
  }
350
554
  }
351
555
  }
352
- if (Arr.isNonEmptyReadonlyArray(messages)) {
353
- return messages;
354
- }
355
- return yield* new _AiError.AiError({
356
- module: "OpenAiLanguageModel",
357
- method,
358
- description: "Prompt contained no messages"
556
+ const finishReason = InternalUtilities.resolveFinishReason(response.incomplete_details?.reason, hasToolCalls);
557
+ const metadata = {
558
+ serviceTier: response.service_tier
559
+ };
560
+ parts.push({
561
+ type: "finish",
562
+ reason: finishReason,
563
+ usage: {
564
+ inputTokens: response.usage?.input_tokens,
565
+ outputTokens: response.usage?.output_tokens,
566
+ totalTokens: (response.usage?.input_tokens ?? 0) + (response.usage?.output_tokens ?? 0),
567
+ reasoningTokens: response.usage?.output_tokens_details?.reasoning_tokens,
568
+ cachedInputTokens: response.usage?.input_tokens_details?.cached_tokens
569
+ },
570
+ metadata: {
571
+ openai: metadata
572
+ }
359
573
  });
574
+ return parts;
360
575
  });
361
- const makeResponse = /*#__PURE__*/Effect.fnUntraced(function* (response, method, structuredTool) {
362
- const choice = response.choices[0];
363
- if (Predicate.isUndefined(choice)) {
364
- return yield* new _AiError.AiError({
365
- module: "OpenAiLanguageModel",
366
- method,
367
- description: "Could not get response"
368
- });
369
- }
370
- const parts = [];
371
- parts.push(new AiResponse.MetadataPart({
372
- id: response.id,
373
- model: response.model,
374
- // OpenAi returns the `created` time in seconds
375
- timestamp: new Date(response.created * 1000)
376
- }, constDisableValidation));
377
- const finishReason = (0, _utilities.resolveFinishReason)(choice.finish_reason);
378
- const inputTokens = response.usage?.prompt_tokens ?? 0;
379
- const outputTokens = response.usage?.completion_tokens ?? 0;
380
- const totalTokens = inputTokens + outputTokens;
381
- const metadata = {};
382
- if (Predicate.isNotUndefined(response.service_tier)) {
383
- metadata.serviceTier = response.service_tier;
384
- }
385
- if (Predicate.isNotUndefined(response.system_fingerprint)) {
386
- metadata.systemFingerprint = response.system_fingerprint;
387
- }
388
- if (Predicate.isNotUndefined(response.usage?.completion_tokens_details?.accepted_prediction_tokens)) {
389
- metadata.acceptedPredictionTokens = response.usage?.completion_tokens_details?.accepted_prediction_tokens ?? 0;
390
- }
391
- if (Predicate.isNotUndefined(response.usage?.completion_tokens_details?.rejected_prediction_tokens)) {
392
- metadata.rejectedPredictionTokens = response.usage?.completion_tokens_details?.rejected_prediction_tokens ?? 0;
393
- }
394
- if (Predicate.isNotUndefined(response.usage?.prompt_tokens_details?.audio_tokens)) {
395
- metadata.inputAudioTokens = response.usage?.prompt_tokens_details?.audio_tokens ?? 0;
396
- }
397
- if (Predicate.isNotUndefined(response.usage?.completion_tokens_details?.audio_tokens)) {
398
- metadata.outputAudioTokens = response.usage?.completion_tokens_details?.audio_tokens ?? 0;
399
- }
400
- parts.push(new AiResponse.FinishPart({
401
- reason: finishReason,
402
- usage: new AiResponse.Usage({
403
- inputTokens,
404
- outputTokens,
405
- totalTokens,
406
- reasoningTokens: response.usage?.completion_tokens_details?.reasoning_tokens ?? 0,
407
- cacheReadInputTokens: response.usage?.prompt_tokens_details?.cached_tokens ?? 0,
408
- cacheWriteInputTokens: 0
409
- }, constDisableValidation),
410
- providerMetadata: {
411
- [InternalUtilities.ProviderMetadataKey]: metadata
576
+ const makeStreamResponse = /*#__PURE__*/Effect.fnUntraced(function* (stream, options) {
577
+ const idGenerator = yield* IdGenerator.IdGenerator;
578
+ let hasToolCalls = false;
579
+ const activeReasoning = {};
580
+ const activeToolCalls = {};
581
+ const webSearchTool = options.tools.find(tool => Tool.isProviderDefined(tool) && (tool.id === "openai.web_search" || tool.id === "openai.web_search_preview"));
582
+ return stream.pipe(Stream.mapEffect(Effect.fnUntraced(function* (event) {
583
+ const parts = [];
584
+ switch (event.type) {
585
+ case "response.created":
586
+ {
587
+ const createdAt = new Date(event.response.created_at * 1000);
588
+ parts.push({
589
+ type: "response-metadata",
590
+ id: event.response.id,
591
+ modelId: event.response.model,
592
+ timestamp: DateTime.formatIso(DateTime.unsafeFromDate(createdAt))
593
+ });
594
+ break;
595
+ }
596
+ case "error":
597
+ {
598
+ parts.push({
599
+ type: "error",
600
+ error: event
601
+ });
602
+ break;
603
+ }
604
+ case "response.completed":
605
+ case "response.incomplete":
606
+ case "response.failed":
607
+ {
608
+ parts.push({
609
+ type: "finish",
610
+ reason: InternalUtilities.resolveFinishReason(event.response.incomplete_details?.reason, hasToolCalls),
611
+ usage: {
612
+ inputTokens: event.response.usage?.input_tokens,
613
+ outputTokens: event.response.usage?.output_tokens,
614
+ totalTokens: (event.response.usage?.input_tokens ?? 0) + (event.response.usage?.output_tokens ?? 0),
615
+ reasoningTokens: event.response.usage?.output_tokens_details?.reasoning_tokens,
616
+ cachedInputTokens: event.response.usage?.input_tokens_details?.cached_tokens
617
+ },
618
+ metadata: {
619
+ openai: {
620
+ serviceTier: event.response.service_tier
621
+ }
622
+ }
623
+ });
624
+ break;
625
+ }
626
+ case "response.output_item.added":
627
+ {
628
+ switch (event.item.type) {
629
+ case "computer_call":
630
+ {
631
+ // TODO(Max): support computer use
632
+ break;
633
+ }
634
+ case "file_search_call":
635
+ {
636
+ activeToolCalls[event.output_index] = {
637
+ id: event.item.id,
638
+ name: "OpenAiFileSearch"
639
+ };
640
+ parts.push({
641
+ type: "tool-params-start",
642
+ id: event.item.id,
643
+ name: "OpenAiFileSearch",
644
+ providerName: "file_search",
645
+ providerExecuted: true
646
+ });
647
+ break;
648
+ }
649
+ case "function_call":
650
+ {
651
+ activeToolCalls[event.output_index] = {
652
+ id: event.item.call_id,
653
+ name: event.item.name
654
+ };
655
+ parts.push({
656
+ type: "tool-params-start",
657
+ id: event.item.call_id,
658
+ name: event.item.name
659
+ });
660
+ break;
661
+ }
662
+ case "message":
663
+ {
664
+ parts.push({
665
+ type: "text-start",
666
+ id: event.item.id,
667
+ metadata: {
668
+ openai: {
669
+ itemId: event.item.id
670
+ }
671
+ }
672
+ });
673
+ break;
674
+ }
675
+ case "reasoning":
676
+ {
677
+ activeReasoning[event.item.id] = {
678
+ summaryParts: [0],
679
+ encryptedContent: event.item.encrypted_content
680
+ };
681
+ parts.push({
682
+ type: "reasoning-start",
683
+ id: `${event.item.id}:0`,
684
+ metadata: {
685
+ openai: {
686
+ itemId: event.item.id,
687
+ encryptedContent: event.item.encrypted_content
688
+ }
689
+ }
690
+ });
691
+ break;
692
+ }
693
+ case "web_search_call":
694
+ {
695
+ activeToolCalls[event.output_index] = {
696
+ id: event.item.id,
697
+ name: webSearchTool?.name ?? "OpenAiWebSearch"
698
+ };
699
+ parts.push({
700
+ type: "tool-params-start",
701
+ id: event.item.id,
702
+ name: webSearchTool?.name ?? "OpenAiWebSearch",
703
+ providerName: webSearchTool?.providerName ?? "web_search",
704
+ providerExecuted: true
705
+ });
706
+ break;
707
+ }
708
+ }
709
+ break;
710
+ }
711
+ case "response.output_item.done":
712
+ {
713
+ switch (event.item.type) {
714
+ case "code_interpreter_call":
715
+ {
716
+ parts.push({
717
+ type: "tool-call",
718
+ id: event.item.id,
719
+ name: "OpenAiCodeInterpreter",
720
+ params: {
721
+ code: event.item.code,
722
+ container_id: event.item.container_id
723
+ },
724
+ providerName: "code_interpreter",
725
+ providerExecuted: true
726
+ });
727
+ parts.push({
728
+ type: "tool-result",
729
+ id: event.item.id,
730
+ name: "OpenAiCodeInterpreter",
731
+ result: {
732
+ outputs: event.item.outputs
733
+ },
734
+ providerName: "code_interpreter",
735
+ providerExecuted: true
736
+ });
737
+ break;
738
+ }
739
+ // TODO(Max): support computer use
740
+ case "computer_call":
741
+ {
742
+ break;
743
+ }
744
+ case "file_search_call":
745
+ {
746
+ delete activeToolCalls[event.output_index];
747
+ parts.push({
748
+ type: "tool-params-end",
749
+ id: event.item.id
750
+ });
751
+ parts.push({
752
+ type: "tool-call",
753
+ id: event.item.id,
754
+ name: "OpenAiFileSearch",
755
+ params: {},
756
+ providerName: "file_search",
757
+ providerExecuted: true
758
+ });
759
+ parts.push({
760
+ type: "tool-result",
761
+ id: event.item.id,
762
+ name: "OpenAiFileSearch",
763
+ result: {
764
+ status: event.item.status,
765
+ queries: event.item.queries,
766
+ ...(event.item.results && {
767
+ results: event.item.results
768
+ })
769
+ },
770
+ providerName: "file_search",
771
+ providerExecuted: true
772
+ });
773
+ break;
774
+ }
775
+ case "function_call":
776
+ {
777
+ hasToolCalls = true;
778
+ const toolName = event.item.name;
779
+ const toolParams = event.item.arguments;
780
+ const params = yield* Effect.try({
781
+ try: () => Tool.unsafeSecureJsonParse(toolParams),
782
+ catch: cause => new AiError.MalformedOutput({
783
+ module: "OpenAiLanguageModel",
784
+ method: "makeStreamResponse",
785
+ description: "Failed to securely parse tool call parameters " + `for tool '${toolName}':\nParameters: ${toolParams}`,
786
+ cause
787
+ })
788
+ });
789
+ parts.push({
790
+ type: "tool-params-end",
791
+ id: event.item.call_id
792
+ });
793
+ parts.push({
794
+ type: "tool-call",
795
+ id: event.item.call_id,
796
+ name: toolName,
797
+ params,
798
+ metadata: {
799
+ openai: {
800
+ itemId: event.item.id
801
+ }
802
+ }
803
+ });
804
+ delete activeToolCalls[event.output_index];
805
+ break;
806
+ }
807
+ case "message":
808
+ {
809
+ parts.push({
810
+ type: "text-end",
811
+ id: event.item.id
812
+ });
813
+ break;
814
+ }
815
+ case "reasoning":
816
+ {
817
+ const reasoningPart = activeReasoning[event.item.id];
818
+ for (const summaryIndex of reasoningPart.summaryParts) {
819
+ parts.push({
820
+ type: "reasoning-end",
821
+ id: `${event.item.id}:${summaryIndex}`,
822
+ metadata: {
823
+ openai: {
824
+ itemId: event.item.id,
825
+ encryptedContent: event.item.encrypted_content
826
+ }
827
+ }
828
+ });
829
+ }
830
+ delete activeReasoning[event.item.id];
831
+ break;
832
+ }
833
+ case "web_search_call":
834
+ {
835
+ delete activeToolCalls[event.output_index];
836
+ parts.push({
837
+ type: "tool-params-end",
838
+ id: event.item.id
839
+ });
840
+ parts.push({
841
+ type: "tool-call",
842
+ id: event.item.id,
843
+ name: "OpenAiWebSearch",
844
+ params: {
845
+ action: event.item.action
846
+ },
847
+ providerName: "web_search",
848
+ providerExecuted: true
849
+ });
850
+ parts.push({
851
+ type: "tool-result",
852
+ id: event.item.id,
853
+ name: "OpenAiWebSearch",
854
+ result: {
855
+ status: event.item.status
856
+ },
857
+ providerName: "web_search",
858
+ providerExecuted: true
859
+ });
860
+ break;
861
+ }
862
+ }
863
+ break;
864
+ }
865
+ case "response.output_text.delta":
866
+ {
867
+ parts.push({
868
+ type: "text-delta",
869
+ id: event.item_id,
870
+ delta: event.delta
871
+ });
872
+ break;
873
+ }
874
+ case "response.output_text.annotation.added":
875
+ {
876
+ if (event.annotation.type === "file_citation") {
877
+ parts.push({
878
+ type: "source",
879
+ sourceType: "document",
880
+ id: yield* idGenerator.generateId(),
881
+ mediaType: "text/plain",
882
+ title: event.annotation.filename ?? "Untitled Document",
883
+ fileName: event.annotation.filename ?? event.annotation.file_id
884
+ });
885
+ }
886
+ if (event.annotation.type === "url_citation") {
887
+ parts.push({
888
+ type: "source",
889
+ sourceType: "url",
890
+ id: yield* idGenerator.generateId(),
891
+ url: event.annotation.url,
892
+ title: event.annotation.title
893
+ });
894
+ }
895
+ break;
896
+ }
897
+ case "response.function_call_arguments.delta":
898
+ {
899
+ const toolCallPart = activeToolCalls[event.output_index];
900
+ if (Predicate.isNotUndefined(toolCallPart)) {
901
+ parts.push({
902
+ type: "tool-params-delta",
903
+ id: toolCallPart.id,
904
+ delta: event.delta
905
+ });
906
+ }
907
+ break;
908
+ }
909
+ case "response.reasoning_summary_part.added":
910
+ {
911
+ // The first reasoning start is pushed in the `response.output_item.added` block
912
+ if (event.summary_index > 0) {
913
+ const reasoningPart = activeReasoning[event.item_id];
914
+ if (Predicate.isNotUndefined(reasoningPart)) {
915
+ reasoningPart.summaryParts.push(event.summary_index);
916
+ }
917
+ parts.push({
918
+ type: "reasoning-start",
919
+ id: `${event.item_id}:${event.summary_index}`,
920
+ metadata: {
921
+ openai: {
922
+ itemId: event.item_id,
923
+ encryptedContent: reasoningPart?.encryptedContent
924
+ }
925
+ }
926
+ });
927
+ }
928
+ break;
929
+ }
930
+ case "response.reasoning_summary_text.delta":
931
+ {
932
+ parts.push({
933
+ type: "reasoning-delta",
934
+ id: `${event.item_id}:${event.summary_index}`,
935
+ delta: event.delta,
936
+ metadata: {
937
+ openai: {
938
+ itemId: event.item_id
939
+ }
940
+ }
941
+ });
942
+ break;
943
+ }
412
944
  }
413
- }, constDisableValidation));
414
- if (Predicate.isNotNullable(choice.message.content)) {
415
- parts.push(new AiResponse.TextPart({
416
- text: choice.message.content
417
- }, constDisableValidation));
418
- }
419
- const output = new AiResponse.AiResponse({
420
- parts
421
- }, constDisableValidation);
422
- if (Predicate.isNotUndefined(structuredTool)) {
423
- return yield* AiResponse.withToolCallsJson(output, [{
424
- id: response.id,
425
- name: structuredTool.name,
426
- params: choice.message.content
427
- }]);
428
- }
429
- if (Predicate.isNotUndefined(choice.message.tool_calls) && choice.message.tool_calls.length > 0) {
430
- return yield* AiResponse.withToolCallsJson(output, choice.message.tool_calls.map(tool => ({
431
- id: tool.id,
432
- name: tool.function.name,
433
- params: tool.function.arguments
434
- })));
435
- }
436
- return output;
945
+ return parts;
946
+ })), Stream.flattenIterables);
437
947
  });
948
+ // =============================================================================
949
+ // Telemetry
950
+ // =============================================================================
438
951
  const annotateRequest = (span, request) => {
439
952
  (0, _OpenAiTelemetry.addGenAIAnnotations)(span, {
440
953
  system: "openai",
@@ -445,59 +958,186 @@ const annotateRequest = (span, request) => {
445
958
  model: request.model,
446
959
  temperature: request.temperature,
447
960
  topP: request.top_p,
448
- maxTokens: request.max_tokens,
449
- stopSequences: Arr.ensure(request.stop).filter(Predicate.isNotNullable),
450
- frequencyPenalty: request.frequency_penalty,
451
- presencePenalty: request.presence_penalty,
452
- seed: request.seed
961
+ maxTokens: request.max_output_tokens
453
962
  },
454
963
  openai: {
455
964
  request: {
456
- responseFormat: request.response_format?.type,
965
+ responseFormat: request.text?.format?.type,
457
966
  serviceTier: request.service_tier
458
967
  }
459
968
  }
460
969
  });
461
970
  };
462
- const annotateChatResponse = (span, response) => {
971
+ const annotateResponse = (span, response) => {
972
+ const finishReason = response.incomplete_details?.reason;
463
973
  (0, _OpenAiTelemetry.addGenAIAnnotations)(span, {
464
974
  response: {
465
975
  id: response.id,
466
976
  model: response.model,
467
- finishReasons: response.choices.map(choice => choice.finish_reason)
977
+ finishReasons: Predicate.isNotUndefined(finishReason) ? [finishReason] : undefined
468
978
  },
469
979
  usage: {
470
- inputTokens: response.usage?.prompt_tokens,
471
- outputTokens: response.usage?.completion_tokens
980
+ inputTokens: response.usage?.input_tokens,
981
+ outputTokens: response.usage?.output_tokens
472
982
  },
473
983
  openai: {
474
984
  response: {
475
- systemFingerprint: response.system_fingerprint,
476
985
  serviceTier: response.service_tier
477
986
  }
478
987
  }
479
988
  });
480
989
  };
481
- const annotateStreamResponse = (span, response) => {
482
- const metadataPart = response.parts.find(part => part._tag === "MetadataPart");
483
- const finishPart = response.parts.find(part => part._tag === "FinishPart");
484
- const providerMetadata = finishPart?.providerMetadata[ProviderMetadata.key];
485
- (0, _OpenAiTelemetry.addGenAIAnnotations)(span, {
486
- response: {
487
- id: metadataPart?.id,
488
- model: metadataPart?.model,
489
- finishReasons: finishPart?.reason ? [finishPart.reason] : undefined
490
- },
491
- usage: {
492
- inputTokens: finishPart?.usage.inputTokens,
493
- outputTokens: finishPart?.usage.outputTokens
494
- },
495
- openai: {
990
+ const annotateStreamResponse = (span, part) => {
991
+ if (part.type === "response-metadata") {
992
+ (0, _OpenAiTelemetry.addGenAIAnnotations)(span, {
993
+ response: {
994
+ id: part.id,
995
+ model: part.modelId
996
+ }
997
+ });
998
+ }
999
+ if (part.type === "finish") {
1000
+ const serviceTier = part.metadata?.openai?.serviceTier;
1001
+ (0, _OpenAiTelemetry.addGenAIAnnotations)(span, {
496
1002
  response: {
497
- serviceTier: providerMetadata?.serviceTier,
498
- systemFingerprint: providerMetadata?.systemFingerprint
1003
+ finishReasons: [part.reason]
1004
+ },
1005
+ usage: {
1006
+ inputTokens: part.usage.inputTokens,
1007
+ outputTokens: part.usage.outputTokens
1008
+ },
1009
+ openai: {
1010
+ response: {
1011
+ serviceTier
1012
+ }
1013
+ }
1014
+ });
1015
+ }
1016
+ };
1017
+ const prepareTools = /*#__PURE__*/Effect.fnUntraced(function* (options) {
1018
+ // Return immediately if no tools are in the toolkit
1019
+ if (options.tools.length === 0) {
1020
+ return {
1021
+ tools: undefined,
1022
+ toolChoice: undefined
1023
+ };
1024
+ }
1025
+ const tools = [];
1026
+ let toolChoice = undefined;
1027
+ // Filter the incoming tools down to the set of allowed tools as indicated by
1028
+ // the tool choice. This must be done here given that there is no tool name
1029
+ // in OpenAI's provider-defined tools, so there would be no way to perform
1030
+ // this filter otherwise
1031
+ let allowedTools = options.tools;
1032
+ if (typeof options.toolChoice === "object" && "oneOf" in options.toolChoice) {
1033
+ const allowedToolNames = new Set(options.toolChoice.oneOf);
1034
+ allowedTools = options.tools.filter(tool => allowedToolNames.has(tool.name));
1035
+ toolChoice = options.toolChoice.mode === "required" ? "required" : "auto";
1036
+ }
1037
+ // Convert the tools in the toolkit to the provider-defined format
1038
+ for (const tool of allowedTools) {
1039
+ if (Tool.isUserDefined(tool)) {
1040
+ tools.push({
1041
+ type: "function",
1042
+ name: tool.name,
1043
+ description: Tool.getDescription(tool),
1044
+ parameters: Tool.getJsonSchema(tool),
1045
+ strict: true
1046
+ });
1047
+ }
1048
+ if (Tool.isProviderDefined(tool)) {
1049
+ switch (tool.id) {
1050
+ case "openai.code_interpreter":
1051
+ {
1052
+ tools.push({
1053
+ ...tool.args,
1054
+ type: "code_interpreter"
1055
+ });
1056
+ break;
1057
+ }
1058
+ case "openai.file_search":
1059
+ {
1060
+ tools.push({
1061
+ ...tool.args,
1062
+ type: "file_search"
1063
+ });
1064
+ break;
1065
+ }
1066
+ case "openai.web_search":
1067
+ {
1068
+ tools.push({
1069
+ ...tool.args,
1070
+ type: "web_search"
1071
+ });
1072
+ break;
1073
+ }
1074
+ case "openai.web_search_preview":
1075
+ {
1076
+ tools.push({
1077
+ ...tool.args,
1078
+ type: "web_search_preview"
1079
+ });
1080
+ break;
1081
+ }
1082
+ default:
1083
+ {
1084
+ return yield* new AiError.MalformedInput({
1085
+ module: "AnthropicLanguageModel",
1086
+ method: "prepareTools",
1087
+ description: `Received request to call unknown provider-defined tool '${tool.name}'`
1088
+ });
1089
+ }
499
1090
  }
500
1091
  }
501
- });
1092
+ }
1093
+ if (options.toolChoice === "auto" || options.toolChoice === "none" || options.toolChoice === "required") {
1094
+ toolChoice = options.toolChoice;
1095
+ }
1096
+ if (typeof options.toolChoice === "object" && "tool" in options.toolChoice) {
1097
+ toolChoice = Predicate.isUndefined(OpenAiTool.getProviderDefinedToolName(options.toolChoice.tool)) ? {
1098
+ type: "function",
1099
+ name: options.toolChoice.tool
1100
+ } : {
1101
+ type: options.toolChoice.tool
1102
+ };
1103
+ }
1104
+ return {
1105
+ tools,
1106
+ toolChoice
1107
+ };
1108
+ });
1109
+ // =============================================================================
1110
+ // Utilities
1111
+ // =============================================================================
1112
+ const isFileId = (data, config) => Predicate.isNotUndefined(config.fileIdPrefixes) && config.fileIdPrefixes.some(prefix => data.startsWith(prefix));
1113
+ const getItemId = part => part.options.openai?.itemId;
1114
+ const getImageDetail = part => part.options.openai?.imageDetail ?? "auto";
1115
+ const prepareInclude = (options, config) => {
1116
+ const include = new Set(config.include ?? []);
1117
+ const codeInterpreterTool = options.tools.find(tool => Tool.isProviderDefined(tool) && tool.id === "openai.code_interpreter");
1118
+ if (Predicate.isNotUndefined(codeInterpreterTool)) {
1119
+ include.add("code_interpreter_call.outputs");
1120
+ }
1121
+ const webSearchTool = options.tools.find(tool => Tool.isProviderDefined(tool) && (tool.id === "openai.web_search" || tool.id === "openai.web_search_preview"));
1122
+ if (Predicate.isNotUndefined(webSearchTool)) {
1123
+ include.add("web_search_call.action.sources");
1124
+ }
1125
+ return Array.from(include);
1126
+ };
1127
+ const prepareResponseFormat = options => {
1128
+ if (options.responseFormat.type === "json") {
1129
+ const name = options.responseFormat.objectName;
1130
+ const schema = options.responseFormat.schema;
1131
+ return {
1132
+ type: "json_schema",
1133
+ name,
1134
+ description: Tool.getDescriptionFromSchemaAst(schema.ast) ?? "Response with a JSON object",
1135
+ schema: Tool.getJsonSchemaFromSchemaAst(schema.ast),
1136
+ strict: true
1137
+ };
1138
+ }
1139
+ return {
1140
+ type: "text"
1141
+ };
502
1142
  };
503
1143
  //# sourceMappingURL=OpenAiLanguageModel.js.map