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