extrait 0.7.0 → 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -91,9 +91,6 @@ __export(exports_src, {
91
91
  });
92
92
  module.exports = __toCommonJS(exports_src);
93
93
 
94
- // src/extract.ts
95
- var import_jsonrepair = require("jsonrepair");
96
-
97
94
  // src/utils/common.ts
98
95
  function toErrorMessage(error) {
99
96
  if (error instanceof Error) {
@@ -311,184 +308,77 @@ function isOnlyWhitespace(value) {
311
308
  return true;
312
309
  }
313
310
 
314
- // src/extract.ts
315
- var RE_EMPTY_OBJECT = /^\{\s*\}$/;
316
- var RE_EMPTY_ARRAY = /^\[\s*\]$/;
317
- var RE_BOUNDARY_CHAR = /[\s,.;:!?`"'()\[\]{}<>]/;
318
- var DEFAULT_EXTRACTION_HEURISTICS = {
319
- firstPassMin: 12,
320
- firstPassCap: 30,
321
- firstPassMultiplier: 6,
322
- secondPassMin: 4,
323
- secondPassCap: 8,
324
- secondPassMultiplier: 2,
325
- hintMaxLength: 50000
326
- };
327
- function extractJsonCandidates(input, options = {}) {
328
- const maxCandidates = options.maxCandidates ?? 5;
329
- const acceptArrays = options.acceptArrays ?? true;
330
- const allowRepairHints = options.allowRepairHints ?? true;
331
- const heuristics = resolveExtractionHeuristics(options.heuristics);
332
- const extractionInput = input;
333
- const candidates = [];
334
- candidates.push(...extractFromMarkdown(extractionInput, acceptArrays));
335
- candidates.push(...scanBalancedSegments(extractionInput, acceptArrays));
336
- if (candidates.length === 0 && extractionInput.trim()) {
337
- const content = extractionInput.trim();
338
- candidates.push({
339
- id: "raw:fallback",
340
- source: "raw",
341
- content,
342
- start: 0,
343
- end: extractionInput.length,
344
- score: 10 + Math.floor(lengthScore(content.length) / 3)
345
- });
346
- }
347
- const prefiltered = prefilterByJsonShape(candidates, acceptArrays);
348
- const firstPassLimit = clamp(maxCandidates * heuristics.firstPassMultiplier, heuristics.firstPassMin, heuristics.firstPassCap);
349
- const firstPass = prefiltered.slice(0, firstPassLimit);
350
- const secondPassLimit = Math.min(firstPass.length, clamp(maxCandidates * heuristics.secondPassMultiplier, heuristics.secondPassMin, heuristics.secondPassCap));
351
- for (let i = 0;i < secondPassLimit; i += 1) {
352
- const candidate = firstPass[i];
353
- if (!candidate) {
354
- continue;
355
- }
356
- const parseHint = buildParseHint(candidate.content, allowRepairHints, heuristics.hintMaxLength);
357
- if (!parseHint) {
358
- continue;
359
- }
360
- candidate.parseHint = parseHint;
361
- candidate.score += parseHintBonus(parseHint);
311
+ // src/extract-parse-hint.ts
312
+ var import_jsonrepair = require("jsonrepair");
313
+ function buildParseHint(content, allowRepair, hintMaxLength) {
314
+ if (content.length > hintMaxLength) {
315
+ return null;
362
316
  }
363
- const sorted = sortCandidates(firstPass);
364
- const deduped = dedupeCandidates(sorted);
365
- return deduped.slice(0, maxCandidates).map((candidate, index) => ({
366
- id: `${candidate.source}:${index}`,
367
- source: candidate.source,
368
- content: candidate.content,
369
- language: candidate.language,
370
- parseHint: candidate.parseHint,
371
- start: candidate.start,
372
- end: candidate.end,
373
- score: candidate.score
374
- }));
375
- }
376
- function prefilterByJsonShape(candidates, acceptArrays) {
377
- const shaped = candidates.map((candidate) => {
378
- const shapeScore = jsonShapeScore(candidate.content, acceptArrays);
317
+ try {
379
318
  return {
380
- ...candidate,
381
- shapeScore,
382
- score: candidate.score + shapeScore
319
+ success: true,
320
+ parsed: JSON.parse(content),
321
+ repaired: null,
322
+ usedRepair: false,
323
+ stage: "parse",
324
+ error: ""
383
325
  };
384
- });
385
- const sorted = sortCandidates(shaped);
386
- const deduped = dedupeCandidates(sorted);
387
- const filtered = deduped.filter((candidate) => passesShapeFilter(candidate));
388
- if (filtered.length > 0) {
389
- const fallback = deduped.find((candidate) => !passesShapeFilter(candidate));
390
- if (fallback) {
391
- filtered.push(fallback);
326
+ } catch (directError) {
327
+ if (!allowRepair) {
328
+ return {
329
+ success: false,
330
+ parsed: null,
331
+ repaired: null,
332
+ usedRepair: false,
333
+ stage: "parse",
334
+ error: toErrorMessage(directError)
335
+ };
392
336
  }
393
- return sortCandidates(filtered);
394
- }
395
- return deduped.slice(0, Math.min(1, deduped.length));
396
- }
397
- function extractFromMarkdown(input, acceptArrays) {
398
- const blocks = extractMarkdownCodeBlocks(input);
399
- return blocks.flatMap((block, index) => {
400
- const language = block.language || null;
401
- const content = block.code.trim();
402
- if (!content) {
403
- return [];
337
+ let repaired;
338
+ try {
339
+ repaired = import_jsonrepair.jsonrepair(content);
340
+ } catch (repairError) {
341
+ return {
342
+ success: false,
343
+ parsed: null,
344
+ repaired: null,
345
+ usedRepair: true,
346
+ stage: "repair",
347
+ error: toErrorMessage(repairError)
348
+ };
404
349
  }
405
- if (!looksLikeJsonEnvelope(content, acceptArrays)) {
406
- return [];
350
+ try {
351
+ return {
352
+ success: true,
353
+ parsed: JSON.parse(repaired),
354
+ repaired,
355
+ usedRepair: true,
356
+ stage: "parse",
357
+ error: ""
358
+ };
359
+ } catch (parseError) {
360
+ return {
361
+ success: false,
362
+ parsed: null,
363
+ repaired,
364
+ usedRepair: true,
365
+ stage: "parse",
366
+ error: toErrorMessage(parseError || directError)
367
+ };
407
368
  }
408
- const langBonus = languageBonus(language);
409
- return [
410
- {
411
- id: `fenced:${index}`,
412
- source: "fenced",
413
- language,
414
- content,
415
- start: block.start,
416
- end: block.end,
417
- score: 260 + langBonus + lengthScore(content.length)
418
- }
419
- ];
420
- });
369
+ }
421
370
  }
422
- function scanBalancedSegments(input, acceptArrays) {
423
- const results = [];
424
- const stack = [];
425
- let inString = false;
426
- let quote = null;
427
- let escaped = false;
428
- for (let i = 0;i < input.length; i += 1) {
429
- const char = input[i];
430
- if (!char) {
431
- continue;
432
- }
433
- if (inString) {
434
- if (escaped) {
435
- escaped = false;
436
- continue;
437
- }
438
- if (char === "\\") {
439
- escaped = true;
440
- continue;
441
- }
442
- if (char === quote) {
443
- inString = false;
444
- quote = null;
445
- }
446
- continue;
447
- }
448
- const allowSingleQuoted = stack.length > 0;
449
- if (char === '"' || allowSingleQuoted && (char === "'" || char === "`")) {
450
- inString = true;
451
- quote = char;
452
- continue;
453
- }
454
- if (char === "{" || char === "[") {
455
- stack.push({ char, index: i });
456
- continue;
457
- }
458
- if (char !== "}" && char !== "]") {
459
- continue;
460
- }
461
- const expectedOpen = char === "}" ? "{" : "[";
462
- while (stack.length > 0 && stack[stack.length - 1]?.char !== expectedOpen) {
463
- stack.pop();
464
- }
465
- const opened = stack.pop();
466
- if (!opened) {
467
- continue;
468
- }
469
- if (stack.length > 0) {
470
- continue;
471
- }
472
- if (!acceptArrays && opened.char === "[") {
473
- continue;
474
- }
475
- const content = input.slice(opened.index, i + 1).trim();
476
- if (!content) {
477
- continue;
478
- }
479
- const rootBonus = opened.char === "{" ? 40 : 20;
480
- const boundaryBonus = boundaryScore(input, opened.index, i + 1);
481
- results.push({
482
- id: `scan:${results.length}`,
483
- source: "scan",
484
- content,
485
- start: opened.index,
486
- end: i + 1,
487
- score: 120 + rootBonus + boundaryBonus + lengthScore(content.length)
488
- });
371
+ function parseHintBonus(hint) {
372
+ if (hint.success) {
373
+ return hint.usedRepair ? 70 : 120;
489
374
  }
490
- return results;
375
+ return hint.usedRepair ? -20 : -10;
491
376
  }
377
+
378
+ // src/extract-shape.ts
379
+ var RE_EMPTY_OBJECT = /^\{\s*\}$/;
380
+ var RE_EMPTY_ARRAY = /^\[\s*\]$/;
381
+ var RE_BOUNDARY_CHAR = /[\s,.;:!?`"'()[\]{}<>]/;
492
382
  function looksLikeJsonEnvelope(content, acceptArrays) {
493
383
  const trimmed = content.trim();
494
384
  if (trimmed.startsWith("{")) {
@@ -597,95 +487,6 @@ function passesShapeFilter(candidate) {
597
487
  }
598
488
  return candidate.shapeScore >= 35;
599
489
  }
600
- function buildParseHint(content, allowRepair, hintMaxLength) {
601
- if (content.length > hintMaxLength) {
602
- return null;
603
- }
604
- try {
605
- return {
606
- success: true,
607
- parsed: JSON.parse(content),
608
- repaired: null,
609
- usedRepair: false,
610
- stage: "parse",
611
- error: ""
612
- };
613
- } catch (directError) {
614
- if (!allowRepair) {
615
- return {
616
- success: false,
617
- parsed: null,
618
- repaired: null,
619
- usedRepair: false,
620
- stage: "parse",
621
- error: toErrorMessage(directError)
622
- };
623
- }
624
- let repaired;
625
- try {
626
- repaired = import_jsonrepair.jsonrepair(content);
627
- } catch (repairError) {
628
- return {
629
- success: false,
630
- parsed: null,
631
- repaired: null,
632
- usedRepair: true,
633
- stage: "repair",
634
- error: toErrorMessage(repairError)
635
- };
636
- }
637
- try {
638
- return {
639
- success: true,
640
- parsed: JSON.parse(repaired),
641
- repaired,
642
- usedRepair: true,
643
- stage: "parse",
644
- error: ""
645
- };
646
- } catch (parseError) {
647
- return {
648
- success: false,
649
- parsed: null,
650
- repaired,
651
- usedRepair: true,
652
- stage: "parse",
653
- error: toErrorMessage(parseError || directError)
654
- };
655
- }
656
- }
657
- }
658
- function resolveExtractionHeuristics(input) {
659
- const merged = {
660
- ...DEFAULT_EXTRACTION_HEURISTICS,
661
- ...input
662
- };
663
- const firstPassMin = normalizeInteger(merged.firstPassMin, DEFAULT_EXTRACTION_HEURISTICS.firstPassMin);
664
- const firstPassCap = Math.max(firstPassMin, normalizeInteger(merged.firstPassCap, DEFAULT_EXTRACTION_HEURISTICS.firstPassCap));
665
- const secondPassMin = normalizeInteger(merged.secondPassMin, DEFAULT_EXTRACTION_HEURISTICS.secondPassMin);
666
- const secondPassCap = Math.max(secondPassMin, normalizeInteger(merged.secondPassCap, DEFAULT_EXTRACTION_HEURISTICS.secondPassCap));
667
- return {
668
- firstPassMin,
669
- firstPassCap,
670
- firstPassMultiplier: normalizeInteger(merged.firstPassMultiplier, DEFAULT_EXTRACTION_HEURISTICS.firstPassMultiplier),
671
- secondPassMin,
672
- secondPassCap,
673
- secondPassMultiplier: normalizeInteger(merged.secondPassMultiplier, DEFAULT_EXTRACTION_HEURISTICS.secondPassMultiplier),
674
- hintMaxLength: normalizeInteger(merged.hintMaxLength, DEFAULT_EXTRACTION_HEURISTICS.hintMaxLength)
675
- };
676
- }
677
- function normalizeInteger(value, fallback) {
678
- if (typeof value !== "number" || !Number.isFinite(value)) {
679
- return fallback;
680
- }
681
- return Math.max(0, Math.floor(value));
682
- }
683
- function parseHintBonus(hint) {
684
- if (hint.success) {
685
- return hint.usedRepair ? 70 : 120;
686
- }
687
- return hint.usedRepair ? -20 : -10;
688
- }
689
490
  function hasBalancedJsonDelimiters(input) {
690
491
  const stack = [];
691
492
  let inString = false;
@@ -771,6 +572,207 @@ function dedupeCandidates(candidates) {
771
572
  function clamp(value, min, max) {
772
573
  return Math.max(min, Math.min(max, Math.floor(value)));
773
574
  }
575
+ function normalizeInteger(value, fallback) {
576
+ if (typeof value !== "number" || !Number.isFinite(value)) {
577
+ return fallback;
578
+ }
579
+ return Math.max(0, Math.floor(value));
580
+ }
581
+ function resolveExtractionHeuristics(input, defaults) {
582
+ const merged = {
583
+ ...defaults,
584
+ ...input
585
+ };
586
+ const firstPassMin = normalizeInteger(merged.firstPassMin, defaults.firstPassMin);
587
+ const firstPassCap = Math.max(firstPassMin, normalizeInteger(merged.firstPassCap, defaults.firstPassCap));
588
+ const secondPassMin = normalizeInteger(merged.secondPassMin, defaults.secondPassMin);
589
+ const secondPassCap = Math.max(secondPassMin, normalizeInteger(merged.secondPassCap, defaults.secondPassCap));
590
+ return {
591
+ firstPassMin,
592
+ firstPassCap,
593
+ firstPassMultiplier: normalizeInteger(merged.firstPassMultiplier, defaults.firstPassMultiplier),
594
+ secondPassMin,
595
+ secondPassCap,
596
+ secondPassMultiplier: normalizeInteger(merged.secondPassMultiplier, defaults.secondPassMultiplier),
597
+ hintMaxLength: normalizeInteger(merged.hintMaxLength, defaults.hintMaxLength)
598
+ };
599
+ }
600
+
601
+ // src/extract.ts
602
+ var DEFAULT_EXTRACTION_HEURISTICS = {
603
+ firstPassMin: 12,
604
+ firstPassCap: 30,
605
+ firstPassMultiplier: 6,
606
+ secondPassMin: 4,
607
+ secondPassCap: 8,
608
+ secondPassMultiplier: 2,
609
+ hintMaxLength: 50000
610
+ };
611
+ function extractJsonCandidates(input, options = {}) {
612
+ const maxCandidates = options.maxCandidates ?? 5;
613
+ const acceptArrays = options.acceptArrays ?? true;
614
+ const allowRepairHints = options.allowRepairHints ?? true;
615
+ const heuristics = resolveExtractionHeuristics(options.heuristics, DEFAULT_EXTRACTION_HEURISTICS);
616
+ const extractionInput = input;
617
+ const candidates = [];
618
+ candidates.push(...extractFromMarkdown(extractionInput, acceptArrays));
619
+ candidates.push(...scanBalancedSegments(extractionInput, acceptArrays));
620
+ if (candidates.length === 0 && extractionInput.trim()) {
621
+ const content = extractionInput.trim();
622
+ candidates.push({
623
+ id: "raw:fallback",
624
+ source: "raw",
625
+ content,
626
+ start: 0,
627
+ end: extractionInput.length,
628
+ score: 10 + Math.floor(lengthScore(content.length) / 3)
629
+ });
630
+ }
631
+ const prefiltered = prefilterByJsonShape(candidates, acceptArrays);
632
+ const firstPassLimit = clamp(maxCandidates * heuristics.firstPassMultiplier, heuristics.firstPassMin, heuristics.firstPassCap);
633
+ const firstPass = prefiltered.slice(0, firstPassLimit);
634
+ const secondPassLimit = Math.min(firstPass.length, clamp(maxCandidates * heuristics.secondPassMultiplier, heuristics.secondPassMin, heuristics.secondPassCap));
635
+ for (let i = 0;i < secondPassLimit; i += 1) {
636
+ const candidate = firstPass[i];
637
+ if (!candidate) {
638
+ continue;
639
+ }
640
+ const parseHint = buildParseHint(candidate.content, allowRepairHints, heuristics.hintMaxLength);
641
+ if (!parseHint) {
642
+ continue;
643
+ }
644
+ candidate.parseHint = parseHint;
645
+ candidate.score += parseHintBonus(parseHint);
646
+ }
647
+ const sorted = sortCandidates(firstPass);
648
+ const deduped = dedupeCandidates(sorted);
649
+ return deduped.slice(0, maxCandidates).map((candidate, index) => ({
650
+ id: `${candidate.source}:${index}`,
651
+ source: candidate.source,
652
+ content: candidate.content,
653
+ language: candidate.language,
654
+ parseHint: candidate.parseHint,
655
+ start: candidate.start,
656
+ end: candidate.end,
657
+ score: candidate.score
658
+ }));
659
+ }
660
+ function prefilterByJsonShape(candidates, acceptArrays) {
661
+ const shaped = candidates.map((candidate) => {
662
+ const shapeScore = jsonShapeScore(candidate.content, acceptArrays);
663
+ return {
664
+ ...candidate,
665
+ shapeScore,
666
+ score: candidate.score + shapeScore
667
+ };
668
+ });
669
+ const sorted = sortCandidates(shaped);
670
+ const deduped = dedupeCandidates(sorted);
671
+ const filtered = deduped.filter((candidate) => passesShapeFilter(candidate));
672
+ if (filtered.length > 0) {
673
+ const fallback = deduped.find((candidate) => !passesShapeFilter(candidate));
674
+ if (fallback) {
675
+ filtered.push(fallback);
676
+ }
677
+ return sortCandidates(filtered);
678
+ }
679
+ return deduped.slice(0, Math.min(1, deduped.length));
680
+ }
681
+ function extractFromMarkdown(input, acceptArrays) {
682
+ const blocks = extractMarkdownCodeBlocks(input);
683
+ return blocks.flatMap((block, index) => {
684
+ const language = block.language || null;
685
+ const content = block.code.trim();
686
+ if (!content) {
687
+ return [];
688
+ }
689
+ if (!looksLikeJsonEnvelope(content, acceptArrays)) {
690
+ return [];
691
+ }
692
+ const langBonus = languageBonus(language);
693
+ return [
694
+ {
695
+ id: `fenced:${index}`,
696
+ source: "fenced",
697
+ language,
698
+ content,
699
+ start: block.start,
700
+ end: block.end,
701
+ score: 260 + langBonus + lengthScore(content.length)
702
+ }
703
+ ];
704
+ });
705
+ }
706
+ function scanBalancedSegments(input, acceptArrays) {
707
+ const results = [];
708
+ const stack = [];
709
+ let inString = false;
710
+ let quote = null;
711
+ let escaped = false;
712
+ for (let i = 0;i < input.length; i += 1) {
713
+ const char = input[i];
714
+ if (!char) {
715
+ continue;
716
+ }
717
+ if (inString) {
718
+ if (escaped) {
719
+ escaped = false;
720
+ continue;
721
+ }
722
+ if (char === "\\") {
723
+ escaped = true;
724
+ continue;
725
+ }
726
+ if (char === quote) {
727
+ inString = false;
728
+ quote = null;
729
+ }
730
+ continue;
731
+ }
732
+ const allowSingleQuoted = stack.length > 0;
733
+ if (char === '"' || allowSingleQuoted && (char === "'" || char === "`")) {
734
+ inString = true;
735
+ quote = char;
736
+ continue;
737
+ }
738
+ if (char === "{" || char === "[") {
739
+ stack.push({ char, index: i });
740
+ continue;
741
+ }
742
+ if (char !== "}" && char !== "]") {
743
+ continue;
744
+ }
745
+ const expectedOpen = char === "}" ? "{" : "[";
746
+ while (stack.length > 0 && stack[stack.length - 1]?.char !== expectedOpen) {
747
+ stack.pop();
748
+ }
749
+ const opened = stack.pop();
750
+ if (!opened) {
751
+ continue;
752
+ }
753
+ if (stack.length > 0) {
754
+ continue;
755
+ }
756
+ if (!acceptArrays && opened.char === "[") {
757
+ continue;
758
+ }
759
+ const content = input.slice(opened.index, i + 1).trim();
760
+ if (!content) {
761
+ continue;
762
+ }
763
+ const rootBonus = opened.char === "{" ? 40 : 20;
764
+ const boundaryBonus = boundaryScore(input, opened.index, i + 1);
765
+ results.push({
766
+ id: `scan:${results.length}`,
767
+ source: "scan",
768
+ content,
769
+ start: opened.index,
770
+ end: i + 1,
771
+ score: 120 + rootBonus + boundaryBonus + lengthScore(content.length)
772
+ });
773
+ }
774
+ return results;
775
+ }
774
776
  // src/schema.ts
