extrait 0.6.1 → 0.7.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/dist/index.cjs CHANGED
@@ -91,9 +91,6 @@ __export(exports_src, {
91
91
  });
92
92
  module.exports = __toCommonJS(exports_src);
93
93
 
94
- // src/extract.ts
95
- var import_jsonrepair = require("jsonrepair");
96
-
97
94
  // src/utils/common.ts
98
95
  function toErrorMessage(error) {
99
96
  if (error instanceof Error) {
@@ -311,184 +308,77 @@ function isOnlyWhitespace(value) {
311
308
  return true;
312
309
  }
313
310
 
314
- // src/extract.ts
315
- var RE_EMPTY_OBJECT = /^\{\s*\}$/;
316
- var RE_EMPTY_ARRAY = /^\[\s*\]$/;
317
- var RE_BOUNDARY_CHAR = /[\s,.;:!?`"'()\[\]{}<>]/;
318
- var DEFAULT_EXTRACTION_HEURISTICS = {
319
- firstPassMin: 12,
320
- firstPassCap: 30,
321
- firstPassMultiplier: 6,
322
- secondPassMin: 4,
323
- secondPassCap: 8,
324
- secondPassMultiplier: 2,
325
- hintMaxLength: 50000
326
- };
327
- function extractJsonCandidates(input, options = {}) {
328
- const maxCandidates = options.maxCandidates ?? 5;
329
- const acceptArrays = options.acceptArrays ?? true;
330
- const allowRepairHints = options.allowRepairHints ?? true;
331
- const heuristics = resolveExtractionHeuristics(options.heuristics);
332
- const extractionInput = input;
333
- const candidates = [];
334
- candidates.push(...extractFromMarkdown(extractionInput, acceptArrays));
335
- candidates.push(...scanBalancedSegments(extractionInput, acceptArrays));
336
- if (candidates.length === 0 && extractionInput.trim()) {
337
- const content = extractionInput.trim();
338
- candidates.push({
339
- id: "raw:fallback",
340
- source: "raw",
341
- content,
342
- start: 0,
343
- end: extractionInput.length,
344
- score: 10 + Math.floor(lengthScore(content.length) / 3)
345
- });
346
- }
347
- const prefiltered = prefilterByJsonShape(candidates, acceptArrays);
348
- const firstPassLimit = clamp(maxCandidates * heuristics.firstPassMultiplier, heuristics.firstPassMin, heuristics.firstPassCap);
349
- const firstPass = prefiltered.slice(0, firstPassLimit);
350
- const secondPassLimit = Math.min(firstPass.length, clamp(maxCandidates * heuristics.secondPassMultiplier, heuristics.secondPassMin, heuristics.secondPassCap));
351
- for (let i = 0;i < secondPassLimit; i += 1) {
352
- const candidate = firstPass[i];
353
- if (!candidate) {
354
- continue;
355
- }
356
- const parseHint = buildParseHint(candidate.content, allowRepairHints, heuristics.hintMaxLength);
357
- if (!parseHint) {
358
- continue;
359
- }
360
- candidate.parseHint = parseHint;
361
- candidate.score += parseHintBonus(parseHint);
311
+ // src/extract-parse-hint.ts
312
+ var import_jsonrepair = require("jsonrepair");
313
+ function buildParseHint(content, allowRepair, hintMaxLength) {
314
+ if (content.length > hintMaxLength) {
315
+ return null;
362
316
  }
363
- const sorted = sortCandidates(firstPass);
364
- const deduped = dedupeCandidates(sorted);
365
- return deduped.slice(0, maxCandidates).map((candidate, index) => ({
366
- id: `${candidate.source}:${index}`,
367
- source: candidate.source,
368
- content: candidate.content,
369
- language: candidate.language,
370
- parseHint: candidate.parseHint,
371
- start: candidate.start,
372
- end: candidate.end,
373
- score: candidate.score
374
- }));
375
- }
376
- function prefilterByJsonShape(candidates, acceptArrays) {
377
- const shaped = candidates.map((candidate) => {
378
- const shapeScore = jsonShapeScore(candidate.content, acceptArrays);
317
+ try {
379
318
  return {
380
- ...candidate,
381
- shapeScore,
382
- score: candidate.score + shapeScore
319
+ success: true,
320
+ parsed: JSON.parse(content),
321
+ repaired: null,
322
+ usedRepair: false,
323
+ stage: "parse",
324
+ error: ""
383
325
  };
384
- });
385
- const sorted = sortCandidates(shaped);
386
- const deduped = dedupeCandidates(sorted);
387
- const filtered = deduped.filter((candidate) => passesShapeFilter(candidate));
388
- if (filtered.length > 0) {
389
- const fallback = deduped.find((candidate) => !passesShapeFilter(candidate));
390
- if (fallback) {
391
- filtered.push(fallback);
326
+ } catch (directError) {
327
+ if (!allowRepair) {
328
+ return {
329
+ success: false,
330
+ parsed: null,
331
+ repaired: null,
332
+ usedRepair: false,
333
+ stage: "parse",
334
+ error: toErrorMessage(directError)
335
+ };
392
336
  }
393
- return sortCandidates(filtered);
394
- }
395
- return deduped.slice(0, Math.min(1, deduped.length));
396
- }
397
- function extractFromMarkdown(input, acceptArrays) {
398
- const blocks = extractMarkdownCodeBlocks(input);
399
- return blocks.flatMap((block, index) => {
400
- const language = block.language || null;
401
- const content = block.code.trim();
402
- if (!content) {
403
- return [];
337
+ let repaired;
338
+ try {
339
+ repaired = import_jsonrepair.jsonrepair(content);
340
+ } catch (repairError) {
341
+ return {
342
+ success: false,
343
+ parsed: null,
344
+ repaired: null,
345
+ usedRepair: true,
346
+ stage: "repair",
347
+ error: toErrorMessage(repairError)
348
+ };
404
349
  }
405
- if (!looksLikeJsonEnvelope(content, acceptArrays)) {
406
- return [];
350
+ try {
351
+ return {
352
+ success: true,
353
+ parsed: JSON.parse(repaired),
354
+ repaired,
355
+ usedRepair: true,
356
+ stage: "parse",
357
+ error: ""
358
+ };
359
+ } catch (parseError) {
360
+ return {
361
+ success: false,
362
+ parsed: null,
363
+ repaired,
364
+ usedRepair: true,
365
+ stage: "parse",
366
+ error: toErrorMessage(parseError || directError)
367
+ };
407
368
  }
408
- const langBonus = languageBonus(language);
409
- return [
410
- {
411
- id: `fenced:${index}`,
412
- source: "fenced",
413
- language,
414
- content,
415
- start: block.start,
416
- end: block.end,
417
- score: 260 + langBonus + lengthScore(content.length)
418
- }
419
- ];
420
- });
369
+ }
421
370
  }
422
- function scanBalancedSegments(input, acceptArrays) {
423
- const results = [];
424
- const stack = [];
425
- let inString = false;
426
- let quote = null;
427
- let escaped = false;
428
- for (let i = 0;i < input.length; i += 1) {
429
- const char = input[i];
430
- if (!char) {
431
- continue;
432
- }
433
- if (inString) {
434
- if (escaped) {
435
- escaped = false;
436
- continue;
437
- }
438
- if (char === "\\") {
439
- escaped = true;
440
- continue;
441
- }
442
- if (char === quote) {
443
- inString = false;
444
- quote = null;
445
- }
446
- continue;
447
- }
448
- const allowSingleQuoted = stack.length > 0;
449
- if (char === '"' || allowSingleQuoted && (char === "'" || char === "`")) {
450
- inString = true;
451
- quote = char;
452
- continue;
453
- }
454
- if (char === "{" || char === "[") {
455
- stack.push({ char, index: i });
456
- continue;
457
- }
458
- if (char !== "}" && char !== "]") {
459
- continue;
460
- }
461
- const expectedOpen = char === "}" ? "{" : "[";
462
- while (stack.length > 0 && stack[stack.length - 1]?.char !== expectedOpen) {
463
- stack.pop();
464
- }
465
- const opened = stack.pop();
466
- if (!opened) {
467
- continue;
468
- }
469
- if (stack.length > 0) {
470
- continue;
471
- }
472
- if (!acceptArrays && opened.char === "[") {
473
- continue;
474
- }
475
- const content = input.slice(opened.index, i + 1).trim();
476
- if (!content) {
477
- continue;
478
- }
479
- const rootBonus = opened.char === "{" ? 40 : 20;
480
- const boundaryBonus = boundaryScore(input, opened.index, i + 1);
481
- results.push({
482
- id: `scan:${results.length}`,
483
- source: "scan",
484
- content,
485
- start: opened.index,
486
- end: i + 1,
487
- score: 120 + rootBonus + boundaryBonus + lengthScore(content.length)
488
- });
371
+ function parseHintBonus(hint) {
372
+ if (hint.success) {
373
+ return hint.usedRepair ? 70 : 120;
489
374
  }
490
- return results;
375
+ return hint.usedRepair ? -20 : -10;
491
376
  }
377
+
378
+ // src/extract-shape.ts
379
+ var RE_EMPTY_OBJECT = /^\{\s*\}$/;
380
+ var RE_EMPTY_ARRAY = /^\[\s*\]$/;
381
+ var RE_BOUNDARY_CHAR = /[\s,.;:!?`"'()[\]{}<>]/;
492
382
  function looksLikeJsonEnvelope(content, acceptArrays) {
493
383
  const trimmed = content.trim();
494
384
  if (trimmed.startsWith("{")) {
@@ -597,95 +487,6 @@ function passesShapeFilter(candidate) {
597
487
  }
598
488
  return candidate.shapeScore >= 35;
599
489
  }
600
- function buildParseHint(content, allowRepair, hintMaxLength) {
601
- if (content.length > hintMaxLength) {
602
- return null;
603
- }
604
- try {
605
- return {
606
- success: true,
607
- parsed: JSON.parse(content),
608
- repaired: null,
609
- usedRepair: false,
610
- stage: "parse",
611
- error: ""
612
- };
613
- } catch (directError) {
614
- if (!allowRepair) {
615
- return {
616
- success: false,
617
- parsed: null,
618
- repaired: null,
619
- usedRepair: false,
620
- stage: "parse",
621
- error: toErrorMessage(directError)
622
- };
623
- }
624
- let repaired;
625
- try {
626
- repaired = import_jsonrepair.jsonrepair(content);
627
- } catch (repairError) {
628
- return {
629
- success: false,
630
- parsed: null,
631
- repaired: null,
632
- usedRepair: true,
633
- stage: "repair",
634
- error: toErrorMessage(repairError)
635
- };
636
- }
637
- try {
638
- return {
639
- success: true,
640
- parsed: JSON.parse(repaired),
641
- repaired,
642
- usedRepair: true,
643
- stage: "parse",
644
- error: ""
645
- };
646
- } catch (parseError) {
647
- return {
648
- success: false,
649
- parsed: null,
650
- repaired,
651
- usedRepair: true,
652
- stage: "parse",
653
- error: toErrorMessage(parseError || directError)
654
- };
655
- }
656
- }
657
- }
658
- function resolveExtractionHeuristics(input) {
659
- const merged = {
660
- ...DEFAULT_EXTRACTION_HEURISTICS,
661
- ...input
662
- };
663
- const firstPassMin = normalizeInteger(merged.firstPassMin, DEFAULT_EXTRACTION_HEURISTICS.firstPassMin);
664
- const firstPassCap = Math.max(firstPassMin, normalizeInteger(merged.firstPassCap, DEFAULT_EXTRACTION_HEURISTICS.firstPassCap));
665
- const secondPassMin = normalizeInteger(merged.secondPassMin, DEFAULT_EXTRACTION_HEURISTICS.secondPassMin);
666
- const secondPassCap = Math.max(secondPassMin, normalizeInteger(merged.secondPassCap, DEFAULT_EXTRACTION_HEURISTICS.secondPassCap));
667
- return {
668
- firstPassMin,
669
- firstPassCap,
670
- firstPassMultiplier: normalizeInteger(merged.firstPassMultiplier, DEFAULT_EXTRACTION_HEURISTICS.firstPassMultiplier),
671
- secondPassMin,
672
- secondPassCap,
673
- secondPassMultiplier: normalizeInteger(merged.secondPassMultiplier, DEFAULT_EXTRACTION_HEURISTICS.secondPassMultiplier),
674
- hintMaxLength: normalizeInteger(merged.hintMaxLength, DEFAULT_EXTRACTION_HEURISTICS.hintMaxLength)
675
- };
676
- }
677
- function normalizeInteger(value, fallback) {
678
- if (typeof value !== "number" || !Number.isFinite(value)) {
679
- return fallback;
680
- }
681
- return Math.max(0, Math.floor(value));
682
- }
683
- function parseHintBonus(hint) {
684
- if (hint.success) {
685
- return hint.usedRepair ? 70 : 120;
686
- }
687
- return hint.usedRepair ? -20 : -10;
688
- }
689
490
  function hasBalancedJsonDelimiters(input) {
690
491
  const stack = [];
691
492
  let inString = false;
@@ -771,6 +572,207 @@ function dedupeCandidates(candidates) {
771
572
  function clamp(value, min, max) {
772
573
  return Math.max(min, Math.min(max, Math.floor(value)));
773
574
  }
575
+ function normalizeInteger(value, fallback) {
576
+ if (typeof value !== "number" || !Number.isFinite(value)) {
577
+ return fallback;
578
+ }
579
+ return Math.max(0, Math.floor(value));
580
+ }
581
+ function resolveExtractionHeuristics(input, defaults) {
582
+ const merged = {
583
+ ...defaults,
584
+ ...input
585
+ };
586
+ const firstPassMin = normalizeInteger(merged.firstPassMin, defaults.firstPassMin);
587
+ const firstPassCap = Math.max(firstPassMin, normalizeInteger(merged.firstPassCap, defaults.firstPassCap));
588
+ const secondPassMin = normalizeInteger(merged.secondPassMin, defaults.secondPassMin);
589
+ const secondPassCap = Math.max(secondPassMin, normalizeInteger(merged.secondPassCap, defaults.secondPassCap));
590
+ return {
591
+ firstPassMin,
592
+ firstPassCap,
593
+ firstPassMultiplier: normalizeInteger(merged.firstPassMultiplier, defaults.firstPassMultiplier),
594
+ secondPassMin,
595
+ secondPassCap,
596
+ secondPassMultiplier: normalizeInteger(merged.secondPassMultiplier, defaults.secondPassMultiplier),
597
+ hintMaxLength: normalizeInteger(merged.hintMaxLength, defaults.hintMaxLength)
598
+ };
599
+ }
600
+
601
+ // src/extract.ts
602
+ var DEFAULT_EXTRACTION_HEURISTICS = {
603
+ firstPassMin: 12,
604
+ firstPassCap: 30,
605
+ firstPassMultiplier: 6,
606
+ secondPassMin: 4,
607
+ secondPassCap: 8,
608
+ secondPassMultiplier: 2,
609
+ hintMaxLength: 50000
610
+ };
611
+ function extractJsonCandidates(input, options = {}) {
612
+ const maxCandidates = options.maxCandidates ?? 5;
613
+ const acceptArrays = options.acceptArrays ?? true;
614
+ const allowRepairHints = options.allowRepairHints ?? true;
615
+ const heuristics = resolveExtractionHeuristics(options.heuristics, DEFAULT_EXTRACTION_HEURISTICS);
616
+ const extractionInput = input;
617
+ const candidates = [];
618
+ candidates.push(...extractFromMarkdown(extractionInput, acceptArrays));
619
+ candidates.push(...scanBalancedSegments(extractionInput, acceptArrays));
620
+ if (candidates.length === 0 && extractionInput.trim()) {
621
+ const content = extractionInput.trim();
622
+ candidates.push({
623
+ id: "raw:fallback",
624
+ source: "raw",
625
+ content,
626
+ start: 0,
627
+ end: extractionInput.length,
628
+ score: 10 + Math.floor(lengthScore(content.length) / 3)
629
+ });
630
+ }
631
+ const prefiltered = prefilterByJsonShape(candidates, acceptArrays);
632
+ const firstPassLimit = clamp(maxCandidates * heuristics.firstPassMultiplier, heuristics.firstPassMin, heuristics.firstPassCap);
633
+ const firstPass = prefiltered.slice(0, firstPassLimit);
634
+ const secondPassLimit = Math.min(firstPass.length, clamp(maxCandidates * heuristics.secondPassMultiplier, heuristics.secondPassMin, heuristics.secondPassCap));
635
+ for (let i = 0;i < secondPassLimit; i += 1) {
636
+ const candidate = firstPass[i];
637
+ if (!candidate) {
638
+ continue;
639
+ }
640
+ const parseHint = buildParseHint(candidate.content, allowRepairHints, heuristics.hintMaxLength);
641
+ if (!parseHint) {
642
+ continue;
643
+ }
644
+ candidate.parseHint = parseHint;
645
+ candidate.score += parseHintBonus(parseHint);
646
+ }
647
+ const sorted = sortCandidates(firstPass);
648
+ const deduped = dedupeCandidates(sorted);
649
+ return deduped.slice(0, maxCandidates).map((candidate, index) => ({
650
+ id: `${candidate.source}:${index}`,
651
+ source: candidate.source,
652
+ content: candidate.content,
653
+ language: candidate.language,
654
+ parseHint: candidate.parseHint,
655
+ start: candidate.start,
656
+ end: candidate.end,
657
+ score: candidate.score
658
+ }));
659
+ }
660
+ function prefilterByJsonShape(candidates, acceptArrays) {
661
+ const shaped = candidates.map((candidate) => {
662
+ const shapeScore = jsonShapeScore(candidate.content, acceptArrays);
663
+ return {
664
+ ...candidate,
665
+ shapeScore,
666
+ score: candidate.score + shapeScore
667
+ };
668
+ });
669
+ const sorted = sortCandidates(shaped);
670
+ const deduped = dedupeCandidates(sorted);
671
+ const filtered = deduped.filter((candidate) => passesShapeFilter(candidate));
672
+ if (filtered.length > 0) {
673
+ const fallback = deduped.find((candidate) => !passesShapeFilter(candidate));
674
+ if (fallback) {
675
+ filtered.push(fallback);
676
+ }
677
+ return sortCandidates(filtered);
678
+ }
679
+ return deduped.slice(0, Math.min(1, deduped.length));
680
+ }
681
+ function extractFromMarkdown(input, acceptArrays) {
682
+ const blocks = extractMarkdownCodeBlocks(input);
683
+ return blocks.flatMap((block, index) => {
684
+ const language = block.language || null;
685
+ const content = block.code.trim();
686
+ if (!content) {
687
+ return [];
688
+ }
689
+ if (!looksLikeJsonEnvelope(content, acceptArrays)) {
690
+ return [];
691
+ }
692
+ const langBonus = languageBonus(language);
693
+ return [
694
+ {
695
+ id: `fenced:${index}`,
696
+ source: "fenced",
697
+ language,
698
+ content,
699
+ start: block.start,
700
+ end: block.end,
701
+ score: 260 + langBonus + lengthScore(content.length)
702
+ }
703
+ ];
704
+ });
705
+ }
706
+ function scanBalancedSegments(input, acceptArrays) {
707
+ const results = [];
708
+ const stack = [];
709
+ let inString = false;
710
+ let quote = null;
711
+ let escaped = false;
712
+ for (let i = 0;i < input.length; i += 1) {
713
+ const char = input[i];
714
+ if (!char) {
715
+ continue;
716
+ }
717
+ if (inString) {
718
+ if (escaped) {
719
+ escaped = false;
720
+ continue;
721
+ }
722
+ if (char === "\\") {
723
+ escaped = true;
724
+ continue;
725
+ }
726
+ if (char === quote) {
727
+ inString = false;
728
+ quote = null;
729
+ }
730
+ continue;
731
+ }
732
+ const allowSingleQuoted = stack.length > 0;
733
+ if (char === '"' || allowSingleQuoted && (char === "'" || char === "`")) {
734
+ inString = true;
735
+ quote = char;
736
+ continue;
737
+ }
738
+ if (char === "{" || char === "[") {
739
+ stack.push({ char, index: i });
740
+ continue;
741
+ }
742
+ if (char !== "}" && char !== "]") {
743
+ continue;
744
+ }
745
+ const expectedOpen = char === "}" ? "{" : "[";
746
+ while (stack.length > 0 && stack[stack.length - 1]?.char !== expectedOpen) {
747
+ stack.pop();
748
+ }
749
+ const opened = stack.pop();
750
+ if (!opened) {
751
+ continue;
752
+ }
753
+ if (stack.length > 0) {
754
+ continue;
755
+ }
756
+ if (!acceptArrays && opened.char === "[") {
757
+ continue;
758
+ }
759
+ const content = input.slice(opened.index, i + 1).trim();
760
+ if (!content) {
761
+ continue;
762
+ }
763
+ const rootBonus = opened.char === "{" ? 40 : 20;
764
+ const boundaryBonus = boundaryScore(input, opened.index, i + 1);
765
+ results.push({
766
+ id: `scan:${results.length}`,
767
+ source: "scan",
768
+ content,
769
+ start: opened.index,
770
+ end: i + 1,
771
+ score: 120 + rootBonus + boundaryBonus + lengthScore(content.length)
772
+ });
773
+ }
774
+ return results;
775
+ }
774
776
  // src/schema.ts
