@tanstack/openai-base 0.1.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/dist/esm/adapters/chat-completions-text.d.ts +2 -2
- package/dist/esm/adapters/chat-completions-text.js +183 -80
- package/dist/esm/adapters/chat-completions-text.js.map +1 -1
- package/dist/esm/adapters/chat-completions-tool-converter.js +3 -1
- package/dist/esm/adapters/chat-completions-tool-converter.js.map +1 -1
- package/dist/esm/adapters/image.js +33 -13
- package/dist/esm/adapters/image.js.map +1 -1
- package/dist/esm/adapters/responses-text.d.ts +4 -5
- package/dist/esm/adapters/responses-text.js +260 -135
- package/dist/esm/adapters/responses-text.js.map +1 -1
- package/dist/esm/adapters/responses-tool-converter.js +3 -1
- package/dist/esm/adapters/responses-tool-converter.js.map +1 -1
- package/dist/esm/adapters/summarize.js +63 -25
- package/dist/esm/adapters/summarize.js.map +1 -1
- package/dist/esm/adapters/transcription.d.ts +0 -5
- package/dist/esm/adapters/transcription.js +53 -61
- package/dist/esm/adapters/transcription.js.map +1 -1
- package/dist/esm/adapters/tts.js +27 -14
- package/dist/esm/adapters/tts.js.map +1 -1
- package/dist/esm/adapters/video.d.ts +24 -0
- package/dist/esm/adapters/video.js +72 -26
- package/dist/esm/adapters/video.js.map +1 -1
- package/dist/esm/index.d.ts +0 -2
- package/dist/esm/index.js +4 -2
- package/dist/esm/tools/apply-patch-tool.d.ts +8 -3
- package/dist/esm/tools/apply-patch-tool.js.map +1 -1
- package/dist/esm/tools/code-interpreter-tool.d.ts +9 -4
- package/dist/esm/tools/code-interpreter-tool.js.map +1 -1
- package/dist/esm/tools/computer-use-tool.d.ts +9 -4
- package/dist/esm/tools/computer-use-tool.js.map +1 -1
- package/dist/esm/tools/custom-tool.d.ts +6 -4
- package/dist/esm/tools/custom-tool.js.map +1 -1
- package/dist/esm/tools/file-search-tool.d.ts +11 -4
- package/dist/esm/tools/file-search-tool.js +3 -2
- package/dist/esm/tools/file-search-tool.js.map +1 -1
- package/dist/esm/tools/function-tool.d.ts +4 -2
- package/dist/esm/tools/function-tool.js.map +1 -1
- package/dist/esm/tools/image-generation-tool.d.ts +16 -5
- package/dist/esm/tools/image-generation-tool.js +4 -3
- package/dist/esm/tools/image-generation-tool.js.map +1 -1
- package/dist/esm/tools/index.d.ts +13 -13
- package/dist/esm/tools/local-shell-tool.d.ts +8 -3
- package/dist/esm/tools/local-shell-tool.js.map +1 -1
- package/dist/esm/tools/mcp-tool.d.ts +10 -5
- package/dist/esm/tools/mcp-tool.js.map +1 -1
- package/dist/esm/tools/shell-tool.d.ts +8 -3
- package/dist/esm/tools/shell-tool.js.map +1 -1
- package/dist/esm/tools/web-search-preview-tool.d.ts +13 -5
- package/dist/esm/tools/web-search-preview-tool.js +2 -3
- package/dist/esm/tools/web-search-preview-tool.js.map +1 -1
- package/dist/esm/tools/web-search-tool.d.ts +14 -5
- package/dist/esm/tools/web-search-tool.js +4 -1
- package/dist/esm/tools/web-search-tool.js.map +1 -1
- package/dist/esm/utils/request-options.d.ts +14 -0
- package/dist/esm/utils/request-options.js +8 -0
- package/dist/esm/utils/request-options.js.map +1 -0
- package/package.json +16 -19
- package/dist/esm/types/message-metadata.d.ts +0 -19
- package/dist/esm/types/provider-options.d.ts +0 -40
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import { BaseTextAdapter } from "@tanstack/ai/adapters";
|
|
2
|
+
import { toRunErrorPayload } from "@tanstack/ai/adapter-internals";
|
|
2
3
|
import { generateId, transformNullsToUndefined } from "@tanstack/ai-utils";
|
|
3
4
|
import { createOpenAICompatibleClient } from "../utils/client.js";
|
|
5
|
+
import { extractRequestOptions } from "../utils/request-options.js";
|
|
4
6
|
import { makeStructuredOutputCompatible } from "../utils/schema-converter.js";
|
|
5
7
|
import { convertToolsToResponsesFormat } from "./responses-tool-converter.js";
|
|
8
|
+
const asChunk = (chunk) => chunk;
|
|
6
9
|
class OpenAICompatibleResponsesTextAdapter extends BaseTextAdapter {
|
|
7
10
|
constructor(config, model, name = "openai-compatible-responses") {
|
|
8
11
|
super({}, model);
|
|
@@ -21,15 +24,16 @@ class OpenAICompatibleResponsesTextAdapter extends BaseTextAdapter {
|
|
|
21
24
|
hasEmittedRunStarted: false
|
|
22
25
|
};
|
|
23
26
|
try {
|
|
27
|
+
options.logger.request(
|
|
28
|
+
`activity=chat provider=${this.name} model=${this.model} messages=${options.messages.length} tools=${options.tools?.length ?? 0} stream=true`,
|
|
29
|
+
{ provider: this.name, model: this.model }
|
|
30
|
+
);
|
|
24
31
|
const response = await this.client.responses.create(
|
|
25
32
|
{
|
|
26
33
|
...requestParams,
|
|
27
34
|
stream: true
|
|
28
35
|
},
|
|
29
|
-
|
|
30
|
-
headers: options.request?.headers,
|
|
31
|
-
signal: options.request?.signal
|
|
32
|
-
}
|
|
36
|
+
extractRequestOptions(options.request)
|
|
33
37
|
);
|
|
34
38
|
yield* this.processStreamChunks(
|
|
35
39
|
response,
|
|
@@ -38,32 +42,30 @@ class OpenAICompatibleResponsesTextAdapter extends BaseTextAdapter {
|
|
|
38
42
|
aguiState
|
|
39
43
|
);
|
|
40
44
|
} catch (error) {
|
|
41
|
-
const
|
|
45
|
+
const errorPayload = toRunErrorPayload(
|
|
46
|
+
error,
|
|
47
|
+
`${this.name}.chatStream failed`
|
|
48
|
+
);
|
|
42
49
|
if (!aguiState.hasEmittedRunStarted) {
|
|
43
50
|
aguiState.hasEmittedRunStarted = true;
|
|
44
|
-
yield {
|
|
51
|
+
yield asChunk({
|
|
45
52
|
type: "RUN_STARTED",
|
|
46
53
|
runId: aguiState.runId,
|
|
47
54
|
model: options.model,
|
|
48
55
|
timestamp
|
|
49
|
-
};
|
|
56
|
+
});
|
|
50
57
|
}
|
|
51
|
-
yield {
|
|
58
|
+
yield asChunk({
|
|
52
59
|
type: "RUN_ERROR",
|
|
53
60
|
runId: aguiState.runId,
|
|
54
61
|
model: options.model,
|
|
55
62
|
timestamp,
|
|
56
|
-
error:
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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);
|
|
63
|
+
error: errorPayload
|
|
64
|
+
});
|
|
65
|
+
options.logger.errors(`${this.name}.chatStream fatal`, {
|
|
66
|
+
error: errorPayload,
|
|
67
|
+
source: `${this.name}.chatStream`
|
|
68
|
+
});
|
|
67
69
|
}
|
|
68
70
|
}
|
|
69
71
|
/**
|
|
@@ -83,12 +85,23 @@ class OpenAICompatibleResponsesTextAdapter extends BaseTextAdapter {
|
|
|
83
85
|
const requestParams = this.mapOptionsToRequest(chatOptions);
|
|
84
86
|
const jsonSchema = this.makeStructuredOutputCompatible(
|
|
85
87
|
outputSchema,
|
|
86
|
-
outputSchema.required
|
|
88
|
+
outputSchema.required
|
|
87
89
|
);
|
|
88
90
|
try {
|
|
91
|
+
const {
|
|
92
|
+
stream: _stream,
|
|
93
|
+
stream_options: _streamOptions,
|
|
94
|
+
...cleanParams
|
|
95
|
+
} = requestParams;
|
|
96
|
+
void _stream;
|
|
97
|
+
void _streamOptions;
|
|
98
|
+
chatOptions.logger.request(
|
|
99
|
+
`activity=structuredOutput provider=${this.name} model=${this.model} messages=${chatOptions.messages.length}`,
|
|
100
|
+
{ provider: this.name, model: this.model }
|
|
101
|
+
);
|
|
89
102
|
const response = await this.client.responses.create(
|
|
90
103
|
{
|
|
91
|
-
...
|
|
104
|
+
...cleanParams,
|
|
92
105
|
stream: false,
|
|
93
106
|
// Configure structured output via text.format
|
|
94
107
|
text: {
|
|
@@ -100,10 +113,7 @@ class OpenAICompatibleResponsesTextAdapter extends BaseTextAdapter {
|
|
|
100
113
|
}
|
|
101
114
|
}
|
|
102
115
|
},
|
|
103
|
-
|
|
104
|
-
headers: chatOptions.request?.headers,
|
|
105
|
-
signal: chatOptions.request?.signal
|
|
106
|
-
}
|
|
116
|
+
extractRequestOptions(chatOptions.request)
|
|
107
117
|
);
|
|
108
118
|
const rawText = this.extractTextFromResponse(
|
|
109
119
|
response
|
|
@@ -122,11 +132,10 @@ class OpenAICompatibleResponsesTextAdapter extends BaseTextAdapter {
|
|
|
122
132
|
rawText
|
|
123
133
|
};
|
|
124
134
|
} catch (error) {
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
);
|
|
129
|
-
console.error(">>> Error message:", err.message);
|
|
135
|
+
chatOptions.logger.errors(`${this.name}.structuredOutput fatal`, {
|
|
136
|
+
error: toRunErrorPayload(error, `${this.name}.structuredOutput failed`),
|
|
137
|
+
source: `${this.name}.structuredOutput`
|
|
138
|
+
});
|
|
130
139
|
throw error;
|
|
131
140
|
}
|
|
132
141
|
}
|
|
@@ -143,15 +152,23 @@ class OpenAICompatibleResponsesTextAdapter extends BaseTextAdapter {
|
|
|
143
152
|
*/
|
|
144
153
|
extractTextFromResponse(response) {
|
|
145
154
|
let textContent = "";
|
|
155
|
+
let refusal;
|
|
146
156
|
for (const item of response.output) {
|
|
147
157
|
if (item.type === "message") {
|
|
148
158
|
for (const part of item.content) {
|
|
149
159
|
if (part.type === "output_text") {
|
|
150
160
|
textContent += part.text;
|
|
161
|
+
} else {
|
|
162
|
+
refusal = part.refusal || refusal || "Refused without explanation";
|
|
151
163
|
}
|
|
152
164
|
}
|
|
153
165
|
}
|
|
154
166
|
}
|
|
167
|
+
if (!textContent && refusal !== void 0) {
|
|
168
|
+
const err = new Error(`Model refused to respond: ${refusal}`);
|
|
169
|
+
err.code = "refusal";
|
|
170
|
+
throw err;
|
|
171
|
+
}
|
|
155
172
|
return textContent;
|
|
156
173
|
}
|
|
157
174
|
/**
|
|
@@ -173,110 +190,126 @@ class OpenAICompatibleResponsesTextAdapter extends BaseTextAdapter {
|
|
|
173
190
|
let accumulatedContent = "";
|
|
174
191
|
let accumulatedReasoning = "";
|
|
175
192
|
const timestamp = aguiState.timestamp;
|
|
176
|
-
let chunkCount = 0;
|
|
177
193
|
let hasStreamedContentDeltas = false;
|
|
178
194
|
let hasStreamedReasoningDeltas = false;
|
|
179
195
|
let model = options.model;
|
|
180
196
|
let stepId = null;
|
|
181
197
|
let hasEmittedTextMessageStart = false;
|
|
182
198
|
let hasEmittedStepStarted = false;
|
|
199
|
+
let runFinishedEmitted = false;
|
|
183
200
|
try {
|
|
184
201
|
for await (const chunk of stream) {
|
|
185
|
-
|
|
202
|
+
options.logger.provider(`provider=${this.name} type=${chunk.type}`, {
|
|
203
|
+
provider: this.name,
|
|
204
|
+
type: chunk.type
|
|
205
|
+
});
|
|
186
206
|
if (!aguiState.hasEmittedRunStarted) {
|
|
187
207
|
aguiState.hasEmittedRunStarted = true;
|
|
188
|
-
yield {
|
|
208
|
+
yield asChunk({
|
|
189
209
|
type: "RUN_STARTED",
|
|
190
210
|
runId: aguiState.runId,
|
|
191
211
|
model: model || options.model,
|
|
192
212
|
timestamp
|
|
193
|
-
};
|
|
213
|
+
});
|
|
194
214
|
}
|
|
195
215
|
const handleContentPart = (contentPart) => {
|
|
196
216
|
if (contentPart.type === "output_text") {
|
|
197
217
|
accumulatedContent += contentPart.text || "";
|
|
198
|
-
return {
|
|
218
|
+
return asChunk({
|
|
199
219
|
type: "TEXT_MESSAGE_CONTENT",
|
|
200
220
|
messageId: aguiState.messageId,
|
|
201
221
|
model: model || options.model,
|
|
202
222
|
timestamp,
|
|
203
223
|
delta: contentPart.text || "",
|
|
204
224
|
content: accumulatedContent
|
|
205
|
-
};
|
|
225
|
+
});
|
|
206
226
|
}
|
|
207
227
|
if (contentPart.type === "reasoning_text") {
|
|
208
228
|
accumulatedReasoning += contentPart.text || "";
|
|
209
|
-
|
|
229
|
+
if (!stepId) {
|
|
230
|
+
stepId = generateId(this.name);
|
|
231
|
+
}
|
|
232
|
+
return asChunk({
|
|
210
233
|
type: "STEP_FINISHED",
|
|
211
|
-
stepId
|
|
234
|
+
stepId,
|
|
212
235
|
model: model || options.model,
|
|
213
236
|
timestamp,
|
|
214
237
|
delta: contentPart.text || "",
|
|
215
238
|
content: accumulatedReasoning
|
|
216
|
-
};
|
|
239
|
+
});
|
|
217
240
|
}
|
|
218
|
-
|
|
241
|
+
const isRefusal = contentPart.type === "refusal";
|
|
242
|
+
const message = isRefusal ? contentPart.refusal || "Refused without explanation" : `Unsupported response content_part type: ${contentPart.type}`;
|
|
243
|
+
return asChunk({
|
|
219
244
|
type: "RUN_ERROR",
|
|
220
245
|
runId: aguiState.runId,
|
|
221
246
|
model: model || options.model,
|
|
222
247
|
timestamp,
|
|
223
248
|
error: {
|
|
224
|
-
message
|
|
249
|
+
message,
|
|
250
|
+
code: isRefusal ? "refusal" : contentPart.type
|
|
225
251
|
}
|
|
226
|
-
};
|
|
252
|
+
});
|
|
227
253
|
};
|
|
228
254
|
if (chunk.type === "response.created" || chunk.type === "response.incomplete" || chunk.type === "response.failed") {
|
|
229
255
|
model = chunk.response.model;
|
|
256
|
+
}
|
|
257
|
+
if (chunk.type === "response.created") {
|
|
230
258
|
hasStreamedContentDeltas = false;
|
|
231
259
|
hasStreamedReasoningDeltas = false;
|
|
232
260
|
hasEmittedTextMessageStart = false;
|
|
233
261
|
hasEmittedStepStarted = false;
|
|
234
262
|
accumulatedContent = "";
|
|
235
263
|
accumulatedReasoning = "";
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
error: chunk.response.error
|
|
243
|
-
};
|
|
244
|
-
}
|
|
245
|
-
if (chunk.response.incomplete_details) {
|
|
246
|
-
yield {
|
|
247
|
-
type: "RUN_ERROR",
|
|
248
|
-
runId: aguiState.runId,
|
|
264
|
+
}
|
|
265
|
+
if (chunk.type === "response.failed" || chunk.type === "response.incomplete") {
|
|
266
|
+
if (hasEmittedTextMessageStart) {
|
|
267
|
+
yield asChunk({
|
|
268
|
+
type: "TEXT_MESSAGE_END",
|
|
269
|
+
messageId: aguiState.messageId,
|
|
249
270
|
model: chunk.response.model,
|
|
250
|
-
timestamp
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
}
|
|
254
|
-
};
|
|
271
|
+
timestamp
|
|
272
|
+
});
|
|
273
|
+
hasEmittedTextMessageStart = false;
|
|
255
274
|
}
|
|
275
|
+
const errorMessage = chunk.response.error?.message || chunk.response.incomplete_details?.reason || (chunk.type === "response.failed" ? "Response failed" : "Response ended incomplete");
|
|
276
|
+
const errorCode = chunk.response.error?.code || (chunk.response.incomplete_details ? "incomplete" : void 0);
|
|
277
|
+
yield asChunk({
|
|
278
|
+
type: "RUN_ERROR",
|
|
279
|
+
runId: aguiState.runId,
|
|
280
|
+
model: chunk.response.model,
|
|
281
|
+
timestamp,
|
|
282
|
+
error: {
|
|
283
|
+
message: errorMessage,
|
|
284
|
+
...errorCode !== void 0 && { code: errorCode }
|
|
285
|
+
}
|
|
286
|
+
});
|
|
287
|
+
runFinishedEmitted = true;
|
|
288
|
+
return;
|
|
256
289
|
}
|
|
257
290
|
if (chunk.type === "response.output_text.delta" && chunk.delta) {
|
|
258
291
|
const textDelta = Array.isArray(chunk.delta) ? chunk.delta.join("") : typeof chunk.delta === "string" ? chunk.delta : "";
|
|
259
292
|
if (textDelta) {
|
|
260
293
|
if (!hasEmittedTextMessageStart) {
|
|
261
294
|
hasEmittedTextMessageStart = true;
|
|
262
|
-
yield {
|
|
295
|
+
yield asChunk({
|
|
263
296
|
type: "TEXT_MESSAGE_START",
|
|
264
297
|
messageId: aguiState.messageId,
|
|
265
298
|
model: model || options.model,
|
|
266
299
|
timestamp,
|
|
267
300
|
role: "assistant"
|
|
268
|
-
};
|
|
301
|
+
});
|
|
269
302
|
}
|
|
270
303
|
accumulatedContent += textDelta;
|
|
271
304
|
hasStreamedContentDeltas = true;
|
|
272
|
-
yield {
|
|
305
|
+
yield asChunk({
|
|
273
306
|
type: "TEXT_MESSAGE_CONTENT",
|
|
274
307
|
messageId: aguiState.messageId,
|
|
275
308
|
model: model || options.model,
|
|
276
309
|
timestamp,
|
|
277
310
|
delta: textDelta,
|
|
278
311
|
content: accumulatedContent
|
|
279
|
-
};
|
|
312
|
+
});
|
|
280
313
|
}
|
|
281
314
|
}
|
|
282
315
|
if (chunk.type === "response.reasoning_text.delta" && chunk.delta) {
|
|
@@ -285,24 +318,24 @@ class OpenAICompatibleResponsesTextAdapter extends BaseTextAdapter {
|
|
|
285
318
|
if (!hasEmittedStepStarted) {
|
|
286
319
|
hasEmittedStepStarted = true;
|
|
287
320
|
stepId = generateId(this.name);
|
|
288
|
-
yield {
|
|
321
|
+
yield asChunk({
|
|
289
322
|
type: "STEP_STARTED",
|
|
290
323
|
stepId,
|
|
291
324
|
model: model || options.model,
|
|
292
325
|
timestamp,
|
|
293
326
|
stepType: "thinking"
|
|
294
|
-
};
|
|
327
|
+
});
|
|
295
328
|
}
|
|
296
329
|
accumulatedReasoning += reasoningDelta;
|
|
297
330
|
hasStreamedReasoningDeltas = true;
|
|
298
|
-
yield {
|
|
331
|
+
yield asChunk({
|
|
299
332
|
type: "STEP_FINISHED",
|
|
300
333
|
stepId: stepId || generateId(this.name),
|
|
301
334
|
model: model || options.model,
|
|
302
335
|
timestamp,
|
|
303
336
|
delta: reasoningDelta,
|
|
304
337
|
content: accumulatedReasoning
|
|
305
|
-
};
|
|
338
|
+
});
|
|
306
339
|
}
|
|
307
340
|
}
|
|
308
341
|
if (chunk.type === "response.reasoning_summary_text.delta" && chunk.delta) {
|
|
@@ -311,50 +344,60 @@ class OpenAICompatibleResponsesTextAdapter extends BaseTextAdapter {
|
|
|
311
344
|
if (!hasEmittedStepStarted) {
|
|
312
345
|
hasEmittedStepStarted = true;
|
|
313
346
|
stepId = generateId(this.name);
|
|
314
|
-
yield {
|
|
347
|
+
yield asChunk({
|
|
315
348
|
type: "STEP_STARTED",
|
|
316
349
|
stepId,
|
|
317
350
|
model: model || options.model,
|
|
318
351
|
timestamp,
|
|
319
352
|
stepType: "thinking"
|
|
320
|
-
};
|
|
353
|
+
});
|
|
321
354
|
}
|
|
322
355
|
accumulatedReasoning += summaryDelta;
|
|
323
356
|
hasStreamedReasoningDeltas = true;
|
|
324
|
-
yield {
|
|
357
|
+
yield asChunk({
|
|
325
358
|
type: "STEP_FINISHED",
|
|
326
359
|
stepId: stepId || generateId(this.name),
|
|
327
360
|
model: model || options.model,
|
|
328
361
|
timestamp,
|
|
329
362
|
delta: summaryDelta,
|
|
330
363
|
content: accumulatedReasoning
|
|
331
|
-
};
|
|
364
|
+
});
|
|
332
365
|
}
|
|
333
366
|
}
|
|
334
367
|
if (chunk.type === "response.content_part.added") {
|
|
335
368
|
const contentPart = chunk.part;
|
|
336
369
|
if (contentPart.type === "output_text" && !hasEmittedTextMessageStart) {
|
|
337
370
|
hasEmittedTextMessageStart = true;
|
|
338
|
-
yield {
|
|
371
|
+
yield asChunk({
|
|
339
372
|
type: "TEXT_MESSAGE_START",
|
|
340
373
|
messageId: aguiState.messageId,
|
|
341
374
|
model: model || options.model,
|
|
342
375
|
timestamp,
|
|
343
376
|
role: "assistant"
|
|
344
|
-
};
|
|
377
|
+
});
|
|
345
378
|
}
|
|
346
379
|
if (contentPart.type === "reasoning_text" && !hasEmittedStepStarted) {
|
|
347
380
|
hasEmittedStepStarted = true;
|
|
348
381
|
stepId = generateId(this.name);
|
|
349
|
-
yield {
|
|
382
|
+
yield asChunk({
|
|
350
383
|
type: "STEP_STARTED",
|
|
351
384
|
stepId,
|
|
352
385
|
model: model || options.model,
|
|
353
386
|
timestamp,
|
|
354
387
|
stepType: "thinking"
|
|
355
|
-
};
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
if (contentPart.type === "output_text") {
|
|
391
|
+
hasStreamedContentDeltas = true;
|
|
392
|
+
} else if (contentPart.type === "reasoning_text") {
|
|
393
|
+
hasStreamedReasoningDeltas = true;
|
|
394
|
+
}
|
|
395
|
+
const partChunk = handleContentPart(contentPart);
|
|
396
|
+
yield partChunk;
|
|
397
|
+
if (partChunk.type === "RUN_ERROR") {
|
|
398
|
+
runFinishedEmitted = true;
|
|
399
|
+
return;
|
|
356
400
|
}
|
|
357
|
-
yield handleContentPart(contentPart);
|
|
358
401
|
}
|
|
359
402
|
if (chunk.type === "response.content_part.done") {
|
|
360
403
|
const contentPart = chunk.part;
|
|
@@ -364,76 +407,121 @@ class OpenAICompatibleResponsesTextAdapter extends BaseTextAdapter {
|
|
|
364
407
|
if (contentPart.type === "reasoning_text" && hasStreamedReasoningDeltas) {
|
|
365
408
|
continue;
|
|
366
409
|
}
|
|
367
|
-
|
|
410
|
+
const doneChunk = handleContentPart(contentPart);
|
|
411
|
+
yield doneChunk;
|
|
412
|
+
if (doneChunk.type === "RUN_ERROR") {
|
|
413
|
+
runFinishedEmitted = true;
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
368
416
|
}
|
|
369
417
|
if (chunk.type === "response.output_item.added") {
|
|
370
418
|
const item = chunk.item;
|
|
371
|
-
if (item.type === "function_call") {
|
|
372
|
-
const
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
419
|
+
if (item.type === "function_call" && item.id) {
|
|
420
|
+
const existing = toolCallMetadata.get(item.id);
|
|
421
|
+
if (!existing?.started) {
|
|
422
|
+
if (!existing) {
|
|
423
|
+
toolCallMetadata.set(item.id, {
|
|
424
|
+
index: chunk.output_index,
|
|
425
|
+
name: item.name || "",
|
|
426
|
+
started: false
|
|
427
|
+
});
|
|
428
|
+
}
|
|
429
|
+
yield asChunk({
|
|
430
|
+
type: "TOOL_CALL_START",
|
|
431
|
+
toolCallId: item.id,
|
|
432
|
+
toolCallName: item.name || "",
|
|
433
|
+
toolName: item.name || "",
|
|
434
|
+
model: model || options.model,
|
|
435
|
+
timestamp,
|
|
436
|
+
index: chunk.output_index
|
|
380
437
|
});
|
|
438
|
+
toolCallMetadata.get(item.id).started = true;
|
|
381
439
|
}
|
|
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
440
|
}
|
|
392
441
|
}
|
|
393
442
|
if (chunk.type === "response.function_call_arguments.delta" && chunk.delta) {
|
|
394
443
|
const metadata = toolCallMetadata.get(chunk.item_id);
|
|
395
|
-
|
|
444
|
+
if (!metadata?.started) {
|
|
445
|
+
options.logger.errors(
|
|
446
|
+
`${this.name}.processStreamChunks orphan function_call_arguments.delta`,
|
|
447
|
+
{
|
|
448
|
+
source: `${this.name}.processStreamChunks`,
|
|
449
|
+
toolCallId: chunk.item_id,
|
|
450
|
+
rawDelta: chunk.delta
|
|
451
|
+
}
|
|
452
|
+
);
|
|
453
|
+
continue;
|
|
454
|
+
}
|
|
455
|
+
yield asChunk({
|
|
396
456
|
type: "TOOL_CALL_ARGS",
|
|
397
|
-
toolCallId:
|
|
457
|
+
toolCallId: chunk.item_id,
|
|
398
458
|
model: model || options.model,
|
|
399
459
|
timestamp,
|
|
400
|
-
delta: chunk.delta
|
|
401
|
-
|
|
402
|
-
};
|
|
460
|
+
delta: chunk.delta
|
|
461
|
+
});
|
|
403
462
|
}
|
|
404
463
|
if (chunk.type === "response.function_call_arguments.done") {
|
|
405
464
|
const { item_id } = chunk;
|
|
406
465
|
const metadata = toolCallMetadata.get(item_id);
|
|
407
|
-
|
|
408
|
-
|
|
466
|
+
if (!metadata?.started) {
|
|
467
|
+
options.logger.errors(
|
|
468
|
+
`${this.name}.processStreamChunks orphan function_call_arguments.done`,
|
|
469
|
+
{
|
|
470
|
+
source: `${this.name}.processStreamChunks`,
|
|
471
|
+
toolCallId: item_id,
|
|
472
|
+
rawArguments: chunk.arguments
|
|
473
|
+
}
|
|
474
|
+
);
|
|
475
|
+
continue;
|
|
476
|
+
}
|
|
477
|
+
const name = metadata.name || "";
|
|
409
478
|
let parsedInput = {};
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
479
|
+
if (chunk.arguments) {
|
|
480
|
+
try {
|
|
481
|
+
const parsed = JSON.parse(chunk.arguments);
|
|
482
|
+
parsedInput = parsed && typeof parsed === "object" ? parsed : {};
|
|
483
|
+
} catch (parseError) {
|
|
484
|
+
options.logger.errors(
|
|
485
|
+
`${this.name}.processStreamChunks tool-args JSON parse failed`,
|
|
486
|
+
{
|
|
487
|
+
error: toRunErrorPayload(
|
|
488
|
+
parseError,
|
|
489
|
+
`tool ${name} (${item_id}) returned malformed JSON arguments`
|
|
490
|
+
),
|
|
491
|
+
source: `${this.name}.processStreamChunks`,
|
|
492
|
+
toolCallId: item_id,
|
|
493
|
+
toolName: name,
|
|
494
|
+
rawArguments: chunk.arguments
|
|
495
|
+
}
|
|
496
|
+
);
|
|
497
|
+
parsedInput = {};
|
|
498
|
+
}
|
|
414
499
|
}
|
|
415
|
-
yield {
|
|
500
|
+
yield asChunk({
|
|
416
501
|
type: "TOOL_CALL_END",
|
|
417
|
-
toolCallId:
|
|
502
|
+
toolCallId: item_id,
|
|
503
|
+
toolCallName: name,
|
|
418
504
|
toolName: name,
|
|
419
505
|
model: model || options.model,
|
|
420
506
|
timestamp,
|
|
421
507
|
input: parsedInput
|
|
422
|
-
};
|
|
508
|
+
});
|
|
423
509
|
}
|
|
424
510
|
if (chunk.type === "response.completed") {
|
|
425
511
|
if (hasEmittedTextMessageStart) {
|
|
426
|
-
yield {
|
|
512
|
+
yield asChunk({
|
|
427
513
|
type: "TEXT_MESSAGE_END",
|
|
428
514
|
messageId: aguiState.messageId,
|
|
429
515
|
model: model || options.model,
|
|
430
516
|
timestamp
|
|
431
|
-
};
|
|
517
|
+
});
|
|
518
|
+
hasEmittedTextMessageStart = false;
|
|
432
519
|
}
|
|
433
520
|
const hasFunctionCalls = chunk.response.output.some(
|
|
434
521
|
(item) => item.type === "function_call"
|
|
435
522
|
);
|
|
436
|
-
|
|
523
|
+
const finishReason = hasFunctionCalls ? "tool_calls" : chunk.response.incomplete_details?.reason ?? "stop";
|
|
524
|
+
yield asChunk({
|
|
437
525
|
type: "RUN_FINISHED",
|
|
438
526
|
runId: aguiState.runId,
|
|
439
527
|
model: model || options.model,
|
|
@@ -443,11 +531,12 @@ class OpenAICompatibleResponsesTextAdapter extends BaseTextAdapter {
|
|
|
443
531
|
completionTokens: chunk.response.usage?.output_tokens || 0,
|
|
444
532
|
totalTokens: chunk.response.usage?.total_tokens || 0
|
|
445
533
|
},
|
|
446
|
-
finishReason
|
|
447
|
-
};
|
|
534
|
+
finishReason
|
|
535
|
+
});
|
|
536
|
+
runFinishedEmitted = true;
|
|
448
537
|
}
|
|
449
538
|
if (chunk.type === "error") {
|
|
450
|
-
yield {
|
|
539
|
+
yield asChunk({
|
|
451
540
|
type: "RUN_ERROR",
|
|
452
541
|
runId: aguiState.runId,
|
|
453
542
|
model: model || options.model,
|
|
@@ -456,22 +545,44 @@ class OpenAICompatibleResponsesTextAdapter extends BaseTextAdapter {
|
|
|
456
545
|
message: chunk.message,
|
|
457
546
|
code: chunk.code ?? void 0
|
|
458
547
|
}
|
|
459
|
-
};
|
|
548
|
+
});
|
|
549
|
+
runFinishedEmitted = true;
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
if (!runFinishedEmitted && aguiState.hasEmittedRunStarted) {
|
|
553
|
+
if (hasEmittedTextMessageStart) {
|
|
554
|
+
yield asChunk({
|
|
555
|
+
type: "TEXT_MESSAGE_END",
|
|
556
|
+
messageId: aguiState.messageId,
|
|
557
|
+
model: model || options.model,
|
|
558
|
+
timestamp
|
|
559
|
+
});
|
|
460
560
|
}
|
|
561
|
+
yield asChunk({
|
|
562
|
+
type: "RUN_FINISHED",
|
|
563
|
+
runId: aguiState.runId,
|
|
564
|
+
model: model || options.model,
|
|
565
|
+
timestamp,
|
|
566
|
+
usage: void 0,
|
|
567
|
+
finishReason: toolCallMetadata.size > 0 ? "tool_calls" : "stop"
|
|
568
|
+
});
|
|
461
569
|
}
|
|
462
570
|
} catch (error) {
|
|
463
|
-
const
|
|
464
|
-
|
|
465
|
-
|
|
571
|
+
const errorPayload = toRunErrorPayload(
|
|
572
|
+
error,
|
|
573
|
+
`${this.name}.processStreamChunks failed`
|
|
574
|
+
);
|
|
575
|
+
options.logger.errors(`${this.name}.processStreamChunks fatal`, {
|
|
576
|
+
error: errorPayload,
|
|
577
|
+
source: `${this.name}.processStreamChunks`
|
|
578
|
+
});
|
|
579
|
+
yield asChunk({
|
|
466
580
|
type: "RUN_ERROR",
|
|
467
581
|
runId: aguiState.runId,
|
|
468
582
|
model: options.model,
|
|
469
583
|
timestamp,
|
|
470
|
-
error:
|
|
471
|
-
|
|
472
|
-
code: err.code
|
|
473
|
-
}
|
|
474
|
-
};
|
|
584
|
+
error: errorPayload
|
|
585
|
+
});
|
|
475
586
|
}
|
|
476
587
|
}
|
|
477
588
|
/**
|
|
@@ -486,15 +597,23 @@ class OpenAICompatibleResponsesTextAdapter extends BaseTextAdapter {
|
|
|
486
597
|
) : void 0;
|
|
487
598
|
const modelOptions = options.modelOptions;
|
|
488
599
|
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
600
|
...modelOptions,
|
|
601
|
+
model: options.model,
|
|
602
|
+
...options.temperature !== void 0 && {
|
|
603
|
+
temperature: options.temperature
|
|
604
|
+
},
|
|
605
|
+
...options.maxTokens !== void 0 && {
|
|
606
|
+
max_output_tokens: options.maxTokens
|
|
607
|
+
},
|
|
608
|
+
...options.topP !== void 0 && { top_p: options.topP },
|
|
609
|
+
...options.metadata !== void 0 && { metadata: options.metadata },
|
|
610
|
+
...options.systemPrompts && options.systemPrompts.length > 0 && {
|
|
611
|
+
instructions: options.systemPrompts.join("\n")
|
|
612
|
+
},
|
|
496
613
|
input,
|
|
497
|
-
tools
|
|
614
|
+
// Conditional spread: `tools: undefined` would clobber any
|
|
615
|
+
// modelOptions.tools the caller set above.
|
|
616
|
+
...tools && tools.length > 0 && { tools }
|
|
498
617
|
};
|
|
499
618
|
}
|
|
500
619
|
/**
|
|
@@ -548,7 +667,9 @@ class OpenAICompatibleResponsesTextAdapter extends BaseTextAdapter {
|
|
|
548
667
|
inputContent.push(this.convertContentPartToInput(part));
|
|
549
668
|
}
|
|
550
669
|
if (inputContent.length === 0) {
|
|
551
|
-
|
|
670
|
+
throw new Error(
|
|
671
|
+
`User message for ${this.name} has no content parts. Empty user messages would produce a paid request with no input; provide at least one text/image/audio part or omit the message.`
|
|
672
|
+
);
|
|
552
673
|
}
|
|
553
674
|
result.push({
|
|
554
675
|
type: "message",
|
|
@@ -580,7 +701,8 @@ class OpenAICompatibleResponsesTextAdapter extends BaseTextAdapter {
|
|
|
580
701
|
};
|
|
581
702
|
}
|
|
582
703
|
const imageValue = part.source.value;
|
|
583
|
-
const
|
|
704
|
+
const imageMime = part.source.mimeType || "application/octet-stream";
|
|
705
|
+
const imageUrl = imageValue.startsWith("data:") ? imageValue : `data:${imageMime};base64,${imageValue}`;
|
|
584
706
|
return {
|
|
585
707
|
type: "input_image",
|
|
586
708
|
image_url: imageUrl,
|
|
@@ -594,9 +716,12 @@ class OpenAICompatibleResponsesTextAdapter extends BaseTextAdapter {
|
|
|
594
716
|
file_url: part.source.value
|
|
595
717
|
};
|
|
596
718
|
}
|
|
719
|
+
const audioValue = part.source.value;
|
|
720
|
+
const audioMime = part.source.mimeType || "application/octet-stream";
|
|
721
|
+
const audioFileData = audioValue.startsWith("data:") ? audioValue : `data:${audioMime};base64,${audioValue}`;
|
|
597
722
|
return {
|
|
598
723
|
type: "input_file",
|
|
599
|
-
file_data:
|
|
724
|
+
file_data: audioFileData
|
|
600
725
|
};
|
|
601
726
|
}
|
|
602
727
|
default:
|