@tanstack/openai-base 0.1.0

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 (110) hide show
  1. package/dist/esm/adapters/chat-completions-text.d.ts +76 -0
  2. package/dist/esm/adapters/chat-completions-text.js +411 -0
  3. package/dist/esm/adapters/chat-completions-text.js.map +1 -0
  4. package/dist/esm/adapters/chat-completions-tool-converter.d.ts +24 -0
  5. package/dist/esm/adapters/chat-completions-tool-converter.js +29 -0
  6. package/dist/esm/adapters/chat-completions-tool-converter.js.map +1 -0
  7. package/dist/esm/adapters/image.d.ts +32 -0
  8. package/dist/esm/adapters/image.js +69 -0
  9. package/dist/esm/adapters/image.js.map +1 -0
  10. package/dist/esm/adapters/responses-text.d.ts +115 -0
  11. package/dist/esm/adapters/responses-text.js +635 -0
  12. package/dist/esm/adapters/responses-text.js.map +1 -0
  13. package/dist/esm/adapters/responses-tool-converter.d.ts +35 -0
  14. package/dist/esm/adapters/responses-tool-converter.js +27 -0
  15. package/dist/esm/adapters/responses-tool-converter.js.map +1 -0
  16. package/dist/esm/adapters/summarize.d.ts +28 -0
  17. package/dist/esm/adapters/summarize.js +74 -0
  18. package/dist/esm/adapters/summarize.js.map +1 -0
  19. package/dist/esm/adapters/transcription.d.ts +39 -0
  20. package/dist/esm/adapters/transcription.js +139 -0
  21. package/dist/esm/adapters/transcription.js.map +1 -0
  22. package/dist/esm/adapters/tts.d.ts +26 -0
  23. package/dist/esm/adapters/tts.js +65 -0
  24. package/dist/esm/adapters/tts.js.map +1 -0
  25. package/dist/esm/adapters/video.d.ts +48 -0
  26. package/dist/esm/adapters/video.js +192 -0
  27. package/dist/esm/adapters/video.js.map +1 -0
  28. package/dist/esm/index.d.ts +15 -0
  29. package/dist/esm/index.js +65 -0
  30. package/dist/esm/index.js.map +1 -0
  31. package/dist/esm/tools/apply-patch-tool.d.ts +11 -0
  32. package/dist/esm/tools/apply-patch-tool.js +17 -0
  33. package/dist/esm/tools/apply-patch-tool.js.map +1 -0
  34. package/dist/esm/tools/code-interpreter-tool.d.ts +11 -0
  35. package/dist/esm/tools/code-interpreter-tool.js +22 -0
  36. package/dist/esm/tools/code-interpreter-tool.js.map +1 -0
  37. package/dist/esm/tools/computer-use-tool.d.ts +11 -0
  38. package/dist/esm/tools/computer-use-tool.js +23 -0
  39. package/dist/esm/tools/computer-use-tool.js.map +1 -0
  40. package/dist/esm/tools/custom-tool.d.ts +11 -0
  41. package/dist/esm/tools/custom-tool.js +23 -0
  42. package/dist/esm/tools/custom-tool.js.map +1 -0
  43. package/dist/esm/tools/file-search-tool.d.ts +11 -0
  44. package/dist/esm/tools/file-search-tool.js +30 -0
  45. package/dist/esm/tools/file-search-tool.js.map +1 -0
  46. package/dist/esm/tools/function-tool.d.ts +15 -0
  47. package/dist/esm/tools/function-tool.js +24 -0
  48. package/dist/esm/tools/function-tool.js.map +1 -0
  49. package/dist/esm/tools/image-generation-tool.d.ts +11 -0
  50. package/dist/esm/tools/image-generation-tool.js +27 -0
  51. package/dist/esm/tools/image-generation-tool.js.map +1 -0
  52. package/dist/esm/tools/index.d.ts +27 -0
  53. package/dist/esm/tools/local-shell-tool.d.ts +11 -0
  54. package/dist/esm/tools/local-shell-tool.js +17 -0
  55. package/dist/esm/tools/local-shell-tool.js.map +1 -0
  56. package/dist/esm/tools/mcp-tool.d.ts +12 -0
  57. package/dist/esm/tools/mcp-tool.js +31 -0
  58. package/dist/esm/tools/mcp-tool.js.map +1 -0
  59. package/dist/esm/tools/shell-tool.d.ts +11 -0
  60. package/dist/esm/tools/shell-tool.js +17 -0
  61. package/dist/esm/tools/shell-tool.js.map +1 -0
  62. package/dist/esm/tools/tool-choice.d.ts +17 -0
  63. package/dist/esm/tools/tool-converter.d.ts +6 -0
  64. package/dist/esm/tools/tool-converter.js +61 -0
  65. package/dist/esm/tools/tool-converter.js.map +1 -0
  66. package/dist/esm/tools/web-search-preview-tool.d.ts +11 -0
  67. package/dist/esm/tools/web-search-preview-tool.js +20 -0
  68. package/dist/esm/tools/web-search-preview-tool.js.map +1 -0
  69. package/dist/esm/tools/web-search-tool.d.ts +11 -0
  70. package/dist/esm/tools/web-search-tool.js +16 -0
  71. package/dist/esm/tools/web-search-tool.js.map +1 -0
  72. package/dist/esm/types/config.d.ts +4 -0
  73. package/dist/esm/types/message-metadata.d.ts +19 -0
  74. package/dist/esm/types/provider-options.d.ts +40 -0
  75. package/dist/esm/utils/client.d.ts +3 -0
  76. package/dist/esm/utils/client.js +8 -0
  77. package/dist/esm/utils/client.js.map +1 -0
  78. package/dist/esm/utils/schema-converter.d.ts +12 -0
  79. package/dist/esm/utils/schema-converter.js +65 -0
  80. package/dist/esm/utils/schema-converter.js.map +1 -0
  81. package/package.json +57 -0
  82. package/src/adapters/chat-completions-text.ts +817 -0
  83. package/src/adapters/chat-completions-tool-converter.ts +70 -0
  84. package/src/adapters/image.ts +158 -0
  85. package/src/adapters/responses-text.ts +1147 -0
  86. package/src/adapters/responses-tool-converter.ts +77 -0
  87. package/src/adapters/summarize.ts +174 -0
  88. package/src/adapters/transcription.ts +194 -0
  89. package/src/adapters/tts.ts +124 -0
  90. package/src/adapters/video.ts +385 -0
  91. package/src/index.ts +24 -0
  92. package/src/tools/apply-patch-tool.ts +32 -0
  93. package/src/tools/code-interpreter-tool.ts +39 -0
  94. package/src/tools/computer-use-tool.ts +38 -0
  95. package/src/tools/custom-tool.ts +33 -0
  96. package/src/tools/file-search-tool.ts +51 -0
  97. package/src/tools/function-tool.ts +44 -0
  98. package/src/tools/image-generation-tool.ts +51 -0
  99. package/src/tools/index.ts +41 -0
  100. package/src/tools/local-shell-tool.ts +32 -0
  101. package/src/tools/mcp-tool.ts +47 -0
  102. package/src/tools/shell-tool.ts +30 -0
  103. package/src/tools/tool-choice.ts +31 -0
  104. package/src/tools/tool-converter.ts +68 -0
  105. package/src/tools/web-search-preview-tool.ts +39 -0
  106. package/src/tools/web-search-tool.ts +38 -0
  107. package/src/types/config.ts +5 -0
  108. package/src/utils/client.ts +8 -0
  109. package/src/utils/request-options.ts +16 -0
  110. package/src/utils/schema-converter.ts +89 -0