775
777
  var RE_SIMPLE_IDENTIFIER = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
776
778
  var RE_WHITESPACE = /\s+/g;
@@ -790,36 +792,36 @@ function unwrap(schema) {
790
792
  let optional = false;
791
793
  let nullable = false;
792
794
  while (true) {
793
- const typeName = current?._def?.type;
795
+ const typeName = current?.def?.type;
794
796
  if (!typeName) {
795
797
  break;
796
798
  }
797
799
  if (typeName === "optional") {
798
800
  optional = true;
799
- current = current._def?.innerType ?? current;
801
+ current = current.def?.innerType ?? current;
800
802
  continue;
801
803
  }
802
804
  if (typeName === "default") {
803
805
  optional = true;
804
- current = current._def?.innerType ?? current;
806
+ current = current.def?.innerType ?? current;
805
807
  continue;
806
808
  }
807
809
  if (typeName === "nullable") {
808
810
  nullable = true;
809
- current = current._def?.innerType ?? current;
811
+ current = current.def?.innerType ?? current;
810
812
  continue;
811
813
  }
812
814
  if (typeName === "pipe") {
813
- const outType = current._def?.out?._def?.type;
815
+ const outType = current.def?.out?.def?.type;
814
816
  if (outType === "transform") {
815
- current = current._def?.in ?? current;
817
+ current = current.def?.in ?? current;
816
818
  } else {
817
- current = current._def?.out ?? current;
819
+ current = current.def?.out ?? current;
818
820
  }
819
821
  continue;
820
822
  }
821
823
  if (typeName === "catch" || typeName === "readonly") {
822
- current = current._def?.innerType ?? current;
824
+ current = current.def?.innerType ?? current;
823
825
  continue;
824
826
  }
825
827
  break;
@@ -835,7 +837,7 @@ function formatCore(schema, depth, seen) {
835
837
  return "unknown";
836
838
  }
837
839
  seen.add(schema);
838
- const typeName = schema?._def?.type;
840
+ const typeName = schema?.def?.type;
839
841
  switch (typeName) {
840
842
  case "string":
841
843
  return "string";
@@ -860,44 +862,44 @@ function formatCore(schema, depth, seen) {
860
862
  case "void":
861
863
  return "void";
862
864
  case "literal": {
863
- const value = schema._def?.values?.[0];
865
+ const value = schema.def?.values?.[0];
864
866
  return JSON.stringify(value);
865
867
  }
866
868
  case "enum": {
867
- const entries = schema._def?.entries;
869
+ const entries = schema.def?.entries;
868
870
  const values = Object.values(entries ?? {});
869
871
  const unique = [...new Set(values.filter((v) => typeof v !== "string" || Number.isNaN(Number(v))))];
870
872
  return unique.map((v) => JSON.stringify(v)).join(" | ") || "string";
871
873
  }
872
874
  case "array": {
873
- const inner = formatType(schema._def?.element ?? schema, depth, seen);
875
+ const inner = formatType(schema.def?.element ?? schema, depth, seen);
874
876
  return requiresParentheses(inner) ? `(${inner})[]` : `${inner}[]`;
875
877
  }
876
878
  case "tuple": {
877
- const items = (schema._def?.items ?? []).map((item) => formatType(item, depth, seen));
879
+ const items = (schema.def?.items ?? []).map((item) => formatType(item, depth, seen));
878
880
  return `[${items.join(", ")}]`;
879
881
  }
880
882
  case "union": {
881
- const options = (schema._def?.options ?? []).map((option) => formatType(option, depth, seen));
883
+ const options = (schema.def?.options ?? []).map((option) => formatType(option, depth, seen));
882
884
  return options.join(" | ") || "unknown";
883
885
  }
884
886
  case "intersection": {
885
- const left = formatType(schema._def?.left ?? schema, depth, seen);
886
- const right = formatType(schema._def?.right ?? schema, depth, seen);
887
+ const left = formatType(schema.def?.left ?? schema, depth, seen);
888
+ const right = formatType(schema.def?.right ?? schema, depth, seen);
887
889
  return `${left} & ${right}`;
888
890
  }
889
891
  case "record": {
890
- const keyType = formatType(schema._def?.keyType ?? schema, depth, seen);
891
- const valueType = formatType(schema._def?.valueType ?? schema, depth, seen);
892
+ const keyType = formatType(schema.def?.keyType ?? schema, depth, seen);
893
+ const valueType = formatType(schema.def?.valueType ?? schema, depth, seen);
892
894
  return `Record<${keyType}, ${valueType}>`;
893
895
  }
894
896
  case "map": {
895
- const keyType = formatType(schema._def?.keyType ?? schema, depth, seen);
896
- const valueType = formatType(schema._def?.valueType ?? schema, depth, seen);
897
+ const keyType = formatType(schema.def?.keyType ?? schema, depth, seen);
898
+ const valueType = formatType(schema.def?.valueType ?? schema, depth, seen);
897
899
  return `Map<${keyType}, ${valueType}>`;
898
900
  }
899
901
  case "set": {
900
- const valueType = formatType(schema._def?.valueType ?? schema, depth, seen);
902
+ const valueType = formatType(schema.def?.valueType ?? schema, depth, seen);
901
903
  return `Set<${valueType}>`;
902
904
  }
903
905
  case "object":
@@ -911,7 +913,7 @@ function formatCore(schema, depth, seen) {
911
913
  function formatObject(schema, depth, seen) {
912
914
  const indent = " ".repeat(depth);
913
915
  const innerIndent = " ".repeat(depth + 1);
914
- const rawShape = schema._def?.shape;
916
+ const rawShape = schema.def?.shape;
915
917
  const shape = typeof rawShape === "function" ? rawShape() : rawShape ?? {};
916
918
  const entries = Object.entries(shape);
917
919
  if (entries.length === 0) {
@@ -937,27 +939,27 @@ function requiresParentheses(typeText) {
937
939
  return typeText.includes(" | ") || typeText.includes(" & ");
938
940
  }
939
941
  function isIntegerNumber(schema) {
940
- const checks = schema._def?.checks ?? [];
942
+ const checks = schema.def?.checks ?? [];
941
943
  return checks.some((check) => check.isInt === true);
942
944
  }
943
945
  function readSchemaDescription(schema) {
944
946
  let current = schema;
945
- while (current?._def?.type) {
947
+ while (current?.def?.type) {
946
948
  const desc = current.description;
947
949
  if (typeof desc === "string" && desc.trim().length > 0) {
948
950
  return sanitizeDescription(desc);
949
951
  }
950
- const typeName = current._def.type;
952
+ const typeName = current.def.type;
951
953
  if (typeName === "optional" || typeName === "default" || typeName === "nullable") {
952
- current = current._def.innerType ?? current;
954
+ current = current.def.innerType ?? current;
953
955
  continue;
954
956
  }
955
957
  if (typeName === "pipe") {
956
- current = current._def.in ?? current;
958
+ current = current.def.in ?? current;
957
959
  continue;
958
960
  }
959
961
  if (typeName === "catch" || typeName === "readonly") {
960
- current = current._def.innerType ?? current;
962
+ current = current.def.innerType ?? current;
961
963
  continue;
962
964
  }
963
965
  break;
@@ -1219,18 +1221,108 @@ function findSSEBoundary(buffer) {
1219
1221
  return Math.max(crlfIndex, lfIndex);
1220
1222
  }
1221
1223
 
1222
- // src/providers/mcp-runtime.ts
1223
- var DEFAULT_MAX_TOOL_ROUNDS = 8;
1224
- async function resolveMCPToolset(clients) {
1225
- if (!Array.isArray(clients) || clients.length === 0) {
1226
- return {
1227
- tools: [],
1228
- byName: new Map
1229
- };
1230
- }
1231
- const listed = [];
1232
- for (const client of clients) {
1233
- let cursor;
1224
+ // src/providers/mcp-runtime-debug.ts
1225
+ function formatToolExecutionDebugLine(execution) {
1226
+ const status = execution.error ? "error" : "ok";
1227
+ const scope = [execution.provider, execution.model].filter(Boolean).join("/") || "unknown";
1228
+ const toolRef = execution.clientId ? `${execution.clientId}:${execution.name ?? "unknown"}` : execution.name ?? "unknown";
1229
+ const duration = typeof execution.durationMs === "number" ? ` ${execution.durationMs}ms` : "";
1230
+ const base = `[tool:mcp:${status}] ${scope} ${toolRef}#${execution.callId}${duration}`;
1231
+ if (execution.error) {
1232
+ return `${base} -> ${execution.error}`;
1233
+ }
1234
+ return base;
1235
+ }
1236
+ function emitToolExecution(request, execution) {
1237
+ request.onToolExecution?.(execution);
1238
+ const debug = resolveToolDebugOptions(request.toolDebug);
1239
+ if (!debug.enabled) {
1240
+ return;
1241
+ }
1242
+ debug.logger(formatToolExecutionDebugLine(execution));
1243
+ if (debug.includeRequest) {
1244
+ debug.logger(formatToolExecutionRequestDebugLine(execution, debug));
1245
+ }
1246
+ if (debug.includeResult && (!execution.error || debug.includeResultOnError)) {
1247
+ debug.logger(formatToolExecutionResultDebugLine(execution, debug));
1248
+ }
1249
+ }
1250
+ function resolveToolDebugOptions(value) {
1251
+ if (value === true) {
1252
+ return {
1253
+ enabled: true,
1254
+ logger: defaultToolDebugLogger,
1255
+ includeRequest: true,
1256
+ includeResult: true,
1257
+ includeResultOnError: true,
1258
+ pretty: false
1259
+ };
1260
+ }
1261
+ if (value === undefined || value === false) {
1262
+ return {
1263
+ enabled: false,
1264
+ logger: () => {
1265
+ return;
1266
+ },
1267
+ includeRequest: false,
1268
+ includeResult: false,
1269
+ includeResultOnError: false,
1270
+ pretty: false
1271
+ };
1272
+ }
1273
+ return {
1274
+ enabled: value.enabled ?? true,
1275
+ logger: value.logger ?? defaultToolDebugLogger,
1276
+ includeRequest: value.includeRequest ?? true,
1277
+ includeResult: value.includeResult ?? true,
1278
+ includeResultOnError: value.includeResultOnError ?? true,
1279
+ pretty: value.pretty ?? false
1280
+ };
1281
+ }
1282
+ function defaultToolDebugLogger(line) {
1283
+ const { log } = globalThis.console;
1284
+ log(line);
1285
+ }
1286
+ function formatToolExecutionRequestDebugLine(execution, debug) {
1287
+ const scope = [execution.provider, execution.model].filter(Boolean).join("/") || "unknown";
1288
+ const toolRef = execution.clientId ? `${execution.clientId}:${execution.name ?? "unknown"}` : execution.name ?? "unknown";
1289
+ const payload = formatDebugPayload(execution.arguments, debug.pretty);
1290
+ return `[tool:mcp:request] ${scope} ${toolRef}#${execution.callId} arguments=${payload}`;
1291
+ }
1292
+ function formatToolExecutionResultDebugLine(execution, debug) {
1293
+ const scope = [execution.provider, execution.model].filter(Boolean).join("/") || "unknown";
1294
+ const toolRef = execution.clientId ? `${execution.clientId}:${execution.name ?? "unknown"}` : execution.name ?? "unknown";
1295
+ if (execution.error) {
1296
+ const payload2 = formatDebugPayload({ error: execution.error }, debug.pretty);
1297
+ return `[tool:mcp:result:error] ${scope} ${toolRef}#${execution.callId} output=${payload2}`;
1298
+ }
1299
+ const payload = formatDebugPayload(execution.output, debug.pretty);
1300
+ return `[tool:mcp:result:ok] ${scope} ${toolRef}#${execution.callId} output=${payload}`;
1301
+ }
1302
+ function formatDebugPayload(value, pretty) {
1303
+ if (value === undefined) {
1304
+ return "undefined";
1305
+ }
1306
+ try {
1307
+ const serialized = JSON.stringify(value, null, pretty ? 2 : 0);
1308
+ return serialized ?? "undefined";
1309
+ } catch {
1310
+ return String(value);
1311
+ }
1312
+ }
1313
+
1314
+ // src/providers/mcp-runtime.ts
1315
+ var DEFAULT_MAX_TOOL_ROUNDS = 100;
1316
+ async function resolveMCPToolset(clients) {
1317
+ if (!Array.isArray(clients) || clients.length === 0) {
1318
+ return {
1319
+ tools: [],
1320
+ byName: new Map
1321
+ };
1322
+ }
1323
+ const listed = [];
1324
+ for (const client of clients) {
1325
+ let cursor;
1234
1326
  do {
1235
1327
  const page = await client.listTools(cursor ? { cursor } : undefined);
1236
1328
  for (const tool of page.tools ?? []) {
@@ -1411,90 +1503,6 @@ function stringifyToolOutput(value) {
1411
1503
  }
1412
1504
  return JSON.stringify(value ?? null);
1413
1505
  }
1414
- function formatToolExecutionDebugLine(execution) {
1415
- const status = execution.error ? "error" : "ok";
1416
- const scope = [execution.provider, execution.model].filter(Boolean).join("/") || "unknown";
1417
- const toolRef = execution.clientId ? `${execution.clientId}:${execution.name ?? "unknown"}` : execution.name ?? "unknown";
1418
- const duration = typeof execution.durationMs === "number" ? ` ${execution.durationMs}ms` : "";
1419
- const base = `[tool:mcp:${status}] ${scope} ${toolRef}#${execution.callId}${duration}`;
1420
- if (execution.error) {
1421
- return `${base} -> ${execution.error}`;
1422
- }
1423
- return base;
1424
- }
1425
- function emitToolExecution(request, execution) {
1426
- request.onToolExecution?.(execution);
1427
- const debug = resolveToolDebugOptions(request.toolDebug);
1428
- if (!debug.enabled) {
1429
- return;
1430
- }
1431
- debug.logger(formatToolExecutionDebugLine(execution));
1432
- if (debug.includeRequest) {
1433
- debug.logger(formatToolExecutionRequestDebugLine(execution, debug));
1434
- }
1435
- if (debug.includeResult && (!execution.error || debug.includeResultOnError)) {
1436
- debug.logger(formatToolExecutionResultDebugLine(execution, debug));
1437
- }
1438
- }
1439
- function resolveToolDebugOptions(value) {
1440
- if (value === true) {
1441
- return {
1442
- enabled: true,
1443
- logger: (line) => console.log(line),
1444
- includeRequest: true,
1445
- includeResult: true,
1446
- includeResultOnError: true,
1447
- pretty: false
1448
- };
1449
- }
1450
- if (value === undefined || value === false) {
1451
- return {
1452
- enabled: false,
1453
- logger: () => {
1454
- return;
1455
- },
1456
- includeRequest: false,
1457
- includeResult: false,
1458
- includeResultOnError: false,
1459
- pretty: false
1460
- };
1461
- }
1462
- return {
1463
- enabled: value.enabled ?? true,
1464
- logger: value.logger ?? ((line) => console.log(line)),
1465
- includeRequest: value.includeRequest ?? true,
1466
- includeResult: value.includeResult ?? true,
1467
- includeResultOnError: value.includeResultOnError ?? true,
1468
- pretty: value.pretty ?? false
1469
- };
1470
- }
1471
- function formatToolExecutionRequestDebugLine(execution, debug) {
1472
- const scope = [execution.provider, execution.model].filter(Boolean).join("/") || "unknown";
1473
- const toolRef = execution.clientId ? `${execution.clientId}:${execution.name ?? "unknown"}` : execution.name ?? "unknown";
1474
- const payload = formatDebugPayload(execution.arguments, debug.pretty);
1475
- return `[tool:mcp:request] ${scope} ${toolRef}#${execution.callId} arguments=${payload}`;
1476
- }
1477
- function formatToolExecutionResultDebugLine(execution, debug) {
1478
- const scope = [execution.provider, execution.model].filter(Boolean).join("/") || "unknown";
1479
- const toolRef = execution.clientId ? `${execution.clientId}:${execution.name ?? "unknown"}` : execution.name ?? "unknown";
1480
- if (execution.error) {
1481
- const payload2 = formatDebugPayload({ error: execution.error }, debug.pretty);
1482
- return `[tool:mcp:result:error] ${scope} ${toolRef}#${execution.callId} output=${payload2}`;
1483
- }
1484
- const payload = formatDebugPayload(execution.output, debug.pretty);
1485
- return `[tool:mcp:result:ok] ${scope} ${toolRef}#${execution.callId} output=${payload}`;
1486
- }
1487
- function formatDebugPayload(value, pretty) {
1488
- if (value === undefined) {
1489
- return "undefined";
1490
- }
1491
- try {
1492
- const serialized = JSON.stringify(value, null, pretty ? 2 : 0);
1493
- return serialized ?? "undefined";
1494
- } catch {
1495
- return String(value);
1496
- }
1497
- }
1498
1506
  function countNameCollisions(names) {
1499
1507
  const out = new Map;
1500
1508
  for (const name of names) {
@@ -1691,103 +1699,89 @@ function createOpenAICompatibleAdapter(options) {
1691
1699
  if (usesMCP) {
1692
1700
  return streamWithChatCompletionsWithMCP(options, fetcher, path, request, callbacks);
1693
1701
  }
1694
- const response = await fetcher(buildURL(options.baseURL, path), {
1695
- method: "POST",
1696
- headers: buildHeaders(options),
1697
- body: JSON.stringify(cleanUndefined({
1698
- ...options.defaultBody,
1699
- ...request.body,
1700
- model: options.model,
1701
- messages: buildMessages(request),
1702
- temperature: request.temperature,
1703
- reasoning_effort: toOpenAIReasoningEffort(request.reasoningEffort),
1704
- max_tokens: request.maxTokens,
1705
- stream: true
1706
- })),
1707
- signal: request.signal
1708
- });
1709
- if (!response.ok) {
1710
- const message = await response.text();
1711
- throw new Error(`HTTP ${response.status}: ${message}`);
1712
- }
1713
- callbacks.onStart?.();
1714
- let text = "";
1715
- let reasoning = "";
1716
- let usage;
1717
- let finishReason;
1718
- await consumeSSE(response, (data) => {
1719
- if (data === "[DONE]") {
1720
- return;
1721
- }
1722
- const json = safeJSONParse(data);
1723
- if (!isRecord2(json)) {
1724
- return;
1725
- }
1726
- const delta = pickAssistantDelta(json);
1727
- const reasoningDelta = pickAssistantReasoningDelta(json);
1728
- const chunkUsage = pickUsage(json);
1729
- const chunkFinishReason = pickFinishReason(json);
1730
- usage = preferLatestUsage(usage, chunkUsage);
1731
- if (chunkFinishReason) {
1732
- finishReason = chunkFinishReason;
1733
- }
1734
- if (delta) {
1735
- text += delta;
1736
- callbacks.onToken?.(delta);
1737
- }
1738
- if (reasoningDelta) {
1739
- reasoning += reasoningDelta;
1740
- }
1741
- if (delta || reasoningDelta || chunkUsage || chunkFinishReason) {
1742
- const chunk = {
1743
- textDelta: delta,
1744
- reasoningDelta: reasoningDelta || undefined,
1745
- raw: json,
1746
- usage: chunkUsage,
1747
- finishReason: chunkFinishReason
1748
- };
1749
- callbacks.onChunk?.(chunk);
1750
- }
1751
- });
1752
- const out = {
1753
- text,
1754
- reasoning: reasoning.length > 0 ? reasoning : undefined,
1755
- usage,
1756
- finishReason
1757
- };
1758
- callbacks.onComplete?.(out);
1759
- return out;
1702
+ return streamWithChatCompletionsPassThrough(options, fetcher, path, request, callbacks);
1760
1703
  },
1761
1704
  async embed(request) {
1762
- const body = cleanUndefined({
1763
- ...options.defaultBody,
1764
- ...request.body,
1765
- model: request.model ?? options.model,
1766
- input: request.input,
1767
- dimensions: request.dimensions,
1768
- encoding_format: "float"
1769
- });
1770
- const response = await fetcher(buildURL(options.baseURL, embeddingPath), {
1771
- method: "POST",
1772
- headers: buildHeaders(options),
1773
- body: JSON.stringify(body)
1774
- });
1775
- if (!response.ok) {
1776
- const message = await response.text();
1777
- throw new Error(`HTTP ${response.status}: ${message}`);
1778
- }
1779
- const json = await response.json();
1780
- const data = json.data;
1781
- if (!Array.isArray(data)) {
1782
- throw new Error("Unexpected embedding response: missing data array");
1783
- }
1784
- return {
1785
- embeddings: data.map((d) => isRecord2(d) && Array.isArray(d.embedding) ? d.embedding : []),
1786
- model: pickString(json.model) ?? body.model,
1787
- usage: pickUsage(json),
1788
- raw: json
1789
- };
1705
+ return embedOpenAI(options, fetcher, embeddingPath, request);
1706
+ }
1707
+ };
1708
+ }
1709
+ async function streamWithChatCompletionsPassThrough(options, fetcher, path, request, callbacks) {
1710
+ const response = await sendOpenAIRequest(options, fetcher, path, request, buildChatCompletionsBody(options, request, {
1711
+ messages: buildMessages(request),
1712
+ stream: true
1713
+ }));
1714
+ if (!response.ok) {
1715
+ const message = await response.text();
1716
+ throw new Error(`HTTP ${response.status}: ${message}`);
1717
+ }
1718
+ callbacks.onStart?.();
1719
+ let text = "";
1720
+ let reasoning = "";
1721
+ let usage;
1722
+ let finishReason;
1723
+ await consumeSSE(response, (data) => {
1724
+ if (data === "[DONE]") {
1725
+ return;
1726
+ }
1727
+ const json = safeJSONParse(data);
1728
+ if (!isRecord2(json)) {
1729
+ return;
1730
+ }
1731
+ const delta = pickAssistantDelta(json);
1732
+ const reasoningDelta = pickAssistantReasoningDelta(json);
1733
+ const chunkUsage = pickUsage(json);
1734
+ const chunkFinishReason = pickFinishReason(json);
1735
+ usage = preferLatestUsage(usage, chunkUsage);
1736
+ if (chunkFinishReason) {
1737
+ finishReason = chunkFinishReason;
1738
+ }
1739
+ if (delta) {
1740
+ text += delta;
1741
+ callbacks.onToken?.(delta);
1790
1742
  }
1743
+ if (reasoningDelta) {
1744
+ reasoning += reasoningDelta;
1745
+ }
1746
+ emitOpenAIStreamChunk(callbacks, undefined, json, delta, reasoningDelta, chunkUsage, chunkFinishReason);
1747
+ });
1748
+ const out = {
1749
+ text,
1750
+ reasoning: reasoning.length > 0 ? reasoning : undefined,
1751
+ usage,
1752
+ finishReason
1753
+ };
1754
+ callbacks.onComplete?.(out);
1755
+ return out;
1756
+ }
1757
+ async function embedOpenAI(options, fetcher, path, request) {
1758
+ const body = cleanUndefined({
1759
+ ...options.defaultBody,
1760
+ ...request.body,
1761
+ model: request.model ?? options.model,
1762
+ input: request.input,
1763
+ dimensions: request.dimensions,
1764
+ encoding_format: "float"
1765
+ });
1766
+ const response = await fetcher(buildURL(options.baseURL, path), {
1767
+ method: "POST",
1768
+ headers: buildHeaders(options),
1769
+ body: JSON.stringify(body)
1770
+ });
1771
+ if (!response.ok) {
1772
+ const message = await response.text();
1773
+ throw new Error(`HTTP ${response.status}: ${message}`);
1774
+ }
1775
+ const json = await response.json();
1776
+ const data = json.data;
1777
+ if (!Array.isArray(data)) {
1778
+ throw new Error("Unexpected embedding response: missing data array");
1779
+ }
1780
+ return {
1781
+ embeddings: data.map((d) => isRecord2(d) && Array.isArray(d.embedding) ? d.embedding : []),
1782
+ model: pickString(json.model) ?? body.model,
1783
+ usage: pickUsage(json),
1784
+ raw: json
1791
1785
  };
1792
1786
  }
1793
1787
  async function completeOpenAIRequest(options, fetcher, chatPath, responsesPath, request) {
@@ -1804,22 +1798,80 @@ async function completeOpenAIRequest(options, fetcher, chatPath, responsesPath,
1804
1798
  }
1805
1799
  return completeWithChatCompletionsPassThrough(options, fetcher, chatPath, request);
1806
1800
  }
1807
- async function completeWithChatCompletionsPassThrough(options, fetcher, path, request) {
1808
- const response = await fetcher(buildURL(options.baseURL, path), {
1801
+ function buildChatCompletionsBody(options, request, overrides) {
1802
+ return buildOpenAIRequestBody(options, request, "max_tokens", overrides);
1803
+ }
1804
+ function buildResponsesBody(options, request, overrides) {
1805
+ return buildOpenAIRequestBody(options, request, "max_output_tokens", overrides);
1806
+ }
1807
+ function buildOpenAIRequestBody(options, request, maxTokenKey, overrides) {
1808
+ return cleanUndefined({
1809
+ ...options.defaultBody,
1810
+ ...request.body,
1811
+ model: options.model,
1812
+ temperature: request.temperature,
1813
+ reasoning_effort: toOpenAIReasoningEffort(request.reasoningEffort),
1814
+ [maxTokenKey]: request.maxTokens,
1815
+ ...overrides
1816
+ });
1817
+ }
1818
+ function sendOpenAIRequest(options, fetcher, path, request, body) {
1819
+ return fetcher(buildURL(options.baseURL, path), {
1809
1820
  method: "POST",
1810
1821
  headers: buildHeaders(options),
1811
- body: JSON.stringify(cleanUndefined({
1812
- ...options.defaultBody,
1813
- ...request.body,
1814
- model: options.model,
1815
- messages: buildMessages(request),
1816
- temperature: request.temperature,
1817
- reasoning_effort: toOpenAIReasoningEffort(request.reasoningEffort),
1818
- max_tokens: request.maxTokens,
1819
- stream: false
1820
- })),
1822
+ body: JSON.stringify(body),
1821
1823
  signal: request.signal
1822
1824
  });
1825
+ }
1826
+ async function sendOpenAIJsonRequest(options, fetcher, path, request, body) {
1827
+ const response = await sendOpenAIRequest(options, fetcher, path, request, body);
1828
+ if (!response.ok) {
1829
+ const message = await response.text();
1830
+ throw new Error(`HTTP ${response.status}: ${message}`);
1831
+ }
1832
+ return await response.json();
1833
+ }
1834
+ function createResponsesMCPState(request) {
1835
+ return {
1836
+ input: buildResponsesInput(request),
1837
+ previousResponseId: pickString(isRecord2(request.body) ? request.body.previous_response_id : undefined),
1838
+ aggregatedUsage: undefined,
1839
+ finishReason: undefined,
1840
+ lastPayload: undefined,
1841
+ executedToolCalls: [],
1842
+ toolExecutions: [],
1843
+ reasoningBlocks: []
1844
+ };
1845
+ }
1846
+ function buildResponsesMCPResult(state, text, raw) {
1847
+ return {
1848
+ text,
1849
+ reasoning: joinReasoningBlocks(state.reasoningBlocks) || undefined,
1850
+ reasoningBlocks: state.reasoningBlocks.length > 0 ? state.reasoningBlocks : undefined,
1851
+ raw,
1852
+ usage: state.aggregatedUsage,
1853
+ finishReason: state.finishReason,
1854
+ toolCalls: state.executedToolCalls.length > 0 ? state.executedToolCalls : undefined,
1855
+ toolExecutions: state.toolExecutions.length > 0 ? state.toolExecutions : undefined
1856
+ };
1857
+ }
1858
+ function emitOpenAIStreamChunk(callbacks, round, raw, delta, reasoningDelta, usage, finishReason) {
1859
+ if (delta || reasoningDelta || usage || finishReason) {
1860
+ callbacks.onChunk?.({
1861
+ textDelta: delta,
1862
+ reasoningDelta: reasoningDelta || undefined,
1863
+ ...round !== undefined ? { turnIndex: round } : {},
1864
+ raw,
1865
+ usage,
1866
+ finishReason
1867
+ });
1868
+ }
1869
+ }
1870
+ async function completeWithChatCompletionsPassThrough(options, fetcher, path, request) {
1871
+ const response = await sendOpenAIRequest(options, fetcher, path, request, buildChatCompletionsBody(options, request, {
1872
+ messages: buildMessages(request),
1873
+ stream: false
1874
+ }));
1823
1875
  if (!response.ok) {
1824
1876
  const message = await response.text();
1825
1877
  throw new Error(`HTTP ${response.status}: ${message}`);
@@ -1867,44 +1919,32 @@ async function completeWithChatCompletionsWithMCP(options, fetcher, path, reques
1867
1919
  let lastPayload;
1868
1920
  const toolCalls = [];
1869
1921
  const toolExecutions = [];
1922
+ const reasoningBlocks = [];
1870
1923
  for (let round = 1;round <= maxToolRounds + 1; round += 1) {
1871
1924
  const mcpToolset = await resolveMCPToolset(request.mcpClients);
1872
1925
  const transportTools = toProviderFunctionTools(mcpToolset);
1873
- const response = await fetcher(buildURL(options.baseURL, path), {
1874
- method: "POST",
1875
- headers: buildHeaders(options),
1876
- body: JSON.stringify(cleanUndefined({
1877
- ...options.defaultBody,
1878
- ...request.body,
1879
- model: options.model,
1880
- messages,
1881
- temperature: request.temperature,
1882
- reasoning_effort: toOpenAIReasoningEffort(request.reasoningEffort),
1883
- max_tokens: request.maxTokens,
1884
- tools: transportTools,
1885
- tool_choice: request.toolChoice,
1886
- parallel_tool_calls: request.parallelToolCalls
1887
- })),
1888
- signal: request.signal
1889
- });
1890
- if (!response.ok) {
1891
- const message = await response.text();
1892
- throw new Error(`HTTP ${response.status}: ${message}`);
1893
- }
1894
- const payload = await response.json();
1926
+ const payload = await sendOpenAIJsonRequest(options, fetcher, path, request, buildChatCompletionsBody(options, request, {
1927
+ messages,
1928
+ tools: transportTools,
1929
+ tool_choice: request.toolChoice,
1930
+ parallel_tool_calls: request.parallelToolCalls
1931
+ }));
1895
1932
  lastPayload = payload;
1896
1933
  aggregatedUsage = mergeUsage(aggregatedUsage, pickUsage(payload));
1897
1934
  finishReason = pickFinishReason(payload);
1898
1935
  const assistantMessage = pickAssistantMessage(payload);
1899
1936
  const calledTools = pickChatToolCalls(payload);
1937
+ const roundReasoning = pickAssistantReasoning(payload);
1938
+ pushReasoningBlock(reasoningBlocks, round, roundReasoning);
1900
1939
  if (!assistantMessage) {
1901
1940
  throw new Error("No assistant message in OpenAI-compatible response.");
1902
1941
  }
1903
1942
  if (calledTools.length === 0) {
1904
- const reasoning = pickAssistantReasoning(payload);
1943
+ const reasoning = joinReasoningBlocks(reasoningBlocks) || undefined;
1905
1944
  return {
1906
1945
  text: pickAssistantText(payload),
1907
- reasoning: reasoning.length > 0 ? reasoning : undefined,
1946
+ reasoning,
1947
+ reasoningBlocks: reasoningBlocks.length > 0 ? reasoningBlocks : undefined,
1908
1948
  raw: payload,
1909
1949
  usage: aggregatedUsage,
1910
1950
  finishReason,
@@ -1932,10 +1972,8 @@ async function completeWithChatCompletionsWithMCP(options, fetcher, path, reques
1932
1972
  }
1933
1973
  return {
1934
1974
  text: pickAssistantText(lastPayload ?? {}),
1935
- reasoning: (() => {
1936
- const value = pickAssistantReasoning(lastPayload ?? {});
1937
- return value.length > 0 ? value : undefined;
1938
- })(),
1975
+ reasoning: joinReasoningBlocks(reasoningBlocks) || undefined,
1976
+ reasoningBlocks: reasoningBlocks.length > 0 ? reasoningBlocks : undefined,
1939
1977
  raw: lastPayload,
1940
1978
  usage: aggregatedUsage,
1941
1979
  finishReason,
@@ -1945,26 +1983,10 @@ async function completeWithChatCompletionsWithMCP(options, fetcher, path, reques
1945
1983
  }
1946
1984
  async function completeWithResponsesAPIPassThrough(options, fetcher, path, request) {
1947
1985
  const body = isRecord2(request.body) ? request.body : undefined;
1948
- const response = await fetcher(buildURL(options.baseURL, path), {
1949
- method: "POST",
1950
- headers: buildHeaders(options),
1951
- body: JSON.stringify(cleanUndefined({
1952
- ...options.defaultBody,
1953
- ...request.body,
1954
- model: options.model,
1955
- input: buildResponsesInput(request),
1956
- previous_response_id: pickString(body?.previous_response_id),
1957
- temperature: request.temperature,
1958
- reasoning_effort: toOpenAIReasoningEffort(request.reasoningEffort),
1959
- max_output_tokens: request.maxTokens
1960
- })),
1961
- signal: request.signal
1962
- });
1963
- if (!response.ok) {
1964
- const message = await response.text();
1965
- throw new Error(`HTTP ${response.status}: ${message}`);
1966
- }
1967
- const payload = await response.json();
1986
+ const payload = await sendOpenAIJsonRequest(options, fetcher, path, request, buildResponsesBody(options, request, {
1987
+ input: buildResponsesInput(request),
1988
+ previous_response_id: pickString(body?.previous_response_id)
1989
+ }));
1968
1990
  const toolCalls = pickResponsesToolCalls(payload);
1969
1991
  return {
1970
1992
  text: pickResponsesText(payload) || pickAssistantText(payload),
@@ -1976,54 +1998,26 @@ async function completeWithResponsesAPIPassThrough(options, fetcher, path, reque
1976
1998
  }
1977
1999
  async function completeWithResponsesAPIWithMCP(options, fetcher, path, request) {
1978
2000
  const maxToolRounds = normalizeMaxToolRounds(request.maxToolRounds ?? options.defaultMaxToolRounds);
1979
- let input = buildResponsesInput(request);
1980
- let previousResponseId = pickString(isRecord2(request.body) ? request.body.previous_response_id : undefined);
1981
- let aggregatedUsage;
1982
- let finishReason;
1983
- let lastPayload;
1984
- const executedToolCalls = [];
1985
- const toolExecutions = [];
2001
+ const state = createResponsesMCPState(request);
1986
2002
  for (let round = 1;round <= maxToolRounds + 1; round += 1) {
1987
2003
  const mcpToolset = await resolveMCPToolset(request.mcpClients);
1988
2004
  const transportTools = toResponsesTools(toProviderFunctionTools(mcpToolset));
1989
- const response = await fetcher(buildURL(options.baseURL, path), {
1990
- method: "POST",
1991
- headers: buildHeaders(options),
1992
- body: JSON.stringify(cleanUndefined({
1993
- ...options.defaultBody,
1994
- ...request.body,
1995
- model: options.model,
1996
- input,
1997
- previous_response_id: previousResponseId,
1998
- temperature: request.temperature,
1999
- reasoning_effort: toOpenAIReasoningEffort(request.reasoningEffort),
2000
- max_output_tokens: request.maxTokens,
2001
- tools: transportTools,
2002
- tool_choice: request.toolChoice,
2003
- parallel_tool_calls: request.parallelToolCalls
2004
- })),
2005
- signal: request.signal
2006
- });
2007
- if (!response.ok) {
2008
- const message = await response.text();
2009
- throw new Error(`HTTP ${response.status}: ${message}`);
2010
- }
2011
- const payload = await response.json();
2012
- lastPayload = payload;
2013
- aggregatedUsage = mergeUsage(aggregatedUsage, pickUsage(payload));
2014
- finishReason = pickResponsesFinishReason(payload) ?? finishReason;
2005
+ const payload = await sendOpenAIJsonRequest(options, fetcher, path, request, buildResponsesBody(options, request, {
2006
+ input: state.input,
2007
+ previous_response_id: state.previousResponseId,
2008
+ tools: transportTools,
2009
+ tool_choice: request.toolChoice,
2010
+ parallel_tool_calls: request.parallelToolCalls
2011
+ }));
2012
+ state.lastPayload = payload;
2013
+ state.aggregatedUsage = mergeUsage(state.aggregatedUsage, pickUsage(payload));
2014
+ state.finishReason = pickResponsesFinishReason(payload) ?? state.finishReason;
2015
+ pushReasoningBlock(state.reasoningBlocks, round, pickResponsesReasoning(payload));
2015
2016
  const providerToolCalls = pickResponsesToolCalls(payload);
2016
2017
  const functionCalls = providerToolCalls.filter((toolCall) => toolCall.type === "function" && typeof toolCall.id === "string" && typeof toolCall.name === "string");
2017
2018
  if (functionCalls.length === 0) {
2018
2019
  const text = pickResponsesText(payload) || pickAssistantText(payload);
2019
- return {
2020
- text,
2021
- raw: payload,
2022
- usage: aggregatedUsage,
2023
- finishReason,
2024
- toolCalls: executedToolCalls.length > 0 ? executedToolCalls : undefined,
2025
- toolExecutions: toolExecutions.length > 0 ? toolExecutions : undefined
2026
- };
2020
+ return buildResponsesMCPResult(state, text, payload);
2027
2021
  }
2028
2022
  if (round > maxToolRounds) {
2029
2023
  throw new Error(`Tool call loop exceeded maxToolRounds (${maxToolRounds}).`);
@@ -2034,23 +2028,16 @@ async function completeWithResponsesAPIWithMCP(options, fetcher, path, request)
2034
2028
  provider: "openai-compatible",
2035
2029
  model: options.model
2036
2030
  });
2037
- executedToolCalls.push(...outputs.map((entry) => entry.call));
2038
- toolExecutions.push(...outputs.map((entry) => entry.execution));
2039
- input = outputs.map((entry) => ({
2031
+ state.executedToolCalls.push(...outputs.map((entry) => entry.call));
2032
+ state.toolExecutions.push(...outputs.map((entry) => entry.execution));
2033
+ state.input = outputs.map((entry) => ({
2040
2034
  type: "function_call_output",
2041
2035
  call_id: entry.call.id,
2042
2036
  output: stringifyToolOutput(entry.call.error ? { error: entry.call.error } : entry.call.output)
2043
2037
  }));
2044
- previousResponseId = pickString(payload.id);
2038
+ state.previousResponseId = pickString(payload.id);
2045
2039
  }
2046
- return {
2047
- text: pickResponsesText(lastPayload ?? {}) || pickAssistantText(lastPayload ?? {}),
2048
- raw: lastPayload,
2049
- usage: aggregatedUsage,
2050
- finishReason,
2051
- toolCalls: executedToolCalls.length > 0 ? executedToolCalls : undefined,
2052
- toolExecutions: toolExecutions.length > 0 ? toolExecutions : undefined
2053
- };
2040
+ return buildResponsesMCPResult(state, pickResponsesText(state.lastPayload ?? {}) || pickAssistantText(state.lastPayload ?? {}), state.lastPayload);
2054
2041
  }
2055
2042
  async function streamWithChatCompletionsWithMCP(options, fetcher, path, request, callbacks) {
2056
2043
  const maxToolRounds = normalizeMaxToolRounds(request.maxToolRounds ?? options.defaultMaxToolRounds);
@@ -2060,30 +2047,19 @@ async function streamWithChatCompletionsWithMCP(options, fetcher, path, request,
2060
2047
  let lastPayload;
2061
2048
  const executedToolCalls = [];
2062
2049
  const toolExecutions = [];
2050
+ const reasoningBlocks = [];
2063
2051
  callbacks.onStart?.();
2064
2052
  let lastRoundText = "";
2065
- let lastRoundReasoning = "";
2066
2053
  for (let round = 1;round <= maxToolRounds + 1; round += 1) {
2067
2054
  const mcpToolset = await resolveMCPToolset(request.mcpClients);
2068
2055
  const transportTools = toProviderFunctionTools(mcpToolset);
2069
- const response = await fetcher(buildURL(options.baseURL, path), {
2070
- method: "POST",
2071
- headers: buildHeaders(options),
2072
- body: JSON.stringify(cleanUndefined({
2073
- ...options.defaultBody,
2074
- ...request.body,
2075
- model: options.model,
2076
- messages,
2077
- temperature: request.temperature,
2078
- reasoning_effort: toOpenAIReasoningEffort(request.reasoningEffort),
2079
- max_tokens: request.maxTokens,
2080
- tools: transportTools,
2081
- tool_choice: request.toolChoice,
2082
- parallel_tool_calls: request.parallelToolCalls,
2083
- stream: true
2084
- })),
2085
- signal: request.signal
2086
- });
2056
+ const response = await sendOpenAIRequest(options, fetcher, path, request, buildChatCompletionsBody(options, request, {
2057
+ messages,
2058
+ tools: transportTools,
2059
+ tool_choice: request.toolChoice,
2060
+ parallel_tool_calls: request.parallelToolCalls,
2061
+ stream: true
2062
+ }));
2087
2063
  if (!response.ok) {
2088
2064
  const message = await response.text();
2089
2065
  throw new Error(`HTTP ${response.status}: ${message}`);
@@ -2120,38 +2096,48 @@ async function streamWithChatCompletionsWithMCP(options, fetcher, path, request,
2120
2096
  roundReasoning += reasoningDelta;
2121
2097
  reasoningFieldName ??= pickAssistantReasoningDeltaFieldName(json);
2122
2098
  }
2123
- if (delta || reasoningDelta || chunkUsage || chunkFinishReason) {
2124
- const chunk = {
2125
- textDelta: delta,
2126
- reasoningDelta: reasoningDelta || undefined,
2127
- raw: json,
2128
- usage: chunkUsage,
2129
- finishReason: chunkFinishReason
2130
- };
2131
- callbacks.onChunk?.(chunk);
2132
- }
2099
+ emitOpenAIStreamChunk(callbacks, round, json, delta, reasoningDelta, chunkUsage, chunkFinishReason);
2133
2100
  });
2134
2101
  aggregatedUsage = mergeUsage(aggregatedUsage, roundUsage);
2135
2102
  if (roundFinishReason) {
2136
2103
  finishReason = roundFinishReason;
2137
2104
  }
2138
2105
  const calledTools = buildOpenAIStreamToolCalls(streamedToolCalls);
2106
+ pushReasoningBlock(reasoningBlocks, round, roundReasoning);
2107
+ request.onTurnTransition?.({
2108
+ turnIndex: round,
2109
+ kind: "reasoningComplete",
2110
+ reasoningText: roundReasoning
2111
+ });
2139
2112
  if (calledTools.length === 0) {
2140
2113
  const out2 = {
2141
2114
  text: roundText,
2142
- reasoning: roundReasoning.length > 0 ? roundReasoning : undefined,
2115
+ reasoning: joinReasoningBlocks(reasoningBlocks) || undefined,
2116
+ reasoningBlocks: reasoningBlocks.length > 0 ? reasoningBlocks : undefined,
2143
2117
  raw: lastPayload,
2144
2118
  usage: aggregatedUsage,
2145
2119
  finishReason,
2146
2120
  toolCalls: executedToolCalls.length > 0 ? executedToolCalls : undefined,
2147
2121
  toolExecutions: toolExecutions.length > 0 ? toolExecutions : undefined
2148
2122
  };
2123
+ request.onTurnTransition?.({ turnIndex: round, kind: "streamEnd" });
2149
2124
  callbacks.onComplete?.(out2);
2150
2125
  return out2;
2151
2126
  }
2152
2127
  if (round > maxToolRounds) {
2153
2128
  throw new Error(`Tool call loop exceeded maxToolRounds (${maxToolRounds}).`);
2154
2129
  }
2130
+ request.onTurnTransition?.({
2131
+ turnIndex: round,
2132
+ kind: "toolCallsEmit",
2133
+ toolCalls: calledTools
2134
+ });
2135
+ callbacks.onChunk?.({
2136
+ textDelta: "",
2137
+ turnIndex: round,
2138
+ toolCalls: calledTools,
2139
+ finishReason: roundFinishReason
2140
+ });
2155
2141
  const outputs = await executeMCPToolCalls(calledTools, mcpToolset, {
2156
2142
  round,
2157
2143
  request,
@@ -2160,8 +2146,8 @@ async function streamWithChatCompletionsWithMCP(options, fetcher, path, request,
2160
2146
  });
2161
2147
  executedToolCalls.push(...outputs.map((entry) => entry.call));
2162
2148
  toolExecutions.push(...outputs.map((entry) => entry.execution));
2149
+ request.onTurnTransition?.({ turnIndex: round, kind: "toolResultsReceived" });
2163
2150
  lastRoundText = roundText;
2164
- lastRoundReasoning = roundReasoning;
2165
2151
  const assistantMessage = buildOpenAIAssistantToolMessage(roundText, calledTools, {
2166
2152
  reasoning: roundReasoning,
2167
2153
  reasoningFieldName
@@ -2175,34 +2161,25 @@ async function streamWithChatCompletionsWithMCP(options, fetcher, path, request,
2175
2161
  }
2176
2162
  const out = {
2177
2163
  text: lastRoundText,
2178
- reasoning: lastRoundReasoning.length > 0 ? lastRoundReasoning : undefined,
2164
+ reasoning: joinReasoningBlocks(reasoningBlocks) || undefined,
2165
+ reasoningBlocks: reasoningBlocks.length > 0 ? reasoningBlocks : undefined,
2179
2166
  raw: lastPayload,
2180
2167
  usage: aggregatedUsage,
2181
2168
  finishReason,
2182
2169
  toolCalls: executedToolCalls.length > 0 ? executedToolCalls : undefined,
2183
2170
  toolExecutions: toolExecutions.length > 0 ? toolExecutions : undefined
2184
2171
  };
2172
+ request.onTurnTransition?.({ turnIndex: maxToolRounds + 1, kind: "streamEnd" });
2185
2173
  callbacks.onComplete?.(out);
2186
2174
  return out;
2187
2175
  }
2188
2176
  async function streamWithResponsesAPIPassThrough(options, fetcher, path, request, callbacks) {
2189
2177
  const body = isRecord2(request.body) ? request.body : undefined;
2190
- const response = await fetcher(buildURL(options.baseURL, path), {
2191
- method: "POST",
2192
- headers: buildHeaders(options),
2193
- body: JSON.stringify(cleanUndefined({
2194
- ...options.defaultBody,
2195
- ...request.body,
2196
- model: options.model,
2197
- input: buildResponsesInput(request),
2198
- previous_response_id: pickString(body?.previous_response_id),
2199
- temperature: request.temperature,
2200
- reasoning_effort: toOpenAIReasoningEffort(request.reasoningEffort),
2201
- max_output_tokens: request.maxTokens,
2202
- stream: true
2203
- })),
2204
- signal: request.signal
2205
- });
2178
+ const response = await sendOpenAIRequest(options, fetcher, path, request, buildResponsesBody(options, request, {
2179
+ input: buildResponsesInput(request),
2180
+ previous_response_id: pickString(body?.previous_response_id),
2181
+ stream: true
2182
+ }));
2206
2183
  if (!response.ok) {
2207
2184
  const message = await response.text();
2208
2185
  throw new Error(`HTTP ${response.status}: ${message}`);
@@ -2235,15 +2212,7 @@ async function streamWithResponsesAPIPassThrough(options, fetcher, path, request
2235
2212
  text += delta;
2236
2213
  callbacks.onToken?.(delta);
2237
2214
  }
2238
- if (delta || chunkUsage || chunkFinishReason) {
2239
- const chunk = {
2240
- textDelta: delta,
2241
- raw: json,
2242
- usage: chunkUsage,
2243
- finishReason: chunkFinishReason
2244
- };
2245
- callbacks.onChunk?.(chunk);
2246
- }
2215
+ emitOpenAIStreamChunk(callbacks, undefined, json, delta, "", chunkUsage, chunkFinishReason);
2247
2216
  });
2248
2217
  const finalPayload = lastPayload ?? {};
2249
2218
  const out = {
@@ -2257,41 +2226,25 @@ async function streamWithResponsesAPIPassThrough(options, fetcher, path, request
2257
2226
  }
2258
2227
  async function streamWithResponsesAPIWithMCP(options, fetcher, path, request, callbacks) {
2259
2228
  const maxToolRounds = normalizeMaxToolRounds(request.maxToolRounds ?? options.defaultMaxToolRounds);
2260
- let input = buildResponsesInput(request);
2261
- let previousResponseId = pickString(isRecord2(request.body) ? request.body.previous_response_id : undefined);
2262
- let aggregatedUsage;
2263
- let finishReason;
2264
- let lastPayload;
2265
- const executedToolCalls = [];
2266
- const toolExecutions = [];
2229
+ const state = createResponsesMCPState(request);
2267
2230
  callbacks.onStart?.();
2268
2231
  for (let round = 1;round <= maxToolRounds + 1; round += 1) {
2269
2232
  const mcpToolset = await resolveMCPToolset(request.mcpClients);
2270
2233
  const transportTools = toResponsesTools(toProviderFunctionTools(mcpToolset));
2271
- const response = await fetcher(buildURL(options.baseURL, path), {
2272
- method: "POST",
2273
- headers: buildHeaders(options),
2274
- body: JSON.stringify(cleanUndefined({
2275
- ...options.defaultBody,
2276
- ...request.body,
2277
- model: options.model,
2278
- input,
2279
- previous_response_id: previousResponseId,
2280
- temperature: request.temperature,
2281
- reasoning_effort: toOpenAIReasoningEffort(request.reasoningEffort),
2282
- max_output_tokens: request.maxTokens,
2283
- tools: transportTools,
2284
- tool_choice: request.toolChoice,
2285
- parallel_tool_calls: request.parallelToolCalls,
2286
- stream: true
2287
- })),
2288
- signal: request.signal
2289
- });
2234
+ const response = await sendOpenAIRequest(options, fetcher, path, request, buildResponsesBody(options, request, {
2235
+ input: state.input,
2236
+ previous_response_id: state.previousResponseId,
2237
+ tools: transportTools,
2238
+ tool_choice: request.toolChoice,
2239
+ parallel_tool_calls: request.parallelToolCalls,
2240
+ stream: true
2241
+ }));
2290
2242
  if (!response.ok) {
2291
2243
  const message = await response.text();
2292
2244
  throw new Error(`HTTP ${response.status}: ${message}`);
2293
2245
  }
2294
2246
  let roundText = "";
2247
+ let roundReasoning = "";
2295
2248
  let roundUsage;
2296
2249
  let roundFinishReason;
2297
2250
  let roundPayload;
@@ -2307,9 +2260,10 @@ async function streamWithResponsesAPIWithMCP(options, fetcher, path, request, ca
2307
2260
  const payload = pickResponsesStreamPayload(json);
2308
2261
  if (payload) {
2309
2262
  roundPayload = payload;
2310
- lastPayload = payload;
2263
+ state.lastPayload = payload;
2311
2264
  }
2312
2265
  const delta = pickResponsesStreamTextDelta(json);
2266
+ const reasoningDelta = pickResponsesStreamReasoningDelta(json);
2313
2267
  const chunkUsage = pickResponsesStreamUsage(json);
2314
2268
  const chunkFinishReason = pickResponsesStreamFinishReason(json);
2315
2269
  collectResponsesStreamToolCalls(json, streamedToolCalls);
@@ -2321,66 +2275,70 @@ async function streamWithResponsesAPIWithMCP(options, fetcher, path, request, ca
2321
2275
  roundText += delta;
2322
2276
  callbacks.onToken?.(delta);
2323
2277
  }
2324
- if (delta || chunkUsage || chunkFinishReason) {
2325
- const chunk = {
2326
- textDelta: delta,
2327
- raw: json,
2328
- usage: chunkUsage,
2329
- finishReason: chunkFinishReason
2330
- };
2331
- callbacks.onChunk?.(chunk);
2278
+ if (reasoningDelta) {
2279
+ roundReasoning += reasoningDelta;
2332
2280
  }
2281
+ emitOpenAIStreamChunk(callbacks, round, json, delta, reasoningDelta, chunkUsage, chunkFinishReason);
2333
2282
  });
2334
2283
  const resolvedRoundUsage = preferLatestUsage(roundUsage, roundPayload ? pickUsage(roundPayload) : undefined);
2335
- aggregatedUsage = mergeUsage(aggregatedUsage, resolvedRoundUsage);
2284
+ state.aggregatedUsage = mergeUsage(state.aggregatedUsage, resolvedRoundUsage);
2336
2285
  if (roundFinishReason) {
2337
- finishReason = roundFinishReason;
2286
+ state.finishReason = roundFinishReason;
2338
2287
  } else if (roundPayload) {
2339
- finishReason = pickResponsesFinishReason(roundPayload) ?? finishReason;
2288
+ state.finishReason = pickResponsesFinishReason(roundPayload) ?? state.finishReason;
2340
2289
  }
2341
2290
  const payloadToolCalls = roundPayload ? pickResponsesToolCalls(roundPayload) : [];
2291
+ if (roundPayload && roundReasoning.length === 0) {
2292
+ roundReasoning = pickResponsesReasoning(roundPayload);
2293
+ }
2342
2294
  const streamedCalls = buildResponsesStreamToolCalls(streamedToolCalls);
2343
2295
  const providerToolCalls = payloadToolCalls.length > 0 ? payloadToolCalls : streamedCalls;
2344
2296
  const functionCalls = providerToolCalls.filter((toolCall) => toolCall.type === "function" && typeof toolCall.id === "string" && typeof toolCall.name === "string");
2297
+ pushReasoningBlock(state.reasoningBlocks, round, roundReasoning);
2298
+ request.onTurnTransition?.({
2299
+ turnIndex: round,
2300
+ kind: "reasoningComplete",
2301
+ reasoningText: roundReasoning
2302
+ });
2345
2303
  if (functionCalls.length === 0) {
2346
2304
  const finalText = roundText.length > 0 ? roundText : roundPayload ? pickResponsesText(roundPayload) || pickAssistantText(roundPayload) : "";
2347
- const out2 = {
2348
- text: finalText,
2349
- raw: roundPayload ?? lastPayload,
2350
- usage: aggregatedUsage,
2351
- finishReason,
2352
- toolCalls: executedToolCalls.length > 0 ? executedToolCalls : undefined,
2353
- toolExecutions: toolExecutions.length > 0 ? toolExecutions : undefined
2354
- };
2305
+ const out2 = buildResponsesMCPResult(state, finalText, roundPayload ?? state.lastPayload);
2306
+ request.onTurnTransition?.({ turnIndex: round, kind: "streamEnd" });
2355
2307
  callbacks.onComplete?.(out2);
2356
2308
  return out2;
2357
2309
  }
2358
2310
  if (round > maxToolRounds) {
2359
2311
  throw new Error(`Tool call loop exceeded maxToolRounds (${maxToolRounds}).`);
2360
2312
  }
2313
+ request.onTurnTransition?.({
2314
+ turnIndex: round,
2315
+ kind: "toolCallsEmit",
2316
+ toolCalls: functionCalls
2317
+ });
2318
+ callbacks.onChunk?.({
2319
+ textDelta: "",
2320
+ turnIndex: round,
2321
+ toolCalls: functionCalls,
2322
+ finishReason: roundFinishReason
2323
+ });
2361
2324
  const outputs = await executeMCPToolCalls(functionCalls, mcpToolset, {
2362
2325
  round,
2363
2326
  request,
2364
2327
  provider: "openai-compatible",
2365
2328
  model: options.model
2366
2329
  });
2367
- executedToolCalls.push(...outputs.map((entry) => entry.call));
2368
- toolExecutions.push(...outputs.map((entry) => entry.execution));
2369
- input = outputs.map((entry) => ({
2330
+ state.executedToolCalls.push(...outputs.map((entry) => entry.call));
2331
+ state.toolExecutions.push(...outputs.map((entry) => entry.execution));
2332
+ request.onTurnTransition?.({ turnIndex: round, kind: "toolResultsReceived" });
2333
+ state.input = outputs.map((entry) => ({
2370
2334
  type: "function_call_output",
2371
2335
  call_id: entry.call.id,
2372
2336
  output: stringifyToolOutput(entry.call.error ? { error: entry.call.error } : entry.call.output)
2373
2337
  }));
2374
- previousResponseId = pickString(roundPayload?.id);
2338
+ state.previousResponseId = pickString(roundPayload?.id);
2375
2339
  }
2376
- const out = {
2377
- text: pickResponsesText(lastPayload ?? {}) || pickAssistantText(lastPayload ?? {}),
2378
- raw: lastPayload,
2379
- usage: aggregatedUsage,
2380
- finishReason,
2381
- toolCalls: executedToolCalls.length > 0 ? executedToolCalls : undefined,
2382
- toolExecutions: toolExecutions.length > 0 ? toolExecutions : undefined
2383
- };
2340
+ const out = buildResponsesMCPResult(state, pickResponsesText(state.lastPayload ?? {}) || pickAssistantText(state.lastPayload ?? {}), state.lastPayload);
2341
+ request.onTurnTransition?.({ turnIndex: maxToolRounds + 1, kind: "streamEnd" });
2384
2342
  callbacks.onComplete?.(out);
2385
2343
  return out;
2386
2344
  }
@@ -2532,7 +2490,7 @@ function pickAssistantDelta(payload) {
2532
2490
  if (!isRecord2(delta)) {
2533
2491
  return "";
2534
2492
  }
2535
- return pickTextFromOpenAIContent(delta.content);
2493
+ return pickTextLike(delta.content);
2536
2494
  }
2537
2495
  function pickAssistantReasoning(payload) {
2538
2496
  const message = pickAssistantMessage(payload);
@@ -2665,6 +2623,20 @@ function pickResponsesStreamTextDelta(payload) {
2665
2623
  }
2666
2624
  return "";
2667
2625
  }
2626
+ function pickResponsesStreamReasoningDelta(payload) {
2627
+ const eventType = pickString(payload.type) ?? "";
2628
+ if (!eventType.includes("reasoning") && !eventType.includes("thinking")) {
2629
+ return "";
2630
+ }
2631
+ const direct = pickString(payload.delta);
2632
+ if (direct) {
2633
+ return direct;
2634
+ }
2635
+ if (isRecord2(payload.delta)) {
2636
+ return pickReasoningText(payload.delta) || pickString(payload.delta.text) || pickString(payload.delta.summary_text) || "";
2637
+ }
2638
+ return "";
2639
+ }
2668
2640
  function pickResponsesStreamUsage(payload) {
2669
2641
  const direct = pickUsage(payload);
2670
2642
  if (direct) {
@@ -2799,10 +2771,34 @@ function pickResponsesText(payload) {
2799
2771
  }).join("");
2800
2772
  }).join("");
2801
2773
  }
2774
+ function pickResponsesReasoning(payload) {
2775
+ const direct = pickReasoningText(payload);
2776
+ if (direct) {
2777
+ return direct;
2778
+ }
2779
+ const output = payload.output;
2780
+ if (!Array.isArray(output)) {
2781
+ return "";
2782
+ }
2783
+ return output.map((item) => {
2784
+ if (!isRecord2(item)) {
2785
+ return "";
2786
+ }
2787
+ const itemReasoning = pickReasoningText(item);
2788
+ if (itemReasoning) {
2789
+ return itemReasoning;
2790
+ }
2791
+ const itemType = pickString(item.type) ?? "";
2792
+ if ((itemType.includes("reasoning") || itemType.includes("thinking")) && Array.isArray(item.content)) {
2793
+ return item.content.map((part) => isRecord2(part) ? pickTextLike(part) : "").join("");
2794
+ }
2795
+ return "";
2796
+ }).join("");
2797
+ }
2802
2798
  function pickAssistantText(payload) {
2803
2799
  const message = pickAssistantMessage(payload);
2804
2800
  if (message) {
2805
- const text = pickTextFromOpenAIContent(message.content);
2801
+ const text = pickTextLike(message.content);
2806
2802
  if (text.length > 0) {
2807
2803
  return text;
2808
2804
  }
@@ -2819,8 +2815,17 @@ function pickAssistantText(payload) {
2819
2815
  function pickReasoningText(value) {
2820
2816
  return pickTextLike(value.reasoning) || pickTextLike(value.reasoning_content);
2821
2817
  }
2822
- function pickTextFromOpenAIContent(value) {
2823
- return pickTextLike(value);
2818
+ function pushReasoningBlock(blocks, turnIndex, text) {
2819
+ const clean = text?.replace(/<\/?think\s*>/gi, "").trim();
2820
+ if (!clean) {
2821
+ return;
2822
+ }
2823
+ blocks.push({ turnIndex, text: clean });
2824
+ }
2825
+ function joinReasoningBlocks(blocks) {
2826
+ return blocks.map((block) => block.text).filter(Boolean).join(`
2827
+
2828
+ `);
2824
2829
  }
2825
2830
  function pickTextLike(value) {
2826
2831
  if (typeof value === "string") {
@@ -2895,83 +2900,81 @@ function createAnthropicCompatibleAdapter(options) {
2895
2900
  if (hasMCPClients(request.mcpClients)) {
2896
2901
  return streamWithMCPToolLoop(options, fetcher, path, request, callbacks);
2897
2902
  }
2898
- const input = resolveAnthropicInput(request);
2899
- const response = await fetcher(buildURL(options.baseURL, path), {
2900
- method: "POST",
2901
- headers: buildHeaders2(options),
2902
- body: JSON.stringify(buildAnthropicRequestBody(options, request, {
2903
- ...options.defaultBody,
2904
- ...request.body,
2905
- model: options.model,
2906
- system: input.systemPrompt,
2907
- messages: input.messages,
2908
- temperature: request.temperature,
2909
- stream: true
2910
- })),
2911
- signal: request.signal
2912
- });
2913
- if (!response.ok) {
2914
- const message = await response.text();
2915
- throw new Error(`HTTP ${response.status}: ${message}`);
2916
- }
2917
- callbacks.onStart?.();
2918
- let text = "";
2919
- let usage;
2920
- let finishReason;
2921
- await consumeSSE(response, (data) => {
2922
- if (data === "[DONE]") {
2923
- return;
2924
- }
2925
- const json = safeJSONParse(data);
2926
- if (!isRecord2(json)) {
2927
- return;
2928
- }
2929
- const delta = pickAnthropicDelta(json);
2930
- const chunkUsage = pickUsage2(json);
2931
- const chunkFinishReason = pickFinishReason2(json);
2932
- usage = preferLatestUsage(usage, chunkUsage);
2933
- if (chunkFinishReason) {
2934
- finishReason = chunkFinishReason;
2935
- }
2936
- if (delta) {
2937
- text += delta;
2938
- callbacks.onToken?.(delta);
2939
- }
2940
- if (delta || chunkUsage || chunkFinishReason) {
2941
- const chunk = {
2942
- textDelta: delta,
2943
- raw: json,
2944
- usage: chunkUsage,
2945
- finishReason: chunkFinishReason
2946
- };
2947
- callbacks.onChunk?.(chunk);
2948
- }
2949
- });
2950
- const out = { text, usage, finishReason };
2951
- callbacks.onComplete?.(out);
2952
- return out;
2903
+ return streamPassThrough(options, fetcher, path, request, callbacks);
2953
2904
  },
2954
2905
  async embed() {
2955
2906
  throw new Error("Anthropic does not provide a native embedding API. " + "Use the openai-compatible provider with Voyage AI (https://api.voyageai.com) — " + "Anthropic's recommended embedding solution, which uses the same request format.");
2956
2907
  }
2957
2908
  };
2958
2909
  }
2959
- async function completePassThrough(options, fetcher, path, request) {
2910
+ async function streamPassThrough(options, fetcher, path, request, callbacks) {
2960
2911
  const input = resolveAnthropicInput(request);
2961
- const response = await fetcher(buildURL(options.baseURL, path), {
2912
+ const response = await sendAnthropicMessage(options, fetcher, path, request, {
2913
+ system: input.systemPrompt,
2914
+ messages: input.messages,
2915
+ stream: true
2916
+ });
2917
+ if (!response.ok) {
2918
+ const message = await response.text();
2919
+ throw new Error(`HTTP ${response.status}: ${message}`);
2920
+ }
2921
+ callbacks.onStart?.();
2922
+ let text = "";
2923
+ let usage;
2924
+ let finishReason;
2925
+ await consumeSSE(response, (data) => {
2926
+ if (data === "[DONE]") {
2927
+ return;
2928
+ }
2929
+ const json = safeJSONParse(data);
2930
+ if (!isRecord2(json)) {
2931
+ return;
2932
+ }
2933
+ const delta = pickAnthropicDelta(json);
2934
+ const chunkUsage = pickUsage2(json);
2935
+ const chunkFinishReason = pickFinishReason2(json);
2936
+ usage = preferLatestUsage(usage, chunkUsage);
2937
+ if (chunkFinishReason) {
2938
+ finishReason = chunkFinishReason;
2939
+ }
2940
+ if (delta) {
2941
+ text += delta;
2942
+ callbacks.onToken?.(delta);
2943
+ }
2944
+ if (delta || chunkUsage || chunkFinishReason) {
2945
+ callbacks.onChunk?.({
2946
+ textDelta: delta,
2947
+ raw: json,
2948
+ usage: chunkUsage,
2949
+ finishReason: chunkFinishReason
2950
+ });
2951
+ }
2952
+ });
2953
+ const out = { text, usage, finishReason };
2954
+ callbacks.onComplete?.(out);
2955
+ return out;
2956
+ }
2957
+ function sendAnthropicMessage(options, fetcher, path, request, body) {
2958
+ return fetcher(buildURL(options.baseURL, path), {
2962
2959
  method: "POST",
2963
2960
  headers: buildHeaders2(options),
2964
2961
  body: JSON.stringify(buildAnthropicRequestBody(options, request, {
2965
2962
  ...options.defaultBody,
2966
2963
  ...request.body,
2967
2964
  model: options.model,
2968
- system: input.systemPrompt,
2969
- messages: input.messages,
2970
2965
  temperature: request.temperature,
2971
- stream: false
2966
+ ...body
2972
2967
  })),
2973
2968
  signal: request.signal
2974
2969
  });
2970
+ }
2971
+ async function completePassThrough(options, fetcher, path, request) {
2972
+ const input = resolveAnthropicInput(request);
2973
+ const response = await sendAnthropicMessage(options, fetcher, path, request, {
2974
+ system: input.systemPrompt,
2975
+ messages: input.messages,
2976
+ stream: false
2977
+ });
2975
2978
  if (!response.ok) {
2976
2979
  const message = await response.text();
2977
2980
  throw new Error(`HTTP ${response.status}: ${message}`);
@@ -2999,24 +3002,16 @@ async function completeWithMCPToolLoop(options, fetcher, path, request) {
2999
3002
  let lastPayload;
3000
3003
  const toolCalls = [];
3001
3004
  const toolExecutions = [];
3005
+ const reasoningBlocks = [];
3002
3006
  for (let round = 1;round <= maxToolRounds + 1; round += 1) {
3003
3007
  const mcpToolset = await resolveMCPToolset(request.mcpClients);
3004
3008
  const tools = toAnthropicTools(toProviderFunctionTools(mcpToolset));
3005
- const response = await fetcher(buildURL(options.baseURL, path), {
3006
- method: "POST",
3007
- headers: buildHeaders2(options),
3008
- body: JSON.stringify(buildAnthropicRequestBody(options, request, {
3009
- ...options.defaultBody,
3010
- ...request.body,
3011
- model: options.model,
3012
- system: input.systemPrompt,
3013
- messages,
3014
- temperature: request.temperature,
3015
- tools,
3016
- tool_choice: toAnthropicToolChoice(request.toolChoice),
3017
- stream: false
3018
- })),
3019
- signal: request.signal
3009
+ const response = await sendAnthropicMessage(options, fetcher, path, request, {
3010
+ system: input.systemPrompt,
3011
+ messages,
3012
+ tools,
3013
+ tool_choice: toAnthropicToolChoice(request.toolChoice),
3014
+ stream: false
3020
3015
  });
3021
3016
  if (!response.ok) {
3022
3017
  const message = await response.text();
@@ -3028,9 +3023,12 @@ async function completeWithMCPToolLoop(options, fetcher, path, request) {
3028
3023
  finishReason = pickFinishReason2(payload);
3029
3024
  const content = Array.isArray(payload.content) ? payload.content : [];
3030
3025
  const calledTools = pickAnthropicToolCalls(payload).filter((call) => call.type === "function");
3026
+ pushReasoningBlock2(reasoningBlocks, round, extractAnthropicReasoning(payload));
3031
3027
  if (calledTools.length === 0) {
3032
3028
  return {
3033
3029
  text: extractAnthropicText(payload),
3030
+ reasoning: joinReasoningBlocks2(reasoningBlocks) || undefined,
3031
+ reasoningBlocks: reasoningBlocks.length > 0 ? reasoningBlocks : undefined,
3034
3032
  raw: payload,
3035
3033
  usage: aggregatedUsage,
3036
3034
  finishReason,
@@ -3066,6 +3064,8 @@ async function completeWithMCPToolLoop(options, fetcher, path, request) {
3066
3064
  }
3067
3065
  return {
3068
3066
  text: extractAnthropicText(lastPayload ?? {}),
3067
+ reasoning: joinReasoningBlocks2(reasoningBlocks) || undefined,
3068
+ reasoningBlocks: reasoningBlocks.length > 0 ? reasoningBlocks : undefined,
3069
3069
  raw: lastPayload,
3070
3070
  usage: aggregatedUsage,
3071
3071
  finishReason,
@@ -3082,31 +3082,24 @@ async function streamWithMCPToolLoop(options, fetcher, path, request, callbacks)
3082
3082
  let lastPayload;
3083
3083
  const toolCalls = [];
3084
3084
  const toolExecutions = [];
3085
+ const reasoningBlocks = [];
3085
3086
  callbacks.onStart?.();
3086
3087
  for (let round = 1;round <= maxToolRounds + 1; round += 1) {
3087
3088
  const mcpToolset = await resolveMCPToolset(request.mcpClients);
3088
3089
  const tools = toAnthropicTools(toProviderFunctionTools(mcpToolset));
3089
- const response = await fetcher(buildURL(options.baseURL, path), {
3090
- method: "POST",
3091
- headers: buildHeaders2(options),
3092
- body: JSON.stringify(buildAnthropicRequestBody(options, request, {
3093
- ...options.defaultBody,
3094
- ...request.body,
3095
- model: options.model,
3096
- system: input.systemPrompt,
3097
- messages,
3098
- temperature: request.temperature,
3099
- tools,
3100
- tool_choice: toAnthropicToolChoice(request.toolChoice),
3101
- stream: true
3102
- })),
3103
- signal: request.signal
3090
+ const response = await sendAnthropicMessage(options, fetcher, path, request, {
3091
+ system: input.systemPrompt,
3092
+ messages,
3093
+ tools,
3094
+ tool_choice: toAnthropicToolChoice(request.toolChoice),
3095
+ stream: true
3104
3096
  });
3105
3097
  if (!response.ok) {
3106
3098
  const message = await response.text();
3107
3099
  throw new Error(`HTTP ${response.status}: ${message}`);
3108
3100
  }
3109
3101
  let roundText = "";
3102
+ let roundReasoning = "";
3110
3103
  let roundUsage;
3111
3104
  let roundFinishReason;
3112
3105
  const streamedToolCalls = new Map;
@@ -3120,6 +3113,7 @@ async function streamWithMCPToolLoop(options, fetcher, path, request, callbacks)
3120
3113
  }
3121
3114
  lastPayload = json;
3122
3115
  const delta = pickAnthropicDelta(json);
3116
+ const reasoningDelta = pickAnthropicReasoningDelta(json);
3123
3117
  const chunkUsage = pickUsage2(json);
3124
3118
  const chunkFinishReason = pickFinishReason2(json);
3125
3119
  collectAnthropicStreamToolCalls(json, streamedToolCalls);
@@ -3131,9 +3125,14 @@ async function streamWithMCPToolLoop(options, fetcher, path, request, callbacks)
3131
3125
  roundText += delta;
3132
3126
  callbacks.onToken?.(delta);
3133
3127
  }
3134
- if (delta || chunkUsage || chunkFinishReason) {
3128
+ if (reasoningDelta) {
3129
+ roundReasoning += reasoningDelta;
3130
+ }
3131
+ if (delta || reasoningDelta || chunkUsage || chunkFinishReason) {
3135
3132
  const chunk = {
3136
3133
  textDelta: delta,
3134
+ reasoningDelta: reasoningDelta || undefined,
3135
+ turnIndex: round,
3137
3136
  raw: json,
3138
3137
  usage: chunkUsage,
3139
3138
  finishReason: chunkFinishReason
@@ -3146,21 +3145,41 @@ async function streamWithMCPToolLoop(options, fetcher, path, request, callbacks)
3146
3145
  finishReason = roundFinishReason;
3147
3146
  }
3148
3147
  const calledTools = buildAnthropicStreamToolCalls(streamedToolCalls);
3148
+ pushReasoningBlock2(reasoningBlocks, round, roundReasoning);
3149
+ request.onTurnTransition?.({
3150
+ turnIndex: round,
3151
+ kind: "reasoningComplete",
3152
+ reasoningText: roundReasoning
3153
+ });
3149
3154
  if (calledTools.length === 0) {
3150
3155
  const out2 = {
3151
3156
  text: roundText,
3157
+ reasoning: joinReasoningBlocks2(reasoningBlocks) || undefined,
3158
+ reasoningBlocks: reasoningBlocks.length > 0 ? reasoningBlocks : undefined,
3152
3159
  raw: lastPayload,
3153
3160
  usage: aggregatedUsage,
3154
3161
  finishReason,
3155
3162
  toolCalls: toolCalls.length > 0 ? toolCalls : undefined,
3156
3163
  toolExecutions: toolExecutions.length > 0 ? toolExecutions : undefined
3157
3164
  };
3165
+ request.onTurnTransition?.({ turnIndex: round, kind: "streamEnd" });
3158
3166
  callbacks.onComplete?.(out2);
3159
3167
  return out2;
3160
3168
  }
3161
3169
  if (round > maxToolRounds) {
3162
3170
  throw new Error(`Tool call loop exceeded maxToolRounds (${maxToolRounds}).`);
3163
3171
  }
3172
+ request.onTurnTransition?.({
3173
+ turnIndex: round,
3174
+ kind: "toolCallsEmit",
3175
+ toolCalls: calledTools
3176
+ });
3177
+ callbacks.onChunk?.({
3178
+ textDelta: "",
3179
+ turnIndex: round,
3180
+ toolCalls: calledTools,
3181
+ finishReason: roundFinishReason
3182
+ });
3164
3183
  const toolResultContent = [];
3165
3184
  const outputs = await executeMCPToolCalls(calledTools, mcpToolset, {
3166
3185
  round,
@@ -3170,6 +3189,7 @@ async function streamWithMCPToolLoop(options, fetcher, path, request, callbacks)
3170
3189
  });
3171
3190
  toolCalls.push(...outputs.map((entry) => entry.call));
3172
3191
  toolExecutions.push(...outputs.map((entry) => entry.execution));
3192
+ request.onTurnTransition?.({ turnIndex: round, kind: "toolResultsReceived" });
3173
3193
  for (const entry of outputs) {
3174
3194
  toolResultContent.push({
3175
3195
  type: "tool_result",
@@ -3186,12 +3206,15 @@ async function streamWithMCPToolLoop(options, fetcher, path, request, callbacks)
3186
3206
  }
3187
3207
  const out = {
3188
3208
  text: "",
3209
+ reasoning: joinReasoningBlocks2(reasoningBlocks) || undefined,
3210
+ reasoningBlocks: reasoningBlocks.length > 0 ? reasoningBlocks : undefined,
3189
3211
  raw: lastPayload,
3190
3212
  usage: aggregatedUsage,
3191
3213
  finishReason,
3192
3214
  toolCalls: toolCalls.length > 0 ? toolCalls : undefined,
3193
3215
  toolExecutions: toolExecutions.length > 0 ? toolExecutions : undefined
3194
3216
  };
3217
+ request.onTurnTransition?.({ turnIndex: maxToolRounds + 1, kind: "streamEnd" });
3195
3218
  callbacks.onComplete?.(out);
3196
3219
  return out;
3197
3220
  }
@@ -3312,10 +3335,26 @@ function extractAnthropicText(payload) {
3312
3335
  return typeof text === "string" ? text : "";
3313
3336
  }).join("");
3314
3337
  }
3315
- function pickAnthropicToolCalls(payload) {
3338
+ function extractAnthropicReasoning(payload) {
3316
3339
  const content = payload.content;
3317
3340
  if (!Array.isArray(content)) {
3318
- return [];
3341
+ return "";
3342
+ }
3343
+ return content.map((part) => {
3344
+ if (!isRecord2(part)) {
3345
+ return "";
3346
+ }
3347
+ const type = pickString(part.type) ?? "";
3348
+ if (type !== "thinking" && type !== "reasoning") {
3349
+ return "";
3350
+ }
3351
+ return pickString(part.thinking) ?? pickString(part.text) ?? pickString(part.reasoning) ?? "";
3352
+ }).join("");
3353
+ }
3354
+ function pickAnthropicToolCalls(payload) {
3355
+ const content = payload.content;
3356
+ if (!Array.isArray(content)) {
3357
+ return [];
3319
3358
  }
3320
3359
  const calls = [];
3321
3360
  for (const part of content) {
@@ -3342,6 +3381,35 @@ function pickAnthropicDelta(payload) {
3342
3381
  }
3343
3382
  return "";
3344
3383
  }
3384
+ function pickAnthropicReasoningDelta(payload) {
3385
+ const deltaObject = payload.delta;
3386
+ if (isRecord2(deltaObject)) {
3387
+ const type = pickString(deltaObject.type) ?? "";
3388
+ if (type === "thinking_delta" || type === "reasoning_delta") {
3389
+ return pickString(deltaObject.thinking) ?? pickString(deltaObject.text) ?? "";
3390
+ }
3391
+ }
3392
+ const contentBlock = payload.content_block;
3393
+ if (isRecord2(contentBlock)) {
3394
+ const type = pickString(contentBlock.type) ?? "";
3395
+ if (type === "thinking" || type === "reasoning") {
3396
+ return pickString(contentBlock.thinking) ?? pickString(contentBlock.text) ?? "";
3397
+ }
3398
+ }
3399
+ return "";
3400
+ }
3401
+ function pushReasoningBlock2(blocks, turnIndex, text) {
3402
+ const clean = text?.replace(/<\/?think\s*>/gi, "").trim();
3403
+ if (!clean) {
3404
+ return;
3405
+ }
3406
+ blocks.push({ turnIndex, text: clean });
3407
+ }
3408
+ function joinReasoningBlocks2(blocks) {
3409
+ return blocks.map((block) => block.text).filter(Boolean).join(`
3410
+
3411
+ `);
3412
+ }
3345
3413
  function collectAnthropicStreamToolCalls(payload, state) {
3346
3414
  const eventType = pickString(payload.type);
3347
3415
  if (!eventType) {
@@ -3568,39 +3636,10 @@ function buildProviderOptions(config) {
3568
3636
  return {
3569
3637
  model: config.model,
3570
3638
  ...transport,
3571
- ...config.options ?? {}
3639
+ ...config.options
3572
3640
  };
3573
3641
  }
3574
3642
 
3575
- // src/utils/debug-colors.ts
3576
- var ANSI = {
3577
- reset: "\x1B[0m",
3578
- bold: "\x1B[1m",
3579
- cyan: "\x1B[36m",
3580
- yellow: "\x1B[33m",
3581
- green: "\x1B[32m",
3582
- red: "\x1B[31m",
3583
- dim: "\x1B[2m"
3584
- };
3585
- function color(config, text, tone) {
3586
- if (!config.colors) {
3587
- return text;
3588
- }
3589
- return `${ANSI[tone]}${text}${ANSI.reset}`;
3590
- }
3591
- function dim(config, text) {
3592
- if (!config.colors) {
3593
- return text;
3594
- }
3595
- return `${ANSI.dim}${text}${ANSI.reset}`;
3596
- }
3597
- function title(config, text) {
3598
- if (!config.colors) {
3599
- return text;
3600
- }
3601
- return `${ANSI.bold}${text}${ANSI.reset}`;
3602
- }
3603
-
3604
3643
  // src/outdent.ts
3605
3644
  var DEFAULT_OPTIONS = {
3606
3645
  trimLeadingNewline: true,
@@ -3738,131 +3777,213 @@ function createOutdent(options = {}) {
3738
3777
  return outdent;
3739
3778
  }
3740
3779
 
3741
- // src/generate-shared.ts
3742
- var sharedOutdent = createOutdent({
3743
- trimLeadingNewline: true,
3744
- trimTrailingNewline: true,
3745
- newline: `
3746
- `
3747
- });
3780
+ // src/generate-tool-timeout.ts
3781
+ function withToolTimeout(client, toolTimeoutMs) {
3782
+ return {
3783
+ id: client.id,
3784
+ listTools: client.listTools.bind(client),
3785
+ close: client.close?.bind(client),
3786
+ async callTool(params) {
3787
+ let timeoutId;
3788
+ const timeoutPromise = new Promise((_, reject) => {
3789
+ timeoutId = setTimeout(() => reject(new Error(`Tool call timed out after ${toolTimeoutMs}ms`)), toolTimeoutMs);
3790
+ });
3791
+ try {
3792
+ return await Promise.race([client.callTool(params), timeoutPromise]);
3793
+ } finally {
3794
+ clearTimeout(timeoutId);
3795
+ }
3796
+ }
3797
+ };
3798
+ }
3799
+ function applyToolTimeout(clients, toolTimeoutMs) {
3800
+ return clients.map((client) => withToolTimeout(client, toolTimeoutMs));
3801
+ }
3802
+ // src/generate-output.ts
3748
3803
  var RE_THINK_TAGS = /<\/?think\s*>/gi;
3749
- function resolvePrompt(prompt, context) {
3750
- const resolved = typeof prompt === "function" ? prompt(context) : prompt;
3751
- return normalizePromptValue(resolved, context);
3804
+ function normalizeModelOutput(text, dedicatedReasoning, reasoningBlocks) {
3805
+ const sanitized = sanitizeThink(text);
3806
+ const visibleText = stripThinkBlocks(text, sanitized.thinkBlocks);
3807
+ const reasoning = joinReasoningSegments([
3808
+ dedicatedReasoning,
3809
+ ...sanitized.thinkBlocks.map((block) => block.content)
3810
+ ]);
3811
+ return {
3812
+ text: visibleText,
3813
+ reasoning,
3814
+ reasoningBlocks: normalizeReasoningBlocks(reasoningBlocks),
3815
+ thinkBlocks: sanitized.thinkBlocks,
3816
+ parseSource: composeParseSource(visibleText, reasoning)
3817
+ };
3752
3818
  }
3753
- function normalizePromptValue(value, _context) {
3754
- if (typeof value === "string") {
3755
- return {
3756
- prompt: value
3757
- };
3758
- }
3759
- if (isPromptResolver(value)) {
3760
- return normalizePromptPayload(value.resolvePrompt(_context));
3819
+ function normalizeReasoningBlocks(blocks) {
3820
+ if (!Array.isArray(blocks)) {
3821
+ return;
3761
3822
  }
3762
- return normalizePromptPayload(value);
3823
+ const normalized = blocks.map((block) => ({
3824
+ turnIndex: block.turnIndex,
3825
+ text: block.text.replace(RE_THINK_TAGS, "").trim()
3826
+ })).filter((block) => Number.isFinite(block.turnIndex) && block.text.length > 0);
3827
+ return normalized.length > 0 ? normalized : undefined;
3763
3828
  }
3764
- function normalizePromptPayload(value) {
3765
- const prompt = typeof value.prompt === "string" ? value.prompt : undefined;
3766
- const messages = Array.isArray(value.messages) ? value.messages.filter(isLLMMessage) : undefined;
3767
- if ((!prompt || prompt.trim().length === 0) && (!messages || messages.length === 0)) {
3768
- throw new Error("Structured prompt payload must include a non-empty prompt or messages.");
3829
+ function appendReasoningBlock(blocks, transition) {
3830
+ const text = transition.reasoningText?.replace(RE_THINK_TAGS, "").trim();
3831
+ if (!text) {
3832
+ return blocks;
3769
3833
  }
3770
- return {
3771
- prompt,
3772
- systemPrompt: typeof value.systemPrompt === "string" ? value.systemPrompt : undefined,
3773
- messages: messages && messages.length > 0 ? messages.map((message) => ({ ...message })) : undefined
3774
- };
3834
+ const next = [...blocks ?? [], { turnIndex: transition.turnIndex, text }];
3835
+ return normalizeReasoningBlocks(next);
3775
3836
  }
3776
- function applyPromptOutdent(payload, enabled) {
3777
- if (!enabled) {
3778
- return payload;
3837
+ function composeParseSource(text, reasoning) {
3838
+ if (typeof reasoning !== "string" || reasoning.length === 0) {
3839
+ return text;
3779
3840
  }
3780
- return {
3781
- prompt: typeof payload.prompt === "string" ? sharedOutdent.string(payload.prompt) : undefined,
3782
- systemPrompt: applyOutdentToOptionalPrompt(payload.systemPrompt, enabled),
3783
- messages: payload.messages?.map((message) => ({
3784
- ...message,
3785
- content: typeof message.content === "string" ? sharedOutdent.string(message.content) : message.content
3786
- }))
3787
- };
3841
+ const sanitized = reasoning.replace(RE_THINK_TAGS, "");
3842
+ if (sanitized.length === 0) {
3843
+ return text;
3844
+ }
3845
+ return `<think>${sanitized}</think>${text}`;
3788
3846
  }
3789
- function applyOutdentToOptionalPrompt(value, enabled) {
3790
- if (!enabled || typeof value !== "string") {
3791
- return value;
3847
+ function aggregateUsage(attempts) {
3848
+ let usage;
3849
+ for (const attempt of attempts) {
3850
+ usage = mergeUsage2(usage, attempt.usage);
3792
3851
  }
3793
- return sharedOutdent.string(value);
3852
+ return usage;
3794
3853
  }
3795
- function mergeSystemPrompts(primary, secondary) {
3796
- const prompts = [primary, secondary].map((value) => value?.trim()).filter((value) => Boolean(value));
3797
- if (prompts.length === 0) {
3854
+ function mergeUsage2(base, next) {
3855
+ if (!base && !next) {
3798
3856
  return;
3799
3857
  }
3800
- return prompts.join(`
3858
+ return {
3859
+ inputTokens: (base?.inputTokens ?? 0) + (next?.inputTokens ?? 0),
3860
+ outputTokens: (base?.outputTokens ?? 0) + (next?.outputTokens ?? 0),
3861
+ totalTokens: (base?.totalTokens ?? 0) + (next?.totalTokens ?? 0),
3862
+ cost: (base?.cost ?? 0) + (next?.cost ?? 0)
3863
+ };
3864
+ }
3865
+ function joinReasoningSegments(parts) {
3866
+ return parts.map((value) => value?.trim()).filter((value) => Boolean(value)).join(`
3801
3867
 
3802
3868
  `);
3803
3869
  }
3804
- function normalizeStreamConfig(option) {
3805
- if (typeof option === "boolean") {
3806
- return {
3807
- enabled: option
3808
- };
3870
+ function stripThinkBlocks(text, thinkBlocks) {
3871
+ if (thinkBlocks.length === 0) {
3872
+ return text;
3809
3873
  }
3810
- if (!option) {
3811
- return {
3812
- enabled: false
3813
- };
3874
+ let output = "";
3875
+ let cursor = 0;
3876
+ for (const block of thinkBlocks) {
3877
+ output += text.slice(cursor, block.start);
3878
+ cursor = block.end;
3814
3879
  }
3815
- return {
3816
- enabled: option.enabled ?? true,
3817
- onData: option.onData,
3818
- to: option.to
3819
- };
3880
+ output += text.slice(cursor);
3881
+ return output;
3820
3882
  }
3821
- function normalizeDebugConfig(option) {
3822
- if (typeof option === "boolean") {
3823
- return {
3824
- enabled: option,
3825
- colors: true,
3826
- verbose: false,
3827
- logger: (line) => console.log(line)
3828
- };
3883
+ function toStreamDataFingerprint(value) {
3884
+ try {
3885
+ return JSON.stringify(value);
3886
+ } catch {
3887
+ return "__unserializable__";
3829
3888
  }
3830
- if (!option) {
3831
- return {
3832
- enabled: false,
3833
- colors: true,
3834
- verbose: false,
3835
- logger: (line) => console.log(line)
3836
- };
3889
+ }
3890
+
3891
+ // src/utils/debug-colors.ts
3892
+ var ANSI = {
3893
+ reset: "\x1B[0m",
3894
+ bold: "\x1B[1m",
3895
+ cyan: "\x1B[36m",
3896
+ yellow: "\x1B[33m",
3897
+ green: "\x1B[32m",
3898
+ red: "\x1B[31m",
3899
+ dim: "\x1B[2m"
3900
+ };
3901
+ function color(config, text, tone) {
3902
+ if (!config.colors) {
3903
+ return text;
3837
3904
  }
3838
- return {
3839
- enabled: option.enabled ?? true,
3840
- colors: option.colors ?? true,
3841
- verbose: option.verbose ?? false,
3842
- logger: option.logger ?? ((line) => console.log(line))
3843
- };
3905
+ return `${ANSI[tone]}${text}${ANSI.reset}`;
3844
3906
  }
3845
- function withToolTimeout(client, toolTimeoutMs) {
3846
- return {
3847
- id: client.id,
3848
- listTools: client.listTools.bind(client),
3849
- close: client.close?.bind(client),
3850
- async callTool(params) {
3851
- let timeoutId;
3852
- const timeoutPromise = new Promise((_, reject) => {
3853
- timeoutId = setTimeout(() => reject(new Error(`Tool call timed out after ${toolTimeoutMs}ms`)), toolTimeoutMs);
3854
- });
3855
- try {
3856
- return await Promise.race([client.callTool(params), timeoutPromise]);
3857
- } finally {
3858
- clearTimeout(timeoutId);
3859
- }
3860
- }
3861
- };
3907
+ function dim(config, text) {
3908
+ if (!config.colors) {
3909
+ return text;
3910
+ }
3911
+ return `${ANSI.dim}${text}${ANSI.reset}`;
3862
3912
  }
3863
- function applyToolTimeout(clients, toolTimeoutMs) {
3864
- return clients.map((client) => withToolTimeout(client, toolTimeoutMs));
3913
+ function title(config, text) {
3914
+ if (!config.colors) {
3915
+ return text;
3916
+ }
3917
+ return `${ANSI.bold}${text}${ANSI.reset}`;
3918
+ }
3919
+
3920
+ // src/generate-debug.ts
3921
+ function emitDebugRequest(config, input) {
3922
+ const requestBody = input.requestPayload.body !== undefined ? JSON.stringify(input.requestPayload.body, null, 2) : "(none)";
3923
+ const requestMessages = input.requestPayload.messages !== undefined ? JSON.stringify(input.requestPayload.messages, null, 2) : "(none)";
3924
+ const lines = [
3925
+ color(config, title(config, [
3926
+ `[${input.label}][request]`,
3927
+ `attempt=${input.attempt}`,
3928
+ `selfHealEnabled=${input.selfHealEnabled}`,
3929
+ `selfHealAttempt=${input.selfHealAttempt}`
3930
+ ].join(" ")), "cyan"),
3931
+ dim(config, [
3932
+ `provider=${input.provider ?? "unknown"}`,
3933
+ `model=${input.model ?? "unknown"}`,
3934
+ `stream=${input.stream}`
3935
+ ].join(" ")),
3936
+ color(config, "prompt:", "yellow"),
3937
+ input.requestPayload.prompt ?? "(none)",
3938
+ color(config, "messages:", "yellow"),
3939
+ requestMessages,
3940
+ color(config, "systemPrompt:", "yellow"),
3941
+ input.requestPayload.systemPrompt ?? "(none)",
3942
+ color(config, "request.body:", "yellow"),
3943
+ requestBody
3944
+ ];
3945
+ emitDebug(config, lines.join(`
3946
+ `));
3947
+ }
3948
+ function emitDebugResponse(config, input) {
3949
+ const text = input.text.length > 0 ? input.text : "(none)";
3950
+ const reasoning = input.reasoning.length > 0 ? input.reasoning : "(none)";
3951
+ const metadata = [
3952
+ `via=${input.via}`,
3953
+ `textChars=${input.text.length}`,
3954
+ `reasoningChars=${input.reasoning.length}`
3955
+ ];
3956
+ if (config.verbose) {
3957
+ metadata.push(`parseSourceChars=${input.parseSource.length}`);
3958
+ }
3959
+ metadata.push(`finishReason=${input.finishReason ?? "unknown"}`, `usage=${JSON.stringify(input.usage ?? {})}`);
3960
+ const lines = [
3961
+ color(config, title(config, [
3962
+ `[${input.label}][response]`,
3963
+ `attempt=${input.attempt}`,
3964
+ `selfHealEnabled=${input.selfHealEnabled}`,
3965
+ `selfHealAttempt=${input.selfHealAttempt}`
3966
+ ].join(" ")), "green"),
3967
+ dim(config, metadata.join(" ")),
3968
+ color(config, "text:", "yellow"),
3969
+ text,
3970
+ color(config, "reasoning:", "yellow"),
3971
+ reasoning
3972
+ ];
3973
+ if (config.verbose) {
3974
+ lines.push(color(config, "parseSource:", "yellow"), input.parseSource);
3975
+ }
3976
+ emitDebug(config, lines.join(`
3977
+ `));
3865
3978
  }
3979
+ function emitDebug(config, message) {
3980
+ if (!config.enabled) {
3981
+ return;
3982
+ }
3983
+ config.logger(message);
3984
+ }
3985
+
3986
+ // src/generate-model-call.ts
3866
3987
  async function callModel(adapter, options) {
3867
3988
  const requestSignal = options.request?.signal ?? (options.timeout?.request !== undefined ? AbortSignal.timeout(options.timeout.request) : undefined);
3868
3989
  const requestPayload = {
@@ -3882,6 +4003,7 @@ async function callModel(adapter, options) {
3882
4003
  transformToolCallParams: options.request?.transformToolCallParams,
3883
4004
  unknownToolError: options.request?.unknownToolError,
3884
4005
  toolDebug: options.request?.toolDebug,
4006
+ onTurnTransition: options.stream.onTurnTransition,
3885
4007
  body: options.request?.body,
3886
4008
  signal: requestSignal
3887
4009
  };
@@ -3909,13 +4031,21 @@ async function callModel(adapter, options) {
3909
4031
  let latestFinishReason;
3910
4032
  let streamedProviderText = "";
3911
4033
  let streamedDedicatedReasoning = "";
4034
+ let currentTurnIndex;
4035
+ let currentToolCalls;
4036
+ let streamedReasoningBlocks;
3912
4037
  let lastSnapshotFingerprint;
3913
4038
  let previousSnapshotText = "";
3914
4039
  let previousSnapshotReasoning = "";
3915
4040
  const emitStreamingData = (done, usage2, finishReason2) => {
3916
- const normalized2 = normalizeModelOutput(streamedProviderText, streamedDedicatedReasoning);
4041
+ const normalized2 = normalizeModelOutput(streamedProviderText, streamedDedicatedReasoning, streamedReasoningBlocks);
3917
4042
  const snapshot = options.buildSnapshot(normalized2);
3918
- const fingerprint = toStreamDataFingerprint(snapshot);
4043
+ const fingerprint = toStreamDataFingerprint({
4044
+ snapshot,
4045
+ done,
4046
+ turnIndex: currentTurnIndex,
4047
+ toolCalls: currentToolCalls
4048
+ });
3919
4049
  if (!done && fingerprint === lastSnapshotFingerprint) {
3920
4050
  return;
3921
4051
  }
@@ -3931,7 +4061,9 @@ async function callModel(adapter, options) {
3931
4061
  snapshot,
3932
4062
  done,
3933
4063
  usage: usage2,
3934
- finishReason: finishReason2
4064
+ finishReason: finishReason2,
4065
+ turnIndex: currentTurnIndex,
4066
+ toolCalls: currentToolCalls
3935
4067
  });
3936
4068
  if (options.stream.to === "stdout" && delta.text) {
3937
4069
  process.stdout.write(delta.text);
@@ -3966,8 +4098,21 @@ async function callModel(adapter, options) {
3966
4098
  streamedDedicatedReasoning += delta;
3967
4099
  emitStreamingData(false);
3968
4100
  };
3969
- const response2 = await adapter.stream(requestPayload, {
4101
+ const streamRequestPayload = {
4102
+ ...requestPayload,
4103
+ onTurnTransition: (transition) => {
4104
+ if (transition.kind === "reasoningComplete") {
4105
+ streamedReasoningBlocks = appendReasoningBlock(streamedReasoningBlocks, transition);
4106
+ }
4107
+ options.stream.onTurnTransition?.(transition);
4108
+ }
4109
+ };
4110
+ const response2 = await adapter.stream(streamRequestPayload, {
3970
4111
  onChunk: (chunk) => {
4112
+ if (chunk.turnIndex !== undefined) {
4113
+ currentTurnIndex = chunk.turnIndex;
4114
+ }
4115
+ currentToolCalls = chunk.toolCalls;
3971
4116
  if (chunk.textDelta) {
3972
4117
  handleTextDelta(chunk.textDelta);
3973
4118
  }
@@ -3980,11 +4125,15 @@ async function callModel(adapter, options) {
3980
4125
  if (chunk.finishReason) {
3981
4126
  latestFinishReason = chunk.finishReason;
3982
4127
  }
4128
+ if (!chunk.textDelta && !chunk.reasoningDelta && (chunk.turnIndex !== undefined || chunk.toolCalls)) {
4129
+ emitStreamingData(false, chunk.usage, chunk.finishReason);
4130
+ }
3983
4131
  }
3984
4132
  });
3985
4133
  streamedProviderText = typeof response2.text === "string" ? response2.text : streamedProviderText;
3986
4134
  streamedDedicatedReasoning = typeof response2.reasoning === "string" ? response2.reasoning : streamedDedicatedReasoning;
3987
- const finalNormalized = normalizeModelOutput(streamedProviderText, streamedDedicatedReasoning);
4135
+ streamedReasoningBlocks = response2.reasoningBlocks ?? streamedReasoningBlocks;
4136
+ const finalNormalized = normalizeModelOutput(streamedProviderText, streamedDedicatedReasoning, streamedReasoningBlocks);
3988
4137
  const usage = preferLatestUsage(latestUsage, response2.usage);
3989
4138
  const finishReason = response2.finishReason ?? latestFinishReason;
3990
4139
  emitStreamingData(true, usage, finishReason);
@@ -4016,11 +4165,12 @@ async function callModel(adapter, options) {
4016
4165
  parseSource: finalNormalized.parseSource,
4017
4166
  via: "stream",
4018
4167
  usage,
4019
- finishReason
4168
+ finishReason,
4169
+ reasoningBlocks: finalNormalized.reasoningBlocks
4020
4170
  };
4021
4171
  }
4022
4172
  const response = await adapter.complete(requestPayload);
4023
- const normalized = normalizeModelOutput(response.text, response.reasoning);
4173
+ const normalized = normalizeModelOutput(response.text, response.reasoning, response.reasoningBlocks);
4024
4174
  options.observe?.(options.buildEvent({
4025
4175
  stage: "llm.response",
4026
4176
  message: "Completion response received.",
@@ -4049,51 +4199,119 @@ async function callModel(adapter, options) {
4049
4199
  parseSource: normalized.parseSource,
4050
4200
  via: "complete",
4051
4201
  usage: response.usage,
4052
- finishReason: response.finishReason
4202
+ finishReason: response.finishReason,
4203
+ reasoningBlocks: normalized.reasoningBlocks
4053
4204
  };
4054
4205
  }
4055
- function normalizeModelOutput(text, dedicatedReasoning) {
4056
- const sanitized = sanitizeThink(text);
4057
- const visibleText = stripThinkBlocks(text, sanitized.thinkBlocks);
4058
- const reasoning = joinReasoningSegments([
4059
- dedicatedReasoning,
4060
- ...sanitized.thinkBlocks.map((block) => block.content)
4061
- ]);
4206
+
4207
+ // src/generate-shared.ts
4208
+ var sharedOutdent = createOutdent({
4209
+ trimLeadingNewline: true,
4210
+ trimTrailingNewline: true,
4211
+ newline: `
4212
+ `
4213
+ });
4214
+ function resolvePrompt(prompt, context) {
4215
+ const resolved = typeof prompt === "function" ? prompt(context) : prompt;
4216
+ return normalizePromptValue(resolved, context);
4217
+ }
4218
+ function normalizePromptValue(value, _context) {
4219
+ if (typeof value === "string") {
4220
+ return {
4221
+ prompt: value
4222
+ };
4223
+ }
4224
+ if (isPromptResolver(value)) {
4225
+ return normalizePromptPayload(value.resolvePrompt(_context));
4226
+ }
4227
+ return normalizePromptPayload(value);
4228
+ }
4229
+ function normalizePromptPayload(value) {
4230
+ const prompt = typeof value.prompt === "string" ? value.prompt : undefined;
4231
+ const messages = Array.isArray(value.messages) ? value.messages.filter(isLLMMessage) : undefined;
4232
+ if ((!prompt || prompt.trim().length === 0) && (!messages || messages.length === 0)) {
4233
+ throw new Error("Structured prompt payload must include a non-empty prompt or messages.");
4234
+ }
4062
4235
  return {
4063
- text: visibleText,
4064
- reasoning,
4065
- thinkBlocks: sanitized.thinkBlocks,
4066
- parseSource: composeParseSource(visibleText, reasoning)
4236
+ prompt,
4237
+ systemPrompt: typeof value.systemPrompt === "string" ? value.systemPrompt : undefined,
4238
+ messages: messages && messages.length > 0 ? messages.map((message) => ({ ...message })) : undefined
4067
4239
  };
4068
4240
  }
4069
- function composeParseSource(text, reasoning) {
4070
- if (typeof reasoning !== "string" || reasoning.length === 0) {
4071
- return text;
4241
+ function applyPromptOutdent(payload, enabled) {
4242
+ if (!enabled) {
4243
+ return payload;
4072
4244
  }
4073
- const sanitized = reasoning.replace(RE_THINK_TAGS, "");
4074
- if (sanitized.length === 0) {
4075
- return text;
4245
+ return {
4246
+ prompt: typeof payload.prompt === "string" ? sharedOutdent.string(payload.prompt) : undefined,
4247
+ systemPrompt: applyOutdentToOptionalPrompt(payload.systemPrompt, enabled),
4248
+ messages: payload.messages?.map((message) => ({
4249
+ ...message,
4250
+ content: typeof message.content === "string" ? sharedOutdent.string(message.content) : message.content
4251
+ }))
4252
+ };
4253
+ }
4254
+ function applyOutdentToOptionalPrompt(value, enabled) {
4255
+ if (!enabled || typeof value !== "string") {
4256
+ return value;
4076
4257
  }
4077
- return `<think>${sanitized}</think>${text}`;
4258
+ return sharedOutdent.string(value);
4078
4259
  }
4079
- function aggregateUsage(attempts) {
4080
- let usage;
4081
- for (const attempt of attempts) {
4082
- usage = mergeUsage2(usage, attempt.usage);
4260
+ function mergeSystemPrompts(primary, secondary) {
4261
+ const prompts = [primary, secondary].map((value) => value?.trim()).filter((value) => Boolean(value));
4262
+ if (prompts.length === 0) {
4263
+ return;
4264
+ }
4265
+ return prompts.join(`
4266
+
4267
+ `);
4268
+ }
4269
+ function normalizeStreamConfig(option) {
4270
+ if (typeof option === "boolean") {
4271
+ return {
4272
+ enabled: option
4273
+ };
4274
+ }
4275
+ if (!option) {
4276
+ return {
4277
+ enabled: false
4278
+ };
4083
4279
  }
4084
- return usage;
4280
+ return {
4281
+ enabled: option.enabled ?? true,
4282
+ onData: option.onData,
4283
+ onTurnTransition: option.onTurnTransition,
4284
+ to: option.to
4285
+ };
4085
4286
  }
4086
- function mergeUsage2(base, next) {
4087
- if (!base && !next) {
4088
- return;
4287
+ function normalizeDebugConfig(option) {
4288
+ if (typeof option === "boolean") {
4289
+ return {
4290
+ enabled: option,
4291
+ colors: true,
4292
+ verbose: false,
4293
+ logger: defaultDebugLogger
4294
+ };
4295
+ }
4296
+ if (!option) {
4297
+ return {
4298
+ enabled: false,
4299
+ colors: true,
4300
+ verbose: false,
4301
+ logger: defaultDebugLogger
4302
+ };
4089
4303
  }
4090
4304
  return {
4091
- inputTokens: (base?.inputTokens ?? 0) + (next?.inputTokens ?? 0),
4092
- outputTokens: (base?.outputTokens ?? 0) + (next?.outputTokens ?? 0),
4093
- totalTokens: (base?.totalTokens ?? 0) + (next?.totalTokens ?? 0),
4094
- cost: (base?.cost ?? 0) + (next?.cost ?? 0)
4305
+ enabled: option.enabled ?? true,
4306
+ colors: option.colors ?? true,
4307
+ verbose: option.verbose ?? false,
4308
+ logger: option.logger ?? defaultDebugLogger
4095
4309
  };
4096
4310
  }
4311
+ function defaultDebugLogger(line) {
4312
+ const { log } = globalThis.console;
4313
+ log(line);
4314
+ }
4097
4315
  function isPromptResolver(value) {
4098
4316
  return typeof value === "object" && value !== null && "resolvePrompt" in value && typeof value.resolvePrompt === "function";
4099
4317
  }
@@ -4107,95 +4325,6 @@ function isLLMMessage(value) {
4107
4325
  }
4108
4326
  return "content" in candidate;
4109
4327
  }
4110
- function joinReasoningSegments(parts) {
4111
- return parts.map((value) => value?.trim()).filter((value) => Boolean(value)).join(`
4112
-
4113
- `);
4114
- }
4115
- function stripThinkBlocks(text, thinkBlocks) {
4116
- if (thinkBlocks.length === 0) {
4117
- return text;
4118
- }
4119
- let output = "";
4120
- let cursor = 0;
4121
- for (const block of thinkBlocks) {
4122
- output += text.slice(cursor, block.start);
4123
- cursor = block.end;
4124
- }
4125
- output += text.slice(cursor);
4126
- return output;
4127
- }
4128
- function toStreamDataFingerprint(value) {
4129
- try {
4130
- return JSON.stringify(value);
4131
- } catch {
4132
- return "__unserializable__";
4133
- }
4134
- }
4135
- function emitDebugRequest(config, input) {
4136
- const requestBody = input.requestPayload.body !== undefined ? JSON.stringify(input.requestPayload.body, null, 2) : "(none)";
4137
- const requestMessages = input.requestPayload.messages !== undefined ? JSON.stringify(input.requestPayload.messages, null, 2) : "(none)";
4138
- const lines = [
4139
- color(config, title(config, [
4140
- `[${input.label}][request]`,
4141
- `attempt=${input.attempt}`,
4142
- `selfHealEnabled=${input.selfHealEnabled}`,
4143
- `selfHealAttempt=${input.selfHealAttempt}`
4144
- ].join(" ")), "cyan"),
4145
- dim(config, [
4146
- `provider=${input.provider ?? "unknown"}`,
4147
- `model=${input.model ?? "unknown"}`,
4148
- `stream=${input.stream}`
4149
- ].join(" ")),
4150
- color(config, "prompt:", "yellow"),
4151
- input.requestPayload.prompt ?? "(none)",
4152
- color(config, "messages:", "yellow"),
4153
- requestMessages,
4154
- color(config, "systemPrompt:", "yellow"),
4155
- input.requestPayload.systemPrompt ?? "(none)",
4156
- color(config, "request.body:", "yellow"),
4157
- requestBody
4158
- ];
4159
- emitDebug(config, lines.join(`
4160
- `));
4161
- }
4162
- function emitDebugResponse(config, input) {
4163
- const text = input.text.length > 0 ? input.text : "(none)";
4164
- const reasoning = input.reasoning.length > 0 ? input.reasoning : "(none)";
4165
- const metadata = [
4166
- `via=${input.via}`,
4167
- `textChars=${input.text.length}`,
4168
- `reasoningChars=${input.reasoning.length}`
4169
- ];
4170
- if (config.verbose) {
4171
- metadata.push(`parseSourceChars=${input.parseSource.length}`);
4172
- }
4173
- metadata.push(`finishReason=${input.finishReason ?? "unknown"}`, `usage=${JSON.stringify(input.usage ?? {})}`);
4174
- const lines = [
4175
- color(config, title(config, [
4176
- `[${input.label}][response]`,
4177
- `attempt=${input.attempt}`,
4178
- `selfHealEnabled=${input.selfHealEnabled}`,
4179
- `selfHealAttempt=${input.selfHealAttempt}`
4180
- ].join(" ")), "green"),
4181
- dim(config, metadata.join(" ")),
4182
- color(config, "text:", "yellow"),
4183
- text,
4184
- color(config, "reasoning:", "yellow"),
4185
- reasoning
4186
- ];
4187
- if (config.verbose) {
4188
- lines.push(color(config, "parseSource:", "yellow"), input.parseSource);
4189
- }
4190
- emitDebug(config, lines.join(`
4191
- `));
4192
- }
4193
- function emitDebug(config, message) {
4194
- if (!config.enabled) {
4195
- return;
4196
- }
4197
- config.logger(message);
4198
- }
4199
4328
 
4200
4329
  // src/generate.ts
4201
4330
  async function generate(adapter, promptOrOptions, callOptions) {
@@ -4225,7 +4354,8 @@ async function generate(adapter, promptOrOptions, callOptions) {
4225
4354
  }),
4226
4355
  buildSnapshot: (model) => ({
4227
4356
  text: model.text,
4228
- reasoning: model.reasoning
4357
+ reasoning: model.reasoning,
4358
+ ...model.reasoningBlocks ? { reasoningBlocks: model.reasoningBlocks } : {}
4229
4359
  }),
4230
4360
  debug: debugConfig,
4231
4361
  debugLabel: "generate",
@@ -4240,7 +4370,8 @@ async function generate(adapter, promptOrOptions, callOptions) {
4240
4370
  text: response.text,
4241
4371
  reasoning: response.reasoning,
4242
4372
  usage: response.usage,
4243
- finishReason: response.finishReason
4373
+ finishReason: response.finishReason,
4374
+ ...response.reasoningBlocks ? { reasoningBlocks: response.reasoningBlocks } : {}
4244
4375
  };
4245
4376
  const attempts = [attempt];
4246
4377
  normalized.observe?.({
@@ -4257,7 +4388,8 @@ async function generate(adapter, promptOrOptions, callOptions) {
4257
4388
  reasoning: attempt.reasoning,
4258
4389
  attempts,
4259
4390
  usage: aggregateUsage(attempts),
4260
- finishReason: attempt.finishReason
4391
+ finishReason: attempt.finishReason,
4392
+ ...attempt.reasoningBlocks ? { reasoningBlocks: attempt.reasoningBlocks } : {}
4261
4393
  };
4262
4394
  }
4263
4395
  function normalizeGenerateInput(promptOrOptions, callOptions) {
@@ -4268,7 +4400,7 @@ function normalizeGenerateInput(promptOrOptions, callOptions) {
4268
4400
  throw new Error("Missing prompt in generate(adapter, prompt, options?) call.");
4269
4401
  }
4270
4402
  return {
4271
- ...callOptions ?? {},
4403
+ ...callOptions,
4272
4404
  prompt: promptOrOptions
4273
4405
  };
4274
4406
  }
@@ -4294,9 +4426,6 @@ function prepareGeneratePromptPayload(payload, systemPrompt) {
4294
4426
  };
4295
4427
  }
4296
4428
 
4297
- // src/structured.ts
4298
- var import_jsonrepair3 = require("jsonrepair");
4299
-
4300
4429
  // src/parse.ts
4301
4430
  var import_jsonrepair2 = require("jsonrepair");
4302
4431
  function parseLLMOutput(output, schema, options = {}) {
@@ -4596,6 +4725,80 @@ function formatZodIssues(issues) {
4596
4725
  `);
4597
4726
  }
4598
4727
 
4728
+ // src/structured-streaming.ts
4729
+ var import_jsonrepair3 = require("jsonrepair");
4730
+ function parseStreamingStructuredData(parseSource) {
4731
+ const sanitized = sanitizeThink(parseSource);
4732
+ const start = findFirstJsonRootStart(sanitized.visibleText);
4733
+ if (start < 0) {
4734
+ return null;
4735
+ }
4736
+ const candidate = sanitized.visibleText.slice(start).trim();
4737
+ if (!candidate) {
4738
+ return null;
4739
+ }
4740
+ try {
4741
+ const repaired = import_jsonrepair3.jsonrepair(candidate);
4742
+ const parsed = JSON.parse(repaired);
4743
+ if (typeof parsed !== "object" || parsed === null) {
4744
+ return null;
4745
+ }
4746
+ return parsed;
4747
+ } catch {
4748
+ return null;
4749
+ }
4750
+ }
4751
+ function findFirstJsonRootStart(input) {
4752
+ const unquotedRootStart = findFirstUnquotedJsonRootStart(input);
4753
+ if (unquotedRootStart >= 0) {
4754
+ return unquotedRootStart;
4755
+ }
4756
+ return findFirstRawJsonRootStart(input);
4757
+ }
4758
+ function findFirstUnquotedJsonRootStart(input) {
4759
+ let inString = false;
4760
+ let escaped = false;
4761
+ for (let index = 0;index < input.length; index += 1) {
4762
+ const char = input[index];
4763
+ if (!char) {
4764
+ continue;
4765
+ }
4766
+ if (inString) {
4767
+ if (escaped) {
4768
+ escaped = false;
4769
+ continue;
4770
+ }
4771
+ if (char === "\\") {
4772
+ escaped = true;
4773
+ continue;
4774
+ }
4775
+ if (char === '"') {
4776
+ inString = false;
4777
+ }
4778
+ continue;
4779
+ }
4780
+ if (char === '"') {
4781
+ inString = true;
4782
+ continue;
4783
+ }
4784
+ if (char === "{" || char === "[") {
4785
+ return index;
4786
+ }
4787
+ }
4788
+ return -1;
4789
+ }
4790
+ function findFirstRawJsonRootStart(input) {
4791
+ const objectStart = input.indexOf("{");
4792
+ const arrayStart = input.indexOf("[");
4793
+ if (objectStart < 0) {
4794
+ return arrayStart;
4795
+ }
4796
+ if (arrayStart < 0) {
4797
+ return objectStart;
4798
+ }
4799
+ return Math.min(objectStart, arrayStart);
4800
+ }
4801
+
4599
4802
  // src/structured.ts
4600
4803
  class StructuredParseError extends Error {
4601
4804
  name = "StructuredParseError";
@@ -4833,7 +5036,7 @@ function normalizeStructuredInput(schemaOrOptions, promptInput, callOptions) {
4833
5036
  throw new Error("Missing prompt in structured(adapter, schema, prompt, options?) call.");
4834
5037
  }
4835
5038
  return {
4836
- ...callOptions ?? {},
5039
+ ...callOptions,
4837
5040
  schema: schemaOrOptions,
4838
5041
  prompt: promptInput
4839
5042
  };
@@ -5077,6 +5280,7 @@ async function executeAttempt(adapter, input) {
5077
5280
  success: parsed.success,
5078
5281
  usage: response.usage,
5079
5282
  finishReason: response.finishReason,
5283
+ ...response.reasoningBlocks ? { reasoningBlocks: response.reasoningBlocks } : {},
5080
5284
  parsed
5081
5285
  };
5082
5286
  return {
@@ -5097,72 +5301,12 @@ async function callModel2(adapter, options) {
5097
5301
  buildSnapshot: (normalized) => ({
5098
5302
  text: normalized.text,
5099
5303
  reasoning: normalized.reasoning,
5304
+ ...normalized.reasoningBlocks ? { reasoningBlocks: normalized.reasoningBlocks } : {},
5100
5305
  data: parseStreamingStructuredData(normalized.parseSource) ?? null
5101
5306
  }),
5102
5307
  debugLabel: "structured"
5103
5308
  });
5104
5309
  }
5105
- function parseStreamingStructuredData(parseSource) {
5106
- const sanitized = sanitizeThink(parseSource);
5107
- const start = findFirstJsonRootStart(sanitized.visibleText);
5108
- if (start < 0) {
5109
- return null;
5110
- }
5111
- const candidate = sanitized.visibleText.slice(start).trim();
5112
- if (!candidate) {
5113
- return null;
5114
- }
5115
- try {
5116
- const repaired = import_jsonrepair3.jsonrepair(candidate);
5117
- const parsed = JSON.parse(repaired);
5118
- if (typeof parsed !== "object" || parsed === null) {
5119
- return null;
5120
- }
5121
- return parsed;
5122
- } catch {
5123
- return null;
5124
- }
5125
- }
5126
- function findFirstJsonRootStart(input) {
5127
- let inString = false;
5128
- let escaped = false;
5129
- for (let index = 0;index < input.length; index += 1) {
5130
- const char = input[index];
5131
- if (!char) {
5132
- continue;
5133
- }
5134
- if (inString) {
5135
- if (escaped) {
5136
- escaped = false;
5137
- continue;
5138
- }
5139
- if (char === "\\") {
5140
- escaped = true;
5141
- continue;
5142
- }
5143
- if (char === '"') {
5144
- inString = false;
5145
- }
5146
- continue;
5147
- }
5148
- if (char === '"') {
5149
- inString = true;
5150
- continue;
5151
- }
5152
- if (char === "{" || char === "[") {
5153
- return index;
5154
- }
5155
- }
5156
- const objectStart = input.indexOf("{");
5157
- const arrayStart = input.indexOf("[");
5158
- if (objectStart < 0) {
5159
- return arrayStart;
5160
- }
5161
- if (arrayStart < 0) {
5162
- return objectStart;
5163
- }
5164
- return Math.min(objectStart, arrayStart);
5165
- }
5166
5310
  function parseWithObserve(output, schema, parseOptions, context) {
5167
5311
  const userParseTrace = parseOptions.onTrace;
5168
5312
  return parseLLMOutput(output, schema, {
@@ -5197,7 +5341,8 @@ function buildSuccessResult(data, attempts) {
5197
5341
  json: final?.json ?? null,
5198
5342
  attempts,
5199
5343
  usage: aggregateUsage(attempts),
5200
- finishReason: final?.finishReason
5344
+ finishReason: final?.finishReason,
5345
+ ...final?.reasoningBlocks ? { reasoningBlocks: final.reasoningBlocks } : {}
5201
5346
  };
5202
5347
  }
5203
5348
  function toStructuredError(attempt) {
@@ -5264,12 +5409,12 @@ function mergeStructuredOptions(defaults, overrides) {
5264
5409
  ...defaults,
5265
5410
  ...overrides,
5266
5411
  parse: {
5267
- ...defaults?.parse ?? {},
5268
- ...overrides?.parse ?? {}
5412
+ ...defaults?.parse,
5413
+ ...overrides?.parse
5269
5414
  },
5270
5415
  request: {
5271
- ...defaults?.request ?? {},
5272
- ...overrides?.request ?? {}
5416
+ ...defaults?.request,
5417
+ ...overrides?.request
5273
5418
  },
5274
5419
  stream: mergeObjectLike(defaults?.stream, overrides?.stream),
5275
5420
  selfHeal: mergeObjectLike(defaults?.selfHeal, overrides?.selfHeal),
@@ -5285,8 +5430,8 @@ function mergeGenerateOptions(defaults, overrides) {
5285
5430
  outdent: overrides?.outdent ?? defaults?.outdent,
5286
5431
  systemPrompt: overrides?.systemPrompt ?? defaults?.systemPrompt,
5287
5432
  request: {
5288
- ...defaults?.request ?? {},
5289
- ...overrides?.request ?? {}
5433
+ ...defaults?.request,
5434
+ ...overrides?.request
5290
5435
  },
5291
5436
  stream: mergeObjectLike(defaults?.stream, overrides?.stream),
5292
5437
  debug: mergeObjectLike(defaults?.debug, overrides?.debug),
@@ -5660,11 +5805,11 @@ function inferSchemaExample(schema) {
5660
5805
  }
5661
5806
  function getObjectShape(schema) {
5662
5807
  const unwrapped = unwrap2(schema).schema;
5663
- const typeName = unwrapped._def?.type;
5808
+ const typeName = unwrapped.def?.type;
5664
5809
  if (typeName !== "object") {
5665
5810
  return null;
5666
5811
  }
5667
- const rawShape = unwrapped._def?.shape;
5812
+ const rawShape = unwrapped.def?.shape;
5668
5813
  if (typeof rawShape === "function") {
5669
5814
  return rawShape();
5670
5815
  }
@@ -5672,11 +5817,11 @@ function getObjectShape(schema) {
5672
5817
  }
5673
5818
  function readDefaultValue(schema) {
5674
5819
  let current = schema;
5675
- while (current?._def?.type) {
5676
- const typeName = current._def.type;
5820
+ while (current?.def?.type) {
5821
+ const typeName = current.def.type;
5677
5822
  if (typeName === "default") {
5678
5823
  try {
5679
- const raw = current._def.defaultValue;
5824
+ const raw = current.def.defaultValue;
5680
5825
  if (typeof raw === "function") {
5681
5826
  return raw();
5682
5827
  }
@@ -5686,11 +5831,11 @@ function readDefaultValue(schema) {
5686
5831
  }
5687
5832
  }
5688
5833
  if (typeName === "optional" || typeName === "nullable" || typeName === "catch" || typeName === "readonly") {
5689
- current = current._def.innerType ?? current;
5834
+ current = current.def.innerType ?? current;
5690
5835
  continue;
5691
5836
  }
5692
5837
  if (typeName === "pipe") {
5693
- current = current._def.in ?? current;
5838
+ current = current.def.in ?? current;
5694
5839
  continue;
5695
5840
  }
5696
5841
  return;
@@ -5699,22 +5844,22 @@ function readDefaultValue(schema) {
5699
5844
  }
5700
5845
  function readSchemaDescription2(schema) {
5701
5846
  let current = schema;
5702
- while (current?._def?.type) {
5847
+ while (current?.def?.type) {
5703
5848
  const desc = current.description;
5704
5849
  if (typeof desc === "string" && desc.trim().length > 0) {
5705
5850
  return desc.trim();
5706
5851
  }
5707
- const typeName = current._def.type;
5852
+ const typeName = current.def.type;
5708
5853
  if (typeName === "optional" || typeName === "default" || typeName === "nullable") {
5709
- current = current._def.innerType ?? current;
5854
+ current = current.def.innerType ?? current;
5710
5855
  continue;
5711
5856
  }
5712
5857
  if (typeName === "catch" || typeName === "readonly") {
5713
- current = current._def.innerType ?? current;
5858
+ current = current.def.innerType ?? current;
5714
5859
  continue;
5715
5860
  }
5716
5861
  if (typeName === "pipe") {
5717
- current = current._def.in ?? current;
5862
+ current = current.def.in ?? current;
5718
5863
  continue;
5719
5864
  }
5720
5865
  break;
@@ -5724,19 +5869,19 @@ function readSchemaDescription2(schema) {
5724
5869
  function unwrap2(schema) {
5725
5870
  let current = schema;
5726
5871
  let optional = false;
5727
- while (current?._def?.type) {
5728
- const typeName = current._def.type;
5872
+ while (current?.def?.type) {
5873
+ const typeName = current.def.type;
5729
5874
  if (typeName === "optional" || typeName === "default") {
5730
5875
  optional = true;
5731
- current = current._def.innerType ?? current;
5876
+ current = current.def.innerType ?? current;
5732
5877
  continue;
5733
5878
  }
5734
5879
  if (typeName === "nullable" || typeName === "catch" || typeName === "readonly") {
5735
- current = current._def.innerType ?? current;
5880
+ current = current.def.innerType ?? current;
5736
5881
  continue;
5737
5882
  }
5738
5883
  if (typeName === "pipe") {
5739
- current = current._def.in ?? current;
5884
+ current = current.def.in ?? current;
5740
5885
  continue;
5741
5886
  }
5742
5887
  break;