@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.
Files changed (79) hide show
  1. package/README.md +121 -0
  2. package/dist/esm/adapters/chat-completions-text.d.ts +49 -21
  3. package/dist/esm/adapters/chat-completions-text.js +476 -68
  4. package/dist/esm/adapters/chat-completions-text.js.map +1 -1
  5. package/dist/esm/adapters/chat-completions-tool-converter.d.ts +8 -4
  6. package/dist/esm/adapters/chat-completions-tool-converter.js.map +1 -1
  7. package/dist/esm/adapters/responses-text.d.ts +46 -33
  8. package/dist/esm/adapters/responses-text.js +657 -142
  9. package/dist/esm/adapters/responses-text.js.map +1 -1
  10. package/dist/esm/index.d.ts +2 -9
  11. package/dist/esm/index.js +4 -16
  12. package/dist/esm/index.js.map +1 -1
  13. package/dist/esm/tools/apply-patch-tool.d.ts +2 -2
  14. package/dist/esm/tools/apply-patch-tool.js.map +1 -1
  15. package/dist/esm/tools/code-interpreter-tool.d.ts +3 -2
  16. package/dist/esm/tools/code-interpreter-tool.js.map +1 -1
  17. package/dist/esm/tools/computer-use-tool.d.ts +2 -2
  18. package/dist/esm/tools/computer-use-tool.js.map +1 -1
  19. package/dist/esm/tools/custom-tool.d.ts +2 -2
  20. package/dist/esm/tools/custom-tool.js.map +1 -1
  21. package/dist/esm/tools/file-search-tool.d.ts +2 -2
  22. package/dist/esm/tools/file-search-tool.js.map +1 -1
  23. package/dist/esm/tools/function-tool.d.ts +2 -2
  24. package/dist/esm/tools/function-tool.js.map +1 -1
  25. package/dist/esm/tools/image-generation-tool.d.ts +3 -2
  26. package/dist/esm/tools/image-generation-tool.js.map +1 -1
  27. package/dist/esm/tools/local-shell-tool.d.ts +3 -2
  28. package/dist/esm/tools/local-shell-tool.js.map +1 -1
  29. package/dist/esm/tools/mcp-tool.d.ts +3 -2
  30. package/dist/esm/tools/mcp-tool.js.map +1 -1
  31. package/dist/esm/tools/shell-tool.d.ts +2 -2
  32. package/dist/esm/tools/shell-tool.js.map +1 -1
  33. package/dist/esm/tools/web-search-preview-tool.d.ts +2 -2
  34. package/dist/esm/tools/web-search-preview-tool.js.map +1 -1
  35. package/dist/esm/tools/web-search-tool.d.ts +2 -2
  36. package/dist/esm/tools/web-search-tool.js.map +1 -1
  37. package/package.json +6 -6
  38. package/src/adapters/chat-completions-text.ts +601 -117
  39. package/src/adapters/chat-completions-tool-converter.ts +9 -5
  40. package/src/adapters/responses-text.ts +865 -210
  41. package/src/index.ts +2 -12
  42. package/src/tools/apply-patch-tool.ts +2 -2
  43. package/src/tools/code-interpreter-tool.ts +4 -2
  44. package/src/tools/computer-use-tool.ts +2 -2
  45. package/src/tools/custom-tool.ts +2 -2
  46. package/src/tools/file-search-tool.ts +3 -3
  47. package/src/tools/function-tool.ts +2 -2
  48. package/src/tools/image-generation-tool.ts +4 -2
  49. package/src/tools/local-shell-tool.ts +4 -2
  50. package/src/tools/mcp-tool.ts +4 -2
  51. package/src/tools/shell-tool.ts +2 -2
  52. package/src/tools/web-search-preview-tool.ts +2 -2
  53. package/src/tools/web-search-tool.ts +2 -2
  54. package/dist/esm/adapters/image.d.ts +0 -32
  55. package/dist/esm/adapters/image.js +0 -89
  56. package/dist/esm/adapters/image.js.map +0 -1
  57. package/dist/esm/adapters/summarize.d.ts +0 -28
  58. package/dist/esm/adapters/summarize.js +0 -112
  59. package/dist/esm/adapters/summarize.js.map +0 -1
  60. package/dist/esm/adapters/transcription.d.ts +0 -34
  61. package/dist/esm/adapters/transcription.js +0 -131
  62. package/dist/esm/adapters/transcription.js.map +0 -1
  63. package/dist/esm/adapters/tts.d.ts +0 -26
  64. package/dist/esm/adapters/tts.js +0 -78
  65. package/dist/esm/adapters/tts.js.map +0 -1
  66. package/dist/esm/adapters/video.d.ts +0 -72
  67. package/dist/esm/adapters/video.js +0 -238
  68. package/dist/esm/adapters/video.js.map +0 -1
  69. package/dist/esm/types/config.d.ts +0 -4
  70. package/dist/esm/utils/client.d.ts +0 -3
  71. package/dist/esm/utils/client.js +0 -8
  72. package/dist/esm/utils/client.js.map +0 -1
  73. package/src/adapters/image.ts +0 -158
  74. package/src/adapters/summarize.ts +0 -174
  75. package/src/adapters/transcription.ts +0 -194
  76. package/src/adapters/tts.ts +0 -124
  77. package/src/adapters/video.ts +0 -385
  78. package/src/types/config.ts +0 -5
  79. package/src/utils/client.ts +0 -8
