extrait 0.7.0 → 0.7.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,9 +1,6 @@
1
1
  import { createRequire } from "node:module";
2
2
  var __require = /* @__PURE__ */ createRequire(import.meta.url);
3
3
 
4
- // src/extract.ts
5
- import { jsonrepair } from "jsonrepair";
6
-
7
4
  // src/utils/common.ts
8
5
  function toErrorMessage(error) {
9
6
  if (error instanceof Error) {
@@ -221,184 +218,77 @@ function isOnlyWhitespace(value) {
221
218
  return true;
222
219
  }
223
220
 
224
- // src/extract.ts
225
- var RE_EMPTY_OBJECT = /^\{\s*\}$/;
226
- var RE_EMPTY_ARRAY = /^\[\s*\]$/;
227
- var RE_BOUNDARY_CHAR = /[\s,.;:!?`"'()\[\]{}<>]/;
228
- var DEFAULT_EXTRACTION_HEURISTICS = {
229
- firstPassMin: 12,
230
- firstPassCap: 30,
231
- firstPassMultiplier: 6,
232
- secondPassMin: 4,
233
- secondPassCap: 8,
234
- secondPassMultiplier: 2,
235
- hintMaxLength: 50000
236
- };
237
- function extractJsonCandidates(input, options = {}) {
238
- const maxCandidates = options.maxCandidates ?? 5;
239
- const acceptArrays = options.acceptArrays ?? true;
240
- const allowRepairHints = options.allowRepairHints ?? true;
241
- const heuristics = resolveExtractionHeuristics(options.heuristics);
242
- const extractionInput = input;
243
- const candidates = [];
244
- candidates.push(...extractFromMarkdown(extractionInput, acceptArrays));
245
- candidates.push(...scanBalancedSegments(extractionInput, acceptArrays));
246
- if (candidates.length === 0 && extractionInput.trim()) {
247
- const content = extractionInput.trim();
248
- candidates.push({
249
- id: "raw:fallback",
250
- source: "raw",
251
- content,
252
- start: 0,
253
- end: extractionInput.length,
254
- score: 10 + Math.floor(lengthScore(content.length) / 3)
255
- });
256
- }
257
- const prefiltered = prefilterByJsonShape(candidates, acceptArrays);
258
- const firstPassLimit = clamp(maxCandidates * heuristics.firstPassMultiplier, heuristics.firstPassMin, heuristics.firstPassCap);
259
- const firstPass = prefiltered.slice(0, firstPassLimit);
260
- const secondPassLimit = Math.min(firstPass.length, clamp(maxCandidates * heuristics.secondPassMultiplier, heuristics.secondPassMin, heuristics.secondPassCap));
261
- for (let i = 0;i < secondPassLimit; i += 1) {
262
- const candidate = firstPass[i];
263
- if (!candidate) {
264
- continue;
265
- }
266
- const parseHint = buildParseHint(candidate.content, allowRepairHints, heuristics.hintMaxLength);
267
- if (!parseHint) {
268
- continue;
269
- }
270
- candidate.parseHint = parseHint;
271
- candidate.score += parseHintBonus(parseHint);
221
+ // src/extract-parse-hint.ts
222
+ import { jsonrepair } from "jsonrepair";
223
+ function buildParseHint(content, allowRepair, hintMaxLength) {
224
+ if (content.length > hintMaxLength) {
225
+ return null;
272
226
  }
273
- const sorted = sortCandidates(firstPass);
274
- const deduped = dedupeCandidates(sorted);
275
- return deduped.slice(0, maxCandidates).map((candidate, index) => ({
276
- id: `${candidate.source}:${index}`,
277
- source: candidate.source,
278
- content: candidate.content,
279
- language: candidate.language,
280
- parseHint: candidate.parseHint,
281
- start: candidate.start,
282
- end: candidate.end,
283
- score: candidate.score
284
- }));
285
- }
286
- function prefilterByJsonShape(candidates, acceptArrays) {
287
- const shaped = candidates.map((candidate) => {
288
- const shapeScore = jsonShapeScore(candidate.content, acceptArrays);
227
+ try {
289
228
  return {
290
- ...candidate,
291
- shapeScore,
292
- score: candidate.score + shapeScore
229
+ success: true,
230
+ parsed: JSON.parse(content),
231
+ repaired: null,
232
+ usedRepair: false,
233
+ stage: "parse",
234
+ error: ""
293
235
  };
294
- });
295
- const sorted = sortCandidates(shaped);
296
- const deduped = dedupeCandidates(sorted);
297
- const filtered = deduped.filter((candidate) => passesShapeFilter(candidate));
298
- if (filtered.length > 0) {
299
- const fallback = deduped.find((candidate) => !passesShapeFilter(candidate));
300
- if (fallback) {
301
- filtered.push(fallback);
236
+ } catch (directError) {
237
+ if (!allowRepair) {
238
+ return {
239
+ success: false,
240
+ parsed: null,
241
+ repaired: null,
242
+ usedRepair: false,
243
+ stage: "parse",
244
+ error: toErrorMessage(directError)
245
+ };
302
246
  }
303
- return sortCandidates(filtered);
304
- }
305
- return deduped.slice(0, Math.min(1, deduped.length));
306
- }
307
- function extractFromMarkdown(input, acceptArrays) {
308
- const blocks = extractMarkdownCodeBlocks(input);
309
- return blocks.flatMap((block, index) => {
310
- const language = block.language || null;
311
- const content = block.code.trim();
312
- if (!content) {
313
- return [];
247
+ let repaired;
248
+ try {
249
+ repaired = jsonrepair(content);
250
+ } catch (repairError) {
251
+ return {
252
+ success: false,
253
+ parsed: null,
254
+ repaired: null,
255
+ usedRepair: true,
256
+ stage: "repair",
257
+ error: toErrorMessage(repairError)
258
+ };
314
259
  }
315
- if (!looksLikeJsonEnvelope(content, acceptArrays)) {
316
- return [];
260
+ try {
261
+ return {
262
+ success: true,
263
+ parsed: JSON.parse(repaired),
264
+ repaired,
265
+ usedRepair: true,
266
+ stage: "parse",
267
+ error: ""
268
+ };
269
+ } catch (parseError) {
270
+ return {
271
+ success: false,
272
+ parsed: null,
273
+ repaired,
274
+ usedRepair: true,
275
+ stage: "parse",
276
+ error: toErrorMessage(parseError || directError)
277
+ };
317
278
  }
318
- const langBonus = languageBonus(language);
319
- return [
320
- {
321
- id: `fenced:${index}`,
322
- source: "fenced",
323
- language,
324
- content,
325
- start: block.start,
326
- end: block.end,
327
- score: 260 + langBonus + lengthScore(content.length)
328
- }
329
- ];
330
- });
279
+ }
331
280
  }
332
- function scanBalancedSegments(input, acceptArrays) {
333
- const results = [];
334
- const stack = [];
335
- let inString = false;
336
- let quote = null;
337
- let escaped = false;
338
- for (let i = 0;i < input.length; i += 1) {
339
- const char = input[i];
340
- if (!char) {
341
- continue;
342
- }
343
- if (inString) {
344
- if (escaped) {
345
- escaped = false;
346
- continue;
347
- }
348
- if (char === "\\") {
349
- escaped = true;
350
- continue;
351
- }
352
- if (char === quote) {
353
- inString = false;
354
- quote = null;
355
- }
356
- continue;
357
- }
358
- const allowSingleQuoted = stack.length > 0;
359
- if (char === '"' || allowSingleQuoted && (char === "'" || char === "`")) {
360
- inString = true;
361
- quote = char;
362
- continue;
363
- }
364
- if (char === "{" || char === "[") {
365
- stack.push({ char, index: i });
366
- continue;
367
- }
368
- if (char !== "}" && char !== "]") {
369
- continue;
370
- }
371
- const expectedOpen = char === "}" ? "{" : "[";
372
- while (stack.length > 0 && stack[stack.length - 1]?.char !== expectedOpen) {
373
- stack.pop();
374
- }
375
- const opened = stack.pop();
376
- if (!opened) {
377
- continue;
378
- }
379
- if (stack.length > 0) {
380
- continue;
381
- }
382
- if (!acceptArrays && opened.char === "[") {
383
- continue;
384
- }
385
- const content = input.slice(opened.index, i + 1).trim();
386
- if (!content) {
387
- continue;
388
- }
389
- const rootBonus = opened.char === "{" ? 40 : 20;
390
- const boundaryBonus = boundaryScore(input, opened.index, i + 1);
391
- results.push({
392
- id: `scan:${results.length}`,
393
- source: "scan",
394
- content,
395
- start: opened.index,
396
- end: i + 1,
397
- score: 120 + rootBonus + boundaryBonus + lengthScore(content.length)
398
- });
281
+ function parseHintBonus(hint) {
282
+ if (hint.success) {
283
+ return hint.usedRepair ? 70 : 120;
399
284
  }
400
- return results;
285
+ return hint.usedRepair ? -20 : -10;
401
286
  }
287
+
288
+ // src/extract-shape.ts
289
+ var RE_EMPTY_OBJECT = /^\{\s*\}$/;
290
+ var RE_EMPTY_ARRAY = /^\[\s*\]$/;
291
+ var RE_BOUNDARY_CHAR = /[\s,.;:!?`"'()[\]{}<>]/;
402
292
  function looksLikeJsonEnvelope(content, acceptArrays) {
403
293
  const trimmed = content.trim();
404
294
  if (trimmed.startsWith("{")) {
@@ -507,95 +397,6 @@ function passesShapeFilter(candidate) {
507
397
  }
508
398
  return candidate.shapeScore >= 35;
509
399
  }
510
- function buildParseHint(content, allowRepair, hintMaxLength) {
511
- if (content.length > hintMaxLength) {
512
- return null;
513
- }
514
- try {
515
- return {
516
- success: true,
517
- parsed: JSON.parse(content),
518
- repaired: null,
519
- usedRepair: false,
520
- stage: "parse",
521
- error: ""
522
- };
523
- } catch (directError) {
524
- if (!allowRepair) {
525
- return {
526
- success: false,
527
- parsed: null,
528
- repaired: null,
529
- usedRepair: false,
530
- stage: "parse",
531
- error: toErrorMessage(directError)
532
- };
533
- }
534
- let repaired;
535
- try {
536
- repaired = jsonrepair(content);
537
- } catch (repairError) {
538
- return {
539
- success: false,
540
- parsed: null,
541
- repaired: null,
542
- usedRepair: true,
543
- stage: "repair",
544
- error: toErrorMessage(repairError)
545
- };
546
- }
547
- try {
548
- return {
549
- success: true,
550
- parsed: JSON.parse(repaired),
551
- repaired,
552
- usedRepair: true,
553
- stage: "parse",
554
- error: ""
555
- };
556
- } catch (parseError) {
557
- return {
558
- success: false,
559
- parsed: null,
560
- repaired,
561
- usedRepair: true,
562
- stage: "parse",
563
- error: toErrorMessage(parseError || directError)
564
- };
565
- }
566
- }
567
- }
568
- function resolveExtractionHeuristics(input) {
569
- const merged = {
570
- ...DEFAULT_EXTRACTION_HEURISTICS,
571
- ...input
572
- };
573
- const firstPassMin = normalizeInteger(merged.firstPassMin, DEFAULT_EXTRACTION_HEURISTICS.firstPassMin);
574
- const firstPassCap = Math.max(firstPassMin, normalizeInteger(merged.firstPassCap, DEFAULT_EXTRACTION_HEURISTICS.firstPassCap));
575
- const secondPassMin = normalizeInteger(merged.secondPassMin, DEFAULT_EXTRACTION_HEURISTICS.secondPassMin);
576
- const secondPassCap = Math.max(secondPassMin, normalizeInteger(merged.secondPassCap, DEFAULT_EXTRACTION_HEURISTICS.secondPassCap));
577
- return {
578
- firstPassMin,
579
- firstPassCap,
580
- firstPassMultiplier: normalizeInteger(merged.firstPassMultiplier, DEFAULT_EXTRACTION_HEURISTICS.firstPassMultiplier),
581
- secondPassMin,
582
- secondPassCap,
583
- secondPassMultiplier: normalizeInteger(merged.secondPassMultiplier, DEFAULT_EXTRACTION_HEURISTICS.secondPassMultiplier),
584
- hintMaxLength: normalizeInteger(merged.hintMaxLength, DEFAULT_EXTRACTION_HEURISTICS.hintMaxLength)
585
- };
586
- }
587
- function normalizeInteger(value, fallback) {
588
- if (typeof value !== "number" || !Number.isFinite(value)) {
589
- return fallback;
590
- }
591
- return Math.max(0, Math.floor(value));
592
- }
593
- function parseHintBonus(hint) {
594
- if (hint.success) {
595
- return hint.usedRepair ? 70 : 120;
596
- }
597
- return hint.usedRepair ? -20 : -10;
598
- }
599
400
  function hasBalancedJsonDelimiters(input) {
600
401
  const stack = [];
601
402
  let inString = false;
@@ -681,6 +482,207 @@ function dedupeCandidates(candidates) {
681
482
  function clamp(value, min, max) {
682
483
  return Math.max(min, Math.min(max, Math.floor(value)));
683
484
  }
485
+ function normalizeInteger(value, fallback) {
486
+ if (typeof value !== "number" || !Number.isFinite(value)) {
487
+ return fallback;
488
+ }
489
+ return Math.max(0, Math.floor(value));
490
+ }
491
+ function resolveExtractionHeuristics(input, defaults) {
492
+ const merged = {
493
+ ...defaults,
494
+ ...input
495
+ };
496
+ const firstPassMin = normalizeInteger(merged.firstPassMin, defaults.firstPassMin);
497
+ const firstPassCap = Math.max(firstPassMin, normalizeInteger(merged.firstPassCap, defaults.firstPassCap));
498
+ const secondPassMin = normalizeInteger(merged.secondPassMin, defaults.secondPassMin);
499
+ const secondPassCap = Math.max(secondPassMin, normalizeInteger(merged.secondPassCap, defaults.secondPassCap));
500
+ return {
501
+ firstPassMin,
502
+ firstPassCap,
503
+ firstPassMultiplier: normalizeInteger(merged.firstPassMultiplier, defaults.firstPassMultiplier),
504
+ secondPassMin,
505
+ secondPassCap,
506
+ secondPassMultiplier: normalizeInteger(merged.secondPassMultiplier, defaults.secondPassMultiplier),
507
+ hintMaxLength: normalizeInteger(merged.hintMaxLength, defaults.hintMaxLength)
508
+ };
509
+ }
510
+
511
+ // src/extract.ts
512
+ var DEFAULT_EXTRACTION_HEURISTICS = {
513
+ firstPassMin: 12,
514
+ firstPassCap: 30,
515
+ firstPassMultiplier: 6,
516
+ secondPassMin: 4,
517
+ secondPassCap: 8,
518
+ secondPassMultiplier: 2,
519
+ hintMaxLength: 50000
520
+ };
521
+ function extractJsonCandidates(input, options = {}) {
522
+ const maxCandidates = options.maxCandidates ?? 5;
523
+ const acceptArrays = options.acceptArrays ?? true;
524
+ const allowRepairHints = options.allowRepairHints ?? true;
525
+ const heuristics = resolveExtractionHeuristics(options.heuristics, DEFAULT_EXTRACTION_HEURISTICS);
526
+ const extractionInput = input;
527
+ const candidates = [];
528
+ candidates.push(...extractFromMarkdown(extractionInput, acceptArrays));
529
+ candidates.push(...scanBalancedSegments(extractionInput, acceptArrays));
530
+ if (candidates.length === 0 && extractionInput.trim()) {
531
+ const content = extractionInput.trim();
532
+ candidates.push({
533
+ id: "raw:fallback",
534
+ source: "raw",
535
+ content,
536
+ start: 0,
537
+ end: extractionInput.length,
538
+ score: 10 + Math.floor(lengthScore(content.length) / 3)
539
+ });
540
+ }
541
+ const prefiltered = prefilterByJsonShape(candidates, acceptArrays);
542
+ const firstPassLimit = clamp(maxCandidates * heuristics.firstPassMultiplier, heuristics.firstPassMin, heuristics.firstPassCap);
543
+ const firstPass = prefiltered.slice(0, firstPassLimit);
544
+ const secondPassLimit = Math.min(firstPass.length, clamp(maxCandidates * heuristics.secondPassMultiplier, heuristics.secondPassMin, heuristics.secondPassCap));
545
+ for (let i = 0;i < secondPassLimit; i += 1) {
546
+ const candidate = firstPass[i];
547
+ if (!candidate) {
548
+ continue;
549
+ }
550
+ const parseHint = buildParseHint(candidate.content, allowRepairHints, heuristics.hintMaxLength);
551
+ if (!parseHint) {
552
+ continue;
553
+ }
554
+ candidate.parseHint = parseHint;
555
+ candidate.score += parseHintBonus(parseHint);
556
+ }
557
+ const sorted = sortCandidates(firstPass);
558
+ const deduped = dedupeCandidates(sorted);
559
+ return deduped.slice(0, maxCandidates).map((candidate, index) => ({
560
+ id: `${candidate.source}:${index}`,
561
+ source: candidate.source,
562
+ content: candidate.content,
563
+ language: candidate.language,
564
+ parseHint: candidate.parseHint,
565
+ start: candidate.start,
566
+ end: candidate.end,
567
+ score: candidate.score
568
+ }));
569
+ }
570
+ function prefilterByJsonShape(candidates, acceptArrays) {
571
+ const shaped = candidates.map((candidate) => {
572
+ const shapeScore = jsonShapeScore(candidate.content, acceptArrays);
573
+ return {
574
+ ...candidate,
575
+ shapeScore,
576
+ score: candidate.score + shapeScore
577
+ };
578
+ });
579
+ const sorted = sortCandidates(shaped);
580
+ const deduped = dedupeCandidates(sorted);
581
+ const filtered = deduped.filter((candidate) => passesShapeFilter(candidate));
582
+ if (filtered.length > 0) {
583
+ const fallback = deduped.find((candidate) => !passesShapeFilter(candidate));
584
+ if (fallback) {
585
+ filtered.push(fallback);
586
+ }
587
+ return sortCandidates(filtered);
588
+ }
589
+ return deduped.slice(0, Math.min(1, deduped.length));
590
+ }
591
+ function extractFromMarkdown(input, acceptArrays) {
592
+ const blocks = extractMarkdownCodeBlocks(input);
593
+ return blocks.flatMap((block, index) => {
594
+ const language = block.language || null;
595
+ const content = block.code.trim();
596
+ if (!content) {
597
+ return [];
598
+ }
599
+ if (!looksLikeJsonEnvelope(content, acceptArrays)) {
600
+ return [];
601
+ }
602
+ const langBonus = languageBonus(language);
603
+ return [
604
+ {
605
+ id: `fenced:${index}`,
606
+ source: "fenced",
607
+ language,
608
+ content,
609
+ start: block.start,
610
+ end: block.end,
611
+ score: 260 + langBonus + lengthScore(content.length)
612
+ }
613
+ ];
614
+ });
615
+ }
616
+ function scanBalancedSegments(input, acceptArrays) {
617
+ const results = [];
618
+ const stack = [];
619
+ let inString = false;
620
+ let quote = null;
621
+ let escaped = false;
622
+ for (let i = 0;i < input.length; i += 1) {
623
+ const char = input[i];
624
+ if (!char) {
625
+ continue;
626
+ }
627
+ if (inString) {
628
+ if (escaped) {
629
+ escaped = false;
630
+ continue;
631
+ }
632
+ if (char === "\\") {
633
+ escaped = true;
634
+ continue;
635
+ }
636
+ if (char === quote) {
637
+ inString = false;
638
+ quote = null;
639
+ }
640
+ continue;
641
+ }
642
+ const allowSingleQuoted = stack.length > 0;
643
+ if (char === '"' || allowSingleQuoted && (char === "'" || char === "`")) {
644
+ inString = true;
645
+ quote = char;
646
+ continue;
647
+ }
648
+ if (char === "{" || char === "[") {
649
+ stack.push({ char, index: i });
650
+ continue;
651
+ }
652
+ if (char !== "}" && char !== "]") {
653
+ continue;
654
+ }
655
+ const expectedOpen = char === "}" ? "{" : "[";
656
+ while (stack.length > 0 && stack[stack.length - 1]?.char !== expectedOpen) {
657
+ stack.pop();
658
+ }
659
+ const opened = stack.pop();
660
+ if (!opened) {
661
+ continue;
662
+ }
663
+ if (stack.length > 0) {
664
+ continue;
665
+ }
666
+ if (!acceptArrays && opened.char === "[") {
667
+ continue;
668
+ }
669
+ const content = input.slice(opened.index, i + 1).trim();
670
+ if (!content) {
671
+ continue;
672
+ }
673
+ const rootBonus = opened.char === "{" ? 40 : 20;
674
+ const boundaryBonus = boundaryScore(input, opened.index, i + 1);
675
+ results.push({
676
+ id: `scan:${results.length}`,
677
+ source: "scan",
678
+ content,
679
+ start: opened.index,
680
+ end: i + 1,
681
+ score: 120 + rootBonus + boundaryBonus + lengthScore(content.length)
682
+ });
683
+ }
684
+ return results;
685
+ }
684
686
  // src/schema.ts
