@caupulican/pi-ai 0.90.12 → 0.91.3

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.
@@ -3,6 +3,7 @@ import { shortHash } from "../utils/hash.js";
3
3
  import { parseStreamingJson } from "../utils/json-parse.js";
4
4
  import { sanitizeSurrogates } from "../utils/sanitize-unicode.js";
5
5
  import { createToolNameMap } from "../utils/tool-names.js";
6
+ import { commitSuccessfulAssistantParse } from "./provider-runtime.js";
6
7
  import { joinTextContent, transformMessages } from "./transform-messages.js";
7
8
  // =============================================================================
8
9
  // Utilities
@@ -279,6 +280,7 @@ export async function processResponsesStream(openaiStream, output, stream, model
279
280
  let currentBlock = null;
280
281
  let currentReasoningSummaryPartText = "";
281
282
  let sawTerminalResponseEvent = false;
283
+ let committedSuccessfulTerminal = false;
282
284
  const reasoningBlocksById = new Map();
283
285
  const blocks = output.content;
284
286
  const blockIndex = () => blocks.length - 1;
@@ -315,6 +317,196 @@ export async function processResponsesStream(openaiStream, output, stream, model
315
317
  block.thinkingSignature = JSON.stringify({ ...storedItem, encrypted_content: item.encrypted_content });
316
318
  }
317
319
  };