@@ -1,28 +1,26 @@
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 { convertToolsToChatCompletionsFormat } from "./chat-completions-tool-converter.js";
8
- const asChunk = (chunk) => chunk;
9
- class OpenAICompatibleChatCompletionsTextAdapter extends BaseTextAdapter {
10
- constructor(config, model, name = "openai-compatible") {
8
+ class OpenAIBaseChatCompletionsTextAdapter extends BaseTextAdapter {
9
+ constructor(model, name, client) {
11
10
  super({}, model);
12
11
  this.kind = "text";
13
12
  this.name = name;
14
- this.client = createOpenAICompatibleClient(config);
13
+ this.client = client;
15
14
  }
16
15
  async *chatStream(options) {
17
- const requestParams = this.mapOptionsToRequest(options);
18
- const timestamp = Date.now();
19
16
  const aguiState = {
20
17
  runId: generateId(this.name),
18
+ threadId: options.threadId ?? generateId(this.name),
21
19
  messageId: generateId(this.name),
22
- timestamp,
23
20
  hasEmittedRunStarted: false
24
21
  };
25
22
  try {
23
+ const requestParams = this.mapOptionsToRequest(options);
26
24
  options.logger.request(
27
25
  `activity=chat provider=${this.name} model=${this.model} messages=${options.messages.length} tools=${options.tools?.length ?? 0} stream=true`,
28
26
  { provider: this.name, model: this.model }
@@ -43,20 +41,22 @@ class OpenAICompatibleChatCompletionsTextAdapter extends BaseTextAdapter {
43
41
  );
44
42
  if (!aguiState.hasEmittedRunStarted) {
45
43
  aguiState.hasEmittedRunStarted = true;
46
- yield asChunk({
47
- type: "RUN_STARTED",
44
+ yield {
45
+ type: EventType.RUN_STARTED,
48
46
  runId: aguiState.runId,
47
+ threadId: aguiState.threadId,
49
48
  model: options.model,
50
- timestamp
51
- });
49
+ timestamp: Date.now()
50
+ };
52
51
  }
53
- yield asChunk({
54
- type: "RUN_ERROR",
55
- runId: aguiState.runId,
52
+ yield {
53
+ type: EventType.RUN_ERROR,
56
54
  model: options.model,
57
- timestamp,
55
+ timestamp: Date.now(),
56
+ message: errorPayload.message,
57
+ code: errorPayload.code,
58
58
  error: errorPayload
59
- });
59
+ };
60
60
  options.logger.errors(`${this.name}.chatStream fatal`, {
61
61
  error: errorPayload,
62
62
  source: `${this.name}.chatStream`
@@ -107,7 +107,12 @@ class OpenAICompatibleChatCompletionsTextAdapter extends BaseTextAdapter {
107
107
  },
108
108
  extractRequestOptions(chatOptions.request)
109
109
  );
110
- const rawText = response.choices[0]?.message.content || "";
110
+ const rawText = response.choices[0]?.message.content;
111
+ if (typeof rawText !== "string" || rawText.length === 0) {
112
+ throw new Error(
113
+ `${this.name}.structuredOutput: response contained no content`
114
+ );
115
+ }
111
116
  let parsed;
112
117
  try {
113
118
  parsed = JSON.parse(rawText);
@@ -116,7 +121,7 @@ class OpenAICompatibleChatCompletionsTextAdapter extends BaseTextAdapter {
116
121
  `Failed to parse structured output as JSON. Content: ${rawText.slice(0, 200)}${rawText.length > 200 ? "..." : ""}`
117
122
  );
118
123
  }
119
- const transformed = transformNullsToUndefined(parsed);
124
+ const transformed = this.transformStructuredOutput(parsed);
120
125
  return {
121
126
  data: transformed,
122
127
  rawText
@@ -129,6 +134,284 @@ class OpenAICompatibleChatCompletionsTextAdapter extends BaseTextAdapter {
129
134
  throw error;
130
135
  }
131
136
  }
137
+ /**
138
+ * Stream structured output. Single Chat Completions request with
139
+ * `response_format: json_schema` + `stream: true`. Emits the standard
140
+ * AG-UI lifecycle (`RUN_STARTED` → `REASONING_*?` → `TEXT_MESSAGE_*`
141
+ * carrying raw JSON deltas → terminal `CUSTOM 'structured-output.complete'`
142
+ * → `RUN_FINISHED`). Subclasses use the same SDK-call / reasoning /
143
+ * structured-output-transform hooks as `chatStream` / `structuredOutput` —
144
+ * no per-subclass override should be needed.
145
+ */
146
+ async *structuredOutputStream(options) {
147
+ const { chatOptions, outputSchema } = options;
148
+ const requestParams = this.mapOptionsToRequest(chatOptions);
149
+ const jsonSchema = this.makeStructuredOutputCompatible(
150
+ outputSchema,
151
+ outputSchema.required
152
+ );
153
+ const timestamp = Date.now();
154
+ const aguiState = {
155
+ runId: generateId(this.name),
156
+ threadId: chatOptions.threadId ?? generateId(this.name),
157
+ messageId: generateId(this.name),
158
+ timestamp,
159
+ hasEmittedRunStarted: false
160
+ };
161
+ let accumulatedContent = "";
162
+ let accumulatedReasoning = "";
163
+ let hasEmittedTextMessageStart = false;
164
+ let reasoningMessageId;
165
+ let hasClosedReasoning = false;
166
+ let stepId;
167
+ let lastModel;
168
+ let lastUsage;
169
+ const closeReasoningLifecycle = (function* () {
170
+ if (reasoningMessageId && !hasClosedReasoning) {
171
+ hasClosedReasoning = true;
172
+ yield {
173
+ type: EventType.REASONING_MESSAGE_END,
174
+ messageId: reasoningMessageId,
175
+ model: lastModel || chatOptions.model,
176
+ timestamp
177
+ };
178
+ yield {
179
+ type: EventType.REASONING_END,
180
+ messageId: reasoningMessageId,
181
+ model: lastModel || chatOptions.model,
182
+ timestamp
183
+ };
184
+ if (stepId) {
185
+ yield {
186
+ type: EventType.STEP_FINISHED,
187
+ stepName: stepId,
188
+ stepId,
189
+ model: lastModel || chatOptions.model,
190
+ timestamp,
191
+ content: accumulatedReasoning
192
+ };
193
+ }
194
+ }
195
+ }).bind(this);
196
+ try {
197
+ const {
198
+ stream_options: _so,
199
+ stream: _s,
200
+ tools: _t,
201
+ ...cleanParams
202
+ } = requestParams;
203
+ chatOptions.logger.request(
204
+ `activity=structuredOutputStream provider=${this.name} model=${this.model} messages=${chatOptions.messages.length}`,
205
+ { provider: this.name, model: this.model }
206
+ );
207
+ const stream = await this.client.chat.completions.create(
208
+ {
209
+ ...cleanParams,
210
+ stream: true,
211
+ stream_options: { include_usage: true },
212
+ response_format: {
213
+ type: "json_schema",
214
+ json_schema: {
215
+ name: "structured_output",
216
+ schema: jsonSchema,
217
+ strict: true
218
+ }
219
+ }
220
+ },
221
+ extractRequestOptions(chatOptions.request)
222
+ );
223
+ for await (const chunk of stream) {
224
+ const choiceForLog = chunk.choices[0];
225
+ chatOptions.logger.provider(
226
+ `provider=${this.name} finish_reason=${choiceForLog?.finish_reason ?? "none"} hasContent=${!!choiceForLog?.delta.content} hasUsage=${!!chunk.usage}`,
227
+ { provider: this.name, model: chunk.model }
228
+ );
229
+ if (chunk.model) lastModel = chunk.model;
230
+ const usage = chunk.usage ?? chunk.x_groq?.usage;
231
+ if (usage) lastUsage = usage;
232
+ if (!aguiState.hasEmittedRunStarted) {
233
+ aguiState.hasEmittedRunStarted = true;
234
+ yield {
235
+ type: EventType.RUN_STARTED,
236
+ runId: aguiState.runId,
237
+ threadId: aguiState.threadId,
238
+ model: chunk.model || chatOptions.model,
239
+ timestamp
240
+ };
241
+ }
242
+ const reasoning = this.extractReasoning(chunk);
243
+ if (reasoning && reasoning.text) {
244
+ if (!reasoningMessageId) {
245
+ reasoningMessageId = generateId(this.name);
246
+ stepId = generateId(this.name);
247
+ yield {
248
+ type: EventType.REASONING_START,
249
+ messageId: reasoningMessageId,
250
+ model: chunk.model || chatOptions.model,
251
+ timestamp
252
+ };
253
+ yield {
254
+ type: EventType.REASONING_MESSAGE_START,
255
+ messageId: reasoningMessageId,
256
+ role: "reasoning",
257
+ model: chunk.model || chatOptions.model,
258
+ timestamp
259
+ };
260
+ yield {
261
+ type: EventType.STEP_STARTED,
262
+ stepName: stepId,
263
+ stepId,
264
+ model: chunk.model || chatOptions.model,
265
+ timestamp,
266
+ stepType: "thinking"
267
+ };
268
+ }
269
+ accumulatedReasoning += reasoning.text;
270
+ yield {
271
+ type: EventType.REASONING_MESSAGE_CONTENT,
272
+ messageId: reasoningMessageId,
273
+ delta: reasoning.text,
274
+ model: chunk.model || chatOptions.model,
275
+ timestamp
276
+ };
277
+ }
278
+ const choice = chunk.choices[0];
279
+ if (!choice) continue;
280
+ const deltaContent = choice.delta.content;
281
+ if (deltaContent) {
282
+ yield* closeReasoningLifecycle();
283
+ if (!hasEmittedTextMessageStart) {
284
+ hasEmittedTextMessageStart = true;
285
+ yield {
286
+ type: EventType.TEXT_MESSAGE_START,
287
+ messageId: aguiState.messageId,
288
+ model: chunk.model || chatOptions.model,
289
+ timestamp,
290
+ role: "assistant"
291
+ };
292
+ }
293
+ accumulatedContent += deltaContent;
294
+ yield {
295
+ type: EventType.TEXT_MESSAGE_CONTENT,
296
+ messageId: aguiState.messageId,
297
+ model: chunk.model || chatOptions.model,
298
+ timestamp,
299
+ delta: deltaContent,
300
+ content: accumulatedContent
301
+ };
302
+ }
303
+ }
304
+ yield* closeReasoningLifecycle();
305
+ if (hasEmittedTextMessageStart) {
306
+ yield {
307
+ type: EventType.TEXT_MESSAGE_END,
308
+ messageId: aguiState.messageId,
309
+ model: lastModel || chatOptions.model,
310
+ timestamp
311
+ };
312
+ }
313
+ if (accumulatedContent.length === 0) {
314
+ yield {
315
+ type: EventType.RUN_ERROR,
316
+ runId: aguiState.runId,
317
+ model: lastModel || chatOptions.model,
318
+ timestamp,
319
+ message: `${this.name}.structuredOutputStream: response contained no content`,
320
+ code: "empty-response",
321
+ error: {
322
+ message: `${this.name}.structuredOutputStream: response contained no content`,
323
+ code: "empty-response"
324
+ }
325
+ };
326
+ return;
327
+ }
328
+ let parsed;
329
+ try {
330
+ parsed = JSON.parse(accumulatedContent);
331
+ } catch {
332
+ yield {
333
+ type: EventType.RUN_ERROR,
334
+ runId: aguiState.runId,
335
+ model: lastModel || chatOptions.model,
336
+ timestamp,
337
+ message: `Failed to parse structured output as JSON. Content: ${accumulatedContent.slice(0, 200)}${accumulatedContent.length > 200 ? "..." : ""}`,
338
+ code: "parse-error",
339
+ error: {
340
+ message: "Failed to parse structured output as JSON",
341
+ code: "parse-error"
342
+ }
343
+ };
344
+ return;
345
+ }
346
+ const transformed = this.transformStructuredOutput(parsed);
347
+ yield {
348
+ type: EventType.CUSTOM,
349
+ name: "structured-output.complete",
350
+ value: {
351
+ object: transformed,
352
+ raw: accumulatedContent,
353
+ ...accumulatedReasoning ? { reasoning: accumulatedReasoning } : {}
354
+ },
355
+ model: lastModel || chatOptions.model,
356
+ timestamp
357
+ };
358
+ yield {
359
+ type: EventType.RUN_FINISHED,
360
+ runId: aguiState.runId,
361
+ threadId: aguiState.threadId,
362
+ model: lastModel || chatOptions.model,
363
+ timestamp,
364
+ finishReason: "stop",
365
+ ...lastUsage && {
366
+ usage: {
367
+ promptTokens: lastUsage.prompt_tokens,
368
+ completionTokens: lastUsage.completion_tokens,
369
+ totalTokens: lastUsage.total_tokens
370
+ }
371
+ }
372
+ };
373
+ } catch (error) {
374
+ if (!aguiState.hasEmittedRunStarted) {
375
+ aguiState.hasEmittedRunStarted = true;
376
+ yield {
377
+ type: EventType.RUN_STARTED,
378
+ runId: aguiState.runId,
379
+ threadId: aguiState.threadId,
380
+ model: chatOptions.model,
381
+ timestamp
382
+ };
383
+ }
384
+ const isAbort = this.isAbortError(error);
385
+ const errorPayload = toRunErrorPayload(
386
+ error,
387
+ `${this.name}.structuredOutputStream failed`
388
+ );
389
+ yield {
390
+ type: EventType.RUN_ERROR,
391
+ runId: aguiState.runId,
392
+ model: lastModel || chatOptions.model,
393
+ timestamp,
394
+ message: errorPayload.message,
395
+ code: isAbort ? "aborted" : errorPayload.code,
396
+ error: { ...errorPayload, ...isAbort && { code: "aborted" } }
397
+ };
398
+ chatOptions.logger.errors(`${this.name}.structuredOutputStream fatal`, {
399
+ error: errorPayload,
400
+ source: `${this.name}.structuredOutputStream`
401
+ });
402
+ }
403
+ }
404
+ /**
405
+ * Cross-SDK abort detection for `structuredOutputStream`. Default duck-types
406
+ * on `name === 'APIUserAbortError'` (OpenAI SDK), `code === 'ERR_CANCELED'`,
407
+ * and standard `AbortError`s. Subclasses with proprietary error types (e.g.
408
+ * `@openrouter/sdk`'s `RequestAbortedError`) override to extend the check.
409
+ */
410
+ isAbortError(error) {
411
+ if (!error || typeof error !== "object") return false;
412
+ const e = error;
413
+ return e.name === "APIUserAbortError" || e.name === "AbortError" || e.code === "ERR_CANCELED";
414
+ }
132
415
  /**
133
416
  * Applies provider-specific transformations for structured output compatibility.
134
417
  * Override this in subclasses to handle provider-specific quirks.
@@ -136,18 +419,41 @@ class OpenAICompatibleChatCompletionsTextAdapter extends BaseTextAdapter {
136
419
  makeStructuredOutputCompatible(schema, originalRequired) {
137
420
  return makeStructuredOutputCompatible(schema, originalRequired);
138
421
  }
422
+ /**
423
+ * Extract reasoning content from a stream chunk. Default returns
424
+ * `undefined` because the OpenAI Chat Completions chunk shape doesn't
425
+ * carry reasoning. The chunk param is typed `unknown` so an override can
426
+ * narrow to its own SDK chunk type without an `as` dance — the base only
427
+ * passes through `processStreamChunks`'s structurally-iterated chunk.
428
+ */
429
+ extractReasoning(_chunk) {
430
+ return void 0;
431
+ }
432
+ /**
433
+ * Final shaping pass applied to parsed structured-output JSON before it is
434
+ * returned to the caller. Default converts `null` values to `undefined` so
435
+ * the result aligns with the original Zod schema's optional-field
436
+ * semantics. Subclasses with different conventions (OpenRouter historically
437
+ * preserves nulls) can override.
438
+ */
439
+ transformStructuredOutput(parsed) {
440
+ return transformNullsToUndefined(parsed);
441
+ }
139
442
  /**
140
443
  * Processes streamed chunks from the Chat Completions API and yields AG-UI events.
141
444
  * Override this in subclasses to handle provider-specific stream behavior.
142
445
  */
143
446
  async *processStreamChunks(stream, options, aguiState) {
144
447
  let accumulatedContent = "";
145
- const timestamp = aguiState.timestamp;
146
448
  let hasEmittedTextMessageStart = false;
147
449
  let lastModel;
148
450
  let lastUsage;
149
451
  let pendingFinishReason;
150
452
  const toolCallsInProgress = /* @__PURE__ */ new Map();
453
+ let reasoningMessageId;
454
+ let hasClosedReasoning = false;
455
+ let stepId;
456
+ let accumulatedReasoning = "";
151
457
  let emittedAnyToolCallEnd = false;
152
458
  try {
153
459
  for await (const chunk of stream) {
@@ -164,12 +470,49 @@ class OpenAICompatibleChatCompletionsTextAdapter extends BaseTextAdapter {
164
470
  }
165
471
  if (!aguiState.hasEmittedRunStarted) {
166
472
  aguiState.hasEmittedRunStarted = true;
167
- yield asChunk({
168
- type: "RUN_STARTED",
473
+ yield {
474
+ type: EventType.RUN_STARTED,
169
475
  runId: aguiState.runId,
476
+ threadId: aguiState.threadId,
170
477
  model: chunk.model || options.model,
171
- timestamp
172
- });
478
+ timestamp: Date.now()
479
+ };
480
+ }
481
+ const reasoning = this.extractReasoning(chunk);
482
+ if (reasoning && reasoning.text) {
483
+ if (!reasoningMessageId) {
484
+ reasoningMessageId = generateId(this.name);
485
+ stepId = generateId(this.name);
486
+ yield {
487
+ type: EventType.REASONING_START,
488
+ messageId: reasoningMessageId,
489
+ model: chunk.model || options.model,
490
+ timestamp: Date.now()
491
+ };
492
+ yield {
493
+ type: EventType.REASONING_MESSAGE_START,
494
+ messageId: reasoningMessageId,
495
+ role: "reasoning",
496
+ model: chunk.model || options.model,
497
+ timestamp: Date.now()
498
+ };
499
+ yield {
500
+ type: EventType.STEP_STARTED,
501
+ stepName: stepId,
502
+ stepId,
503
+ model: chunk.model || options.model,
504
+ timestamp: Date.now(),
505
+ stepType: "thinking"
506
+ };
507
+ }
508
+ accumulatedReasoning += reasoning.text;
509
+ yield {
510
+ type: EventType.REASONING_MESSAGE_CONTENT,
511
+ messageId: reasoningMessageId,
512
+ delta: reasoning.text,
513
+ model: chunk.model || options.model,
514
+ timestamp: Date.now()
515
+ };
173
516
  }
174
517
  const choice = chunk.choices[0];
175
518
  if (!choice) continue;
@@ -177,25 +520,50 @@ class OpenAICompatibleChatCompletionsTextAdapter extends BaseTextAdapter {
177
520
  const deltaContent = delta.content;
178
521
  const deltaToolCalls = delta.tool_calls;
179
522
  if (deltaContent) {
523
+ if (reasoningMessageId && !hasClosedReasoning) {
524
+ hasClosedReasoning = true;
525
+ yield {
526
+ type: EventType.REASONING_MESSAGE_END,
527
+ messageId: reasoningMessageId,
528
+ model: chunk.model || options.model,
529
+ timestamp: Date.now()
530
+ };
531
+ yield {
532
+ type: EventType.REASONING_END,
533
+ messageId: reasoningMessageId,
534
+ model: chunk.model || options.model,
535
+ timestamp: Date.now()
536
+ };
537
+ if (stepId) {
538
+ yield {
539
+ type: EventType.STEP_FINISHED,
540
+ stepName: stepId,
541
+ stepId,
542
+ model: chunk.model || options.model,
543
+ timestamp: Date.now(),
544
+ content: accumulatedReasoning
545
+ };
546
+ }
547
+ }
180
548
  if (!hasEmittedTextMessageStart) {
181
549
  hasEmittedTextMessageStart = true;
182
- yield asChunk({
183
- type: "TEXT_MESSAGE_START",
550
+ yield {
551
+ type: EventType.TEXT_MESSAGE_START,
184
552
  messageId: aguiState.messageId,
185
553
  model: chunk.model || options.model,
186
- timestamp,
554
+ timestamp: Date.now(),
187
555
  role: "assistant"
188
- });
556
+ };
189
557
  }
190
558
  accumulatedContent += deltaContent;
191
- yield asChunk({
192
- type: "TEXT_MESSAGE_CONTENT",
559
+ yield {
560
+ type: EventType.TEXT_MESSAGE_CONTENT,
193
561
  messageId: aguiState.messageId,
194
562
  model: chunk.model || options.model,
195
- timestamp,
563
+ timestamp: Date.now(),
196
564
  delta: deltaContent,
197
565
  content: accumulatedContent
198
- });
566
+ };
199
567
  }
200
568
  if (deltaToolCalls) {
201
569
  for (const toolCallDelta of deltaToolCalls) {
@@ -220,24 +588,24 @@ class OpenAICompatibleChatCompletionsTextAdapter extends BaseTextAdapter {
220
588
  }
221
589
  if (toolCall.id && toolCall.name && !toolCall.started) {
222
590
  toolCall.started = true;
223
- yield asChunk({
224
- type: "TOOL_CALL_START",
591
+ yield {
592
+ type: EventType.TOOL_CALL_START,
225
593
  toolCallId: toolCall.id,
226
594
  toolCallName: toolCall.name,
227
595
  toolName: toolCall.name,
228
596
  model: chunk.model || options.model,
229
- timestamp,
597
+ timestamp: Date.now(),
230
598
  index
231
- });
599
+ };
232
600
  }
233
601
  if (toolCallDelta.function?.arguments && toolCall.started) {
234
- yield asChunk({
235
- type: "TOOL_CALL_ARGS",
602
+ yield {
603
+ type: EventType.TOOL_CALL_ARGS,
236
604
  toolCallId: toolCall.id,
237
605
  model: chunk.model || options.model,
238
- timestamp,
606
+ timestamp: Date.now(),
239
607
  delta: toolCallDelta.function.arguments
240
- });
608
+ };
241
609
  }
242
610
  }
243
611
  }
@@ -267,26 +635,26 @@ class OpenAICompatibleChatCompletionsTextAdapter extends BaseTextAdapter {
267
635
  parsedInput = {};
268
636
  }
269
637
  }
270
- yield asChunk({
271
- type: "TOOL_CALL_END",
638
+ yield {
639
+ type: EventType.TOOL_CALL_END,
272
640
  toolCallId: toolCall.id,
273
641
  toolCallName: toolCall.name,
274
642
  toolName: toolCall.name,
275
643
  model: chunk.model || options.model,
276
- timestamp,
644
+ timestamp: Date.now(),
277
645
  input: parsedInput
278
- });
646
+ };
279
647
  emittedAnyToolCallEnd = true;
280
648
  }
281
649
  toolCallsInProgress.clear();
282
650
  }
283
651
  if (hasEmittedTextMessageStart) {
284
- yield asChunk({
285
- type: "TEXT_MESSAGE_END",
652
+ yield {
653
+ type: EventType.TEXT_MESSAGE_END,
286
654
  messageId: aguiState.messageId,
287
655
  model: chunk.model || options.model,
288
- timestamp
289
- });
656
+ timestamp: Date.now()
657
+ };
290
658
  hasEmittedTextMessageStart = false;
291
659
  }
292
660
  pendingFinishReason = choice.finish_reason;
@@ -301,44 +669,83 @@ class OpenAICompatibleChatCompletionsTextAdapter extends BaseTextAdapter {
301
669
  try {
302
670
  const parsed = JSON.parse(toolCall.arguments);
303
671
  parsedInput = parsed && typeof parsed === "object" ? parsed : {};
304
- } catch {
672
+ } catch (parseError) {
673
+ options.logger.errors(
674
+ `${this.name}.processStreamChunks tool-args JSON parse failed (drain)`,
675
+ {
676
+ error: toRunErrorPayload(
677
+ parseError,
678
+ `tool ${toolCall.name} (${toolCall.id}) returned malformed JSON arguments`
679
+ ),
680
+ source: `${this.name}.processStreamChunks`,
681
+ toolCallId: toolCall.id,
682
+ toolName: toolCall.name,
683
+ rawArguments: toolCall.arguments
684
+ }
685
+ );
305
686
  parsedInput = {};
306
687
  }
307
688
  }
308
- yield asChunk({
309
- type: "TOOL_CALL_END",
689
+ yield {
690
+ type: EventType.TOOL_CALL_END,
310
691
  toolCallId: toolCall.id,
311
692
  toolCallName: toolCall.name,
312
693
  toolName: toolCall.name,
313
694
  model: lastModel || options.model,
314
- timestamp,
695
+ timestamp: Date.now(),
315
696
  input: parsedInput
316
- });
697
+ };
317
698
  pendingToolCount += 1;
318
699
  emittedAnyToolCallEnd = true;
319
700
  }
320
701
  toolCallsInProgress.clear();
321
702
  if (hasEmittedTextMessageStart) {
322
- yield asChunk({
323
- type: "TEXT_MESSAGE_END",
703
+ yield {
704
+ type: EventType.TEXT_MESSAGE_END,
324
705
  messageId: aguiState.messageId,
325
706
  model: lastModel || options.model,
326
- timestamp
327
- });
707
+ timestamp: Date.now()
708
+ };
709
+ }
710
+ if (reasoningMessageId && !hasClosedReasoning) {
711
+ hasClosedReasoning = true;
712
+ yield {
713
+ type: EventType.REASONING_MESSAGE_END,
714
+ messageId: reasoningMessageId,
715
+ model: lastModel || options.model,
716
+ timestamp: Date.now()
717
+ };
718
+ yield {
719
+ type: EventType.REASONING_END,
720
+ messageId: reasoningMessageId,
721
+ model: lastModel || options.model,
722
+ timestamp: Date.now()
723
+ };
724
+ if (stepId) {
725
+ yield {
726
+ type: EventType.STEP_FINISHED,
727
+ stepName: stepId,
728
+ stepId,
729
+ model: lastModel || options.model,
730
+ timestamp: Date.now(),
731
+ content: accumulatedReasoning
732
+ };
733
+ }
328
734
  }
329
- const finishReason = emittedAnyToolCallEnd ? "tool_calls" : pendingFinishReason === "tool_calls" ? "stop" : pendingFinishReason ?? "stop";
330
- yield asChunk({
331
- type: "RUN_FINISHED",
735
+ const finishReason = emittedAnyToolCallEnd ? "tool_calls" : pendingFinishReason === "tool_calls" ? "stop" : pendingFinishReason === "function_call" ? "tool_calls" : pendingFinishReason ?? "stop";
736
+ yield {
737
+ type: EventType.RUN_FINISHED,
332
738
  runId: aguiState.runId,
739
+ threadId: aguiState.threadId,
333
740
  model: lastModel || options.model,
334
- timestamp,
741
+ timestamp: Date.now(),
335
742
  usage: lastUsage ? {
336
743
  promptTokens: lastUsage.prompt_tokens || 0,
337
744
  completionTokens: lastUsage.completion_tokens || 0,
338
745
  totalTokens: lastUsage.total_tokens || 0
339
746
  } : void 0,
340
747
  finishReason
341
- });
748
+ };
342
749
  }
343
750
  } catch (error) {
344
751
  const errorPayload = toRunErrorPayload(
@@ -349,13 +756,14 @@ class OpenAICompatibleChatCompletionsTextAdapter extends BaseTextAdapter {
349
756
  error: errorPayload,
350
757
  source: `${this.name}.processStreamChunks`
351
758
  });
352
- yield asChunk({
353
- type: "RUN_ERROR",
354
- runId: aguiState.runId,
759
+ yield {
760
+ type: EventType.RUN_ERROR,
355
761
  model: options.model,
356
- timestamp,
762
+ timestamp: Date.now(),
763
+ message: errorPayload.message,
764
+ code: errorPayload.code,
357
765
  error: errorPayload
358
- });
766
+ };
359
767
  }
360
768
  }
361
769
  /**
@@ -509,6 +917,6 @@ class OpenAICompatibleChatCompletionsTextAdapter extends BaseTextAdapter {
509
917
  }
510
918
  }
511
919
  export {
512
- OpenAICompatibleChatCompletionsTextAdapter
920
+ OpenAIBaseChatCompletionsTextAdapter
513
921
  };
514
922
  //# sourceMappingURL=chat-completions-text.js.map