685
687
  var RE_SIMPLE_IDENTIFIER = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
686
688
  var RE_WHITESPACE = /\s+/g;
@@ -1129,17 +1131,107 @@ function findSSEBoundary(buffer) {
1129
1131
  return Math.max(crlfIndex, lfIndex);
1130
1132
  }
1131
1133
 
1132
- // src/providers/mcp-runtime.ts
1133
- var DEFAULT_MAX_TOOL_ROUNDS = 100;
1134
- async function resolveMCPToolset(clients) {
1135
- if (!Array.isArray(clients) || clients.length === 0) {
1136
- return {
1137
- tools: [],
1138
- byName: new Map
1139
- };
1140
- }
1141
- const listed = [];
1142
- for (const client of clients) {
1134
+ // src/providers/mcp-runtime-debug.ts
1135
+ function formatToolExecutionDebugLine(execution) {
1136
+ const status = execution.error ? "error" : "ok";
1137
+ const scope = [execution.provider, execution.model].filter(Boolean).join("/") || "unknown";
1138
+ const toolRef = execution.clientId ? `${execution.clientId}:${execution.name ?? "unknown"}` : execution.name ?? "unknown";
1139
+ const duration = typeof execution.durationMs === "number" ? ` ${execution.durationMs}ms` : "";
1140
+ const base = `[tool:mcp:${status}] ${scope} ${toolRef}#${execution.callId}${duration}`;
1141
+ if (execution.error) {
1142
+ return `${base} -> ${execution.error}`;
1143
+ }
1144
+ return base;
1145
+ }
1146
+ function emitToolExecution(request, execution) {
1147
+ request.onToolExecution?.(execution);
1148
+ const debug = resolveToolDebugOptions(request.toolDebug);
1149
+ if (!debug.enabled) {
1150
+ return;
1151
+ }
1152
+ debug.logger(formatToolExecutionDebugLine(execution));
1153
+ if (debug.includeRequest) {
1154
+ debug.logger(formatToolExecutionRequestDebugLine(execution, debug));
1155
+ }
1156
+ if (debug.includeResult && (!execution.error || debug.includeResultOnError)) {
1157
+ debug.logger(formatToolExecutionResultDebugLine(execution, debug));
1158
+ }
1159
+ }
1160
+ function resolveToolDebugOptions(value) {
1161
+ if (value === true) {
1162
+ return {
1163
+ enabled: true,
1164
+ logger: defaultToolDebugLogger,
1165
+ includeRequest: true,
1166
+ includeResult: true,
1167
+ includeResultOnError: true,
1168
+ pretty: false
1169
+ };
1170
+ }
1171
+ if (value === undefined || value === false) {
1172
+ return {
1173
+ enabled: false,
1174
+ logger: () => {
1175
+ return;
1176
+ },
1177
+ includeRequest: false,
1178
+ includeResult: false,
1179
+ includeResultOnError: false,
1180
+ pretty: false
1181
+ };
1182
+ }
1183
+ return {
1184
+ enabled: value.enabled ?? true,
1185
+ logger: value.logger ?? defaultToolDebugLogger,
1186
+ includeRequest: value.includeRequest ?? true,
1187
+ includeResult: value.includeResult ?? true,
1188
+ includeResultOnError: value.includeResultOnError ?? true,
1189
+ pretty: value.pretty ?? false
1190
+ };
1191
+ }
1192
+ function defaultToolDebugLogger(line) {
1193
+ const { log } = globalThis.console;
1194
+ log(line);
1195
+ }
1196
+ function formatToolExecutionRequestDebugLine(execution, debug) {
1197
+ const scope = [execution.provider, execution.model].filter(Boolean).join("/") || "unknown";
1198
+ const toolRef = execution.clientId ? `${execution.clientId}:${execution.name ?? "unknown"}` : execution.name ?? "unknown";
1199
+ const payload = formatDebugPayload(execution.arguments, debug.pretty);
1200
+ return `[tool:mcp:request] ${scope} ${toolRef}#${execution.callId} arguments=${payload}`;
1201
+ }
1202
+ function formatToolExecutionResultDebugLine(execution, debug) {
1203
+ const scope = [execution.provider, execution.model].filter(Boolean).join("/") || "unknown";
1204
+ const toolRef = execution.clientId ? `${execution.clientId}:${execution.name ?? "unknown"}` : execution.name ?? "unknown";
1205
+ if (execution.error) {
1206
+ const payload2 = formatDebugPayload({ error: execution.error }, debug.pretty);
1207
+ return `[tool:mcp:result:error] ${scope} ${toolRef}#${execution.callId} output=${payload2}`;
1208
+ }
1209
+ const payload = formatDebugPayload(execution.output, debug.pretty);
1210
+ return `[tool:mcp:result:ok] ${scope} ${toolRef}#${execution.callId} output=${payload}`;
1211
+ }
1212
+ function formatDebugPayload(value, pretty) {
1213
+ if (value === undefined) {
1214
+ return "undefined";
1215
+ }
1216
+ try {
1217
+ const serialized = JSON.stringify(value, null, pretty ? 2 : 0);
1218
+ return serialized ?? "undefined";
1219
+ } catch {
1220
+ return String(value);
1221
+ }
1222
+ }
1223
+
1224
+ // src/providers/mcp-runtime.ts
1225
+ var DEFAULT_MAX_TOOL_ROUNDS = 100;
1226
+ async function resolveMCPToolset(clients) {
1227
+ if (!Array.isArray(clients) || clients.length === 0) {
1228
+ return {
1229
+ tools: [],
1230
+ byName: new Map
1231
+ };
1232
+ }
1233
+ const listed = [];
1234
+ for (const client of clients) {
1143
1235
  let cursor;
1144
1236
  do {
1145
1237
  const page = await client.listTools(cursor ? { cursor } : undefined);
@@ -1321,90 +1413,6 @@ function stringifyToolOutput(value) {
1321
1413
  }
1322
1414
  return JSON.stringify(value ?? null);
1323
1415
  }
1324
- function formatToolExecutionDebugLine(execution) {
1325
- const status = execution.error ? "error" : "ok";
1326
- const scope = [execution.provider, execution.model].filter(Boolean).join("/") || "unknown";
1327
- const toolRef = execution.clientId ? `${execution.clientId}:${execution.name ?? "unknown"}` : execution.name ?? "unknown";
1328
- const duration = typeof execution.durationMs === "number" ? ` ${execution.durationMs}ms` : "";
1329
- const base = `[tool:mcp:${status}] ${scope} ${toolRef}#${execution.callId}${duration}`;
1330
- if (execution.error) {
1331
- return `${base} -> ${execution.error}`;
1332
- }
1333
- return base;
1334
- }
1335
- function emitToolExecution(request, execution) {
1336
- request.onToolExecution?.(execution);
1337
- const debug = resolveToolDebugOptions(request.toolDebug);
1338
- if (!debug.enabled) {
1339
- return;
1340
- }
1341
- debug.logger(formatToolExecutionDebugLine(execution));
1342
- if (debug.includeRequest) {
1343
- debug.logger(formatToolExecutionRequestDebugLine(execution, debug));
1344
- }
1345
- if (debug.includeResult && (!execution.error || debug.includeResultOnError)) {
1346
- debug.logger(formatToolExecutionResultDebugLine(execution, debug));
1347
- }
1348
- }
1349
- function resolveToolDebugOptions(value) {
1350
- if (value === true) {
1351
- return {
1352
- enabled: true,
1353
- logger: (line) => console.log(line),
1354
- includeRequest: true,
1355
- includeResult: true,
1356
- includeResultOnError: true,
1357
- pretty: false
1358
- };
1359
- }
1360
- if (value === undefined || value === false) {
1361
- return {
1362
- enabled: false,
1363
- logger: () => {
1364
- return;
1365
- },
1366
- includeRequest: false,
1367
- includeResult: false,
1368
- includeResultOnError: false,
1369
- pretty: false
1370
- };
1371
- }
1372
- return {
1373
- enabled: value.enabled ?? true,
1374
- logger: value.logger ?? ((line) => console.log(line)),
1375
- includeRequest: value.includeRequest ?? true,
1376
- includeResult: value.includeResult ?? true,
1377
- includeResultOnError: value.includeResultOnError ?? true,
1378
- pretty: value.pretty ?? false
1379
- };
1380
- }
1381
- function formatToolExecutionRequestDebugLine(execution, debug) {
1382
- const scope = [execution.provider, execution.model].filter(Boolean).join("/") || "unknown";
1383
- const toolRef = execution.clientId ? `${execution.clientId}:${execution.name ?? "unknown"}` : execution.name ?? "unknown";
1384
- const payload = formatDebugPayload(execution.arguments, debug.pretty);
1385
- return `[tool:mcp:request] ${scope} ${toolRef}#${execution.callId} arguments=${payload}`;
1386
- }
1387
- function formatToolExecutionResultDebugLine(execution, debug) {
1388
- const scope = [execution.provider, execution.model].filter(Boolean).join("/") || "unknown";
1389
- const toolRef = execution.clientId ? `${execution.clientId}:${execution.name ?? "unknown"}` : execution.name ?? "unknown";
1390
- if (execution.error) {
1391
- const payload2 = formatDebugPayload({ error: execution.error }, debug.pretty);
1392
- return `[tool:mcp:result:error] ${scope} ${toolRef}#${execution.callId} output=${payload2}`;
1393
- }
1394
- const payload = formatDebugPayload(execution.output, debug.pretty);
1395
- return `[tool:mcp:result:ok] ${scope} ${toolRef}#${execution.callId} output=${payload}`;
1396
- }
1397
- function formatDebugPayload(value, pretty) {
1398
- if (value === undefined) {
1399
- return "undefined";
1400
- }
1401
- try {
1402
- const serialized = JSON.stringify(value, null, pretty ? 2 : 0);
1403
- return serialized ?? "undefined";
1404
- } catch {
1405
- return String(value);
1406
- }
1407
- }
1408
1416
  function countNameCollisions(names) {
1409
1417
  const out = new Map;
1410
1418
  for (const name of names) {
@@ -1601,103 +1609,89 @@ function createOpenAICompatibleAdapter(options) {
1601
1609
  if (usesMCP) {
1602
1610
  return streamWithChatCompletionsWithMCP(options, fetcher, path, request, callbacks);
1603
1611
  }
1604
- const response = await fetcher(buildURL(options.baseURL, path), {
1605
- method: "POST",
1606
- headers: buildHeaders(options),
1607
- body: JSON.stringify(cleanUndefined({
1608
- ...options.defaultBody,
1609
- ...request.body,
1610
- model: options.model,
1611
- messages: buildMessages(request),
1612
- temperature: request.temperature,
1613
- reasoning_effort: toOpenAIReasoningEffort(request.reasoningEffort),
1614
- max_tokens: request.maxTokens,
1615
- stream: true
1616
- })),
1617
- signal: request.signal
1618
- });
1619
- if (!response.ok) {
1620
- const message = await response.text();
1621
- throw new Error(`HTTP ${response.status}: ${message}`);
1622
- }
1623
- callbacks.onStart?.();
1624
- let text = "";
1625
- let reasoning = "";
1626
- let usage;
1627
- let finishReason;
1628
- await consumeSSE(response, (data) => {
1629
- if (data === "[DONE]") {
1630
- return;
1631
- }
1632
- const json = safeJSONParse(data);
1633
- if (!isRecord2(json)) {
1634
- return;
1635
- }
1636
- const delta = pickAssistantDelta(json);
1637
- const reasoningDelta = pickAssistantReasoningDelta(json);
1638
- const chunkUsage = pickUsage(json);
1639
- const chunkFinishReason = pickFinishReason(json);
1640
- usage = preferLatestUsage(usage, chunkUsage);
1641
- if (chunkFinishReason) {
1642
- finishReason = chunkFinishReason;
1643
- }
1644
- if (delta) {
1645
- text += delta;
1646
- callbacks.onToken?.(delta);
1647
- }
1648
- if (reasoningDelta) {
1649
- reasoning += reasoningDelta;
1650
- }
1651
- if (delta || reasoningDelta || chunkUsage || chunkFinishReason) {
1652
- const chunk = {
1653
- textDelta: delta,
1654
- reasoningDelta: reasoningDelta || undefined,
1655
- raw: json,
1656
- usage: chunkUsage,
1657
- finishReason: chunkFinishReason
1658
- };
1659
- callbacks.onChunk?.(chunk);
1660
- }
1661
- });
1662
- const out = {
1663
- text,
1664
- reasoning: reasoning.length > 0 ? reasoning : undefined,
1665
- usage,
1666
- finishReason
1667
- };
1668
- callbacks.onComplete?.(out);
1669
- return out;
1612
+ return streamWithChatCompletionsPassThrough(options, fetcher, path, request, callbacks);
1670
1613
  },
1671
1614
  async embed(request) {
1672
- const body = cleanUndefined({
1673
- ...options.defaultBody,
1674
- ...request.body,
1675
- model: request.model ?? options.model,
1676
- input: request.input,
1677
- dimensions: request.dimensions,
1678
- encoding_format: "float"
1679
- });
1680
- const response = await fetcher(buildURL(options.baseURL, embeddingPath), {
1681
- method: "POST",
1682
- headers: buildHeaders(options),
1683
- body: JSON.stringify(body)
1684
- });
1685
- if (!response.ok) {
1686
- const message = await response.text();
1687
- throw new Error(`HTTP ${response.status}: ${message}`);
1688
- }
1689
- const json = await response.json();
1690
- const data = json.data;
1691
- if (!Array.isArray(data)) {
1692
- throw new Error("Unexpected embedding response: missing data array");
1693
- }
1694
- return {
1695
- embeddings: data.map((d) => isRecord2(d) && Array.isArray(d.embedding) ? d.embedding : []),
1696
- model: pickString(json.model) ?? body.model,
1697
- usage: pickUsage(json),
1698
- raw: json
1699
- };
1615
+ return embedOpenAI(options, fetcher, embeddingPath, request);
1616
+ }
1617
+ };
1618
+ }
1619
+ async function streamWithChatCompletionsPassThrough(options, fetcher, path, request, callbacks) {
1620
+ const response = await sendOpenAIRequest(options, fetcher, path, request, buildChatCompletionsBody(options, request, {
1621
+ messages: buildMessages(request),
1622
+ stream: true
1623
+ }));
1624
+ if (!response.ok) {
1625
+ const message = await response.text();
1626
+ throw new Error(`HTTP ${response.status}: ${message}`);
1627
+ }
1628
+ callbacks.onStart?.();
1629
+ let text = "";
1630
+ let reasoning = "";
1631
+ let usage;
1632
+ let finishReason;
1633
+ await consumeSSE(response, (data) => {
1634
+ if (data === "[DONE]") {
1635
+ return;
1636
+ }
1637
+ const json = safeJSONParse(data);
1638
+ if (!isRecord2(json)) {
1639
+ return;
1640
+ }
1641
+ const delta = pickAssistantDelta(json);
1642
+ const reasoningDelta = pickAssistantReasoningDelta(json);
1643
+ const chunkUsage = pickUsage(json);
1644
+ const chunkFinishReason = pickFinishReason(json);
1645
+ usage = preferLatestUsage(usage, chunkUsage);
1646
+ if (chunkFinishReason) {
1647
+ finishReason = chunkFinishReason;
1648
+ }
1649
+ if (delta) {
1650
+ text += delta;
1651
+ callbacks.onToken?.(delta);
1700
1652
  }
1653
+ if (reasoningDelta) {
1654
+ reasoning += reasoningDelta;
1655
+ }
1656
+ emitOpenAIStreamChunk(callbacks, undefined, json, delta, reasoningDelta, chunkUsage, chunkFinishReason);
1657
+ });
1658
+ const out = {
1659
+ text,
1660
+ reasoning: reasoning.length > 0 ? reasoning : undefined,
1661
+ usage,
1662
+ finishReason
1663
+ };
1664
+ callbacks.onComplete?.(out);
1665
+ return out;
1666
+ }
1667
+ async function embedOpenAI(options, fetcher, path, request) {
1668
+ const body = cleanUndefined({
1669
+ ...options.defaultBody,
1670
+ ...request.body,
1671
+ model: request.model ?? options.model,
1672
+ input: request.input,
1673
+ dimensions: request.dimensions,
1674
+ encoding_format: "float"
1675
+ });
1676
+ const response = await fetcher(buildURL(options.baseURL, path), {
1677
+ method: "POST",
1678
+ headers: buildHeaders(options),
1679
+ body: JSON.stringify(body)
1680
+ });
1681
+ if (!response.ok) {
1682
+ const message = await response.text();
1683
+ throw new Error(`HTTP ${response.status}: ${message}`);
1684
+ }
1685
+ const json = await response.json();
1686
+ const data = json.data;
1687
+ if (!Array.isArray(data)) {
1688
+ throw new Error("Unexpected embedding response: missing data array");
1689
+ }
1690
+ return {
1691
+ embeddings: data.map((d) => isRecord2(d) && Array.isArray(d.embedding) ? d.embedding : []),
1692
+ model: pickString(json.model) ?? body.model,
1693
+ usage: pickUsage(json),
1694
+ raw: json
1701
1695
  };
1702
1696
  }
1703
1697
  async function completeOpenAIRequest(options, fetcher, chatPath, responsesPath, request) {
@@ -1714,22 +1708,80 @@ async function completeOpenAIRequest(options, fetcher, chatPath, responsesPath,
1714
1708
  }
1715
1709
  return completeWithChatCompletionsPassThrough(options, fetcher, chatPath, request);
1716
1710
  }
1717
- async function completeWithChatCompletionsPassThrough(options, fetcher, path, request) {
1718
- const response = await fetcher(buildURL(options.baseURL, path), {
1711
+ function buildChatCompletionsBody(options, request, overrides) {
1712
+ return buildOpenAIRequestBody(options, request, "max_tokens", overrides);
1713
+ }
1714
+ function buildResponsesBody(options, request, overrides) {
1715
+ return buildOpenAIRequestBody(options, request, "max_output_tokens", overrides);
1716
+ }
1717
+ function buildOpenAIRequestBody(options, request, maxTokenKey, overrides) {
1718
+ return cleanUndefined({
1719
+ ...options.defaultBody,
1720
+ ...request.body,
1721
+ model: options.model,
1722
+ temperature: request.temperature,
1723
+ reasoning_effort: toOpenAIReasoningEffort(request.reasoningEffort),
1724
+ [maxTokenKey]: request.maxTokens,
1725
+ ...overrides
1726
+ });
1727
+ }
1728
+ function sendOpenAIRequest(options, fetcher, path, request, body) {
1729
+ return fetcher(buildURL(options.baseURL, path), {
1719
1730
  method: "POST",
1720
1731
  headers: buildHeaders(options),
1721
- body: JSON.stringify(cleanUndefined({
1722
- ...options.defaultBody,
1723
- ...request.body,
1724
- model: options.model,
1725
- messages: buildMessages(request),
1726
- temperature: request.temperature,
1727
- reasoning_effort: toOpenAIReasoningEffort(request.reasoningEffort),
1728
- max_tokens: request.maxTokens,
1729
- stream: false
1730
- })),
1732
+ body: JSON.stringify(body),
1731
1733
  signal: request.signal
1732
1734
  });
1735
+ }
1736
+ async function sendOpenAIJsonRequest(options, fetcher, path, request, body) {
1737
+ const response = await sendOpenAIRequest(options, fetcher, path, request, body);
1738
+ if (!response.ok) {
1739
+ const message = await response.text();
1740
+ throw new Error(`HTTP ${response.status}: ${message}`);
1741
+ }
1742
+ return await response.json();
1743
+ }
1744
+ function createResponsesMCPState(request) {
1745
+ return {
1746
+ input: buildResponsesInput(request),
1747
+ previousResponseId: pickString(isRecord2(request.body) ? request.body.previous_response_id : undefined),
1748
+ aggregatedUsage: undefined,
1749
+ finishReason: undefined,
1750
+ lastPayload: undefined,
1751
+ executedToolCalls: [],
1752
+ toolExecutions: [],
1753
+ reasoningBlocks: []
1754
+ };
1755
+ }
1756
+ function buildResponsesMCPResult(state, text, raw) {
1757
+ return {
1758
+ text,
1759
+ reasoning: joinReasoningBlocks(state.reasoningBlocks) || undefined,
1760
+ reasoningBlocks: state.reasoningBlocks.length > 0 ? state.reasoningBlocks : undefined,
1761
+ raw,
1762
+ usage: state.aggregatedUsage,
1763
+ finishReason: state.finishReason,
1764
+ toolCalls: state.executedToolCalls.length > 0 ? state.executedToolCalls : undefined,
1765
+ toolExecutions: state.toolExecutions.length > 0 ? state.toolExecutions : undefined
1766
+ };
1767
+ }
1768
+ function emitOpenAIStreamChunk(callbacks, round, raw, delta, reasoningDelta, usage, finishReason) {
1769
+ if (delta || reasoningDelta || usage || finishReason) {
1770
+ callbacks.onChunk?.({
1771
+ textDelta: delta,
1772
+ reasoningDelta: reasoningDelta || undefined,
1773
+ ...round !== undefined ? { turnIndex: round } : {},
1774
+ raw,
1775
+ usage,
1776
+ finishReason
1777
+ });
1778
+ }
1779
+ }
1780
+ async function completeWithChatCompletionsPassThrough(options, fetcher, path, request) {
1781
+ const response = await sendOpenAIRequest(options, fetcher, path, request, buildChatCompletionsBody(options, request, {
1782
+ messages: buildMessages(request),
1783
+ stream: false
1784
+ }));
1733
1785
  if (!response.ok) {
1734
1786
  const message = await response.text();
1735
1787
  throw new Error(`HTTP ${response.status}: ${message}`);
@@ -1781,28 +1833,12 @@ async function completeWithChatCompletionsWithMCP(options, fetcher, path, reques
1781
1833
  for (let round = 1;round <= maxToolRounds + 1; round += 1) {
1782
1834
  const mcpToolset = await resolveMCPToolset(request.mcpClients);
1783
1835
  const transportTools = toProviderFunctionTools(mcpToolset);
1784
- const response = await fetcher(buildURL(options.baseURL, path), {
1785
- method: "POST",
1786
- headers: buildHeaders(options),
1787
- body: JSON.stringify(cleanUndefined({
1788
- ...options.defaultBody,
1789
- ...request.body,
1790
- model: options.model,
1791
- messages,
1792
- temperature: request.temperature,
1793
- reasoning_effort: toOpenAIReasoningEffort(request.reasoningEffort),
1794
- max_tokens: request.maxTokens,
1795
- tools: transportTools,
1796
- tool_choice: request.toolChoice,
1797
- parallel_tool_calls: request.parallelToolCalls
1798
- })),
1799
- signal: request.signal
1800
- });
1801
- if (!response.ok) {
1802
- const message = await response.text();
1803
- throw new Error(`HTTP ${response.status}: ${message}`);
1804
- }
1805
- const payload = await response.json();
1836
+ const payload = await sendOpenAIJsonRequest(options, fetcher, path, request, buildChatCompletionsBody(options, request, {
1837
+ messages,
1838
+ tools: transportTools,
1839
+ tool_choice: request.toolChoice,
1840
+ parallel_tool_calls: request.parallelToolCalls
1841
+ }));
1806
1842
  lastPayload = payload;
1807
1843
  aggregatedUsage = mergeUsage(aggregatedUsage, pickUsage(payload));
1808
1844
  finishReason = pickFinishReason(payload);
@@ -1857,26 +1893,10 @@ async function completeWithChatCompletionsWithMCP(options, fetcher, path, reques
1857
1893
  }
1858
1894
  async function completeWithResponsesAPIPassThrough(options, fetcher, path, request) {
1859
1895
  const body = isRecord2(request.body) ? request.body : undefined;
1860
- const response = await fetcher(buildURL(options.baseURL, path), {
1861
- method: "POST",
1862
- headers: buildHeaders(options),
1863
- body: JSON.stringify(cleanUndefined({
1864
- ...options.defaultBody,
1865
- ...request.body,
1866
- model: options.model,
1867
- input: buildResponsesInput(request),
1868
- previous_response_id: pickString(body?.previous_response_id),
1869
- temperature: request.temperature,
1870
- reasoning_effort: toOpenAIReasoningEffort(request.reasoningEffort),
1871
- max_output_tokens: request.maxTokens
1872
- })),
1873
- signal: request.signal
1874
- });
1875
- if (!response.ok) {
1876
- const message = await response.text();
1877
- throw new Error(`HTTP ${response.status}: ${message}`);
1878
- }
1879
- const payload = await response.json();
1896
+ const payload = await sendOpenAIJsonRequest(options, fetcher, path, request, buildResponsesBody(options, request, {
1897
+ input: buildResponsesInput(request),
1898
+ previous_response_id: pickString(body?.previous_response_id)
1899
+ }));
1880
1900
  const toolCalls = pickResponsesToolCalls(payload);
1881
1901
  return {
1882
1902
  text: pickResponsesText(payload) || pickAssistantText(payload),
@@ -1888,58 +1908,26 @@ async function completeWithResponsesAPIPassThrough(options, fetcher, path, reque
1888
1908
  }
1889
1909
  async function completeWithResponsesAPIWithMCP(options, fetcher, path, request) {
1890
1910
  const maxToolRounds = normalizeMaxToolRounds(request.maxToolRounds ?? options.defaultMaxToolRounds);
1891
- let input = buildResponsesInput(request);
1892
- let previousResponseId = pickString(isRecord2(request.body) ? request.body.previous_response_id : undefined);
1893
- let aggregatedUsage;
1894
- let finishReason;
1895
- let lastPayload;
1896
- const executedToolCalls = [];
1897
- const toolExecutions = [];
1898
- const reasoningBlocks = [];
1911
+ const state = createResponsesMCPState(request);
1899
1912
  for (let round = 1;round <= maxToolRounds + 1; round += 1) {
1900
1913
  const mcpToolset = await resolveMCPToolset(request.mcpClients);
1901
1914
  const transportTools = toResponsesTools(toProviderFunctionTools(mcpToolset));
1902
- const response = await fetcher(buildURL(options.baseURL, path), {
1903
- method: "POST",
1904
- headers: buildHeaders(options),
1905
- body: JSON.stringify(cleanUndefined({
1906
- ...options.defaultBody,
1907
- ...request.body,
1908
- model: options.model,
1909
- input,
1910
- previous_response_id: previousResponseId,
1911
- temperature: request.temperature,
1912
- reasoning_effort: toOpenAIReasoningEffort(request.reasoningEffort),
1913
- max_output_tokens: request.maxTokens,
1914
- tools: transportTools,
1915
- tool_choice: request.toolChoice,
1916
- parallel_tool_calls: request.parallelToolCalls
1917
- })),
1918
- signal: request.signal
1919
- });
1920
- if (!response.ok) {
1921
- const message = await response.text();
1922
- throw new Error(`HTTP ${response.status}: ${message}`);
1923
- }
1924
- const payload = await response.json();
1925
- lastPayload = payload;
1926
- aggregatedUsage = mergeUsage(aggregatedUsage, pickUsage(payload));
1927
- finishReason = pickResponsesFinishReason(payload) ?? finishReason;
1928
- pushReasoningBlock(reasoningBlocks, round, pickResponsesReasoning(payload));
1915
+ const payload = await sendOpenAIJsonRequest(options, fetcher, path, request, buildResponsesBody(options, request, {
1916
+ input: state.input,
1917
+ previous_response_id: state.previousResponseId,
1918
+ tools: transportTools,
1919
+ tool_choice: request.toolChoice,
1920
+ parallel_tool_calls: request.parallelToolCalls
1921
+ }));
1922
+ state.lastPayload = payload;
1923
+ state.aggregatedUsage = mergeUsage(state.aggregatedUsage, pickUsage(payload));
1924
+ state.finishReason = pickResponsesFinishReason(payload) ?? state.finishReason;
1925
+ pushReasoningBlock(state.reasoningBlocks, round, pickResponsesReasoning(payload));
1929
1926
  const providerToolCalls = pickResponsesToolCalls(payload);
1930
1927
  const functionCalls = providerToolCalls.filter((toolCall) => toolCall.type === "function" && typeof toolCall.id === "string" && typeof toolCall.name === "string");
1931
1928
  if (functionCalls.length === 0) {
1932
1929
  const text = pickResponsesText(payload) || pickAssistantText(payload);
1933
- return {
1934
- text,
1935
- reasoning: joinReasoningBlocks(reasoningBlocks) || undefined,
1936
- reasoningBlocks: reasoningBlocks.length > 0 ? reasoningBlocks : undefined,
1937
- raw: payload,
1938
- usage: aggregatedUsage,
1939
- finishReason,
1940
- toolCalls: executedToolCalls.length > 0 ? executedToolCalls : undefined,
1941
- toolExecutions: toolExecutions.length > 0 ? toolExecutions : undefined
1942
- };
1930
+ return buildResponsesMCPResult(state, text, payload);
1943
1931
  }
1944
1932
  if (round > maxToolRounds) {
1945
1933
  throw new Error(`Tool call loop exceeded maxToolRounds (${maxToolRounds}).`);
@@ -1950,25 +1938,16 @@ async function completeWithResponsesAPIWithMCP(options, fetcher, path, request)
1950
1938
  provider: "openai-compatible",
1951
1939
  model: options.model
1952
1940
  });
1953
- executedToolCalls.push(...outputs.map((entry) => entry.call));
1954
- toolExecutions.push(...outputs.map((entry) => entry.execution));
1955
- input = outputs.map((entry) => ({
1941
+ state.executedToolCalls.push(...outputs.map((entry) => entry.call));
1942
+ state.toolExecutions.push(...outputs.map((entry) => entry.execution));
1943
+ state.input = outputs.map((entry) => ({
1956
1944
  type: "function_call_output",
1957
1945
  call_id: entry.call.id,
1958
1946
  output: stringifyToolOutput(entry.call.error ? { error: entry.call.error } : entry.call.output)
1959
1947
  }));
1960
- previousResponseId = pickString(payload.id);
1948
+ state.previousResponseId = pickString(payload.id);
1961
1949
  }
1962
- return {
1963
- text: pickResponsesText(lastPayload ?? {}) || pickAssistantText(lastPayload ?? {}),
1964
- reasoning: joinReasoningBlocks(reasoningBlocks) || undefined,
1965
- reasoningBlocks: reasoningBlocks.length > 0 ? reasoningBlocks : undefined,
1966
- raw: lastPayload,
1967
- usage: aggregatedUsage,
1968
- finishReason,
1969
- toolCalls: executedToolCalls.length > 0 ? executedToolCalls : undefined,
1970
- toolExecutions: toolExecutions.length > 0 ? toolExecutions : undefined
1971
- };
1950
+ return buildResponsesMCPResult(state, pickResponsesText(state.lastPayload ?? {}) || pickAssistantText(state.lastPayload ?? {}), state.lastPayload);
1972
1951
  }
1973
1952
  async function streamWithChatCompletionsWithMCP(options, fetcher, path, request, callbacks) {
1974
1953
  const maxToolRounds = normalizeMaxToolRounds(request.maxToolRounds ?? options.defaultMaxToolRounds);
@@ -1984,24 +1963,13 @@ async function streamWithChatCompletionsWithMCP(options, fetcher, path, request,
1984
1963
  for (let round = 1;round <= maxToolRounds + 1; round += 1) {
1985
1964
  const mcpToolset = await resolveMCPToolset(request.mcpClients);
1986
1965
  const transportTools = toProviderFunctionTools(mcpToolset);
1987
- const response = await fetcher(buildURL(options.baseURL, path), {
1988
- method: "POST",
1989
- headers: buildHeaders(options),
1990
- body: JSON.stringify(cleanUndefined({
1991
- ...options.defaultBody,
1992
- ...request.body,
1993
- model: options.model,
1994
- messages,
1995
- temperature: request.temperature,
1996
- reasoning_effort: toOpenAIReasoningEffort(request.reasoningEffort),
1997
- max_tokens: request.maxTokens,
1998
- tools: transportTools,
1999
- tool_choice: request.toolChoice,
2000
- parallel_tool_calls: request.parallelToolCalls,
2001
- stream: true
2002
- })),
2003
- signal: request.signal
2004
- });
1966
+ const response = await sendOpenAIRequest(options, fetcher, path, request, buildChatCompletionsBody(options, request, {
1967
+ messages,
1968
+ tools: transportTools,
1969
+ tool_choice: request.toolChoice,
1970
+ parallel_tool_calls: request.parallelToolCalls,
1971
+ stream: true
1972
+ }));
2005
1973
  if (!response.ok) {
2006
1974
  const message = await response.text();
2007
1975
  throw new Error(`HTTP ${response.status}: ${message}`);
@@ -2038,17 +2006,7 @@ async function streamWithChatCompletionsWithMCP(options, fetcher, path, request,
2038
2006
  roundReasoning += reasoningDelta;
2039
2007
  reasoningFieldName ??= pickAssistantReasoningDeltaFieldName(json);
2040
2008
  }
2041
- if (delta || reasoningDelta || chunkUsage || chunkFinishReason) {
2042
- const chunk = {
2043
- textDelta: delta,
2044
- reasoningDelta: reasoningDelta || undefined,
2045
- turnIndex: round,
2046
- raw: json,
2047
- usage: chunkUsage,
2048
- finishReason: chunkFinishReason
2049
- };
2050
- callbacks.onChunk?.(chunk);
2051
- }
2009
+ emitOpenAIStreamChunk(callbacks, round, json, delta, reasoningDelta, chunkUsage, chunkFinishReason);
2052
2010
  });
2053
2011
  aggregatedUsage = mergeUsage(aggregatedUsage, roundUsage);
2054
2012
  if (roundFinishReason) {
@@ -2127,22 +2085,11 @@ async function streamWithChatCompletionsWithMCP(options, fetcher, path, request,
2127
2085
  }
2128
2086
  async function streamWithResponsesAPIPassThrough(options, fetcher, path, request, callbacks) {
2129
2087
  const body = isRecord2(request.body) ? request.body : undefined;
2130
- const response = await fetcher(buildURL(options.baseURL, path), {
2131
- method: "POST",
2132
- headers: buildHeaders(options),
2133
- body: JSON.stringify(cleanUndefined({
2134
- ...options.defaultBody,
2135
- ...request.body,
2136
- model: options.model,
2137
- input: buildResponsesInput(request),
2138
- previous_response_id: pickString(body?.previous_response_id),
2139
- temperature: request.temperature,
2140
- reasoning_effort: toOpenAIReasoningEffort(request.reasoningEffort),
2141
- max_output_tokens: request.maxTokens,
2142
- stream: true
2143
- })),
2144
- signal: request.signal
2145
- });
2088
+ const response = await sendOpenAIRequest(options, fetcher, path, request, buildResponsesBody(options, request, {
2089
+ input: buildResponsesInput(request),
2090
+ previous_response_id: pickString(body?.previous_response_id),
2091
+ stream: true
2092
+ }));
2146
2093
  if (!response.ok) {
2147
2094
  const message = await response.text();
2148
2095
  throw new Error(`HTTP ${response.status}: ${message}`);
@@ -2175,15 +2122,7 @@ async function streamWithResponsesAPIPassThrough(options, fetcher, path, request
2175
2122
  text += delta;
2176
2123
  callbacks.onToken?.(delta);
2177
2124
  }
2178
- if (delta || chunkUsage || chunkFinishReason) {
2179
- const chunk = {
2180
- textDelta: delta,
2181
- raw: json,
2182
- usage: chunkUsage,
2183
- finishReason: chunkFinishReason
2184
- };
2185
- callbacks.onChunk?.(chunk);
2186
- }
2125
+ emitOpenAIStreamChunk(callbacks, undefined, json, delta, "", chunkUsage, chunkFinishReason);
2187
2126
  });
2188
2127
  const finalPayload = lastPayload ?? {};
2189
2128
  const out = {
@@ -2197,37 +2136,19 @@ async function streamWithResponsesAPIPassThrough(options, fetcher, path, request
2197
2136
  }
2198
2137
  async function streamWithResponsesAPIWithMCP(options, fetcher, path, request, callbacks) {
2199
2138
  const maxToolRounds = normalizeMaxToolRounds(request.maxToolRounds ?? options.defaultMaxToolRounds);
2200
- let input = buildResponsesInput(request);
2201
- let previousResponseId = pickString(isRecord2(request.body) ? request.body.previous_response_id : undefined);
2202
- let aggregatedUsage;
2203
- let finishReason;
2204
- let lastPayload;
2205
- const executedToolCalls = [];
2206
- const toolExecutions = [];
2207
- const reasoningBlocks = [];
2139
+ const state = createResponsesMCPState(request);
2208
2140
  callbacks.onStart?.();
2209
2141
  for (let round = 1;round <= maxToolRounds + 1; round += 1) {
2210
2142
  const mcpToolset = await resolveMCPToolset(request.mcpClients);
2211
2143
  const transportTools = toResponsesTools(toProviderFunctionTools(mcpToolset));
2212
- const response = await fetcher(buildURL(options.baseURL, path), {
2213
- method: "POST",
2214
- headers: buildHeaders(options),
2215
- body: JSON.stringify(cleanUndefined({
2216
- ...options.defaultBody,
2217
- ...request.body,
2218
- model: options.model,
2219
- input,
2220
- previous_response_id: previousResponseId,
2221
- temperature: request.temperature,
2222
- reasoning_effort: toOpenAIReasoningEffort(request.reasoningEffort),
2223
- max_output_tokens: request.maxTokens,
2224
- tools: transportTools,
2225
- tool_choice: request.toolChoice,
2226
- parallel_tool_calls: request.parallelToolCalls,
2227
- stream: true
2228
- })),
2229
- signal: request.signal
2230
- });
2144
+ const response = await sendOpenAIRequest(options, fetcher, path, request, buildResponsesBody(options, request, {
2145
+ input: state.input,
2146
+ previous_response_id: state.previousResponseId,
2147
+ tools: transportTools,
2148
+ tool_choice: request.toolChoice,
2149
+ parallel_tool_calls: request.parallelToolCalls,
2150
+ stream: true
2151
+ }));
2231
2152
  if (!response.ok) {
2232
2153
  const message = await response.text();
2233
2154
  throw new Error(`HTTP ${response.status}: ${message}`);
@@ -2249,7 +2170,7 @@ async function streamWithResponsesAPIWithMCP(options, fetcher, path, request, ca
2249
2170
  const payload = pickResponsesStreamPayload(json);
2250
2171
  if (payload) {
2251
2172
  roundPayload = payload;
2252
- lastPayload = payload;
2173
+ state.lastPayload = payload;
2253
2174
  }
2254
2175
  const delta = pickResponsesStreamTextDelta(json);
2255
2176
  const reasoningDelta = pickResponsesStreamReasoningDelta(json);
@@ -2267,24 +2188,14 @@ async function streamWithResponsesAPIWithMCP(options, fetcher, path, request, ca
2267
2188
  if (reasoningDelta) {
2268
2189
  roundReasoning += reasoningDelta;
2269
2190
  }
2270
- if (delta || reasoningDelta || chunkUsage || chunkFinishReason) {
2271
- const chunk = {
2272
- textDelta: delta,
2273
- reasoningDelta: reasoningDelta || undefined,
2274
- turnIndex: round,
2275
- raw: json,
2276
- usage: chunkUsage,
2277
- finishReason: chunkFinishReason
2278
- };
2279
- callbacks.onChunk?.(chunk);
2280
- }
2191
+ emitOpenAIStreamChunk(callbacks, round, json, delta, reasoningDelta, chunkUsage, chunkFinishReason);
2281
2192
  });
2282
2193
  const resolvedRoundUsage = preferLatestUsage(roundUsage, roundPayload ? pickUsage(roundPayload) : undefined);
2283
- aggregatedUsage = mergeUsage(aggregatedUsage, resolvedRoundUsage);
2194
+ state.aggregatedUsage = mergeUsage(state.aggregatedUsage, resolvedRoundUsage);
2284
2195
  if (roundFinishReason) {
2285
- finishReason = roundFinishReason;
2196
+ state.finishReason = roundFinishReason;
2286
2197
  } else if (roundPayload) {
2287
- finishReason = pickResponsesFinishReason(roundPayload) ?? finishReason;
2198
+ state.finishReason = pickResponsesFinishReason(roundPayload) ?? state.finishReason;
2288
2199
  }
2289
2200
  const payloadToolCalls = roundPayload ? pickResponsesToolCalls(roundPayload) : [];
2290
2201
  if (roundPayload && roundReasoning.length === 0) {
@@ -2293,7 +2204,7 @@ async function streamWithResponsesAPIWithMCP(options, fetcher, path, request, ca
2293
2204
  const streamedCalls = buildResponsesStreamToolCalls(streamedToolCalls);
2294
2205
  const providerToolCalls = payloadToolCalls.length > 0 ? payloadToolCalls : streamedCalls;
2295
2206
  const functionCalls = providerToolCalls.filter((toolCall) => toolCall.type === "function" && typeof toolCall.id === "string" && typeof toolCall.name === "string");
2296
- pushReasoningBlock(reasoningBlocks, round, roundReasoning);
2207
+ pushReasoningBlock(state.reasoningBlocks, round, roundReasoning);
2297
2208
  request.onTurnTransition?.({
2298
2209
  turnIndex: round,
2299
2210
  kind: "reasoningComplete",
@@ -2301,16 +2212,7 @@ async function streamWithResponsesAPIWithMCP(options, fetcher, path, request, ca
2301
2212
  });
2302
2213
  if (functionCalls.length === 0) {
2303
2214
  const finalText = roundText.length > 0 ? roundText : roundPayload ? pickResponsesText(roundPayload) || pickAssistantText(roundPayload) : "";
2304
- const out2 = {
2305
- text: finalText,
2306
- reasoning: joinReasoningBlocks(reasoningBlocks) || undefined,
2307
- reasoningBlocks: reasoningBlocks.length > 0 ? reasoningBlocks : undefined,
2308
- raw: roundPayload ?? lastPayload,
2309
- usage: aggregatedUsage,
2310
- finishReason,
2311
- toolCalls: executedToolCalls.length > 0 ? executedToolCalls : undefined,
2312
- toolExecutions: toolExecutions.length > 0 ? toolExecutions : undefined
2313
- };
2215
+ const out2 = buildResponsesMCPResult(state, finalText, roundPayload ?? state.lastPayload);
2314
2216
  request.onTurnTransition?.({ turnIndex: round, kind: "streamEnd" });
2315
2217
  callbacks.onComplete?.(out2);
2316
2218
  return out2;
@@ -2335,26 +2237,17 @@ async function streamWithResponsesAPIWithMCP(options, fetcher, path, request, ca
2335
2237
  provider: "openai-compatible",
2336
2238
  model: options.model
2337
2239
  });
2338
- executedToolCalls.push(...outputs.map((entry) => entry.call));
2339
- toolExecutions.push(...outputs.map((entry) => entry.execution));
2240
+ state.executedToolCalls.push(...outputs.map((entry) => entry.call));
2241
+ state.toolExecutions.push(...outputs.map((entry) => entry.execution));
2340
2242
  request.onTurnTransition?.({ turnIndex: round, kind: "toolResultsReceived" });
2341
- input = outputs.map((entry) => ({
2243
+ state.input = outputs.map((entry) => ({
2342
2244
  type: "function_call_output",
2343
2245
  call_id: entry.call.id,
2344
2246
  output: stringifyToolOutput(entry.call.error ? { error: entry.call.error } : entry.call.output)
2345
2247
  }));
2346
- previousResponseId = pickString(roundPayload?.id);
2248
+ state.previousResponseId = pickString(roundPayload?.id);
2347
2249
  }
2348
- const out = {
2349
- text: pickResponsesText(lastPayload ?? {}) || pickAssistantText(lastPayload ?? {}),
2350
- reasoning: joinReasoningBlocks(reasoningBlocks) || undefined,
2351
- reasoningBlocks: reasoningBlocks.length > 0 ? reasoningBlocks : undefined,
2352
- raw: lastPayload,
2353
- usage: aggregatedUsage,
2354
- finishReason,
2355
- toolCalls: executedToolCalls.length > 0 ? executedToolCalls : undefined,
2356
- toolExecutions: toolExecutions.length > 0 ? toolExecutions : undefined
2357
- };
2250
+ const out = buildResponsesMCPResult(state, pickResponsesText(state.lastPayload ?? {}) || pickAssistantText(state.lastPayload ?? {}), state.lastPayload);
2358
2251
  request.onTurnTransition?.({ turnIndex: maxToolRounds + 1, kind: "streamEnd" });
2359
2252
  callbacks.onComplete?.(out);
2360
2253
  return out;
@@ -2507,7 +2400,7 @@ function pickAssistantDelta(payload) {
2507
2400
  if (!isRecord2(delta)) {
2508
2401
  return "";
2509
2402
  }
2510
- return pickTextFromOpenAIContent(delta.content);
2403
+ return pickTextLike(delta.content);
2511
2404
  }
2512
2405
  function pickAssistantReasoning(payload) {
2513
2406
  const message = pickAssistantMessage(payload);
@@ -2815,7 +2708,7 @@ function pickResponsesReasoning(payload) {
2815
2708
  function pickAssistantText(payload) {
2816
2709
  const message = pickAssistantMessage(payload);
2817
2710
  if (message) {
2818
- const text = pickTextFromOpenAIContent(message.content);
2711
+ const text = pickTextLike(message.content);
2819
2712
  if (text.length > 0) {
2820
2713
  return text;
2821
2714
  }
@@ -2844,9 +2737,6 @@ function joinReasoningBlocks(blocks) {
2844
2737
 
2845
2738
  `);
2846
2739
  }
2847
- function pickTextFromOpenAIContent(value) {
2848
- return pickTextLike(value);
2849
- }
2850
2740
  function pickTextLike(value) {
2851
2741
  if (typeof value === "string") {
2852
2742
  return value;
@@ -2920,83 +2810,81 @@ function createAnthropicCompatibleAdapter(options) {
2920
2810
  if (hasMCPClients(request.mcpClients)) {
2921
2811
  return streamWithMCPToolLoop(options, fetcher, path, request, callbacks);
2922
2812
  }
2923
- const input = resolveAnthropicInput(request);
2924
- const response = await fetcher(buildURL(options.baseURL, path), {
2925
- method: "POST",
2926
- headers: buildHeaders2(options),
2927
- body: JSON.stringify(buildAnthropicRequestBody(options, request, {
2928
- ...options.defaultBody,
2929
- ...request.body,
2930
- model: options.model,
2931
- system: input.systemPrompt,
2932
- messages: input.messages,
2933
- temperature: request.temperature,
2934
- stream: true
2935
- })),
2936
- signal: request.signal
2937
- });
2938
- if (!response.ok) {
2939
- const message = await response.text();
2940
- throw new Error(`HTTP ${response.status}: ${message}`);
2941
- }
2942
- callbacks.onStart?.();
2943
- let text = "";
2944
- let usage;
2945
- let finishReason;
2946
- await consumeSSE(response, (data) => {
2947
- if (data === "[DONE]") {
2948
- return;
2949
- }
2950
- const json = safeJSONParse(data);
2951
- if (!isRecord2(json)) {
2952
- return;
2953
- }
2954
- const delta = pickAnthropicDelta(json);
2955
- const chunkUsage = pickUsage2(json);
2956
- const chunkFinishReason = pickFinishReason2(json);
2957
- usage = preferLatestUsage(usage, chunkUsage);
2958
- if (chunkFinishReason) {
2959
- finishReason = chunkFinishReason;
2960
- }
2961
- if (delta) {
2962
- text += delta;
2963
- callbacks.onToken?.(delta);
2964
- }
2965
- if (delta || chunkUsage || chunkFinishReason) {
2966
- const chunk = {
2967
- textDelta: delta,
2968
- raw: json,
2969
- usage: chunkUsage,
2970
- finishReason: chunkFinishReason
2971
- };
2972
- callbacks.onChunk?.(chunk);
2973
- }
2974
- });
2975
- const out = { text, usage, finishReason };
2976
- callbacks.onComplete?.(out);
2977
- return out;
2813
+ return streamPassThrough(options, fetcher, path, request, callbacks);
2978
2814
  },
2979
2815
  async embed() {
2980
2816
  throw new Error("Anthropic does not provide a native embedding API. " + "Use the openai-compatible provider with Voyage AI (https://api.voyageai.com) — " + "Anthropic's recommended embedding solution, which uses the same request format.");
2981
2817
  }
2982
2818
  };
2983
2819
  }
2984
- async function completePassThrough(options, fetcher, path, request) {
2820
+ async function streamPassThrough(options, fetcher, path, request, callbacks) {
2985
2821
  const input = resolveAnthropicInput(request);
2986
- const response = await fetcher(buildURL(options.baseURL, path), {
2822
+ const response = await sendAnthropicMessage(options, fetcher, path, request, {
2823
+ system: input.systemPrompt,
2824
+ messages: input.messages,
2825
+ stream: true
2826
+ });
2827
+ if (!response.ok) {
2828
+ const message = await response.text();
2829
+ throw new Error(`HTTP ${response.status}: ${message}`);
2830
+ }
2831
+ callbacks.onStart?.();
2832
+ let text = "";
2833
+ let usage;
2834
+ let finishReason;
2835
+ await consumeSSE(response, (data) => {
2836
+ if (data === "[DONE]") {
2837
+ return;
2838
+ }
2839
+ const json = safeJSONParse(data);
2840
+ if (!isRecord2(json)) {
2841
+ return;
2842
+ }
2843
+ const delta = pickAnthropicDelta(json);
2844
+ const chunkUsage = pickUsage2(json);
2845
+ const chunkFinishReason = pickFinishReason2(json);
2846
+ usage = preferLatestUsage(usage, chunkUsage);
2847
+ if (chunkFinishReason) {
2848
+ finishReason = chunkFinishReason;
2849
+ }
2850
+ if (delta) {
2851
+ text += delta;
2852
+ callbacks.onToken?.(delta);
2853
+ }
2854
+ if (delta || chunkUsage || chunkFinishReason) {
2855
+ callbacks.onChunk?.({
2856
+ textDelta: delta,
2857
+ raw: json,
2858
+ usage: chunkUsage,
2859
+ finishReason: chunkFinishReason
2860
+ });
2861
+ }
2862
+ });
2863
+ const out = { text, usage, finishReason };
2864
+ callbacks.onComplete?.(out);
2865
+ return out;
2866
+ }
2867
+ function sendAnthropicMessage(options, fetcher, path, request, body) {
2868
+ return fetcher(buildURL(options.baseURL, path), {
2987
2869
  method: "POST",
2988
2870
  headers: buildHeaders2(options),
2989
2871
  body: JSON.stringify(buildAnthropicRequestBody(options, request, {
2990
2872
  ...options.defaultBody,
2991
2873
  ...request.body,
2992
2874
  model: options.model,
2993
- system: input.systemPrompt,
2994
- messages: input.messages,
2995
2875
  temperature: request.temperature,
2996
- stream: false
2876
+ ...body
2997
2877
  })),
2998
2878
  signal: request.signal
2999
2879
  });
2880
+ }
2881
+ async function completePassThrough(options, fetcher, path, request) {
2882
+ const input = resolveAnthropicInput(request);
2883
+ const response = await sendAnthropicMessage(options, fetcher, path, request, {
2884
+ system: input.systemPrompt,
2885
+ messages: input.messages,
2886
+ stream: false
2887
+ });
3000
2888
  if (!response.ok) {
3001
2889
  const message = await response.text();
3002
2890
  throw new Error(`HTTP ${response.status}: ${message}`);
@@ -3028,21 +2916,12 @@ async function completeWithMCPToolLoop(options, fetcher, path, request) {
3028
2916
  for (let round = 1;round <= maxToolRounds + 1; round += 1) {
3029
2917
  const mcpToolset = await resolveMCPToolset(request.mcpClients);
3030
2918
  const tools = toAnthropicTools(toProviderFunctionTools(mcpToolset));
3031
- const response = await fetcher(buildURL(options.baseURL, path), {
3032
- method: "POST",
3033
- headers: buildHeaders2(options),
3034
- body: JSON.stringify(buildAnthropicRequestBody(options, request, {
3035
- ...options.defaultBody,
3036
- ...request.body,
3037
- model: options.model,
3038
- system: input.systemPrompt,
3039
- messages,
3040
- temperature: request.temperature,
3041
- tools,
3042
- tool_choice: toAnthropicToolChoice(request.toolChoice),
3043
- stream: false
3044
- })),
3045
- signal: request.signal
2919
+ const response = await sendAnthropicMessage(options, fetcher, path, request, {
2920
+ system: input.systemPrompt,
2921
+ messages,
2922
+ tools,
2923
+ tool_choice: toAnthropicToolChoice(request.toolChoice),
2924
+ stream: false
3046
2925
  });
3047
2926
  if (!response.ok) {
3048
2927
  const message = await response.text();
@@ -3118,21 +2997,12 @@ async function streamWithMCPToolLoop(options, fetcher, path, request, callbacks)
3118
2997
  for (let round = 1;round <= maxToolRounds + 1; round += 1) {
3119
2998
  const mcpToolset = await resolveMCPToolset(request.mcpClients);
3120
2999
  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: true
3134
- })),
3135
- signal: request.signal
3000
+ const response = await sendAnthropicMessage(options, fetcher, path, request, {
3001
+ system: input.systemPrompt,
3002
+ messages,
3003
+ tools,
3004
+ tool_choice: toAnthropicToolChoice(request.toolChoice),
3005
+ stream: true
3136
3006
  });
3137
3007
  if (!response.ok) {
3138
3008
  const message = await response.text();
@@ -3676,39 +3546,10 @@ function buildProviderOptions(config) {
3676
3546
  return {
3677
3547
  model: config.model,
3678
3548
  ...transport,
3679
- ...config.options ?? {}
3549
+ ...config.options
3680
3550
  };
3681
3551
  }
3682
3552
 
3683
- // src/utils/debug-colors.ts
3684
- var ANSI = {
3685
- reset: "\x1B[0m",
3686
- bold: "\x1B[1m",
3687
- cyan: "\x1B[36m",
3688
- yellow: "\x1B[33m",
3689
- green: "\x1B[32m",
3690
- red: "\x1B[31m",
3691
- dim: "\x1B[2m"
3692
- };
3693
- function color(config, text, tone) {
3694
- if (!config.colors) {
3695
- return text;
3696
- }
3697
- return `${ANSI[tone]}${text}${ANSI.reset}`;
3698
- }
3699
- function dim(config, text) {
3700
- if (!config.colors) {
3701
- return text;
3702
- }
3703
- return `${ANSI.dim}${text}${ANSI.reset}`;
3704
- }
3705
- function title(config, text) {
3706
- if (!config.colors) {
3707
- return text;
3708
- }
3709
- return `${ANSI.bold}${text}${ANSI.reset}`;
3710
- }
3711
-
3712
3553
  // src/outdent.ts
3713
3554
  var DEFAULT_OPTIONS = {
3714
3555
  trimLeadingNewline: true,
@@ -3846,132 +3687,213 @@ function createOutdent(options = {}) {
3846
3687
  return outdent;
3847
3688
  }
3848
3689
 
3849
- // src/generate-shared.ts
3850
- var sharedOutdent = createOutdent({
3851
- trimLeadingNewline: true,
3852
- trimTrailingNewline: true,
3853
- newline: `
3854
- `
3855
- });
3690
+ // src/generate-tool-timeout.ts
3691
+ function withToolTimeout(client, toolTimeoutMs) {
3692
+ return {
3693
+ id: client.id,
3694
+ listTools: client.listTools.bind(client),
3695
+ close: client.close?.bind(client),
3696
+ async callTool(params) {
3697
+ let timeoutId;
3698
+ const timeoutPromise = new Promise((_, reject) => {
3699
+ timeoutId = setTimeout(() => reject(new Error(`Tool call timed out after ${toolTimeoutMs}ms`)), toolTimeoutMs);
3700
+ });
3701
+ try {
3702
+ return await Promise.race([client.callTool(params), timeoutPromise]);
3703
+ } finally {
3704
+ clearTimeout(timeoutId);
3705
+ }
3706
+ }
3707
+ };
3708
+ }
3709
+ function applyToolTimeout(clients, toolTimeoutMs) {
3710
+ return clients.map((client) => withToolTimeout(client, toolTimeoutMs));
3711
+ }
3712
+ // src/generate-output.ts
3856
3713
  var RE_THINK_TAGS = /<\/?think\s*>/gi;
3857
- function resolvePrompt(prompt, context) {
3858
- const resolved = typeof prompt === "function" ? prompt(context) : prompt;
3859
- return normalizePromptValue(resolved, context);
3714
+ function normalizeModelOutput(text, dedicatedReasoning, reasoningBlocks) {
3715
+ const sanitized = sanitizeThink(text);
3716
+ const visibleText = stripThinkBlocks(text, sanitized.thinkBlocks);
3717
+ const reasoning = joinReasoningSegments([
3718
+ dedicatedReasoning,
3719
+ ...sanitized.thinkBlocks.map((block) => block.content)
3720
+ ]);
3721
+ return {
3722
+ text: visibleText,
3723
+ reasoning,
3724
+ reasoningBlocks: normalizeReasoningBlocks(reasoningBlocks),
3725
+ thinkBlocks: sanitized.thinkBlocks,
3726
+ parseSource: composeParseSource(visibleText, reasoning)
3727
+ };
3860
3728
  }
3861
- function normalizePromptValue(value, _context) {
3862
- if (typeof value === "string") {
3863
- return {
3864
- prompt: value
3865
- };
3866
- }
3867
- if (isPromptResolver(value)) {
3868
- return normalizePromptPayload(value.resolvePrompt(_context));
3729
+ function normalizeReasoningBlocks(blocks) {
3730
+ if (!Array.isArray(blocks)) {
3731
+ return;
3869
3732
  }
3870
- return normalizePromptPayload(value);
3733
+ const normalized = blocks.map((block) => ({
3734
+ turnIndex: block.turnIndex,
3735
+ text: block.text.replace(RE_THINK_TAGS, "").trim()
3736
+ })).filter((block) => Number.isFinite(block.turnIndex) && block.text.length > 0);
3737
+ return normalized.length > 0 ? normalized : undefined;
3871
3738
  }
3872
- function normalizePromptPayload(value) {
3873
- const prompt = typeof value.prompt === "string" ? value.prompt : undefined;
3874
- const messages = Array.isArray(value.messages) ? value.messages.filter(isLLMMessage) : undefined;
3875
- if ((!prompt || prompt.trim().length === 0) && (!messages || messages.length === 0)) {
3876
- throw new Error("Structured prompt payload must include a non-empty prompt or messages.");
3739
+ function appendReasoningBlock(blocks, transition) {
3740
+ const text = transition.reasoningText?.replace(RE_THINK_TAGS, "").trim();
3741
+ if (!text) {
3742
+ return blocks;
3877
3743
  }
3878
- return {
3879
- prompt,
3880
- systemPrompt: typeof value.systemPrompt === "string" ? value.systemPrompt : undefined,
3881
- messages: messages && messages.length > 0 ? messages.map((message) => ({ ...message })) : undefined
3882
- };
3744
+ const next = [...blocks ?? [], { turnIndex: transition.turnIndex, text }];
3745
+ return normalizeReasoningBlocks(next);
3883
3746
  }
3884
- function applyPromptOutdent(payload, enabled) {
3885
- if (!enabled) {
3886
- return payload;
3747
+ function composeParseSource(text, reasoning) {
3748
+ if (typeof reasoning !== "string" || reasoning.length === 0) {
3749
+ return text;
3887
3750
  }
3888
- return {
3889
- prompt: typeof payload.prompt === "string" ? sharedOutdent.string(payload.prompt) : undefined,
3890
- systemPrompt: applyOutdentToOptionalPrompt(payload.systemPrompt, enabled),
3891
- messages: payload.messages?.map((message) => ({
3892
- ...message,
3893
- content: typeof message.content === "string" ? sharedOutdent.string(message.content) : message.content
3894
- }))
3895
- };
3751
+ const sanitized = reasoning.replace(RE_THINK_TAGS, "");
3752
+ if (sanitized.length === 0) {
3753
+ return text;
3754
+ }
3755
+ return `<think>${sanitized}</think>${text}`;
3896
3756
  }
3897
- function applyOutdentToOptionalPrompt(value, enabled) {
3898
- if (!enabled || typeof value !== "string") {
3899
- return value;
3757
+ function aggregateUsage(attempts) {
3758
+ let usage;
3759
+ for (const attempt of attempts) {
3760
+ usage = mergeUsage2(usage, attempt.usage);
3900
3761
  }
3901
- return sharedOutdent.string(value);
3762
+ return usage;
3902
3763
  }
3903
- function mergeSystemPrompts(primary, secondary) {
3904
- const prompts = [primary, secondary].map((value) => value?.trim()).filter((value) => Boolean(value));
3905
- if (prompts.length === 0) {
3764
+ function mergeUsage2(base, next) {
3765
+ if (!base && !next) {
3906
3766
  return;
3907
3767
  }
3908
- return prompts.join(`
3768
+ return {
3769
+ inputTokens: (base?.inputTokens ?? 0) + (next?.inputTokens ?? 0),
3770
+ outputTokens: (base?.outputTokens ?? 0) + (next?.outputTokens ?? 0),
3771
+ totalTokens: (base?.totalTokens ?? 0) + (next?.totalTokens ?? 0),
3772
+ cost: (base?.cost ?? 0) + (next?.cost ?? 0)
3773
+ };
3774
+ }
3775
+ function joinReasoningSegments(parts) {
3776
+ return parts.map((value) => value?.trim()).filter((value) => Boolean(value)).join(`
3909
3777
 
3910
3778
  `);
3911
3779
  }
3912
- function normalizeStreamConfig(option) {
3913
- if (typeof option === "boolean") {
3914
- return {
3915
- enabled: option
3916
- };
3780
+ function stripThinkBlocks(text, thinkBlocks) {
3781
+ if (thinkBlocks.length === 0) {
3782
+ return text;
3917
3783
  }
3918
- if (!option) {
3919
- return {
3920
- enabled: false
3921
- };
3784
+ let output = "";
3785
+ let cursor = 0;
3786
+ for (const block of thinkBlocks) {
3787
+ output += text.slice(cursor, block.start);
3788
+ cursor = block.end;
3922
3789
  }
3923
- return {
3924
- enabled: option.enabled ?? true,
3925
- onData: option.onData,
3926
- onTurnTransition: option.onTurnTransition,
3927
- to: option.to
3928
- };
3790
+ output += text.slice(cursor);
3791
+ return output;
3929
3792
  }
3930
- function normalizeDebugConfig(option) {
3931
- if (typeof option === "boolean") {
3932
- return {
3933
- enabled: option,
3934
- colors: true,
3935
- verbose: false,
3936
- logger: (line) => console.log(line)
3937
- };
3793
+ function toStreamDataFingerprint(value) {
3794
+ try {
3795
+ return JSON.stringify(value);
3796
+ } catch {
3797
+ return "__unserializable__";
3938
3798
  }
3939
- if (!option) {
3940
- return {
3941
- enabled: false,
3942
- colors: true,
3943
- verbose: false,
3944
- logger: (line) => console.log(line)
3945
- };
3799
+ }
3800
+
3801
+ // src/utils/debug-colors.ts
3802
+ var ANSI = {
3803
+ reset: "\x1B[0m",
3804
+ bold: "\x1B[1m",
3805
+ cyan: "\x1B[36m",
3806
+ yellow: "\x1B[33m",
3807
+ green: "\x1B[32m",
3808
+ red: "\x1B[31m",
3809
+ dim: "\x1B[2m"
3810
+ };
3811
+ function color(config, text, tone) {
3812
+ if (!config.colors) {
3813
+ return text;
3946
3814
  }
3947
- return {
3948
- enabled: option.enabled ?? true,
3949
- colors: option.colors ?? true,
3950
- verbose: option.verbose ?? false,
3951
- logger: option.logger ?? ((line) => console.log(line))
3952
- };
3815
+ return `${ANSI[tone]}${text}${ANSI.reset}`;
3953
3816
  }
3954
- function withToolTimeout(client, toolTimeoutMs) {
3955
- return {
3956
- id: client.id,
3957
- listTools: client.listTools.bind(client),
3958
- close: client.close?.bind(client),
3959
- async callTool(params) {
3960
- let timeoutId;
3961
- const timeoutPromise = new Promise((_, reject) => {
3962
- timeoutId = setTimeout(() => reject(new Error(`Tool call timed out after ${toolTimeoutMs}ms`)), toolTimeoutMs);
3963
- });
3964
- try {
3965
- return await Promise.race([client.callTool(params), timeoutPromise]);
3966
- } finally {
3967
- clearTimeout(timeoutId);
3968
- }
3969
- }
3970
- };
3817
+ function dim(config, text) {
3818
+ if (!config.colors) {
3819
+ return text;
3820
+ }
3821
+ return `${ANSI.dim}${text}${ANSI.reset}`;
3971
3822
  }
3972
- function applyToolTimeout(clients, toolTimeoutMs) {
3973
- return clients.map((client) => withToolTimeout(client, toolTimeoutMs));
3823
+ function title(config, text) {
3824
+ if (!config.colors) {
3825
+ return text;
3826
+ }
3827
+ return `${ANSI.bold}${text}${ANSI.reset}`;
3974
3828
  }
3829
+
3830
+ // src/generate-debug.ts
3831
+ function emitDebugRequest(config, input) {
3832
+ const requestBody = input.requestPayload.body !== undefined ? JSON.stringify(input.requestPayload.body, null, 2) : "(none)";
3833
+ const requestMessages = input.requestPayload.messages !== undefined ? JSON.stringify(input.requestPayload.messages, null, 2) : "(none)";
3834
+ const lines = [
3835
+ color(config, title(config, [
3836
+ `[${input.label}][request]`,
3837
+ `attempt=${input.attempt}`,
3838
+ `selfHealEnabled=${input.selfHealEnabled}`,
3839
+ `selfHealAttempt=${input.selfHealAttempt}`
3840
+ ].join(" ")), "cyan"),
3841
+ dim(config, [
3842
+ `provider=${input.provider ?? "unknown"}`,
3843
+ `model=${input.model ?? "unknown"}`,
3844
+ `stream=${input.stream}`
3845
+ ].join(" ")),
3846
+ color(config, "prompt:", "yellow"),
3847
+ input.requestPayload.prompt ?? "(none)",
3848
+ color(config, "messages:", "yellow"),
3849
+ requestMessages,
3850
+ color(config, "systemPrompt:", "yellow"),
3851
+ input.requestPayload.systemPrompt ?? "(none)",
3852
+ color(config, "request.body:", "yellow"),
3853
+ requestBody
3854
+ ];
3855
+ emitDebug(config, lines.join(`
3856
+ `));
3857
+ }
3858
+ function emitDebugResponse(config, input) {
3859
+ const text = input.text.length > 0 ? input.text : "(none)";
3860
+ const reasoning = input.reasoning.length > 0 ? input.reasoning : "(none)";
3861
+ const metadata = [
3862
+ `via=${input.via}`,
3863
+ `textChars=${input.text.length}`,
3864
+ `reasoningChars=${input.reasoning.length}`
3865
+ ];
3866
+ if (config.verbose) {
3867
+ metadata.push(`parseSourceChars=${input.parseSource.length}`);
3868
+ }
3869
+ metadata.push(`finishReason=${input.finishReason ?? "unknown"}`, `usage=${JSON.stringify(input.usage ?? {})}`);
3870
+ const lines = [
3871
+ color(config, title(config, [
3872
+ `[${input.label}][response]`,
3873
+ `attempt=${input.attempt}`,
3874
+ `selfHealEnabled=${input.selfHealEnabled}`,
3875
+ `selfHealAttempt=${input.selfHealAttempt}`
3876
+ ].join(" ")), "green"),
3877
+ dim(config, metadata.join(" ")),
3878
+ color(config, "text:", "yellow"),
3879
+ text,
3880
+ color(config, "reasoning:", "yellow"),
3881
+ reasoning
3882
+ ];
3883
+ if (config.verbose) {
3884
+ lines.push(color(config, "parseSource:", "yellow"), input.parseSource);
3885
+ }
3886
+ emitDebug(config, lines.join(`
3887
+ `));
3888
+ }
3889
+ function emitDebug(config, message) {
3890
+ if (!config.enabled) {
3891
+ return;
3892
+ }
3893
+ config.logger(message);
3894
+ }
3895
+
3896
+ // src/generate-model-call.ts
3975
3897
  async function callModel(adapter, options) {
3976
3898
  const requestSignal = options.request?.signal ?? (options.timeout?.request !== undefined ? AbortSignal.timeout(options.timeout.request) : undefined);
3977
3899
  const requestPayload = {
@@ -4191,67 +4113,115 @@ async function callModel(adapter, options) {
4191
4113
  reasoningBlocks: normalized.reasoningBlocks
4192
4114
  };
4193
4115
  }
4194
- function normalizeModelOutput(text, dedicatedReasoning, reasoningBlocks) {
4195
- const sanitized = sanitizeThink(text);
4196
- const visibleText = stripThinkBlocks(text, sanitized.thinkBlocks);
4197
- const reasoning = joinReasoningSegments([
4198
- dedicatedReasoning,
4199
- ...sanitized.thinkBlocks.map((block) => block.content)
4200
- ]);
4116
+
4117
+ // src/generate-shared.ts
4118
+ var sharedOutdent = createOutdent({
4119
+ trimLeadingNewline: true,
4120
+ trimTrailingNewline: true,
4121
+ newline: `
4122
+ `
4123
+ });
4124
+ function resolvePrompt(prompt, context) {
4125
+ const resolved = typeof prompt === "function" ? prompt(context) : prompt;
4126
+ return normalizePromptValue(resolved, context);
4127
+ }
4128
+ function normalizePromptValue(value, _context) {
4129
+ if (typeof value === "string") {
4130
+ return {
4131
+ prompt: value
4132
+ };
4133
+ }
4134
+ if (isPromptResolver(value)) {
4135
+ return normalizePromptPayload(value.resolvePrompt(_context));
4136
+ }
4137
+ return normalizePromptPayload(value);
4138
+ }
4139
+ function normalizePromptPayload(value) {
4140
+ const prompt = typeof value.prompt === "string" ? value.prompt : undefined;
4141
+ const messages = Array.isArray(value.messages) ? value.messages.filter(isLLMMessage) : undefined;
4142
+ if ((!prompt || prompt.trim().length === 0) && (!messages || messages.length === 0)) {
4143
+ throw new Error("Structured prompt payload must include a non-empty prompt or messages.");
4144
+ }
4201
4145
  return {
4202
- text: visibleText,
4203
- reasoning,
4204
- reasoningBlocks: normalizeReasoningBlocks(reasoningBlocks),
4205
- thinkBlocks: sanitized.thinkBlocks,
4206
- parseSource: composeParseSource(visibleText, reasoning)
4146
+ prompt,
4147
+ systemPrompt: typeof value.systemPrompt === "string" ? value.systemPrompt : undefined,
4148
+ messages: messages && messages.length > 0 ? messages.map((message) => ({ ...message })) : undefined
4207
4149
  };
4208
4150
  }
4209
- function normalizeReasoningBlocks(blocks) {
4210
- if (!Array.isArray(blocks)) {
4211
- return;
4151
+ function applyPromptOutdent(payload, enabled) {
4152
+ if (!enabled) {
4153
+ return payload;
4212
4154
  }
4213
- const normalized = blocks.map((block) => ({
4214
- turnIndex: block.turnIndex,
4215
- text: block.text.replace(RE_THINK_TAGS, "").trim()
4216
- })).filter((block) => Number.isFinite(block.turnIndex) && block.text.length > 0);
4217
- return normalized.length > 0 ? normalized : undefined;
4155
+ return {
4156
+ prompt: typeof payload.prompt === "string" ? sharedOutdent.string(payload.prompt) : undefined,
4157
+ systemPrompt: applyOutdentToOptionalPrompt(payload.systemPrompt, enabled),
4158
+ messages: payload.messages?.map((message) => ({
4159
+ ...message,
4160
+ content: typeof message.content === "string" ? sharedOutdent.string(message.content) : message.content
4161
+ }))
4162
+ };
4218
4163
  }
4219
- function appendReasoningBlock(blocks, transition) {
4220
- const text = transition.reasoningText?.replace(RE_THINK_TAGS, "").trim();
4221
- if (!text) {
4222
- return blocks;
4164
+ function applyOutdentToOptionalPrompt(value, enabled) {
4165
+ if (!enabled || typeof value !== "string") {
4166
+ return value;
4223
4167
  }
4224
- const next = [...blocks ?? [], { turnIndex: transition.turnIndex, text }];
4225
- return normalizeReasoningBlocks(next);
4168
+ return sharedOutdent.string(value);
4226
4169
  }
4227
- function composeParseSource(text, reasoning) {
4228
- if (typeof reasoning !== "string" || reasoning.length === 0) {
4229
- return text;
4230
- }
4231
- const sanitized = reasoning.replace(RE_THINK_TAGS, "");
4232
- if (sanitized.length === 0) {
4233
- return text;
4170
+ function mergeSystemPrompts(primary, secondary) {
4171
+ const prompts = [primary, secondary].map((value) => value?.trim()).filter((value) => Boolean(value));
4172
+ if (prompts.length === 0) {
4173
+ return;
4234
4174
  }
4235
- return `<think>${sanitized}</think>${text}`;
4175
+ return prompts.join(`
4176
+
4177
+ `);
4236
4178
  }
4237
- function aggregateUsage(attempts) {
4238
- let usage;
4239
- for (const attempt of attempts) {
4240
- usage = mergeUsage2(usage, attempt.usage);
4179
+ function normalizeStreamConfig(option) {
4180
+ if (typeof option === "boolean") {
4181
+ return {
4182
+ enabled: option
4183
+ };
4184
+ }
4185
+ if (!option) {
4186
+ return {
4187
+ enabled: false
4188
+ };
4241
4189
  }
4242
- return usage;
4190
+ return {
4191
+ enabled: option.enabled ?? true,
4192
+ onData: option.onData,
4193
+ onTurnTransition: option.onTurnTransition,
4194
+ to: option.to
4195
+ };
4243
4196
  }
4244
- function mergeUsage2(base, next) {
4245
- if (!base && !next) {
4246
- return;
4197
+ function normalizeDebugConfig(option) {
4198
+ if (typeof option === "boolean") {
4199
+ return {
4200
+ enabled: option,
4201
+ colors: true,
4202
+ verbose: false,
4203
+ logger: defaultDebugLogger
4204
+ };
4205
+ }
4206
+ if (!option) {
4207
+ return {
4208
+ enabled: false,
4209
+ colors: true,
4210
+ verbose: false,
4211
+ logger: defaultDebugLogger
4212
+ };
4247
4213
  }
4248
4214
  return {
4249
- inputTokens: (base?.inputTokens ?? 0) + (next?.inputTokens ?? 0),
4250
- outputTokens: (base?.outputTokens ?? 0) + (next?.outputTokens ?? 0),
4251
- totalTokens: (base?.totalTokens ?? 0) + (next?.totalTokens ?? 0),
4252
- cost: (base?.cost ?? 0) + (next?.cost ?? 0)
4215
+ enabled: option.enabled ?? true,
4216
+ colors: option.colors ?? true,
4217
+ verbose: option.verbose ?? false,
4218
+ logger: option.logger ?? defaultDebugLogger
4253
4219
  };
4254
4220
  }
4221
+ function defaultDebugLogger(line) {
4222
+ const { log } = globalThis.console;
4223
+ log(line);
4224
+ }
4255
4225
  function isPromptResolver(value) {
4256
4226
  return typeof value === "object" && value !== null && "resolvePrompt" in value && typeof value.resolvePrompt === "function";
4257
4227
  }
@@ -4265,95 +4235,6 @@ function isLLMMessage(value) {
4265
4235
  }
4266
4236
  return "content" in candidate;
4267
4237
  }
4268
- function joinReasoningSegments(parts) {
4269
- return parts.map((value) => value?.trim()).filter((value) => Boolean(value)).join(`
4270
-
4271
- `);
4272
- }
4273
- function stripThinkBlocks(text, thinkBlocks) {
4274
- if (thinkBlocks.length === 0) {
4275
- return text;
4276
- }
4277
- let output = "";
4278
- let cursor = 0;
4279
- for (const block of thinkBlocks) {
4280
- output += text.slice(cursor, block.start);
4281
- cursor = block.end;
4282
- }
4283
- output += text.slice(cursor);
4284
- return output;
4285
- }
4286
- function toStreamDataFingerprint(value) {
4287
- try {
4288
- return JSON.stringify(value);
4289
- } catch {
4290
- return "__unserializable__";
4291
- }
4292
- }
4293
- function emitDebugRequest(config, input) {
4294
- const requestBody = input.requestPayload.body !== undefined ? JSON.stringify(input.requestPayload.body, null, 2) : "(none)";
4295
- const requestMessages = input.requestPayload.messages !== undefined ? JSON.stringify(input.requestPayload.messages, null, 2) : "(none)";
4296
- const lines = [
4297
- color(config, title(config, [
4298
- `[${input.label}][request]`,
4299
- `attempt=${input.attempt}`,
4300
- `selfHealEnabled=${input.selfHealEnabled}`,
4301
- `selfHealAttempt=${input.selfHealAttempt}`
4302
- ].join(" ")), "cyan"),
4303
- dim(config, [
4304
- `provider=${input.provider ?? "unknown"}`,
4305
- `model=${input.model ?? "unknown"}`,
4306
- `stream=${input.stream}`
4307
- ].join(" ")),
4308
- color(config, "prompt:", "yellow"),
4309
- input.requestPayload.prompt ?? "(none)",
4310
- color(config, "messages:", "yellow"),
4311
- requestMessages,
4312
- color(config, "systemPrompt:", "yellow"),
4313
- input.requestPayload.systemPrompt ?? "(none)",
4314
- color(config, "request.body:", "yellow"),
4315
- requestBody
4316
- ];
4317
- emitDebug(config, lines.join(`
4318
- `));
4319
- }
4320
- function emitDebugResponse(config, input) {
4321
- const text = input.text.length > 0 ? input.text : "(none)";
4322
- const reasoning = input.reasoning.length > 0 ? input.reasoning : "(none)";
4323
- const metadata = [
4324
- `via=${input.via}`,
4325
- `textChars=${input.text.length}`,
4326
- `reasoningChars=${input.reasoning.length}`
4327
- ];
4328
- if (config.verbose) {
4329
- metadata.push(`parseSourceChars=${input.parseSource.length}`);
4330
- }
4331
- metadata.push(`finishReason=${input.finishReason ?? "unknown"}`, `usage=${JSON.stringify(input.usage ?? {})}`);
4332
- const lines = [
4333
- color(config, title(config, [
4334
- `[${input.label}][response]`,
4335
- `attempt=${input.attempt}`,
4336
- `selfHealEnabled=${input.selfHealEnabled}`,
4337
- `selfHealAttempt=${input.selfHealAttempt}`
4338
- ].join(" ")), "green"),
4339
- dim(config, metadata.join(" ")),
4340
- color(config, "text:", "yellow"),
4341
- text,
4342
- color(config, "reasoning:", "yellow"),
4343
- reasoning
4344
- ];
4345
- if (config.verbose) {
4346
- lines.push(color(config, "parseSource:", "yellow"), input.parseSource);
4347
- }
4348
- emitDebug(config, lines.join(`
4349
- `));
4350
- }
4351
- function emitDebug(config, message) {
4352
- if (!config.enabled) {
4353
- return;
4354
- }
4355
- config.logger(message);
4356
- }
4357
4238
 
4358
4239
  // src/generate.ts
4359
4240
  async function generate(adapter, promptOrOptions, callOptions) {
@@ -4429,7 +4310,7 @@ function normalizeGenerateInput(promptOrOptions, callOptions) {
4429
4310
  throw new Error("Missing prompt in generate(adapter, prompt, options?) call.");
4430
4311
  }
4431
4312
  return {
4432
- ...callOptions ?? {},
4313
+ ...callOptions,
4433
4314
  prompt: promptOrOptions
4434
4315
  };
4435
4316
  }
@@ -4455,9 +4336,6 @@ function prepareGeneratePromptPayload(payload, systemPrompt) {
4455
4336
  };
4456
4337
  }
4457
4338
 
4458
- // src/structured.ts
4459
- import { jsonrepair as jsonrepair3 } from "jsonrepair";
4460
-
4461
4339
  // src/parse.ts
4462
4340
  import { jsonrepair as jsonrepair2 } from "jsonrepair";
4463
4341
  function parseLLMOutput(output, schema, options = {}) {
@@ -4757,6 +4635,80 @@ function formatZodIssues(issues) {
4757
4635
  `);
4758
4636
  }
4759
4637
 
4638
+ // src/structured-streaming.ts
4639
+ import { jsonrepair as jsonrepair3 } from "jsonrepair";
4640
+ function parseStreamingStructuredData(parseSource) {
4641
+ const sanitized = sanitizeThink(parseSource);
4642
+ const start = findFirstJsonRootStart(sanitized.visibleText);
4643
+ if (start < 0) {
4644
+ return null;
4645
+ }
4646
+ const candidate = sanitized.visibleText.slice(start).trim();
4647
+ if (!candidate) {
4648
+ return null;
4649
+ }
4650
+ try {
4651
+ const repaired = jsonrepair3(candidate);
4652
+ const parsed = JSON.parse(repaired);
4653
+ if (typeof parsed !== "object" || parsed === null) {
4654
+ return null;
4655
+ }
4656
+ return parsed;
4657
+ } catch {
4658
+ return null;
4659
+ }
4660
+ }
4661
+ function findFirstJsonRootStart(input) {
4662
+ const unquotedRootStart = findFirstUnquotedJsonRootStart(input);
4663
+ if (unquotedRootStart >= 0) {
4664
+ return unquotedRootStart;
4665
+ }
4666
+ return findFirstRawJsonRootStart(input);
4667
+ }
4668
+ function findFirstUnquotedJsonRootStart(input) {
4669
+ let inString = false;
4670
+ let escaped = false;
4671
+ for (let index = 0;index < input.length; index += 1) {
4672
+ const char = input[index];
4673
+ if (!char) {
4674
+ continue;
4675
+ }
4676
+ if (inString) {
4677
+ if (escaped) {
4678
+ escaped = false;
4679
+ continue;
4680
+ }
4681
+ if (char === "\\") {
4682
+ escaped = true;
4683
+ continue;
4684
+ }
4685
+ if (char === '"') {
4686
+ inString = false;
4687
+ }
4688
+ continue;
4689
+ }
4690
+ if (char === '"') {
4691
+ inString = true;
4692
+ continue;
4693
+ }
4694
+ if (char === "{" || char === "[") {
4695
+ return index;
4696
+ }
4697
+ }
4698
+ return -1;
4699
+ }
4700
+ function findFirstRawJsonRootStart(input) {
4701
+ const objectStart = input.indexOf("{");
4702
+ const arrayStart = input.indexOf("[");
4703
+ if (objectStart < 0) {
4704
+ return arrayStart;
4705
+ }
4706
+ if (arrayStart < 0) {
4707
+ return objectStart;
4708
+ }
4709
+ return Math.min(objectStart, arrayStart);
4710
+ }
4711
+
4760
4712
  // src/structured.ts
4761
4713
  class StructuredParseError extends Error {
4762
4714
  name = "StructuredParseError";
@@ -4994,7 +4946,7 @@ function normalizeStructuredInput(schemaOrOptions, promptInput, callOptions) {
4994
4946
  throw new Error("Missing prompt in structured(adapter, schema, prompt, options?) call.");
4995
4947
  }
4996
4948
  return {
4997
- ...callOptions ?? {},
4949
+ ...callOptions,
4998
4950
  schema: schemaOrOptions,
4999
4951
  prompt: promptInput
5000
4952
  };
@@ -5265,67 +5217,6 @@ async function callModel2(adapter, options) {
5265
5217
  debugLabel: "structured"
5266
5218
  });
5267
5219
  }
5268
- function parseStreamingStructuredData(parseSource) {
5269
- const sanitized = sanitizeThink(parseSource);
5270
- const start = findFirstJsonRootStart(sanitized.visibleText);
5271
- if (start < 0) {
5272
- return null;
5273
- }
5274
- const candidate = sanitized.visibleText.slice(start).trim();
5275
- if (!candidate) {
5276
- return null;
5277
- }
5278
- try {
5279
- const repaired = jsonrepair3(candidate);
5280
- const parsed = JSON.parse(repaired);
5281
- if (typeof parsed !== "object" || parsed === null) {
5282
- return null;
5283
- }
5284
- return parsed;
5285
- } catch {
5286
- return null;
5287
- }
5288
- }
5289
- function findFirstJsonRootStart(input) {
5290
- let inString = false;
5291
- let escaped = false;
5292
- for (let index = 0;index < input.length; index += 1) {
5293
- const char = input[index];
5294
- if (!char) {
5295
- continue;
5296
- }
5297
- if (inString) {
5298
- if (escaped) {
5299
- escaped = false;
5300
- continue;
5301
- }
5302
- if (char === "\\") {
5303
- escaped = true;
5304
- continue;
5305
- }
5306
- if (char === '"') {
5307
- inString = false;
5308
- }
5309
- continue;
5310
- }
5311
- if (char === '"') {
5312
- inString = true;
5313
- continue;
5314
- }
5315
- if (char === "{" || char === "[") {
5316
- return index;
5317
- }
5318
- }
5319
- const objectStart = input.indexOf("{");
5320
- const arrayStart = input.indexOf("[");
5321
- if (objectStart < 0) {
5322
- return arrayStart;
5323
- }
5324
- if (arrayStart < 0) {
5325
- return objectStart;
5326
- }
5327
- return Math.min(objectStart, arrayStart);
5328
- }
5329
5220
  function parseWithObserve(output, schema, parseOptions, context) {
5330
5221
  const userParseTrace = parseOptions.onTrace;
5331
5222
  return parseLLMOutput(output, schema, {
@@ -5428,12 +5319,12 @@ function mergeStructuredOptions(defaults, overrides) {
5428
5319
  ...defaults,
5429
5320
  ...overrides,
5430
5321
  parse: {
5431
- ...defaults?.parse ?? {},
5432
- ...overrides?.parse ?? {}
5322
+ ...defaults?.parse,
5323
+ ...overrides?.parse
5433
5324
  },
5434
5325
  request: {
5435
- ...defaults?.request ?? {},
5436
- ...overrides?.request ?? {}
5326
+ ...defaults?.request,
5327
+ ...overrides?.request
5437
5328
  },
5438
5329
  stream: mergeObjectLike(defaults?.stream, overrides?.stream),
5439
5330
  selfHeal: mergeObjectLike(defaults?.selfHeal, overrides?.selfHeal),
@@ -5449,8 +5340,8 @@ function mergeGenerateOptions(defaults, overrides) {
5449
5340
  outdent: overrides?.outdent ?? defaults?.outdent,
5450
5341
  systemPrompt: overrides?.systemPrompt ?? defaults?.systemPrompt,
5451
5342
  request: {
5452
- ...defaults?.request ?? {},
5453
- ...overrides?.request ?? {}
5343
+ ...defaults?.request,
5344
+ ...overrides?.request
5454
5345
  },
5455
5346
  stream: mergeObjectLike(defaults?.stream, overrides?.stream),
5456
5347
  debug: mergeObjectLike(defaults?.debug, overrides?.debug),
@@ -5647,7 +5538,7 @@ function toPromptString(value) {
5647
5538
  return String(value);
5648
5539
  }
5649
5540
  }
5650
- var dedent = createOutdent({
5541
+ var outdent = createOutdent({
5651
5542
  trimLeadingNewline: true,
5652
5543
  trimTrailingNewline: true,
5653
5544
  newline: `
@@ -5680,7 +5571,7 @@ function stripOuterBlankLines(text) {
5680
5571
  `);
5681
5572
  }
5682
5573
  function renderPromptTemplate(strings, values) {
5683
- return stripOuterBlankLines(dedent(strings, ...values.map(toPromptString)));
5574
+ return stripOuterBlankLines(outdent(strings, ...values.map(toPromptString)));
5684
5575
  }
5685
5576
  function isTemplateStringsArray(value) {
5686
5577
  return Array.isArray(value) && "raw" in value;
@@ -5728,6 +5619,9 @@ class PromptMessageBuilderImpl {
5728
5619
  function createPromptMessageBuilder() {
5729
5620
  return new PromptMessageBuilderImpl;
5730
5621
  }
5622
+ function dedent(strings, ...values) {
5623
+ return renderPromptTemplate(strings, values);
5624
+ }
5731
5625
  function prompt(input, ...values) {
5732
5626
  if (isTemplateStringsArray(input)) {
5733
5627
  return renderPromptTemplate(input, values);
@@ -5934,6 +5828,7 @@ export {
5934
5828
  extractMarkdownCodeBlocks,
5935
5829
  extractJsonCandidates,
5936
5830
  extractFirstMarkdownCode,
5831
+ dedent,
5937
5832
  createProviderRegistry,
5938
5833
  createOpenAICompatibleAdapter,
5939
5834
  createModelAdapter,