@tanstack/openai-base 0.9.12 → 0.9.14

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.
@@ -115,6 +115,53 @@ export abstract class OpenAIBaseChatCompletionsTextAdapter<
115
115
  }
116
116
  }
117
117
 
118
+ const rejectedToolCall = this.extractRejectedToolCall(
119
+ rawEvent,
120
+ errorPayload.message,
121
+ )
122
+ if (rejectedToolCall) {
123
+ const toolCallId = generateId(this.name)
124
+ yield {
125
+ type: EventType.TOOL_CALL_START,
126
+ toolCallId,
127
+ toolCallName: rejectedToolCall.toolName,
128
+ toolName: rejectedToolCall.toolName,
129
+ parentMessageId: aguiState.messageId,
130
+ model: options.model,
131
+ timestamp: Date.now(),
132
+ }
133
+ yield {
134
+ type: EventType.TOOL_CALL_ARGS,
135
+ toolCallId,
136
+ delta: rejectedToolCall.arguments,
137
+ args: rejectedToolCall.arguments,
138
+ model: options.model,
139
+ timestamp: Date.now(),
140
+ }
141
+ yield {
142
+ type: EventType.TOOL_CALL_END,
143
+ toolCallId,
144
+ toolCallName: rejectedToolCall.toolName,
145
+ toolName: rejectedToolCall.toolName,
146
+ ...(rejectedToolCall.input !== undefined && {
147
+ input: rejectedToolCall.input,
148
+ }),
149
+ result: JSON.stringify({ error: rejectedToolCall.error }),
150
+ state: 'output-error',
151
+ model: options.model,
152
+ timestamp: Date.now(),
153
+ }
154
+ yield {
155
+ type: EventType.RUN_FINISHED,
156
+ runId: aguiState.runId,
157
+ threadId: aguiState.threadId,
158
+ model: options.model,
159
+ timestamp: Date.now(),
160
+ finishReason: 'tool_calls',
161
+ }
162
+ return
163
+ }
164
+
118
165
  // Emit AG-UI RUN_ERROR. Conditional `code` spread keeps the wire
119
166
  // shape spec-compliant under `exactOptionalPropertyTypes`: AG-UI's
120
167
  // `RunErrorEvent.code` is `string?` (absent vs explicit `undefined`
@@ -142,6 +189,24 @@ export abstract class OpenAIBaseChatCompletionsTextAdapter<
142
189
  }
143
190
  }
144
191
 