320
+ const finalizedOutputItemKeys = new Set();
321
+ const functionCallBlocksByItemId = new Map();
322
+ const functionCallBlocksByCallId = new Map();
323
+ const outputItemKeys = (item) => {
324
+ if (item.type === "reasoning" || item.type === "message") {
325
+ return item.id ? [`${item.type}:${item.id}`] : [];
326
+ }
327
+ if (item.type === "function_call") {
328
+ const keys = [];
329
+ if (item.id)
330
+ keys.push(`function_call:id:${item.id}`);
331
+ if (item.call_id)
332
+ keys.push(`function_call:call:${item.call_id}`);
333
+ keys.push(`function_call:${item.call_id ?? ""}|${item.id ?? ""}`);
334
+ return keys;
335
+ }
336
+ return [];
337
+ };
338
+ const registerFunctionCallBlock = (item, block) => {
339
+ if (item.id)
340
+ functionCallBlocksByItemId.set(item.id, block);
341
+ if (item.call_id)
342
+ functionCallBlocksByCallId.set(item.call_id, block);
343
+ };
344
+ const findFunctionCallBlock = (item) => {
345
+ const id = `${item.call_id}|${item.id}`;
346
+ const byExactId = output.content.find((candidate) => candidate.type === "toolCall" && candidate.id === id);
347
+ if (byExactId)
348
+ return byExactId;
349
+ if (item.id) {
350
+ const byItemId = functionCallBlocksByItemId.get(item.id);
351
+ if (byItemId)
352
+ return byItemId;
353
+ }
354
+ if (item.call_id) {
355
+ const byCallId = functionCallBlocksByCallId.get(item.call_id);
356
+ if (byCallId)
357
+ return byCallId;
358
+ }
359
+ if (currentBlock?.type === "toolCall")
360
+ return currentBlock;
361
+ return undefined;
362
+ };
363
+ const contentIndexOf = (block) => output.content.indexOf(block);
364
+ const applyCompletedOutputItem = (item, emit) => {
365
+ const keys = outputItemKeys(item);
366
+ if (keys.some((key) => finalizedOutputItemKeys.has(key)))
367
+ return;
368
+ if (item.type === "reasoning") {
369
+ if (item.summary) {
370
+ item.summary = item.summary
371
+ .map((part) => ({ ...part, text: cleanReasoningSummaryText(part.text ?? "") }))
372
+ .filter((part) => part.text.trim().length > 0);
373
+ }
374
+ const summaryText = normalizeReasoningSummaryText(item.summary);
375
+ const contentText = item.content?.map((part) => part.text).join("\n\n") || "";
376
+ let block = item.id ? reasoningBlocksById.get(item.id) : undefined;
377
+ if (!block && currentBlock?.type === "thinking")
378
+ block = currentBlock;
379
+ const isNew = !block;
380
+ if (!block) {
381
+ block = { type: "thinking", thinking: "" };
382
+ output.content.push(block);
383
+ }
384
+ block.thinking = summaryText || contentText || block.thinking;
385
+ block.thinkingSignature = JSON.stringify(item);
386
+ if (item.id)
387
+ reasoningBlocksById.set(item.id, block);
388
+ if (emit) {
389
+ const contentIndex = contentIndexOf(block);
390
+ if (isNew) {
391
+ stream.push({ type: "thinking_start", contentIndex, partial: output });
392
+ }
393
+ stream.push({
394
+ type: "thinking_end",
395
+ contentIndex,
396
+ content: block.thinking,
397
+ partial: output,
398
+ });
399
+ }
400
+ if (currentBlock === block)
401
+ currentBlock = null;
402
+ for (const key of keys)
403
+ finalizedOutputItemKeys.add(key);
404
+ return;
405
+ }
406
+ if (item.type === "message") {
407
+ const text = (item.content ?? [])
408
+ .map((part) => (part.type === "output_text" ? part.text : part.refusal))
409
+ .join("");
410
+ let block;
411
+ if (currentBlock?.type === "text") {
412
+ block = currentBlock;
413
+ }
414
+ else if (item.id) {
415
+ block = output.content.find((candidate) => candidate.type === "text" && parseTextSignature(candidate.textSignature)?.id === item.id);
416
+ }
417
+ // xAI/Grok emits message.added + deltas, then function_call items, then
418
+ // message.done / response.output. currentBlock has already moved to the
419
+ // tool; the live text block must be reused or the same sentence is stored
420
+ // twice (unsigned before tools, signed after).
421
+ if (!block) {
422
+ block = [...output.content]
423
+ .reverse()
424
+ .find((candidate) => candidate.type === "text" &&
425
+ !candidate.textSignature &&
426
+ (candidate.text === "" ||
427
+ text === "" ||
428
+ text.startsWith(candidate.text) ||
429
+ candidate.text.startsWith(text)));
430
+ }
431
+ const isNew = !block;
432
+ if (!block) {
433
+ block = { type: "text", text: "" };
434
+ output.content.push(block);
435
+ }
436
+ if (text.length > 0)
437
+ block.text = text;
438
+ if (item.id)
439
+ block.textSignature = encodeTextSignatureV1(item.id, item.phase ?? undefined);
440
+ if (emit) {
441
+ const contentIndex = contentIndexOf(block);
442
+ if (isNew) {
443
+ stream.push({ type: "text_start", contentIndex, partial: output });
444
+ }
445
+ stream.push({ type: "text_end", contentIndex, content: block.text, partial: output });
446
+ }
447
+ if (currentBlock === block)
448
+ currentBlock = null;
449
+ for (const key of keys)
450
+ finalizedOutputItemKeys.add(key);
451
+ return;
452
+ }
453
+ const id = `${item.call_id}|${item.id}`;
454
+ let block = findFunctionCallBlock(item);
455
+ const isNew = !block;
456
+ if (!block) {
457
+ block = {
458
+ type: "toolCall",
459
+ id,
460
+ name: options?.toolNameMap?.toOriginalName(item.name) ?? item.name,
461
+ arguments: {},
462
+ };
463
+ output.content.push(block);
464
+ }
465
+ else {
466
+ block.id = id;
467
+ block.name = options?.toolNameMap?.toOriginalName(item.name) ?? item.name;
468
+ }
469
+ block.arguments = parseStreamingJson(block.partialJson || item.arguments || "{}");
470
+ delete block.partialJson;
471
+ registerFunctionCallBlock(item, block);
472
+ if (emit) {
473
+ const contentIndex = contentIndexOf(block);
474
+ if (isNew) {
475
+ stream.push({ type: "toolcall_start", contentIndex, partial: output });
476
+ }
477
+ stream.push({ type: "toolcall_end", contentIndex, toolCall: block, partial: output });
478
+ }
479
+ if (currentBlock === block)
480
+ currentBlock = null;
481
+ for (const key of keys)
482
+ finalizedOutputItemKeys.add(key);
483
+ };
484
+ const applyResponseOutput = (responseOutput) => {
485
+ for (const item of responseOutput) {
486
+ if (item.type === "reasoning" || item.type === "message" || item.type === "function_call") {
487
+ applyCompletedOutputItem(item, true);
488
+ }
489
+ }
490
+ if (!currentBlock)
491
+ return;
492
+ const contentIndex = blockIndex();
493
+ if (currentBlock.type === "text") {
494
+ stream.push({ type: "text_end", contentIndex, content: currentBlock.text, partial: output });
495
+ }
496
+ else if (currentBlock.type === "thinking") {
497
+ stream.push({
498
+ type: "thinking_end",
499
+ contentIndex,
500
+ content: currentBlock.thinking,
501
+ partial: output,
502
+ });
503
+ }
504
+ else {
505
+ delete currentBlock.partialJson;
506
+ stream.push({ type: "toolcall_end", contentIndex, toolCall: currentBlock, partial: output });
507
+ }
508
+ currentBlock = null;
509
+ };
318
510
  const finalizeResponse = (response) => {
319
511
  sawTerminalResponseEvent = true;
320
512
  backfillReasoningSignatures((response.output ?? []).filter((item) => item.type === "reasoning"));
@@ -362,235 +554,203 @@ export async function processResponsesStream(openaiStream, output, stream, model
362
554
  : (response?.service_tier ?? options.serviceTier);
363
555
  options.applyServiceTierPricing(output.usage, serviceTier);
364
556
  }
557
+ applyResponseOutput(response.output ?? []);
365
558
  // Map status to stop reason
366
559
  output.stopReason = mapStopReason(response?.status);
367
560
  if (output.content.some((b) => b.type === "toolCall") && output.stopReason === "stop") {
368
561
  output.stopReason = "toolUse";
369
562
  }
370
- };
371
- for await (const event of openaiStream) {
372
- if (event.type === "response.created") {
373
- output.responseId = event.response.id;
374
- }
375
- else if (event.type === "response.output_item.added") {
376
- const item = event.item;
377
- if (item.type === "reasoning") {
378
- currentItem = item;
379
- currentBlock = { type: "thinking", thinking: "" };
380
- currentReasoningSummaryPartText = "";
381
- output.content.push(currentBlock);
382
- stream.push({ type: "thinking_start", contentIndex: blockIndex(), partial: output });
383
- }
384
- else if (item.type === "message") {
385
- currentItem = item;
386
- currentBlock = { type: "text", text: "" };
387
- output.content.push(currentBlock);
388
- stream.push({ type: "text_start", contentIndex: blockIndex(), partial: output });
389
- }
390
- else if (item.type === "function_call") {
391
- currentItem = item;
392
- currentBlock = {
393
- type: "toolCall",
394
- id: `${item.call_id}|${item.id}`,
395
- name: options?.toolNameMap?.toOriginalName(item.name) ?? item.name,
396
- arguments: {},
397
- partialJson: item.arguments || "",
398
- };
399
- output.content.push(currentBlock);
400
- stream.push({ type: "toolcall_start", contentIndex: blockIndex(), partial: output });
401
- }
563
+ if (output.stopReason !== "error" && output.stopReason !== "aborted") {
564
+ committedSuccessfulTerminal = true;
565
+ commitSuccessfulAssistantParse(output);
402
566
  }
403
- else if (event.type === "response.reasoning_summary_part.added") {
404
- if (currentItem && currentItem.type === "reasoning") {
405
- currentItem.summary = currentItem.summary || [];
406
- currentItem.summary.push(event.part);
407
- currentReasoningSummaryPartText = event.part.text ?? "";
567
+ };
568
+ try {
569
+ for await (const event of openaiStream) {
570
+ if (event.type === "response.created") {
571
+ output.responseId = event.response.id;
408
572
  }
409
- }
410
- else if (event.type === "response.reasoning_summary_text.delta") {
411
- if (currentItem?.type === "reasoning") {
412
- currentItem.summary = currentItem.summary || [];
413
- const lastPart = currentItem.summary[currentItem.summary.length - 1];
414
- if (lastPart) {
415
- lastPart.text += event.delta;
416
- currentReasoningSummaryPartText += event.delta;
573
+ else if (event.type === "response.output_item.added") {
574
+ const item = event.item;
575
+ if (item.type === "reasoning") {
576
+ currentItem = item;
577
+ currentBlock = { type: "thinking", thinking: "" };
578
+ currentReasoningSummaryPartText = "";
579
+ output.content.push(currentBlock);
580
+ if (item.id)
581
+ reasoningBlocksById.set(item.id, currentBlock);
582
+ stream.push({ type: "thinking_start", contentIndex: blockIndex(), partial: output });
417
583
  }
418
- }
419
- }
420
- else if (event.type === "response.reasoning_summary_part.done") {
421
- if (currentItem?.type === "reasoning" && currentBlock?.type === "thinking") {
422
- currentItem.summary = currentItem.summary || [];
423
- const lastPart = currentItem.summary[currentItem.summary.length - 1];
424
- const partText = cleanReasoningSummaryText(lastPart?.text ?? currentReasoningSummaryPartText);
425
- if (lastPart) {
426
- lastPart.text = partText;
584
+ else if (item.type === "message") {
585
+ currentItem = item;
586
+ currentBlock = { type: "text", text: "" };
587
+ if (item.id) {
588
+ currentBlock.textSignature = encodeTextSignatureV1(item.id, item.phase ?? undefined);
589
+ }
590
+ output.content.push(currentBlock);
591
+ stream.push({ type: "text_start", contentIndex: blockIndex(), partial: output });
427
592
  }
428
- if (partText.trim().length > 0) {
429
- const delta = `${partText}\n\n`;
430
- currentBlock.thinking += delta;
431
- stream.push({
432
- type: "thinking_delta",
433
- contentIndex: blockIndex(),
434
- delta,
435
- partial: output,
436
- });
593
+ else if (item.type === "function_call") {
594
+ currentItem = item;
595
+ currentBlock = {
596
+ type: "toolCall",
597
+ id: `${item.call_id}|${item.id}`,
598
+ name: options?.toolNameMap?.toOriginalName(item.name) ?? item.name,
599
+ arguments: {},
600
+ partialJson: item.arguments || "",
601
+ };
602
+ output.content.push(currentBlock);
603
+ registerFunctionCallBlock(item, currentBlock);
604
+ stream.push({ type: "toolcall_start", contentIndex: blockIndex(), partial: output });
437
605
  }
438
- currentReasoningSummaryPartText = "";
439
606
  }
440
- }
441
- else if (event.type === "response.reasoning_text.delta") {
442
- if (currentItem?.type === "reasoning" && currentBlock?.type === "thinking") {
443
- currentBlock.thinking += event.delta;
444
- stream.push({
445
- type: "thinking_delta",
446
- contentIndex: blockIndex(),
447
- delta: event.delta,
448
- partial: output,
449
- });
607
+ else if (event.type === "response.reasoning_summary_part.added") {
608
+ if (currentItem && currentItem.type === "reasoning") {
609
+ currentItem.summary = currentItem.summary || [];
610
+ currentItem.summary.push(event.part);
611
+ currentReasoningSummaryPartText = event.part.text ?? "";
612
+ }
450
613
  }
451
- }
452
- else if (event.type === "response.content_part.added") {
453
- if (currentItem?.type === "message") {
454
- currentItem.content = currentItem.content || [];
455
- // Filter out ReasoningText, only accept output_text and refusal
456
- if (event.part.type === "output_text" || event.part.type === "refusal") {
457
- currentItem.content.push(event.part);
614
+ else if (event.type === "response.reasoning_summary_text.delta") {
615
+ if (currentItem?.type === "reasoning") {
616
+ currentItem.summary = currentItem.summary || [];
617
+ const lastPart = currentItem.summary[currentItem.summary.length - 1];
618
+ if (lastPart) {
619
+ lastPart.text += event.delta;
620
+ currentReasoningSummaryPartText += event.delta;
621
+ }
458
622
  }
459
623
  }
460
- }
461
- else if (event.type === "response.output_text.delta") {
462
- if (currentItem?.type === "message" && currentBlock?.type === "text") {
463
- const lastPart = ensureMessageOutputTextPart();
464
- if (lastPart) {
465
- currentBlock.text += event.delta;
466
- lastPart.text += event.delta;
467
- stream.push({
468
- type: "text_delta",
469
- contentIndex: blockIndex(),
470
- delta: event.delta,
471
- partial: output,
472
- });
624
+ else if (event.type === "response.reasoning_summary_part.done") {
625
+ if (currentItem?.type === "reasoning" && currentBlock?.type === "thinking") {
626
+ currentItem.summary = currentItem.summary || [];
627
+ const lastPart = currentItem.summary[currentItem.summary.length - 1];
628
+ const partText = cleanReasoningSummaryText(lastPart?.text ?? currentReasoningSummaryPartText);
629
+ if (lastPart) {
630
+ lastPart.text = partText;
631
+ }
632
+ if (partText.trim().length > 0) {
633
+ const delta = `${partText}\n\n`;
634
+ currentBlock.thinking += delta;
635
+ stream.push({
636
+ type: "thinking_delta",
637
+ contentIndex: blockIndex(),
638
+ delta,
639
+ partial: output,
640
+ });
641
+ }
642
+ currentReasoningSummaryPartText = "";
473
643
  }
474
644
  }
475
- }
476
- else if (event.type === "response.refusal.delta") {
477
- if (currentItem?.type === "message" && currentBlock?.type === "text") {
478
- const lastPart = ensureMessageRefusalPart();
479
- if (lastPart) {
480
- currentBlock.text += event.delta;
481
- lastPart.refusal += event.delta;
645
+ else if (event.type === "response.reasoning_text.delta") {
646
+ if (currentItem?.type === "reasoning" && currentBlock?.type === "thinking") {
647
+ currentBlock.thinking += event.delta;
482
648
  stream.push({
483
- type: "text_delta",
649
+ type: "thinking_delta",
484
650
  contentIndex: blockIndex(),
485
651
  delta: event.delta,
486
652
  partial: output,
487
653
  });
488
654
  }
489
655
  }
490
- }
491
- else if (event.type === "response.function_call_arguments.delta") {
492
- if (currentItem?.type === "function_call" && currentBlock?.type === "toolCall") {
493
- currentBlock.partialJson += event.delta;
494
- currentBlock.arguments = parseStreamingJson(currentBlock.partialJson);
495
- stream.push({
496
- type: "toolcall_delta",
497
- contentIndex: blockIndex(),
498
- delta: event.delta,
499
- partial: output,
500
- });
656
+ else if (event.type === "response.content_part.added") {
657
+ if (currentItem?.type === "message") {
658
+ currentItem.content = currentItem.content || [];
659
+ // Filter out ReasoningText, only accept output_text and refusal
660
+ if (event.part.type === "output_text" || event.part.type === "refusal") {
661
+ currentItem.content.push(event.part);
662
+ }
663
+ }
501
664
  }
502
- }
503
- else if (event.type === "response.function_call_arguments.done") {
504
- if (currentItem?.type === "function_call" && currentBlock?.type === "toolCall") {
505
- const previousPartialJson = currentBlock.partialJson;
506
- currentBlock.partialJson = event.arguments;
507
- currentBlock.arguments = parseStreamingJson(currentBlock.partialJson);
508
- if (event.arguments.startsWith(previousPartialJson)) {
509
- const delta = event.arguments.slice(previousPartialJson.length);
510
- if (delta.length > 0) {
665
+ else if (event.type === "response.output_text.delta") {
666
+ if (currentItem?.type === "message" && currentBlock?.type === "text") {
667
+ const lastPart = ensureMessageOutputTextPart();
668
+ if (lastPart) {
669
+ currentBlock.text += event.delta;
670
+ lastPart.text += event.delta;
511
671
  stream.push({
512
- type: "toolcall_delta",
672
+ type: "text_delta",
513
673
  contentIndex: blockIndex(),
514
- delta,
674
+ delta: event.delta,
515
675
  partial: output,
516
676
  });
517
677
  }
518
678
  }
519
679
  }
520
- }
521
- else if (event.type === "response.output_item.done") {
522
- const item = event.item;
523
- if (item.type === "reasoning" && currentBlock?.type === "thinking") {
524
- if (item.summary) {
525
- item.summary = item.summary
526
- .map((part) => ({ ...part, text: cleanReasoningSummaryText(part.text ?? "") }))
527
- .filter((part) => part.text.trim().length > 0);
680
+ else if (event.type === "response.refusal.delta") {
681
+ if (currentItem?.type === "message" && currentBlock?.type === "text") {
682
+ const lastPart = ensureMessageRefusalPart();
683
+ if (lastPart) {
684
+ currentBlock.text += event.delta;
685
+ lastPart.refusal += event.delta;
686
+ stream.push({
687
+ type: "text_delta",
688
+ contentIndex: blockIndex(),
689
+ delta: event.delta,
690
+ partial: output,
691
+ });
692
+ }
528
693
  }
529
- const summaryText = normalizeReasoningSummaryText(item.summary);
530
- const contentText = item.content?.map((c) => c.text).join("\n\n") || "";
531
- currentBlock.thinking = summaryText || contentText || currentBlock.thinking;
532
- currentBlock.thinkingSignature = JSON.stringify(item);
533
- reasoningBlocksById.set(item.id, currentBlock);
534
- stream.push({
535
- type: "thinking_end",
536
- contentIndex: blockIndex(),
537
- content: currentBlock.thinking,
538
- partial: output,
539
- });
540
- currentBlock = null;
541
694
  }
542
- else if (item.type === "message" && currentBlock?.type === "text") {
543
- currentBlock.text = item.content.map((c) => (c.type === "output_text" ? c.text : c.refusal)).join("");
544
- currentBlock.textSignature = encodeTextSignatureV1(item.id, item.phase ?? undefined);
545
- stream.push({
546
- type: "text_end",
547
- contentIndex: blockIndex(),
548
- content: currentBlock.text,
549
- partial: output,
550
- });
551
- currentBlock = null;
695
+ else if (event.type === "response.function_call_arguments.delta") {
696
+ if (currentItem?.type === "function_call" && currentBlock?.type === "toolCall") {
697
+ currentBlock.partialJson = (currentBlock.partialJson ?? "") + event.delta;
698
+ currentBlock.arguments = parseStreamingJson(currentBlock.partialJson);
699
+ stream.push({
700
+ type: "toolcall_delta",
701
+ contentIndex: blockIndex(),
702
+ delta: event.delta,
703
+ partial: output,
704
+ });
705
+ }
552
706
  }
553
- else if (item.type === "function_call") {
554
- const args = currentBlock?.type === "toolCall" && currentBlock.partialJson
555
- ? parseStreamingJson(currentBlock.partialJson)
556
- : parseStreamingJson(item.arguments || "{}");
557
- let toolCall;
558
- if (currentBlock?.type === "toolCall") {
559
- // Finalize in-place and strip the scratch buffer so replay only
560
- // carries parsed arguments.
561
- currentBlock.arguments = args;
562
- delete currentBlock.partialJson;
563
- toolCall = currentBlock;
707
+ else if (event.type === "response.function_call_arguments.done") {
708
+ if (currentItem?.type === "function_call" && currentBlock?.type === "toolCall") {
709
+ const previousPartialJson = currentBlock.partialJson;
710
+ currentBlock.partialJson = event.arguments;
711
+ currentBlock.arguments = parseStreamingJson(currentBlock.partialJson);
712
+ if (previousPartialJson !== undefined && event.arguments.startsWith(previousPartialJson)) {
713
+ const delta = event.arguments.slice(previousPartialJson.length);
714
+ if (delta.length > 0) {
715
+ stream.push({
716
+ type: "toolcall_delta",
717
+ contentIndex: blockIndex(),
718
+ delta,
719
+ partial: output,
720
+ });
721
+ }
722
+ }
564
723
  }
565
- else {
566
- toolCall = {
567
- type: "toolCall",
568
- id: `${item.call_id}|${item.id}`,
569
- name: options?.toolNameMap?.toOriginalName(item.name) ?? item.name,
570
- arguments: args,
571
- };
724
+ }
725
+ else if (event.type === "response.output_item.done") {
726
+ const item = event.item;
727
+ if (item.type === "reasoning" || item.type === "message" || item.type === "function_call") {
728
+ applyCompletedOutputItem(item, true);
572
729
  }
573
- currentBlock = null;
574
- stream.push({ type: "toolcall_end", contentIndex: blockIndex(), toolCall, partial: output });
730
+ }
731
+ else if (event.type === "response.completed" || event.type === "response.incomplete") {
732
+ finalizeResponse(event.response);
733
+ }
734
+ else if (event.type === "error") {
735
+ throw new Error(`Error Code ${event.code}: ${event.message}` || "Unknown error");
736
+ }
737
+ else if (event.type === "response.failed") {
738
+ sawTerminalResponseEvent = true;
739
+ const error = event.response?.error;
740
+ const details = event.response?.incomplete_details;
741
+ const msg = error
742
+ ? `${error.code || "unknown"}: ${error.message || "no message"}`
743
+ : details?.reason
744
+ ? `incomplete: ${details.reason}`
745
+ : "Unknown error (no error details in response)";
746
+ throw new Error(msg);
575
747
  }
576
748
  }
577
- else if (event.type === "response.completed" || event.type === "response.incomplete") {
578
- finalizeResponse(event.response);
579
- }
580
- else if (event.type === "error") {
581
- throw new Error(`Error Code ${event.code}: ${event.message}` || "Unknown error");
582
- }
583
- else if (event.type === "response.failed") {
584
- sawTerminalResponseEvent = true;
585
- const error = event.response?.error;
586
- const details = event.response?.incomplete_details;
587
- const msg = error
588
- ? `${error.code || "unknown"}: ${error.message || "no message"}`
589
- : details?.reason
590
- ? `incomplete: ${details.reason}`
591
- : "Unknown error (no error details in response)";
592
- throw new Error(msg);
593
- }
749
+ }
750
+ catch (error) {
751
+ if (committedSuccessfulTerminal)
752
+ return;
753
+ throw error;
594
754
  }
595
755
  if (!sawTerminalResponseEvent) {
596
756
  throw new Error("OpenAI Responses stream ended before a terminal response event");