@tanstack/openai-base 0.2.1 → 0.3.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/README.md +121 -0
- package/dist/esm/adapters/chat-completions-text.d.ts +49 -21
- package/dist/esm/adapters/chat-completions-text.js +476 -68
- package/dist/esm/adapters/chat-completions-text.js.map +1 -1
- package/dist/esm/adapters/chat-completions-tool-converter.d.ts +8 -4
- package/dist/esm/adapters/chat-completions-tool-converter.js.map +1 -1
- package/dist/esm/adapters/responses-text.d.ts +46 -33
- package/dist/esm/adapters/responses-text.js +657 -142
- package/dist/esm/adapters/responses-text.js.map +1 -1
- package/dist/esm/index.d.ts +2 -9
- package/dist/esm/index.js +4 -16
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/tools/apply-patch-tool.d.ts +2 -2
- package/dist/esm/tools/apply-patch-tool.js.map +1 -1
- package/dist/esm/tools/code-interpreter-tool.d.ts +3 -2
- package/dist/esm/tools/code-interpreter-tool.js.map +1 -1
- package/dist/esm/tools/computer-use-tool.d.ts +2 -2
- package/dist/esm/tools/computer-use-tool.js.map +1 -1
- package/dist/esm/tools/custom-tool.d.ts +2 -2
- package/dist/esm/tools/custom-tool.js.map +1 -1
- package/dist/esm/tools/file-search-tool.d.ts +2 -2
- package/dist/esm/tools/file-search-tool.js.map +1 -1
- package/dist/esm/tools/function-tool.d.ts +2 -2
- package/dist/esm/tools/function-tool.js.map +1 -1
- package/dist/esm/tools/image-generation-tool.d.ts +3 -2
- package/dist/esm/tools/image-generation-tool.js.map +1 -1
- package/dist/esm/tools/local-shell-tool.d.ts +3 -2
- package/dist/esm/tools/local-shell-tool.js.map +1 -1
- package/dist/esm/tools/mcp-tool.d.ts +3 -2
- package/dist/esm/tools/mcp-tool.js.map +1 -1
- package/dist/esm/tools/shell-tool.d.ts +2 -2
- package/dist/esm/tools/shell-tool.js.map +1 -1
- package/dist/esm/tools/web-search-preview-tool.d.ts +2 -2
- package/dist/esm/tools/web-search-preview-tool.js.map +1 -1
- package/dist/esm/tools/web-search-tool.d.ts +2 -2
- package/dist/esm/tools/web-search-tool.js.map +1 -1
- package/package.json +6 -6
- package/src/adapters/chat-completions-text.ts +601 -117
- package/src/adapters/chat-completions-tool-converter.ts +9 -5
- package/src/adapters/responses-text.ts +865 -210
- package/src/index.ts +2 -12
- package/src/tools/apply-patch-tool.ts +2 -2
- package/src/tools/code-interpreter-tool.ts +4 -2
- package/src/tools/computer-use-tool.ts +2 -2
- package/src/tools/custom-tool.ts +2 -2
- package/src/tools/file-search-tool.ts +3 -3
- package/src/tools/function-tool.ts +2 -2
- package/src/tools/image-generation-tool.ts +4 -2
- package/src/tools/local-shell-tool.ts +4 -2
- package/src/tools/mcp-tool.ts +4 -2
- package/src/tools/shell-tool.ts +2 -2
- package/src/tools/web-search-preview-tool.ts +2 -2
- package/src/tools/web-search-tool.ts +2 -2
- package/dist/esm/adapters/image.d.ts +0 -32
- package/dist/esm/adapters/image.js +0 -89
- package/dist/esm/adapters/image.js.map +0 -1
- package/dist/esm/adapters/summarize.d.ts +0 -28
- package/dist/esm/adapters/summarize.js +0 -112
- package/dist/esm/adapters/summarize.js.map +0 -1
- package/dist/esm/adapters/transcription.d.ts +0 -34
- package/dist/esm/adapters/transcription.js +0 -131
- package/dist/esm/adapters/transcription.js.map +0 -1
- package/dist/esm/adapters/tts.d.ts +0 -26
- package/dist/esm/adapters/tts.js +0 -78
- package/dist/esm/adapters/tts.js.map +0 -1
- package/dist/esm/adapters/video.d.ts +0 -72
- package/dist/esm/adapters/video.js +0 -238
- package/dist/esm/adapters/video.js.map +0 -1
- package/dist/esm/types/config.d.ts +0 -4
- package/dist/esm/utils/client.d.ts +0 -3
- package/dist/esm/utils/client.js +0 -8
- package/dist/esm/utils/client.js.map +0 -1
- package/src/adapters/image.ts +0 -158
- package/src/adapters/summarize.ts +0 -174
- package/src/adapters/transcription.ts +0 -194
- package/src/adapters/tts.ts +0 -124
- package/src/adapters/video.ts +0 -385
- package/src/types/config.ts +0 -5
- package/src/utils/client.ts +0 -8
|
@@ -1,29 +1,27 @@
|
|
|
1
|
+
import { EventType } from "@tanstack/ai";
|
|
1
2
|
import { BaseTextAdapter } from "@tanstack/ai/adapters";
|
|
2
3
|
import { toRunErrorPayload } from "@tanstack/ai/adapter-internals";
|
|
3
4
|
import { generateId, transformNullsToUndefined } from "@tanstack/ai-utils";
|
|
4
|
-
import { createOpenAICompatibleClient } from "../utils/client.js";
|
|
5
5
|
import { extractRequestOptions } from "../utils/request-options.js";
|
|
6
6
|
import { makeStructuredOutputCompatible } from "../utils/schema-converter.js";
|
|
7
7
|
import { convertToolsToResponsesFormat } from "./responses-tool-converter.js";
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
constructor(config, model, name = "openai-compatible-responses") {
|
|
8
|
+
class OpenAIBaseResponsesTextAdapter extends BaseTextAdapter {
|
|
9
|
+
constructor(model, name, client) {
|
|
11
10
|
super({}, model);
|
|
12
11
|
this.kind = "text";
|
|
13
12
|
this.name = name;
|
|
14
|
-
this.client =
|
|
13
|
+
this.client = client;
|
|
15
14
|
}
|
|
16
15
|
async *chatStream(options) {
|
|
17
16
|
const toolCallMetadata = /* @__PURE__ */ new Map();
|
|
18
|
-
const requestParams = this.mapOptionsToRequest(options);
|
|
19
|
-
const timestamp = Date.now();
|
|
20
17
|
const aguiState = {
|
|
21
18
|
runId: generateId(this.name),
|
|
19
|
+
threadId: options.threadId ?? generateId(this.name),
|
|
22
20
|
messageId: generateId(this.name),
|
|
23
|
-
timestamp,
|
|
24
21
|
hasEmittedRunStarted: false
|
|
25
22
|
};
|
|
26
23
|
try {
|
|
24
|
+
const requestParams = this.mapOptionsToRequest(options);
|
|
27
25
|
options.logger.request(
|
|
28
26
|
`activity=chat provider=${this.name} model=${this.model} messages=${options.messages.length} tools=${options.tools?.length ?? 0} stream=true`,
|
|
29
27
|
{ provider: this.name, model: this.model }
|
|
@@ -48,20 +46,22 @@ class OpenAICompatibleResponsesTextAdapter extends BaseTextAdapter {
|
|
|
48
46
|
);
|
|
49
47
|
if (!aguiState.hasEmittedRunStarted) {
|
|
50
48
|
aguiState.hasEmittedRunStarted = true;
|
|
51
|
-
yield
|
|
52
|
-
type:
|
|
49
|
+
yield {
|
|
50
|
+
type: EventType.RUN_STARTED,
|
|
53
51
|
runId: aguiState.runId,
|
|
52
|
+
threadId: aguiState.threadId,
|
|
54
53
|
model: options.model,
|
|
55
|
-
timestamp
|
|
56
|
-
}
|
|
54
|
+
timestamp: Date.now()
|
|
55
|
+
};
|
|
57
56
|
}
|
|
58
|
-
yield
|
|
59
|
-
type:
|
|
60
|
-
runId: aguiState.runId,
|
|
57
|
+
yield {
|
|
58
|
+
type: EventType.RUN_ERROR,
|
|
61
59
|
model: options.model,
|
|
62
|
-
timestamp,
|
|
60
|
+
timestamp: Date.now(),
|
|
61
|
+
message: errorPayload.message,
|
|
62
|
+
code: errorPayload.code,
|
|
63
63
|
error: errorPayload
|
|
64
|
-
}
|
|
64
|
+
};
|
|
65
65
|
options.logger.errors(`${this.name}.chatStream fatal`, {
|
|
66
66
|
error: errorPayload,
|
|
67
67
|
source: `${this.name}.chatStream`
|
|
@@ -115,9 +115,12 @@ class OpenAICompatibleResponsesTextAdapter extends BaseTextAdapter {
|
|
|
115
115
|
},
|
|
116
116
|
extractRequestOptions(chatOptions.request)
|
|
117
117
|
);
|
|
118
|
-
const rawText = this.extractTextFromResponse(
|
|
119
|
-
|
|
120
|
-
|
|
118
|
+
const rawText = this.extractTextFromResponse(response);
|
|
119
|
+
if (rawText.length === 0) {
|
|
120
|
+
throw new Error(
|
|
121
|
+
`${this.name}.structuredOutput: response contained no content`
|
|
122
|
+
);
|
|
123
|
+
}
|
|
121
124
|
let parsed;
|
|
122
125
|
try {
|
|
123
126
|
parsed = JSON.parse(rawText);
|
|
@@ -126,7 +129,7 @@ class OpenAICompatibleResponsesTextAdapter extends BaseTextAdapter {
|
|
|
126
129
|
`Failed to parse structured output as JSON. Content: ${rawText.slice(0, 200)}${rawText.length > 200 ? "..." : ""}`
|
|
127
130
|
);
|
|
128
131
|
}
|
|
129
|
-
const transformed =
|
|
132
|
+
const transformed = this.transformStructuredOutput(parsed);
|
|
130
133
|
return {
|
|
131
134
|
data: transformed,
|
|
132
135
|
rawText
|
|
@@ -139,6 +142,322 @@ class OpenAICompatibleResponsesTextAdapter extends BaseTextAdapter {
|
|
|
139
142
|
throw error;
|
|
140
143
|
}
|
|
141
144
|
}
|
|
145
|
+
/**
|
|
146
|
+
* Stream structured output via the Responses API: single request with
|
|
147
|
+
* `text.format: json_schema` + `stream: true`. Consumes Responses-API
|
|
148
|
+
* events (`response.output_text.delta`, `response.reasoning_text.delta`,
|
|
149
|
+
* `response.reasoning_summary_text.delta`, `response.refusal.delta`,
|
|
150
|
+
* `response.completed`, `response.failed`) and re-emits the standard AG-UI
|
|
151
|
+
* lifecycle ending with `CUSTOM 'structured-output.complete'`.
|
|
152
|
+
*
|
|
153
|
+
* Tools are stripped (structured output is mutually exclusive with tool
|
|
154
|
+
* calls in this path). Reasoning text is accumulated and surfaced both as
|
|
155
|
+
* REASONING_* lifecycle events during the stream and on the terminal
|
|
156
|
+
* CUSTOM event's `value.reasoning`.
|
|
157
|
+
*/
|
|
158
|
+
async *structuredOutputStream(options) {
|
|
159
|
+
const { chatOptions, outputSchema } = options;
|
|
160
|
+
const requestParams = this.mapOptionsToRequest(chatOptions);
|
|
161
|
+
const jsonSchema = this.makeStructuredOutputCompatible(
|
|
162
|
+
outputSchema,
|
|
163
|
+
outputSchema.required
|
|
164
|
+
);
|
|
165
|
+
const timestamp = Date.now();
|
|
166
|
+
const aguiState = {
|
|
167
|
+
runId: generateId(this.name),
|
|
168
|
+
threadId: chatOptions.threadId ?? generateId(this.name),
|
|
169
|
+
messageId: generateId(this.name),
|
|
170
|
+
timestamp,
|
|
171
|
+
hasEmittedRunStarted: false
|
|
172
|
+
};
|
|
173
|
+
let accumulatedContent = "";
|
|
174
|
+
let accumulatedReasoning = "";
|
|
175
|
+
let hasEmittedTextMessageStart = false;
|
|
176
|
+
let reasoningMessageId;
|
|
177
|
+
let stepId;
|
|
178
|
+
let hasClosedReasoning = false;
|
|
179
|
+
let model = chatOptions.model;
|
|
180
|
+
let usage;
|
|
181
|
+
const closeReasoning = (function* () {
|
|
182
|
+
if (reasoningMessageId && !hasClosedReasoning) {
|
|
183
|
+
hasClosedReasoning = true;
|
|
184
|
+
yield {
|
|
185
|
+
type: EventType.REASONING_MESSAGE_END,
|
|
186
|
+
messageId: reasoningMessageId,
|
|
187
|
+
model,
|
|
188
|
+
timestamp
|
|
189
|
+
};
|
|
190
|
+
yield {
|
|
191
|
+
type: EventType.REASONING_END,
|
|
192
|
+
messageId: reasoningMessageId,
|
|
193
|
+
model,
|
|
194
|
+
timestamp
|
|
195
|
+
};
|
|
196
|
+
if (stepId) {
|
|
197
|
+
yield {
|
|
198
|
+
type: EventType.STEP_FINISHED,
|
|
199
|
+
stepName: stepId,
|
|
200
|
+
stepId,
|
|
201
|
+
model,
|
|
202
|
+
timestamp,
|
|
203
|
+
content: accumulatedReasoning
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}).bind(this);
|
|
208
|
+
const openReasoning = (function* () {
|
|
209
|
+
if (reasoningMessageId) return;
|
|
210
|
+
reasoningMessageId = generateId(this.name);
|
|
211
|
+
stepId = generateId(this.name);
|
|
212
|
+
yield {
|
|
213
|
+
type: EventType.REASONING_START,
|
|
214
|
+
messageId: reasoningMessageId,
|
|
215
|
+
model,
|
|
216
|
+
timestamp
|
|
217
|
+
};
|
|
218
|
+
yield {
|
|
219
|
+
type: EventType.REASONING_MESSAGE_START,
|
|
220
|
+
messageId: reasoningMessageId,
|
|
221
|
+
role: "reasoning",
|
|
222
|
+
model,
|
|
223
|
+
timestamp
|
|
224
|
+
};
|
|
225
|
+
yield {
|
|
226
|
+
type: EventType.STEP_STARTED,
|
|
227
|
+
stepName: stepId,
|
|
228
|
+
stepId,
|
|
229
|
+
model,
|
|
230
|
+
timestamp,
|
|
231
|
+
stepType: "thinking"
|
|
232
|
+
};
|
|
233
|
+
}).bind(this);
|
|
234
|
+
try {
|
|
235
|
+
const { tools: _tools, ...cleanParams } = requestParams;
|
|
236
|
+
void _tools;
|
|
237
|
+
chatOptions.logger.request(
|
|
238
|
+
`activity=structuredOutputStream provider=${this.name} model=${this.model} messages=${chatOptions.messages.length}`,
|
|
239
|
+
{ provider: this.name, model: this.model }
|
|
240
|
+
);
|
|
241
|
+
const stream = await this.client.responses.create(
|
|
242
|
+
{
|
|
243
|
+
...cleanParams,
|
|
244
|
+
stream: true,
|
|
245
|
+
text: {
|
|
246
|
+
format: {
|
|
247
|
+
type: "json_schema",
|
|
248
|
+
name: "structured_output",
|
|
249
|
+
schema: jsonSchema,
|
|
250
|
+
strict: true
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
},
|
|
254
|
+
extractRequestOptions(chatOptions.request)
|
|
255
|
+
);
|
|
256
|
+
for await (const chunk of stream) {
|
|
257
|
+
chatOptions.logger.provider(
|
|
258
|
+
`provider=${this.name} type=${chunk.type}`,
|
|
259
|
+
{ provider: this.name, type: chunk.type }
|
|
260
|
+
);
|
|
261
|
+
if (!aguiState.hasEmittedRunStarted) {
|
|
262
|
+
aguiState.hasEmittedRunStarted = true;
|
|
263
|
+
yield {
|
|
264
|
+
type: EventType.RUN_STARTED,
|
|
265
|
+
runId: aguiState.runId,
|
|
266
|
+
threadId: aguiState.threadId,
|
|
267
|
+
model,
|
|
268
|
+
timestamp
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
if (chunk.type === "response.created" || chunk.type === "response.in_progress") {
|
|
272
|
+
const responseModel = chunk.response?.model;
|
|
273
|
+
if (responseModel) model = responseModel;
|
|
274
|
+
continue;
|
|
275
|
+
}
|
|
276
|
+
if (chunk.type === "response.refusal.delta") {
|
|
277
|
+
const delta = typeof chunk.delta === "string" ? chunk.delta : "";
|
|
278
|
+
yield {
|
|
279
|
+
type: EventType.RUN_ERROR,
|
|
280
|
+
runId: aguiState.runId,
|
|
281
|
+
model,
|
|
282
|
+
timestamp,
|
|
283
|
+
message: `Model refused: ${delta}`,
|
|
284
|
+
code: "refusal",
|
|
285
|
+
error: { message: `Model refused: ${delta}`, code: "refusal" }
|
|
286
|
+
};
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
if (chunk.type === "response.reasoning_text.delta" || chunk.type === "response.reasoning_summary_text.delta") {
|
|
290
|
+
const raw = chunk.delta;
|
|
291
|
+
const reasoningDelta = Array.isArray(raw) ? raw.join("") : typeof raw === "string" ? raw : "";
|
|
292
|
+
if (!reasoningDelta) continue;
|
|
293
|
+
yield* openReasoning();
|
|
294
|
+
const messageId = reasoningMessageId;
|
|
295
|
+
accumulatedReasoning += reasoningDelta;
|
|
296
|
+
yield {
|
|
297
|
+
type: EventType.REASONING_MESSAGE_CONTENT,
|
|
298
|
+
messageId,
|
|
299
|
+
delta: reasoningDelta,
|
|
300
|
+
model,
|
|
301
|
+
timestamp
|
|
302
|
+
};
|
|
303
|
+
continue;
|
|
304
|
+
}
|
|
305
|
+
if (chunk.type === "response.output_text.delta") {
|
|
306
|
+
const raw = chunk.delta;
|
|
307
|
+
const textDelta = Array.isArray(raw) ? raw.join("") : typeof raw === "string" ? raw : "";
|
|
308
|
+
if (!textDelta) continue;
|
|
309
|
+
yield* closeReasoning();
|
|
310
|
+
if (!hasEmittedTextMessageStart) {
|
|
311
|
+
hasEmittedTextMessageStart = true;
|
|
312
|
+
yield {
|
|
313
|
+
type: EventType.TEXT_MESSAGE_START,
|
|
314
|
+
messageId: aguiState.messageId,
|
|
315
|
+
model,
|
|
316
|
+
timestamp,
|
|
317
|
+
role: "assistant"
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
accumulatedContent += textDelta;
|
|
321
|
+
yield {
|
|
322
|
+
type: EventType.TEXT_MESSAGE_CONTENT,
|
|
323
|
+
messageId: aguiState.messageId,
|
|
324
|
+
model,
|
|
325
|
+
timestamp,
|
|
326
|
+
delta: textDelta,
|
|
327
|
+
content: accumulatedContent
|
|
328
|
+
};
|
|
329
|
+
continue;
|
|
330
|
+
}
|
|
331
|
+
if (chunk.type === "response.completed") {
|
|
332
|
+
const response = chunk.response;
|
|
333
|
+
if (response.usage) usage = response.usage;
|
|
334
|
+
if (response.model) model = response.model;
|
|
335
|
+
continue;
|
|
336
|
+
}
|
|
337
|
+
if (chunk.type === "response.failed") {
|
|
338
|
+
const response = chunk.response;
|
|
339
|
+
const message = response?.error?.message || "Responses API stream failed";
|
|
340
|
+
yield {
|
|
341
|
+
type: EventType.RUN_ERROR,
|
|
342
|
+
runId: aguiState.runId,
|
|
343
|
+
model,
|
|
344
|
+
timestamp,
|
|
345
|
+
message,
|
|
346
|
+
code: response?.error?.code,
|
|
347
|
+
error: { message, code: response?.error?.code }
|
|
348
|
+
};
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
yield* closeReasoning();
|
|
353
|
+
if (hasEmittedTextMessageStart) {
|
|
354
|
+
yield {
|
|
355
|
+
type: EventType.TEXT_MESSAGE_END,
|
|
356
|
+
messageId: aguiState.messageId,
|
|
357
|
+
model,
|
|
358
|
+
timestamp
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
if (accumulatedContent.length === 0) {
|
|
362
|
+
yield {
|
|
363
|
+
type: EventType.RUN_ERROR,
|
|
364
|
+
runId: aguiState.runId,
|
|
365
|
+
model,
|
|
366
|
+
timestamp,
|
|
367
|
+
message: `${this.name}.structuredOutputStream: response contained no content`,
|
|
368
|
+
code: "empty-response",
|
|
369
|
+
error: {
|
|
370
|
+
message: `${this.name}.structuredOutputStream: response contained no content`,
|
|
371
|
+
code: "empty-response"
|
|
372
|
+
}
|
|
373
|
+
};
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
376
|
+
let parsed;
|
|
377
|
+
try {
|
|
378
|
+
parsed = JSON.parse(accumulatedContent);
|
|
379
|
+
} catch {
|
|
380
|
+
yield {
|
|
381
|
+
type: EventType.RUN_ERROR,
|
|
382
|
+
runId: aguiState.runId,
|
|
383
|
+
model,
|
|
384
|
+
timestamp,
|
|
385
|
+
message: `Failed to parse structured output as JSON. Content: ${accumulatedContent.slice(0, 200)}${accumulatedContent.length > 200 ? "..." : ""}`,
|
|
386
|
+
code: "parse-error",
|
|
387
|
+
error: {
|
|
388
|
+
message: "Failed to parse structured output as JSON",
|
|
389
|
+
code: "parse-error"
|
|
390
|
+
}
|
|
391
|
+
};
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
394
|
+
const transformed = transformNullsToUndefined(parsed);
|
|
395
|
+
yield {
|
|
396
|
+
type: EventType.CUSTOM,
|
|
397
|
+
name: "structured-output.complete",
|
|
398
|
+
value: {
|
|
399
|
+
object: transformed,
|
|
400
|
+
raw: accumulatedContent,
|
|
401
|
+
...accumulatedReasoning ? { reasoning: accumulatedReasoning } : {}
|
|
402
|
+
},
|
|
403
|
+
model,
|
|
404
|
+
timestamp
|
|
405
|
+
};
|
|
406
|
+
yield {
|
|
407
|
+
type: EventType.RUN_FINISHED,
|
|
408
|
+
runId: aguiState.runId,
|
|
409
|
+
threadId: aguiState.threadId,
|
|
410
|
+
model,
|
|
411
|
+
timestamp,
|
|
412
|
+
finishReason: "stop",
|
|
413
|
+
...usage && {
|
|
414
|
+
usage: {
|
|
415
|
+
promptTokens: usage.input_tokens,
|
|
416
|
+
completionTokens: usage.output_tokens,
|
|
417
|
+
totalTokens: usage.total_tokens
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
};
|
|
421
|
+
} catch (error) {
|
|
422
|
+
if (!aguiState.hasEmittedRunStarted) {
|
|
423
|
+
aguiState.hasEmittedRunStarted = true;
|
|
424
|
+
yield {
|
|
425
|
+
type: EventType.RUN_STARTED,
|
|
426
|
+
runId: aguiState.runId,
|
|
427
|
+
threadId: aguiState.threadId,
|
|
428
|
+
model,
|
|
429
|
+
timestamp
|
|
430
|
+
};
|
|
431
|
+
}
|
|
432
|
+
const isAbort = this.isAbortError(error);
|
|
433
|
+
const errorPayload = toRunErrorPayload(
|
|
434
|
+
error,
|
|
435
|
+
`${this.name}.structuredOutputStream failed`
|
|
436
|
+
);
|
|
437
|
+
yield {
|
|
438
|
+
type: EventType.RUN_ERROR,
|
|
439
|
+
runId: aguiState.runId,
|
|
440
|
+
model,
|
|
441
|
+
timestamp,
|
|
442
|
+
message: errorPayload.message,
|
|
443
|
+
code: isAbort ? "aborted" : errorPayload.code,
|
|
444
|
+
error: { ...errorPayload, ...isAbort && { code: "aborted" } }
|
|
445
|
+
};
|
|
446
|
+
chatOptions.logger.errors(`${this.name}.structuredOutputStream fatal`, {
|
|
447
|
+
error: errorPayload,
|
|
448
|
+
source: `${this.name}.structuredOutputStream`
|
|
449
|
+
});
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
/**
|
|
453
|
+
* Cross-SDK abort detection for `structuredOutputStream`. Mirrors the
|
|
454
|
+
* Chat Completions base; subclasses with proprietary error types override.
|
|
455
|
+
*/
|
|
456
|
+
isAbortError(error) {
|
|
457
|
+
if (!error || typeof error !== "object") return false;
|
|
458
|
+
const e = error;
|
|
459
|
+
return e.name === "APIUserAbortError" || e.name === "AbortError" || e.code === "ERR_CANCELED";
|
|
460
|
+
}
|
|
142
461
|
/**
|
|
143
462
|
* Applies provider-specific transformations for structured output compatibility.
|
|
144
463
|
* Override this in subclasses to handle provider-specific quirks.
|
|
@@ -146,6 +465,18 @@ class OpenAICompatibleResponsesTextAdapter extends BaseTextAdapter {
|
|
|
146
465
|
makeStructuredOutputCompatible(schema, originalRequired) {
|
|
147
466
|
return makeStructuredOutputCompatible(schema, originalRequired);
|
|
148
467
|
}
|
|
468
|
+
/**
|
|
469
|
+
* Final shaping pass applied to parsed structured-output JSON before it is
|
|
470
|
+
* returned to the caller. Default converts `null` values to `undefined` so
|
|
471
|
+
* the result aligns with the original Zod schema's optional-field
|
|
472
|
+
* semantics. Subclasses with different conventions (OpenRouter historically
|
|
473
|
+
* preserves nulls) can override — mirrors the chat-completions base's hook
|
|
474
|
+
* so a subclass that opts out of null-stripping doesn't have to fork the
|
|
475
|
+
* whole `structuredOutput` method.
|
|
476
|
+
*/
|
|
477
|
+
transformStructuredOutput(parsed) {
|
|
478
|
+
return transformNullsToUndefined(parsed);
|
|
479
|
+
}
|
|
149
480
|
/**
|
|
150
481
|
* Extract text content from a non-streaming Responses API response.
|
|
151
482
|
* Override this in subclasses for provider-specific response shapes.
|
|
@@ -153,13 +484,23 @@ class OpenAICompatibleResponsesTextAdapter extends BaseTextAdapter {
|
|
|
153
484
|
extractTextFromResponse(response) {
|
|
154
485
|
let textContent = "";
|
|
155
486
|
let refusal;
|
|
487
|
+
let sawMessageItem = false;
|
|
488
|
+
const observedItemTypes = /* @__PURE__ */ new Set();
|
|
156
489
|
for (const item of response.output) {
|
|
490
|
+
observedItemTypes.add(item.type);
|
|
157
491
|
if (item.type === "message") {
|
|
492
|
+
sawMessageItem = true;
|
|
158
493
|
for (const part of item.content) {
|
|
159
|
-
|
|
160
|
-
|
|
494
|
+
const partType = part.type;
|
|
495
|
+
if (partType === "output_text") {
|
|
496
|
+
textContent += part.text ?? "";
|
|
497
|
+
} else if (partType === "refusal") {
|
|
498
|
+
const refusalText = part.refusal;
|
|
499
|
+
refusal = refusalText || refusal || "Refused without explanation";
|
|
161
500
|
} else {
|
|
162
|
-
|
|
501
|
+
throw new Error(
|
|
502
|
+
`${this.name}.extractTextFromResponse: unsupported message content part type "${partType}"`
|
|
503
|
+
);
|
|
163
504
|
}
|
|
164
505
|
}
|
|
165
506
|
}
|
|
@@ -169,6 +510,11 @@ class OpenAICompatibleResponsesTextAdapter extends BaseTextAdapter {
|
|
|
169
510
|
err.code = "refusal";
|
|
170
511
|
throw err;
|
|
171
512
|
}
|
|
513
|
+
if (!textContent && response.output.length > 0 && !sawMessageItem) {
|
|
514
|
+
throw new Error(
|
|
515
|
+
`${this.name}.extractTextFromResponse: response.output contained items of type(s) [${[...observedItemTypes].sort().join(", ")}] but no message text — the model returned a non-text response`
|
|
516
|
+
);
|
|
517
|
+
}
|
|
172
518
|
return textContent;
|
|
173
519
|
}
|
|
174
520
|
/**
|
|
@@ -189,7 +535,6 @@ class OpenAICompatibleResponsesTextAdapter extends BaseTextAdapter {
|
|
|
189
535
|
async *processStreamChunks(stream, toolCallMetadata, options, aguiState) {
|
|
190
536
|
let accumulatedContent = "";
|
|
191
537
|
let accumulatedReasoning = "";
|
|
192
|
-
const timestamp = aguiState.timestamp;
|
|
193
538
|
let hasStreamedContentDeltas = false;
|
|
194
539
|
let hasStreamedReasoningDeltas = false;
|
|
195
540
|
let model = options.model;
|
|
@@ -205,51 +550,52 @@ class OpenAICompatibleResponsesTextAdapter extends BaseTextAdapter {
|
|
|
205
550
|
});
|
|
206
551
|
if (!aguiState.hasEmittedRunStarted) {
|
|
207
552
|
aguiState.hasEmittedRunStarted = true;
|
|
208
|
-
yield
|
|
209
|
-
type:
|
|
553
|
+
yield {
|
|
554
|
+
type: EventType.RUN_STARTED,
|
|
210
555
|
runId: aguiState.runId,
|
|
556
|
+
threadId: aguiState.threadId,
|
|
211
557
|
model: model || options.model,
|
|
212
|
-
timestamp
|
|
213
|
-
}
|
|
558
|
+
timestamp: Date.now()
|
|
559
|
+
};
|
|
214
560
|
}
|
|
215
561
|
const handleContentPart = (contentPart) => {
|
|
216
562
|
if (contentPart.type === "output_text") {
|
|
217
563
|
accumulatedContent += contentPart.text || "";
|
|
218
|
-
return
|
|
219
|
-
type:
|
|
564
|
+
return {
|
|
565
|
+
type: EventType.TEXT_MESSAGE_CONTENT,
|
|
220
566
|
messageId: aguiState.messageId,
|
|
221
567
|
model: model || options.model,
|
|
222
|
-
timestamp,
|
|
568
|
+
timestamp: Date.now(),
|
|
223
569
|
delta: contentPart.text || "",
|
|
224
570
|
content: accumulatedContent
|
|
225
|
-
}
|
|
571
|
+
};
|
|
226
572
|
}
|
|
227
573
|
if (contentPart.type === "reasoning_text") {
|
|
228
574
|
accumulatedReasoning += contentPart.text || "";
|
|
229
575
|
if (!stepId) {
|
|
230
576
|
stepId = generateId(this.name);
|
|
231
577
|
}
|
|
232
|
-
return
|
|
233
|
-
type:
|
|
578
|
+
return {
|
|
579
|
+
type: EventType.STEP_FINISHED,
|
|
580
|
+
stepName: stepId,
|
|
234
581
|
stepId,
|
|
235
582
|
model: model || options.model,
|
|
236
|
-
timestamp,
|
|
583
|
+
timestamp: Date.now(),
|
|
237
584
|
delta: contentPart.text || "",
|
|
238
585
|
content: accumulatedReasoning
|
|
239
|
-
}
|
|
586
|
+
};
|
|
240
587
|
}
|
|
241
588
|
const isRefusal = contentPart.type === "refusal";
|
|
242
589
|
const message = isRefusal ? contentPart.refusal || "Refused without explanation" : `Unsupported response content_part type: ${contentPart.type}`;
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
590
|
+
const code = isRefusal ? "refusal" : contentPart.type;
|
|
591
|
+
return {
|
|
592
|
+
type: EventType.RUN_ERROR,
|
|
246
593
|
model: model || options.model,
|
|
247
|
-
timestamp,
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
});
|
|
594
|
+
timestamp: Date.now(),
|
|
595
|
+
message,
|
|
596
|
+
code,
|
|
597
|
+
error: { message, code }
|
|
598
|
+
};
|
|
253
599
|
};
|
|
254
600
|
if (chunk.type === "response.created" || chunk.type === "response.incomplete" || chunk.type === "response.failed") {
|
|
255
601
|
model = chunk.response.model;
|
|
@@ -264,26 +610,27 @@ class OpenAICompatibleResponsesTextAdapter extends BaseTextAdapter {
|
|
|
264
610
|
}
|
|
265
611
|
if (chunk.type === "response.failed" || chunk.type === "response.incomplete") {
|
|
266
612
|
if (hasEmittedTextMessageStart) {
|
|
267
|
-
yield
|
|
268
|
-
type:
|
|
613
|
+
yield {
|
|
614
|
+
type: EventType.TEXT_MESSAGE_END,
|
|
269
615
|
messageId: aguiState.messageId,
|
|
270
616
|
model: chunk.response.model,
|
|
271
|
-
timestamp
|
|
272
|
-
}
|
|
617
|
+
timestamp: Date.now()
|
|
618
|
+
};
|
|
273
619
|
hasEmittedTextMessageStart = false;
|
|
274
620
|
}
|
|
275
621
|
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
|
|
277
|
-
yield
|
|
278
|
-
type:
|
|
279
|
-
runId: aguiState.runId,
|
|
622
|
+
const errorCode = chunk.response.error?.code ?? (chunk.response.incomplete_details ? "incomplete" : void 0) ?? void 0;
|
|
623
|
+
yield {
|
|
624
|
+
type: EventType.RUN_ERROR,
|
|
280
625
|
model: chunk.response.model,
|
|
281
|
-
timestamp,
|
|
626
|
+
timestamp: Date.now(),
|
|
627
|
+
message: errorMessage,
|
|
628
|
+
...errorCode !== void 0 && { code: errorCode },
|
|
282
629
|
error: {
|
|
283
630
|
message: errorMessage,
|
|
284
631
|
...errorCode !== void 0 && { code: errorCode }
|
|
285
632
|
}
|
|
286
|
-
}
|
|
633
|
+
};
|
|
287
634
|
runFinishedEmitted = true;
|
|
288
635
|
return;
|
|
289
636
|
}
|
|
@@ -292,24 +639,24 @@ class OpenAICompatibleResponsesTextAdapter extends BaseTextAdapter {
|
|
|
292
639
|
if (textDelta) {
|
|
293
640
|
if (!hasEmittedTextMessageStart) {
|
|
294
641
|
hasEmittedTextMessageStart = true;
|
|
295
|
-
yield
|
|
296
|
-
type:
|
|
642
|
+
yield {
|
|
643
|
+
type: EventType.TEXT_MESSAGE_START,
|
|
297
644
|
messageId: aguiState.messageId,
|
|
298
645
|
model: model || options.model,
|
|
299
|
-
timestamp,
|
|
646
|
+
timestamp: Date.now(),
|
|
300
647
|
role: "assistant"
|
|
301
|
-
}
|
|
648
|
+
};
|
|
302
649
|
}
|
|
303
650
|
accumulatedContent += textDelta;
|
|
304
651
|
hasStreamedContentDeltas = true;
|
|
305
|
-
yield
|
|
306
|
-
type:
|
|
652
|
+
yield {
|
|
653
|
+
type: EventType.TEXT_MESSAGE_CONTENT,
|
|
307
654
|
messageId: aguiState.messageId,
|
|
308
655
|
model: model || options.model,
|
|
309
|
-
timestamp,
|
|
656
|
+
timestamp: Date.now(),
|
|
310
657
|
delta: textDelta,
|
|
311
658
|
content: accumulatedContent
|
|
312
|
-
}
|
|
659
|
+
};
|
|
313
660
|
}
|
|
314
661
|
}
|
|
315
662
|
if (chunk.type === "response.reasoning_text.delta" && chunk.delta) {
|
|
@@ -318,24 +665,27 @@ class OpenAICompatibleResponsesTextAdapter extends BaseTextAdapter {
|
|
|
318
665
|
if (!hasEmittedStepStarted) {
|
|
319
666
|
hasEmittedStepStarted = true;
|
|
320
667
|
stepId = generateId(this.name);
|
|
321
|
-
yield
|
|
322
|
-
type:
|
|
668
|
+
yield {
|
|
669
|
+
type: EventType.STEP_STARTED,
|
|
670
|
+
stepName: stepId,
|
|
323
671
|
stepId,
|
|
324
672
|
model: model || options.model,
|
|
325
|
-
timestamp,
|
|
673
|
+
timestamp: Date.now(),
|
|
326
674
|
stepType: "thinking"
|
|
327
|
-
}
|
|
675
|
+
};
|
|
328
676
|
}
|
|
329
677
|
accumulatedReasoning += reasoningDelta;
|
|
330
678
|
hasStreamedReasoningDeltas = true;
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
679
|
+
const fallbackStepId = stepId || generateId(this.name);
|
|
680
|
+
yield {
|
|
681
|
+
type: EventType.STEP_FINISHED,
|
|
682
|
+
stepName: fallbackStepId,
|
|
683
|
+
stepId: fallbackStepId,
|
|
334
684
|
model: model || options.model,
|
|
335
|
-
timestamp,
|
|
685
|
+
timestamp: Date.now(),
|
|
336
686
|
delta: reasoningDelta,
|
|
337
687
|
content: accumulatedReasoning
|
|
338
|
-
}
|
|
688
|
+
};
|
|
339
689
|
}
|
|
340
690
|
}
|
|
341
691
|
if (chunk.type === "response.reasoning_summary_text.delta" && chunk.delta) {
|
|
@@ -344,48 +694,52 @@ class OpenAICompatibleResponsesTextAdapter extends BaseTextAdapter {
|
|
|
344
694
|
if (!hasEmittedStepStarted) {
|
|
345
695
|
hasEmittedStepStarted = true;
|
|
346
696
|
stepId = generateId(this.name);
|
|
347
|
-
yield
|
|
348
|
-
type:
|
|
697
|
+
yield {
|
|
698
|
+
type: EventType.STEP_STARTED,
|
|
699
|
+
stepName: stepId,
|
|
349
700
|
stepId,
|
|
350
701
|
model: model || options.model,
|
|
351
|
-
timestamp,
|
|
702
|
+
timestamp: Date.now(),
|
|
352
703
|
stepType: "thinking"
|
|
353
|
-
}
|
|
704
|
+
};
|
|
354
705
|
}
|
|
355
706
|
accumulatedReasoning += summaryDelta;
|
|
356
707
|
hasStreamedReasoningDeltas = true;
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
708
|
+
const fallbackStepId = stepId || generateId(this.name);
|
|
709
|
+
yield {
|
|
710
|
+
type: EventType.STEP_FINISHED,
|
|
711
|
+
stepName: fallbackStepId,
|
|
712
|
+
stepId: fallbackStepId,
|
|
360
713
|
model: model || options.model,
|
|
361
|
-
timestamp,
|
|
714
|
+
timestamp: Date.now(),
|
|
362
715
|
delta: summaryDelta,
|
|
363
716
|
content: accumulatedReasoning
|
|
364
|
-
}
|
|
717
|
+
};
|
|
365
718
|
}
|
|
366
719
|
}
|
|
367
720
|
if (chunk.type === "response.content_part.added") {
|
|
368
721
|
const contentPart = chunk.part;
|
|
369
722
|
if (contentPart.type === "output_text" && !hasEmittedTextMessageStart) {
|
|
370
723
|
hasEmittedTextMessageStart = true;
|
|
371
|
-
yield
|
|
372
|
-
type:
|
|
724
|
+
yield {
|
|
725
|
+
type: EventType.TEXT_MESSAGE_START,
|
|
373
726
|
messageId: aguiState.messageId,
|
|
374
727
|
model: model || options.model,
|
|
375
|
-
timestamp,
|
|
728
|
+
timestamp: Date.now(),
|
|
376
729
|
role: "assistant"
|
|
377
|
-
}
|
|
730
|
+
};
|
|
378
731
|
}
|
|
379
732
|
if (contentPart.type === "reasoning_text" && !hasEmittedStepStarted) {
|
|
380
733
|
hasEmittedStepStarted = true;
|
|
381
734
|
stepId = generateId(this.name);
|
|
382
|
-
yield
|
|
383
|
-
type:
|
|
735
|
+
yield {
|
|
736
|
+
type: EventType.STEP_STARTED,
|
|
737
|
+
stepName: stepId,
|
|
384
738
|
stepId,
|
|
385
739
|
model: model || options.model,
|
|
386
|
-
timestamp,
|
|
740
|
+
timestamp: Date.now(),
|
|
387
741
|
stepType: "thinking"
|
|
388
|
-
}
|
|
742
|
+
};
|
|
389
743
|
}
|
|
390
744
|
if (contentPart.type === "output_text") {
|
|
391
745
|
hasStreamedContentDeltas = true;
|
|
@@ -407,6 +761,27 @@ class OpenAICompatibleResponsesTextAdapter extends BaseTextAdapter {
|
|
|
407
761
|
if (contentPart.type === "reasoning_text" && hasStreamedReasoningDeltas) {
|
|
408
762
|
continue;
|
|
409
763
|
}
|
|
764
|
+
if (contentPart.type === "output_text" && !hasEmittedTextMessageStart) {
|
|
765
|
+
hasEmittedTextMessageStart = true;
|
|
766
|
+
yield {
|
|
767
|
+
type: EventType.TEXT_MESSAGE_START,
|
|
768
|
+
messageId: aguiState.messageId,
|
|
769
|
+
model: model || options.model,
|
|
770
|
+
timestamp: Date.now(),
|
|
771
|
+
role: "assistant"
|
|
772
|
+
};
|
|
773
|
+
} else if (contentPart.type === "reasoning_text" && !hasEmittedStepStarted) {
|
|
774
|
+
hasEmittedStepStarted = true;
|
|
775
|
+
stepId = generateId(this.name);
|
|
776
|
+
yield {
|
|
777
|
+
type: EventType.STEP_STARTED,
|
|
778
|
+
stepName: stepId,
|
|
779
|
+
stepId,
|
|
780
|
+
model: model || options.model,
|
|
781
|
+
timestamp: Date.now(),
|
|
782
|
+
stepType: "thinking"
|
|
783
|
+
};
|
|
784
|
+
}
|
|
410
785
|
const doneChunk = handleContentPart(contentPart);
|
|
411
786
|
yield doneChunk;
|
|
412
787
|
if (doneChunk.type === "RUN_ERROR") {
|
|
@@ -418,24 +793,27 @@ class OpenAICompatibleResponsesTextAdapter extends BaseTextAdapter {
|
|
|
418
793
|
const item = chunk.item;
|
|
419
794
|
if (item.type === "function_call" && item.id) {
|
|
420
795
|
const existing = toolCallMetadata.get(item.id);
|
|
421
|
-
if (!existing
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
796
|
+
if (!existing) {
|
|
797
|
+
toolCallMetadata.set(item.id, {
|
|
798
|
+
index: chunk.output_index,
|
|
799
|
+
name: item.name || "",
|
|
800
|
+
started: false
|
|
801
|
+
});
|
|
802
|
+
} else if (!existing.name && item.name) {
|
|
803
|
+
existing.name = item.name;
|
|
804
|
+
}
|
|
805
|
+
const metadata = toolCallMetadata.get(item.id);
|
|
806
|
+
if (!metadata.started && metadata.name) {
|
|
807
|
+
yield {
|
|
808
|
+
type: EventType.TOOL_CALL_START,
|
|
431
809
|
toolCallId: item.id,
|
|
432
|
-
toolCallName:
|
|
433
|
-
toolName:
|
|
810
|
+
toolCallName: metadata.name,
|
|
811
|
+
toolName: metadata.name,
|
|
434
812
|
model: model || options.model,
|
|
435
|
-
timestamp,
|
|
813
|
+
timestamp: Date.now(),
|
|
436
814
|
index: chunk.output_index
|
|
437
|
-
}
|
|
438
|
-
|
|
815
|
+
};
|
|
816
|
+
metadata.started = true;
|
|
439
817
|
}
|
|
440
818
|
}
|
|
441
819
|
}
|
|
@@ -452,20 +830,23 @@ class OpenAICompatibleResponsesTextAdapter extends BaseTextAdapter {
|
|
|
452
830
|
);
|
|
453
831
|
continue;
|
|
454
832
|
}
|
|
455
|
-
yield
|
|
456
|
-
type:
|
|
833
|
+
yield {
|
|
834
|
+
type: EventType.TOOL_CALL_ARGS,
|
|
457
835
|
toolCallId: chunk.item_id,
|
|
458
836
|
model: model || options.model,
|
|
459
|
-
timestamp,
|
|
837
|
+
timestamp: Date.now(),
|
|
460
838
|
delta: chunk.delta
|
|
461
|
-
}
|
|
839
|
+
};
|
|
462
840
|
}
|
|
463
841
|
if (chunk.type === "response.function_call_arguments.done") {
|
|
464
842
|
const { item_id } = chunk;
|
|
465
843
|
const metadata = toolCallMetadata.get(item_id);
|
|
466
844
|
if (!metadata?.started) {
|
|
845
|
+
if (metadata) {
|
|
846
|
+
metadata.pendingArguments = chunk.arguments;
|
|
847
|
+
}
|
|
467
848
|
options.logger.errors(
|
|
468
|
-
`${this.name}.processStreamChunks
|
|
849
|
+
`${this.name}.processStreamChunks deferring function_call_arguments.done — TOOL_CALL_START not yet emitted (waiting for name)`,
|
|
469
850
|
{
|
|
470
851
|
source: `${this.name}.processStreamChunks`,
|
|
471
852
|
toolCallId: item_id,
|
|
@@ -474,7 +855,9 @@ class OpenAICompatibleResponsesTextAdapter extends BaseTextAdapter {
|
|
|
474
855
|
);
|
|
475
856
|
continue;
|
|
476
857
|
}
|
|
858
|
+
if (metadata.ended) continue;
|
|
477
859
|
const name = metadata.name || "";
|
|
860
|
+
metadata.ended = true;
|
|
478
861
|
let parsedInput = {};
|
|
479
862
|
if (chunk.arguments) {
|
|
480
863
|
try {
|
|
@@ -497,75 +880,206 @@ class OpenAICompatibleResponsesTextAdapter extends BaseTextAdapter {
|
|
|
497
880
|
parsedInput = {};
|
|
498
881
|
}
|
|
499
882
|
}
|
|
500
|
-
yield
|
|
501
|
-
type:
|
|
883
|
+
yield {
|
|
884
|
+
type: EventType.TOOL_CALL_END,
|
|
502
885
|
toolCallId: item_id,
|
|
503
886
|
toolCallName: name,
|
|
504
887
|
toolName: name,
|
|
505
888
|
model: model || options.model,
|
|
506
|
-
timestamp,
|
|
889
|
+
timestamp: Date.now(),
|
|
507
890
|
input: parsedInput
|
|
508
|
-
}
|
|
891
|
+
};
|
|
892
|
+
}
|
|
893
|
+
if (chunk.type === "response.output_item.done") {
|
|
894
|
+
const item = chunk.item;
|
|
895
|
+
if (item.type === "function_call" && item.id) {
|
|
896
|
+
const metadata = toolCallMetadata.get(item.id) ?? {
|
|
897
|
+
index: chunk.output_index,
|
|
898
|
+
name: item.name || "",
|
|
899
|
+
started: false
|
|
900
|
+
};
|
|
901
|
+
if (!toolCallMetadata.has(item.id)) {
|
|
902
|
+
toolCallMetadata.set(item.id, metadata);
|
|
903
|
+
} else if (!metadata.name && item.name) {
|
|
904
|
+
metadata.name = item.name;
|
|
905
|
+
}
|
|
906
|
+
if (!metadata.started && metadata.name) {
|
|
907
|
+
yield {
|
|
908
|
+
type: EventType.TOOL_CALL_START,
|
|
909
|
+
toolCallId: item.id,
|
|
910
|
+
toolCallName: metadata.name,
|
|
911
|
+
toolName: metadata.name,
|
|
912
|
+
model: model || options.model,
|
|
913
|
+
timestamp: Date.now(),
|
|
914
|
+
index: metadata.index
|
|
915
|
+
};
|
|
916
|
+
metadata.started = true;
|
|
917
|
+
}
|
|
918
|
+
const rawArgs = typeof item.arguments === "string" && item.arguments.length > 0 ? item.arguments : metadata.pendingArguments;
|
|
919
|
+
if (metadata.started && !metadata.ended && rawArgs !== void 0) {
|
|
920
|
+
const name = metadata.name || "";
|
|
921
|
+
let parsedInput = {};
|
|
922
|
+
if (rawArgs) {
|
|
923
|
+
try {
|
|
924
|
+
const parsed = JSON.parse(rawArgs);
|
|
925
|
+
parsedInput = parsed && typeof parsed === "object" ? parsed : {};
|
|
926
|
+
} catch (parseError) {
|
|
927
|
+
options.logger.errors(
|
|
928
|
+
`${this.name}.processStreamChunks tool-args JSON parse failed (output_item.done backfill)`,
|
|
929
|
+
{
|
|
930
|
+
error: toRunErrorPayload(
|
|
931
|
+
parseError,
|
|
932
|
+
`tool ${name} (${item.id}) returned malformed JSON arguments`
|
|
933
|
+
),
|
|
934
|
+
source: `${this.name}.processStreamChunks`,
|
|
935
|
+
toolCallId: item.id,
|
|
936
|
+
toolName: name,
|
|
937
|
+
rawArguments: rawArgs
|
|
938
|
+
}
|
|
939
|
+
);
|
|
940
|
+
parsedInput = {};
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
yield {
|
|
944
|
+
type: EventType.TOOL_CALL_END,
|
|
945
|
+
toolCallId: item.id,
|
|
946
|
+
toolCallName: name,
|
|
947
|
+
toolName: name,
|
|
948
|
+
model: model || options.model,
|
|
949
|
+
timestamp: Date.now(),
|
|
950
|
+
input: parsedInput
|
|
951
|
+
};
|
|
952
|
+
metadata.ended = true;
|
|
953
|
+
metadata.pendingArguments = void 0;
|
|
954
|
+
}
|
|
955
|
+
}
|
|
509
956
|
}
|
|
510
957
|
if (chunk.type === "response.completed") {
|
|
958
|
+
for (const item of chunk.response.output) {
|
|
959
|
+
if (item.type !== "function_call" || !item.id) continue;
|
|
960
|
+
const metadata = toolCallMetadata.get(item.id) ?? {
|
|
961
|
+
index: 0,
|
|
962
|
+
name: item.name || "",
|
|
963
|
+
started: false
|
|
964
|
+
};
|
|
965
|
+
if (!toolCallMetadata.has(item.id)) {
|
|
966
|
+
toolCallMetadata.set(item.id, metadata);
|
|
967
|
+
} else if (!metadata.name && item.name) {
|
|
968
|
+
metadata.name = item.name;
|
|
969
|
+
}
|
|
970
|
+
if (!metadata.started && metadata.name) {
|
|
971
|
+
yield {
|
|
972
|
+
type: EventType.TOOL_CALL_START,
|
|
973
|
+
toolCallId: item.id,
|
|
974
|
+
toolCallName: metadata.name,
|
|
975
|
+
toolName: metadata.name,
|
|
976
|
+
model: model || options.model,
|
|
977
|
+
timestamp: Date.now(),
|
|
978
|
+
index: metadata.index
|
|
979
|
+
};
|
|
980
|
+
metadata.started = true;
|
|
981
|
+
}
|
|
982
|
+
const rawArgs = typeof item.arguments === "string" && item.arguments.length > 0 ? item.arguments : metadata.pendingArguments;
|
|
983
|
+
if (metadata.started && !metadata.ended) {
|
|
984
|
+
const name = metadata.name || "";
|
|
985
|
+
let parsedInput = {};
|
|
986
|
+
if (rawArgs) {
|
|
987
|
+
try {
|
|
988
|
+
const parsed = JSON.parse(rawArgs);
|
|
989
|
+
parsedInput = parsed && typeof parsed === "object" ? parsed : {};
|
|
990
|
+
} catch (parseError) {
|
|
991
|
+
options.logger.errors(
|
|
992
|
+
`${this.name}.processStreamChunks tool-args JSON parse failed (response.completed backfill)`,
|
|
993
|
+
{
|
|
994
|
+
error: toRunErrorPayload(
|
|
995
|
+
parseError,
|
|
996
|
+
`tool ${name} (${item.id}) returned malformed JSON arguments`
|
|
997
|
+
),
|
|
998
|
+
source: `${this.name}.processStreamChunks`,
|
|
999
|
+
toolCallId: item.id,
|
|
1000
|
+
toolName: name,
|
|
1001
|
+
rawArguments: rawArgs
|
|
1002
|
+
}
|
|
1003
|
+
);
|
|
1004
|
+
parsedInput = {};
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
yield {
|
|
1008
|
+
type: EventType.TOOL_CALL_END,
|
|
1009
|
+
toolCallId: item.id,
|
|
1010
|
+
toolCallName: name,
|
|
1011
|
+
toolName: name,
|
|
1012
|
+
model: model || options.model,
|
|
1013
|
+
timestamp: Date.now(),
|
|
1014
|
+
input: parsedInput
|
|
1015
|
+
};
|
|
1016
|
+
metadata.ended = true;
|
|
1017
|
+
metadata.pendingArguments = void 0;
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
511
1020
|
if (hasEmittedTextMessageStart) {
|
|
512
|
-
yield
|
|
513
|
-
type:
|
|
1021
|
+
yield {
|
|
1022
|
+
type: EventType.TEXT_MESSAGE_END,
|
|
514
1023
|
messageId: aguiState.messageId,
|
|
515
1024
|
model: model || options.model,
|
|
516
|
-
timestamp
|
|
517
|
-
}
|
|
1025
|
+
timestamp: Date.now()
|
|
1026
|
+
};
|
|
518
1027
|
hasEmittedTextMessageStart = false;
|
|
519
1028
|
}
|
|
520
1029
|
const hasFunctionCalls = chunk.response.output.some(
|
|
521
1030
|
(item) => item.type === "function_call"
|
|
522
1031
|
);
|
|
523
|
-
const
|
|
524
|
-
|
|
525
|
-
|
|
1032
|
+
const incompleteReason = chunk.response.incomplete_details?.reason;
|
|
1033
|
+
const finishReason = hasFunctionCalls ? "tool_calls" : incompleteReason === "max_output_tokens" ? "length" : incompleteReason === "content_filter" ? "content_filter" : "stop";
|
|
1034
|
+
yield {
|
|
1035
|
+
type: EventType.RUN_FINISHED,
|
|
526
1036
|
runId: aguiState.runId,
|
|
1037
|
+
threadId: aguiState.threadId,
|
|
527
1038
|
model: model || options.model,
|
|
528
|
-
timestamp,
|
|
1039
|
+
timestamp: Date.now(),
|
|
529
1040
|
usage: {
|
|
530
1041
|
promptTokens: chunk.response.usage?.input_tokens || 0,
|
|
531
1042
|
completionTokens: chunk.response.usage?.output_tokens || 0,
|
|
532
1043
|
totalTokens: chunk.response.usage?.total_tokens || 0
|
|
533
1044
|
},
|
|
534
1045
|
finishReason
|
|
535
|
-
}
|
|
1046
|
+
};
|
|
536
1047
|
runFinishedEmitted = true;
|
|
537
1048
|
}
|
|
538
1049
|
if (chunk.type === "error") {
|
|
539
|
-
yield
|
|
540
|
-
type:
|
|
541
|
-
runId: aguiState.runId,
|
|
1050
|
+
yield {
|
|
1051
|
+
type: EventType.RUN_ERROR,
|
|
542
1052
|
model: model || options.model,
|
|
543
|
-
timestamp,
|
|
1053
|
+
timestamp: Date.now(),
|
|
1054
|
+
message: chunk.message,
|
|
1055
|
+
code: chunk.code ?? void 0,
|
|
544
1056
|
error: {
|
|
545
1057
|
message: chunk.message,
|
|
546
1058
|
code: chunk.code ?? void 0
|
|
547
1059
|
}
|
|
548
|
-
}
|
|
1060
|
+
};
|
|
549
1061
|
runFinishedEmitted = true;
|
|
1062
|
+
return;
|
|
550
1063
|
}
|
|
551
1064
|
}
|
|
552
1065
|
if (!runFinishedEmitted && aguiState.hasEmittedRunStarted) {
|
|
553
1066
|
if (hasEmittedTextMessageStart) {
|
|
554
|
-
yield
|
|
555
|
-
type:
|
|
1067
|
+
yield {
|
|
1068
|
+
type: EventType.TEXT_MESSAGE_END,
|
|
556
1069
|
messageId: aguiState.messageId,
|
|
557
1070
|
model: model || options.model,
|
|
558
|
-
timestamp
|
|
559
|
-
}
|
|
1071
|
+
timestamp: Date.now()
|
|
1072
|
+
};
|
|
560
1073
|
}
|
|
561
|
-
yield
|
|
562
|
-
type:
|
|
1074
|
+
yield {
|
|
1075
|
+
type: EventType.RUN_FINISHED,
|
|
563
1076
|
runId: aguiState.runId,
|
|
1077
|
+
threadId: aguiState.threadId,
|
|
564
1078
|
model: model || options.model,
|
|
565
|
-
timestamp,
|
|
1079
|
+
timestamp: Date.now(),
|
|
566
1080
|
usage: void 0,
|
|
567
1081
|
finishReason: toolCallMetadata.size > 0 ? "tool_calls" : "stop"
|
|
568
|
-
}
|
|
1082
|
+
};
|
|
569
1083
|
}
|
|
570
1084
|
} catch (error) {
|
|
571
1085
|
const errorPayload = toRunErrorPayload(
|
|
@@ -576,13 +1090,14 @@ class OpenAICompatibleResponsesTextAdapter extends BaseTextAdapter {
|
|
|
576
1090
|
error: errorPayload,
|
|
577
1091
|
source: `${this.name}.processStreamChunks`
|
|
578
1092
|
});
|
|
579
|
-
yield
|
|
580
|
-
type:
|
|
581
|
-
runId: aguiState.runId,
|
|
1093
|
+
yield {
|
|
1094
|
+
type: EventType.RUN_ERROR,
|
|
582
1095
|
model: options.model,
|
|
583
|
-
timestamp,
|
|
1096
|
+
timestamp: Date.now(),
|
|
1097
|
+
message: errorPayload.message,
|
|
1098
|
+
code: errorPayload.code,
|
|
584
1099
|
error: errorPayload
|
|
585
|
-
}
|
|
1100
|
+
};
|
|
586
1101
|
}
|
|
587
1102
|
}
|
|
588
1103
|
/**
|
|
@@ -755,6 +1270,6 @@ class OpenAICompatibleResponsesTextAdapter extends BaseTextAdapter {
|
|
|
755
1270
|
}
|
|
756
1271
|
}
|
|
757
1272
|
export {
|
|
758
|
-
|
|
1273
|
+
OpenAIBaseResponsesTextAdapter
|
|
759
1274
|
};
|
|
760
1275
|
//# sourceMappingURL=responses-text.js.map
|