192
+ /**
193
+ * Extracts a rejected tool call from a provider error. Returned calls are
194
+ * emitted as non-executable `output-error` results so the model can repair them.
195
+ */
196
+ protected extractRejectedToolCall(
197
+ _rawEvent: unknown,
198
+ _fallbackMessage: string,
199
+ ):
200
+ | {
201
+ toolName: string
202
+ arguments: string
203
+ input?: unknown
204
+ error: string
205
+ }
206
+ | undefined {
207
+ return undefined
208
+ }
209
+
145
210
  /**
146
211
  * Generate structured output using the provider's JSON Schema response format.
147
212
  * Uses stream: false to get the complete response in one call.
@@ -256,12 +321,10 @@ export abstract class OpenAIBaseChatCompletionsTextAdapter<
256
321
  outputSchema.required,
257
322
  )
258
323
 
259
- const timestamp = Date.now()
260
324
  const aguiState = {
261
325
  runId: generateId(this.name),
262
326
  threadId: chatOptions.threadId ?? generateId(this.name),
263
327
  messageId: generateId(this.name),
264
- timestamp,
265
328
  hasEmittedRunStarted: false,
266
329
  }
267
330
 
@@ -285,13 +348,13 @@ export abstract class OpenAIBaseChatCompletionsTextAdapter<
285
348
  type: EventType.REASONING_MESSAGE_END,
286
349
  messageId: reasoningMessageId,
287
350
  model: lastModel || chatOptions.model,
288
- timestamp,
351
+ timestamp: Date.now(),
289
352
  }
290
353
  yield {
291
354
  type: EventType.REASONING_END,
292
355
  messageId: reasoningMessageId,
293
356
  model: lastModel || chatOptions.model,
294
- timestamp,
357
+ timestamp: Date.now(),
295
358
  }
296
359
  if (stepId) {
297
360
  yield {
@@ -299,7 +362,7 @@ export abstract class OpenAIBaseChatCompletionsTextAdapter<
299
362
  stepName: stepId,
300
363
  stepId,
301
364
  model: lastModel || chatOptions.model,
302
- timestamp,
365
+ timestamp: Date.now(),
303
366
  content: accumulatedReasoning,
304
367
  }
305
368
  }
@@ -364,7 +427,7 @@ export abstract class OpenAIBaseChatCompletionsTextAdapter<
364
427
  runId: aguiState.runId,
365
428
  threadId: aguiState.threadId,
366
429
  model: chunk.model || chatOptions.model,
367
- timestamp,
430
+ timestamp: Date.now(),
368
431
  parentRunId: chatOptions.parentRunId,
369
432
  }
370
433
  }
@@ -379,21 +442,21 @@ export abstract class OpenAIBaseChatCompletionsTextAdapter<
379
442
  type: EventType.REASONING_START,
380
443
  messageId: reasoningMessageId,
381
444
  model: chunk.model || chatOptions.model,
382
- timestamp,
445
+ timestamp: Date.now(),
383
446
  }
384
447
  yield {
385
448
  type: EventType.REASONING_MESSAGE_START,
386
449
  messageId: reasoningMessageId,
387
450
  role: 'reasoning' as const,
388
451
  model: chunk.model || chatOptions.model,
389
- timestamp,
452
+ timestamp: Date.now(),
390
453
  }
391
454
  yield {
392
455
  type: EventType.STEP_STARTED,
393
456
  stepName: stepId,
394
457
  stepId,
395
458
  model: chunk.model || chatOptions.model,
396
- timestamp,
459
+ timestamp: Date.now(),
397
460
  stepType: 'thinking',
398
461
  }
399
462
  }
@@ -403,7 +466,7 @@ export abstract class OpenAIBaseChatCompletionsTextAdapter<
403
466
  messageId: reasoningMessageId,
404
467
  delta: reasoning.text,
405
468
  model: chunk.model || chatOptions.model,
406
- timestamp,
469
+ timestamp: Date.now(),
407
470
  }
408
471
  }
409
472
 
@@ -420,7 +483,7 @@ export abstract class OpenAIBaseChatCompletionsTextAdapter<
420
483
  type: EventType.TEXT_MESSAGE_START,
421
484
  messageId: aguiState.messageId,
422
485
  model: chunk.model || chatOptions.model,
423
- timestamp,
486
+ timestamp: Date.now(),
424
487
  role: 'assistant',
425
488
  }
426
489
  }
@@ -431,7 +494,7 @@ export abstract class OpenAIBaseChatCompletionsTextAdapter<
431
494
  type: EventType.TEXT_MESSAGE_CONTENT,
432
495
  messageId: aguiState.messageId,
433
496
  model: chunk.model || chatOptions.model,
434
- timestamp,
497
+ timestamp: Date.now(),
435
498
  delta: deltaContent,
436
499
  content: accumulatedContent,
437
500
  }
@@ -448,7 +511,7 @@ export abstract class OpenAIBaseChatCompletionsTextAdapter<
448
511
  type: EventType.TEXT_MESSAGE_END,
449
512
  messageId: aguiState.messageId,
450
513
  model: lastModel || chatOptions.model,
451
- timestamp,
514
+ timestamp: Date.now(),
452
515
  }
453
516
  }
454
517
 
@@ -457,7 +520,7 @@ export abstract class OpenAIBaseChatCompletionsTextAdapter<
457
520
  type: EventType.RUN_ERROR,
458
521
  runId: aguiState.runId,
459
522
  model: lastModel || chatOptions.model,
460
- timestamp,
523
+ timestamp: Date.now(),
461
524
  message: `${this.name}.structuredOutputStream: response contained no content`,
462
525
  code: 'empty-response',
463
526
  error: {
@@ -476,7 +539,7 @@ export abstract class OpenAIBaseChatCompletionsTextAdapter<
476
539
  type: EventType.RUN_ERROR,
477
540
  runId: aguiState.runId,
478
541
  model: lastModel || chatOptions.model,
479
- timestamp,
542
+ timestamp: Date.now(),
480
543
  message: `Failed to parse structured output as JSON. Content: ${accumulatedContent.slice(0, 200)}${accumulatedContent.length > 200 ? '...' : ''}`,
481
544
  code: 'parse-error',
482
545
  error: {
@@ -498,7 +561,7 @@ export abstract class OpenAIBaseChatCompletionsTextAdapter<
498
561
  ...(accumulatedReasoning ? { reasoning: accumulatedReasoning } : {}),
499
562
  },
500
563
  model: lastModel || chatOptions.model,
501
- timestamp,
564
+ timestamp: Date.now(),
502
565
  }
503
566
 
504
567
  yield {
@@ -506,7 +569,7 @@ export abstract class OpenAIBaseChatCompletionsTextAdapter<
506
569
  runId: aguiState.runId,
507
570
  threadId: aguiState.threadId,
508
571
  model: lastModel || chatOptions.model,
509
- timestamp,
572
+ timestamp: Date.now(),
510
573
  finishReason: 'stop',
511
574
  ...(lastUsage && {
512
575
  usage: buildChatCompletionsUsage(lastUsage),
@@ -520,7 +583,7 @@ export abstract class OpenAIBaseChatCompletionsTextAdapter<
520
583
  runId: aguiState.runId,
521
584
  threadId: aguiState.threadId,
522
585
  model: chatOptions.model,
523
- timestamp,
586
+ timestamp: Date.now(),
524
587
  parentRunId: chatOptions.parentRunId,
525
588
  }
526
589
  }
@@ -540,7 +603,7 @@ export abstract class OpenAIBaseChatCompletionsTextAdapter<
540
603
  type: EventType.RUN_ERROR,
541
604
  runId: aguiState.runId,
542
605
  model: lastModel || chatOptions.model,
543
- timestamp,
606
+ timestamp: Date.now(),
544
607
  message: errorPayload.message,
545
608
  ...(resolvedCode !== undefined && { code: resolvedCode }),
546
609
  ...(rawEvent !== undefined && { rawEvent }),
@@ -303,12 +303,10 @@ export abstract class OpenAIBaseResponsesTextAdapter<
303
303
  outputSchema.required,
304
304
  )
305
305
 
306
- const timestamp = Date.now()
307
306
  const aguiState = {
308
307
  runId: generateId(this.name),
309
308
  threadId: chatOptions.threadId ?? generateId(this.name),
310
309
  messageId: generateId(this.name),
311
- timestamp,
312
310
  hasEmittedRunStarted: false,
313
311
  }
314
312
 
@@ -330,13 +328,13 @@ export abstract class OpenAIBaseResponsesTextAdapter<
330
328
  type: EventType.REASONING_MESSAGE_END,
331
329
  messageId: reasoningMessageId,
332
330
  model,
333
- timestamp,
331
+ timestamp: Date.now(),
334
332
  }
335
333
  yield {
336
334
  type: EventType.REASONING_END,
337
335
  messageId: reasoningMessageId,
338
336
  model,
339
- timestamp,
337
+ timestamp: Date.now(),
340
338
  }
341
339
  if (stepId) {
342
340
  yield {
@@ -344,7 +342,7 @@ export abstract class OpenAIBaseResponsesTextAdapter<
344
342
  stepName: stepId,
345
343
  stepId,
346
344
  model,
347
- timestamp,
345
+ timestamp: Date.now(),
348
346
  content: accumulatedReasoning,
349
347
  }
350
348
  }
@@ -361,21 +359,21 @@ export abstract class OpenAIBaseResponsesTextAdapter<
361
359
  type: EventType.REASONING_START,
362
360
  messageId: reasoningMessageId,
363
361
  model,
364
- timestamp,
362
+ timestamp: Date.now(),
365
363
  }
366
364
  yield {
367
365
  type: EventType.REASONING_MESSAGE_START,
368
366
  messageId: reasoningMessageId,
369
367
  role: 'reasoning' as const,
370
368
  model,
371
- timestamp,
369
+ timestamp: Date.now(),
372
370
  }
373
371
  yield {
374
372
  type: EventType.STEP_STARTED,
375
373
  stepName: stepId,
376
374
  stepId,
377
375
  model,
378
- timestamp,
376
+ timestamp: Date.now(),
379
377
  stepType: 'thinking',
380
378
  }
381
379
  }.bind(this)
@@ -418,7 +416,7 @@ export abstract class OpenAIBaseResponsesTextAdapter<
418
416
  runId: aguiState.runId,
419
417
  threadId: aguiState.threadId,
420
418
  model,
421
- timestamp,
419
+ timestamp: Date.now(),
422
420
  parentRunId: chatOptions.parentRunId,
423
421
  }
424
422
  }
@@ -442,7 +440,7 @@ export abstract class OpenAIBaseResponsesTextAdapter<
442
440
  type: EventType.RUN_ERROR,
443
441
  runId: aguiState.runId,
444
442
  model,
445
- timestamp,
443
+ timestamp: Date.now(),
446
444
  message: `Model refused: ${delta}`,
447
445
  code: 'refusal',
448
446
  error: { message: `Model refused: ${delta}`, code: 'refusal' },
@@ -471,7 +469,7 @@ export abstract class OpenAIBaseResponsesTextAdapter<
471
469
  messageId: reasoningMessageId,
472
470
  delta: reasoningDelta,
473
471
  model,
474
- timestamp,
472
+ timestamp: Date.now(),
475
473
  }
476
474
  continue
477
475
  }
@@ -493,7 +491,7 @@ export abstract class OpenAIBaseResponsesTextAdapter<
493
491
  type: EventType.TEXT_MESSAGE_START,
494
492
  messageId: aguiState.messageId,
495
493
  model,
496
- timestamp,
494
+ timestamp: Date.now(),
497
495
  role: 'assistant',
498
496
  }
499
497
  }
@@ -502,7 +500,7 @@ export abstract class OpenAIBaseResponsesTextAdapter<
502
500
  type: EventType.TEXT_MESSAGE_CONTENT,
503
501
  messageId: aguiState.messageId,
504
502
  model,
505
- timestamp,
503
+ timestamp: Date.now(),
506
504
  delta: textDelta,
507
505
  content: accumulatedContent,
508
506
  }
@@ -531,7 +529,7 @@ export abstract class OpenAIBaseResponsesTextAdapter<
531
529
  type: EventType.RUN_ERROR,
532
530
  runId: aguiState.runId,
533
531
  model,
534
- timestamp,
532
+ timestamp: Date.now(),
535
533
  message,
536
534
  ...(code !== undefined && { code }),
537
535
  error: { message, ...(code !== undefined && { code }) },
@@ -547,7 +545,7 @@ export abstract class OpenAIBaseResponsesTextAdapter<
547
545
  type: EventType.TEXT_MESSAGE_END,
548
546
  messageId: aguiState.messageId,
549
547
  model,
550
- timestamp,
548
+ timestamp: Date.now(),
551
549
  }
552
550
  }
553
551
 
@@ -556,7 +554,7 @@ export abstract class OpenAIBaseResponsesTextAdapter<
556
554
  type: EventType.RUN_ERROR,
557
555
  runId: aguiState.runId,
558
556
  model,
559
- timestamp,
557
+ timestamp: Date.now(),
560
558
  message: `${this.name}.structuredOutputStream: response contained no content`,
561
559
  code: 'empty-response',
562
560
  error: {
@@ -575,7 +573,7 @@ export abstract class OpenAIBaseResponsesTextAdapter<
575
573
  type: EventType.RUN_ERROR,
576
574
  runId: aguiState.runId,
577
575
  model,
578
- timestamp,
576
+ timestamp: Date.now(),
579
577
  message: `Failed to parse structured output as JSON. Content: ${accumulatedContent.slice(0, 200)}${accumulatedContent.length > 200 ? '...' : ''}`,
580
578
  code: 'parse-error',
581
579
  error: {
@@ -600,7 +598,7 @@ export abstract class OpenAIBaseResponsesTextAdapter<
600
598
  ...(accumulatedReasoning ? { reasoning: accumulatedReasoning } : {}),
601
599
  },
602
600
  model,
603
- timestamp,
601
+ timestamp: Date.now(),
604
602
  }
605
603
 
606
604
  yield {
@@ -608,7 +606,7 @@ export abstract class OpenAIBaseResponsesTextAdapter<
608
606
  runId: aguiState.runId,
609
607
  threadId: aguiState.threadId,
610
608
  model,
611
- timestamp,
609
+ timestamp: Date.now(),
612
610
  finishReason: 'stop',
613
611
  ...(usage && {
614
612
  usage: buildResponsesUsage(usage),
@@ -622,7 +620,7 @@ export abstract class OpenAIBaseResponsesTextAdapter<
622
620
  runId: aguiState.runId,
623
621
  threadId: aguiState.threadId,
624
622
  model,
625
- timestamp,
623
+ timestamp: Date.now(),
626
624
  parentRunId: chatOptions.parentRunId,
627
625
  }
628
626
  }
@@ -641,7 +639,7 @@ export abstract class OpenAIBaseResponsesTextAdapter<
641
639
  type: EventType.RUN_ERROR,
642
640
  runId: aguiState.runId,
643
641
  model,
644
- timestamp,
642
+ timestamp: Date.now(),
645
643
  message: errorPayload.message,
646
644
  ...(resolvedCode !== undefined && { code: resolvedCode }),
647
645
  ...(rawEvent !== undefined && { rawEvent }),