775
777
  var RE_SIMPLE_IDENTIFIER = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
776
778
  var RE_WHITESPACE = /\s+/g;
@@ -1219,17 +1221,107 @@ function findSSEBoundary(buffer) {
1219
1221
  return Math.max(crlfIndex, lfIndex);
1220
1222
  }
1221
1223
 
1222
- // src/providers/mcp-runtime.ts
1223
- var DEFAULT_MAX_TOOL_ROUNDS = 100;
1224
- async function resolveMCPToolset(clients) {
1225
- if (!Array.isArray(clients) || clients.length === 0) {
1226
- return {
1227
- tools: [],
1228
- byName: new Map
1229
- };
1230
- }
1231
- const listed = [];
1232
- for (const client of clients) {
1224
+ // src/providers/mcp-runtime-debug.ts
1225
+ function formatToolExecutionDebugLine(execution) {
1226
+ const status = execution.error ? "error" : "ok";
1227
+ const scope = [execution.provider, execution.model].filter(Boolean).join("/") || "unknown";
1228
+ const toolRef = execution.clientId ? `${execution.clientId}:${execution.name ?? "unknown"}` : execution.name ?? "unknown";
1229
+ const duration = typeof execution.durationMs === "number" ? ` ${execution.durationMs}ms` : "";
1230
+ const base = `[tool:mcp:${status}] ${scope} ${toolRef}#${execution.callId}${duration}`;
1231
+ if (execution.error) {
1232
+ return `${base} -> ${execution.error}`;
1233
+ }
1234
+ return base;
1235
+ }
1236
+ function emitToolExecution(request, execution) {
1237
+ request.onToolExecution?.(execution);
1238
+ const debug = resolveToolDebugOptions(request.toolDebug);
1239
+ if (!debug.enabled) {
1240
+ return;
1241
+ }
1242
+ debug.logger(formatToolExecutionDebugLine(execution));
1243
+ if (debug.includeRequest) {
1244
+ debug.logger(formatToolExecutionRequestDebugLine(execution, debug));
1245
+ }
1246
+ if (debug.includeResult && (!execution.error || debug.includeResultOnError)) {
1247
+ debug.logger(formatToolExecutionResultDebugLine(execution, debug));
1248
+ }
1249
+ }
1250
+ function resolveToolDebugOptions(value) {
1251
+ if (value === true) {
1252
+ return {
1253
+ enabled: true,
1254
+ logger: defaultToolDebugLogger,
1255
+ includeRequest: true,
1256
+ includeResult: true,
1257
+ includeResultOnError: true,
1258
+ pretty: false
1259
+ };
1260
+ }
1261
+ if (value === undefined || value === false) {
1262
+ return {
1263
+ enabled: false,
1264
+ logger: () => {
1265
+ return;
1266
+ },
1267
+ includeRequest: false,
1268
+ includeResult: false,
1269
+ includeResultOnError: false,
1270
+ pretty: false
1271
+ };
1272
+ }
1273
+ return {
1274
+ enabled: value.enabled ?? true,
1275
+ logger: value.logger ?? defaultToolDebugLogger,
1276
+ includeRequest: value.includeRequest ?? true,
1277
+ includeResult: value.includeResult ?? true,
1278
+ includeResultOnError: value.includeResultOnError ?? true,
1279
+ pretty: value.pretty ?? false
1280
+ };
1281
+ }
1282
+ function defaultToolDebugLogger(line) {
1283
+ const { log } = globalThis.console;
1284
+ log(line);
1285
+ }
1286
+ function formatToolExecutionRequestDebugLine(execution, debug) {
1287
+ const scope = [execution.provider, execution.model].filter(Boolean).join("/") || "unknown";
1288
+ const toolRef = execution.clientId ? `${execution.clientId}:${execution.name ?? "unknown"}` : execution.name ?? "unknown";
1289
+ const payload = formatDebugPayload(execution.arguments, debug.pretty);
1290
+ return `[tool:mcp:request] ${scope} ${toolRef}#${execution.callId} arguments=${payload}`;
1291
+ }
1292
+ function formatToolExecutionResultDebugLine(execution, debug) {
1293
+ const scope = [execution.provider, execution.model].filter(Boolean).join("/") || "unknown";
1294
+ const toolRef = execution.clientId ? `${execution.clientId}:${execution.name ?? "unknown"}` : execution.name ?? "unknown";
1295
+ if (execution.error) {
1296
+ const payload2 = formatDebugPayload({ error: execution.error }, debug.pretty);
1297
+ return `[tool:mcp:result:error] ${scope} ${toolRef}#${execution.callId} output=${payload2}`;
1298
+ }
1299
+ const payload = formatDebugPayload(execution.output, debug.pretty);
1300
+ return `[tool:mcp:result:ok] ${scope} ${toolRef}#${execution.callId} output=${payload}`;
1301
+ }
1302
+ function formatDebugPayload(value, pretty) {
1303
+ if (value === undefined) {
1304
+ return "undefined";
1305
+ }
1306
+ try {
1307
+ const serialized = JSON.stringify(value, null, pretty ? 2 : 0);
1308
+ return serialized ?? "undefined";
1309
+ } catch {
1310
+ return String(value);
1311
+ }
1312
+ }
1313
+
1314
+ // src/providers/mcp-runtime.ts
1315
+ var DEFAULT_MAX_TOOL_ROUNDS = 100;
1316
+ async function resolveMCPToolset(clients) {
1317
+ if (!Array.isArray(clients) || clients.length === 0) {
1318
+ return {
1319
+ tools: [],
1320
+ byName: new Map
1321
+ };
1322
+ }
1323
+ const listed = [];
1324
+ for (const client of clients) {
1233
1325
  let cursor;
1234
1326
  do {
1235
1327
  const page = await client.listTools(cursor ? { cursor } : undefined);
@@ -1411,90 +1503,6 @@ function stringifyToolOutput(value) {
1411
1503
  }
1412
1504
  return JSON.stringify(value ?? null);
1413
1505
  }
1414
- function formatToolExecutionDebugLine(execution) {
1415
- const status = execution.error ? "error" : "ok";
1416
- const scope = [execution.provider, execution.model].filter(Boolean).join("/") || "unknown";
1417
- const toolRef = execution.clientId ? `${execution.clientId}:${execution.name ?? "unknown"}` : execution.name ?? "unknown";
1418
- const duration = typeof execution.durationMs === "number" ? ` ${execution.durationMs}ms` : "";
1419
- const base = `[tool:mcp:${status}] ${scope} ${toolRef}#${execution.callId}${duration}`;
1420
- if (execution.error) {
1421
- return `${base} -> ${execution.error}`;
1422
- }
1423
- return base;
1424
- }
1425
- function emitToolExecution(request, execution) {
1426
- request.onToolExecution?.(execution);
1427
- const debug = resolveToolDebugOptions(request.toolDebug);
1428
- if (!debug.enabled) {
1429
- return;
1430
- }
1431
- debug.logger(formatToolExecutionDebugLine(execution));
1432
- if (debug.includeRequest) {
1433
- debug.logger(formatToolExecutionRequestDebugLine(execution, debug));
1434
- }
1435
- if (debug.includeResult && (!execution.error || debug.includeResultOnError)) {
1436
- debug.logger(formatToolExecutionResultDebugLine(execution, debug));
1437
- }
1438
- }
1439
- function resolveToolDebugOptions(value) {
1440
- if (value === true) {
1441
- return {
1442
- enabled: true,
1443
- logger: (line) => console.log(line),
1444
- includeRequest: true,
1445
- includeResult: true,
1446
- includeResultOnError: true,
1447
- pretty: false
1448
- };
1449
- }
1450
- if (value === undefined || value === false) {
1451
- return {
1452
- enabled: false,
1453
- logger: () => {
1454
- return;
1455
- },
1456
- includeRequest: false,
1457
- includeResult: false,
1458
- includeResultOnError: false,
1459
- pretty: false
1460
- };
1461
- }
1462
- return {
1463
- enabled: value.enabled ?? true,
1464
- logger: value.logger ?? ((line) => console.log(line)),
1465
- includeRequest: value.includeRequest ?? true,
1466
- includeResult: value.includeResult ?? true,
1467
- includeResultOnError: value.includeResultOnError ?? true,
1468
- pretty: value.pretty ?? false
1469
- };
1470
- }
1471
- function formatToolExecutionRequestDebugLine(execution, debug) {
1472
- const scope = [execution.provider, execution.model].filter(Boolean).join("/") || "unknown";
1473
- const toolRef = execution.clientId ? `${execution.clientId}:${execution.name ?? "unknown"}` : execution.name ?? "unknown";
1474
- const payload = formatDebugPayload(execution.arguments, debug.pretty);
1475
- return `[tool:mcp:request] ${scope} ${toolRef}#${execution.callId} arguments=${payload}`;
1476
- }
1477
- function formatToolExecutionResultDebugLine(execution, debug) {
1478
- const scope = [execution.provider, execution.model].filter(Boolean).join("/") || "unknown";
1479
- const toolRef = execution.clientId ? `${execution.clientId}:${execution.name ?? "unknown"}` : execution.name ?? "unknown";
1480
- if (execution.error) {
1481
- const payload2 = formatDebugPayload({ error: execution.error }, debug.pretty);
1482
- return `[tool:mcp:result:error] ${scope} ${toolRef}#${execution.callId} output=${payload2}`;
1483
- }
1484
- const payload = formatDebugPayload(execution.output, debug.pretty);
1485
- return `[tool:mcp:result:ok] ${scope} ${toolRef}#${execution.callId} output=${payload}`;
1486
- }
1487
- function formatDebugPayload(value, pretty) {
1488
- if (value === undefined) {
1489
- return "undefined";
1490
- }
1491
- try {
1492
- const serialized = JSON.stringify(value, null, pretty ? 2 : 0);
1493
- return serialized ?? "undefined";
1494
- } catch {
1495
- return String(value);
1496
- }
1497
- }
1498
1506
  function countNameCollisions(names) {
1499
1507
  const out = new Map;
1500
1508
  for (const name of names) {
@@ -1691,103 +1699,89 @@ function createOpenAICompatibleAdapter(options) {
1691
1699
  if (usesMCP) {
1692
1700
  return streamWithChatCompletionsWithMCP(options, fetcher, path, request, callbacks);
1693
1701
  }
1694
- const response = await fetcher(buildURL(options.baseURL, path), {
1695
- method: "POST",
1696
- headers: buildHeaders(options),
1697
- body: JSON.stringify(cleanUndefined({
1698
- ...options.defaultBody,
1699
- ...request.body,
1700
- model: options.model,
1701
- messages: buildMessages(request),
1702
- temperature: request.temperature,
1703
- reasoning_effort: toOpenAIReasoningEffort(request.reasoningEffort),
1704
- max_tokens: request.maxTokens,
1705
- stream: true
1706
- })),
1707
- signal: request.signal
1708
- });
1709
- if (!response.ok) {
1710
- const message = await response.text();
1711
- throw new Error(`HTTP ${response.status}: ${message}`);
1712
- }
1713
- callbacks.onStart?.();
1714
- let text = "";
1715
- let reasoning = "";
1716
- let usage;
1717
- let finishReason;
1718
- await consumeSSE(response, (data) => {
1719
- if (data === "[DONE]") {
1720
- return;
1721
- }
1722
- const json = safeJSONParse(data);
1723
- if (!isRecord2(json)) {
1724
- return;
1725
- }
1726
- const delta = pickAssistantDelta(json);
1727
- const reasoningDelta = pickAssistantReasoningDelta(json);
1728
- const chunkUsage = pickUsage(json);
1729
- const chunkFinishReason = pickFinishReason(json);
1730
- usage = preferLatestUsage(usage, chunkUsage);
1731
- if (chunkFinishReason) {
1732
- finishReason = chunkFinishReason;
1733
- }
1734
- if (delta) {
1735
- text += delta;
1736
- callbacks.onToken?.(delta);
1737
- }
1738
- if (reasoningDelta) {
1739
- reasoning += reasoningDelta;
1740
- }
1741
- if (delta || reasoningDelta || chunkUsage || chunkFinishReason) {
1742
- const chunk = {
1743
- textDelta: delta,
1744
- reasoningDelta: reasoningDelta || undefined,
1745
- raw: json,
1746
- usage: chunkUsage,
1747
- finishReason: chunkFinishReason
1748
- };
1749
- callbacks.onChunk?.(chunk);
1750
- }
1751
- });
1752
- const out = {
1753
- text,
1754
- reasoning: reasoning.length > 0 ? reasoning : undefined,
1755
- usage,
1756
- finishReason
1757
- };
1758
- callbacks.onComplete?.(out);
1759
- return out;
1702
+ return streamWithChatCompletionsPassThrough(options, fetcher, path, request, callbacks);
1760
1703
  },
1761
1704
  async embed(request) {
1762
- const body = cleanUndefined({
1763
- ...options.defaultBody,
1764
- ...request.body,
1765
- model: request.model ?? options.model,
1766
- input: request.input,
1767
- dimensions: request.dimensions,
1768
- encoding_format: "float"
1769
- });
1770
- const response = await fetcher(buildURL(options.baseURL, embeddingPath), {
1771
- method: "POST",
1772
- headers: buildHeaders(options),
1773
- body: JSON.stringify(body)
1774
- });
1775
- if (!response.ok) {
1776
- const message = await response.text();
1777
- throw new Error(`HTTP ${response.status}: ${message}`);
1778
- }
1779
- const json = await response.json();
1780
- const data = json.data;
1781
- if (!Array.isArray(data)) {
1782
- throw new Error("Unexpected embedding response: missing data array");
1783
- }
1784
- return {
1785
- embeddings: data.map((d) => isRecord2(d) && Array.isArray(d.embedding) ? d.embedding : []),
1786
- model: pickString(json.model) ?? body.model,
1787
- usage: pickUsage(json),
1788
- raw: json
1789
- };
1705
+ return embedOpenAI(options, fetcher, embeddingPath, request);
1706
+ }
1707
+ };
1708
+ }
1709
+ async function streamWithChatCompletionsPassThrough(options, fetcher, path, request, callbacks) {
1710
+ const response = await sendOpenAIRequest(options, fetcher, path, request, buildChatCompletionsBody(options, request, {
1711
+ messages: buildMessages(request),
1712
+ stream: true
1713
+ }));
1714
+ if (!response.ok) {
1715
+ const message = await response.text();
1716
+ throw new Error(`HTTP ${response.status}: ${message}`);
1717
+ }
1718
+ callbacks.onStart?.();
1719
+ let text = "";
1720
+ let reasoning = "";
1721
+ let usage;
1722
+ let finishReason;
1723
+ await consumeSSE(response, (data) => {
1724
+ if (data === "[DONE]") {
1725
+ return;
1726
+ }
1727
+ const json = safeJSONParse(data);
1728
+ if (!isRecord2(json)) {
1729
+ return;
1730
+ }
1731
+ const delta = pickAssistantDelta(json);
1732
+ const reasoningDelta = pickAssistantReasoningDelta(json);
1733
+ const chunkUsage = pickUsage(json);
1734
+ const chunkFinishReason = pickFinishReason(json);
1735
+ usage = preferLatestUsage(usage, chunkUsage);
1736
+ if (chunkFinishReason) {
1737
+ finishReason = chunkFinishReason;
1738
+ }
1739
+ if (delta) {
1740
+ text += delta;
1741
+ callbacks.onToken?.(delta);
1790
1742
  }
1743
+ if (reasoningDelta) {
1744
+ reasoning += reasoningDelta;
1745
+ }
1746
+ emitOpenAIStreamChunk(callbacks, undefined, json, delta, reasoningDelta, chunkUsage, chunkFinishReason);
1747
+ });
1748
+ const out = {
1749
+ text,
1750
+ reasoning: reasoning.length > 0 ? reasoning : undefined,
1751
+ usage,
1752
+ finishReason
1753
+ };
1754
+ callbacks.onComplete?.(out);
1755
+ return out;
1756
+ }
1757
+ async function embedOpenAI(options, fetcher, path, request) {
1758
+ const body = cleanUndefined({
1759
+ ...options.defaultBody,
1760
+ ...request.body,
1761
+ model: request.model ?? options.model,
1762
+ input: request.input,
1763
+ dimensions: request.dimensions,
1764
+ encoding_format: "float"
1765
+ });
1766
+ const response = await fetcher(buildURL(options.baseURL, path), {
1767
+ method: "POST",
1768
+ headers: buildHeaders(options),
1769
+ body: JSON.stringify(body)
1770
+ });
1771
+ if (!response.ok) {
1772
+ const message = await response.text();
1773
+ throw new Error(`HTTP ${response.status}: ${message}`);
1774
+ }
1775
+ const json = await response.json();
1776
+ const data = json.data;
1777
+ if (!Array.isArray(data)) {
1778
+ throw new Error("Unexpected embedding response: missing data array");
1779
+ }
1780
+ return {
1781
+ embeddings: data.map((d) => isRecord2(d) && Array.isArray(d.embedding) ? d.embedding : []),
1782
+ model: pickString(json.model) ?? body.model,
1783
+ usage: pickUsage(json),
1784
+ raw: json
1791
1785
  };
1792
1786
  }
1793
1787
  async function completeOpenAIRequest(options, fetcher, chatPath, responsesPath, request) {
@@ -1804,22 +1798,80 @@ async function completeOpenAIRequest(options, fetcher, chatPath, responsesPath,
1804
1798
  }
1805
1799
  return completeWithChatCompletionsPassThrough(options, fetcher, chatPath, request);
1806
1800
  }
1807
- async function completeWithChatCompletionsPassThrough(options, fetcher, path, request) {
1808
- const response = await fetcher(buildURL(options.baseURL, path), {
1801
+ function buildChatCompletionsBody(options, request, overrides) {
1802
+ return buildOpenAIRequestBody(options, request, "max_tokens", overrides);
1803
+ }
1804
+ function buildResponsesBody(options, request, overrides) {
1805
+ return buildOpenAIRequestBody(options, request, "max_output_tokens", overrides);
1806
+ }
1807
+ function buildOpenAIRequestBody(options, request, maxTokenKey, overrides) {
1808
+ return cleanUndefined({
1809
+ ...options.defaultBody,
1810
+ ...request.body,
1811
+ model: options.model,
1812
+ temperature: request.temperature,
1813
+ reasoning_effort: toOpenAIReasoningEffort(request.reasoningEffort),
1814
+ [maxTokenKey]: request.maxTokens,
1815
+ ...overrides
1816
+ });
1817
+ }
1818
+ function sendOpenAIRequest(options, fetcher, path, request, body) {
1819
+ return fetcher(buildURL(options.baseURL, path), {
1809
1820
  method: "POST",
1810
1821
  headers: buildHeaders(options),
1811
- body: JSON.stringify(cleanUndefined({
1812
- ...options.defaultBody,
1813
- ...request.body,
1814
- model: options.model,
1815
- messages: buildMessages(request),
1816
- temperature: request.temperature,
1817
- reasoning_effort: toOpenAIReasoningEffort(request.reasoningEffort),
1818
- max_tokens: request.maxTokens,
1819
- stream: false
1820
- })),
1822
+ body: JSON.stringify(body),
1821
1823
  signal: request.signal
1822
1824
  });
1825
+ }
1826
+ async function sendOpenAIJsonRequest(options, fetcher, path, request, body) {
1827
+ const response = await sendOpenAIRequest(options, fetcher, path, request, body);
1828
+ if (!response.ok) {
1829
+ const message = await response.text();
1830
+ throw new Error(`HTTP ${response.status}: ${message}`);
1831
+ }
1832
+ return await response.json();
1833
+ }
1834
+ function createResponsesMCPState(request) {
1835
+ return {
1836
+ input: buildResponsesInput(request),
1837
+ previousResponseId: pickString(isRecord2(request.body) ? request.body.previous_response_id : undefined),
1838
+ aggregatedUsage: undefined,
1839
+ finishReason: undefined,
1840
+ lastPayload: undefined,
1841
+ executedToolCalls: [],
1842
+ toolExecutions: [],
1843
+ reasoningBlocks: []
1844
+ };
1845
+ }
1846
+ function buildResponsesMCPResult(state, text, raw) {
1847
+ return {
1848
+ text,
1849
+ reasoning: joinReasoningBlocks(state.reasoningBlocks) || undefined,
1850
+ reasoningBlocks: state.reasoningBlocks.length > 0 ? state.reasoningBlocks : undefined,
1851
+ raw,
1852
+ usage: state.aggregatedUsage,
1853
+ finishReason: state.finishReason,
1854
+ toolCalls: state.executedToolCalls.length > 0 ? state.executedToolCalls : undefined,
1855
+ toolExecutions: state.toolExecutions.length > 0 ? state.toolExecutions : undefined
1856
+ };
1857
+ }
1858
+ function emitOpenAIStreamChunk(callbacks, round, raw, delta, reasoningDelta, usage, finishReason) {
1859
+ if (delta || reasoningDelta || usage || finishReason) {
1860
+ callbacks.onChunk?.({
1861
+ textDelta: delta,
1862
+ reasoningDelta: reasoningDelta || undefined,
1863
+ ...round !== undefined ? { turnIndex: round } : {},
1864
+ raw,
1865
+ usage,
1866
+ finishReason
1867
+ });
1868
+ }
1869
+ }
1870
+ async function completeWithChatCompletionsPassThrough(options, fetcher, path, request) {
1871
+ const response = await sendOpenAIRequest(options, fetcher, path, request, buildChatCompletionsBody(options, request, {
1872
+ messages: buildMessages(request),
1873
+ stream: false
1874
+ }));
1823
1875
  if (!response.ok) {
1824
1876
  const message = await response.text();
1825
1877
  throw new Error(`HTTP ${response.status}: ${message}`);
@@ -1871,28 +1923,12 @@ async function completeWithChatCompletionsWithMCP(options, fetcher, path, reques
1871
1923
  for (let round = 1;round <= maxToolRounds + 1; round += 1) {
1872
1924
  const mcpToolset = await resolveMCPToolset(request.mcpClients);
1873
1925
  const transportTools = toProviderFunctionTools(mcpToolset);
1874
- const response = await fetcher(buildURL(options.baseURL, path), {
1875
- method: "POST",
1876
- headers: buildHeaders(options),
1877
- body: JSON.stringify(cleanUndefined({
1878
- ...options.defaultBody,
1879
- ...request.body,
1880
- model: options.model,
1881
- messages,
1882
- temperature: request.temperature,
1883
- reasoning_effort: toOpenAIReasoningEffort(request.reasoningEffort),
1884
- max_tokens: request.maxTokens,
1885
- tools: transportTools,
1886
- tool_choice: request.toolChoice,
1887
- parallel_tool_calls: request.parallelToolCalls
1888
- })),
1889
- signal: request.signal
1890
- });
1891
- if (!response.ok) {
1892
- const message = await response.text();
1893
- throw new Error(`HTTP ${response.status}: ${message}`);
1894
- }
1895
- const payload = await response.json();
1926
+ const payload = await sendOpenAIJsonRequest(options, fetcher, path, request, buildChatCompletionsBody(options, request, {
1927
+ messages,
1928
+ tools: transportTools,
1929
+ tool_choice: request.toolChoice,
1930
+ parallel_tool_calls: request.parallelToolCalls
1931
+ }));
1896
1932
  lastPayload = payload;
1897
1933
  aggregatedUsage = mergeUsage(aggregatedUsage, pickUsage(payload));
1898
1934
  finishReason = pickFinishReason(payload);
@@ -1947,26 +1983,10 @@ async function completeWithChatCompletionsWithMCP(options, fetcher, path, reques
1947
1983
  }
1948
1984
  async function completeWithResponsesAPIPassThrough(options, fetcher, path, request) {
1949
1985
  const body = isRecord2(request.body) ? request.body : undefined;
1950
- const response = await fetcher(buildURL(options.baseURL, path), {
1951
- method: "POST",
1952
- headers: buildHeaders(options),
1953
- body: JSON.stringify(cleanUndefined({
1954
- ...options.defaultBody,
1955
- ...request.body,
1956
- model: options.model,
1957
- input: buildResponsesInput(request),
1958
- previous_response_id: pickString(body?.previous_response_id),
1959
- temperature: request.temperature,
1960
- reasoning_effort: toOpenAIReasoningEffort(request.reasoningEffort),
1961
- max_output_tokens: request.maxTokens
1962
- })),
1963
- signal: request.signal
1964
- });
1965
- if (!response.ok) {
1966
- const message = await response.text();
1967
- throw new Error(`HTTP ${response.status}: ${message}`);
1968
- }
1969
- const payload = await response.json();
1986
+ const payload = await sendOpenAIJsonRequest(options, fetcher, path, request, buildResponsesBody(options, request, {
1987
+ input: buildResponsesInput(request),
1988
+ previous_response_id: pickString(body?.previous_response_id)
1989
+ }));
1970
1990
  const toolCalls = pickResponsesToolCalls(payload);
1971
1991
  return {
1972
1992
  text: pickResponsesText(payload) || pickAssistantText(payload),
@@ -1978,58 +1998,26 @@ async function completeWithResponsesAPIPassThrough(options, fetcher, path, reque
1978
1998
  }
1979
1999
  async function completeWithResponsesAPIWithMCP(options, fetcher, path, request) {
1980
2000
  const maxToolRounds = normalizeMaxToolRounds(request.maxToolRounds ?? options.defaultMaxToolRounds);
1981
- let input = buildResponsesInput(request);
1982
- let previousResponseId = pickString(isRecord2(request.body) ? request.body.previous_response_id : undefined);
1983
- let aggregatedUsage;
1984
- let finishReason;
1985
- let lastPayload;
1986
- const executedToolCalls = [];
1987
- const toolExecutions = [];
1988
- const reasoningBlocks = [];
2001
+ const state = createResponsesMCPState(request);
1989
2002
  for (let round = 1;round <= maxToolRounds + 1; round += 1) {
1990
2003
  const mcpToolset = await resolveMCPToolset(request.mcpClients);
1991
2004
  const transportTools = toResponsesTools(toProviderFunctionTools(mcpToolset));
1992
- const response = await fetcher(buildURL(options.baseURL, path), {
1993
- method: "POST",
1994
- headers: buildHeaders(options),
1995
- body: JSON.stringify(cleanUndefined({
1996
- ...options.defaultBody,
1997
- ...request.body,
1998
- model: options.model,
1999
- input,
2000
- previous_response_id: previousResponseId,
2001
- temperature: request.temperature,
2002
- reasoning_effort: toOpenAIReasoningEffort(request.reasoningEffort),
2003
- max_output_tokens: request.maxTokens,
2004
- tools: transportTools,
2005
- tool_choice: request.toolChoice,
2006
- parallel_tool_calls: request.parallelToolCalls
2007
- })),
2008
- signal: request.signal
2009
- });
2010
- if (!response.ok) {
2011
- const message = await response.text();
2012
- throw new Error(`HTTP ${response.status}: ${message}`);
2013
- }
2014
- const payload = await response.json();
2015
- lastPayload = payload;
2016
- aggregatedUsage = mergeUsage(aggregatedUsage, pickUsage(payload));
2017
- finishReason = pickResponsesFinishReason(payload) ?? finishReason;
2018
- pushReasoningBlock(reasoningBlocks, round, pickResponsesReasoning(payload));
2005
+ const payload = await sendOpenAIJsonRequest(options, fetcher, path, request, buildResponsesBody(options, request, {
2006
+ input: state.input,
2007
+ previous_response_id: state.previousResponseId,
2008
+ tools: transportTools,
2009
+ tool_choice: request.toolChoice,
2010
+ parallel_tool_calls: request.parallelToolCalls
2011
+ }));
2012
+ state.lastPayload = payload;
2013
+ state.aggregatedUsage = mergeUsage(state.aggregatedUsage, pickUsage(payload));
2014
+ state.finishReason = pickResponsesFinishReason(payload) ?? state.finishReason;
2015
+ pushReasoningBlock(state.reasoningBlocks, round, pickResponsesReasoning(payload));
2019
2016
  const providerToolCalls = pickResponsesToolCalls(payload);
2020
2017
  const functionCalls = providerToolCalls.filter((toolCall) => toolCall.type === "function" && typeof toolCall.id === "string" && typeof toolCall.name === "string");
2021
2018
  if (functionCalls.length === 0) {
2022
2019
  const text = pickResponsesText(payload) || pickAssistantText(payload);
2023
- return {
2024
- text,
2025
- reasoning: joinReasoningBlocks(reasoningBlocks) || undefined,
2026
- reasoningBlocks: reasoningBlocks.length > 0 ? reasoningBlocks : undefined,
2027
- raw: payload,
2028
- usage: aggregatedUsage,
2029
- finishReason,
2030
- toolCalls: executedToolCalls.length > 0 ? executedToolCalls : undefined,
2031
- toolExecutions: toolExecutions.length > 0 ? toolExecutions : undefined
2032
- };
2020
+ return buildResponsesMCPResult(state, text, payload);
2033
2021
  }
2034
2022
  if (round > maxToolRounds) {
2035
2023
  throw new Error(`Tool call loop exceeded maxToolRounds (${maxToolRounds}).`);
@@ -2040,25 +2028,16 @@ async function completeWithResponsesAPIWithMCP(options, fetcher, path, request)
2040
2028
  provider: "openai-compatible",
2041
2029
  model: options.model
2042
2030
  });
2043
- executedToolCalls.push(...outputs.map((entry) => entry.call));
2044
- toolExecutions.push(...outputs.map((entry) => entry.execution));
2045
- input = outputs.map((entry) => ({
2031
+ state.executedToolCalls.push(...outputs.map((entry) => entry.call));
2032
+ state.toolExecutions.push(...outputs.map((entry) => entry.execution));
2033
+ state.input = outputs.map((entry) => ({
2046
2034
  type: "function_call_output",
2047
2035
  call_id: entry.call.id,
2048
2036
  output: stringifyToolOutput(entry.call.error ? { error: entry.call.error } : entry.call.output)
2049
2037
  }));
2050
- previousResponseId = pickString(payload.id);
2038
+ state.previousResponseId = pickString(payload.id);
2051
2039
  }
2052
- return {
2053
- text: pickResponsesText(lastPayload ?? {}) || pickAssistantText(lastPayload ?? {}),
2054
- reasoning: joinReasoningBlocks(reasoningBlocks) || undefined,
2055
- reasoningBlocks: reasoningBlocks.length > 0 ? reasoningBlocks : undefined,
2056
- raw: lastPayload,
2057
- usage: aggregatedUsage,
2058
- finishReason,
2059
- toolCalls: executedToolCalls.length > 0 ? executedToolCalls : undefined,
2060
- toolExecutions: toolExecutions.length > 0 ? toolExecutions : undefined
2061
- };
2040
+ return buildResponsesMCPResult(state, pickResponsesText(state.lastPayload ?? {}) || pickAssistantText(state.lastPayload ?? {}), state.lastPayload);
2062
2041
  }
2063
2042
  async function streamWithChatCompletionsWithMCP(options, fetcher, path, request, callbacks) {
2064
2043
  const maxToolRounds = normalizeMaxToolRounds(request.maxToolRounds ?? options.defaultMaxToolRounds);
@@ -2074,24 +2053,13 @@ async function streamWithChatCompletionsWithMCP(options, fetcher, path, request,
2074
2053
  for (let round = 1;round <= maxToolRounds + 1; round += 1) {
2075
2054
  const mcpToolset = await resolveMCPToolset(request.mcpClients);
2076
2055
  const transportTools = toProviderFunctionTools(mcpToolset);
2077
- const response = await fetcher(buildURL(options.baseURL, path), {
2078
- method: "POST",
2079
- headers: buildHeaders(options),
2080
- body: JSON.stringify(cleanUndefined({
2081
- ...options.defaultBody,
2082
- ...request.body,
2083
- model: options.model,
2084
- messages,
2085
- temperature: request.temperature,
2086
- reasoning_effort: toOpenAIReasoningEffort(request.reasoningEffort),
2087
- max_tokens: request.maxTokens,
2088
- tools: transportTools,
2089
- tool_choice: request.toolChoice,
2090
- parallel_tool_calls: request.parallelToolCalls,
2091
- stream: true
2092
- })),
2093
- signal: request.signal
2094
- });
2056
+ const response = await sendOpenAIRequest(options, fetcher, path, request, buildChatCompletionsBody(options, request, {
2057
+ messages,
2058
+ tools: transportTools,
2059
+ tool_choice: request.toolChoice,
2060
+ parallel_tool_calls: request.parallelToolCalls,
2061
+ stream: true
2062
+ }));
2095
2063
  if (!response.ok) {
2096
2064
  const message = await response.text();
2097
2065
  throw new Error(`HTTP ${response.status}: ${message}`);
@@ -2128,17 +2096,7 @@ async function streamWithChatCompletionsWithMCP(options, fetcher, path, request,
2128
2096
  roundReasoning += reasoningDelta;
2129
2097
  reasoningFieldName ??= pickAssistantReasoningDeltaFieldName(json);
2130
2098
  }
2131
- if (delta || reasoningDelta || chunkUsage || chunkFinishReason) {
2132
- const chunk = {
2133
- textDelta: delta,
2134
- reasoningDelta: reasoningDelta || undefined,
2135
- turnIndex: round,
2136
- raw: json,
2137
- usage: chunkUsage,
2138
- finishReason: chunkFinishReason
2139
- };
2140
- callbacks.onChunk?.(chunk);
2141
- }
2099
+ emitOpenAIStreamChunk(callbacks, round, json, delta, reasoningDelta, chunkUsage, chunkFinishReason);
2142
2100
  });
2143
2101
  aggregatedUsage = mergeUsage(aggregatedUsage, roundUsage);
2144
2102
  if (roundFinishReason) {
@@ -2217,22 +2175,11 @@ async function streamWithChatCompletionsWithMCP(options, fetcher, path, request,
2217
2175
  }
2218
2176
  async function streamWithResponsesAPIPassThrough(options, fetcher, path, request, callbacks) {
2219
2177
  const body = isRecord2(request.body) ? request.body : undefined;
2220
- const response = await fetcher(buildURL(options.baseURL, path), {
2221
- method: "POST",
2222
- headers: buildHeaders(options),
2223
- body: JSON.stringify(cleanUndefined({
2224
- ...options.defaultBody,
2225
- ...request.body,
2226
- model: options.model,
2227
- input: buildResponsesInput(request),
2228
- previous_response_id: pickString(body?.previous_response_id),
2229
- temperature: request.temperature,
2230
- reasoning_effort: toOpenAIReasoningEffort(request.reasoningEffort),
2231
- max_output_tokens: request.maxTokens,
2232
- stream: true
2233
- })),
2234
- signal: request.signal
2235
- });
2178
+ const response = await sendOpenAIRequest(options, fetcher, path, request, buildResponsesBody(options, request, {
2179
+ input: buildResponsesInput(request),
2180
+ previous_response_id: pickString(body?.previous_response_id),
2181
+ stream: true
2182
+ }));
2236
2183
  if (!response.ok) {
2237
2184
  const message = await response.text();
2238
2185
  throw new Error(`HTTP ${response.status}: ${message}`);
@@ -2265,15 +2212,7 @@ async function streamWithResponsesAPIPassThrough(options, fetcher, path, request
2265
2212
  text += delta;
2266
2213
  callbacks.onToken?.(delta);
2267
2214
  }
2268
- if (delta || chunkUsage || chunkFinishReason) {
2269
- const chunk = {
2270
- textDelta: delta,
2271
- raw: json,
2272
- usage: chunkUsage,
2273
- finishReason: chunkFinishReason
2274
- };
2275
- callbacks.onChunk?.(chunk);
2276
- }
2215
+ emitOpenAIStreamChunk(callbacks, undefined, json, delta, "", chunkUsage, chunkFinishReason);
2277
2216
  });
2278
2217
  const finalPayload = lastPayload ?? {};
2279
2218
  const out = {
@@ -2287,37 +2226,19 @@ async function streamWithResponsesAPIPassThrough(options, fetcher, path, request
2287
2226
  }
2288
2227
  async function streamWithResponsesAPIWithMCP(options, fetcher, path, request, callbacks) {
2289
2228
  const maxToolRounds = normalizeMaxToolRounds(request.maxToolRounds ?? options.defaultMaxToolRounds);
2290
- let input = buildResponsesInput(request);
2291
- let previousResponseId = pickString(isRecord2(request.body) ? request.body.previous_response_id : undefined);
2292
- let aggregatedUsage;
2293
- let finishReason;
2294
- let lastPayload;
2295
- const executedToolCalls = [];
2296
- const toolExecutions = [];
2297
- const reasoningBlocks = [];
2229
+ const state = createResponsesMCPState(request);
2298
2230
  callbacks.onStart?.();
2299
2231
  for (let round = 1;round <= maxToolRounds + 1; round += 1) {
2300
2232
  const mcpToolset = await resolveMCPToolset(request.mcpClients);
2301
2233
  const transportTools = toResponsesTools(toProviderFunctionTools(mcpToolset));
2302
- const response = await fetcher(buildURL(options.baseURL, path), {
2303
- method: "POST",
2304
- headers: buildHeaders(options),
2305
- body: JSON.stringify(cleanUndefined({
2306
- ...options.defaultBody,
2307
- ...request.body,
2308
- model: options.model,
2309
- input,
2310
- previous_response_id: previousResponseId,
2311
- temperature: request.temperature,
2312
- reasoning_effort: toOpenAIReasoningEffort(request.reasoningEffort),
2313
- max_output_tokens: request.maxTokens,
2314
- tools: transportTools,
2315
- tool_choice: request.toolChoice,
2316
- parallel_tool_calls: request.parallelToolCalls,
2317
- stream: true
2318
- })),
2319
- signal: request.signal
2320
- });
2234
+ const response = await sendOpenAIRequest(options, fetcher, path, request, buildResponsesBody(options, request, {
2235
+ input: state.input,
2236
+ previous_response_id: state.previousResponseId,
2237
+ tools: transportTools,
2238
+ tool_choice: request.toolChoice,
2239
+ parallel_tool_calls: request.parallelToolCalls,
2240
+ stream: true
2241
+ }));
2321
2242
  if (!response.ok) {
2322
2243
  const message = await response.text();
2323
2244
  throw new Error(`HTTP ${response.status}: ${message}`);
@@ -2339,7 +2260,7 @@ async function streamWithResponsesAPIWithMCP(options, fetcher, path, request, ca
2339
2260
  const payload = pickResponsesStreamPayload(json);
2340
2261
  if (payload) {
2341
2262
  roundPayload = payload;
2342
- lastPayload = payload;
2263
+ state.lastPayload = payload;
2343
2264
  }
2344
2265
  const delta = pickResponsesStreamTextDelta(json);
2345
2266
  const reasoningDelta = pickResponsesStreamReasoningDelta(json);
@@ -2357,24 +2278,14 @@ async function streamWithResponsesAPIWithMCP(options, fetcher, path, request, ca
2357
2278
  if (reasoningDelta) {
2358
2279
  roundReasoning += reasoningDelta;
2359
2280
  }
2360
- if (delta || reasoningDelta || chunkUsage || chunkFinishReason) {
2361
- const chunk = {
2362
- textDelta: delta,
2363
- reasoningDelta: reasoningDelta || undefined,
2364
- turnIndex: round,
2365
- raw: json,
2366
- usage: chunkUsage,
2367
- finishReason: chunkFinishReason
2368
- };
2369
- callbacks.onChunk?.(chunk);
2370
- }
2281
+ emitOpenAIStreamChunk(callbacks, round, json, delta, reasoningDelta, chunkUsage, chunkFinishReason);
2371
2282
  });
2372
2283
  const resolvedRoundUsage = preferLatestUsage(roundUsage, roundPayload ? pickUsage(roundPayload) : undefined);
2373
- aggregatedUsage = mergeUsage(aggregatedUsage, resolvedRoundUsage);
2284
+ state.aggregatedUsage = mergeUsage(state.aggregatedUsage, resolvedRoundUsage);
2374
2285
  if (roundFinishReason) {
2375
- finishReason = roundFinishReason;
2286
+ state.finishReason = roundFinishReason;
2376
2287
  } else if (roundPayload) {
2377
- finishReason = pickResponsesFinishReason(roundPayload) ?? finishReason;
2288
+ state.finishReason = pickResponsesFinishReason(roundPayload) ?? state.finishReason;
2378
2289
  }
2379
2290
  const payloadToolCalls = roundPayload ? pickResponsesToolCalls(roundPayload) : [];
2380
2291
  if (roundPayload && roundReasoning.length === 0) {
@@ -2383,7 +2294,7 @@ async function streamWithResponsesAPIWithMCP(options, fetcher, path, request, ca
2383
2294
  const streamedCalls = buildResponsesStreamToolCalls(streamedToolCalls);
2384
2295
  const providerToolCalls = payloadToolCalls.length > 0 ? payloadToolCalls : streamedCalls;
2385
2296
  const functionCalls = providerToolCalls.filter((toolCall) => toolCall.type === "function" && typeof toolCall.id === "string" && typeof toolCall.name === "string");
2386
- pushReasoningBlock(reasoningBlocks, round, roundReasoning);
2297
+ pushReasoningBlock(state.reasoningBlocks, round, roundReasoning);
2387
2298
  request.onTurnTransition?.({
2388
2299
  turnIndex: round,
2389
2300
  kind: "reasoningComplete",
@@ -2391,16 +2302,7 @@ async function streamWithResponsesAPIWithMCP(options, fetcher, path, request, ca
2391
2302
  });
2392
2303
  if (functionCalls.length === 0) {
2393
2304
  const finalText = roundText.length > 0 ? roundText : roundPayload ? pickResponsesText(roundPayload) || pickAssistantText(roundPayload) : "";
2394
- const out2 = {
2395
- text: finalText,
2396
- reasoning: joinReasoningBlocks(reasoningBlocks) || undefined,
2397
- reasoningBlocks: reasoningBlocks.length > 0 ? reasoningBlocks : undefined,
2398
- raw: roundPayload ?? lastPayload,
2399
- usage: aggregatedUsage,
2400
- finishReason,
2401
- toolCalls: executedToolCalls.length > 0 ? executedToolCalls : undefined,
2402
- toolExecutions: toolExecutions.length > 0 ? toolExecutions : undefined
2403
- };
2305
+ const out2 = buildResponsesMCPResult(state, finalText, roundPayload ?? state.lastPayload);
2404
2306
  request.onTurnTransition?.({ turnIndex: round, kind: "streamEnd" });
2405
2307
  callbacks.onComplete?.(out2);
2406
2308
  return out2;
@@ -2425,26 +2327,17 @@ async function streamWithResponsesAPIWithMCP(options, fetcher, path, request, ca
2425
2327
  provider: "openai-compatible",
2426
2328
  model: options.model
2427
2329
  });
2428
- executedToolCalls.push(...outputs.map((entry) => entry.call));
2429
- toolExecutions.push(...outputs.map((entry) => entry.execution));
2330
+ state.executedToolCalls.push(...outputs.map((entry) => entry.call));
2331
+ state.toolExecutions.push(...outputs.map((entry) => entry.execution));
2430
2332
  request.onTurnTransition?.({ turnIndex: round, kind: "toolResultsReceived" });
2431
- input = outputs.map((entry) => ({
2333
+ state.input = outputs.map((entry) => ({
2432
2334
  type: "function_call_output",
2433
2335
  call_id: entry.call.id,
2434
2336
  output: stringifyToolOutput(entry.call.error ? { error: entry.call.error } : entry.call.output)
2435
2337
  }));
2436
- previousResponseId = pickString(roundPayload?.id);
2338
+ state.previousResponseId = pickString(roundPayload?.id);
2437
2339
  }
2438
- const out = {
2439
- text: pickResponsesText(lastPayload ?? {}) || pickAssistantText(lastPayload ?? {}),
2440
- reasoning: joinReasoningBlocks(reasoningBlocks) || undefined,
2441
- reasoningBlocks: reasoningBlocks.length > 0 ? reasoningBlocks : undefined,
2442
- raw: lastPayload,
2443
- usage: aggregatedUsage,
2444
- finishReason,
2445
- toolCalls: executedToolCalls.length > 0 ? executedToolCalls : undefined,
2446
- toolExecutions: toolExecutions.length > 0 ? toolExecutions : undefined
2447
- };
2340
+ const out = buildResponsesMCPResult(state, pickResponsesText(state.lastPayload ?? {}) || pickAssistantText(state.lastPayload ?? {}), state.lastPayload);
2448
2341
  request.onTurnTransition?.({ turnIndex: maxToolRounds + 1, kind: "streamEnd" });
2449
2342
  callbacks.onComplete?.(out);
2450
2343
  return out;
@@ -2597,7 +2490,7 @@ function pickAssistantDelta(payload) {
2597
2490
  if (!isRecord2(delta)) {
2598
2491
  return "";
2599
2492
  }
2600
- return pickTextFromOpenAIContent(delta.content);
2493
+ return pickTextLike(delta.content);
2601
2494
  }
2602
2495
  function pickAssistantReasoning(payload) {
2603
2496
  const message = pickAssistantMessage(payload);
@@ -2905,7 +2798,7 @@ function pickResponsesReasoning(payload) {
2905
2798
  function pickAssistantText(payload) {
2906
2799
  const message = pickAssistantMessage(payload);
2907
2800
  if (message) {
2908
- const text = pickTextFromOpenAIContent(message.content);
2801
+ const text = pickTextLike(message.content);
2909
2802
  if (text.length > 0) {
2910
2803
  return text;
2911
2804
  }
@@ -2934,9 +2827,6 @@ function joinReasoningBlocks(blocks) {
2934
2827
 
2935
2828
  `);
2936
2829
  }
2937
- function pickTextFromOpenAIContent(value) {
2938
- return pickTextLike(value);
2939
- }
2940
2830
  function pickTextLike(value) {
2941
2831
  if (typeof value === "string") {
2942
2832
  return value;
@@ -3010,83 +2900,81 @@ function createAnthropicCompatibleAdapter(options) {
3010
2900
  if (hasMCPClients(request.mcpClients)) {
3011
2901
  return streamWithMCPToolLoop(options, fetcher, path, request, callbacks);
3012
2902
  }
3013
- const input = resolveAnthropicInput(request);
3014
- const response = await fetcher(buildURL(options.baseURL, path), {
3015
- method: "POST",
3016
- headers: buildHeaders2(options),
3017
- body: JSON.stringify(buildAnthropicRequestBody(options, request, {
3018
- ...options.defaultBody,
3019
- ...request.body,
3020
- model: options.model,
3021
- system: input.systemPrompt,
3022
- messages: input.messages,
3023
- temperature: request.temperature,
3024
- stream: true
3025
- })),
3026
- signal: request.signal
3027
- });
3028
- if (!response.ok) {
3029
- const message = await response.text();
3030
- throw new Error(`HTTP ${response.status}: ${message}`);
3031
- }
3032
- callbacks.onStart?.();
3033
- let text = "";
3034
- let usage;
3035
- let finishReason;
3036
- await consumeSSE(response, (data) => {
3037
- if (data === "[DONE]") {
3038
- return;
3039
- }
3040
- const json = safeJSONParse(data);
3041
- if (!isRecord2(json)) {
3042
- return;
3043
- }
3044
- const delta = pickAnthropicDelta(json);
3045
- const chunkUsage = pickUsage2(json);
3046
- const chunkFinishReason = pickFinishReason2(json);
3047
- usage = preferLatestUsage(usage, chunkUsage);
3048
- if (chunkFinishReason) {
3049
- finishReason = chunkFinishReason;
3050
- }
3051
- if (delta) {
3052
- text += delta;
3053
- callbacks.onToken?.(delta);
3054
- }
3055
- if (delta || chunkUsage || chunkFinishReason) {
3056
- const chunk = {
3057
- textDelta: delta,
3058
- raw: json,
3059
- usage: chunkUsage,
3060
- finishReason: chunkFinishReason
3061
- };
3062
- callbacks.onChunk?.(chunk);
3063
- }
3064
- });
3065
- const out = { text, usage, finishReason };
3066
- callbacks.onComplete?.(out);
3067
- return out;
2903
+ return streamPassThrough(options, fetcher, path, request, callbacks);
3068
2904
  },
3069
2905
  async embed() {
3070
2906
  throw new Error("Anthropic does not provide a native embedding API. " + "Use the openai-compatible provider with Voyage AI (https://api.voyageai.com) — " + "Anthropic's recommended embedding solution, which uses the same request format.");
3071
2907
  }
3072
2908
  };
3073
2909
  }
3074
- async function completePassThrough(options, fetcher, path, request) {
2910
+ async function streamPassThrough(options, fetcher, path, request, callbacks) {
3075
2911
  const input = resolveAnthropicInput(request);
3076
- const response = await fetcher(buildURL(options.baseURL, path), {
2912
+ const response = await sendAnthropicMessage(options, fetcher, path, request, {
2913
+ system: input.systemPrompt,
2914
+ messages: input.messages,
2915
+ stream: true
2916
+ });
2917
+ if (!response.ok) {
2918
+ const message = await response.text();
2919
+ throw new Error(`HTTP ${response.status}: ${message}`);
2920
+ }
2921
+ callbacks.onStart?.();
2922
+ let text = "";
2923
+ let usage;
2924
+ let finishReason;
2925
+ await consumeSSE(response, (data) => {
2926
+ if (data === "[DONE]") {
2927
+ return;
2928
+ }
2929
+ const json = safeJSONParse(data);
2930
+ if (!isRecord2(json)) {
2931
+ return;
2932
+ }
2933
+ const delta = pickAnthropicDelta(json);
2934
+ const chunkUsage = pickUsage2(json);
2935
+ const chunkFinishReason = pickFinishReason2(json);
2936
+ usage = preferLatestUsage(usage, chunkUsage);
2937
+ if (chunkFinishReason) {
2938
+ finishReason = chunkFinishReason;
2939
+ }
2940
+ if (delta) {
2941
+ text += delta;
2942
+ callbacks.onToken?.(delta);
2943
+ }
2944
+ if (delta || chunkUsage || chunkFinishReason) {
2945
+ callbacks.onChunk?.({
2946
+ textDelta: delta,
2947
+ raw: json,
2948
+ usage: chunkUsage,
2949
+ finishReason: chunkFinishReason
2950
+ });
2951
+ }
2952
+ });
2953
+ const out = { text, usage, finishReason };
2954
+ callbacks.onComplete?.(out);
2955
+ return out;
2956
+ }
2957
+ function sendAnthropicMessage(options, fetcher, path, request, body) {
2958
+ return fetcher(buildURL(options.baseURL, path), {
3077
2959
  method: "POST",
3078
2960
  headers: buildHeaders2(options),
3079
2961
  body: JSON.stringify(buildAnthropicRequestBody(options, request, {
3080
2962
  ...options.defaultBody,
3081
2963
  ...request.body,
3082
2964
  model: options.model,
3083
- system: input.systemPrompt,
3084
- messages: input.messages,
3085
2965
  temperature: request.temperature,
3086
- stream: false
2966
+ ...body
3087
2967
  })),
3088
2968
  signal: request.signal
3089
2969
  });
2970
+ }
2971
+ async function completePassThrough(options, fetcher, path, request) {
2972
+ const input = resolveAnthropicInput(request);
2973
+ const response = await sendAnthropicMessage(options, fetcher, path, request, {
2974
+ system: input.systemPrompt,
2975
+ messages: input.messages,
2976
+ stream: false
2977
+ });
3090
2978
  if (!response.ok) {
3091
2979
  const message = await response.text();
3092
2980
  throw new Error(`HTTP ${response.status}: ${message}`);
@@ -3118,21 +3006,12 @@ async function completeWithMCPToolLoop(options, fetcher, path, request) {
3118
3006
  for (let round = 1;round <= maxToolRounds + 1; round += 1) {
3119
3007
  const mcpToolset = await resolveMCPToolset(request.mcpClients);
3120
3008
  const tools = toAnthropicTools(toProviderFunctionTools(mcpToolset));
3121
- const response = await fetcher(buildURL(options.baseURL, path), {
3122
- method: "POST",
3123
- headers: buildHeaders2(options),
3124
- body: JSON.stringify(buildAnthropicRequestBody(options, request, {
3125
- ...options.defaultBody,
3126
- ...request.body,
3127
- model: options.model,
3128
- system: input.systemPrompt,
3129
- messages,
3130
- temperature: request.temperature,
3131
- tools,
3132
- tool_choice: toAnthropicToolChoice(request.toolChoice),
3133
- stream: false
3134
- })),
3135
- signal: request.signal
3009
+ const response = await sendAnthropicMessage(options, fetcher, path, request, {
3010
+ system: input.systemPrompt,
3011
+ messages,
3012
+ tools,
3013
+ tool_choice: toAnthropicToolChoice(request.toolChoice),
3014
+ stream: false
3136
3015
  });
3137
3016
  if (!response.ok) {
3138
3017
  const message = await response.text();
@@ -3208,21 +3087,12 @@ async function streamWithMCPToolLoop(options, fetcher, path, request, callbacks)
3208
3087
  for (let round = 1;round <= maxToolRounds + 1; round += 1) {
3209
3088
  const mcpToolset = await resolveMCPToolset(request.mcpClients);
3210
3089
  const tools = toAnthropicTools(toProviderFunctionTools(mcpToolset));
3211
- const response = await fetcher(buildURL(options.baseURL, path), {
3212
- method: "POST",
3213
- headers: buildHeaders2(options),
3214
- body: JSON.stringify(buildAnthropicRequestBody(options, request, {
3215
- ...options.defaultBody,
3216
- ...request.body,
3217
- model: options.model,
3218
- system: input.systemPrompt,
3219
- messages,
3220
- temperature: request.temperature,
3221
- tools,
3222
- tool_choice: toAnthropicToolChoice(request.toolChoice),
3223
- stream: true
3224
- })),
3225
- signal: request.signal
3090
+ const response = await sendAnthropicMessage(options, fetcher, path, request, {
3091
+ system: input.systemPrompt,
3092
+ messages,
3093
+ tools,
3094
+ tool_choice: toAnthropicToolChoice(request.toolChoice),
3095
+ stream: true
3226
3096
  });
3227
3097
  if (!response.ok) {
3228
3098
  const message = await response.text();
@@ -3766,39 +3636,10 @@ function buildProviderOptions(config) {
3766
3636
  return {
3767
3637
  model: config.model,
3768
3638
  ...transport,
3769
- ...config.options ?? {}
3639
+ ...config.options
3770
3640
  };
3771
3641
  }
3772
3642
 
3773
- // src/utils/debug-colors.ts
3774
- var ANSI = {
3775
- reset: "\x1B[0m",
3776
- bold: "\x1B[1m",
3777
- cyan: "\x1B[36m",
3778
- yellow: "\x1B[33m",
3779
- green: "\x1B[32m",
3780
- red: "\x1B[31m",
3781
- dim: "\x1B[2m"
3782
- };
3783
- function color(config, text, tone) {
3784
- if (!config.colors) {
3785
- return text;
3786
- }
3787
- return `${ANSI[tone]}${text}${ANSI.reset}`;
3788
- }
3789
- function dim(config, text) {
3790
- if (!config.colors) {
3791
- return text;
3792
- }
3793
- return `${ANSI.dim}${text}${ANSI.reset}`;
3794
- }
3795
- function title(config, text) {
3796
- if (!config.colors) {
3797
- return text;
3798
- }
3799
- return `${ANSI.bold}${text}${ANSI.reset}`;
3800
- }
3801
-
3802
3643
  // src/outdent.ts
3803
3644
  var DEFAULT_OPTIONS = {
3804
3645
  trimLeadingNewline: true,
@@ -3936,132 +3777,213 @@ function createOutdent(options = {}) {
3936
3777
  return outdent;
3937
3778
  }
3938
3779
 
3939
- // src/generate-shared.ts
3940
- var sharedOutdent = createOutdent({
3941
- trimLeadingNewline: true,
3942
- trimTrailingNewline: true,
3943
- newline: `
3944
- `
3945
- });
3780
+ // src/generate-tool-timeout.ts
3781
+ function withToolTimeout(client, toolTimeoutMs) {
3782
+ return {
3783
+ id: client.id,
3784
+ listTools: client.listTools.bind(client),
3785
+ close: client.close?.bind(client),
3786
+ async callTool(params) {
3787
+ let timeoutId;
3788
+ const timeoutPromise = new Promise((_, reject) => {
3789
+ timeoutId = setTimeout(() => reject(new Error(`Tool call timed out after ${toolTimeoutMs}ms`)), toolTimeoutMs);
3790
+ });
3791
+ try {
3792
+ return await Promise.race([client.callTool(params), timeoutPromise]);
3793
+ } finally {
3794
+ clearTimeout(timeoutId);
3795
+ }
3796
+ }
3797
+ };
3798
+ }
3799
+ function applyToolTimeout(clients, toolTimeoutMs) {
3800
+ return clients.map((client) => withToolTimeout(client, toolTimeoutMs));
3801
+ }
3802
+ // src/generate-output.ts
3946
3803
  var RE_THINK_TAGS = /<\/?think\s*>/gi;
3947
- function resolvePrompt(prompt, context) {
3948
- const resolved = typeof prompt === "function" ? prompt(context) : prompt;
3949
- return normalizePromptValue(resolved, context);
3804
+ function normalizeModelOutput(text, dedicatedReasoning, reasoningBlocks) {
3805
+ const sanitized = sanitizeThink(text);
3806
+ const visibleText = stripThinkBlocks(text, sanitized.thinkBlocks);
3807
+ const reasoning = joinReasoningSegments([
3808
+ dedicatedReasoning,
3809
+ ...sanitized.thinkBlocks.map((block) => block.content)
3810
+ ]);
3811
+ return {
3812
+ text: visibleText,
3813
+ reasoning,
3814
+ reasoningBlocks: normalizeReasoningBlocks(reasoningBlocks),
3815
+ thinkBlocks: sanitized.thinkBlocks,
3816
+ parseSource: composeParseSource(visibleText, reasoning)
3817
+ };
3950
3818
  }
3951
- function normalizePromptValue(value, _context) {
3952
- if (typeof value === "string") {
3953
- return {
3954
- prompt: value
3955
- };
3956
- }
3957
- if (isPromptResolver(value)) {
3958
- return normalizePromptPayload(value.resolvePrompt(_context));
3819
+ function normalizeReasoningBlocks(blocks) {
3820
+ if (!Array.isArray(blocks)) {
3821
+ return;
3959
3822
  }
3960
- return normalizePromptPayload(value);
3823
+ const normalized = blocks.map((block) => ({
3824
+ turnIndex: block.turnIndex,
3825
+ text: block.text.replace(RE_THINK_TAGS, "").trim()
3826
+ })).filter((block) => Number.isFinite(block.turnIndex) && block.text.length > 0);
3827
+ return normalized.length > 0 ? normalized : undefined;
3961
3828
  }
3962
- function normalizePromptPayload(value) {
3963
- const prompt = typeof value.prompt === "string" ? value.prompt : undefined;
3964
- const messages = Array.isArray(value.messages) ? value.messages.filter(isLLMMessage) : undefined;
3965
- if ((!prompt || prompt.trim().length === 0) && (!messages || messages.length === 0)) {
3966
- throw new Error("Structured prompt payload must include a non-empty prompt or messages.");
3829
+ function appendReasoningBlock(blocks, transition) {
3830
+ const text = transition.reasoningText?.replace(RE_THINK_TAGS, "").trim();
3831
+ if (!text) {
3832
+ return blocks;
3967
3833
  }
3968
- return {
3969
- prompt,
3970
- systemPrompt: typeof value.systemPrompt === "string" ? value.systemPrompt : undefined,
3971
- messages: messages && messages.length > 0 ? messages.map((message) => ({ ...message })) : undefined
3972
- };
3834
+ const next = [...blocks ?? [], { turnIndex: transition.turnIndex, text }];
3835
+ return normalizeReasoningBlocks(next);
3973
3836
  }
3974
- function applyPromptOutdent(payload, enabled) {
3975
- if (!enabled) {
3976
- return payload;
3837
+ function composeParseSource(text, reasoning) {
3838
+ if (typeof reasoning !== "string" || reasoning.length === 0) {
3839
+ return text;
3977
3840
  }
3978
- return {
3979
- prompt: typeof payload.prompt === "string" ? sharedOutdent.string(payload.prompt) : undefined,
3980
- systemPrompt: applyOutdentToOptionalPrompt(payload.systemPrompt, enabled),
3981
- messages: payload.messages?.map((message) => ({
3982
- ...message,
3983
- content: typeof message.content === "string" ? sharedOutdent.string(message.content) : message.content
3984
- }))
3985
- };
3841
+ const sanitized = reasoning.replace(RE_THINK_TAGS, "");
3842
+ if (sanitized.length === 0) {
3843
+ return text;
3844
+ }
3845
+ return `<think>${sanitized}</think>${text}`;
3986
3846
  }
3987
- function applyOutdentToOptionalPrompt(value, enabled) {
3988
- if (!enabled || typeof value !== "string") {
3989
- return value;
3847
+ function aggregateUsage(attempts) {
3848
+ let usage;
3849
+ for (const attempt of attempts) {
3850
+ usage = mergeUsage2(usage, attempt.usage);
3990
3851
  }
3991
- return sharedOutdent.string(value);
3852
+ return usage;
3992
3853
  }
3993
- function mergeSystemPrompts(primary, secondary) {
3994
- const prompts = [primary, secondary].map((value) => value?.trim()).filter((value) => Boolean(value));
3995
- if (prompts.length === 0) {
3854
+ function mergeUsage2(base, next) {
3855
+ if (!base && !next) {
3996
3856
  return;
3997
3857
  }
3998
- return prompts.join(`
3858
+ return {
3859
+ inputTokens: (base?.inputTokens ?? 0) + (next?.inputTokens ?? 0),
3860
+ outputTokens: (base?.outputTokens ?? 0) + (next?.outputTokens ?? 0),
3861
+ totalTokens: (base?.totalTokens ?? 0) + (next?.totalTokens ?? 0),
3862
+ cost: (base?.cost ?? 0) + (next?.cost ?? 0)
3863
+ };
3864
+ }
3865
+ function joinReasoningSegments(parts) {
3866
+ return parts.map((value) => value?.trim()).filter((value) => Boolean(value)).join(`
3999
3867
 
4000
3868
  `);
4001
3869
  }
4002
- function normalizeStreamConfig(option) {
4003
- if (typeof option === "boolean") {
4004
- return {
4005
- enabled: option
4006
- };
3870
+ function stripThinkBlocks(text, thinkBlocks) {
3871
+ if (thinkBlocks.length === 0) {
3872
+ return text;
4007
3873
  }
4008
- if (!option) {
4009
- return {
4010
- enabled: false
4011
- };
3874
+ let output = "";
3875
+ let cursor = 0;
3876
+ for (const block of thinkBlocks) {
3877
+ output += text.slice(cursor, block.start);
3878
+ cursor = block.end;
4012
3879
  }
4013
- return {
4014
- enabled: option.enabled ?? true,
4015
- onData: option.onData,
4016
- onTurnTransition: option.onTurnTransition,
4017
- to: option.to
4018
- };
3880
+ output += text.slice(cursor);
3881
+ return output;
4019
3882
  }
4020
- function normalizeDebugConfig(option) {
4021
- if (typeof option === "boolean") {
4022
- return {
4023
- enabled: option,
4024
- colors: true,
4025
- verbose: false,
4026
- logger: (line) => console.log(line)
4027
- };
3883
+ function toStreamDataFingerprint(value) {
3884
+ try {
3885
+ return JSON.stringify(value);
3886
+ } catch {
3887
+ return "__unserializable__";
4028
3888
  }
4029
- if (!option) {
4030
- return {
4031
- enabled: false,
4032
- colors: true,
4033
- verbose: false,
4034
- logger: (line) => console.log(line)
4035
- };
3889
+ }
3890
+
3891
+ // src/utils/debug-colors.ts
3892
+ var ANSI = {
3893
+ reset: "\x1B[0m",
3894
+ bold: "\x1B[1m",
3895
+ cyan: "\x1B[36m",
3896
+ yellow: "\x1B[33m",
3897
+ green: "\x1B[32m",
3898
+ red: "\x1B[31m",
3899
+ dim: "\x1B[2m"
3900
+ };
3901
+ function color(config, text, tone) {
3902
+ if (!config.colors) {
3903
+ return text;
4036
3904
  }
4037
- return {
4038
- enabled: option.enabled ?? true,
4039
- colors: option.colors ?? true,
4040
- verbose: option.verbose ?? false,
4041
- logger: option.logger ?? ((line) => console.log(line))
4042
- };
3905
+ return `${ANSI[tone]}${text}${ANSI.reset}`;
4043
3906
  }
4044
- function withToolTimeout(client, toolTimeoutMs) {
4045
- return {
4046
- id: client.id,
4047
- listTools: client.listTools.bind(client),
4048
- close: client.close?.bind(client),
4049
- async callTool(params) {
4050
- let timeoutId;
4051
- const timeoutPromise = new Promise((_, reject) => {
4052
- timeoutId = setTimeout(() => reject(new Error(`Tool call timed out after ${toolTimeoutMs}ms`)), toolTimeoutMs);
4053
- });
4054
- try {
4055
- return await Promise.race([client.callTool(params), timeoutPromise]);
4056
- } finally {
4057
- clearTimeout(timeoutId);
4058
- }
4059
- }
4060
- };
3907
+ function dim(config, text) {
3908
+ if (!config.colors) {
3909
+ return text;
3910
+ }
3911
+ return `${ANSI.dim}${text}${ANSI.reset}`;
4061
3912
  }
4062
- function applyToolTimeout(clients, toolTimeoutMs) {
4063
- return clients.map((client) => withToolTimeout(client, toolTimeoutMs));
3913
+ function title(config, text) {
3914
+ if (!config.colors) {
3915
+ return text;
3916
+ }
3917
+ return `${ANSI.bold}${text}${ANSI.reset}`;
4064
3918
  }
3919
+
3920
+ // src/generate-debug.ts
3921
+ function emitDebugRequest(config, input) {
3922
+ const requestBody = input.requestPayload.body !== undefined ? JSON.stringify(input.requestPayload.body, null, 2) : "(none)";
3923
+ const requestMessages = input.requestPayload.messages !== undefined ? JSON.stringify(input.requestPayload.messages, null, 2) : "(none)";
3924
+ const lines = [
3925
+ color(config, title(config, [
3926
+ `[${input.label}][request]`,
3927
+ `attempt=${input.attempt}`,
3928
+ `selfHealEnabled=${input.selfHealEnabled}`,
3929
+ `selfHealAttempt=${input.selfHealAttempt}`
3930
+ ].join(" ")), "cyan"),
3931
+ dim(config, [
3932
+ `provider=${input.provider ?? "unknown"}`,
3933
+ `model=${input.model ?? "unknown"}`,
3934
+ `stream=${input.stream}`
3935
+ ].join(" ")),
3936
+ color(config, "prompt:", "yellow"),
3937
+ input.requestPayload.prompt ?? "(none)",
3938
+ color(config, "messages:", "yellow"),
3939
+ requestMessages,
3940
+ color(config, "systemPrompt:", "yellow"),
3941
+ input.requestPayload.systemPrompt ?? "(none)",
3942
+ color(config, "request.body:", "yellow"),
3943
+ requestBody
3944
+ ];
3945
+ emitDebug(config, lines.join(`
3946
+ `));
3947
+ }
3948
+ function emitDebugResponse(config, input) {
3949
+ const text = input.text.length > 0 ? input.text : "(none)";
3950
+ const reasoning = input.reasoning.length > 0 ? input.reasoning : "(none)";
3951
+ const metadata = [
3952
+ `via=${input.via}`,
3953
+ `textChars=${input.text.length}`,
3954
+ `reasoningChars=${input.reasoning.length}`
3955
+ ];
3956
+ if (config.verbose) {
3957
+ metadata.push(`parseSourceChars=${input.parseSource.length}`);
3958
+ }
3959
+ metadata.push(`finishReason=${input.finishReason ?? "unknown"}`, `usage=${JSON.stringify(input.usage ?? {})}`);
3960
+ const lines = [
3961
+ color(config, title(config, [
3962
+ `[${input.label}][response]`,
3963
+ `attempt=${input.attempt}`,
3964
+ `selfHealEnabled=${input.selfHealEnabled}`,
3965
+ `selfHealAttempt=${input.selfHealAttempt}`
3966
+ ].join(" ")), "green"),
3967
+ dim(config, metadata.join(" ")),
3968
+ color(config, "text:", "yellow"),
3969
+ text,
3970
+ color(config, "reasoning:", "yellow"),
3971
+ reasoning
3972
+ ];
3973
+ if (config.verbose) {
3974
+ lines.push(color(config, "parseSource:", "yellow"), input.parseSource);
3975
+ }
3976
+ emitDebug(config, lines.join(`
3977
+ `));
3978
+ }
3979
+ function emitDebug(config, message) {
3980
+ if (!config.enabled) {
3981
+ return;
3982
+ }
3983
+ config.logger(message);
3984
+ }
3985
+
3986
+ // src/generate-model-call.ts
4065
3987
  async function callModel(adapter, options) {
4066
3988
  const requestSignal = options.request?.signal ?? (options.timeout?.request !== undefined ? AbortSignal.timeout(options.timeout.request) : undefined);
4067
3989
  const requestPayload = {
@@ -4281,67 +4203,115 @@ async function callModel(adapter, options) {
4281
4203
  reasoningBlocks: normalized.reasoningBlocks
4282
4204
  };
4283
4205
  }
4284
- function normalizeModelOutput(text, dedicatedReasoning, reasoningBlocks) {
4285
- const sanitized = sanitizeThink(text);
4286
- const visibleText = stripThinkBlocks(text, sanitized.thinkBlocks);
4287
- const reasoning = joinReasoningSegments([
4288
- dedicatedReasoning,
4289
- ...sanitized.thinkBlocks.map((block) => block.content)
4290
- ]);
4206
+
4207
+ // src/generate-shared.ts
4208
+ var sharedOutdent = createOutdent({
4209
+ trimLeadingNewline: true,
4210
+ trimTrailingNewline: true,
4211
+ newline: `
4212
+ `
4213
+ });
4214
+ function resolvePrompt(prompt, context) {
4215
+ const resolved = typeof prompt === "function" ? prompt(context) : prompt;
4216
+ return normalizePromptValue(resolved, context);
4217
+ }
4218
+ function normalizePromptValue(value, _context) {
4219
+ if (typeof value === "string") {
4220
+ return {
4221
+ prompt: value
4222
+ };
4223
+ }
4224
+ if (isPromptResolver(value)) {
4225
+ return normalizePromptPayload(value.resolvePrompt(_context));
4226
+ }
4227
+ return normalizePromptPayload(value);
4228
+ }
4229
+ function normalizePromptPayload(value) {
4230
+ const prompt = typeof value.prompt === "string" ? value.prompt : undefined;
4231
+ const messages = Array.isArray(value.messages) ? value.messages.filter(isLLMMessage) : undefined;
4232
+ if ((!prompt || prompt.trim().length === 0) && (!messages || messages.length === 0)) {
4233
+ throw new Error("Structured prompt payload must include a non-empty prompt or messages.");
4234
+ }
4291
4235
  return {
4292
- text: visibleText,
4293
- reasoning,
4294
- reasoningBlocks: normalizeReasoningBlocks(reasoningBlocks),
4295
- thinkBlocks: sanitized.thinkBlocks,
4296
- parseSource: composeParseSource(visibleText, reasoning)
4236
+ prompt,
4237
+ systemPrompt: typeof value.systemPrompt === "string" ? value.systemPrompt : undefined,
4238
+ messages: messages && messages.length > 0 ? messages.map((message) => ({ ...message })) : undefined
4297
4239
  };
4298
4240
  }
4299
- function normalizeReasoningBlocks(blocks) {
4300
- if (!Array.isArray(blocks)) {
4301
- return;
4241
+ function applyPromptOutdent(payload, enabled) {
4242
+ if (!enabled) {
4243
+ return payload;
4302
4244
  }
4303
- const normalized = blocks.map((block) => ({
4304
- turnIndex: block.turnIndex,
4305
- text: block.text.replace(RE_THINK_TAGS, "").trim()
4306
- })).filter((block) => Number.isFinite(block.turnIndex) && block.text.length > 0);
4307
- return normalized.length > 0 ? normalized : undefined;
4245
+ return {
4246
+ prompt: typeof payload.prompt === "string" ? sharedOutdent.string(payload.prompt) : undefined,
4247
+ systemPrompt: applyOutdentToOptionalPrompt(payload.systemPrompt, enabled),
4248
+ messages: payload.messages?.map((message) => ({
4249
+ ...message,
4250
+ content: typeof message.content === "string" ? sharedOutdent.string(message.content) : message.content
4251
+ }))
4252
+ };
4308
4253
  }
4309
- function appendReasoningBlock(blocks, transition) {
4310
- const text = transition.reasoningText?.replace(RE_THINK_TAGS, "").trim();
4311
- if (!text) {
4312
- return blocks;
4254
+ function applyOutdentToOptionalPrompt(value, enabled) {
4255
+ if (!enabled || typeof value !== "string") {
4256
+ return value;
4313
4257
  }
4314
- const next = [...blocks ?? [], { turnIndex: transition.turnIndex, text }];
4315
- return normalizeReasoningBlocks(next);
4258
+ return sharedOutdent.string(value);
4316
4259
  }
4317
- function composeParseSource(text, reasoning) {
4318
- if (typeof reasoning !== "string" || reasoning.length === 0) {
4319
- return text;
4320
- }
4321
- const sanitized = reasoning.replace(RE_THINK_TAGS, "");
4322
- if (sanitized.length === 0) {
4323
- return text;
4260
+ function mergeSystemPrompts(primary, secondary) {
4261
+ const prompts = [primary, secondary].map((value) => value?.trim()).filter((value) => Boolean(value));
4262
+ if (prompts.length === 0) {
4263
+ return;
4324
4264
  }
4325
- return `<think>${sanitized}</think>${text}`;
4265
+ return prompts.join(`
4266
+
4267
+ `);
4326
4268
  }
4327
- function aggregateUsage(attempts) {
4328
- let usage;
4329
- for (const attempt of attempts) {
4330
- usage = mergeUsage2(usage, attempt.usage);
4269
+ function normalizeStreamConfig(option) {
4270
+ if (typeof option === "boolean") {
4271
+ return {
4272
+ enabled: option
4273
+ };
4274
+ }
4275
+ if (!option) {
4276
+ return {
4277
+ enabled: false
4278
+ };
4331
4279
  }
4332
- return usage;
4280
+ return {
4281
+ enabled: option.enabled ?? true,
4282
+ onData: option.onData,
4283
+ onTurnTransition: option.onTurnTransition,
4284
+ to: option.to
4285
+ };
4333
4286
  }
4334
- function mergeUsage2(base, next) {
4335
- if (!base && !next) {
4336
- return;
4287
+ function normalizeDebugConfig(option) {
4288
+ if (typeof option === "boolean") {
4289
+ return {
4290
+ enabled: option,
4291
+ colors: true,
4292
+ verbose: false,
4293
+ logger: defaultDebugLogger
4294
+ };
4295
+ }
4296
+ if (!option) {
4297
+ return {
4298
+ enabled: false,
4299
+ colors: true,
4300
+ verbose: false,
4301
+ logger: defaultDebugLogger
4302
+ };
4337
4303
  }
4338
4304
  return {
4339
- inputTokens: (base?.inputTokens ?? 0) + (next?.inputTokens ?? 0),
4340
- outputTokens: (base?.outputTokens ?? 0) + (next?.outputTokens ?? 0),
4341
- totalTokens: (base?.totalTokens ?? 0) + (next?.totalTokens ?? 0),
4342
- cost: (base?.cost ?? 0) + (next?.cost ?? 0)
4305
+ enabled: option.enabled ?? true,
4306
+ colors: option.colors ?? true,
4307
+ verbose: option.verbose ?? false,
4308
+ logger: option.logger ?? defaultDebugLogger
4343
4309
  };
4344
4310
  }
4311
+ function defaultDebugLogger(line) {
4312
+ const { log } = globalThis.console;
4313
+ log(line);
4314
+ }
4345
4315
  function isPromptResolver(value) {
4346
4316
  return typeof value === "object" && value !== null && "resolvePrompt" in value && typeof value.resolvePrompt === "function";
4347
4317
  }
@@ -4355,95 +4325,6 @@ function isLLMMessage(value) {
4355
4325
  }
4356
4326
  return "content" in candidate;
4357
4327
  }
4358
- function joinReasoningSegments(parts) {
4359
- return parts.map((value) => value?.trim()).filter((value) => Boolean(value)).join(`
4360
-
4361
- `);
4362
- }
4363
- function stripThinkBlocks(text, thinkBlocks) {
4364
- if (thinkBlocks.length === 0) {
4365
- return text;
4366
- }
4367
- let output = "";
4368
- let cursor = 0;
4369
- for (const block of thinkBlocks) {
4370
- output += text.slice(cursor, block.start);
4371
- cursor = block.end;
4372
- }
4373
- output += text.slice(cursor);
4374
- return output;
4375
- }
4376
- function toStreamDataFingerprint(value) {
4377
- try {
4378
- return JSON.stringify(value);
4379
- } catch {
4380
- return "__unserializable__";
4381
- }
4382
- }
4383
- function emitDebugRequest(config, input) {
4384
- const requestBody = input.requestPayload.body !== undefined ? JSON.stringify(input.requestPayload.body, null, 2) : "(none)";
4385
- const requestMessages = input.requestPayload.messages !== undefined ? JSON.stringify(input.requestPayload.messages, null, 2) : "(none)";
4386
- const lines = [
4387
- color(config, title(config, [
4388
- `[${input.label}][request]`,
4389
- `attempt=${input.attempt}`,
4390
- `selfHealEnabled=${input.selfHealEnabled}`,
4391
- `selfHealAttempt=${input.selfHealAttempt}`
4392
- ].join(" ")), "cyan"),
4393
- dim(config, [
4394
- `provider=${input.provider ?? "unknown"}`,
4395
- `model=${input.model ?? "unknown"}`,
4396
- `stream=${input.stream}`
4397
- ].join(" ")),
4398
- color(config, "prompt:", "yellow"),
4399
- input.requestPayload.prompt ?? "(none)",
4400
- color(config, "messages:", "yellow"),
4401
- requestMessages,
4402
- color(config, "systemPrompt:", "yellow"),
4403
- input.requestPayload.systemPrompt ?? "(none)",
4404
- color(config, "request.body:", "yellow"),
4405
- requestBody
4406
- ];
4407
- emitDebug(config, lines.join(`
4408
- `));
4409
- }
4410
- function emitDebugResponse(config, input) {
4411
- const text = input.text.length > 0 ? input.text : "(none)";
4412
- const reasoning = input.reasoning.length > 0 ? input.reasoning : "(none)";
4413
- const metadata = [
4414
- `via=${input.via}`,
4415
- `textChars=${input.text.length}`,
4416
- `reasoningChars=${input.reasoning.length}`
4417
- ];
4418
- if (config.verbose) {
4419
- metadata.push(`parseSourceChars=${input.parseSource.length}`);
4420
- }
4421
- metadata.push(`finishReason=${input.finishReason ?? "unknown"}`, `usage=${JSON.stringify(input.usage ?? {})}`);
4422
- const lines = [
4423
- color(config, title(config, [
4424
- `[${input.label}][response]`,
4425
- `attempt=${input.attempt}`,
4426
- `selfHealEnabled=${input.selfHealEnabled}`,
4427
- `selfHealAttempt=${input.selfHealAttempt}`
4428
- ].join(" ")), "green"),
4429
- dim(config, metadata.join(" ")),
4430
- color(config, "text:", "yellow"),
4431
- text,
4432
- color(config, "reasoning:", "yellow"),
4433
- reasoning
4434
- ];
4435
- if (config.verbose) {
4436
- lines.push(color(config, "parseSource:", "yellow"), input.parseSource);
4437
- }
4438
- emitDebug(config, lines.join(`
4439
- `));
4440
- }
4441
- function emitDebug(config, message) {
4442
- if (!config.enabled) {
4443
- return;
4444
- }
4445
- config.logger(message);
4446
- }
4447
4328
 
4448
4329
  // src/generate.ts
4449
4330
  async function generate(adapter, promptOrOptions, callOptions) {
@@ -4519,7 +4400,7 @@ function normalizeGenerateInput(promptOrOptions, callOptions) {
4519
4400
  throw new Error("Missing prompt in generate(adapter, prompt, options?) call.");
4520
4401
  }
4521
4402
  return {
4522
- ...callOptions ?? {},
4403
+ ...callOptions,
4523
4404
  prompt: promptOrOptions
4524
4405
  };
4525
4406
  }
@@ -4545,9 +4426,6 @@ function prepareGeneratePromptPayload(payload, systemPrompt) {
4545
4426
  };
4546
4427
  }
4547
4428
 
4548
- // src/structured.ts
4549
- var import_jsonrepair3 = require("jsonrepair");
4550
-
4551
4429
  // src/parse.ts
4552
4430
  var import_jsonrepair2 = require("jsonrepair");
4553
4431
  function parseLLMOutput(output, schema, options = {}) {
@@ -4847,6 +4725,80 @@ function formatZodIssues(issues) {
4847
4725
  `);
4848
4726
  }
4849
4727
 
4728
+ // src/structured-streaming.ts
4729
+ var import_jsonrepair3 = require("jsonrepair");
4730
+ function parseStreamingStructuredData(parseSource) {
4731
+ const sanitized = sanitizeThink(parseSource);
4732
+ const start = findFirstJsonRootStart(sanitized.visibleText);
4733
+ if (start < 0) {
4734
+ return null;
4735
+ }
4736
+ const candidate = sanitized.visibleText.slice(start).trim();
4737
+ if (!candidate) {
4738
+ return null;
4739
+ }
4740
+ try {
4741
+ const repaired = import_jsonrepair3.jsonrepair(candidate);
4742
+ const parsed = JSON.parse(repaired);
4743
+ if (typeof parsed !== "object" || parsed === null) {
4744
+ return null;
4745
+ }
4746
+ return parsed;
4747
+ } catch {
4748
+ return null;
4749
+ }
4750
+ }
4751
+ function findFirstJsonRootStart(input) {
4752
+ const unquotedRootStart = findFirstUnquotedJsonRootStart(input);
4753
+ if (unquotedRootStart >= 0) {
4754
+ return unquotedRootStart;
4755
+ }
4756
+ return findFirstRawJsonRootStart(input);
4757
+ }
4758
+ function findFirstUnquotedJsonRootStart(input) {
4759
+ let inString = false;
4760
+ let escaped = false;
4761
+ for (let index = 0;index < input.length; index += 1) {
4762
+ const char = input[index];
4763
+ if (!char) {
4764
+ continue;
4765
+ }
4766
+ if (inString) {
4767
+ if (escaped) {
4768
+ escaped = false;
4769
+ continue;
4770
+ }
4771
+ if (char === "\\") {
4772
+ escaped = true;
4773
+ continue;
4774
+ }
4775
+ if (char === '"') {
4776
+ inString = false;
4777
+ }
4778
+ continue;
4779
+ }
4780
+ if (char === '"') {
4781
+ inString = true;
4782
+ continue;
4783
+ }
4784
+ if (char === "{" || char === "[") {
4785
+ return index;
4786
+ }
4787
+ }
4788
+ return -1;
4789
+ }
4790
+ function findFirstRawJsonRootStart(input) {
4791
+ const objectStart = input.indexOf("{");
4792
+ const arrayStart = input.indexOf("[");
4793
+ if (objectStart < 0) {
4794
+ return arrayStart;
4795
+ }
4796
+ if (arrayStart < 0) {
4797
+ return objectStart;
4798
+ }
4799
+ return Math.min(objectStart, arrayStart);
4800
+ }
4801
+
4850
4802
  // src/structured.ts
4851
4803
  class StructuredParseError extends Error {
4852
4804
  name = "StructuredParseError";
@@ -5084,7 +5036,7 @@ function normalizeStructuredInput(schemaOrOptions, promptInput, callOptions) {
5084
5036
  throw new Error("Missing prompt in structured(adapter, schema, prompt, options?) call.");
5085
5037
  }
5086
5038
  return {
5087
- ...callOptions ?? {},
5039
+ ...callOptions,
5088
5040
  schema: schemaOrOptions,
5089
5041
  prompt: promptInput
5090
5042
  };
@@ -5355,67 +5307,6 @@ async function callModel2(adapter, options) {
5355
5307
  debugLabel: "structured"
5356
5308
  });
5357
5309
  }
5358
- function parseStreamingStructuredData(parseSource) {
5359
- const sanitized = sanitizeThink(parseSource);
5360
- const start = findFirstJsonRootStart(sanitized.visibleText);
5361
- if (start < 0) {
5362
- return null;
5363
- }
5364
- const candidate = sanitized.visibleText.slice(start).trim();
5365
- if (!candidate) {
5366
- return null;
5367
- }
5368
- try {
5369
- const repaired = import_jsonrepair3.jsonrepair(candidate);
5370
- const parsed = JSON.parse(repaired);
5371
- if (typeof parsed !== "object" || parsed === null) {
5372
- return null;
5373
- }
5374
- return parsed;
5375
- } catch {
5376
- return null;
5377
- }
5378
- }
5379
- function findFirstJsonRootStart(input) {
5380
- let inString = false;
5381
- let escaped = false;
5382
- for (let index = 0;index < input.length; index += 1) {
5383
- const char = input[index];
5384
- if (!char) {
5385
- continue;
5386
- }
5387
- if (inString) {
5388
- if (escaped) {
5389
- escaped = false;
5390
- continue;
5391
- }
5392
- if (char === "\\") {
5393
- escaped = true;
5394
- continue;
5395
- }
5396
- if (char === '"') {
5397
- inString = false;
5398
- }
5399
- continue;
5400
- }
5401
- if (char === '"') {
5402
- inString = true;
5403
- continue;
5404
- }
5405
- if (char === "{" || char === "[") {
5406
- return index;
5407
- }
5408
- }
5409
- const objectStart = input.indexOf("{");
5410
- const arrayStart = input.indexOf("[");
5411
- if (objectStart < 0) {
5412
- return arrayStart;
5413
- }
5414
- if (arrayStart < 0) {
5415
- return objectStart;
5416
- }
5417
- return Math.min(objectStart, arrayStart);
5418
- }
5419
5310
  function parseWithObserve(output, schema, parseOptions, context) {
5420
5311
  const userParseTrace = parseOptions.onTrace;
5421
5312
  return parseLLMOutput(output, schema, {
@@ -5518,12 +5409,12 @@ function mergeStructuredOptions(defaults, overrides) {
5518
5409
  ...defaults,
5519
5410
  ...overrides,
5520
5411
  parse: {
5521
- ...defaults?.parse ?? {},
5522
- ...overrides?.parse ?? {}
5412
+ ...defaults?.parse,
5413
+ ...overrides?.parse
5523
5414
  },
5524
5415
  request: {
5525
- ...defaults?.request ?? {},
5526
- ...overrides?.request ?? {}
5416
+ ...defaults?.request,
5417
+ ...overrides?.request
5527
5418
  },
5528
5419
  stream: mergeObjectLike(defaults?.stream, overrides?.stream),
5529
5420
  selfHeal: mergeObjectLike(defaults?.selfHeal, overrides?.selfHeal),
@@ -5539,8 +5430,8 @@ function mergeGenerateOptions(defaults, overrides) {
5539
5430
  outdent: overrides?.outdent ?? defaults?.outdent,
5540
5431
  systemPrompt: overrides?.systemPrompt ?? defaults?.systemPrompt,
5541
5432
  request: {
5542
- ...defaults?.request ?? {},
5543
- ...overrides?.request ?? {}
5433
+ ...defaults?.request,
5434
+ ...overrides?.request
5544
5435
  },
5545
5436
  stream: mergeObjectLike(defaults?.stream, overrides?.stream),
5546
5437
  debug: mergeObjectLike(defaults?.debug, overrides?.debug),