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.js CHANGED
@@ -1,9 +1,6 @@
1
1
  import { createRequire } from "node:module";
2
2
  var __require = /* @__PURE__ */ createRequire(import.meta.url);
3
3
 
4
- // src/extract.ts
5
- import { jsonrepair } from "jsonrepair";
6
-
7
4
  // src/utils/common.ts
8
5
  function toErrorMessage(error) {
9
6
  if (error instanceof Error) {
@@ -221,184 +218,77 @@ function isOnlyWhitespace(value) {
221
218
  return true;
222
219
  }
223
220
 
224
- // src/extract.ts
225
- var RE_EMPTY_OBJECT = /^\{\s*\}$/;
226
- var RE_EMPTY_ARRAY = /^\[\s*\]$/;
227
- var RE_BOUNDARY_CHAR = /[\s,.;:!?`"'()\[\]{}<>]/;
228
- var DEFAULT_EXTRACTION_HEURISTICS = {
229
- firstPassMin: 12,
230
- firstPassCap: 30,
231
- firstPassMultiplier: 6,
232
- secondPassMin: 4,
233
- secondPassCap: 8,
234
- secondPassMultiplier: 2,
235
- hintMaxLength: 50000
236
- };
237
- function extractJsonCandidates(input, options = {}) {
238
- const maxCandidates = options.maxCandidates ?? 5;
239
- const acceptArrays = options.acceptArrays ?? true;
240
- const allowRepairHints = options.allowRepairHints ?? true;
241
- const heuristics = resolveExtractionHeuristics(options.heuristics);
242
- const extractionInput = input;
243
- const candidates = [];
244
- candidates.push(...extractFromMarkdown(extractionInput, acceptArrays));
245
- candidates.push(...scanBalancedSegments(extractionInput, acceptArrays));
246
- if (candidates.length === 0 && extractionInput.trim()) {
247
- const content = extractionInput.trim();
248
- candidates.push({
249
- id: "raw:fallback",
250
- source: "raw",
251
- content,
252
- start: 0,
253
- end: extractionInput.length,
254
- score: 10 + Math.floor(lengthScore(content.length) / 3)
255
- });
256
- }
257
- const prefiltered = prefilterByJsonShape(candidates, acceptArrays);
258
- const firstPassLimit = clamp(maxCandidates * heuristics.firstPassMultiplier, heuristics.firstPassMin, heuristics.firstPassCap);
259
- const firstPass = prefiltered.slice(0, firstPassLimit);
260
- const secondPassLimit = Math.min(firstPass.length, clamp(maxCandidates * heuristics.secondPassMultiplier, heuristics.secondPassMin, heuristics.secondPassCap));
261
- for (let i = 0;i < secondPassLimit; i += 1) {
262
- const candidate = firstPass[i];
263
- if (!candidate) {
264
- continue;
265
- }
266
- const parseHint = buildParseHint(candidate.content, allowRepairHints, heuristics.hintMaxLength);
267
- if (!parseHint) {
268
- continue;
269
- }
270
- candidate.parseHint = parseHint;
271
- candidate.score += parseHintBonus(parseHint);
221
+ // src/extract-parse-hint.ts
222
+ import { jsonrepair } from "jsonrepair";
223
+ function buildParseHint(content, allowRepair, hintMaxLength) {
224
+ if (content.length > hintMaxLength) {
225
+ return null;
272
226
  }
273
- const sorted = sortCandidates(firstPass);
274
- const deduped = dedupeCandidates(sorted);
275
- return deduped.slice(0, maxCandidates).map((candidate, index) => ({
276
- id: `${candidate.source}:${index}`,
277
- source: candidate.source,
278
- content: candidate.content,
279
- language: candidate.language,
280
- parseHint: candidate.parseHint,
281
- start: candidate.start,
282
- end: candidate.end,
283
- score: candidate.score
284
- }));
285
- }
286
- function prefilterByJsonShape(candidates, acceptArrays) {
287
- const shaped = candidates.map((candidate) => {
288
- const shapeScore = jsonShapeScore(candidate.content, acceptArrays);
227
+ try {
289
228
  return {
290
- ...candidate,
291
- shapeScore,
292
- score: candidate.score + shapeScore
229
+ success: true,
230
+ parsed: JSON.parse(content),
231
+ repaired: null,
232
+ usedRepair: false,
233
+ stage: "parse",
234
+ error: ""
293
235
  };
294
- });
295
- const sorted = sortCandidates(shaped);
296
- const deduped = dedupeCandidates(sorted);
297
- const filtered = deduped.filter((candidate) => passesShapeFilter(candidate));
298
- if (filtered.length > 0) {
299
- const fallback = deduped.find((candidate) => !passesShapeFilter(candidate));
300
- if (fallback) {
301
- filtered.push(fallback);
236
+ } catch (directError) {
237
+ if (!allowRepair) {
238
+ return {
239
+ success: false,
240
+ parsed: null,
241
+ repaired: null,
242
+ usedRepair: false,
243
+ stage: "parse",
244
+ error: toErrorMessage(directError)
245
+ };
302
246
  }
303
- return sortCandidates(filtered);
304
- }
305
- return deduped.slice(0, Math.min(1, deduped.length));
306
- }
307
- function extractFromMarkdown(input, acceptArrays) {
308
- const blocks = extractMarkdownCodeBlocks(input);
309
- return blocks.flatMap((block, index) => {
310
- const language = block.language || null;
311
- const content = block.code.trim();
312
- if (!content) {
313
- return [];
247
+ let repaired;
248
+ try {
249
+ repaired = jsonrepair(content);
250
+ } catch (repairError) {
251
+ return {
252
+ success: false,
253
+ parsed: null,
254
+ repaired: null,
255
+ usedRepair: true,
256
+ stage: "repair",
257
+ error: toErrorMessage(repairError)
258
+ };
314
259
  }
315
- if (!looksLikeJsonEnvelope(content, acceptArrays)) {
316
- return [];
260
+ try {
261
+ return {
262
+ success: true,
263
+ parsed: JSON.parse(repaired),
264
+ repaired,
265
+ usedRepair: true,
266
+ stage: "parse",
267
+ error: ""
268
+ };
269
+ } catch (parseError) {
270
+ return {
271
+ success: false,
272
+ parsed: null,
273
+ repaired,
274
+ usedRepair: true,
275
+ stage: "parse",
276
+ error: toErrorMessage(parseError || directError)
277
+ };
317
278
  }
318
- const langBonus = languageBonus(language);
319
- return [
320
- {
321
- id: `fenced:${index}`,
322
- source: "fenced",
323
- language,
324
- content,
325
- start: block.start,
326
- end: block.end,
327
- score: 260 + langBonus + lengthScore(content.length)
328
- }
329
- ];
330
- });
279
+ }
331
280
  }
332
- function scanBalancedSegments(input, acceptArrays) {
333
- const results = [];
334
- const stack = [];
335
- let inString = false;
336
- let quote = null;
337
- let escaped = false;
338
- for (let i = 0;i < input.length; i += 1) {
339
- const char = input[i];
340
- if (!char) {
341
- continue;
342
- }
343
- if (inString) {
344
- if (escaped) {
345
- escaped = false;
346
- continue;
347
- }
348
- if (char === "\\") {
349
- escaped = true;
350
- continue;
351
- }
352
- if (char === quote) {
353
- inString = false;
354
- quote = null;
355
- }
356
- continue;
357
- }
358
- const allowSingleQuoted = stack.length > 0;
359
- if (char === '"' || allowSingleQuoted && (char === "'" || char === "`")) {
360
- inString = true;
361
- quote = char;
362
- continue;
363
- }
364
- if (char === "{" || char === "[") {
365
- stack.push({ char, index: i });
366
- continue;
367
- }
368
- if (char !== "}" && char !== "]") {
369
- continue;
370
- }
371
- const expectedOpen = char === "}" ? "{" : "[";
372
- while (stack.length > 0 && stack[stack.length - 1]?.char !== expectedOpen) {
373
- stack.pop();
374
- }
375
- const opened = stack.pop();
376
- if (!opened) {
377
- continue;
378
- }
379
- if (stack.length > 0) {
380
- continue;
381
- }
382
- if (!acceptArrays && opened.char === "[") {
383
- continue;
384
- }
385
- const content = input.slice(opened.index, i + 1).trim();
386
- if (!content) {
387
- continue;
388
- }
389
- const rootBonus = opened.char === "{" ? 40 : 20;
390
- const boundaryBonus = boundaryScore(input, opened.index, i + 1);
391
- results.push({
392
- id: `scan:${results.length}`,
393
- source: "scan",
394
- content,
395
- start: opened.index,
396
- end: i + 1,
397
- score: 120 + rootBonus + boundaryBonus + lengthScore(content.length)
398
- });
281
+ function parseHintBonus(hint) {
282
+ if (hint.success) {
283
+ return hint.usedRepair ? 70 : 120;
399
284
  }
400
- return results;
285
+ return hint.usedRepair ? -20 : -10;
401
286
  }
287
+
288
+ // src/extract-shape.ts
289
+ var RE_EMPTY_OBJECT = /^\{\s*\}$/;
290
+ var RE_EMPTY_ARRAY = /^\[\s*\]$/;
291
+ var RE_BOUNDARY_CHAR = /[\s,.;:!?`"'()[\]{}<>]/;
402
292
  function looksLikeJsonEnvelope(content, acceptArrays) {
403
293
  const trimmed = content.trim();
404
294
  if (trimmed.startsWith("{")) {
@@ -507,95 +397,6 @@ function passesShapeFilter(candidate) {
507
397
  }
508
398
  return candidate.shapeScore >= 35;
509
399
  }
510
- function buildParseHint(content, allowRepair, hintMaxLength) {
511
- if (content.length > hintMaxLength) {
512
- return null;
513
- }
514
- try {
515
- return {
516
- success: true,
517
- parsed: JSON.parse(content),
518
- repaired: null,
519
- usedRepair: false,
520
- stage: "parse",
521
- error: ""
522
- };
523
- } catch (directError) {
524
- if (!allowRepair) {
525
- return {
526
- success: false,
527
- parsed: null,
528
- repaired: null,
529
- usedRepair: false,
530
- stage: "parse",
531
- error: toErrorMessage(directError)
532
- };
533
- }
534
- let repaired;
535
- try {
536
- repaired = jsonrepair(content);
537
- } catch (repairError) {
538
- return {
539
- success: false,
540
- parsed: null,
541
- repaired: null,
542
- usedRepair: true,
543
- stage: "repair",
544
- error: toErrorMessage(repairError)
545
- };
546
- }
547
- try {
548
- return {
549
- success: true,
550
- parsed: JSON.parse(repaired),
551
- repaired,
552
- usedRepair: true,
553
- stage: "parse",
554
- error: ""
555
- };
556
- } catch (parseError) {
557
- return {
558
- success: false,
559
- parsed: null,
560
- repaired,
561
- usedRepair: true,
562
- stage: "parse",
563
- error: toErrorMessage(parseError || directError)
564
- };
565
- }
566
- }
567
- }
568
- function resolveExtractionHeuristics(input) {
569
- const merged = {
570
- ...DEFAULT_EXTRACTION_HEURISTICS,
571
- ...input
572
- };
573
- const firstPassMin = normalizeInteger(merged.firstPassMin, DEFAULT_EXTRACTION_HEURISTICS.firstPassMin);
574
- const firstPassCap = Math.max(firstPassMin, normalizeInteger(merged.firstPassCap, DEFAULT_EXTRACTION_HEURISTICS.firstPassCap));
575
- const secondPassMin = normalizeInteger(merged.secondPassMin, DEFAULT_EXTRACTION_HEURISTICS.secondPassMin);
576
- const secondPassCap = Math.max(secondPassMin, normalizeInteger(merged.secondPassCap, DEFAULT_EXTRACTION_HEURISTICS.secondPassCap));
577
- return {
578
- firstPassMin,
579
- firstPassCap,
580
- firstPassMultiplier: normalizeInteger(merged.firstPassMultiplier, DEFAULT_EXTRACTION_HEURISTICS.firstPassMultiplier),
581
- secondPassMin,
582
- secondPassCap,
583
- secondPassMultiplier: normalizeInteger(merged.secondPassMultiplier, DEFAULT_EXTRACTION_HEURISTICS.secondPassMultiplier),
584
- hintMaxLength: normalizeInteger(merged.hintMaxLength, DEFAULT_EXTRACTION_HEURISTICS.hintMaxLength)
585
- };
586
- }
587
- function normalizeInteger(value, fallback) {
588
- if (typeof value !== "number" || !Number.isFinite(value)) {
589
- return fallback;
590
- }
591
- return Math.max(0, Math.floor(value));
592
- }
593
- function parseHintBonus(hint) {
594
- if (hint.success) {
595
- return hint.usedRepair ? 70 : 120;
596
- }
597
- return hint.usedRepair ? -20 : -10;
598
- }
599
400
  function hasBalancedJsonDelimiters(input) {
600
401
  const stack = [];
601
402
  let inString = false;
@@ -681,6 +482,207 @@ function dedupeCandidates(candidates) {
681
482
  function clamp(value, min, max) {
682
483
  return Math.max(min, Math.min(max, Math.floor(value)));
683
484
  }
485
+ function normalizeInteger(value, fallback) {
486
+ if (typeof value !== "number" || !Number.isFinite(value)) {
487
+ return fallback;
488
+ }
489
+ return Math.max(0, Math.floor(value));
490
+ }
491
+ function resolveExtractionHeuristics(input, defaults) {
492
+ const merged = {
493
+ ...defaults,
494
+ ...input
495
+ };
496
+ const firstPassMin = normalizeInteger(merged.firstPassMin, defaults.firstPassMin);
497
+ const firstPassCap = Math.max(firstPassMin, normalizeInteger(merged.firstPassCap, defaults.firstPassCap));
498
+ const secondPassMin = normalizeInteger(merged.secondPassMin, defaults.secondPassMin);
499
+ const secondPassCap = Math.max(secondPassMin, normalizeInteger(merged.secondPassCap, defaults.secondPassCap));
500
+ return {
501
+ firstPassMin,
502
+ firstPassCap,
503
+ firstPassMultiplier: normalizeInteger(merged.firstPassMultiplier, defaults.firstPassMultiplier),
504
+ secondPassMin,
505
+ secondPassCap,
506
+ secondPassMultiplier: normalizeInteger(merged.secondPassMultiplier, defaults.secondPassMultiplier),
507
+ hintMaxLength: normalizeInteger(merged.hintMaxLength, defaults.hintMaxLength)
508
+ };
509
+ }
510
+
511
+ // src/extract.ts
512
+ var DEFAULT_EXTRACTION_HEURISTICS = {
513
+ firstPassMin: 12,
514
+ firstPassCap: 30,
515
+ firstPassMultiplier: 6,
516
+ secondPassMin: 4,
517
+ secondPassCap: 8,
518
+ secondPassMultiplier: 2,
519
+ hintMaxLength: 50000
520
+ };
521
+ function extractJsonCandidates(input, options = {}) {
522
+ const maxCandidates = options.maxCandidates ?? 5;
523
+ const acceptArrays = options.acceptArrays ?? true;
524
+ const allowRepairHints = options.allowRepairHints ?? true;
525
+ const heuristics = resolveExtractionHeuristics(options.heuristics, DEFAULT_EXTRACTION_HEURISTICS);
526
+ const extractionInput = input;
527
+ const candidates = [];
528
+ candidates.push(...extractFromMarkdown(extractionInput, acceptArrays));
529
+ candidates.push(...scanBalancedSegments(extractionInput, acceptArrays));
530
+ if (candidates.length === 0 && extractionInput.trim()) {
531
+ const content = extractionInput.trim();
532
+ candidates.push({
533
+ id: "raw:fallback",
534
+ source: "raw",
535
+ content,
536
+ start: 0,
537
+ end: extractionInput.length,
538
+ score: 10 + Math.floor(lengthScore(content.length) / 3)
539
+ });
540
+ }
541
+ const prefiltered = prefilterByJsonShape(candidates, acceptArrays);
542
+ const firstPassLimit = clamp(maxCandidates * heuristics.firstPassMultiplier, heuristics.firstPassMin, heuristics.firstPassCap);
543
+ const firstPass = prefiltered.slice(0, firstPassLimit);
544
+ const secondPassLimit = Math.min(firstPass.length, clamp(maxCandidates * heuristics.secondPassMultiplier, heuristics.secondPassMin, heuristics.secondPassCap));
545
+ for (let i = 0;i < secondPassLimit; i += 1) {
546
+ const candidate = firstPass[i];
547
+ if (!candidate) {
548
+ continue;
549
+ }
550
+ const parseHint = buildParseHint(candidate.content, allowRepairHints, heuristics.hintMaxLength);
551
+ if (!parseHint) {
552
+ continue;
553
+ }
554
+ candidate.parseHint = parseHint;
555
+ candidate.score += parseHintBonus(parseHint);
556
+ }
557
+ const sorted = sortCandidates(firstPass);
558
+ const deduped = dedupeCandidates(sorted);
559
+ return deduped.slice(0, maxCandidates).map((candidate, index) => ({
560
+ id: `${candidate.source}:${index}`,
561
+ source: candidate.source,
562
+ content: candidate.content,
563
+ language: candidate.language,
564
+ parseHint: candidate.parseHint,
565
+ start: candidate.start,
566
+ end: candidate.end,
567
+ score: candidate.score
568
+ }));
569
+ }
570
+ function prefilterByJsonShape(candidates, acceptArrays) {
571
+ const shaped = candidates.map((candidate) => {
572
+ const shapeScore = jsonShapeScore(candidate.content, acceptArrays);
573
+ return {
574
+ ...candidate,
575
+ shapeScore,
576
+ score: candidate.score + shapeScore
577
+ };
578
+ });
579
+ const sorted = sortCandidates(shaped);
580
+ const deduped = dedupeCandidates(sorted);
581
+ const filtered = deduped.filter((candidate) => passesShapeFilter(candidate));
582
+ if (filtered.length > 0) {
583
+ const fallback = deduped.find((candidate) => !passesShapeFilter(candidate));
584
+ if (fallback) {
585
+ filtered.push(fallback);
586
+ }
587
+ return sortCandidates(filtered);
588
+ }
589
+ return deduped.slice(0, Math.min(1, deduped.length));
590
+ }
591
+ function extractFromMarkdown(input, acceptArrays) {
592
+ const blocks = extractMarkdownCodeBlocks(input);
593
+ return blocks.flatMap((block, index) => {
594
+ const language = block.language || null;
595
+ const content = block.code.trim();
596
+ if (!content) {
597
+ return [];
598
+ }
599
+ if (!looksLikeJsonEnvelope(content, acceptArrays)) {
600
+ return [];
601
+ }
602
+ const langBonus = languageBonus(language);
603
+ return [
604
+ {
605
+ id: `fenced:${index}`,
606
+ source: "fenced",
607
+ language,
608
+ content,
609
+ start: block.start,
610
+ end: block.end,
611
+ score: 260 + langBonus + lengthScore(content.length)
612
+ }
613
+ ];
614
+ });
615
+ }
616
+ function scanBalancedSegments(input, acceptArrays) {
617
+ const results = [];
618
+ const stack = [];
619
+ let inString = false;
620
+ let quote = null;
621
+ let escaped = false;
622
+ for (let i = 0;i < input.length; i += 1) {
623
+ const char = input[i];
624
+ if (!char) {
625
+ continue;
626
+ }
627
+ if (inString) {
628
+ if (escaped) {
629
+ escaped = false;
630
+ continue;
631
+ }
632
+ if (char === "\\") {
633
+ escaped = true;
634
+ continue;
635
+ }
636
+ if (char === quote) {
637
+ inString = false;
638
+ quote = null;
639
+ }
640
+ continue;
641
+ }
642
+ const allowSingleQuoted = stack.length > 0;
643
+ if (char === '"' || allowSingleQuoted && (char === "'" || char === "`")) {
644
+ inString = true;
645
+ quote = char;
646
+ continue;
647
+ }
648
+ if (char === "{" || char === "[") {
649
+ stack.push({ char, index: i });
650
+ continue;
651
+ }
652
+ if (char !== "}" && char !== "]") {
653
+ continue;
654
+ }
655
+ const expectedOpen = char === "}" ? "{" : "[";
656
+ while (stack.length > 0 && stack[stack.length - 1]?.char !== expectedOpen) {
657
+ stack.pop();
658
+ }
659
+ const opened = stack.pop();
660
+ if (!opened) {
661
+ continue;
662
+ }
663
+ if (stack.length > 0) {
664
+ continue;
665
+ }
666
+ if (!acceptArrays && opened.char === "[") {
667
+ continue;
668
+ }
669
+ const content = input.slice(opened.index, i + 1).trim();
670
+ if (!content) {
671
+ continue;
672
+ }
673
+ const rootBonus = opened.char === "{" ? 40 : 20;
674
+ const boundaryBonus = boundaryScore(input, opened.index, i + 1);
675
+ results.push({
676
+ id: `scan:${results.length}`,
677
+ source: "scan",
678
+ content,
679
+ start: opened.index,
680
+ end: i + 1,
681
+ score: 120 + rootBonus + boundaryBonus + lengthScore(content.length)
682
+ });
683
+ }
684
+ return results;
685
+ }
684
686
  // src/schema.ts
