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