@@ -0,0 +1,635 @@
1
+ import { BaseTextAdapter } from "@tanstack/ai/adapters";
2
+ import { generateId, transformNullsToUndefined } from "@tanstack/ai-utils";
3
+ import { createOpenAICompatibleClient } from "../utils/client.js";
4
+ import { makeStructuredOutputCompatible } from "../utils/schema-converter.js";
5
+ import { convertToolsToResponsesFormat } from "./responses-tool-converter.js";
6
+ class OpenAICompatibleResponsesTextAdapter extends BaseTextAdapter {
7
+ constructor(config, model, name = "openai-compatible-responses") {
8
+ super({}, model);
9
+ this.kind = "text";
10
+ this.name = name;
11
+ this.client = createOpenAICompatibleClient(config);
12
+ }
13
+ async *chatStream(options) {
14
+ const toolCallMetadata = /* @__PURE__ */ new Map();
15
+ const requestParams = this.mapOptionsToRequest(options);
16
+ const timestamp = Date.now();
17
+ const aguiState = {
18
+ runId: generateId(this.name),
19
+ messageId: generateId(this.name),
20
+ timestamp,
21
+ hasEmittedRunStarted: false
22
+ };
23
+ try {
24
+ const response = await this.client.responses.create(
25
+ {
26
+ ...requestParams,
27
+ stream: true
28
+ },
29
+ {
30
+ headers: options.request?.headers,
31
+ signal: options.request?.signal
32
+ }
33
+ );
34
+ yield* this.processStreamChunks(
35
+ response,
36
+ toolCallMetadata,
37
+ options,
38
+ aguiState
39
+ );
40
+ } catch (error) {
41
+ const err = error;
42
+ if (!aguiState.hasEmittedRunStarted) {
43
+ aguiState.hasEmittedRunStarted = true;
44
+ yield {
45
+ type: "RUN_STARTED",
46
+ runId: aguiState.runId,
47
+ model: options.model,
48
+ timestamp
49
+ };
50
+ }
51
+ yield {
52
+ type: "RUN_ERROR",
53
+ runId: aguiState.runId,
54
+ model: options.model,
55
+ timestamp,
56
+ error: {
57
+ message: err.message || "Unknown error",
58
+ code: err.code
59
+ }
60
+ };
61
+ console.error(
62
+ `>>> [${this.name}] chatStream: Fatal error during response creation <<<`
63
+ );
64
+ console.error(">>> Error message:", err.message);
65
+ console.error(">>> Error stack:", err.stack);
66
+ console.error(">>> Full error:", err);
67
+ }
68
+ }
69
+ /**
70
+ * Generate structured output using the provider's native JSON Schema response format.
71
+ * Uses stream: false to get the complete response in one call.
72
+ *
73
+ * OpenAI-compatible Responses APIs have strict requirements for structured output:
74
+ * - All properties must be in the `required` array
75
+ * - Optional fields should have null added to their type union
76
+ * - additionalProperties must be false for all objects
77
+ *
78
+ * The outputSchema is already JSON Schema (converted in the ai layer).
79
+ * We apply provider-specific transformations for structured output compatibility.
80
+ */
81
+ async structuredOutput(options) {
82
+ const { chatOptions, outputSchema } = options;
83
+ const requestParams = this.mapOptionsToRequest(chatOptions);
84
+ const jsonSchema = this.makeStructuredOutputCompatible(
85
+ outputSchema,
86
+ outputSchema.required || []
87
+ );
88
+ try {
89
+ const response = await this.client.responses.create(
90
+ {
91
+ ...requestParams,
92
+ stream: false,
93
+ // Configure structured output via text.format
94
+ text: {
95
+ format: {
96
+ type: "json_schema",
97
+ name: "structured_output",
98
+ schema: jsonSchema,
99
+ strict: true
100
+ }
101
+ }
102
+ },
103
+ {
104
+ headers: chatOptions.request?.headers,
105
+ signal: chatOptions.request?.signal
106
+ }
107
+ );
108
+ const rawText = this.extractTextFromResponse(
109
+ response
110
+ );
111
+ let parsed;
112
+ try {
113
+ parsed = JSON.parse(rawText);
114
+ } catch {
115
+ throw new Error(
116
+ `Failed to parse structured output as JSON. Content: ${rawText.slice(0, 200)}${rawText.length > 200 ? "..." : ""}`
117
+ );
118
+ }
119
+ const transformed = transformNullsToUndefined(parsed);
120
+ return {
121
+ data: transformed,
122
+ rawText
123
+ };
124
+ } catch (error) {
125
+ const err = error;
126
+ console.error(
127
+ `>>> [${this.name}] structuredOutput: Error during response creation <<<`
128
+ );
129
+ console.error(">>> Error message:", err.message);
130
+ throw error;
131
+ }
132
+ }
133
+ /**
134
+ * Applies provider-specific transformations for structured output compatibility.
135
+ * Override this in subclasses to handle provider-specific quirks.
136
+ */
137
+ makeStructuredOutputCompatible(schema, originalRequired) {
138
+ return makeStructuredOutputCompatible(schema, originalRequired);
139
+ }
140
+ /**
141
+ * Extract text content from a non-streaming Responses API response.
142
+ * Override this in subclasses for provider-specific response shapes.
143
+ */
144
+ extractTextFromResponse(response) {
145
+ let textContent = "";
146
+ for (const item of response.output) {
147
+ if (item.type === "message") {
148
+ for (const part of item.content) {
149
+ if (part.type === "output_text") {
150
+ textContent += part.text;
151
+ }
152
+ }
153
+ }
154
+ }
155
+ return textContent;
156
+ }
157
+ /**
158
+ * Processes streamed chunks from the Responses API and yields AG-UI events.
159
+ * Override this in subclasses to handle provider-specific stream behavior.
160
+ *
161
+ * Handles the following event types:
162
+ * - response.created / response.incomplete / response.failed
163
+ * - response.output_text.delta
164
+ * - response.reasoning_text.delta
165
+ * - response.reasoning_summary_text.delta
166
+ * - response.content_part.added / response.content_part.done
167
+ * - response.output_item.added
168
+ * - response.function_call_arguments.delta / response.function_call_arguments.done
169
+ * - response.completed
170
+ * - error
171
+ */
172
+ async *processStreamChunks(stream, toolCallMetadata, options, aguiState) {
173
+ let accumulatedContent = "";
174
+ let accumulatedReasoning = "";
175
+ const timestamp = aguiState.timestamp;
176
+ let chunkCount = 0;
177
+ let hasStreamedContentDeltas = false;
178
+ let hasStreamedReasoningDeltas = false;
179
+ let model = options.model;
180
+ let stepId = null;
181
+ let hasEmittedTextMessageStart = false;
182
+ let hasEmittedStepStarted = false;
183
+ try {
184
+ for await (const chunk of stream) {
185
+ chunkCount++;
186
+ if (!aguiState.hasEmittedRunStarted) {
187
+ aguiState.hasEmittedRunStarted = true;
188
+ yield {
189
+ type: "RUN_STARTED",
190
+ runId: aguiState.runId,
191
+ model: model || options.model,
192
+ timestamp
193
+ };
194
+ }
195
+ const handleContentPart = (contentPart) => {
196
+ if (contentPart.type === "output_text") {
197
+ accumulatedContent += contentPart.text || "";
198
+ return {
199
+ type: "TEXT_MESSAGE_CONTENT",
200
+ messageId: aguiState.messageId,
201
+ model: model || options.model,
202
+ timestamp,
203
+ delta: contentPart.text || "",
204
+ content: accumulatedContent
205
+ };
206
+ }
207
+ if (contentPart.type === "reasoning_text") {
208
+ accumulatedReasoning += contentPart.text || "";
209
+ return {
210
+ type: "STEP_FINISHED",
211
+ stepId: stepId || generateId(this.name),
212
+ model: model || options.model,
213
+ timestamp,
214
+ delta: contentPart.text || "",
215
+ content: accumulatedReasoning
216
+ };
217
+ }
218
+ return {
219
+ type: "RUN_ERROR",
220
+ runId: aguiState.runId,
221
+ model: model || options.model,
222
+ timestamp,
223
+ error: {
224
+ message: contentPart.refusal || "Unknown refusal"
225
+ }
226
+ };
227
+ };
228
+ if (chunk.type === "response.created" || chunk.type === "response.incomplete" || chunk.type === "response.failed") {
229
+ model = chunk.response.model;
230
+ hasStreamedContentDeltas = false;
231
+ hasStreamedReasoningDeltas = false;
232
+ hasEmittedTextMessageStart = false;
233
+ hasEmittedStepStarted = false;
234
+ accumulatedContent = "";
235
+ accumulatedReasoning = "";
236
+ if (chunk.response.error) {
237
+ yield {
238
+ type: "RUN_ERROR",
239
+ runId: aguiState.runId,
240
+ model: chunk.response.model,
241
+ timestamp,
242
+ error: chunk.response.error
243
+ };
244
+ }
245
+ if (chunk.response.incomplete_details) {
246
+ yield {
247
+ type: "RUN_ERROR",
248
+ runId: aguiState.runId,
249
+ model: chunk.response.model,
250
+ timestamp,
251
+ error: {
252
+ message: chunk.response.incomplete_details.reason ?? ""
253
+ }
254
+ };
255
+ }
256
+ }
257
+ if (chunk.type === "response.output_text.delta" && chunk.delta) {
258
+ const textDelta = Array.isArray(chunk.delta) ? chunk.delta.join("") : typeof chunk.delta === "string" ? chunk.delta : "";
259
+ if (textDelta) {
260
+ if (!hasEmittedTextMessageStart) {
261
+ hasEmittedTextMessageStart = true;
262
+ yield {
263
+ type: "TEXT_MESSAGE_START",
264
+ messageId: aguiState.messageId,
265
+ model: model || options.model,
266
+ timestamp,
267
+ role: "assistant"
268
+ };
269
+ }
270
+ accumulatedContent += textDelta;
271
+ hasStreamedContentDeltas = true;
272
+ yield {
273
+ type: "TEXT_MESSAGE_CONTENT",
274
+ messageId: aguiState.messageId,
275
+ model: model || options.model,
276
+ timestamp,
277
+ delta: textDelta,
278
+ content: accumulatedContent
279
+ };
280
+ }
281
+ }
282
+ if (chunk.type === "response.reasoning_text.delta" && chunk.delta) {
283
+ const reasoningDelta = Array.isArray(chunk.delta) ? chunk.delta.join("") : typeof chunk.delta === "string" ? chunk.delta : "";
284
+ if (reasoningDelta) {
285
+ if (!hasEmittedStepStarted) {
286
+ hasEmittedStepStarted = true;
287
+ stepId = generateId(this.name);
288
+ yield {
289
+ type: "STEP_STARTED",
290
+ stepId,
291
+ model: model || options.model,
292
+ timestamp,
293
+ stepType: "thinking"
294
+ };
295
+ }
296
+ accumulatedReasoning += reasoningDelta;
297
+ hasStreamedReasoningDeltas = true;
298
+ yield {
299
+ type: "STEP_FINISHED",
300
+ stepId: stepId || generateId(this.name),
301
+ model: model || options.model,
302
+ timestamp,
303
+ delta: reasoningDelta,
304
+ content: accumulatedReasoning
305
+ };
306
+ }
307
+ }
308
+ if (chunk.type === "response.reasoning_summary_text.delta" && chunk.delta) {
309
+ const summaryDelta = typeof chunk.delta === "string" ? chunk.delta : "";
310
+ if (summaryDelta) {
311
+ if (!hasEmittedStepStarted) {
312
+ hasEmittedStepStarted = true;
313
+ stepId = generateId(this.name);
314
+ yield {
315
+ type: "STEP_STARTED",
316
+ stepId,
317
+ model: model || options.model,
318
+ timestamp,
319
+ stepType: "thinking"
320
+ };
321
+ }
322
+ accumulatedReasoning += summaryDelta;
323
+ hasStreamedReasoningDeltas = true;
324
+ yield {
325
+ type: "STEP_FINISHED",
326
+ stepId: stepId || generateId(this.name),
327
+ model: model || options.model,
328
+ timestamp,
329
+ delta: summaryDelta,
330
+ content: accumulatedReasoning
331
+ };
332
+ }
333
+ }
334
+ if (chunk.type === "response.content_part.added") {
335
+ const contentPart = chunk.part;
336
+ if (contentPart.type === "output_text" && !hasEmittedTextMessageStart) {
337
+ hasEmittedTextMessageStart = true;
338
+ yield {
339
+ type: "TEXT_MESSAGE_START",
340
+ messageId: aguiState.messageId,
341
+ model: model || options.model,
342
+ timestamp,
343
+ role: "assistant"
344
+ };
345
+ }
346
+ if (contentPart.type === "reasoning_text" && !hasEmittedStepStarted) {
347
+ hasEmittedStepStarted = true;
348
+ stepId = generateId(this.name);
349
+ yield {
350
+ type: "STEP_STARTED",
351
+ stepId,
352
+ model: model || options.model,
353
+ timestamp,
354
+ stepType: "thinking"
355
+ };
356
+ }
357
+ yield handleContentPart(contentPart);
358
+ }
359
+ if (chunk.type === "response.content_part.done") {
360
+ const contentPart = chunk.part;
361
+ if (contentPart.type === "output_text" && hasStreamedContentDeltas) {
362
+ continue;
363
+ }
364
+ if (contentPart.type === "reasoning_text" && hasStreamedReasoningDeltas) {
365
+ continue;
366
+ }
367
+ yield handleContentPart(contentPart);
368
+ }
369
+ if (chunk.type === "response.output_item.added") {
370
+ const item = chunk.item;
371
+ if (item.type === "function_call") {
372
+ const itemId = item.id || item.call_id;
373
+ const callId = item.call_id || item.id || "";
374
+ if (!toolCallMetadata.has(itemId)) {
375
+ toolCallMetadata.set(itemId, {
376
+ index: chunk.output_index,
377
+ name: item.name || "",
378
+ callId,
379
+ started: false
380
+ });
381
+ }
382
+ yield {
383
+ type: "TOOL_CALL_START",
384
+ toolCallId: callId,
385
+ toolName: item.name || "",
386
+ model: model || options.model,
387
+ timestamp,
388
+ index: chunk.output_index
389
+ };
390
+ toolCallMetadata.get(itemId).started = true;
391
+ }
392
+ }
393
+ if (chunk.type === "response.function_call_arguments.delta" && chunk.delta) {
394
+ const metadata = toolCallMetadata.get(chunk.item_id);
395
+ yield {
396
+ type: "TOOL_CALL_ARGS",
397
+ toolCallId: metadata?.callId || chunk.item_id,
398
+ model: model || options.model,
399
+ timestamp,
400
+ delta: chunk.delta,
401
+ args: metadata ? void 0 : chunk.delta
402
+ };
403
+ }
404
+ if (chunk.type === "response.function_call_arguments.done") {
405
+ const { item_id } = chunk;
406
+ const metadata = toolCallMetadata.get(item_id);
407
+ const name = metadata?.name || "";
408
+ const callId = metadata?.callId || item_id;
409
+ let parsedInput = {};
410
+ try {
411
+ parsedInput = chunk.arguments ? JSON.parse(chunk.arguments) : {};
412
+ } catch {
413
+ parsedInput = {};
414
+ }
415
+ yield {
416
+ type: "TOOL_CALL_END",
417
+ toolCallId: callId,
418
+ toolName: name,
419
+ model: model || options.model,
420
+ timestamp,
421
+ input: parsedInput
422
+ };
423
+ }
424
+ if (chunk.type === "response.completed") {
425
+ if (hasEmittedTextMessageStart) {
426
+ yield {
427
+ type: "TEXT_MESSAGE_END",
428
+ messageId: aguiState.messageId,
429
+ model: model || options.model,
430
+ timestamp
431
+ };
432
+ }
433
+ const hasFunctionCalls = chunk.response.output.some(
434
+ (item) => item.type === "function_call"
435
+ );
436
+ yield {
437
+ type: "RUN_FINISHED",
438
+ runId: aguiState.runId,
439
+ model: model || options.model,
440
+ timestamp,
441
+ usage: {
442
+ promptTokens: chunk.response.usage?.input_tokens || 0,
443
+ completionTokens: chunk.response.usage?.output_tokens || 0,
444
+ totalTokens: chunk.response.usage?.total_tokens || 0
445
+ },
446
+ finishReason: hasFunctionCalls ? "tool_calls" : "stop"
447
+ };
448
+ }
449
+ if (chunk.type === "error") {
450
+ yield {
451
+ type: "RUN_ERROR",
452
+ runId: aguiState.runId,
453
+ model: model || options.model,
454
+ timestamp,
455
+ error: {
456
+ message: chunk.message,
457
+ code: chunk.code ?? void 0
458
+ }
459
+ };
460
+ }
461
+ }
462
+ } catch (error) {
463
+ const err = error;
464
+ console.error(`[${this.name}] Stream ended with error:`, err.message);
465
+ yield {
466
+ type: "RUN_ERROR",
467
+ runId: aguiState.runId,
468
+ model: options.model,
469
+ timestamp,
470
+ error: {
471
+ message: err.message || "Unknown error occurred",
472
+ code: err.code
473
+ }
474
+ };
475
+ }
476
+ }
477
+ /**
478
+ * Maps common TextOptions to Responses API request format.
479
+ * Override this in subclasses to add provider-specific options.
480
+ */
481
+ mapOptionsToRequest(options) {
482
+ const input = this.convertMessagesToInput(options.messages);
483
+ const tools = options.tools ? convertToolsToResponsesFormat(
484
+ options.tools,
485
+ this.makeStructuredOutputCompatible.bind(this)
486
+ ) : void 0;
487
+ const modelOptions = options.modelOptions;
488
+ return {
489
+ model: options.model,
490
+ temperature: options.temperature,
491
+ max_output_tokens: options.maxTokens,
492
+ top_p: options.topP,
493
+ metadata: options.metadata,
494
+ instructions: options.systemPrompts?.join("\n"),
495
+ ...modelOptions,
496
+ input,
497
+ tools
498
+ };
499
+ }
500
+ /**
501
+ * Converts ModelMessage[] to Responses API ResponseInput format.
502
+ * Override this in subclasses for provider-specific message format quirks.
503
+ *
504
+ * Key differences from Chat Completions:
505
+ * - Tool results use `function_call_output` type (not `tool` role)
506
+ * - Assistant tool calls are `function_call` objects (not nested in `tool_calls`)
507
+ * - User content uses `input_text`, `input_image`, `input_file` types
508
+ * - System prompts go in `instructions`, not as messages
509
+ */
510
+ convertMessagesToInput(messages) {
511
+ const result = [];
512
+ for (const message of messages) {
513
+ if (message.role === "tool") {
514
+ result.push({
515
+ type: "function_call_output",
516
+ call_id: message.toolCallId || "",
517
+ output: typeof message.content === "string" ? message.content : JSON.stringify(message.content)
518
+ });
519
+ continue;
520
+ }
521
+ if (message.role === "assistant") {
522
+ if (message.toolCalls && message.toolCalls.length > 0) {
523
+ for (const toolCall of message.toolCalls) {
524
+ const argumentsString = typeof toolCall.function.arguments === "string" ? toolCall.function.arguments : JSON.stringify(toolCall.function.arguments);
525
+ result.push({
526
+ type: "function_call",
527
+ call_id: toolCall.id,
528
+ name: toolCall.function.name,
529
+ arguments: argumentsString
530
+ });
531
+ }
532
+ }
533
+ if (message.content) {
534
+ const contentStr = this.extractTextContent(message.content);
535
+ if (contentStr) {
536
+ result.push({
537
+ type: "message",
538
+ role: "assistant",
539
+ content: contentStr
540
+ });
541
+ }
542
+ }
543
+ continue;
544
+ }
545
+ const contentParts = this.normalizeContent(message.content);
546
+ const inputContent = [];
547
+ for (const part of contentParts) {
548
+ inputContent.push(this.convertContentPartToInput(part));
549
+ }
550
+ if (inputContent.length === 0) {
551
+ inputContent.push({ type: "input_text", text: "" });
552
+ }
553
+ result.push({
554
+ type: "message",
555
+ role: "user",
556
+ content: inputContent
557
+ });
558
+ }
559
+ return result;
560
+ }
561
+ /**
562
+ * Converts a ContentPart to Responses API input content item.
563
+ * Handles text, image, and audio content parts.
564
+ * Override this in subclasses for additional content types or provider-specific metadata.
565
+ */
566
+ convertContentPartToInput(part) {
567
+ switch (part.type) {
568
+ case "text":
569
+ return {
570
+ type: "input_text",
571
+ text: part.content
572
+ };
573
+ case "image": {
574
+ const imageMetadata = part.metadata;
575
+ if (part.source.type === "url") {
576
+ return {
577
+ type: "input_image",
578
+ image_url: part.source.value,
579
+ detail: imageMetadata?.detail || "auto"
580
+ };
581
+ }
582
+ const imageValue = part.source.value;
583
+ const imageUrl = imageValue.startsWith("data:") ? imageValue : `data:${part.source.mimeType};base64,${imageValue}`;
584
+ return {
585
+ type: "input_image",
586
+ image_url: imageUrl,
587
+ detail: imageMetadata?.detail || "auto"
588
+ };
589
+ }
590
+ case "audio": {
591
+ if (part.source.type === "url") {
592
+ return {
593
+ type: "input_file",
594
+ file_url: part.source.value
595
+ };
596
+ }
597
+ return {
598
+ type: "input_file",
599
+ file_data: part.source.value
600
+ };
601
+ }
602
+ default:
603
+ throw new Error(`Unsupported content part type: ${part.type}`);
604
+ }
605
+ }
606
+ /**
607
+ * Normalizes message content to an array of ContentPart.
608
+ * Handles backward compatibility with string content.
609
+ */
610
+ normalizeContent(content) {
611
+ if (content === null) {
612
+ return [];
613
+ }
614
+ if (typeof content === "string") {
615
+ return [{ type: "text", content }];
616
+ }
617
+ return content;
618
+ }
619
+ /**
620
+ * Extracts text content from a content value that may be string, null, or ContentPart array.
621
+ */
622
+ extractTextContent(content) {
623
+ if (content === null) {
624
+ return "";
625
+ }
626
+ if (typeof content === "string") {
627
+ return content;
628
+ }
629
+ return content.filter((p) => p.type === "text").map((p) => p.content).join("");
630
+ }
631
+ }
632
+ export {
633
+ OpenAICompatibleResponsesTextAdapter
634
+ };
635
+ //# sourceMappingURL=responses-text.js.map