685
687
  var RE_SIMPLE_IDENTIFIER = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
686
688
  var RE_WHITESPACE = /\s+/g;
@@ -700,36 +702,36 @@ function unwrap(schema) {
700
702
  let optional = false;
701
703
  let nullable = false;
702
704
  while (true) {
703
- const typeName = current?._def?.type;
705
+ const typeName = current?.def?.type;
704
706
  if (!typeName) {
705
707
  break;
706
708
  }
707
709
  if (typeName === "optional") {
708
710
  optional = true;
709
- current = current._def?.innerType ?? current;
711
+ current = current.def?.innerType ?? current;
710
712
  continue;
711
713
  }
712
714
  if (typeName === "default") {
713
715
  optional = true;
714
- current = current._def?.innerType ?? current;
716
+ current = current.def?.innerType ?? current;
715
717
  continue;
716
718
  }
717
719
  if (typeName === "nullable") {
718
720
  nullable = true;
719
- current = current._def?.innerType ?? current;
721
+ current = current.def?.innerType ?? current;
720
722
  continue;
721
723
  }
722
724
  if (typeName === "pipe") {
723
- const outType = current._def?.out?._def?.type;
725
+ const outType = current.def?.out?.def?.type;
724
726
  if (outType === "transform") {
725
- current = current._def?.in ?? current;
727
+ current = current.def?.in ?? current;
726
728
  } else {
727
- current = current._def?.out ?? current;
729
+ current = current.def?.out ?? current;
728
730
  }
729
731
  continue;
730
732
  }
731
733
  if (typeName === "catch" || typeName === "readonly") {
732
- current = current._def?.innerType ?? current;
734
+ current = current.def?.innerType ?? current;
733
735
  continue;
734
736
  }
735
737
  break;
@@ -745,7 +747,7 @@ function formatCore(schema, depth, seen) {
745
747
  return "unknown";
746
748
  }
747
749
  seen.add(schema);
748
- const typeName = schema?._def?.type;
750
+ const typeName = schema?.def?.type;
749
751
  switch (typeName) {
750
752
  case "string":
751
753
  return "string";
@@ -770,44 +772,44 @@ function formatCore(schema, depth, seen) {
770
772
  case "void":
771
773
  return "void";
772
774
  case "literal": {
773
- const value = schema._def?.values?.[0];
775
+ const value = schema.def?.values?.[0];
774
776
  return JSON.stringify(value);
775
777
  }
776
778
  case "enum": {
777
- const entries = schema._def?.entries;
779
+ const entries = schema.def?.entries;
778
780
  const values = Object.values(entries ?? {});
779
781
  const unique = [...new Set(values.filter((v) => typeof v !== "string" || Number.isNaN(Number(v))))];
780
782
  return unique.map((v) => JSON.stringify(v)).join(" | ") || "string";
781
783
  }
782
784
  case "array": {
783
- const inner = formatType(schema._def?.element ?? schema, depth, seen);
785
+ const inner = formatType(schema.def?.element ?? schema, depth, seen);
784
786
  return requiresParentheses(inner) ? `(${inner})[]` : `${inner}[]`;
785
787
  }
786
788
  case "tuple": {
787
- const items = (schema._def?.items ?? []).map((item) => formatType(item, depth, seen));
789
+ const items = (schema.def?.items ?? []).map((item) => formatType(item, depth, seen));
788
790
  return `[${items.join(", ")}]`;
789
791
  }
790
792
  case "union": {
791
- const options = (schema._def?.options ?? []).map((option) => formatType(option, depth, seen));
793
+ const options = (schema.def?.options ?? []).map((option) => formatType(option, depth, seen));
792
794
  return options.join(" | ") || "unknown";
793
795
  }
794
796
  case "intersection": {
795
- const left = formatType(schema._def?.left ?? schema, depth, seen);
796
- const right = formatType(schema._def?.right ?? schema, depth, seen);
797
+ const left = formatType(schema.def?.left ?? schema, depth, seen);
798
+ const right = formatType(schema.def?.right ?? schema, depth, seen);
797
799
  return `${left} & ${right}`;
798
800
  }
799
801
  case "record": {
800
- const keyType = formatType(schema._def?.keyType ?? schema, depth, seen);
801
- const valueType = formatType(schema._def?.valueType ?? schema, depth, seen);
802
+ const keyType = formatType(schema.def?.keyType ?? schema, depth, seen);
803
+ const valueType = formatType(schema.def?.valueType ?? schema, depth, seen);
802
804
  return `Record<${keyType}, ${valueType}>`;
803
805
  }
804
806
  case "map": {
805
- const keyType = formatType(schema._def?.keyType ?? schema, depth, seen);
806
- const valueType = formatType(schema._def?.valueType ?? schema, depth, seen);
807
+ const keyType = formatType(schema.def?.keyType ?? schema, depth, seen);
808
+ const valueType = formatType(schema.def?.valueType ?? schema, depth, seen);
807
809
  return `Map<${keyType}, ${valueType}>`;
808
810
  }
809
811
  case "set": {
810
- const valueType = formatType(schema._def?.valueType ?? schema, depth, seen);
812
+ const valueType = formatType(schema.def?.valueType ?? schema, depth, seen);
811
813
  return `Set<${valueType}>`;
812
814
  }
813
815
  case "object":
@@ -821,7 +823,7 @@ function formatCore(schema, depth, seen) {
821
823
  function formatObject(schema, depth, seen) {
822
824
  const indent = " ".repeat(depth);
823
825
  const innerIndent = " ".repeat(depth + 1);
824
- const rawShape = schema._def?.shape;
826
+ const rawShape = schema.def?.shape;
825
827
  const shape = typeof rawShape === "function" ? rawShape() : rawShape ?? {};
826
828
  const entries = Object.entries(shape);
827
829
  if (entries.length === 0) {
@@ -847,27 +849,27 @@ function requiresParentheses(typeText) {
847
849
  return typeText.includes(" | ") || typeText.includes(" & ");
848
850
  }
849
851
  function isIntegerNumber(schema) {
850
- const checks = schema._def?.checks ?? [];
852
+ const checks = schema.def?.checks ?? [];
851
853
  return checks.some((check) => check.isInt === true);
852
854
  }
853
855
  function readSchemaDescription(schema) {
854
856
  let current = schema;
855
- while (current?._def?.type) {
857
+ while (current?.def?.type) {
856
858
  const desc = current.description;
857
859
  if (typeof desc === "string" && desc.trim().length > 0) {
858
860
  return sanitizeDescription(desc);
859
861
  }
860
- const typeName = current._def.type;
862
+ const typeName = current.def.type;
861
863
  if (typeName === "optional" || typeName === "default" || typeName === "nullable") {
862
- current = current._def.innerType ?? current;
864
+ current = current.def.innerType ?? current;
863
865
  continue;
864
866
  }
865
867
  if (typeName === "pipe") {
866
- current = current._def.in ?? current;
868
+ current = current.def.in ?? current;
867
869
  continue;
868
870
  }
869
871
  if (typeName === "catch" || typeName === "readonly") {
870
- current = current._def.innerType ?? current;
872
+ current = current.def.innerType ?? current;
871
873
  continue;
872
874
  }
873
875
  break;
@@ -1129,18 +1131,108 @@ function findSSEBoundary(buffer) {
1129
1131
  return Math.max(crlfIndex, lfIndex);
1130
1132
  }
1131
1133
 
1132
- // src/providers/mcp-runtime.ts
1133
- var DEFAULT_MAX_TOOL_ROUNDS = 8;
1134
- async function resolveMCPToolset(clients) {
1135
- if (!Array.isArray(clients) || clients.length === 0) {
1136
- return {
1137
- tools: [],
1138
- byName: new Map
1139
- };
1140
- }
1141
- const listed = [];
1142
- for (const client of clients) {
1143
- let cursor;
1134
+ // src/providers/mcp-runtime-debug.ts
1135
+ function formatToolExecutionDebugLine(execution) {
1136
+ const status = execution.error ? "error" : "ok";
1137
+ const scope = [execution.provider, execution.model].filter(Boolean).join("/") || "unknown";
1138
+ const toolRef = execution.clientId ? `${execution.clientId}:${execution.name ?? "unknown"}` : execution.name ?? "unknown";
1139
+ const duration = typeof execution.durationMs === "number" ? ` ${execution.durationMs}ms` : "";
1140
+ const base = `[tool:mcp:${status}] ${scope} ${toolRef}#${execution.callId}${duration}`;
1141
+ if (execution.error) {
1142
+ return `${base} -> ${execution.error}`;
1143
+ }
1144
+ return base;
1145
+ }
1146
+ function emitToolExecution(request, execution) {
1147
+ request.onToolExecution?.(execution);
1148
+ const debug = resolveToolDebugOptions(request.toolDebug);
1149
+ if (!debug.enabled) {
1150
+ return;
1151
+ }
1152
+ debug.logger(formatToolExecutionDebugLine(execution));
1153
+ if (debug.includeRequest) {
1154
+ debug.logger(formatToolExecutionRequestDebugLine(execution, debug));
1155
+ }
1156
+ if (debug.includeResult && (!execution.error || debug.includeResultOnError)) {
1157
+ debug.logger(formatToolExecutionResultDebugLine(execution, debug));
1158
+ }
1159
+ }
1160
+ function resolveToolDebugOptions(value) {
1161
+ if (value === true) {
1162
+ return {
1163
+ enabled: true,
1164
+ logger: defaultToolDebugLogger,
1165
+ includeRequest: true,
1166
+ includeResult: true,
1167
+ includeResultOnError: true,
1168
+ pretty: false
1169
+ };
1170
+ }
1171
+ if (value === undefined || value === false) {
1172
+ return {
1173
+ enabled: false,
1174
+ logger: () => {
1175
+ return;
1176
+ },
1177
+ includeRequest: false,
1178
+ includeResult: false,
1179
+ includeResultOnError: false,
1180
+ pretty: false
1181
+ };
1182
+ }
1183
+ return {
1184
+ enabled: value.enabled ?? true,
1185
+ logger: value.logger ?? defaultToolDebugLogger,
1186
+ includeRequest: value.includeRequest ?? true,
1187
+ includeResult: value.includeResult ?? true,
1188
+ includeResultOnError: value.includeResultOnError ?? true,
1189
+ pretty: value.pretty ?? false
1190
+ };
1191
+ }
1192
+ function defaultToolDebugLogger(line) {
1193
+ const { log } = globalThis.console;
1194
+ log(line);
1195
+ }
1196
+ function formatToolExecutionRequestDebugLine(execution, debug) {
1197
+ const scope = [execution.provider, execution.model].filter(Boolean).join("/") || "unknown";
1198
+ const toolRef = execution.clientId ? `${execution.clientId}:${execution.name ?? "unknown"}` : execution.name ?? "unknown";
1199
+ const payload = formatDebugPayload(execution.arguments, debug.pretty);
1200
+ return `[tool:mcp:request] ${scope} ${toolRef}#${execution.callId} arguments=${payload}`;
1201
+ }
1202
+ function formatToolExecutionResultDebugLine(execution, debug) {
1203
+ const scope = [execution.provider, execution.model].filter(Boolean).join("/") || "unknown";
1204
+ const toolRef = execution.clientId ? `${execution.clientId}:${execution.name ?? "unknown"}` : execution.name ?? "unknown";
1205
+ if (execution.error) {
1206
+ const payload2 = formatDebugPayload({ error: execution.error }, debug.pretty);
1207
+ return `[tool:mcp:result:error] ${scope} ${toolRef}#${execution.callId} output=${payload2}`;
1208
+ }
1209
+ const payload = formatDebugPayload(execution.output, debug.pretty);
1210
+ return `[tool:mcp:result:ok] ${scope} ${toolRef}#${execution.callId} output=${payload}`;
1211
+ }
1212
+ function formatDebugPayload(value, pretty) {
1213
+ if (value === undefined) {
1214
+ return "undefined";
1215
+ }
1216
+ try {
1217
+ const serialized = JSON.stringify(value, null, pretty ? 2 : 0);
1218
+ return serialized ?? "undefined";
1219
+ } catch {
1220
+ return String(value);
1221
+ }
1222
+ }
1223
+
1224
+ // src/providers/mcp-runtime.ts
1225
+ var DEFAULT_MAX_TOOL_ROUNDS = 100;
1226
+ async function resolveMCPToolset(clients) {
1227
+ if (!Array.isArray(clients) || clients.length === 0) {
1228
+ return {
1229
+ tools: [],
1230
+ byName: new Map
1231
+ };
1232
+ }
1233
+ const listed = [];
1234
+ for (const client of clients) {
1235
+ let cursor;
1144
1236
  do {
1145
1237
  const page = await client.listTools(cursor ? { cursor } : undefined);
1146
1238
  for (const tool of page.tools ?? []) {
@@ -1321,90 +1413,6 @@ function stringifyToolOutput(value) {
1321
1413
  }
1322
1414
  return JSON.stringify(value ?? null);
1323
1415
  }
1324
- function formatToolExecutionDebugLine(execution) {
1325
- const status = execution.error ? "error" : "ok";
1326
- const scope = [execution.provider, execution.model].filter(Boolean).join("/") || "unknown";
1327
- const toolRef = execution.clientId ? `${execution.clientId}:${execution.name ?? "unknown"}` : execution.name ?? "unknown";
1328
- const duration = typeof execution.durationMs === "number" ? ` ${execution.durationMs}ms` : "";
1329
- const base = `[tool:mcp:${status}] ${scope} ${toolRef}#${execution.callId}${duration}`;
1330
- if (execution.error) {
1331
- return `${base} -> ${execution.error}`;
1332
- }
1333
- return base;
1334
- }
1335
- function emitToolExecution(request, execution) {
1336
- request.onToolExecution?.(execution);
1337
- const debug = resolveToolDebugOptions(request.toolDebug);
1338
- if (!debug.enabled) {
1339
- return;
1340
- }
1341
- debug.logger(formatToolExecutionDebugLine(execution));
1342
- if (debug.includeRequest) {
1343
- debug.logger(formatToolExecutionRequestDebugLine(execution, debug));
1344
- }
1345
- if (debug.includeResult && (!execution.error || debug.includeResultOnError)) {
1346
- debug.logger(formatToolExecutionResultDebugLine(execution, debug));
1347
- }
1348
- }
1349
- function resolveToolDebugOptions(value) {
1350
- if (value === true) {
1351
- return {
1352
- enabled: true,
1353
- logger: (line) => console.log(line),
1354
- includeRequest: true,
1355
- includeResult: true,
1356
- includeResultOnError: true,
1357
- pretty: false
1358
- };
1359
- }
1360
- if (value === undefined || value === false) {
1361
- return {
1362
- enabled: false,
1363
- logger: () => {
1364
- return;
1365
- },
1366
- includeRequest: false,
1367
- includeResult: false,
1368
- includeResultOnError: false,
1369
- pretty: false
1370
- };
1371
- }
1372
- return {
1373
- enabled: value.enabled ?? true,
1374
- logger: value.logger ?? ((line) => console.log(line)),
1375
- includeRequest: value.includeRequest ?? true,
1376
- includeResult: value.includeResult ?? true,
1377
- includeResultOnError: value.includeResultOnError ?? true,
1378
- pretty: value.pretty ?? false
1379
- };
1380
- }
1381
- function formatToolExecutionRequestDebugLine(execution, debug) {
1382
- const scope = [execution.provider, execution.model].filter(Boolean).join("/") || "unknown";
1383
- const toolRef = execution.clientId ? `${execution.clientId}:${execution.name ?? "unknown"}` : execution.name ?? "unknown";
1384
- const payload = formatDebugPayload(execution.arguments, debug.pretty);
1385
- return `[tool:mcp:request] ${scope} ${toolRef}#${execution.callId} arguments=${payload}`;
1386
- }
1387
- function formatToolExecutionResultDebugLine(execution, debug) {
1388
- const scope = [execution.provider, execution.model].filter(Boolean).join("/") || "unknown";
1389
- const toolRef = execution.clientId ? `${execution.clientId}:${execution.name ?? "unknown"}` : execution.name ?? "unknown";
1390
- if (execution.error) {
1391
- const payload2 = formatDebugPayload({ error: execution.error }, debug.pretty);
1392
- return `[tool:mcp:result:error] ${scope} ${toolRef}#${execution.callId} output=${payload2}`;
1393
- }
1394
- const payload = formatDebugPayload(execution.output, debug.pretty);
1395
- return `[tool:mcp:result:ok] ${scope} ${toolRef}#${execution.callId} output=${payload}`;
1396
- }
1397
- function formatDebugPayload(value, pretty) {
1398
- if (value === undefined) {
1399
- return "undefined";
1400
- }
1401
- try {
1402
- const serialized = JSON.stringify(value, null, pretty ? 2 : 0);
1403
- return serialized ?? "undefined";
1404
- } catch {
1405
- return String(value);
1406
- }
1407
- }
1408
1416
  function countNameCollisions(names) {
1409
1417
  const out = new Map;
1410
1418
  for (const name of names) {
@@ -1601,103 +1609,89 @@ function createOpenAICompatibleAdapter(options) {
1601
1609
  if (usesMCP) {
1602
1610
  return streamWithChatCompletionsWithMCP(options, fetcher, path, request, callbacks);
1603
1611
  }
1604
- const response = await fetcher(buildURL(options.baseURL, path), {
1605
- method: "POST",
1606
- headers: buildHeaders(options),
1607
- body: JSON.stringify(cleanUndefined({
1608
- ...options.defaultBody,
1609
- ...request.body,
1610
- model: options.model,
1611
- messages: buildMessages(request),
1612
- temperature: request.temperature,
1613
- reasoning_effort: toOpenAIReasoningEffort(request.reasoningEffort),
1614
- max_tokens: request.maxTokens,
1615
- stream: true
1616
- })),
1617
- signal: request.signal
1618
- });
1619
- if (!response.ok) {
1620
- const message = await response.text();
1621
- throw new Error(`HTTP ${response.status}: ${message}`);
1622
- }
1623
- callbacks.onStart?.();
1624
- let text = "";
1625
- let reasoning = "";
1626
- let usage;
1627
- let finishReason;
1628
- await consumeSSE(response, (data) => {
1629
- if (data === "[DONE]") {
1630
- return;
1631
- }
1632
- const json = safeJSONParse(data);
1633
- if (!isRecord2(json)) {
1634
- return;
1635
- }
1636
- const delta = pickAssistantDelta(json);
1637
- const reasoningDelta = pickAssistantReasoningDelta(json);
1638
- const chunkUsage = pickUsage(json);
1639
- const chunkFinishReason = pickFinishReason(json);
1640
- usage = preferLatestUsage(usage, chunkUsage);
1641
- if (chunkFinishReason) {
1642
- finishReason = chunkFinishReason;
1643
- }
1644
- if (delta) {
1645
- text += delta;
1646
- callbacks.onToken?.(delta);
1647
- }
1648
- if (reasoningDelta) {
1649
- reasoning += reasoningDelta;
1650
- }
1651
- if (delta || reasoningDelta || chunkUsage || chunkFinishReason) {
1652
- const chunk = {
1653
- textDelta: delta,
1654
- reasoningDelta: reasoningDelta || undefined,
1655
- raw: json,
1656
- usage: chunkUsage,
1657
- finishReason: chunkFinishReason
1658
- };
1659
- callbacks.onChunk?.(chunk);
1660
- }
1661
- });
1662
- const out = {
1663
- text,
1664
- reasoning: reasoning.length > 0 ? reasoning : undefined,
1665
- usage,
1666
- finishReason
1667
- };
1668
- callbacks.onComplete?.(out);
1669
- return out;
1612
+ return streamWithChatCompletionsPassThrough(options, fetcher, path, request, callbacks);
1670
1613
  },
1671
1614
  async embed(request) {
1672
- const body = cleanUndefined({
1673
- ...options.defaultBody,
1674
- ...request.body,
1675
- model: request.model ?? options.model,
1676
- input: request.input,
1677
- dimensions: request.dimensions,
1678
- encoding_format: "float"
1679
- });
1680
- const response = await fetcher(buildURL(options.baseURL, embeddingPath), {
1681
- method: "POST",
1682
- headers: buildHeaders(options),
1683
- body: JSON.stringify(body)
1684
- });
1685
- if (!response.ok) {
1686
- const message = await response.text();
1687
- throw new Error(`HTTP ${response.status}: ${message}`);
1688
- }
1689
- const json = await response.json();
1690
- const data = json.data;
1691
- if (!Array.isArray(data)) {
1692
- throw new Error("Unexpected embedding response: missing data array");
1693
- }
1694
- return {
1695
- embeddings: data.map((d) => isRecord2(d) && Array.isArray(d.embedding) ? d.embedding : []),
1696
- model: pickString(json.model) ?? body.model,
1697
- usage: pickUsage(json),
1698
- raw: json
1699
- };
1615
+ return embedOpenAI(options, fetcher, embeddingPath, request);
1616
+ }
1617
+ };
1618
+ }
1619
+ async function streamWithChatCompletionsPassThrough(options, fetcher, path, request, callbacks) {
1620
+ const response = await sendOpenAIRequest(options, fetcher, path, request, buildChatCompletionsBody(options, request, {
1621
+ messages: buildMessages(request),
1622
+ stream: true
1623
+ }));
1624
+ if (!response.ok) {
1625
+ const message = await response.text();
1626
+ throw new Error(`HTTP ${response.status}: ${message}`);
1627
+ }
1628
+ callbacks.onStart?.();
1629
+ let text = "";
1630
+ let reasoning = "";
1631
+ let usage;
1632
+ let finishReason;
1633
+ await consumeSSE(response, (data) => {
1634
+ if (data === "[DONE]") {
1635
+ return;
1636
+ }
1637
+ const json = safeJSONParse(data);
1638
+ if (!isRecord2(json)) {
1639
+ return;
1640
+ }
1641
+ const delta = pickAssistantDelta(json);
1642
+ const reasoningDelta = pickAssistantReasoningDelta(json);
1643
+ const chunkUsage = pickUsage(json);
1644
+ const chunkFinishReason = pickFinishReason(json);
1645
+ usage = preferLatestUsage(usage, chunkUsage);
1646
+ if (chunkFinishReason) {
1647
+ finishReason = chunkFinishReason;
1648
+ }
1649
+ if (delta) {
1650
+ text += delta;
1651
+ callbacks.onToken?.(delta);
1700
1652
  }
1653
+ if (reasoningDelta) {
1654
+ reasoning += reasoningDelta;
1655
+ }
1656
+ emitOpenAIStreamChunk(callbacks, undefined, json, delta, reasoningDelta, chunkUsage, chunkFinishReason);
1657
+ });
1658
+ const out = {
1659
+ text,
1660
+ reasoning: reasoning.length > 0 ? reasoning : undefined,
1661
+ usage,
1662
+ finishReason
1663
+ };
1664
+ callbacks.onComplete?.(out);
1665
+ return out;
1666
+ }
1667
+ async function embedOpenAI(options, fetcher, path, request) {
1668
+ const body = cleanUndefined({
1669
+ ...options.defaultBody,
1670
+ ...request.body,
1671
+ model: request.model ?? options.model,
1672
+ input: request.input,
1673
+ dimensions: request.dimensions,
1674
+ encoding_format: "float"
1675
+ });
1676
+ const response = await fetcher(buildURL(options.baseURL, path), {
1677
+ method: "POST",
1678
+ headers: buildHeaders(options),
1679
+ body: JSON.stringify(body)
1680
+ });
1681
+ if (!response.ok) {
1682
+ const message = await response.text();
1683
+ throw new Error(`HTTP ${response.status}: ${message}`);
1684
+ }
1685
+ const json = await response.json();
1686
+ const data = json.data;
1687
+ if (!Array.isArray(data)) {
1688
+ throw new Error("Unexpected embedding response: missing data array");
1689
+ }
1690
+ return {
1691
+ embeddings: data.map((d) => isRecord2(d) && Array.isArray(d.embedding) ? d.embedding : []),
1692
+ model: pickString(json.model) ?? body.model,
1693
+ usage: pickUsage(json),
1694
+ raw: json
1701
1695
  };
1702
1696
  }
1703
1697
  async function completeOpenAIRequest(options, fetcher, chatPath, responsesPath, request) {
@@ -1714,22 +1708,80 @@ async function completeOpenAIRequest(options, fetcher, chatPath, responsesPath,
1714
1708
  }
1715
1709
  return completeWithChatCompletionsPassThrough(options, fetcher, chatPath, request);
1716
1710
  }
1717
- async function completeWithChatCompletionsPassThrough(options, fetcher, path, request) {
1718
- const response = await fetcher(buildURL(options.baseURL, path), {
1711
+ function buildChatCompletionsBody(options, request, overrides) {
1712
+ return buildOpenAIRequestBody(options, request, "max_tokens", overrides);
1713
+ }
1714
+ function buildResponsesBody(options, request, overrides) {
1715
+ return buildOpenAIRequestBody(options, request, "max_output_tokens", overrides);
1716
+ }
1717
+ function buildOpenAIRequestBody(options, request, maxTokenKey, overrides) {
1718
+ return cleanUndefined({
1719
+ ...options.defaultBody,
1720
+ ...request.body,
1721
+ model: options.model,
1722
+ temperature: request.temperature,
1723
+ reasoning_effort: toOpenAIReasoningEffort(request.reasoningEffort),
1724
+ [maxTokenKey]: request.maxTokens,
1725
+ ...overrides
1726
+ });
1727
+ }
1728
+ function sendOpenAIRequest(options, fetcher, path, request, body) {
1729
+ return fetcher(buildURL(options.baseURL, path), {
1719
1730
  method: "POST",
1720
1731
  headers: buildHeaders(options),
1721
- body: JSON.stringify(cleanUndefined({
1722
- ...options.defaultBody,
1723
- ...request.body,
1724
- model: options.model,
1725
- messages: buildMessages(request),
1726
- temperature: request.temperature,
1727
- reasoning_effort: toOpenAIReasoningEffort(request.reasoningEffort),
1728
- max_tokens: request.maxTokens,
1729
- stream: false
1730
- })),
1732
+ body: JSON.stringify(body),
1731
1733
  signal: request.signal
1732
1734
  });
1735
+ }
1736
+ async function sendOpenAIJsonRequest(options, fetcher, path, request, body) {
1737
+ const response = await sendOpenAIRequest(options, fetcher, path, request, body);
1738
+ if (!response.ok) {
1739
+ const message = await response.text();
1740
+ throw new Error(`HTTP ${response.status}: ${message}`);
1741
+ }
1742
+ return await response.json();
1743
+ }
1744
+ function createResponsesMCPState(request) {
1745
+ return {
1746
+ input: buildResponsesInput(request),
1747
+ previousResponseId: pickString(isRecord2(request.body) ? request.body.previous_response_id : undefined),
1748
+ aggregatedUsage: undefined,
1749
+ finishReason: undefined,
1750
+ lastPayload: undefined,
1751
+ executedToolCalls: [],
1752
+ toolExecutions: [],
1753
+ reasoningBlocks: []
1754
+ };
1755
+ }
1756
+ function buildResponsesMCPResult(state, text, raw) {
1757
+ return {
1758
+ text,
1759
+ reasoning: joinReasoningBlocks(state.reasoningBlocks) || undefined,
1760
+ reasoningBlocks: state.reasoningBlocks.length > 0 ? state.reasoningBlocks : undefined,
1761
+ raw,
1762
+ usage: state.aggregatedUsage,
1763
+ finishReason: state.finishReason,
1764
+ toolCalls: state.executedToolCalls.length > 0 ? state.executedToolCalls : undefined,
1765
+ toolExecutions: state.toolExecutions.length > 0 ? state.toolExecutions : undefined
1766
+ };
1767
+ }
1768
+ function emitOpenAIStreamChunk(callbacks, round, raw, delta, reasoningDelta, usage, finishReason) {
1769
+ if (delta || reasoningDelta || usage || finishReason) {
1770
+ callbacks.onChunk?.({
1771
+ textDelta: delta,
1772
+ reasoningDelta: reasoningDelta || undefined,
1773
+ ...round !== undefined ? { turnIndex: round } : {},
1774
+ raw,
1775
+ usage,
1776
+ finishReason
1777
+ });
1778
+ }
1779
+ }
1780
+ async function completeWithChatCompletionsPassThrough(options, fetcher, path, request) {
1781
+ const response = await sendOpenAIRequest(options, fetcher, path, request, buildChatCompletionsBody(options, request, {
1782
+ messages: buildMessages(request),
1783
+ stream: false
1784
+ }));
1733
1785
  if (!response.ok) {
1734
1786
  const message = await response.text();
1735
1787
  throw new Error(`HTTP ${response.status}: ${message}`);
@@ -1777,44 +1829,32 @@ async function completeWithChatCompletionsWithMCP(options, fetcher, path, reques
1777
1829
  let lastPayload;
1778
1830
  const toolCalls = [];
1779
1831
  const toolExecutions = [];
1832
+ const reasoningBlocks = [];
1780
1833
  for (let round = 1;round <= maxToolRounds + 1; round += 1) {
1781
1834
  const mcpToolset = await resolveMCPToolset(request.mcpClients);
1782
1835
  const transportTools = toProviderFunctionTools(mcpToolset);
1783
- const response = await fetcher(buildURL(options.baseURL, path), {
1784
- method: "POST",
1785
- headers: buildHeaders(options),
1786
- body: JSON.stringify(cleanUndefined({
1787
- ...options.defaultBody,
1788
- ...request.body,
1789
- model: options.model,
1790
- messages,
1791
- temperature: request.temperature,
1792
- reasoning_effort: toOpenAIReasoningEffort(request.reasoningEffort),
1793
- max_tokens: request.maxTokens,
1794
- tools: transportTools,
1795
- tool_choice: request.toolChoice,
1796
- parallel_tool_calls: request.parallelToolCalls
1797
- })),
1798
- signal: request.signal
1799
- });
1800
- if (!response.ok) {
1801
- const message = await response.text();
1802
- throw new Error(`HTTP ${response.status}: ${message}`);
1803
- }
1804
- const payload = await response.json();
1836
+ const payload = await sendOpenAIJsonRequest(options, fetcher, path, request, buildChatCompletionsBody(options, request, {
1837
+ messages,
1838
+ tools: transportTools,
1839
+ tool_choice: request.toolChoice,
1840
+ parallel_tool_calls: request.parallelToolCalls
1841
+ }));
1805
1842
  lastPayload = payload;
1806
1843
  aggregatedUsage = mergeUsage(aggregatedUsage, pickUsage(payload));
1807
1844
  finishReason = pickFinishReason(payload);
1808
1845
  const assistantMessage = pickAssistantMessage(payload);
1809
1846
  const calledTools = pickChatToolCalls(payload);
1847
+ const roundReasoning = pickAssistantReasoning(payload);
1848
+ pushReasoningBlock(reasoningBlocks, round, roundReasoning);
1810
1849
  if (!assistantMessage) {
1811
1850
  throw new Error("No assistant message in OpenAI-compatible response.");
1812
1851
  }
1813
1852
  if (calledTools.length === 0) {
1814
- const reasoning = pickAssistantReasoning(payload);
1853
+ const reasoning = joinReasoningBlocks(reasoningBlocks) || undefined;
1815
1854
  return {
1816
1855
  text: pickAssistantText(payload),
1817
- reasoning: reasoning.length > 0 ? reasoning : undefined,
1856
+ reasoning,
1857
+ reasoningBlocks: reasoningBlocks.length > 0 ? reasoningBlocks : undefined,
1818
1858
  raw: payload,
1819
1859
  usage: aggregatedUsage,
1820
1860
  finishReason,
@@ -1842,10 +1882,8 @@ async function completeWithChatCompletionsWithMCP(options, fetcher, path, reques
1842
1882
  }
1843
1883
  return {
1844
1884
  text: pickAssistantText(lastPayload ?? {}),
1845
- reasoning: (() => {
1846
- const value = pickAssistantReasoning(lastPayload ?? {});
1847
- return value.length > 0 ? value : undefined;
1848
- })(),
1885
+ reasoning: joinReasoningBlocks(reasoningBlocks) || undefined,
1886
+ reasoningBlocks: reasoningBlocks.length > 0 ? reasoningBlocks : undefined,
1849
1887
  raw: lastPayload,
1850
1888
  usage: aggregatedUsage,
1851
1889
  finishReason,
@@ -1855,26 +1893,10 @@ async function completeWithChatCompletionsWithMCP(options, fetcher, path, reques
1855
1893
  }
1856
1894
  async function completeWithResponsesAPIPassThrough(options, fetcher, path, request) {
1857
1895
  const body = isRecord2(request.body) ? request.body : undefined;
1858
- const response = await fetcher(buildURL(options.baseURL, path), {
1859
- method: "POST",
1860
- headers: buildHeaders(options),
1861
- body: JSON.stringify(cleanUndefined({
1862
- ...options.defaultBody,
1863
- ...request.body,
1864
- model: options.model,
1865
- input: buildResponsesInput(request),
1866
- previous_response_id: pickString(body?.previous_response_id),
1867
- temperature: request.temperature,
1868
- reasoning_effort: toOpenAIReasoningEffort(request.reasoningEffort),
1869
- max_output_tokens: request.maxTokens
1870
- })),
1871
- signal: request.signal
1872
- });
1873
- if (!response.ok) {
1874
- const message = await response.text();
1875
- throw new Error(`HTTP ${response.status}: ${message}`);
1876
- }
1877
- const payload = await response.json();
1896
+ const payload = await sendOpenAIJsonRequest(options, fetcher, path, request, buildResponsesBody(options, request, {
1897
+ input: buildResponsesInput(request),
1898
+ previous_response_id: pickString(body?.previous_response_id)
1899
+ }));
1878
1900
  const toolCalls = pickResponsesToolCalls(payload);
1879
1901
  return {
1880
1902
  text: pickResponsesText(payload) || pickAssistantText(payload),
@@ -1886,54 +1908,26 @@ async function completeWithResponsesAPIPassThrough(options, fetcher, path, reque
1886
1908
  }
1887
1909
  async function completeWithResponsesAPIWithMCP(options, fetcher, path, request) {
1888
1910
  const maxToolRounds = normalizeMaxToolRounds(request.maxToolRounds ?? options.defaultMaxToolRounds);
1889
- let input = buildResponsesInput(request);
1890
- let previousResponseId = pickString(isRecord2(request.body) ? request.body.previous_response_id : undefined);
1891
- let aggregatedUsage;
1892
- let finishReason;
1893
- let lastPayload;
1894
- const executedToolCalls = [];
1895
- const toolExecutions = [];
1911
+ const state = createResponsesMCPState(request);
1896
1912
  for (let round = 1;round <= maxToolRounds + 1; round += 1) {
1897
1913
  const mcpToolset = await resolveMCPToolset(request.mcpClients);
1898
1914
  const transportTools = toResponsesTools(toProviderFunctionTools(mcpToolset));
1899
- const response = await fetcher(buildURL(options.baseURL, path), {
1900
- method: "POST",
1901
- headers: buildHeaders(options),
1902
- body: JSON.stringify(cleanUndefined({
1903
- ...options.defaultBody,
1904
- ...request.body,
1905
- model: options.model,
1906
- input,
1907
- previous_response_id: previousResponseId,
1908
- temperature: request.temperature,
1909
- reasoning_effort: toOpenAIReasoningEffort(request.reasoningEffort),
1910
- max_output_tokens: request.maxTokens,
1911
- tools: transportTools,
1912
- tool_choice: request.toolChoice,
1913
- parallel_tool_calls: request.parallelToolCalls
1914
- })),
1915
- signal: request.signal
1916
- });
1917
- if (!response.ok) {
1918
- const message = await response.text();
1919
- throw new Error(`HTTP ${response.status}: ${message}`);
1920
- }
1921
- const payload = await response.json();
1922
- lastPayload = payload;
1923
- aggregatedUsage = mergeUsage(aggregatedUsage, pickUsage(payload));
1924
- finishReason = pickResponsesFinishReason(payload) ?? finishReason;
1915
+ const payload = await sendOpenAIJsonRequest(options, fetcher, path, request, buildResponsesBody(options, request, {
1916
+ input: state.input,
1917
+ previous_response_id: state.previousResponseId,
1918
+ tools: transportTools,
1919
+ tool_choice: request.toolChoice,
1920
+ parallel_tool_calls: request.parallelToolCalls
1921
+ }));
1922
+ state.lastPayload = payload;
1923
+ state.aggregatedUsage = mergeUsage(state.aggregatedUsage, pickUsage(payload));
1924
+ state.finishReason = pickResponsesFinishReason(payload) ?? state.finishReason;
1925
+ pushReasoningBlock(state.reasoningBlocks, round, pickResponsesReasoning(payload));
1925
1926
  const providerToolCalls = pickResponsesToolCalls(payload);
1926
1927
  const functionCalls = providerToolCalls.filter((toolCall) => toolCall.type === "function" && typeof toolCall.id === "string" && typeof toolCall.name === "string");
1927
1928
  if (functionCalls.length === 0) {
1928
1929
  const text = pickResponsesText(payload) || pickAssistantText(payload);
1929
- return {
1930
- text,
1931
- raw: payload,
1932
- usage: aggregatedUsage,
1933
- finishReason,
1934
- toolCalls: executedToolCalls.length > 0 ? executedToolCalls : undefined,
1935
- toolExecutions: toolExecutions.length > 0 ? toolExecutions : undefined
1936
- };
1930
+ return buildResponsesMCPResult(state, text, payload);
1937
1931
  }
1938
1932
  if (round > maxToolRounds) {
1939
1933
  throw new Error(`Tool call loop exceeded maxToolRounds (${maxToolRounds}).`);
@@ -1944,23 +1938,16 @@ async function completeWithResponsesAPIWithMCP(options, fetcher, path, request)
1944
1938
  provider: "openai-compatible",
1945
1939
  model: options.model
1946
1940
  });
1947
- executedToolCalls.push(...outputs.map((entry) => entry.call));
1948
- toolExecutions.push(...outputs.map((entry) => entry.execution));
1949
- input = outputs.map((entry) => ({
1941
+ state.executedToolCalls.push(...outputs.map((entry) => entry.call));
1942
+ state.toolExecutions.push(...outputs.map((entry) => entry.execution));
1943
+ state.input = outputs.map((entry) => ({
1950
1944
  type: "function_call_output",
1951
1945
  call_id: entry.call.id,
1952
1946
  output: stringifyToolOutput(entry.call.error ? { error: entry.call.error } : entry.call.output)
1953
1947
  }));
1954
- previousResponseId = pickString(payload.id);
1948
+ state.previousResponseId = pickString(payload.id);
1955
1949
  }
1956
- return {
1957
- text: pickResponsesText(lastPayload ?? {}) || pickAssistantText(lastPayload ?? {}),
1958
- raw: lastPayload,
1959
- usage: aggregatedUsage,
1960
- finishReason,
1961
- toolCalls: executedToolCalls.length > 0 ? executedToolCalls : undefined,
1962
- toolExecutions: toolExecutions.length > 0 ? toolExecutions : undefined
1963
- };
1950
+ return buildResponsesMCPResult(state, pickResponsesText(state.lastPayload ?? {}) || pickAssistantText(state.lastPayload ?? {}), state.lastPayload);
1964
1951
  }
1965
1952
  async function streamWithChatCompletionsWithMCP(options, fetcher, path, request, callbacks) {
1966
1953
  const maxToolRounds = normalizeMaxToolRounds(request.maxToolRounds ?? options.defaultMaxToolRounds);
@@ -1970,30 +1957,19 @@ async function streamWithChatCompletionsWithMCP(options, fetcher, path, request,
1970
1957
  let lastPayload;
1971
1958
  const executedToolCalls = [];
1972
1959
  const toolExecutions = [];
1960
+ const reasoningBlocks = [];
1973
1961
  callbacks.onStart?.();
1974
1962
  let lastRoundText = "";
1975
- let lastRoundReasoning = "";
1976
1963
  for (let round = 1;round <= maxToolRounds + 1; round += 1) {
1977
1964
  const mcpToolset = await resolveMCPToolset(request.mcpClients);
1978
1965
  const transportTools = toProviderFunctionTools(mcpToolset);
1979
- const response = await fetcher(buildURL(options.baseURL, path), {
1980
- method: "POST",
1981
- headers: buildHeaders(options),
1982
- body: JSON.stringify(cleanUndefined({
1983
- ...options.defaultBody,
1984
- ...request.body,
1985
- model: options.model,
1986
- messages,
1987
- temperature: request.temperature,
1988
- reasoning_effort: toOpenAIReasoningEffort(request.reasoningEffort),
1989
- max_tokens: request.maxTokens,
1990
- tools: transportTools,
1991
- tool_choice: request.toolChoice,
1992
- parallel_tool_calls: request.parallelToolCalls,
1993
- stream: true
1994
- })),
1995
- signal: request.signal
1996
- });
1966
+ const response = await sendOpenAIRequest(options, fetcher, path, request, buildChatCompletionsBody(options, request, {
1967
+ messages,
1968
+ tools: transportTools,
1969
+ tool_choice: request.toolChoice,
1970
+ parallel_tool_calls: request.parallelToolCalls,
1971
+ stream: true
1972
+ }));
1997
1973
  if (!response.ok) {
1998
1974
  const message = await response.text();
1999
1975
  throw new Error(`HTTP ${response.status}: ${message}`);
@@ -2030,38 +2006,48 @@ async function streamWithChatCompletionsWithMCP(options, fetcher, path, request,
2030
2006
  roundReasoning += reasoningDelta;
2031
2007
  reasoningFieldName ??= pickAssistantReasoningDeltaFieldName(json);
2032
2008
  }
2033
- if (delta || reasoningDelta || chunkUsage || chunkFinishReason) {
2034
- const chunk = {
2035
- textDelta: delta,
2036
- reasoningDelta: reasoningDelta || undefined,
2037
- raw: json,
2038
- usage: chunkUsage,
2039
- finishReason: chunkFinishReason
2040
- };
2041
- callbacks.onChunk?.(chunk);
2042
- }
2009
+ emitOpenAIStreamChunk(callbacks, round, json, delta, reasoningDelta, chunkUsage, chunkFinishReason);
2043
2010
  });
2044
2011
  aggregatedUsage = mergeUsage(aggregatedUsage, roundUsage);
2045
2012
  if (roundFinishReason) {
2046
2013
  finishReason = roundFinishReason;
2047
2014
  }
2048
2015
  const calledTools = buildOpenAIStreamToolCalls(streamedToolCalls);
2016
+ pushReasoningBlock(reasoningBlocks, round, roundReasoning);
2017
+ request.onTurnTransition?.({
2018
+ turnIndex: round,
2019
+ kind: "reasoningComplete",
2020
+ reasoningText: roundReasoning
2021
+ });
2049
2022
  if (calledTools.length === 0) {
2050
2023
  const out2 = {
2051
2024
  text: roundText,
2052
- reasoning: roundReasoning.length > 0 ? roundReasoning : undefined,
2025
+ reasoning: joinReasoningBlocks(reasoningBlocks) || undefined,
2026
+ reasoningBlocks: reasoningBlocks.length > 0 ? reasoningBlocks : undefined,
2053
2027
  raw: lastPayload,
2054
2028
  usage: aggregatedUsage,
2055
2029
  finishReason,
2056
2030
  toolCalls: executedToolCalls.length > 0 ? executedToolCalls : undefined,
2057
2031
  toolExecutions: toolExecutions.length > 0 ? toolExecutions : undefined
2058
2032
  };
2033
+ request.onTurnTransition?.({ turnIndex: round, kind: "streamEnd" });
2059
2034
  callbacks.onComplete?.(out2);
2060
2035
  return out2;
2061
2036
  }
2062
2037
  if (round > maxToolRounds) {
2063
2038
  throw new Error(`Tool call loop exceeded maxToolRounds (${maxToolRounds}).`);
2064
2039
  }
2040
+ request.onTurnTransition?.({
2041
+ turnIndex: round,
2042
+ kind: "toolCallsEmit",
2043
+ toolCalls: calledTools
2044
+ });
2045
+ callbacks.onChunk?.({
2046
+ textDelta: "",
2047
+ turnIndex: round,
2048
+ toolCalls: calledTools,
2049
+ finishReason: roundFinishReason
2050
+ });
2065
2051
  const outputs = await executeMCPToolCalls(calledTools, mcpToolset, {
2066
2052
  round,
2067
2053
  request,
@@ -2070,8 +2056,8 @@ async function streamWithChatCompletionsWithMCP(options, fetcher, path, request,
2070
2056
  });
2071
2057
  executedToolCalls.push(...outputs.map((entry) => entry.call));
2072
2058
  toolExecutions.push(...outputs.map((entry) => entry.execution));
2059
+ request.onTurnTransition?.({ turnIndex: round, kind: "toolResultsReceived" });
2073
2060
  lastRoundText = roundText;
2074
- lastRoundReasoning = roundReasoning;
2075
2061
  const assistantMessage = buildOpenAIAssistantToolMessage(roundText, calledTools, {
2076
2062
  reasoning: roundReasoning,
2077
2063
  reasoningFieldName
@@ -2085,34 +2071,25 @@ async function streamWithChatCompletionsWithMCP(options, fetcher, path, request,
2085
2071
  }
2086
2072
  const out = {
2087
2073
  text: lastRoundText,
2088
- reasoning: lastRoundReasoning.length > 0 ? lastRoundReasoning : undefined,
2074
+ reasoning: joinReasoningBlocks(reasoningBlocks) || undefined,
2075
+ reasoningBlocks: reasoningBlocks.length > 0 ? reasoningBlocks : undefined,
2089
2076
  raw: lastPayload,
2090
2077
  usage: aggregatedUsage,
2091
2078
  finishReason,
2092
2079
  toolCalls: executedToolCalls.length > 0 ? executedToolCalls : undefined,
2093
2080
  toolExecutions: toolExecutions.length > 0 ? toolExecutions : undefined
2094
2081
  };
2082
+ request.onTurnTransition?.({ turnIndex: maxToolRounds + 1, kind: "streamEnd" });
2095
2083
  callbacks.onComplete?.(out);
2096
2084
  return out;
2097
2085
  }
2098
2086
  async function streamWithResponsesAPIPassThrough(options, fetcher, path, request, callbacks) {
2099
2087
  const body = isRecord2(request.body) ? request.body : undefined;
2100
- const response = await fetcher(buildURL(options.baseURL, path), {
2101
- method: "POST",
2102
- headers: buildHeaders(options),
2103
- body: JSON.stringify(cleanUndefined({
2104
- ...options.defaultBody,
2105
- ...request.body,
2106
- model: options.model,
2107
- input: buildResponsesInput(request),
2108
- previous_response_id: pickString(body?.previous_response_id),
2109
- temperature: request.temperature,
2110
- reasoning_effort: toOpenAIReasoningEffort(request.reasoningEffort),
2111
- max_output_tokens: request.maxTokens,
2112
- stream: true
2113
- })),
2114
- signal: request.signal
2115
- });
2088
+ const response = await sendOpenAIRequest(options, fetcher, path, request, buildResponsesBody(options, request, {
2089
+ input: buildResponsesInput(request),
2090
+ previous_response_id: pickString(body?.previous_response_id),
2091
+ stream: true
2092
+ }));
2116
2093
  if (!response.ok) {
2117
2094
  const message = await response.text();
2118
2095
  throw new Error(`HTTP ${response.status}: ${message}`);
@@ -2145,15 +2122,7 @@ async function streamWithResponsesAPIPassThrough(options, fetcher, path, request
2145
2122
  text += delta;
2146
2123
  callbacks.onToken?.(delta);
2147
2124
  }
2148
- if (delta || chunkUsage || chunkFinishReason) {
2149
- const chunk = {
2150
- textDelta: delta,
2151
- raw: json,
2152
- usage: chunkUsage,
2153
- finishReason: chunkFinishReason
2154
- };
2155
- callbacks.onChunk?.(chunk);
2156
- }
2125
+ emitOpenAIStreamChunk(callbacks, undefined, json, delta, "", chunkUsage, chunkFinishReason);
2157
2126
  });
2158
2127
  const finalPayload = lastPayload ?? {};
2159
2128
  const out = {
@@ -2167,41 +2136,25 @@ async function streamWithResponsesAPIPassThrough(options, fetcher, path, request
2167
2136
  }
2168
2137
  async function streamWithResponsesAPIWithMCP(options, fetcher, path, request, callbacks) {
2169
2138
  const maxToolRounds = normalizeMaxToolRounds(request.maxToolRounds ?? options.defaultMaxToolRounds);
2170
- let input = buildResponsesInput(request);
2171
- let previousResponseId = pickString(isRecord2(request.body) ? request.body.previous_response_id : undefined);
2172
- let aggregatedUsage;
2173
- let finishReason;
2174
- let lastPayload;
2175
- const executedToolCalls = [];
2176
- const toolExecutions = [];
2139
+ const state = createResponsesMCPState(request);
2177
2140
  callbacks.onStart?.();
2178
2141
  for (let round = 1;round <= maxToolRounds + 1; round += 1) {
2179
2142
  const mcpToolset = await resolveMCPToolset(request.mcpClients);
2180
2143
  const transportTools = toResponsesTools(toProviderFunctionTools(mcpToolset));
2181
- const response = await fetcher(buildURL(options.baseURL, path), {
2182
- method: "POST",
2183
- headers: buildHeaders(options),
2184
- body: JSON.stringify(cleanUndefined({
2185
- ...options.defaultBody,
2186
- ...request.body,
2187
- model: options.model,
2188
- input,
2189
- previous_response_id: previousResponseId,
2190
- temperature: request.temperature,
2191
- reasoning_effort: toOpenAIReasoningEffort(request.reasoningEffort),
2192
- max_output_tokens: request.maxTokens,
2193
- tools: transportTools,
2194
- tool_choice: request.toolChoice,
2195
- parallel_tool_calls: request.parallelToolCalls,
2196
- stream: true
2197
- })),
2198
- signal: request.signal
2199
- });
2144
+ const response = await sendOpenAIRequest(options, fetcher, path, request, buildResponsesBody(options, request, {
2145
+ input: state.input,
2146
+ previous_response_id: state.previousResponseId,
2147
+ tools: transportTools,
2148
+ tool_choice: request.toolChoice,
2149
+ parallel_tool_calls: request.parallelToolCalls,
2150
+ stream: true
2151
+ }));
2200
2152
  if (!response.ok) {
2201
2153
  const message = await response.text();
2202
2154
  throw new Error(`HTTP ${response.status}: ${message}`);
2203
2155
  }
2204
2156
  let roundText = "";
2157
+ let roundReasoning = "";
2205
2158
  let roundUsage;
2206
2159
  let roundFinishReason;
2207
2160
  let roundPayload;
@@ -2217,9 +2170,10 @@ async function streamWithResponsesAPIWithMCP(options, fetcher, path, request, ca
2217
2170
  const payload = pickResponsesStreamPayload(json);
2218
2171
  if (payload) {
2219
2172
  roundPayload = payload;
2220
- lastPayload = payload;
2173
+ state.lastPayload = payload;
2221
2174
  }
2222
2175
  const delta = pickResponsesStreamTextDelta(json);
2176
+ const reasoningDelta = pickResponsesStreamReasoningDelta(json);
2223
2177
  const chunkUsage = pickResponsesStreamUsage(json);
2224
2178
  const chunkFinishReason = pickResponsesStreamFinishReason(json);
2225
2179
  collectResponsesStreamToolCalls(json, streamedToolCalls);
@@ -2231,66 +2185,70 @@ async function streamWithResponsesAPIWithMCP(options, fetcher, path, request, ca
2231
2185
  roundText += delta;
2232
2186
  callbacks.onToken?.(delta);
2233
2187
  }
2234
- if (delta || chunkUsage || chunkFinishReason) {
2235
- const chunk = {
2236
- textDelta: delta,
2237
- raw: json,
2238
- usage: chunkUsage,
2239
- finishReason: chunkFinishReason
2240
- };
2241
- callbacks.onChunk?.(chunk);
2188
+ if (reasoningDelta) {
2189
+ roundReasoning += reasoningDelta;
2242
2190
  }
2191
+ emitOpenAIStreamChunk(callbacks, round, json, delta, reasoningDelta, chunkUsage, chunkFinishReason);
2243
2192
  });
2244
2193
  const resolvedRoundUsage = preferLatestUsage(roundUsage, roundPayload ? pickUsage(roundPayload) : undefined);
2245
- aggregatedUsage = mergeUsage(aggregatedUsage, resolvedRoundUsage);
2194
+ state.aggregatedUsage = mergeUsage(state.aggregatedUsage, resolvedRoundUsage);
2246
2195
  if (roundFinishReason) {
2247
- finishReason = roundFinishReason;
2196
+ state.finishReason = roundFinishReason;
2248
2197
  } else if (roundPayload) {
2249
- finishReason = pickResponsesFinishReason(roundPayload) ?? finishReason;
2198
+ state.finishReason = pickResponsesFinishReason(roundPayload) ?? state.finishReason;
2250
2199
  }
2251
2200
  const payloadToolCalls = roundPayload ? pickResponsesToolCalls(roundPayload) : [];
2201
+ if (roundPayload && roundReasoning.length === 0) {
2202
+ roundReasoning = pickResponsesReasoning(roundPayload);
2203
+ }
2252
2204
  const streamedCalls = buildResponsesStreamToolCalls(streamedToolCalls);
2253
2205
  const providerToolCalls = payloadToolCalls.length > 0 ? payloadToolCalls : streamedCalls;
2254
2206
  const functionCalls = providerToolCalls.filter((toolCall) => toolCall.type === "function" && typeof toolCall.id === "string" && typeof toolCall.name === "string");
2207
+ pushReasoningBlock(state.reasoningBlocks, round, roundReasoning);
2208
+ request.onTurnTransition?.({
2209
+ turnIndex: round,
2210
+ kind: "reasoningComplete",
2211
+ reasoningText: roundReasoning
2212
+ });
2255
2213
  if (functionCalls.length === 0) {
2256
2214
  const finalText = roundText.length > 0 ? roundText : roundPayload ? pickResponsesText(roundPayload) || pickAssistantText(roundPayload) : "";
2257
- const out2 = {
2258
- text: finalText,
2259
- raw: roundPayload ?? lastPayload,
2260
- usage: aggregatedUsage,
2261
- finishReason,
2262
- toolCalls: executedToolCalls.length > 0 ? executedToolCalls : undefined,
2263
- toolExecutions: toolExecutions.length > 0 ? toolExecutions : undefined
2264
- };
2215
+ const out2 = buildResponsesMCPResult(state, finalText, roundPayload ?? state.lastPayload);
2216
+ request.onTurnTransition?.({ turnIndex: round, kind: "streamEnd" });
2265
2217
  callbacks.onComplete?.(out2);
2266
2218
  return out2;
2267
2219
  }
2268
2220
  if (round > maxToolRounds) {
2269
2221
  throw new Error(`Tool call loop exceeded maxToolRounds (${maxToolRounds}).`);
2270
2222
  }
2223
+ request.onTurnTransition?.({
2224
+ turnIndex: round,
2225
+ kind: "toolCallsEmit",
2226
+ toolCalls: functionCalls
2227
+ });
2228
+ callbacks.onChunk?.({
2229
+ textDelta: "",
2230
+ turnIndex: round,
2231
+ toolCalls: functionCalls,
2232
+ finishReason: roundFinishReason
2233
+ });
2271
2234
  const outputs = await executeMCPToolCalls(functionCalls, mcpToolset, {
2272
2235
  round,
2273
2236
  request,
2274
2237
  provider: "openai-compatible",
2275
2238
  model: options.model
2276
2239
  });
2277
- executedToolCalls.push(...outputs.map((entry) => entry.call));
2278
- toolExecutions.push(...outputs.map((entry) => entry.execution));
2279
- input = outputs.map((entry) => ({
2240
+ state.executedToolCalls.push(...outputs.map((entry) => entry.call));
2241
+ state.toolExecutions.push(...outputs.map((entry) => entry.execution));
2242
+ request.onTurnTransition?.({ turnIndex: round, kind: "toolResultsReceived" });
2243
+ state.input = outputs.map((entry) => ({
2280
2244
  type: "function_call_output",
2281
2245
  call_id: entry.call.id,
2282
2246
  output: stringifyToolOutput(entry.call.error ? { error: entry.call.error } : entry.call.output)
2283
2247
  }));
2284
- previousResponseId = pickString(roundPayload?.id);
2248
+ state.previousResponseId = pickString(roundPayload?.id);
2285
2249
  }
2286
- const out = {
2287
- text: pickResponsesText(lastPayload ?? {}) || pickAssistantText(lastPayload ?? {}),
2288
- raw: lastPayload,
2289
- usage: aggregatedUsage,
2290
- finishReason,
2291
- toolCalls: executedToolCalls.length > 0 ? executedToolCalls : undefined,
2292
- toolExecutions: toolExecutions.length > 0 ? toolExecutions : undefined
2293
- };
2250
+ const out = buildResponsesMCPResult(state, pickResponsesText(state.lastPayload ?? {}) || pickAssistantText(state.lastPayload ?? {}), state.lastPayload);
2251
+ request.onTurnTransition?.({ turnIndex: maxToolRounds + 1, kind: "streamEnd" });
2294
2252
  callbacks.onComplete?.(out);
2295
2253
  return out;
2296
2254
  }
@@ -2442,7 +2400,7 @@ function pickAssistantDelta(payload) {
2442
2400
  if (!isRecord2(delta)) {
2443
2401
  return "";
2444
2402
  }
2445
- return pickTextFromOpenAIContent(delta.content);
2403
+ return pickTextLike(delta.content);
2446
2404
  }
2447
2405
  function pickAssistantReasoning(payload) {
2448
2406
  const message = pickAssistantMessage(payload);
@@ -2575,6 +2533,20 @@ function pickResponsesStreamTextDelta(payload) {
2575
2533
  }
2576
2534
  return "";
2577
2535
  }
2536
+ function pickResponsesStreamReasoningDelta(payload) {
2537
+ const eventType = pickString(payload.type) ?? "";
2538
+ if (!eventType.includes("reasoning") && !eventType.includes("thinking")) {
2539
+ return "";
2540
+ }
2541
+ const direct = pickString(payload.delta);
2542
+ if (direct) {
2543
+ return direct;
2544
+ }
2545
+ if (isRecord2(payload.delta)) {
2546
+ return pickReasoningText(payload.delta) || pickString(payload.delta.text) || pickString(payload.delta.summary_text) || "";
2547
+ }
2548
+ return "";
2549
+ }
2578
2550
  function pickResponsesStreamUsage(payload) {
2579
2551
  const direct = pickUsage(payload);
2580
2552
  if (direct) {
@@ -2709,10 +2681,34 @@ function pickResponsesText(payload) {
2709
2681
  }).join("");
2710
2682
  }).join("");
2711
2683
  }
2684
+ function pickResponsesReasoning(payload) {
2685
+ const direct = pickReasoningText(payload);
2686
+ if (direct) {
2687
+ return direct;
2688
+ }
2689
+ const output = payload.output;
2690
+ if (!Array.isArray(output)) {
2691
+ return "";
2692
+ }
2693
+ return output.map((item) => {
2694
+ if (!isRecord2(item)) {
2695
+ return "";
2696
+ }
2697
+ const itemReasoning = pickReasoningText(item);
2698
+ if (itemReasoning) {
2699
+ return itemReasoning;
2700
+ }
2701
+ const itemType = pickString(item.type) ?? "";
2702
+ if ((itemType.includes("reasoning") || itemType.includes("thinking")) && Array.isArray(item.content)) {
2703
+ return item.content.map((part) => isRecord2(part) ? pickTextLike(part) : "").join("");
2704
+ }
2705
+ return "";
2706
+ }).join("");
2707
+ }
2712
2708
  function pickAssistantText(payload) {
2713
2709
  const message = pickAssistantMessage(payload);
2714
2710
  if (message) {
2715
- const text = pickTextFromOpenAIContent(message.content);
2711
+ const text = pickTextLike(message.content);
2716
2712
  if (text.length > 0) {
2717
2713
  return text;
2718
2714
  }
@@ -2729,8 +2725,17 @@ function pickAssistantText(payload) {
2729
2725
  function pickReasoningText(value) {
2730
2726
  return pickTextLike(value.reasoning) || pickTextLike(value.reasoning_content);
2731
2727
  }
2732
- function pickTextFromOpenAIContent(value) {
2733
- return pickTextLike(value);
2728
+ function pushReasoningBlock(blocks, turnIndex, text) {
2729
+ const clean = text?.replace(/<\/?think\s*>/gi, "").trim();
2730
+ if (!clean) {
2731
+ return;
2732
+ }
2733
+ blocks.push({ turnIndex, text: clean });
2734
+ }
2735
+ function joinReasoningBlocks(blocks) {
2736
+ return blocks.map((block) => block.text).filter(Boolean).join(`
2737
+
2738
+ `);
2734
2739
  }
2735
2740
  function pickTextLike(value) {
2736
2741
  if (typeof value === "string") {
@@ -2805,83 +2810,81 @@ function createAnthropicCompatibleAdapter(options) {
2805
2810
  if (hasMCPClients(request.mcpClients)) {
2806
2811
  return streamWithMCPToolLoop(options, fetcher, path, request, callbacks);
2807
2812
  }
2808
- const input = resolveAnthropicInput(request);
2809
- const response = await fetcher(buildURL(options.baseURL, path), {
2810
- method: "POST",
2811
- headers: buildHeaders2(options),
2812
- body: JSON.stringify(buildAnthropicRequestBody(options, request, {
2813
- ...options.defaultBody,
2814
- ...request.body,
2815
- model: options.model,
2816
- system: input.systemPrompt,
2817
- messages: input.messages,
2818
- temperature: request.temperature,
2819
- stream: true
2820
- })),
2821
- signal: request.signal
2822
- });
2823
- if (!response.ok) {
2824
- const message = await response.text();
2825
- throw new Error(`HTTP ${response.status}: ${message}`);
2826
- }
2827
- callbacks.onStart?.();
2828
- let text = "";
2829
- let usage;
2830
- let finishReason;
2831
- await consumeSSE(response, (data) => {
2832
- if (data === "[DONE]") {
2833
- return;
2834
- }
2835
- const json = safeJSONParse(data);
2836
- if (!isRecord2(json)) {
2837
- return;
2838
- }
2839
- const delta = pickAnthropicDelta(json);
2840
- const chunkUsage = pickUsage2(json);
2841
- const chunkFinishReason = pickFinishReason2(json);
2842
- usage = preferLatestUsage(usage, chunkUsage);
2843
- if (chunkFinishReason) {
2844
- finishReason = chunkFinishReason;
2845
- }
2846
- if (delta) {
2847
- text += delta;
2848
- callbacks.onToken?.(delta);
2849
- }
2850
- if (delta || chunkUsage || chunkFinishReason) {
2851
- const chunk = {
2852
- textDelta: delta,
2853
- raw: json,
2854
- usage: chunkUsage,
2855
- finishReason: chunkFinishReason
2856
- };
2857
- callbacks.onChunk?.(chunk);
2858
- }
2859
- });
2860
- const out = { text, usage, finishReason };
2861
- callbacks.onComplete?.(out);
2862
- return out;
2813
+ return streamPassThrough(options, fetcher, path, request, callbacks);
2863
2814
  },
2864
2815
  async embed() {
2865
2816
  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.");
2866
2817
  }
2867
2818
  };
2868
2819
  }
2869
- async function completePassThrough(options, fetcher, path, request) {
2820
+ async function streamPassThrough(options, fetcher, path, request, callbacks) {
2870
2821
  const input = resolveAnthropicInput(request);
2871
- const response = await fetcher(buildURL(options.baseURL, path), {
2822
+ const response = await sendAnthropicMessage(options, fetcher, path, request, {
2823
+ system: input.systemPrompt,
2824
+ messages: input.messages,
2825
+ stream: true
2826
+ });
2827
+ if (!response.ok) {
2828
+ const message = await response.text();
2829
+ throw new Error(`HTTP ${response.status}: ${message}`);
2830
+ }
2831
+ callbacks.onStart?.();
2832
+ let text = "";
2833
+ let usage;
2834
+ let finishReason;
2835
+ await consumeSSE(response, (data) => {
2836
+ if (data === "[DONE]") {
2837
+ return;
2838
+ }
2839
+ const json = safeJSONParse(data);
2840
+ if (!isRecord2(json)) {
2841
+ return;
2842
+ }
2843
+ const delta = pickAnthropicDelta(json);
2844
+ const chunkUsage = pickUsage2(json);
2845
+ const chunkFinishReason = pickFinishReason2(json);
2846
+ usage = preferLatestUsage(usage, chunkUsage);
2847
+ if (chunkFinishReason) {
2848
+ finishReason = chunkFinishReason;
2849
+ }
2850
+ if (delta) {
2851
+ text += delta;
2852
+ callbacks.onToken?.(delta);
2853
+ }
2854
+ if (delta || chunkUsage || chunkFinishReason) {
2855
+ callbacks.onChunk?.({
2856
+ textDelta: delta,
2857
+ raw: json,
2858
+ usage: chunkUsage,
2859
+ finishReason: chunkFinishReason
2860
+ });
2861
+ }
2862
+ });
2863
+ const out = { text, usage, finishReason };
2864
+ callbacks.onComplete?.(out);
2865
+ return out;
2866
+ }
2867
+ function sendAnthropicMessage(options, fetcher, path, request, body) {
2868
+ return fetcher(buildURL(options.baseURL, path), {
2872
2869
  method: "POST",
2873
2870
  headers: buildHeaders2(options),
2874
2871
  body: JSON.stringify(buildAnthropicRequestBody(options, request, {
2875
2872
  ...options.defaultBody,
2876
2873
  ...request.body,
2877
2874
  model: options.model,
2878
- system: input.systemPrompt,
2879
- messages: input.messages,
2880
2875
  temperature: request.temperature,
2881
- stream: false
2876
+ ...body
2882
2877
  })),
2883
2878
  signal: request.signal
2884
2879
  });
2880
+ }
2881
+ async function completePassThrough(options, fetcher, path, request) {
2882
+ const input = resolveAnthropicInput(request);
2883
+ const response = await sendAnthropicMessage(options, fetcher, path, request, {
2884
+ system: input.systemPrompt,
2885
+ messages: input.messages,
2886
+ stream: false
2887
+ });
2885
2888
  if (!response.ok) {
2886
2889
  const message = await response.text();
2887
2890
  throw new Error(`HTTP ${response.status}: ${message}`);
@@ -2909,24 +2912,16 @@ async function completeWithMCPToolLoop(options, fetcher, path, request) {
2909
2912
  let lastPayload;
2910
2913
  const toolCalls = [];
2911
2914
  const toolExecutions = [];
2915
+ const reasoningBlocks = [];
2912
2916
  for (let round = 1;round <= maxToolRounds + 1; round += 1) {
2913
2917
  const mcpToolset = await resolveMCPToolset(request.mcpClients);
2914
2918
  const tools = toAnthropicTools(toProviderFunctionTools(mcpToolset));
2915
- const response = await fetcher(buildURL(options.baseURL, path), {
2916
- method: "POST",
2917
- headers: buildHeaders2(options),
2918
- body: JSON.stringify(buildAnthropicRequestBody(options, request, {
2919
- ...options.defaultBody,
2920
- ...request.body,
2921
- model: options.model,
2922
- system: input.systemPrompt,
2923
- messages,
2924
- temperature: request.temperature,
2925
- tools,
2926
- tool_choice: toAnthropicToolChoice(request.toolChoice),
2927
- stream: false
2928
- })),
2929
- signal: request.signal
2919
+ const response = await sendAnthropicMessage(options, fetcher, path, request, {
2920
+ system: input.systemPrompt,
2921
+ messages,
2922
+ tools,
2923
+ tool_choice: toAnthropicToolChoice(request.toolChoice),
2924
+ stream: false
2930
2925
  });
2931
2926
  if (!response.ok) {
2932
2927
  const message = await response.text();
@@ -2938,9 +2933,12 @@ async function completeWithMCPToolLoop(options, fetcher, path, request) {
2938
2933
  finishReason = pickFinishReason2(payload);
2939
2934
  const content = Array.isArray(payload.content) ? payload.content : [];
2940
2935
  const calledTools = pickAnthropicToolCalls(payload).filter((call) => call.type === "function");
2936
+ pushReasoningBlock2(reasoningBlocks, round, extractAnthropicReasoning(payload));
2941
2937
  if (calledTools.length === 0) {
2942
2938
  return {
2943
2939
  text: extractAnthropicText(payload),
2940
+ reasoning: joinReasoningBlocks2(reasoningBlocks) || undefined,
2941
+ reasoningBlocks: reasoningBlocks.length > 0 ? reasoningBlocks : undefined,
2944
2942
  raw: payload,
2945
2943
  usage: aggregatedUsage,
2946
2944
  finishReason,
@@ -2976,6 +2974,8 @@ async function completeWithMCPToolLoop(options, fetcher, path, request) {
2976
2974
  }
2977
2975
  return {
2978
2976
  text: extractAnthropicText(lastPayload ?? {}),
2977
+ reasoning: joinReasoningBlocks2(reasoningBlocks) || undefined,
2978
+ reasoningBlocks: reasoningBlocks.length > 0 ? reasoningBlocks : undefined,
2979
2979
  raw: lastPayload,
2980
2980
  usage: aggregatedUsage,
2981
2981
  finishReason,
@@ -2992,31 +2992,24 @@ async function streamWithMCPToolLoop(options, fetcher, path, request, callbacks)
2992
2992
  let lastPayload;
2993
2993
  const toolCalls = [];
2994
2994
  const toolExecutions = [];
2995
+ const reasoningBlocks = [];
2995
2996
  callbacks.onStart?.();
2996
2997
  for (let round = 1;round <= maxToolRounds + 1; round += 1) {
2997
2998
  const mcpToolset = await resolveMCPToolset(request.mcpClients);
2998
2999
  const tools = toAnthropicTools(toProviderFunctionTools(mcpToolset));
2999
- const response = await fetcher(buildURL(options.baseURL, path), {
3000
- method: "POST",
3001
- headers: buildHeaders2(options),
3002
- body: JSON.stringify(buildAnthropicRequestBody(options, request, {
3003
- ...options.defaultBody,
3004
- ...request.body,
3005
- model: options.model,
3006
- system: input.systemPrompt,
3007
- messages,
3008
- temperature: request.temperature,
3009
- tools,
3010
- tool_choice: toAnthropicToolChoice(request.toolChoice),
3011
- stream: true
3012
- })),
3013
- signal: request.signal
3000
+ const response = await sendAnthropicMessage(options, fetcher, path, request, {
3001
+ system: input.systemPrompt,
3002
+ messages,
3003
+ tools,
3004
+ tool_choice: toAnthropicToolChoice(request.toolChoice),
3005
+ stream: true
3014
3006
  });
3015
3007
  if (!response.ok) {
3016
3008
  const message = await response.text();
3017
3009
  throw new Error(`HTTP ${response.status}: ${message}`);
3018
3010
  }
3019
3011
  let roundText = "";
3012
+ let roundReasoning = "";
3020
3013
  let roundUsage;
3021
3014
  let roundFinishReason;
3022
3015
  const streamedToolCalls = new Map;
@@ -3030,6 +3023,7 @@ async function streamWithMCPToolLoop(options, fetcher, path, request, callbacks)
3030
3023
  }
3031
3024
  lastPayload = json;
3032
3025
  const delta = pickAnthropicDelta(json);
3026
+ const reasoningDelta = pickAnthropicReasoningDelta(json);
3033
3027
  const chunkUsage = pickUsage2(json);
3034
3028
  const chunkFinishReason = pickFinishReason2(json);
3035
3029
  collectAnthropicStreamToolCalls(json, streamedToolCalls);
@@ -3041,9 +3035,14 @@ async function streamWithMCPToolLoop(options, fetcher, path, request, callbacks)
3041
3035
  roundText += delta;
3042
3036
  callbacks.onToken?.(delta);
3043
3037
  }
3044
- if (delta || chunkUsage || chunkFinishReason) {
3038
+ if (reasoningDelta) {
3039
+ roundReasoning += reasoningDelta;
3040
+ }
3041
+ if (delta || reasoningDelta || chunkUsage || chunkFinishReason) {
3045
3042
  const chunk = {
3046
3043
  textDelta: delta,
3044
+ reasoningDelta: reasoningDelta || undefined,
3045
+ turnIndex: round,
3047
3046
  raw: json,
3048
3047
  usage: chunkUsage,
3049
3048
  finishReason: chunkFinishReason
@@ -3056,21 +3055,41 @@ async function streamWithMCPToolLoop(options, fetcher, path, request, callbacks)
3056
3055
  finishReason = roundFinishReason;
3057
3056
  }
3058
3057
  const calledTools = buildAnthropicStreamToolCalls(streamedToolCalls);
3058
+ pushReasoningBlock2(reasoningBlocks, round, roundReasoning);
3059
+ request.onTurnTransition?.({
3060
+ turnIndex: round,
3061
+ kind: "reasoningComplete",
3062
+ reasoningText: roundReasoning
3063
+ });
3059
3064
  if (calledTools.length === 0) {
3060
3065
  const out2 = {
3061
3066
  text: roundText,
3067
+ reasoning: joinReasoningBlocks2(reasoningBlocks) || undefined,
3068
+ reasoningBlocks: reasoningBlocks.length > 0 ? reasoningBlocks : undefined,
3062
3069
  raw: lastPayload,
3063
3070
  usage: aggregatedUsage,
3064
3071
  finishReason,
3065
3072
  toolCalls: toolCalls.length > 0 ? toolCalls : undefined,
3066
3073
  toolExecutions: toolExecutions.length > 0 ? toolExecutions : undefined
3067
3074
  };
3075
+ request.onTurnTransition?.({ turnIndex: round, kind: "streamEnd" });
3068
3076
  callbacks.onComplete?.(out2);
3069
3077
  return out2;
3070
3078
  }
3071
3079
  if (round > maxToolRounds) {
3072
3080
  throw new Error(`Tool call loop exceeded maxToolRounds (${maxToolRounds}).`);
3073
3081
  }
3082
+ request.onTurnTransition?.({
3083
+ turnIndex: round,
3084
+ kind: "toolCallsEmit",
3085
+ toolCalls: calledTools
3086
+ });
3087
+ callbacks.onChunk?.({
3088
+ textDelta: "",
3089
+ turnIndex: round,
3090
+ toolCalls: calledTools,
3091
+ finishReason: roundFinishReason
3092
+ });
3074
3093
  const toolResultContent = [];
3075
3094
  const outputs = await executeMCPToolCalls(calledTools, mcpToolset, {
3076
3095
  round,
@@ -3080,6 +3099,7 @@ async function streamWithMCPToolLoop(options, fetcher, path, request, callbacks)
3080
3099
  });
3081
3100
  toolCalls.push(...outputs.map((entry) => entry.call));
3082
3101
  toolExecutions.push(...outputs.map((entry) => entry.execution));
3102
+ request.onTurnTransition?.({ turnIndex: round, kind: "toolResultsReceived" });
3083
3103
  for (const entry of outputs) {
3084
3104
  toolResultContent.push({
3085
3105
  type: "tool_result",
@@ -3096,12 +3116,15 @@ async function streamWithMCPToolLoop(options, fetcher, path, request, callbacks)
3096
3116
  }
3097
3117
  const out = {
3098
3118
  text: "",
3119
+ reasoning: joinReasoningBlocks2(reasoningBlocks) || undefined,
3120
+ reasoningBlocks: reasoningBlocks.length > 0 ? reasoningBlocks : undefined,
3099
3121
  raw: lastPayload,
3100
3122
  usage: aggregatedUsage,
3101
3123
  finishReason,
3102
3124
  toolCalls: toolCalls.length > 0 ? toolCalls : undefined,
3103
3125
  toolExecutions: toolExecutions.length > 0 ? toolExecutions : undefined
3104
3126
  };
3127
+ request.onTurnTransition?.({ turnIndex: maxToolRounds + 1, kind: "streamEnd" });
3105
3128
  callbacks.onComplete?.(out);
3106
3129
  return out;
3107
3130
  }
@@ -3222,10 +3245,26 @@ function extractAnthropicText(payload) {
3222
3245
  return typeof text === "string" ? text : "";
3223
3246
  }).join("");
3224
3247
  }
3225
- function pickAnthropicToolCalls(payload) {
3248
+ function extractAnthropicReasoning(payload) {
3226
3249
  const content = payload.content;
3227
3250
  if (!Array.isArray(content)) {
3228
- return [];
3251
+ return "";
3252
+ }
3253
+ return content.map((part) => {
3254
+ if (!isRecord2(part)) {
3255
+ return "";
3256
+ }
3257
+ const type = pickString(part.type) ?? "";
3258
+ if (type !== "thinking" && type !== "reasoning") {
3259
+ return "";
3260
+ }
3261
+ return pickString(part.thinking) ?? pickString(part.text) ?? pickString(part.reasoning) ?? "";
3262
+ }).join("");
3263
+ }
3264
+ function pickAnthropicToolCalls(payload) {
3265
+ const content = payload.content;
3266
+ if (!Array.isArray(content)) {
3267
+ return [];
3229
3268
  }
3230
3269
  const calls = [];
3231
3270
  for (const part of content) {
@@ -3252,6 +3291,35 @@ function pickAnthropicDelta(payload) {
3252
3291
  }
3253
3292
  return "";
3254
3293
  }
3294
+ function pickAnthropicReasoningDelta(payload) {
3295
+ const deltaObject = payload.delta;
3296
+ if (isRecord2(deltaObject)) {
3297
+ const type = pickString(deltaObject.type) ?? "";
3298
+ if (type === "thinking_delta" || type === "reasoning_delta") {
3299
+ return pickString(deltaObject.thinking) ?? pickString(deltaObject.text) ?? "";
3300
+ }
3301
+ }
3302
+ const contentBlock = payload.content_block;
3303
+ if (isRecord2(contentBlock)) {
3304
+ const type = pickString(contentBlock.type) ?? "";
3305
+ if (type === "thinking" || type === "reasoning") {
3306
+ return pickString(contentBlock.thinking) ?? pickString(contentBlock.text) ?? "";
3307
+ }
3308
+ }
3309
+ return "";
3310
+ }
3311
+ function pushReasoningBlock2(blocks, turnIndex, text) {
3312
+ const clean = text?.replace(/<\/?think\s*>/gi, "").trim();
3313
+ if (!clean) {
3314
+ return;
3315
+ }
3316
+ blocks.push({ turnIndex, text: clean });
3317
+ }
3318
+ function joinReasoningBlocks2(blocks) {
3319
+ return blocks.map((block) => block.text).filter(Boolean).join(`
3320
+
3321
+ `);
3322
+ }
3255
3323
  function collectAnthropicStreamToolCalls(payload, state) {
3256
3324
  const eventType = pickString(payload.type);
3257
3325
  if (!eventType) {
@@ -3478,39 +3546,10 @@ function buildProviderOptions(config) {
3478
3546
  return {
3479
3547
  model: config.model,
3480
3548
  ...transport,
3481
- ...config.options ?? {}
3549
+ ...config.options
3482
3550
  };
3483
3551
  }
3484
3552
 
3485
- // src/utils/debug-colors.ts
3486
- var ANSI = {
3487
- reset: "\x1B[0m",
3488
- bold: "\x1B[1m",
3489
- cyan: "\x1B[36m",
3490
- yellow: "\x1B[33m",
3491
- green: "\x1B[32m",
3492
- red: "\x1B[31m",
3493
- dim: "\x1B[2m"
3494
- };
3495
- function color(config, text, tone) {
3496
- if (!config.colors) {
3497
- return text;
3498
- }
3499
- return `${ANSI[tone]}${text}${ANSI.reset}`;
3500
- }
3501
- function dim(config, text) {
3502
- if (!config.colors) {
3503
- return text;
3504
- }
3505
- return `${ANSI.dim}${text}${ANSI.reset}`;
3506
- }
3507
- function title(config, text) {
3508
- if (!config.colors) {
3509
- return text;
3510
- }
3511
- return `${ANSI.bold}${text}${ANSI.reset}`;
3512
- }
3513
-
3514
3553
  // src/outdent.ts
3515
3554
  var DEFAULT_OPTIONS = {
3516
3555
  trimLeadingNewline: true,
@@ -3648,131 +3687,213 @@ function createOutdent(options = {}) {
3648
3687
  return outdent;
3649
3688
  }
3650
3689
 
3651
- // src/generate-shared.ts
3652
- var sharedOutdent = createOutdent({
3653
- trimLeadingNewline: true,
3654
- trimTrailingNewline: true,
3655
- newline: `
3656
- `
3657
- });
3690
+ // src/generate-tool-timeout.ts
3691
+ function withToolTimeout(client, toolTimeoutMs) {
3692
+ return {
3693
+ id: client.id,
3694
+ listTools: client.listTools.bind(client),
3695
+ close: client.close?.bind(client),
3696
+ async callTool(params) {
3697
+ let timeoutId;
3698
+ const timeoutPromise = new Promise((_, reject) => {
3699
+ timeoutId = setTimeout(() => reject(new Error(`Tool call timed out after ${toolTimeoutMs}ms`)), toolTimeoutMs);
3700
+ });
3701
+ try {
3702
+ return await Promise.race([client.callTool(params), timeoutPromise]);
3703
+ } finally {
3704
+ clearTimeout(timeoutId);
3705
+ }
3706
+ }
3707
+ };
3708
+ }
3709
+ function applyToolTimeout(clients, toolTimeoutMs) {
3710
+ return clients.map((client) => withToolTimeout(client, toolTimeoutMs));
3711
+ }
3712
+ // src/generate-output.ts
3658
3713
  var RE_THINK_TAGS = /<\/?think\s*>/gi;
3659
- function resolvePrompt(prompt, context) {
3660
- const resolved = typeof prompt === "function" ? prompt(context) : prompt;
3661
- return normalizePromptValue(resolved, context);
3714
+ function normalizeModelOutput(text, dedicatedReasoning, reasoningBlocks) {
3715
+ const sanitized = sanitizeThink(text);
3716
+ const visibleText = stripThinkBlocks(text, sanitized.thinkBlocks);
3717
+ const reasoning = joinReasoningSegments([
3718
+ dedicatedReasoning,
3719
+ ...sanitized.thinkBlocks.map((block) => block.content)
3720
+ ]);
3721
+ return {
3722
+ text: visibleText,
3723
+ reasoning,
3724
+ reasoningBlocks: normalizeReasoningBlocks(reasoningBlocks),
3725
+ thinkBlocks: sanitized.thinkBlocks,
3726
+ parseSource: composeParseSource(visibleText, reasoning)
3727
+ };
3662
3728
  }
3663
- function normalizePromptValue(value, _context) {
3664
- if (typeof value === "string") {
3665
- return {
3666
- prompt: value
3667
- };
3668
- }
3669
- if (isPromptResolver(value)) {
3670
- return normalizePromptPayload(value.resolvePrompt(_context));
3729
+ function normalizeReasoningBlocks(blocks) {
3730
+ if (!Array.isArray(blocks)) {
3731
+ return;
3671
3732
  }
3672
- return normalizePromptPayload(value);
3733
+ const normalized = blocks.map((block) => ({
3734
+ turnIndex: block.turnIndex,
3735
+ text: block.text.replace(RE_THINK_TAGS, "").trim()
3736
+ })).filter((block) => Number.isFinite(block.turnIndex) && block.text.length > 0);
3737
+ return normalized.length > 0 ? normalized : undefined;
3673
3738
  }
3674
- function normalizePromptPayload(value) {
3675
- const prompt = typeof value.prompt === "string" ? value.prompt : undefined;
3676
- const messages = Array.isArray(value.messages) ? value.messages.filter(isLLMMessage) : undefined;
3677
- if ((!prompt || prompt.trim().length === 0) && (!messages || messages.length === 0)) {
3678
- throw new Error("Structured prompt payload must include a non-empty prompt or messages.");
3739
+ function appendReasoningBlock(blocks, transition) {
3740
+ const text = transition.reasoningText?.replace(RE_THINK_TAGS, "").trim();
3741
+ if (!text) {
3742
+ return blocks;
3679
3743
  }
3680
- return {
3681
- prompt,
3682
- systemPrompt: typeof value.systemPrompt === "string" ? value.systemPrompt : undefined,
3683
- messages: messages && messages.length > 0 ? messages.map((message) => ({ ...message })) : undefined
3684
- };
3744
+ const next = [...blocks ?? [], { turnIndex: transition.turnIndex, text }];
3745
+ return normalizeReasoningBlocks(next);
3685
3746
  }
3686
- function applyPromptOutdent(payload, enabled) {
3687
- if (!enabled) {
3688
- return payload;
3747
+ function composeParseSource(text, reasoning) {
3748
+ if (typeof reasoning !== "string" || reasoning.length === 0) {
3749
+ return text;
3689
3750
  }
3690
- return {
3691
- prompt: typeof payload.prompt === "string" ? sharedOutdent.string(payload.prompt) : undefined,
3692
- systemPrompt: applyOutdentToOptionalPrompt(payload.systemPrompt, enabled),
3693
- messages: payload.messages?.map((message) => ({
3694
- ...message,
3695
- content: typeof message.content === "string" ? sharedOutdent.string(message.content) : message.content
3696
- }))
3697
- };
3751
+ const sanitized = reasoning.replace(RE_THINK_TAGS, "");
3752
+ if (sanitized.length === 0) {
3753
+ return text;
3754
+ }
3755
+ return `<think>${sanitized}</think>${text}`;
3698
3756
  }
3699
- function applyOutdentToOptionalPrompt(value, enabled) {
3700
- if (!enabled || typeof value !== "string") {
3701
- return value;
3757
+ function aggregateUsage(attempts) {
3758
+ let usage;
3759
+ for (const attempt of attempts) {
3760
+ usage = mergeUsage2(usage, attempt.usage);
3702
3761
  }
3703
- return sharedOutdent.string(value);
3762
+ return usage;
3704
3763
  }
3705
- function mergeSystemPrompts(primary, secondary) {
3706
- const prompts = [primary, secondary].map((value) => value?.trim()).filter((value) => Boolean(value));
3707
- if (prompts.length === 0) {
3764
+ function mergeUsage2(base, next) {
3765
+ if (!base && !next) {
3708
3766
  return;
3709
3767
  }
3710
- return prompts.join(`
3768
+ return {
3769
+ inputTokens: (base?.inputTokens ?? 0) + (next?.inputTokens ?? 0),
3770
+ outputTokens: (base?.outputTokens ?? 0) + (next?.outputTokens ?? 0),
3771
+ totalTokens: (base?.totalTokens ?? 0) + (next?.totalTokens ?? 0),
3772
+ cost: (base?.cost ?? 0) + (next?.cost ?? 0)
3773
+ };
3774
+ }
3775
+ function joinReasoningSegments(parts) {
3776
+ return parts.map((value) => value?.trim()).filter((value) => Boolean(value)).join(`
3711
3777
 
3712
3778
  `);
3713
3779
  }
3714
- function normalizeStreamConfig(option) {
3715
- if (typeof option === "boolean") {
3716
- return {
3717
- enabled: option
3718
- };
3780
+ function stripThinkBlocks(text, thinkBlocks) {
3781
+ if (thinkBlocks.length === 0) {
3782
+ return text;
3719
3783
  }
3720
- if (!option) {
3721
- return {
3722
- enabled: false
3723
- };
3784
+ let output = "";
3785
+ let cursor = 0;
3786
+ for (const block of thinkBlocks) {
3787
+ output += text.slice(cursor, block.start);
3788
+ cursor = block.end;
3724
3789
  }
3725
- return {
3726
- enabled: option.enabled ?? true,
3727
- onData: option.onData,
3728
- to: option.to
3729
- };
3790
+ output += text.slice(cursor);
3791
+ return output;
3730
3792
  }
3731
- function normalizeDebugConfig(option) {
3732
- if (typeof option === "boolean") {
3733
- return {
3734
- enabled: option,
3735
- colors: true,
3736
- verbose: false,
3737
- logger: (line) => console.log(line)
3738
- };
3793
+ function toStreamDataFingerprint(value) {
3794
+ try {
3795
+ return JSON.stringify(value);
3796
+ } catch {
3797
+ return "__unserializable__";
3739
3798
  }
3740
- if (!option) {
3741
- return {
3742
- enabled: false,
3743
- colors: true,
3744
- verbose: false,
3745
- logger: (line) => console.log(line)
3746
- };
3799
+ }
3800
+
3801
+ // src/utils/debug-colors.ts
3802
+ var ANSI = {
3803
+ reset: "\x1B[0m",
3804
+ bold: "\x1B[1m",
3805
+ cyan: "\x1B[36m",
3806
+ yellow: "\x1B[33m",
3807
+ green: "\x1B[32m",
3808
+ red: "\x1B[31m",
3809
+ dim: "\x1B[2m"
3810
+ };
3811
+ function color(config, text, tone) {
3812
+ if (!config.colors) {
3813
+ return text;
3747
3814
  }
3748
- return {
3749
- enabled: option.enabled ?? true,
3750
- colors: option.colors ?? true,
3751
- verbose: option.verbose ?? false,
3752
- logger: option.logger ?? ((line) => console.log(line))
3753
- };
3815
+ return `${ANSI[tone]}${text}${ANSI.reset}`;
3754
3816
  }
3755
- function withToolTimeout(client, toolTimeoutMs) {
3756
- return {
3757
- id: client.id,
3758
- listTools: client.listTools.bind(client),
3759
- close: client.close?.bind(client),
3760
- async callTool(params) {
3761
- let timeoutId;
3762
- const timeoutPromise = new Promise((_, reject) => {
3763
- timeoutId = setTimeout(() => reject(new Error(`Tool call timed out after ${toolTimeoutMs}ms`)), toolTimeoutMs);
3764
- });
3765
- try {
3766
- return await Promise.race([client.callTool(params), timeoutPromise]);
3767
- } finally {
3768
- clearTimeout(timeoutId);
3769
- }
3770
- }
3771
- };
3817
+ function dim(config, text) {
3818
+ if (!config.colors) {
3819
+ return text;
3820
+ }
3821
+ return `${ANSI.dim}${text}${ANSI.reset}`;
3772
3822
  }
3773
- function applyToolTimeout(clients, toolTimeoutMs) {
3774
- return clients.map((client) => withToolTimeout(client, toolTimeoutMs));
3823
+ function title(config, text) {
3824
+ if (!config.colors) {
3825
+ return text;
3826
+ }
3827
+ return `${ANSI.bold}${text}${ANSI.reset}`;
3828
+ }
3829
+
3830
+ // src/generate-debug.ts
3831
+ function emitDebugRequest(config, input) {
3832
+ const requestBody = input.requestPayload.body !== undefined ? JSON.stringify(input.requestPayload.body, null, 2) : "(none)";
3833
+ const requestMessages = input.requestPayload.messages !== undefined ? JSON.stringify(input.requestPayload.messages, null, 2) : "(none)";
3834
+ const lines = [
3835
+ color(config, title(config, [
3836
+ `[${input.label}][request]`,
3837
+ `attempt=${input.attempt}`,
3838
+ `selfHealEnabled=${input.selfHealEnabled}`,
3839
+ `selfHealAttempt=${input.selfHealAttempt}`
3840
+ ].join(" ")), "cyan"),
3841
+ dim(config, [
3842
+ `provider=${input.provider ?? "unknown"}`,
3843
+ `model=${input.model ?? "unknown"}`,
3844
+ `stream=${input.stream}`
3845
+ ].join(" ")),
3846
+ color(config, "prompt:", "yellow"),
3847
+ input.requestPayload.prompt ?? "(none)",
3848
+ color(config, "messages:", "yellow"),
3849
+ requestMessages,
3850
+ color(config, "systemPrompt:", "yellow"),
3851
+ input.requestPayload.systemPrompt ?? "(none)",
3852
+ color(config, "request.body:", "yellow"),
3853
+ requestBody
3854
+ ];
3855
+ emitDebug(config, lines.join(`
3856
+ `));
3857
+ }
3858
+ function emitDebugResponse(config, input) {
3859
+ const text = input.text.length > 0 ? input.text : "(none)";
3860
+ const reasoning = input.reasoning.length > 0 ? input.reasoning : "(none)";
3861
+ const metadata = [
3862
+ `via=${input.via}`,
3863
+ `textChars=${input.text.length}`,
3864
+ `reasoningChars=${input.reasoning.length}`
3865
+ ];
3866
+ if (config.verbose) {
3867
+ metadata.push(`parseSourceChars=${input.parseSource.length}`);
3868
+ }
3869
+ metadata.push(`finishReason=${input.finishReason ?? "unknown"}`, `usage=${JSON.stringify(input.usage ?? {})}`);
3870
+ const lines = [
3871
+ color(config, title(config, [
3872
+ `[${input.label}][response]`,
3873
+ `attempt=${input.attempt}`,
3874
+ `selfHealEnabled=${input.selfHealEnabled}`,
3875
+ `selfHealAttempt=${input.selfHealAttempt}`
3876
+ ].join(" ")), "green"),
3877
+ dim(config, metadata.join(" ")),
3878
+ color(config, "text:", "yellow"),
3879
+ text,
3880
+ color(config, "reasoning:", "yellow"),
3881
+ reasoning
3882
+ ];
3883
+ if (config.verbose) {
3884
+ lines.push(color(config, "parseSource:", "yellow"), input.parseSource);
3885
+ }
3886
+ emitDebug(config, lines.join(`
3887
+ `));
3775
3888
  }
3889
+ function emitDebug(config, message) {
3890
+ if (!config.enabled) {
3891
+ return;
3892
+ }
3893
+ config.logger(message);
3894
+ }
3895
+
3896
+ // src/generate-model-call.ts
3776
3897
  async function callModel(adapter, options) {
3777
3898
  const requestSignal = options.request?.signal ?? (options.timeout?.request !== undefined ? AbortSignal.timeout(options.timeout.request) : undefined);
3778
3899
  const requestPayload = {
@@ -3792,6 +3913,7 @@ async function callModel(adapter, options) {
3792
3913
  transformToolCallParams: options.request?.transformToolCallParams,
3793
3914
  unknownToolError: options.request?.unknownToolError,
3794
3915
  toolDebug: options.request?.toolDebug,
3916
+ onTurnTransition: options.stream.onTurnTransition,
3795
3917
  body: options.request?.body,
3796
3918
  signal: requestSignal
3797
3919
  };
@@ -3819,13 +3941,21 @@ async function callModel(adapter, options) {
3819
3941
  let latestFinishReason;
3820
3942
  let streamedProviderText = "";
3821
3943
  let streamedDedicatedReasoning = "";
3944
+ let currentTurnIndex;
3945
+ let currentToolCalls;
3946
+ let streamedReasoningBlocks;
3822
3947
  let lastSnapshotFingerprint;
3823
3948
  let previousSnapshotText = "";
3824
3949
  let previousSnapshotReasoning = "";
3825
3950
  const emitStreamingData = (done, usage2, finishReason2) => {
3826
- const normalized2 = normalizeModelOutput(streamedProviderText, streamedDedicatedReasoning);
3951
+ const normalized2 = normalizeModelOutput(streamedProviderText, streamedDedicatedReasoning, streamedReasoningBlocks);
3827
3952
  const snapshot = options.buildSnapshot(normalized2);
3828
- const fingerprint = toStreamDataFingerprint(snapshot);
3953
+ const fingerprint = toStreamDataFingerprint({
3954
+ snapshot,
3955
+ done,
3956
+ turnIndex: currentTurnIndex,
3957
+ toolCalls: currentToolCalls
3958
+ });
3829
3959
  if (!done && fingerprint === lastSnapshotFingerprint) {
3830
3960
  return;
3831
3961
  }
@@ -3841,7 +3971,9 @@ async function callModel(adapter, options) {
3841
3971
  snapshot,
3842
3972
  done,
3843
3973
  usage: usage2,
3844
- finishReason: finishReason2
3974
+ finishReason: finishReason2,
3975
+ turnIndex: currentTurnIndex,
3976
+ toolCalls: currentToolCalls
3845
3977
  });
3846
3978
  if (options.stream.to === "stdout" && delta.text) {
3847
3979
  process.stdout.write(delta.text);
@@ -3876,8 +4008,21 @@ async function callModel(adapter, options) {
3876
4008
  streamedDedicatedReasoning += delta;
3877
4009
  emitStreamingData(false);
3878
4010
  };
3879
- const response2 = await adapter.stream(requestPayload, {
4011
+ const streamRequestPayload = {
4012
+ ...requestPayload,
4013
+ onTurnTransition: (transition) => {
4014
+ if (transition.kind === "reasoningComplete") {
4015
+ streamedReasoningBlocks = appendReasoningBlock(streamedReasoningBlocks, transition);
4016
+ }
4017
+ options.stream.onTurnTransition?.(transition);
4018
+ }
4019
+ };
4020
+ const response2 = await adapter.stream(streamRequestPayload, {
3880
4021
  onChunk: (chunk) => {
4022
+ if (chunk.turnIndex !== undefined) {
4023
+ currentTurnIndex = chunk.turnIndex;
4024
+ }
4025
+ currentToolCalls = chunk.toolCalls;
3881
4026
  if (chunk.textDelta) {
3882
4027
  handleTextDelta(chunk.textDelta);
3883
4028
  }
@@ -3890,11 +4035,15 @@ async function callModel(adapter, options) {
3890
4035
  if (chunk.finishReason) {
3891
4036
  latestFinishReason = chunk.finishReason;
3892
4037
  }
4038
+ if (!chunk.textDelta && !chunk.reasoningDelta && (chunk.turnIndex !== undefined || chunk.toolCalls)) {
4039
+ emitStreamingData(false, chunk.usage, chunk.finishReason);
4040
+ }
3893
4041
  }
3894
4042
  });
3895
4043
  streamedProviderText = typeof response2.text === "string" ? response2.text : streamedProviderText;
3896
4044
  streamedDedicatedReasoning = typeof response2.reasoning === "string" ? response2.reasoning : streamedDedicatedReasoning;
3897
- const finalNormalized = normalizeModelOutput(streamedProviderText, streamedDedicatedReasoning);
4045
+ streamedReasoningBlocks = response2.reasoningBlocks ?? streamedReasoningBlocks;
4046
+ const finalNormalized = normalizeModelOutput(streamedProviderText, streamedDedicatedReasoning, streamedReasoningBlocks);
3898
4047
  const usage = preferLatestUsage(latestUsage, response2.usage);
3899
4048
  const finishReason = response2.finishReason ?? latestFinishReason;
3900
4049
  emitStreamingData(true, usage, finishReason);
@@ -3926,11 +4075,12 @@ async function callModel(adapter, options) {
3926
4075
  parseSource: finalNormalized.parseSource,
3927
4076
  via: "stream",
3928
4077
  usage,
3929
- finishReason
4078
+ finishReason,
4079
+ reasoningBlocks: finalNormalized.reasoningBlocks
3930
4080
  };
3931
4081
  }
3932
4082
  const response = await adapter.complete(requestPayload);
3933
- const normalized = normalizeModelOutput(response.text, response.reasoning);
4083
+ const normalized = normalizeModelOutput(response.text, response.reasoning, response.reasoningBlocks);
3934
4084
  options.observe?.(options.buildEvent({
3935
4085
  stage: "llm.response",
3936
4086
  message: "Completion response received.",
@@ -3959,51 +4109,119 @@ async function callModel(adapter, options) {
3959
4109
  parseSource: normalized.parseSource,
3960
4110
  via: "complete",
3961
4111
  usage: response.usage,
3962
- finishReason: response.finishReason
4112
+ finishReason: response.finishReason,
4113
+ reasoningBlocks: normalized.reasoningBlocks
3963
4114
  };
3964
4115
  }
3965
- function normalizeModelOutput(text, dedicatedReasoning) {
3966
- const sanitized = sanitizeThink(text);
3967
- const visibleText = stripThinkBlocks(text, sanitized.thinkBlocks);
3968
- const reasoning = joinReasoningSegments([
3969
- dedicatedReasoning,
3970
- ...sanitized.thinkBlocks.map((block) => block.content)
3971
- ]);
4116
+
4117
+ // src/generate-shared.ts
4118
+ var sharedOutdent = createOutdent({
4119
+ trimLeadingNewline: true,
4120
+ trimTrailingNewline: true,
4121
+ newline: `
4122
+ `
4123
+ });
4124
+ function resolvePrompt(prompt, context) {
4125
+ const resolved = typeof prompt === "function" ? prompt(context) : prompt;
4126
+ return normalizePromptValue(resolved, context);
4127
+ }
4128
+ function normalizePromptValue(value, _context) {
4129
+ if (typeof value === "string") {
4130
+ return {
4131
+ prompt: value
4132
+ };
4133
+ }
4134
+ if (isPromptResolver(value)) {
4135
+ return normalizePromptPayload(value.resolvePrompt(_context));
4136
+ }
4137
+ return normalizePromptPayload(value);
4138
+ }
4139
+ function normalizePromptPayload(value) {
4140
+ const prompt = typeof value.prompt === "string" ? value.prompt : undefined;
4141
+ const messages = Array.isArray(value.messages) ? value.messages.filter(isLLMMessage) : undefined;
4142
+ if ((!prompt || prompt.trim().length === 0) && (!messages || messages.length === 0)) {
4143
+ throw new Error("Structured prompt payload must include a non-empty prompt or messages.");
4144
+ }
3972
4145
  return {
3973
- text: visibleText,
3974
- reasoning,
3975
- thinkBlocks: sanitized.thinkBlocks,
3976
- parseSource: composeParseSource(visibleText, reasoning)
4146
+ prompt,
4147
+ systemPrompt: typeof value.systemPrompt === "string" ? value.systemPrompt : undefined,
4148
+ messages: messages && messages.length > 0 ? messages.map((message) => ({ ...message })) : undefined
3977
4149
  };
3978
4150
  }
3979
- function composeParseSource(text, reasoning) {
3980
- if (typeof reasoning !== "string" || reasoning.length === 0) {
3981
- return text;
4151
+ function applyPromptOutdent(payload, enabled) {
4152
+ if (!enabled) {
4153
+ return payload;
3982
4154
  }
3983
- const sanitized = reasoning.replace(RE_THINK_TAGS, "");
3984
- if (sanitized.length === 0) {
3985
- return text;
4155
+ return {
4156
+ prompt: typeof payload.prompt === "string" ? sharedOutdent.string(payload.prompt) : undefined,
4157
+ systemPrompt: applyOutdentToOptionalPrompt(payload.systemPrompt, enabled),
4158
+ messages: payload.messages?.map((message) => ({
4159
+ ...message,
4160
+ content: typeof message.content === "string" ? sharedOutdent.string(message.content) : message.content
4161
+ }))
4162
+ };
4163
+ }
4164
+ function applyOutdentToOptionalPrompt(value, enabled) {
4165
+ if (!enabled || typeof value !== "string") {
4166
+ return value;
3986
4167
  }
3987
- return `<think>${sanitized}</think>${text}`;
4168
+ return sharedOutdent.string(value);
3988
4169
  }
3989
- function aggregateUsage(attempts) {
3990
- let usage;
3991
- for (const attempt of attempts) {
3992
- usage = mergeUsage2(usage, attempt.usage);
4170
+ function mergeSystemPrompts(primary, secondary) {
4171
+ const prompts = [primary, secondary].map((value) => value?.trim()).filter((value) => Boolean(value));
4172
+ if (prompts.length === 0) {
4173
+ return;
4174
+ }
4175
+ return prompts.join(`
4176
+
4177
+ `);
4178
+ }
4179
+ function normalizeStreamConfig(option) {
4180
+ if (typeof option === "boolean") {
4181
+ return {
4182
+ enabled: option
4183
+ };
4184
+ }
4185
+ if (!option) {
4186
+ return {
4187
+ enabled: false
4188
+ };
3993
4189
  }
3994
- return usage;
4190
+ return {
4191
+ enabled: option.enabled ?? true,
4192
+ onData: option.onData,
4193
+ onTurnTransition: option.onTurnTransition,
4194
+ to: option.to
4195
+ };
3995
4196
  }
3996
- function mergeUsage2(base, next) {
3997
- if (!base && !next) {
3998
- return;
4197
+ function normalizeDebugConfig(option) {
4198
+ if (typeof option === "boolean") {
4199
+ return {
4200
+ enabled: option,
4201
+ colors: true,
4202
+ verbose: false,
4203
+ logger: defaultDebugLogger
4204
+ };
4205
+ }
4206
+ if (!option) {
4207
+ return {
4208
+ enabled: false,
4209
+ colors: true,
4210
+ verbose: false,
4211
+ logger: defaultDebugLogger
4212
+ };
3999
4213
  }
4000
4214
  return {
4001
- inputTokens: (base?.inputTokens ?? 0) + (next?.inputTokens ?? 0),
4002
- outputTokens: (base?.outputTokens ?? 0) + (next?.outputTokens ?? 0),
4003
- totalTokens: (base?.totalTokens ?? 0) + (next?.totalTokens ?? 0),
4004
- cost: (base?.cost ?? 0) + (next?.cost ?? 0)
4215
+ enabled: option.enabled ?? true,
4216
+ colors: option.colors ?? true,
4217
+ verbose: option.verbose ?? false,
4218
+ logger: option.logger ?? defaultDebugLogger
4005
4219
  };
4006
4220
  }
4221
+ function defaultDebugLogger(line) {
4222
+ const { log } = globalThis.console;
4223
+ log(line);
4224
+ }
4007
4225
  function isPromptResolver(value) {
4008
4226
  return typeof value === "object" && value !== null && "resolvePrompt" in value && typeof value.resolvePrompt === "function";
4009
4227
  }
@@ -4017,95 +4235,6 @@ function isLLMMessage(value) {
4017
4235
  }
4018
4236
  return "content" in candidate;
4019
4237
  }
4020
- function joinReasoningSegments(parts) {
4021
- return parts.map((value) => value?.trim()).filter((value) => Boolean(value)).join(`
4022
-
4023
- `);
4024
- }
4025
- function stripThinkBlocks(text, thinkBlocks) {
4026
- if (thinkBlocks.length === 0) {
4027
- return text;
4028
- }
4029
- let output = "";
4030
- let cursor = 0;
4031
- for (const block of thinkBlocks) {
4032
- output += text.slice(cursor, block.start);
4033
- cursor = block.end;
4034
- }
4035
- output += text.slice(cursor);
4036
- return output;
4037
- }
4038
- function toStreamDataFingerprint(value) {
4039
- try {
4040
- return JSON.stringify(value);
4041
- } catch {
4042
- return "__unserializable__";
4043
- }
4044
- }
4045
- function emitDebugRequest(config, input) {
4046
- const requestBody = input.requestPayload.body !== undefined ? JSON.stringify(input.requestPayload.body, null, 2) : "(none)";
4047
- const requestMessages = input.requestPayload.messages !== undefined ? JSON.stringify(input.requestPayload.messages, null, 2) : "(none)";
4048
- const lines = [
4049
- color(config, title(config, [
4050
- `[${input.label}][request]`,
4051
- `attempt=${input.attempt}`,
4052
- `selfHealEnabled=${input.selfHealEnabled}`,
4053
- `selfHealAttempt=${input.selfHealAttempt}`
4054
- ].join(" ")), "cyan"),
4055
- dim(config, [
4056
- `provider=${input.provider ?? "unknown"}`,
4057
- `model=${input.model ?? "unknown"}`,
4058
- `stream=${input.stream}`
4059
- ].join(" ")),
4060
- color(config, "prompt:", "yellow"),
4061
- input.requestPayload.prompt ?? "(none)",
4062
- color(config, "messages:", "yellow"),
4063
- requestMessages,
4064
- color(config, "systemPrompt:", "yellow"),
4065
- input.requestPayload.systemPrompt ?? "(none)",
4066
- color(config, "request.body:", "yellow"),
4067
- requestBody
4068
- ];
4069
- emitDebug(config, lines.join(`
4070
- `));
4071
- }
4072
- function emitDebugResponse(config, input) {
4073
- const text = input.text.length > 0 ? input.text : "(none)";
4074
- const reasoning = input.reasoning.length > 0 ? input.reasoning : "(none)";
4075
- const metadata = [
4076
- `via=${input.via}`,
4077
- `textChars=${input.text.length}`,
4078
- `reasoningChars=${input.reasoning.length}`
4079
- ];
4080
- if (config.verbose) {
4081
- metadata.push(`parseSourceChars=${input.parseSource.length}`);
4082
- }
4083
- metadata.push(`finishReason=${input.finishReason ?? "unknown"}`, `usage=${JSON.stringify(input.usage ?? {})}`);
4084
- const lines = [
4085
- color(config, title(config, [
4086
- `[${input.label}][response]`,
4087
- `attempt=${input.attempt}`,
4088
- `selfHealEnabled=${input.selfHealEnabled}`,
4089
- `selfHealAttempt=${input.selfHealAttempt}`
4090
- ].join(" ")), "green"),
4091
- dim(config, metadata.join(" ")),
4092
- color(config, "text:", "yellow"),
4093
- text,
4094
- color(config, "reasoning:", "yellow"),
4095
- reasoning
4096
- ];
4097
- if (config.verbose) {
4098
- lines.push(color(config, "parseSource:", "yellow"), input.parseSource);
4099
- }
4100
- emitDebug(config, lines.join(`
4101
- `));
4102
- }
4103
- function emitDebug(config, message) {
4104
- if (!config.enabled) {
4105
- return;
4106
- }
4107
- config.logger(message);
4108
- }
4109
4238
 
4110
4239
  // src/generate.ts
4111
4240
  async function generate(adapter, promptOrOptions, callOptions) {
@@ -4135,7 +4264,8 @@ async function generate(adapter, promptOrOptions, callOptions) {
4135
4264
  }),
4136
4265
  buildSnapshot: (model) => ({
4137
4266
  text: model.text,
4138
- reasoning: model.reasoning
4267
+ reasoning: model.reasoning,
4268
+ ...model.reasoningBlocks ? { reasoningBlocks: model.reasoningBlocks } : {}
4139
4269
  }),
4140
4270
  debug: debugConfig,
4141
4271
  debugLabel: "generate",
@@ -4150,7 +4280,8 @@ async function generate(adapter, promptOrOptions, callOptions) {
4150
4280
  text: response.text,
4151
4281
  reasoning: response.reasoning,
4152
4282
  usage: response.usage,
4153
- finishReason: response.finishReason
4283
+ finishReason: response.finishReason,
4284
+ ...response.reasoningBlocks ? { reasoningBlocks: response.reasoningBlocks } : {}
4154
4285
  };
4155
4286
  const attempts = [attempt];
4156
4287
  normalized.observe?.({
@@ -4167,7 +4298,8 @@ async function generate(adapter, promptOrOptions, callOptions) {
4167
4298
  reasoning: attempt.reasoning,
4168
4299
  attempts,
4169
4300
  usage: aggregateUsage(attempts),
4170
- finishReason: attempt.finishReason
4301
+ finishReason: attempt.finishReason,
4302
+ ...attempt.reasoningBlocks ? { reasoningBlocks: attempt.reasoningBlocks } : {}
4171
4303
  };
4172
4304
  }
4173
4305
  function normalizeGenerateInput(promptOrOptions, callOptions) {
@@ -4178,7 +4310,7 @@ function normalizeGenerateInput(promptOrOptions, callOptions) {
4178
4310
  throw new Error("Missing prompt in generate(adapter, prompt, options?) call.");
4179
4311
  }
4180
4312
  return {
4181
- ...callOptions ?? {},
4313
+ ...callOptions,
4182
4314
  prompt: promptOrOptions
4183
4315
  };
4184
4316
  }
@@ -4204,9 +4336,6 @@ function prepareGeneratePromptPayload(payload, systemPrompt) {
4204
4336
  };
4205
4337
  }
4206
4338
 
4207
- // src/structured.ts
4208
- import { jsonrepair as jsonrepair3 } from "jsonrepair";
4209
-
4210
4339
  // src/parse.ts
4211
4340
  import { jsonrepair as jsonrepair2 } from "jsonrepair";
4212
4341
  function parseLLMOutput(output, schema, options = {}) {
@@ -4506,6 +4635,80 @@ function formatZodIssues(issues) {
4506
4635
  `);
4507
4636
  }
4508
4637
 
4638
+ // src/structured-streaming.ts
4639
+ import { jsonrepair as jsonrepair3 } from "jsonrepair";
4640
+ function parseStreamingStructuredData(parseSource) {
4641
+ const sanitized = sanitizeThink(parseSource);
4642
+ const start = findFirstJsonRootStart(sanitized.visibleText);
4643
+ if (start < 0) {
4644
+ return null;
4645
+ }
4646
+ const candidate = sanitized.visibleText.slice(start).trim();
4647
+ if (!candidate) {
4648
+ return null;
4649
+ }
4650
+ try {
4651
+ const repaired = jsonrepair3(candidate);
4652
+ const parsed = JSON.parse(repaired);
4653
+ if (typeof parsed !== "object" || parsed === null) {
4654
+ return null;
4655
+ }
4656
+ return parsed;
4657
+ } catch {
4658
+ return null;
4659
+ }
4660
+ }
4661
+ function findFirstJsonRootStart(input) {
4662
+ const unquotedRootStart = findFirstUnquotedJsonRootStart(input);
4663
+ if (unquotedRootStart >= 0) {
4664
+ return unquotedRootStart;
4665
+ }
4666
+ return findFirstRawJsonRootStart(input);
4667
+ }
4668
+ function findFirstUnquotedJsonRootStart(input) {
4669
+ let inString = false;
4670
+ let escaped = false;
4671
+ for (let index = 0;index < input.length; index += 1) {
4672
+ const char = input[index];
4673
+ if (!char) {
4674
+ continue;
4675
+ }
4676
+ if (inString) {
4677
+ if (escaped) {
4678
+ escaped = false;
4679
+ continue;
4680
+ }
4681
+ if (char === "\\") {
4682
+ escaped = true;
4683
+ continue;
4684
+ }
4685
+ if (char === '"') {
4686
+ inString = false;
4687
+ }
4688
+ continue;
4689
+ }
4690
+ if (char === '"') {
4691
+ inString = true;
4692
+ continue;
4693
+ }
4694
+ if (char === "{" || char === "[") {
4695
+ return index;
4696
+ }
4697
+ }
4698
+ return -1;
4699
+ }
4700
+ function findFirstRawJsonRootStart(input) {
4701
+ const objectStart = input.indexOf("{");
4702
+ const arrayStart = input.indexOf("[");
4703
+ if (objectStart < 0) {
4704
+ return arrayStart;
4705
+ }
4706
+ if (arrayStart < 0) {
4707
+ return objectStart;
4708
+ }
4709
+ return Math.min(objectStart, arrayStart);
4710
+ }
4711
+
4509
4712
  // src/structured.ts
4510
4713
  class StructuredParseError extends Error {
4511
4714
  name = "StructuredParseError";
@@ -4743,7 +4946,7 @@ function normalizeStructuredInput(schemaOrOptions, promptInput, callOptions) {
4743
4946
  throw new Error("Missing prompt in structured(adapter, schema, prompt, options?) call.");
4744
4947
  }
4745
4948
  return {
4746
- ...callOptions ?? {},
4949
+ ...callOptions,
4747
4950
  schema: schemaOrOptions,
4748
4951
  prompt: promptInput
4749
4952
  };
@@ -4987,6 +5190,7 @@ async function executeAttempt(adapter, input) {
4987
5190
  success: parsed.success,
4988
5191
  usage: response.usage,
4989
5192
  finishReason: response.finishReason,
5193
+ ...response.reasoningBlocks ? { reasoningBlocks: response.reasoningBlocks } : {},
4990
5194
  parsed
4991
5195
  };
4992
5196
  return {
@@ -5007,72 +5211,12 @@ async function callModel2(adapter, options) {
5007
5211
  buildSnapshot: (normalized) => ({
5008
5212
  text: normalized.text,
5009
5213
  reasoning: normalized.reasoning,
5214
+ ...normalized.reasoningBlocks ? { reasoningBlocks: normalized.reasoningBlocks } : {},
5010
5215
  data: parseStreamingStructuredData(normalized.parseSource) ?? null
5011
5216
  }),
5012
5217
  debugLabel: "structured"
5013
5218
  });
5014
5219
  }
5015
- function parseStreamingStructuredData(parseSource) {
5016
- const sanitized = sanitizeThink(parseSource);
5017
- const start = findFirstJsonRootStart(sanitized.visibleText);
5018
- if (start < 0) {
5019
- return null;
5020
- }
5021
- const candidate = sanitized.visibleText.slice(start).trim();
5022
- if (!candidate) {
5023
- return null;
5024
- }
5025
- try {
5026
- const repaired = jsonrepair3(candidate);
5027
- const parsed = JSON.parse(repaired);
5028
- if (typeof parsed !== "object" || parsed === null) {
5029
- return null;
5030
- }
5031
- return parsed;
5032
- } catch {
5033
- return null;
5034
- }
5035
- }
5036
- function findFirstJsonRootStart(input) {
5037
- let inString = false;
5038
- let escaped = false;
5039
- for (let index = 0;index < input.length; index += 1) {
5040
- const char = input[index];
5041
- if (!char) {
5042
- continue;
5043
- }
5044
- if (inString) {
5045
- if (escaped) {
5046
- escaped = false;
5047
- continue;
5048
- }
5049
- if (char === "\\") {
5050
- escaped = true;
5051
- continue;
5052
- }
5053
- if (char === '"') {
5054
- inString = false;
5055
- }
5056
- continue;
5057
- }
5058
- if (char === '"') {
5059
- inString = true;
5060
- continue;
5061
- }
5062
- if (char === "{" || char === "[") {
5063
- return index;
5064
- }
5065
- }
5066
- const objectStart = input.indexOf("{");
5067
- const arrayStart = input.indexOf("[");
5068
- if (objectStart < 0) {
5069
- return arrayStart;
5070
- }
5071
- if (arrayStart < 0) {
5072
- return objectStart;
5073
- }
5074
- return Math.min(objectStart, arrayStart);
5075
- }
5076
5220
  function parseWithObserve(output, schema, parseOptions, context) {
5077
5221
  const userParseTrace = parseOptions.onTrace;
5078
5222
  return parseLLMOutput(output, schema, {
@@ -5107,7 +5251,8 @@ function buildSuccessResult(data, attempts) {
5107
5251
  json: final?.json ?? null,
5108
5252
  attempts,
5109
5253
  usage: aggregateUsage(attempts),
5110
- finishReason: final?.finishReason
5254
+ finishReason: final?.finishReason,
5255
+ ...final?.reasoningBlocks ? { reasoningBlocks: final.reasoningBlocks } : {}
5111
5256
  };
5112
5257
  }
5113
5258
  function toStructuredError(attempt) {
@@ -5174,12 +5319,12 @@ function mergeStructuredOptions(defaults, overrides) {
5174
5319
  ...defaults,
5175
5320
  ...overrides,
5176
5321
  parse: {
5177
- ...defaults?.parse ?? {},
5178
- ...overrides?.parse ?? {}
5322
+ ...defaults?.parse,
5323
+ ...overrides?.parse
5179
5324
  },
5180
5325
  request: {
5181
- ...defaults?.request ?? {},
5182
- ...overrides?.request ?? {}
5326
+ ...defaults?.request,
5327
+ ...overrides?.request
5183
5328
  },
5184
5329
  stream: mergeObjectLike(defaults?.stream, overrides?.stream),
5185
5330
  selfHeal: mergeObjectLike(defaults?.selfHeal, overrides?.selfHeal),
@@ -5195,8 +5340,8 @@ function mergeGenerateOptions(defaults, overrides) {
5195
5340
  outdent: overrides?.outdent ?? defaults?.outdent,
5196
5341
  systemPrompt: overrides?.systemPrompt ?? defaults?.systemPrompt,
5197
5342
  request: {
5198
- ...defaults?.request ?? {},
5199
- ...overrides?.request ?? {}
5343
+ ...defaults?.request,
5344
+ ...overrides?.request
5200
5345
  },
5201
5346
  stream: mergeObjectLike(defaults?.stream, overrides?.stream),
5202
5347
  debug: mergeObjectLike(defaults?.debug, overrides?.debug),
@@ -5574,11 +5719,11 @@ function inferSchemaExample(schema) {
5574
5719
  }
5575
5720
  function getObjectShape(schema) {
5576
5721
  const unwrapped = unwrap2(schema).schema;
5577
- const typeName = unwrapped._def?.type;
5722
+ const typeName = unwrapped.def?.type;
5578
5723
  if (typeName !== "object") {
5579
5724
  return null;
5580
5725
  }
5581
- const rawShape = unwrapped._def?.shape;
5726
+ const rawShape = unwrapped.def?.shape;
5582
5727
  if (typeof rawShape === "function") {
5583
5728
  return rawShape();
5584
5729
  }
@@ -5586,11 +5731,11 @@ function getObjectShape(schema) {
5586
5731
  }
5587
5732
  function readDefaultValue(schema) {
5588
5733
  let current = schema;
5589
- while (current?._def?.type) {
5590
- const typeName = current._def.type;
5734
+ while (current?.def?.type) {
5735
+ const typeName = current.def.type;
5591
5736
  if (typeName === "default") {
5592
5737
  try {
5593
- const raw = current._def.defaultValue;
5738
+ const raw = current.def.defaultValue;
5594
5739
  if (typeof raw === "function") {
5595
5740
  return raw();
5596
5741
  }
@@ -5600,11 +5745,11 @@ function readDefaultValue(schema) {
5600
5745
  }
5601
5746
  }
5602
5747
  if (typeName === "optional" || typeName === "nullable" || typeName === "catch" || typeName === "readonly") {
5603
- current = current._def.innerType ?? current;
5748
+ current = current.def.innerType ?? current;
5604
5749
  continue;
5605
5750
  }
5606
5751
  if (typeName === "pipe") {
5607
- current = current._def.in ?? current;
5752
+ current = current.def.in ?? current;
5608
5753
  continue;
5609
5754
  }
5610
5755
  return;
@@ -5613,22 +5758,22 @@ function readDefaultValue(schema) {
5613
5758
  }
5614
5759
  function readSchemaDescription2(schema) {
5615
5760
  let current = schema;
5616
- while (current?._def?.type) {
5761
+ while (current?.def?.type) {
5617
5762
  const desc = current.description;
5618
5763
  if (typeof desc === "string" && desc.trim().length > 0) {
5619
5764
  return desc.trim();
5620
5765
  }
5621
- const typeName = current._def.type;
5766
+ const typeName = current.def.type;
5622
5767
  if (typeName === "optional" || typeName === "default" || typeName === "nullable") {
5623
- current = current._def.innerType ?? current;
5768
+ current = current.def.innerType ?? current;
5624
5769
  continue;
5625
5770
  }
5626
5771
  if (typeName === "catch" || typeName === "readonly") {
5627
- current = current._def.innerType ?? current;
5772
+ current = current.def.innerType ?? current;
5628
5773
  continue;
5629
5774
  }
5630
5775
  if (typeName === "pipe") {
5631
- current = current._def.in ?? current;
5776
+ current = current.def.in ?? current;
5632
5777
  continue;
5633
5778
  }
5634
5779
  break;
@@ -5638,19 +5783,19 @@ function readSchemaDescription2(schema) {
5638
5783
  function unwrap2(schema) {
5639
5784
  let current = schema;
5640
5785
  let optional = false;
5641
- while (current?._def?.type) {
5642
- const typeName = current._def.type;
5786
+ while (current?.def?.type) {
5787
+ const typeName = current.def.type;
5643
5788
  if (typeName === "optional" || typeName === "default") {
5644
5789
  optional = true;
5645
- current = current._def.innerType ?? current;
5790
+ current = current.def.innerType ?? current;
5646
5791
  continue;
5647
5792
  }
5648
5793
  if (typeName === "nullable" || typeName === "catch" || typeName === "readonly") {
5649
- current = current._def.innerType ?? current;
5794
+ current = current.def.innerType ?? current;
5650
5795
  continue;
5651
5796
  }
5652
5797
  if (typeName === "pipe") {
5653
- current = current._def.in ?? current;
5798
+ current = current.def.in ?? current;
5654
5799
  continue;
5655
5800
  }
5656
5801
  break;