@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.
- package/dist/esm/adapters/chat-completions-text.d.ts +76 -0
- package/dist/esm/adapters/chat-completions-text.js +411 -0
- package/dist/esm/adapters/chat-completions-text.js.map +1 -0
- package/dist/esm/adapters/chat-completions-tool-converter.d.ts +24 -0
- package/dist/esm/adapters/chat-completions-tool-converter.js +29 -0
- package/dist/esm/adapters/chat-completions-tool-converter.js.map +1 -0
- package/dist/esm/adapters/image.d.ts +32 -0
- package/dist/esm/adapters/image.js +69 -0
- package/dist/esm/adapters/image.js.map +1 -0
- package/dist/esm/adapters/responses-text.d.ts +115 -0
- package/dist/esm/adapters/responses-text.js +635 -0
- package/dist/esm/adapters/responses-text.js.map +1 -0
- package/dist/esm/adapters/responses-tool-converter.d.ts +35 -0
- package/dist/esm/adapters/responses-tool-converter.js +27 -0
- package/dist/esm/adapters/responses-tool-converter.js.map +1 -0
- package/dist/esm/adapters/summarize.d.ts +28 -0
- package/dist/esm/adapters/summarize.js +74 -0
- package/dist/esm/adapters/summarize.js.map +1 -0
- package/dist/esm/adapters/transcription.d.ts +39 -0
- package/dist/esm/adapters/transcription.js +139 -0
- package/dist/esm/adapters/transcription.js.map +1 -0
- package/dist/esm/adapters/tts.d.ts +26 -0
- package/dist/esm/adapters/tts.js +65 -0
- package/dist/esm/adapters/tts.js.map +1 -0
- package/dist/esm/adapters/video.d.ts +48 -0
- package/dist/esm/adapters/video.js +192 -0
- package/dist/esm/adapters/video.js.map +1 -0
- package/dist/esm/index.d.ts +15 -0
- package/dist/esm/index.js +65 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/tools/apply-patch-tool.d.ts +11 -0
- package/dist/esm/tools/apply-patch-tool.js +17 -0
- package/dist/esm/tools/apply-patch-tool.js.map +1 -0
- package/dist/esm/tools/code-interpreter-tool.d.ts +11 -0
- package/dist/esm/tools/code-interpreter-tool.js +22 -0
- package/dist/esm/tools/code-interpreter-tool.js.map +1 -0
- package/dist/esm/tools/computer-use-tool.d.ts +11 -0
- package/dist/esm/tools/computer-use-tool.js +23 -0
- package/dist/esm/tools/computer-use-tool.js.map +1 -0
- package/dist/esm/tools/custom-tool.d.ts +11 -0
- package/dist/esm/tools/custom-tool.js +23 -0
- package/dist/esm/tools/custom-tool.js.map +1 -0
- package/dist/esm/tools/file-search-tool.d.ts +11 -0
- package/dist/esm/tools/file-search-tool.js +30 -0
- package/dist/esm/tools/file-search-tool.js.map +1 -0
- package/dist/esm/tools/function-tool.d.ts +15 -0
- package/dist/esm/tools/function-tool.js +24 -0
- package/dist/esm/tools/function-tool.js.map +1 -0
- package/dist/esm/tools/image-generation-tool.d.ts +11 -0
- package/dist/esm/tools/image-generation-tool.js +27 -0
- package/dist/esm/tools/image-generation-tool.js.map +1 -0
- package/dist/esm/tools/index.d.ts +27 -0
- package/dist/esm/tools/local-shell-tool.d.ts +11 -0
- package/dist/esm/tools/local-shell-tool.js +17 -0
- package/dist/esm/tools/local-shell-tool.js.map +1 -0
- package/dist/esm/tools/mcp-tool.d.ts +12 -0
- package/dist/esm/tools/mcp-tool.js +31 -0
- package/dist/esm/tools/mcp-tool.js.map +1 -0
- package/dist/esm/tools/shell-tool.d.ts +11 -0
- package/dist/esm/tools/shell-tool.js +17 -0
- package/dist/esm/tools/shell-tool.js.map +1 -0
- package/dist/esm/tools/tool-choice.d.ts +17 -0
- package/dist/esm/tools/tool-converter.d.ts +6 -0
- package/dist/esm/tools/tool-converter.js +61 -0
- package/dist/esm/tools/tool-converter.js.map +1 -0
- package/dist/esm/tools/web-search-preview-tool.d.ts +11 -0
- package/dist/esm/tools/web-search-preview-tool.js +20 -0
- package/dist/esm/tools/web-search-preview-tool.js.map +1 -0
- package/dist/esm/tools/web-search-tool.d.ts +11 -0
- package/dist/esm/tools/web-search-tool.js +16 -0
- package/dist/esm/tools/web-search-tool.js.map +1 -0
- package/dist/esm/types/config.d.ts +4 -0
- package/dist/esm/types/message-metadata.d.ts +19 -0
- package/dist/esm/types/provider-options.d.ts +40 -0
- package/dist/esm/utils/client.d.ts +3 -0
- package/dist/esm/utils/client.js +8 -0
- package/dist/esm/utils/client.js.map +1 -0
- package/dist/esm/utils/schema-converter.d.ts +12 -0
- package/dist/esm/utils/schema-converter.js +65 -0
- package/dist/esm/utils/schema-converter.js.map +1 -0
- package/package.json +57 -0
- package/src/adapters/chat-completions-text.ts +817 -0
- package/src/adapters/chat-completions-tool-converter.ts +70 -0
- package/src/adapters/image.ts +158 -0
- package/src/adapters/responses-text.ts +1147 -0
- package/src/adapters/responses-tool-converter.ts +77 -0
- package/src/adapters/summarize.ts +174 -0
- package/src/adapters/transcription.ts +194 -0
- package/src/adapters/tts.ts +124 -0
- package/src/adapters/video.ts +385 -0
- package/src/index.ts +24 -0
- package/src/tools/apply-patch-tool.ts +32 -0
- package/src/tools/code-interpreter-tool.ts +39 -0
- package/src/tools/computer-use-tool.ts +38 -0
- package/src/tools/custom-tool.ts +33 -0
- package/src/tools/file-search-tool.ts +51 -0
- package/src/tools/function-tool.ts +44 -0
- package/src/tools/image-generation-tool.ts +51 -0
- package/src/tools/index.ts +41 -0
- package/src/tools/local-shell-tool.ts +32 -0
- package/src/tools/mcp-tool.ts +47 -0
- package/src/tools/shell-tool.ts +30 -0
- package/src/tools/tool-choice.ts +31 -0
- package/src/tools/tool-converter.ts +68 -0
- package/src/tools/web-search-preview-tool.ts +39 -0
- package/src/tools/web-search-tool.ts +38 -0
- package/src/types/config.ts +5 -0
- package/src/utils/client.ts +8 -0
- package/src/utils/request-options.ts +16 -0
- 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":";;;;;AAiCO,MAAM,mDAMH,gBAKR;AAAA,EAMA,YACE,QACA,OACA,OAAe,qBACf;AACA,UAAM,CAAA,GAAI,KAAK;AAVjB,SAAS,OAAO;AAWd,SAAK,OAAO;AACZ,SAAK,SAAS,6BAA6B,MAAM;AAAA,EACnD;AAAA,EAEA,OAAO,WACL,SAC4B;AAC5B,UAAM,gBAAgB,KAAK,oBAAoB,OAAO;AACtD,UAAM,YAAY,KAAK,IAAA;AAGvB,UAAM,YAAY;AAAA,MAChB,OAAO,WAAW,KAAK,IAAI;AAAA,MAC3B,WAAW,WAAW,KAAK,IAAI;AAAA,MAC/B;AAAA,MACA,sBAAsB;AAAA,IAAA;AAGxB,QAAI;AACF,YAAM,SAAS,MAAM,KAAK,OAAO,KAAK,YAAY;AAAA,QAChD;AAAA,UACE,GAAG;AAAA,UACH,QAAQ;AAAA,UACR,gBAAgB,EAAE,eAAe,KAAA;AAAA,QAAK;AAAA,QAExC;AAAA,UACE,SAAU,QAAQ,SAAqC;AAAA,UAGvD,QAAS,QAAQ,SAAqC;AAAA,QAAA;AAAA,MACxD;AAGF,aAAO,KAAK,oBAAoB,QAAQ,SAAS,SAAS;AAAA,IAC5D,SAAS,OAAgB;AACvB,YAAM,MAAM;AAGZ,UAAI,CAAC,UAAU,sBAAsB;AACnC,kBAAU,uBAAuB;AACjC,cAAM;AAAA,UACJ,MAAM;AAAA,UACN,OAAO,UAAU;AAAA,UACjB,OAAO,QAAQ;AAAA,UACf;AAAA,QAAA;AAAA,MAEJ;AAGA,YAAM;AAAA,QACJ,MAAM;AAAA,QACN,OAAO,UAAU;AAAA,QACjB,OAAO,QAAQ;AAAA,QACf;AAAA,QACA,OAAO;AAAA,UACL,SAAS,IAAI,WAAW;AAAA,UACxB,MAAM,IAAI;AAAA,QAAA;AAAA,MACZ;AAGF,cAAQ;AAAA,QACN,QAAQ,KAAK,IAAI;AAAA,MAAA;AAEnB,cAAQ,MAAM,sBAAsB,IAAI,OAAO;AAC/C,cAAQ,MAAM,oBAAoB,IAAI,KAAK;AAC3C,cAAQ,MAAM,mBAAmB,GAAG;AAAA,IACtC;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAcA,MAAM,iBACJ,SAC0C;AAC1C,UAAM,EAAE,aAAa,aAAA,IAAiB;AACtC,UAAM,gBAAgB,KAAK,oBAAoB,WAAW;AAE1D,UAAM,aAAa,KAAK;AAAA,MACtB;AAAA,MACA,aAAa,YAAY,CAAA;AAAA,IAAC;AAG5B,QAAI;AAEF,YAAM;AAAA,QACJ,gBAAgB;AAAA,QAChB,QAAQ;AAAA,QACR,GAAG;AAAA,MAAA,IACD;AACJ,YAAM,WAAW,MAAM,KAAK,OAAO,KAAK,YAAY;AAAA,QAClD;AAAA,UACE,GAAG;AAAA,UACH,QAAQ;AAAA,UACR,iBAAiB;AAAA,YACf,MAAM;AAAA,YACN,aAAa;AAAA,cACX,MAAM;AAAA,cACN,QAAQ;AAAA,cACR,QAAQ;AAAA,YAAA;AAAA,UACV;AAAA,QACF;AAAA,QAEF;AAAA,UACE,SAAU,YAAY,SAAqC;AAAA,UAG3D,QAAS,YAAY,SAAqC;AAAA,QAAA;AAAA,MAC5D;AAIF,YAAM,UAAU,SAAS,QAAQ,CAAC,GAAG,QAAQ,WAAW;AAGxD,UAAI;AACJ,UAAI;AACF,iBAAS,KAAK,MAAM,OAAO;AAAA,MAC7B,QAAQ;AACN,cAAM,IAAI;AAAA,UACR,uDAAuD,QAAQ,MAAM,GAAG,GAAG,CAAC,GAAG,QAAQ,SAAS,MAAM,QAAQ,EAAE;AAAA,QAAA;AAAA,MAEpH;AAIA,YAAM,cAAc,0BAA0B,MAAM;AAEpD,aAAO;AAAA,QACL,MAAM;AAAA,QACN;AAAA,MAAA;AAAA,IAEJ,SAAS,OAAgB;AACvB,YAAM,MAAM;AACZ,cAAQ;AAAA,QACN,QAAQ,KAAK,IAAI;AAAA,MAAA;AAEnB,cAAQ,MAAM,sBAAsB,IAAI,OAAO;AAC/C,YAAM;AAAA,IACR;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMU,+BACR,QACA,kBACqB;AACrB,WAAO,+BAA+B,QAAQ,gBAAgB;AAAA,EAChE;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,OAAiB,oBACf,QACA,SACA,WAM4B;AAC5B,QAAI,qBAAqB;AACzB,UAAM,YAAY,UAAU;AAC5B,QAAI,6BAA6B;AAGjC,UAAM,0CAA0B,IAAA;AAUhC,QAAI;AACF,uBAAiB,SAAS,QAAQ;AAChC,cAAM,SAAS,MAAM,QAAQ,CAAC;AAE9B,YAAI,CAAC,OAAQ;AAGb,YAAI,CAAC,UAAU,sBAAsB;AACnC,oBAAU,uBAAuB;AACjC,gBAAM;AAAA,YACJ,MAAM;AAAA,YACN,OAAO,UAAU;AAAA,YACjB,OAAO,MAAM,SAAS,QAAQ;AAAA,YAC9B;AAAA,UAAA;AAAA,QAEJ;AAEA,cAAM,QAAQ,OAAO;AACrB,cAAM,eAAe,MAAM;AAC3B,cAAM,iBAAiB,MAAM;AAG7B,YAAI,cAAc;AAEhB,cAAI,CAAC,4BAA4B;AAC/B,yCAA6B;AAC7B,kBAAM;AAAA,cACJ,MAAM;AAAA,cACN,WAAW,UAAU;AAAA,cACrB,OAAO,MAAM,SAAS,QAAQ;AAAA,cAC9B;AAAA,cACA,MAAM;AAAA,YAAA;AAAA,UAEV;AAEA,gCAAsB;AAGtB,gBAAM;AAAA,YACJ,MAAM;AAAA,YACN,WAAW,UAAU;AAAA,YACrB,OAAO,MAAM,SAAS,QAAQ;AAAA,YAC9B;AAAA,YACA,OAAO;AAAA,YACP,SAAS;AAAA,UAAA;AAAA,QAEb;AAGA,YAAI,gBAAgB;AAClB,qBAAW,iBAAiB,gBAAgB;AAC1C,kBAAM,QAAQ,cAAc;AAG5B,gBAAI,CAAC,oBAAoB,IAAI,KAAK,GAAG;AACnC,kCAAoB,IAAI,OAAO;AAAA,gBAC7B,IAAI,cAAc,MAAM;AAAA,gBACxB,MAAM,cAAc,UAAU,QAAQ;AAAA,gBACtC,WAAW;AAAA,gBACX,SAAS;AAAA,cAAA,CACV;AAAA,YACH;AAEA,kBAAM,WAAW,oBAAoB,IAAI,KAAK;AAG9C,gBAAI,cAAc,IAAI;AACpB,uBAAS,KAAK,cAAc;AAAA,YAC9B;AACA,gBAAI,cAAc,UAAU,MAAM;AAChC,uBAAS,OAAO,cAAc,SAAS;AAAA,YACzC;AACA,gBAAI,cAAc,UAAU,WAAW;AACrC,uBAAS,aAAa,cAAc,SAAS;AAAA,YAC/C;AAGA,gBAAI,SAAS,MAAM,SAAS,QAAQ,CAAC,SAAS,SAAS;AACrD,uBAAS,UAAU;AACnB,oBAAM;AAAA,gBACJ,MAAM;AAAA,gBACN,YAAY,SAAS;AAAA,gBACrB,UAAU,SAAS;AAAA,gBACnB,OAAO,MAAM,SAAS,QAAQ;AAAA,gBAC9B;AAAA,gBACA;AAAA,cAAA;AAAA,YAEJ;AAGA,gBAAI,cAAc,UAAU,aAAa,SAAS,SAAS;AACzD,oBAAM;AAAA,gBACJ,MAAM;AAAA,gBACN,YAAY,SAAS;AAAA,gBACrB,OAAO,MAAM,SAAS,QAAQ;AAAA,gBAC9B;AAAA,gBACA,OAAO,cAAc,SAAS;AAAA,cAAA;AAAA,YAElC;AAAA,UACF;AAAA,QACF;AAGA,YAAI,OAAO,eAAe;AAExB,cACE,OAAO,kBAAkB,gBACzB,oBAAoB,OAAO,GAC3B;AACA,uBAAW,CAAA,EAAG,QAAQ,KAAK,qBAAqB;AAE9C,kBAAI,cAAuB,CAAA;AAC3B,kBAAI;AACF,8BAAc,SAAS,YACnB,KAAK,MAAM,SAAS,SAAS,IAC7B,CAAA;AAAA,cACN,QAAQ;AACN,8BAAc,CAAA;AAAA,cAChB;AAGA,oBAAM;AAAA,gBACJ,MAAM;AAAA,gBACN,YAAY,SAAS;AAAA,gBACrB,UAAU,SAAS;AAAA,gBACnB,OAAO,MAAM,SAAS,QAAQ;AAAA,gBAC9B;AAAA,gBACA,OAAO;AAAA,cAAA;AAAA,YAEX;AAAA,UACF;AAEA,gBAAM,uBACJ,OAAO,kBAAkB,gBACzB,oBAAoB,OAAO,IACvB,eACA;AAGN,cAAI,4BAA4B;AAC9B,kBAAM;AAAA,cACJ,MAAM;AAAA,cACN,WAAW,UAAU;AAAA,cACrB,OAAO,MAAM,SAAS,QAAQ;AAAA,cAC9B;AAAA,YAAA;AAAA,UAEJ;AAGA,gBAAM;AAAA,YACJ,MAAM;AAAA,YACN,OAAO,UAAU;AAAA,YACjB,OAAO,MAAM,SAAS,QAAQ;AAAA,YAC9B;AAAA,YACA,OAAO,MAAM,QACT;AAAA,cACE,cAAc,MAAM,MAAM,iBAAiB;AAAA,cAC3C,kBAAkB,MAAM,MAAM,qBAAqB;AAAA,cACnD,aAAa,MAAM,MAAM,gBAAgB;AAAA,YAAA,IAE3C;AAAA,YACJ,cAAc;AAAA,UAAA;AAAA,QAElB;AAAA,MACF;AAAA,IACF,SAAS,OAAgB;AACvB,YAAM,MAAM;AACZ,cAAQ,MAAM,IAAI,KAAK,IAAI,8BAA8B,IAAI,OAAO;AAGpE,YAAM;AAAA,QACJ,MAAM;AAAA,QACN,OAAO,UAAU;AAAA,QACjB,OAAO,QAAQ;AAAA,QACf;AAAA,QACA,OAAO;AAAA,UACL,SAAS,IAAI,WAAW;AAAA,UACxB,MAAM,IAAI;AAAA,QAAA;AAAA,MACZ;AAAA,IAEJ;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMU,oBACR,SACiE;AACjE,UAAM,QAAQ,QAAQ,QAClB;AAAA,MACE,QAAQ;AAAA,MACR,KAAK,+BAA+B,KAAK,IAAI;AAAA,IAAA,IAE/C;AAGJ,UAAM,WACJ,CAAA;AAGF,QAAI,QAAQ,iBAAiB,QAAQ,cAAc,SAAS,GAAG;AAC7D,eAAS,KAAK;AAAA,QACZ,MAAM;AAAA,QACN,SAAS,QAAQ,cAAc,KAAK,IAAI;AAAA,MAAA,CACzC;AAAA,IACH;AAGA,eAAW,WAAW,QAAQ,UAAU;AACtC,eAAS,KAAK,KAAK,eAAe,OAAO,CAAC;AAAA,IAC5C;AAEA,UAAM,eAAe,QAAQ;AAE7B,WAAO;AAAA,MACL,GAAG;AAAA,MACH,OAAO,QAAQ;AAAA,MACf;AAAA,MACA,aAAa,QAAQ;AAAA,MACrB,YAAY,QAAQ;AAAA,MACpB,OAAO,QAAQ;AAAA,MACf;AAAA,MACA,QAAQ;AAAA,IAAA;AAAA,EAEZ;AAAA;AAAA;AAAA;AAAA;AAAA,EAMU,eACR,SACwD;AAExD,QAAI,QAAQ,SAAS,QAAQ;AAC3B,aAAO;AAAA,QACL,MAAM;AAAA,QACN,cAAc,QAAQ,cAAc;AAAA,QACpC,SACE,OAAO,QAAQ,YAAY,WACvB,QAAQ,UACR,KAAK,UAAU,QAAQ,OAAO;AAAA,MAAA;AAAA,IAExC;AAGA,QAAI,QAAQ,SAAS,aAAa;AAChC,YAAM,YAAY,QAAQ,WAAW,IAAI,CAAC,QAAQ;AAAA,QAChD,IAAI,GAAG;AAAA,QACP,MAAM;AAAA,QACN,UAAU;AAAA,UACR,MAAM,GAAG,SAAS;AAAA,UAClB,WACE,OAAO,GAAG,SAAS,cAAc,WAC7B,GAAG,SAAS,YACZ,KAAK,UAAU,GAAG,SAAS,SAAS;AAAA,QAAA;AAAA,MAC5C,EACA;AAEF,aAAO;AAAA,QACL,MAAM;AAAA,QACN,SAAS,KAAK,mBAAmB,QAAQ,OAAO;AAAA,QAChD,GAAI,aAAa,UAAU,SAAS,IAAI,EAAE,YAAY,cAAc,CAAA;AAAA,MAAC;AAAA,IAEzE;AAGA,UAAM,eAAe,KAAK,iBAAiB,QAAQ,OAAO;AAG1D,QAAI,aAAa,WAAW,KAAK,aAAa,CAAC,GAAG,SAAS,QAAQ;AACjE,aAAO;AAAA,QACL,MAAM;AAAA,QACN,SAAS,aAAa,CAAC,EAAE;AAAA,MAAA;AAAA,IAE7B;AAGA,UAAM,QACJ,CAAA;AACF,eAAW,QAAQ,cAAc;AAC/B,YAAM,YAAY,KAAK,mBAAmB,IAAI;AAC9C,UAAI,WAAW;AACb,cAAM,KAAK,SAAS;AAAA,MACtB;AAAA,IACF;AAEA,WAAO;AAAA,MACL,MAAM;AAAA,MACN,SAAS,MAAM,SAAS,IAAI,QAAQ;AAAA,IAAA;AAAA,EAExC;AAAA;AAAA;AAAA;AAAA;AAAA,EAMU,mBACR,MAC8D;AAC9D,QAAI,KAAK,SAAS,QAAQ;AACxB,aAAO,EAAE,MAAM,QAAQ,MAAM,KAAK,QAAA;AAAA,IACpC;AAEA,QAAI,KAAK,SAAS,SAAS;AACzB,YAAM,gBAAgB,KAAK;AAK3B,YAAM,aAAa,KAAK,OAAO;AAC/B,YAAM,WACJ,KAAK,OAAO,SAAS,UAAU,CAAC,WAAW,WAAW,OAAO,IACzD,QAAQ,KAAK,OAAO,QAAQ,WAAW,UAAU,KACjD;AAEN,aAAO;AAAA,QACL,MAAM;AAAA,QACN,WAAW;AAAA,UACT,KAAK;AAAA,UACL,QAAQ,eAAe,UAAU;AAAA,QAAA;AAAA,MACnC;AAAA,IAEJ;AAGA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA,EAMU,iBACR,SACoB;AACpB,QAAI,YAAY,MAAM;AACpB,aAAO,CAAA;AAAA,IACT;AACA,QAAI,OAAO,YAAY,UAAU;AAC/B,aAAO,CAAC,EAAE,MAAM,QAAQ,SAAkB;AAAA,IAC5C;AACA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKU,mBACR,SACQ;AACR,QAAI,YAAY,MAAM;AACpB,aAAO;AAAA,IACT;AACA,QAAI,OAAO,YAAY,UAAU;AAC/B,aAAO;AAAA,IACT;AAEA,WAAO,QACJ,OAAO,CAAC,MAAM,EAAE,SAAS,MAAM,EAC/B,IAAI,CAAC,MAAM,EAAE,OAAO,EACpB,KAAK,EAAE;AAAA,EACZ;AACF;"}
|
|
@@ -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
|
+
}
|