@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,76 @@
1
+ import { BaseTextAdapter, StructuredOutputOptions, StructuredOutputResult } from '@tanstack/ai/adapters';
2
+ import { default as OpenAI_SDK } from 'openai';
3
+ import { ContentPart, DefaultMessageMetadataByModality, Modality, ModelMessage, StreamChunk, TextOptions } from '@tanstack/ai';
4
+ import { OpenAICompatibleClientConfig } from '../types/config.js';
5
+ /**
6
+ * OpenAI-compatible Chat Completions Text Adapter
7
+ *
8
+ * A generalized base class for providers that use the OpenAI Chat Completions API
9
+ * (`/v1/chat/completions`). Providers like Grok, Groq, OpenRouter, and others can
10
+ * extend this class and only need to:
11
+ * - Set `baseURL` in the config
12
+ * - Lock the generic type parameters to provider-specific types
13
+ * - Override specific methods for quirks
14
+ *
15
+ * All methods that build requests or process responses are `protected` so subclasses
16
+ * can override them.
17
+ */
18
+ export declare class OpenAICompatibleChatCompletionsTextAdapter<TModel extends string, TProviderOptions extends Record<string, any> = Record<string, any>, TInputModalities extends ReadonlyArray<Modality> = ReadonlyArray<Modality>, TMessageMetadata extends DefaultMessageMetadataByModality = DefaultMessageMetadataByModality> extends BaseTextAdapter<TModel, TProviderOptions, TInputModalities, TMessageMetadata> {
19
+ readonly kind: "text";
20
+ readonly name: string;
21
+ protected client: OpenAI_SDK;
22
+ constructor(config: OpenAICompatibleClientConfig, model: TModel, name?: string);
23
+ chatStream(options: TextOptions<TProviderOptions>): AsyncIterable<StreamChunk>;
24
+ /**
25
+ * Generate structured output using the provider's JSON Schema response format.
26
+ * Uses stream: false to get the complete response in one call.
27
+ *
28
+ * OpenAI-compatible APIs have strict requirements for structured output:
29
+ * - All properties must be in the `required` array
30
+ * - Optional fields should have null added to their type union
31
+ * - additionalProperties must be false for all objects
32
+ *
33
+ * The outputSchema is already JSON Schema (converted in the ai layer).
34
+ * We apply provider-specific transformations for structured output compatibility.
35
+ */
36
+ structuredOutput(options: StructuredOutputOptions<TProviderOptions>): Promise<StructuredOutputResult<unknown>>;
37
+ /**
38
+ * Applies provider-specific transformations for structured output compatibility.
39
+ * Override this in subclasses to handle provider-specific quirks.
40
+ */
41
+ protected makeStructuredOutputCompatible(schema: Record<string, any>, originalRequired: Array<string>): Record<string, any>;
42
+ /**
43
+ * Processes streamed chunks from the Chat Completions API and yields AG-UI events.
44
+ * Override this in subclasses to handle provider-specific stream behavior.
45
+ */
46
+ protected processStreamChunks(stream: AsyncIterable<OpenAI_SDK.Chat.Completions.ChatCompletionChunk>, options: TextOptions, aguiState: {
47
+ runId: string;
48
+ messageId: string;
49
+ timestamp: number;
50
+ hasEmittedRunStarted: boolean;
51
+ }): AsyncIterable<StreamChunk>;
52
+ /**
53
+ * Maps common TextOptions to Chat Completions API request format.
54
+ * Override this in subclasses to add provider-specific options.
55
+ */
56
+ protected mapOptionsToRequest(options: TextOptions): OpenAI_SDK.Chat.Completions.ChatCompletionCreateParamsStreaming;
57
+ /**
58
+ * Converts a single ModelMessage to the Chat Completions API message format.
59
+ * Override this in subclasses to handle provider-specific message formats.
60
+ */
61
+ protected convertMessage(message: ModelMessage): OpenAI_SDK.Chat.Completions.ChatCompletionMessageParam;
62
+ /**
63
+ * Converts a single ContentPart to the Chat Completions API content part format.
64
+ * Override this in subclasses to handle additional content types or provider-specific metadata.
65
+ */
66
+ protected convertContentPart(part: ContentPart): OpenAI_SDK.Chat.Completions.ChatCompletionContentPart | null;
67
+ /**
68
+ * Normalizes message content to an array of ContentPart.
69
+ * Handles backward compatibility with string content.
70
+ */
71
+ protected normalizeContent(content: string | null | Array<ContentPart>): Array<ContentPart>;
72
+ /**
73
+ * Extracts text content from a content value that may be string, null, or ContentPart array.
74
+ */
75
+ protected extractTextContent(content: string | null | Array<ContentPart>): string;
76
+ }
@@ -0,0 +1,411 @@
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 { convertToolsToChatCompletionsFormat } from "./chat-completions-tool-converter.js";
6
+ class OpenAICompatibleChatCompletionsTextAdapter extends BaseTextAdapter {
7
+ constructor(config, model, name = "openai-compatible") {
8
+ super({}, model);
9
+ this.kind = "text";
10
+ this.name = name;
11
+ this.client = createOpenAICompatibleClient(config);
12
+ }
13
+ async *chatStream(options) {
14
+ const requestParams = this.mapOptionsToRequest(options);
15
+ const timestamp = Date.now();
16
+ const aguiState = {
17
+ runId: generateId(this.name),
18
+ messageId: generateId(this.name),
19
+ timestamp,
20
+ hasEmittedRunStarted: false
21
+ };
22
+ try {
23
+ const stream = await this.client.chat.completions.create(
24
+ {
25
+ ...requestParams,
26
+ stream: true,
27
+ stream_options: { include_usage: true }
28
+ },
29
+ {
30
+ headers: options.request?.headers,
31
+ signal: options.request?.signal
32
+ }
33
+ );
34
+ yield* this.processStreamChunks(stream, options, aguiState);
35
+ } catch (error) {
36
+ const err = error;
37
+ if (!aguiState.hasEmittedRunStarted) {
38
+ aguiState.hasEmittedRunStarted = true;
39
+ yield {
40
+ type: "RUN_STARTED",
41
+ runId: aguiState.runId,
42
+ model: options.model,
43
+ timestamp
44
+ };
45
+ }
46
+ yield {
47
+ type: "RUN_ERROR",
48
+ runId: aguiState.runId,
49
+ model: options.model,
50
+ timestamp,
51
+ error: {
52
+ message: err.message || "Unknown error",
53
+ code: err.code
54
+ }
55
+ };
56
+ console.error(
57
+ `>>> [${this.name}] chatStream: Fatal error during response creation <<<`
58
+ );
59
+ console.error(">>> Error message:", err.message);
60
+ console.error(">>> Error stack:", err.stack);
61
+ console.error(">>> Full error:", err);
62
+ }
63
+ }
64
+ /**
65
+ * Generate structured output using the provider's JSON Schema response format.
66
+ * Uses stream: false to get the complete response in one call.
67
+ *
68
+ * OpenAI-compatible APIs have strict requirements for structured output:
69
+ * - All properties must be in the `required` array
70
+ * - Optional fields should have null added to their type union
71
+ * - additionalProperties must be false for all objects
72
+ *
73
+ * The outputSchema is already JSON Schema (converted in the ai layer).
74
+ * We apply provider-specific transformations for structured output compatibility.
75
+ */
76
+ async structuredOutput(options) {
77
+ const { chatOptions, outputSchema } = options;
78
+ const requestParams = this.mapOptionsToRequest(chatOptions);
79
+ const jsonSchema = this.makeStructuredOutputCompatible(
80
+ outputSchema,
81
+ outputSchema.required || []
82
+ );
83
+ try {
84
+ const {
85
+ stream_options: _,
86
+ stream: __,
87
+ ...cleanParams
88
+ } = requestParams;
89
+ const response = await this.client.chat.completions.create(
90
+ {
91
+ ...cleanParams,
92
+ stream: false,
93
+ response_format: {
94
+ type: "json_schema",
95
+ json_schema: {
96
+ name: "structured_output",
97
+ schema: jsonSchema,
98
+ strict: true
99
+ }
100
+ }
101
+ },
102
+ {
103
+ headers: chatOptions.request?.headers,
104
+ signal: chatOptions.request?.signal
105
+ }
106
+ );
107
+ const rawText = response.choices[0]?.message.content || "";
108
+ let parsed;
109
+ try {
110
+ parsed = JSON.parse(rawText);
111
+ } catch {
112
+ throw new Error(
113
+ `Failed to parse structured output as JSON. Content: ${rawText.slice(0, 200)}${rawText.length > 200 ? "..." : ""}`
114
+ );
115
+ }
116
+ const transformed = transformNullsToUndefined(parsed);
117
+ return {
118
+ data: transformed,
119
+ rawText
120
+ };
121
+ } catch (error) {
122
+ const err = error;
123
+ console.error(
124
+ `>>> [${this.name}] structuredOutput: Error during response creation <<<`
125
+ );
126
+ console.error(">>> Error message:", err.message);
127
+ throw error;
128
+ }
129
+ }
130
+ /**
131
+ * Applies provider-specific transformations for structured output compatibility.
132
+ * Override this in subclasses to handle provider-specific quirks.
133
+ */
134
+ makeStructuredOutputCompatible(schema, originalRequired) {
135
+ return makeStructuredOutputCompatible(schema, originalRequired);
136
+ }
137
+ /**
138
+ * Processes streamed chunks from the Chat Completions API and yields AG-UI events.
139
+ * Override this in subclasses to handle provider-specific stream behavior.
140
+ */
141
+ async *processStreamChunks(stream, options, aguiState) {
142
+ let accumulatedContent = "";
143
+ const timestamp = aguiState.timestamp;
144
+ let hasEmittedTextMessageStart = false;
145
+ const toolCallsInProgress = /* @__PURE__ */ new Map();
146
+ try {
147
+ for await (const chunk of stream) {
148
+ const choice = chunk.choices[0];
149
+ if (!choice) continue;
150
+ if (!aguiState.hasEmittedRunStarted) {
151
+ aguiState.hasEmittedRunStarted = true;
152
+ yield {
153
+ type: "RUN_STARTED",
154
+ runId: aguiState.runId,
155
+ model: chunk.model || options.model,
156
+ timestamp
157
+ };
158
+ }
159
+ const delta = choice.delta;
160
+ const deltaContent = delta.content;
161
+ const deltaToolCalls = delta.tool_calls;
162
+ if (deltaContent) {
163
+ if (!hasEmittedTextMessageStart) {
164
+ hasEmittedTextMessageStart = true;
165
+ yield {
166
+ type: "TEXT_MESSAGE_START",
167
+ messageId: aguiState.messageId,
168
+ model: chunk.model || options.model,
169
+ timestamp,
170
+ role: "assistant"
171
+ };
172
+ }
173
+ accumulatedContent += deltaContent;
174
+ yield {
175
+ type: "TEXT_MESSAGE_CONTENT",
176
+ messageId: aguiState.messageId,
177
+ model: chunk.model || options.model,
178
+ timestamp,
179
+ delta: deltaContent,
180
+ content: accumulatedContent
181
+ };
182
+ }
183
+ if (deltaToolCalls) {
184
+ for (const toolCallDelta of deltaToolCalls) {
185
+ const index = toolCallDelta.index;
186
+ if (!toolCallsInProgress.has(index)) {
187
+ toolCallsInProgress.set(index, {
188
+ id: toolCallDelta.id || "",
189
+ name: toolCallDelta.function?.name || "",
190
+ arguments: "",
191
+ started: false
192
+ });
193
+ }
194
+ const toolCall = toolCallsInProgress.get(index);
195
+ if (toolCallDelta.id) {
196
+ toolCall.id = toolCallDelta.id;
197
+ }
198
+ if (toolCallDelta.function?.name) {
199
+ toolCall.name = toolCallDelta.function.name;
200
+ }
201
+ if (toolCallDelta.function?.arguments) {
202
+ toolCall.arguments += toolCallDelta.function.arguments;
203
+ }
204
+ if (toolCall.id && toolCall.name && !toolCall.started) {
205
+ toolCall.started = true;
206
+ yield {
207
+ type: "TOOL_CALL_START",
208
+ toolCallId: toolCall.id,
209
+ toolName: toolCall.name,
210
+ model: chunk.model || options.model,
211
+ timestamp,
212
+ index
213
+ };
214
+ }
215
+ if (toolCallDelta.function?.arguments && toolCall.started) {
216
+ yield {
217
+ type: "TOOL_CALL_ARGS",
218
+ toolCallId: toolCall.id,
219
+ model: chunk.model || options.model,
220
+ timestamp,
221
+ delta: toolCallDelta.function.arguments
222
+ };
223
+ }
224
+ }
225
+ }
226
+ if (choice.finish_reason) {
227
+ if (choice.finish_reason === "tool_calls" || toolCallsInProgress.size > 0) {
228
+ for (const [, toolCall] of toolCallsInProgress) {
229
+ let parsedInput = {};
230
+ try {
231
+ parsedInput = toolCall.arguments ? JSON.parse(toolCall.arguments) : {};
232
+ } catch {
233
+ parsedInput = {};
234
+ }
235
+ yield {
236
+ type: "TOOL_CALL_END",
237
+ toolCallId: toolCall.id,
238
+ toolName: toolCall.name,
239
+ model: chunk.model || options.model,
240
+ timestamp,
241
+ input: parsedInput
242
+ };
243
+ }
244
+ }
245
+ const computedFinishReason = choice.finish_reason === "tool_calls" || toolCallsInProgress.size > 0 ? "tool_calls" : "stop";
246
+ if (hasEmittedTextMessageStart) {
247
+ yield {
248
+ type: "TEXT_MESSAGE_END",
249
+ messageId: aguiState.messageId,
250
+ model: chunk.model || options.model,
251
+ timestamp
252
+ };
253
+ }
254
+ yield {
255
+ type: "RUN_FINISHED",
256
+ runId: aguiState.runId,
257
+ model: chunk.model || options.model,
258
+ timestamp,
259
+ usage: chunk.usage ? {
260
+ promptTokens: chunk.usage.prompt_tokens || 0,
261
+ completionTokens: chunk.usage.completion_tokens || 0,
262
+ totalTokens: chunk.usage.total_tokens || 0
263
+ } : void 0,
264
+ finishReason: computedFinishReason
265
+ };
266
+ }
267
+ }
268
+ } catch (error) {
269
+ const err = error;
270
+ console.error(`[${this.name}] Stream ended with error:`, err.message);
271
+ yield {
272
+ type: "RUN_ERROR",
273
+ runId: aguiState.runId,
274
+ model: options.model,
275
+ timestamp,
276
+ error: {
277
+ message: err.message || "Unknown error occurred",
278
+ code: err.code
279
+ }
280
+ };
281
+ }
282
+ }
283
+ /**
284
+ * Maps common TextOptions to Chat Completions API request format.
285
+ * Override this in subclasses to add provider-specific options.
286
+ */
287
+ mapOptionsToRequest(options) {
288
+ const tools = options.tools ? convertToolsToChatCompletionsFormat(
289
+ options.tools,
290
+ this.makeStructuredOutputCompatible.bind(this)
291
+ ) : void 0;
292
+ const messages = [];
293
+ if (options.systemPrompts && options.systemPrompts.length > 0) {
294
+ messages.push({
295
+ role: "system",
296
+ content: options.systemPrompts.join("\n")
297
+ });
298
+ }
299
+ for (const message of options.messages) {
300
+ messages.push(this.convertMessage(message));
301
+ }
302
+ const modelOptions = options.modelOptions;
303
+ return {
304
+ ...modelOptions,
305
+ model: options.model,
306
+ messages,
307
+ temperature: options.temperature,
308
+ max_tokens: options.maxTokens,
309
+ top_p: options.topP,
310
+ tools,
311
+ stream: true
312
+ };
313
+ }
314
+ /**
315
+ * Converts a single ModelMessage to the Chat Completions API message format.
316
+ * Override this in subclasses to handle provider-specific message formats.
317
+ */
318
+ convertMessage(message) {
319
+ if (message.role === "tool") {
320
+ return {
321
+ role: "tool",
322
+ tool_call_id: message.toolCallId || "",
323
+ content: typeof message.content === "string" ? message.content : JSON.stringify(message.content)
324
+ };
325
+ }
326
+ if (message.role === "assistant") {
327
+ const toolCalls = message.toolCalls?.map((tc) => ({
328
+ id: tc.id,
329
+ type: "function",
330
+ function: {
331
+ name: tc.function.name,
332
+ arguments: typeof tc.function.arguments === "string" ? tc.function.arguments : JSON.stringify(tc.function.arguments)
333
+ }
334
+ }));
335
+ return {
336
+ role: "assistant",
337
+ content: this.extractTextContent(message.content),
338
+ ...toolCalls && toolCalls.length > 0 ? { tool_calls: toolCalls } : {}
339
+ };
340
+ }
341
+ const contentParts = this.normalizeContent(message.content);
342
+ if (contentParts.length === 1 && contentParts[0]?.type === "text") {
343
+ return {
344
+ role: "user",
345
+ content: contentParts[0].content
346
+ };
347
+ }
348
+ const parts = [];
349
+ for (const part of contentParts) {
350
+ const converted = this.convertContentPart(part);
351
+ if (converted) {
352
+ parts.push(converted);
353
+ }
354
+ }
355
+ return {
356
+ role: "user",
357
+ content: parts.length > 0 ? parts : ""
358
+ };
359
+ }
360
+ /**
361
+ * Converts a single ContentPart to the Chat Completions API content part format.
362
+ * Override this in subclasses to handle additional content types or provider-specific metadata.
363
+ */
364
+ convertContentPart(part) {
365
+ if (part.type === "text") {
366
+ return { type: "text", text: part.content };
367
+ }
368
+ if (part.type === "image") {
369
+ const imageMetadata = part.metadata;
370
+ const imageValue = part.source.value;
371
+ const imageUrl = part.source.type === "data" && !imageValue.startsWith("data:") ? `data:${part.source.mimeType};base64,${imageValue}` : imageValue;
372
+ return {
373
+ type: "image_url",
374
+ image_url: {
375
+ url: imageUrl,
376
+ detail: imageMetadata?.detail || "auto"
377
+ }
378
+ };
379
+ }
380
+ return null;
381
+ }
382
+ /**
383
+ * Normalizes message content to an array of ContentPart.
384
+ * Handles backward compatibility with string content.
385
+ */
386
+ normalizeContent(content) {
387
+ if (content === null) {
388
+ return [];
389
+ }
390
+ if (typeof content === "string") {
391
+ return [{ type: "text", content }];
392
+ }
393
+ return content;
394
+ }
395
+ /**
396
+ * Extracts text content from a content value that may be string, null, or ContentPart array.
397
+ */
398
+ extractTextContent(content) {
399
+ if (content === null) {
400
+ return "";
401
+ }
402
+ if (typeof content === "string") {
403
+ return content;
404
+ }
405
+ return content.filter((p) => p.type === "text").map((p) => p.content).join("");
406
+ }
407
+ }
408
+ export {
409
+ OpenAICompatibleChatCompletionsTextAdapter
410
+ };
411
+ //# sourceMappingURL=chat-completions-text.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chat-completions-text.js","sources":["../../../src/adapters/chat-completions-text.ts"],"sourcesContent":["import { BaseTextAdapter } from '@tanstack/ai/adapters'\nimport { generateId, transformNullsToUndefined } from '@tanstack/ai-utils'\nimport { createOpenAICompatibleClient } from '../utils/client'\nimport { makeStructuredOutputCompatible } from '../utils/schema-converter'\nimport { convertToolsToChatCompletionsFormat } from './chat-completions-tool-converter'\nimport type {\n StructuredOutputOptions,\n StructuredOutputResult,\n} from '@tanstack/ai/adapters'\nimport type OpenAI_SDK from 'openai'\nimport type {\n ContentPart,\n DefaultMessageMetadataByModality,\n Modality,\n ModelMessage,\n StreamChunk,\n TextOptions,\n} from '@tanstack/ai'\nimport type { OpenAICompatibleClientConfig } from '../types/config'\n\n/**\n * OpenAI-compatible Chat Completions Text Adapter\n *\n * A generalized base class for providers that use the OpenAI Chat Completions API\n * (`/v1/chat/completions`). Providers like Grok, Groq, OpenRouter, and others can\n * extend this class and only need to:\n * - Set `baseURL` in the config\n * - Lock the generic type parameters to provider-specific types\n * - Override specific methods for quirks\n *\n * All methods that build requests or process responses are `protected` so subclasses\n * can override them.\n */\nexport class OpenAICompatibleChatCompletionsTextAdapter<\n TModel extends string,\n TProviderOptions extends Record<string, any> = Record<string, any>,\n TInputModalities extends ReadonlyArray<Modality> = ReadonlyArray<Modality>,\n TMessageMetadata extends DefaultMessageMetadataByModality =\n DefaultMessageMetadataByModality,\n> extends BaseTextAdapter<\n TModel,\n TProviderOptions,\n TInputModalities,\n TMessageMetadata\n> {\n readonly kind = 'text' as const\n readonly name: string\n\n protected client: OpenAI_SDK\n\n constructor(\n config: OpenAICompatibleClientConfig,\n model: TModel,\n name: string = 'openai-compatible',\n ) {\n super({}, model)\n this.name = name\n this.client = createOpenAICompatibleClient(config)\n }\n\n async *chatStream(\n options: TextOptions<TProviderOptions>,\n ): AsyncIterable<StreamChunk> {\n const requestParams = this.mapOptionsToRequest(options)\n const timestamp = Date.now()\n\n // AG-UI lifecycle tracking (mutable state object for ESLint compatibility)\n const aguiState = {\n runId: generateId(this.name),\n messageId: generateId(this.name),\n timestamp,\n hasEmittedRunStarted: false,\n }\n\n try {\n const stream = await this.client.chat.completions.create(\n {\n ...requestParams,\n stream: true,\n stream_options: { include_usage: true },\n },\n {\n headers: (options.request as RequestInit | undefined)?.headers as\n | Record<string, string>\n | undefined,\n signal: (options.request as RequestInit | undefined)?.signal,\n },\n )\n\n yield* this.processStreamChunks(stream, options, aguiState)\n } catch (error: unknown) {\n const err = error as Error & { code?: string }\n\n // Emit RUN_STARTED if not yet emitted\n if (!aguiState.hasEmittedRunStarted) {\n aguiState.hasEmittedRunStarted = true\n yield {\n type: 'RUN_STARTED',\n runId: aguiState.runId,\n model: options.model,\n timestamp,\n }\n }\n\n // Emit AG-UI RUN_ERROR\n yield {\n type: 'RUN_ERROR',\n runId: aguiState.runId,\n model: options.model,\n timestamp,\n error: {\n message: err.message || 'Unknown error',\n code: err.code,\n },\n }\n\n console.error(\n `>>> [${this.name}] chatStream: Fatal error during response creation <<<`,\n )\n console.error('>>> Error message:', err.message)\n console.error('>>> Error stack:', err.stack)\n console.error('>>> Full error:', err)\n }\n }\n\n /**\n * Generate structured output using the provider's JSON Schema response format.\n * Uses stream: false to get the complete response in one call.\n *\n * OpenAI-compatible APIs have strict requirements for structured output:\n * - All properties must be in the `required` array\n * - Optional fields should have null added to their type union\n * - additionalProperties must be false for all objects\n *\n * The outputSchema is already JSON Schema (converted in the ai layer).\n * We apply provider-specific transformations for structured output compatibility.\n */\n async structuredOutput(\n options: StructuredOutputOptions<TProviderOptions>,\n ): Promise<StructuredOutputResult<unknown>> {\n const { chatOptions, outputSchema } = options\n const requestParams = this.mapOptionsToRequest(chatOptions)\n\n const jsonSchema = this.makeStructuredOutputCompatible(\n outputSchema,\n outputSchema.required || [],\n )\n\n try {\n // Strip stream_options which is only valid for streaming calls\n const {\n stream_options: _,\n stream: __,\n ...cleanParams\n } = requestParams as any\n const response = await this.client.chat.completions.create(\n {\n ...cleanParams,\n stream: false,\n response_format: {\n type: 'json_schema',\n json_schema: {\n name: 'structured_output',\n schema: jsonSchema,\n strict: true,\n },\n },\n },\n {\n headers: (chatOptions.request as RequestInit | undefined)?.headers as\n | Record<string, string>\n | undefined,\n signal: (chatOptions.request as RequestInit | undefined)?.signal,\n },\n )\n\n // Extract text content from the response\n const rawText = response.choices[0]?.message.content || ''\n\n // Parse the JSON response\n let parsed: unknown\n try {\n parsed = JSON.parse(rawText)\n } catch {\n throw new Error(\n `Failed to parse structured output as JSON. Content: ${rawText.slice(0, 200)}${rawText.length > 200 ? '...' : ''}`,\n )\n }\n\n // Transform null values to undefined to match original Zod schema expectations\n // Provider returns null for optional fields we made nullable in the schema\n const transformed = transformNullsToUndefined(parsed)\n\n return {\n data: transformed,\n rawText,\n }\n } catch (error: unknown) {\n const err = error as Error\n console.error(\n `>>> [${this.name}] structuredOutput: Error during response creation <<<`,\n )\n console.error('>>> Error message:', err.message)\n throw error\n }\n }\n\n /**\n * Applies provider-specific transformations for structured output compatibility.\n * Override this in subclasses to handle provider-specific quirks.\n */\n protected makeStructuredOutputCompatible(\n schema: Record<string, any>,\n originalRequired: Array<string>,\n ): Record<string, any> {\n return makeStructuredOutputCompatible(schema, originalRequired)\n }\n\n /**\n * Processes streamed chunks from the Chat Completions API and yields AG-UI events.\n * Override this in subclasses to handle provider-specific stream behavior.\n */\n protected async *processStreamChunks(\n stream: AsyncIterable<OpenAI_SDK.Chat.Completions.ChatCompletionChunk>,\n options: TextOptions,\n aguiState: {\n runId: string\n messageId: string\n timestamp: number\n hasEmittedRunStarted: boolean\n },\n ): AsyncIterable<StreamChunk> {\n let accumulatedContent = ''\n const timestamp = aguiState.timestamp\n let hasEmittedTextMessageStart = false\n\n // Track tool calls being streamed (arguments come in chunks)\n const toolCallsInProgress = new Map<\n number,\n {\n id: string\n name: string\n arguments: string\n started: boolean // Track if TOOL_CALL_START has been emitted\n }\n >()\n\n try {\n for await (const chunk of stream) {\n const choice = chunk.choices[0]\n\n if (!choice) continue\n\n // Emit RUN_STARTED on first chunk\n if (!aguiState.hasEmittedRunStarted) {\n aguiState.hasEmittedRunStarted = true\n yield {\n type: 'RUN_STARTED',\n runId: aguiState.runId,\n model: chunk.model || options.model,\n timestamp,\n }\n }\n\n const delta = choice.delta\n const deltaContent = delta.content\n const deltaToolCalls = delta.tool_calls\n\n // Handle content delta\n if (deltaContent) {\n // Emit TEXT_MESSAGE_START on first text content\n if (!hasEmittedTextMessageStart) {\n hasEmittedTextMessageStart = true\n yield {\n type: 'TEXT_MESSAGE_START',\n messageId: aguiState.messageId,\n model: chunk.model || options.model,\n timestamp,\n role: 'assistant',\n }\n }\n\n accumulatedContent += deltaContent\n\n // Emit AG-UI TEXT_MESSAGE_CONTENT\n yield {\n type: 'TEXT_MESSAGE_CONTENT',\n messageId: aguiState.messageId,\n model: chunk.model || options.model,\n timestamp,\n delta: deltaContent,\n content: accumulatedContent,\n }\n }\n\n // Handle tool calls - they come in as deltas\n if (deltaToolCalls) {\n for (const toolCallDelta of deltaToolCalls) {\n const index = toolCallDelta.index\n\n // Initialize or update the tool call in progress\n if (!toolCallsInProgress.has(index)) {\n toolCallsInProgress.set(index, {\n id: toolCallDelta.id || '',\n name: toolCallDelta.function?.name || '',\n arguments: '',\n started: false,\n })\n }\n\n const toolCall = toolCallsInProgress.get(index)!\n\n // Update with any new data from the delta\n if (toolCallDelta.id) {\n toolCall.id = toolCallDelta.id\n }\n if (toolCallDelta.function?.name) {\n toolCall.name = toolCallDelta.function.name\n }\n if (toolCallDelta.function?.arguments) {\n toolCall.arguments += toolCallDelta.function.arguments\n }\n\n // Emit TOOL_CALL_START when we have id and name\n if (toolCall.id && toolCall.name && !toolCall.started) {\n toolCall.started = true\n yield {\n type: 'TOOL_CALL_START',\n toolCallId: toolCall.id,\n toolName: toolCall.name,\n model: chunk.model || options.model,\n timestamp,\n index,\n }\n }\n\n // Emit TOOL_CALL_ARGS for argument deltas\n if (toolCallDelta.function?.arguments && toolCall.started) {\n yield {\n type: 'TOOL_CALL_ARGS',\n toolCallId: toolCall.id,\n model: chunk.model || options.model,\n timestamp,\n delta: toolCallDelta.function.arguments,\n }\n }\n }\n }\n\n // Handle finish reason\n if (choice.finish_reason) {\n // Emit all completed tool calls\n if (\n choice.finish_reason === 'tool_calls' ||\n toolCallsInProgress.size > 0\n ) {\n for (const [, toolCall] of toolCallsInProgress) {\n // Parse arguments for TOOL_CALL_END\n let parsedInput: unknown = {}\n try {\n parsedInput = toolCall.arguments\n ? JSON.parse(toolCall.arguments)\n : {}\n } catch {\n parsedInput = {}\n }\n\n // Emit AG-UI TOOL_CALL_END\n yield {\n type: 'TOOL_CALL_END',\n toolCallId: toolCall.id,\n toolName: toolCall.name,\n model: chunk.model || options.model,\n timestamp,\n input: parsedInput,\n }\n }\n }\n\n const computedFinishReason =\n choice.finish_reason === 'tool_calls' ||\n toolCallsInProgress.size > 0\n ? 'tool_calls'\n : 'stop'\n\n // Emit TEXT_MESSAGE_END if we had text content\n if (hasEmittedTextMessageStart) {\n yield {\n type: 'TEXT_MESSAGE_END',\n messageId: aguiState.messageId,\n model: chunk.model || options.model,\n timestamp,\n }\n }\n\n // Emit AG-UI RUN_FINISHED\n yield {\n type: 'RUN_FINISHED',\n runId: aguiState.runId,\n model: chunk.model || options.model,\n timestamp,\n usage: chunk.usage\n ? {\n promptTokens: chunk.usage.prompt_tokens || 0,\n completionTokens: chunk.usage.completion_tokens || 0,\n totalTokens: chunk.usage.total_tokens || 0,\n }\n : undefined,\n finishReason: computedFinishReason,\n }\n }\n }\n } catch (error: unknown) {\n const err = error as Error & { code?: string }\n console.error(`[${this.name}] Stream ended with error:`, err.message)\n\n // Emit AG-UI RUN_ERROR\n yield {\n type: 'RUN_ERROR',\n runId: aguiState.runId,\n model: options.model,\n timestamp,\n error: {\n message: err.message || 'Unknown error occurred',\n code: err.code,\n },\n }\n }\n }\n\n /**\n * Maps common TextOptions to Chat Completions API request format.\n * Override this in subclasses to add provider-specific options.\n */\n protected mapOptionsToRequest(\n options: TextOptions,\n ): OpenAI_SDK.Chat.Completions.ChatCompletionCreateParamsStreaming {\n const tools = options.tools\n ? convertToolsToChatCompletionsFormat(\n options.tools,\n this.makeStructuredOutputCompatible.bind(this),\n )\n : undefined\n\n // Build messages array with system prompts\n const messages: Array<OpenAI_SDK.Chat.Completions.ChatCompletionMessageParam> =\n []\n\n // Add system prompts first\n if (options.systemPrompts && options.systemPrompts.length > 0) {\n messages.push({\n role: 'system',\n content: options.systemPrompts.join('\\n'),\n })\n }\n\n // Convert messages\n for (const message of options.messages) {\n messages.push(this.convertMessage(message))\n }\n\n const modelOptions = options.modelOptions\n\n return {\n ...modelOptions,\n model: options.model,\n messages,\n temperature: options.temperature,\n max_tokens: options.maxTokens,\n top_p: options.topP,\n tools: tools as Array<OpenAI_SDK.Chat.Completions.ChatCompletionTool>,\n stream: true,\n }\n }\n\n /**\n * Converts a single ModelMessage to the Chat Completions API message format.\n * Override this in subclasses to handle provider-specific message formats.\n */\n protected convertMessage(\n message: ModelMessage,\n ): OpenAI_SDK.Chat.Completions.ChatCompletionMessageParam {\n // Handle tool messages\n if (message.role === 'tool') {\n return {\n role: 'tool',\n tool_call_id: message.toolCallId || '',\n content:\n typeof message.content === 'string'\n ? message.content\n : JSON.stringify(message.content),\n }\n }\n\n // Handle assistant messages\n if (message.role === 'assistant') {\n const toolCalls = message.toolCalls?.map((tc) => ({\n id: tc.id,\n type: 'function' as const,\n function: {\n name: tc.function.name,\n arguments:\n typeof tc.function.arguments === 'string'\n ? tc.function.arguments\n : JSON.stringify(tc.function.arguments),\n },\n }))\n\n return {\n role: 'assistant',\n content: this.extractTextContent(message.content),\n ...(toolCalls && toolCalls.length > 0 ? { tool_calls: toolCalls } : {}),\n }\n }\n\n // Handle user messages - support multimodal content\n const contentParts = this.normalizeContent(message.content)\n\n // If only text, use simple string format\n if (contentParts.length === 1 && contentParts[0]?.type === 'text') {\n return {\n role: 'user',\n content: contentParts[0].content,\n }\n }\n\n // Otherwise, use array format for multimodal\n const parts: Array<OpenAI_SDK.Chat.Completions.ChatCompletionContentPart> =\n []\n for (const part of contentParts) {\n const converted = this.convertContentPart(part)\n if (converted) {\n parts.push(converted)\n }\n }\n\n return {\n role: 'user',\n content: parts.length > 0 ? parts : '',\n }\n }\n\n /**\n * Converts a single ContentPart to the Chat Completions API content part format.\n * Override this in subclasses to handle additional content types or provider-specific metadata.\n */\n protected convertContentPart(\n part: ContentPart,\n ): OpenAI_SDK.Chat.Completions.ChatCompletionContentPart | null {\n if (part.type === 'text') {\n return { type: 'text', text: part.content }\n }\n\n if (part.type === 'image') {\n const imageMetadata = part.metadata as\n | { detail?: 'auto' | 'low' | 'high' }\n | undefined\n\n // For base64 data, construct a data URI using the mimeType from source\n const imageValue = part.source.value\n const imageUrl =\n part.source.type === 'data' && !imageValue.startsWith('data:')\n ? `data:${part.source.mimeType};base64,${imageValue}`\n : imageValue\n\n return {\n type: 'image_url',\n image_url: {\n url: imageUrl,\n detail: imageMetadata?.detail || 'auto',\n },\n }\n }\n\n // Unsupported content type — subclasses can override to handle more types\n return null\n }\n\n /**\n * Normalizes message content to an array of ContentPart.\n * Handles backward compatibility with string content.\n */\n protected normalizeContent(\n content: string | null | Array<ContentPart>,\n ): Array<ContentPart> {\n if (content === null) {\n return []\n }\n if (typeof content === 'string') {\n return [{ type: 'text', content: content }]\n }\n return content\n }\n\n /**\n * Extracts text content from a content value that may be string, null, or ContentPart array.\n */\n protected extractTextContent(\n content: string | null | Array<ContentPart>,\n ): string {\n if (content === null) {\n return ''\n }\n if (typeof content === 'string') {\n return content\n }\n // It's an array of ContentPart\n return content\n .filter((p) => p.type === 'text')\n .map((p) => p.content)\n .join('')\n }\n}\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,24 @@
1
+ import { Tool } from '@tanstack/ai';
2
+ import { default as OpenAI } from 'openai';
3
+ /**
4
+ * Chat Completions API tool format.
5
+ * This is distinct from the Responses API tool format.
6
+ */
7
+ export type ChatCompletionFunctionTool = OpenAI.Chat.Completions.ChatCompletionTool;
8
+ /**
9
+ * Converts a standard Tool to OpenAI Chat Completions ChatCompletionTool format.
10
+ *
11
+ * Tool schemas are already converted to JSON Schema in the ai layer.
12
+ * We apply OpenAI-compatible transformations for strict mode:
13
+ * - All properties in required array
14
+ * - Optional fields made nullable
15
+ * - additionalProperties: false
16
+ *
17
+ * This enables strict mode for all tools automatically.
18
+ */
19
+ export declare function convertFunctionToolToChatCompletionsFormat(tool: Tool, schemaConverter?: (schema: Record<string, any>, required: Array<string>) => Record<string, any>): ChatCompletionFunctionTool;
20
+ /**
21
+ * Converts an array of standard Tools to Chat Completions format.
22
+ * Chat Completions API primarily supports function tools.
23
+ */
24
+ export declare function convertToolsToChatCompletionsFormat(tools: Array<Tool>, schemaConverter?: (schema: Record<string, any>, required: Array<string>) => Record<string, any>): Array<ChatCompletionFunctionTool>;
@@ -0,0 +1,29 @@
1
+ import { makeStructuredOutputCompatible } from "../utils/schema-converter.js";
2
+ function convertFunctionToolToChatCompletionsFormat(tool, schemaConverter = makeStructuredOutputCompatible) {
3
+ const inputSchema = tool.inputSchema ?? {
4
+ type: "object",
5
+ properties: {},
6
+ required: []
7
+ };
8
+ const jsonSchema = schemaConverter(inputSchema, inputSchema.required || []);
9
+ jsonSchema.additionalProperties = false;
10
+ return {
11
+ type: "function",
12
+ function: {
13
+ name: tool.name,
14
+ description: tool.description,
15
+ parameters: jsonSchema,
16
+ strict: true
17
+ }
18
+ };
19
+ }
20
+ function convertToolsToChatCompletionsFormat(tools, schemaConverter) {
21
+ return tools.map(
22
+ (tool) => convertFunctionToolToChatCompletionsFormat(tool, schemaConverter)
23
+ );
24
+ }
25
+ export {
26
+ convertFunctionToolToChatCompletionsFormat,
27
+ convertToolsToChatCompletionsFormat
28
+ };
29
+ //# sourceMappingURL=chat-completions-tool-converter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chat-completions-tool-converter.js","sources":["../../../src/adapters/chat-completions-tool-converter.ts"],"sourcesContent":["import { makeStructuredOutputCompatible } from '../utils/schema-converter'\nimport type { JSONSchema, Tool } from '@tanstack/ai'\nimport type OpenAI from 'openai'\n\n/**\n * Chat Completions API tool format.\n * This is distinct from the Responses API tool format.\n */\nexport type ChatCompletionFunctionTool =\n OpenAI.Chat.Completions.ChatCompletionTool\n\n/**\n * Converts a standard Tool to OpenAI Chat Completions ChatCompletionTool format.\n *\n * Tool schemas are already converted to JSON Schema in the ai layer.\n * We apply OpenAI-compatible transformations for strict mode:\n * - All properties in required array\n * - Optional fields made nullable\n * - additionalProperties: false\n *\n * This enables strict mode for all tools automatically.\n */\nexport function convertFunctionToolToChatCompletionsFormat(\n tool: Tool,\n schemaConverter: (\n schema: Record<string, any>,\n required: Array<string>,\n ) => Record<string, any> = makeStructuredOutputCompatible,\n): ChatCompletionFunctionTool {\n const inputSchema = (tool.inputSchema ?? {\n type: 'object',\n properties: {},\n required: [],\n }) as JSONSchema\n\n const jsonSchema = schemaConverter(inputSchema, inputSchema.required || [])\n\n // Ensure additionalProperties is false for strict mode\n jsonSchema.additionalProperties = false\n\n return {\n type: 'function',\n function: {\n name: tool.name,\n description: tool.description,\n parameters: jsonSchema,\n strict: true,\n },\n } satisfies ChatCompletionFunctionTool\n}\n\n/**\n * Converts an array of standard Tools to Chat Completions format.\n * Chat Completions API primarily supports function tools.\n */\nexport function convertToolsToChatCompletionsFormat(\n tools: Array<Tool>,\n schemaConverter?: (\n schema: Record<string, any>,\n required: Array<string>,\n ) => Record<string, any>,\n): Array<ChatCompletionFunctionTool> {\n return tools.map((tool) =>\n convertFunctionToolToChatCompletionsFormat(tool, schemaConverter),\n )\n}\n"],"names":[],"mappings":";AAsBO,SAAS,2CACd,MACA,kBAG2B,gCACC;AAC5B,QAAM,cAAe,KAAK,eAAe;AAAA,IACvC,MAAM;AAAA,IACN,YAAY,CAAA;AAAA,IACZ,UAAU,CAAA;AAAA,EAAC;AAGb,QAAM,aAAa,gBAAgB,aAAa,YAAY,YAAY,CAAA,CAAE;AAG1E,aAAW,uBAAuB;AAElC,SAAO;AAAA,IACL,MAAM;AAAA,IACN,UAAU;AAAA,MACR,MAAM,KAAK;AAAA,MACX,aAAa,KAAK;AAAA,MAClB,YAAY;AAAA,MACZ,QAAQ;AAAA,IAAA;AAAA,EACV;AAEJ;AAMO,SAAS,oCACd,OACA,iBAImC;AACnC,SAAO,MAAM;AAAA,IAAI,CAAC,SAChB,2CAA2C,MAAM,eAAe;AAAA,EAAA;AAEpE;"}
@@ -0,0 +1,32 @@
1
+ import { BaseImageAdapter } from '@tanstack/ai/adapters';
2
+ import { ImageGenerationOptions, ImageGenerationResult } from '@tanstack/ai';
3
+ import { default as OpenAI_SDK } from 'openai';
4
+ import { OpenAICompatibleClientConfig } from '../types/config.js';
5
+ /**
6
+ * OpenAI-Compatible Image Generation Adapter
7
+ *
8
+ * A generalized base class for providers that implement OpenAI-compatible image
9
+ * generation APIs. Providers like OpenAI, Grok, and others can extend this class
10
+ * and only need to:
11
+ * - Set `baseURL` in the config
12
+ * - Lock the generic type parameters to provider-specific types
13
+ * - Override validation or request building methods for provider-specific constraints
14
+ *
15
+ * All methods that validate inputs, build requests, or transform responses are
16
+ * `protected` so subclasses can override them.
17
+ */
18
+ export declare class OpenAICompatibleImageAdapter<TModel extends string, TProviderOptions extends object = Record<string, any>, TModelProviderOptionsByName extends Record<string, any> = Record<string, any>, TModelSizeByName extends Record<string, string> = Record<string, string>> extends BaseImageAdapter<TModel, TProviderOptions, TModelProviderOptionsByName, TModelSizeByName> {
19
+ readonly kind: "image";
20
+ readonly name: string;
21
+ protected client: OpenAI_SDK;
22
+ constructor(config: OpenAICompatibleClientConfig, model: TModel, name?: string);
23
+ generateImages(options: ImageGenerationOptions<TProviderOptions>): Promise<ImageGenerationResult>;
24
+ protected buildRequest(options: ImageGenerationOptions<TProviderOptions>): OpenAI_SDK.Images.ImageGenerateParams;
25
+ protected transformResponse(model: string, response: OpenAI_SDK.Images.ImagesResponse): ImageGenerationResult;
26
+ protected validatePrompt(options: {
27
+ prompt: string;
28
+ model: string;
29
+ }): void;
30
+ protected validateImageSize(_model: string, _size: string | undefined): void;
31
+ protected validateNumberOfImages(_model: string, numberOfImages: number | undefined): void;
32
+ }