extrait 0.1.0

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 ADDED
@@ -0,0 +1,4290 @@
1
+ // src/extract.ts
2
+ import { jsonrepair } from "jsonrepair";
3
+
4
+ // src/utils/common.ts
5
+ function toErrorMessage(error) {
6
+ if (error instanceof Error) {
7
+ return error.message;
8
+ }
9
+ return String(error);
10
+ }
11
+ function isWhitespace(char) {
12
+ return char === " " || char === "\t" || char === `
13
+ ` || char === "\r";
14
+ }
15
+
16
+ // src/markdown.ts
17
+ function extractMarkdownCodeBlocks(input, options = {}) {
18
+ const wantedLanguage = normalizeLanguage(options.language);
19
+ const blocks = [];
20
+ let cursor = 0;
21
+ while (cursor < input.length) {
22
+ const lineStart = cursor;
23
+ const [lineEnd, nextLineStart] = nextLineBounds(input, lineStart);
24
+ const line = input.slice(lineStart, lineEnd);
25
+ const opening = parseOpeningFence(line);
26
+ if (!opening) {
27
+ cursor = nextLineStart;
28
+ continue;
29
+ }
30
+ const rawLanguage = normalizeLanguage(extractInfoLanguage(opening.info));
31
+ const contentStart = nextLineStart;
32
+ let scanCursor = contentStart;
33
+ let closed = false;
34
+ while (scanCursor <= input.length) {
35
+ const closingLineStart = scanCursor;
36
+ const [closingLineEnd, afterClosingLine] = nextLineBounds(input, closingLineStart);
37
+ const closingLine = input.slice(closingLineStart, closingLineEnd);
38
+ const closing = parseClosingFence(closingLine, opening.marker, opening.length);
39
+ const inlineClosing = parseInlineClosingFence(closingLine, opening.marker, opening.length);
40
+ if (closing !== null) {
41
+ if (!wantedLanguage || rawLanguage === wantedLanguage) {
42
+ blocks.push({
43
+ language: rawLanguage,
44
+ code: input.slice(contentStart, closingLineStart).trim(),
45
+ start: lineStart,
46
+ end: closingLineStart + closing
47
+ });
48
+ if (options.firstOnly) {
49
+ return blocks;
50
+ }
51
+ }
52
+ cursor = afterClosingLine;
53
+ closed = true;
54
+ break;
55
+ }
56
+ if (inlineClosing) {
57
+ if (!wantedLanguage || rawLanguage === wantedLanguage) {
58
+ blocks.push({
59
+ language: rawLanguage,
60
+ code: input.slice(contentStart, closingLineStart + inlineClosing.start).trim(),
61
+ start: lineStart,
62
+ end: closingLineStart + inlineClosing.end
63
+ });
64
+ if (options.firstOnly) {
65
+ return blocks;
66
+ }
67
+ }
68
+ cursor = afterClosingLine;
69
+ closed = true;
70
+ break;
71
+ }
72
+ if (afterClosingLine <= scanCursor) {
73
+ break;
74
+ }
75
+ scanCursor = afterClosingLine;
76
+ }
77
+ if (!closed) {
78
+ cursor = nextLineStart;
79
+ }
80
+ }
81
+ return blocks;
82
+ }
83
+ function extractFirstMarkdownCode(input, options = {}) {
84
+ return extractMarkdownCodeBlocks(input, { ...options, firstOnly: true })[0] || null;
85
+ }
86
+ function nextLineBounds(input, start) {
87
+ const newline = input.indexOf(`
88
+ `, start);
89
+ if (newline === -1) {
90
+ return [input.length, input.length];
91
+ }
92
+ return [newline, newline + 1];
93
+ }
94
+ function parseOpeningFence(line) {
95
+ const end = stripTrailingCarriageReturn(line);
96
+ const indent = leadingSpaces(end);
97
+ if (indent > 3 || indent >= end.length) {
98
+ return null;
99
+ }
100
+ const marker = end[indent];
101
+ if (marker !== "`" && marker !== "~") {
102
+ return null;
103
+ }
104
+ let fenceEnd = indent;
105
+ while (fenceEnd < end.length && end[fenceEnd] === marker) {
106
+ fenceEnd += 1;
107
+ }
108
+ const length = fenceEnd - indent;
109
+ if (length < 3) {
110
+ return null;
111
+ }
112
+ const info = end.slice(fenceEnd).trim();
113
+ if (marker === "`" && info.includes("`")) {
114
+ return null;
115
+ }
116
+ return { marker, length, info };
117
+ }
118
+ function parseClosingFence(line, marker, openingLength) {
119
+ const end = stripTrailingCarriageReturn(line);
120
+ const indent = leadingSpaces(end);
121
+ if (indent > 3 || indent >= end.length) {
122
+ return null;
123
+ }
124
+ if (end[indent] !== marker) {
125
+ return null;
126
+ }
127
+ let fenceEnd = indent;
128
+ while (fenceEnd < end.length && end[fenceEnd] === marker) {
129
+ fenceEnd += 1;
130
+ }
131
+ if (fenceEnd - indent < openingLength) {
132
+ return null;
133
+ }
134
+ for (let i = fenceEnd;i < end.length; i += 1) {
135
+ const char = end[i];
136
+ if (char !== " " && char !== "\t") {
137
+ return null;
138
+ }
139
+ }
140
+ return fenceEnd;
141
+ }
142
+ function parseInlineClosingFence(line, marker, openingLength) {
143
+ const end = stripTrailingCarriageReturn(line);
144
+ const trimmedEnd = trimRightWhitespaceIndex(end);
145
+ if (trimmedEnd <= 0) {
146
+ return null;
147
+ }
148
+ let fenceStart = trimmedEnd;
149
+ while (fenceStart > 0 && end[fenceStart - 1] === marker) {
150
+ fenceStart -= 1;
151
+ }
152
+ const fenceLength = trimmedEnd - fenceStart;
153
+ if (fenceLength < openingLength) {
154
+ return null;
155
+ }
156
+ if (fenceStart === 0 || isOnlyWhitespace(end.slice(0, fenceStart))) {
157
+ return null;
158
+ }
159
+ return {
160
+ start: fenceStart,
161
+ end: trimmedEnd
162
+ };
163
+ }
164
+ function extractInfoLanguage(info) {
165
+ if (!info) {
166
+ return null;
167
+ }
168
+ let start = 0;
169
+ while (start < info.length && isWhitespace(info[start])) {
170
+ start += 1;
171
+ }
172
+ if (start >= info.length) {
173
+ return null;
174
+ }
175
+ let end = start;
176
+ while (end < info.length && !isWhitespace(info[end])) {
177
+ end += 1;
178
+ }
179
+ let language = info.slice(start, end);
180
+ if (language.startsWith(".")) {
181
+ language = language.slice(1);
182
+ }
183
+ return language || null;
184
+ }
185
+ function normalizeLanguage(value) {
186
+ if (!value) {
187
+ return null;
188
+ }
189
+ const normalized = value.trim().toLowerCase();
190
+ return normalized || null;
191
+ }
192
+ function leadingSpaces(value) {
193
+ let count = 0;
194
+ while (count < value.length && value[count] === " ") {
195
+ count += 1;
196
+ }
197
+ return count;
198
+ }
199
+ function stripTrailingCarriageReturn(value) {
200
+ if (value.endsWith("\r")) {
201
+ return value.slice(0, -1);
202
+ }
203
+ return value;
204
+ }
205
+ function trimRightWhitespaceIndex(value) {
206
+ let end = value.length;
207
+ while (end > 0 && isWhitespace(value[end - 1])) {
208
+ end -= 1;
209
+ }
210
+ return end;
211
+ }
212
+ function isOnlyWhitespace(value) {
213
+ for (let i = 0;i < value.length; i += 1) {
214
+ if (!isWhitespace(value[i])) {
215
+ return false;
216
+ }
217
+ }
218
+ return true;
219
+ }
220
+
221
+ // src/extract.ts
222
+ var DEFAULT_EXTRACTION_HEURISTICS = {
223
+ firstPassMin: 12,
224
+ firstPassCap: 30,
225
+ firstPassMultiplier: 6,
226
+ secondPassMin: 4,
227
+ secondPassCap: 8,
228
+ secondPassMultiplier: 2,
229
+ hintMaxLength: 50000
230
+ };
231
+ function extractJsonCandidates(input, options = {}) {
232
+ const maxCandidates = options.maxCandidates ?? 5;
233
+ const acceptArrays = options.acceptArrays ?? true;
234
+ const allowRepairHints = options.allowRepairHints ?? true;
235
+ const heuristics = resolveExtractionHeuristics(options.heuristics);
236
+ const extractionInput = input;
237
+ const candidates = [];
238
+ candidates.push(...extractFromMarkdown(extractionInput, acceptArrays));
239
+ candidates.push(...scanBalancedSegments(extractionInput, acceptArrays));
240
+ if (candidates.length === 0 && extractionInput.trim()) {
241
+ const content = extractionInput.trim();
242
+ candidates.push({
243
+ id: "raw:fallback",
244
+ source: "raw",
245
+ content,
246
+ start: 0,
247
+ end: extractionInput.length,
248
+ score: 10 + Math.floor(lengthScore(content.length) / 3)
249
+ });
250
+ }
251
+ const prefiltered = prefilterByJsonShape(candidates, acceptArrays);
252
+ const firstPassLimit = clamp(maxCandidates * heuristics.firstPassMultiplier, heuristics.firstPassMin, heuristics.firstPassCap);
253
+ const firstPass = prefiltered.slice(0, firstPassLimit);
254
+ const secondPassLimit = Math.min(firstPass.length, clamp(maxCandidates * heuristics.secondPassMultiplier, heuristics.secondPassMin, heuristics.secondPassCap));
255
+ for (let i = 0;i < secondPassLimit; i += 1) {
256
+ const candidate = firstPass[i];
257
+ if (!candidate) {
258
+ continue;
259
+ }
260
+ const parseHint = buildParseHint(candidate.content, allowRepairHints, heuristics.hintMaxLength);
261
+ if (!parseHint) {
262
+ continue;
263
+ }
264
+ candidate.parseHint = parseHint;
265
+ candidate.score += parseHintBonus(parseHint);
266
+ }
267
+ const sorted = sortCandidates(firstPass);
268
+ const deduped = dedupeCandidates(sorted);
269
+ return deduped.slice(0, maxCandidates).map((candidate, index) => ({
270
+ id: `${candidate.source}:${index}`,
271
+ source: candidate.source,
272
+ content: candidate.content,
273
+ language: candidate.language,
274
+ parseHint: candidate.parseHint,
275
+ start: candidate.start,
276
+ end: candidate.end,
277
+ score: candidate.score
278
+ }));
279
+ }
280
+ function prefilterByJsonShape(candidates, acceptArrays) {
281
+ const shaped = candidates.map((candidate) => {
282
+ const shapeScore = jsonShapeScore(candidate.content, acceptArrays);
283
+ return {
284
+ ...candidate,
285
+ shapeScore,
286
+ score: candidate.score + shapeScore
287
+ };
288
+ });
289
+ const sorted = sortCandidates(shaped);
290
+ const deduped = dedupeCandidates(sorted);
291
+ const filtered = deduped.filter((candidate) => passesShapeFilter(candidate));
292
+ if (filtered.length > 0) {
293
+ const fallback = deduped.find((candidate) => !passesShapeFilter(candidate));
294
+ if (fallback) {
295
+ filtered.push(fallback);
296
+ }
297
+ return sortCandidates(filtered);
298
+ }
299
+ return deduped.slice(0, Math.min(1, deduped.length));
300
+ }
301
+ function extractFromMarkdown(input, acceptArrays) {
302
+ const blocks = extractMarkdownCodeBlocks(input);
303
+ return blocks.flatMap((block, index) => {
304
+ const language = block.language || null;
305
+ const content = block.code.trim();
306
+ if (!content) {
307
+ return [];
308
+ }
309
+ if (!looksLikeJsonEnvelope(content, acceptArrays)) {
310
+ return [];
311
+ }
312
+ const langBonus = languageBonus(language);
313
+ return [
314
+ {
315
+ id: `fenced:${index}`,
316
+ source: "fenced",
317
+ language,
318
+ content,
319
+ start: block.start,
320
+ end: block.end,
321
+ score: 260 + langBonus + lengthScore(content.length)
322
+ }
323
+ ];
324
+ });
325
+ }
326
+ function scanBalancedSegments(input, acceptArrays) {
327
+ const results = [];
328
+ const stack = [];
329
+ let inString = false;
330
+ let quote = null;
331
+ let escaped = false;
332
+ for (let i = 0;i < input.length; i += 1) {
333
+ const char = input[i];
334
+ if (!char) {
335
+ continue;
336
+ }
337
+ if (inString) {
338
+ if (escaped) {
339
+ escaped = false;
340
+ continue;
341
+ }
342
+ if (char === "\\") {
343
+ escaped = true;
344
+ continue;
345
+ }
346
+ if (char === quote) {
347
+ inString = false;
348
+ quote = null;
349
+ }
350
+ continue;
351
+ }
352
+ const allowSingleQuoted = stack.length > 0;
353
+ if (char === '"' || allowSingleQuoted && (char === "'" || char === "`")) {
354
+ inString = true;
355
+ quote = char;
356
+ continue;
357
+ }
358
+ if (char === "{" || char === "[") {
359
+ stack.push({ char, index: i });
360
+ continue;
361
+ }
362
+ if (char !== "}" && char !== "]") {
363
+ continue;
364
+ }
365
+ const expectedOpen = char === "}" ? "{" : "[";
366
+ while (stack.length > 0 && stack[stack.length - 1]?.char !== expectedOpen) {
367
+ stack.pop();
368
+ }
369
+ const opened = stack.pop();
370
+ if (!opened) {
371
+ continue;
372
+ }
373
+ if (stack.length > 0) {
374
+ continue;
375
+ }
376
+ if (!acceptArrays && opened.char === "[") {
377
+ continue;
378
+ }
379
+ const content = input.slice(opened.index, i + 1).trim();
380
+ if (!content) {
381
+ continue;
382
+ }
383
+ const rootBonus = opened.char === "{" ? 40 : 20;
384
+ const boundaryBonus = boundaryScore(input, opened.index, i + 1);
385
+ results.push({
386
+ id: `scan:${results.length}`,
387
+ source: "scan",
388
+ content,
389
+ start: opened.index,
390
+ end: i + 1,
391
+ score: 120 + rootBonus + boundaryBonus + lengthScore(content.length)
392
+ });
393
+ }
394
+ return results;
395
+ }
396
+ function looksLikeJsonEnvelope(content, acceptArrays) {
397
+ const trimmed = content.trim();
398
+ if (trimmed.startsWith("{")) {
399
+ return true;
400
+ }
401
+ if (acceptArrays && trimmed.startsWith("[")) {
402
+ return true;
403
+ }
404
+ return false;
405
+ }
406
+ function languageBonus(language) {
407
+ if (!language) {
408
+ return 40;
409
+ }
410
+ if (language === "json") {
411
+ return 140;
412
+ }
413
+ if (language === "jsonc" || language === "json5") {
414
+ return 100;
415
+ }
416
+ if (language === "javascript" || language === "typescript" || language === "js" || language === "ts") {
417
+ return 40;
418
+ }
419
+ return 0;
420
+ }
421
+ function jsonShapeScore(content, acceptArrays) {
422
+ const trimmed = content.trim();
423
+ if (!trimmed) {
424
+ return -100;
425
+ }
426
+ const root = trimmed[0];
427
+ const end = trimmed[trimmed.length - 1];
428
+ if (root !== "{" && root !== "[") {
429
+ return -100;
430
+ }
431
+ if (root === "[" && !acceptArrays) {
432
+ return -100;
433
+ }
434
+ const expectedEnd = root === "{" ? "}" : "]";
435
+ let score = 20;
436
+ if (end === expectedEnd) {
437
+ score += 20;
438
+ } else {
439
+ score -= 30;
440
+ }
441
+ score += hasBalancedJsonDelimiters(trimmed) ? 25 : -25;
442
+ const colonCount = countChar(trimmed, ":");
443
+ const commaCount = countChar(trimmed, ",");
444
+ const quoteCount = countChar(trimmed, '"');
445
+ if (root === "{") {
446
+ if (/^\{\s*\}$/.test(trimmed)) {
447
+ score += 12;
448
+ } else if (colonCount > 0) {
449
+ score += 22;
450
+ } else {
451
+ score -= 30;
452
+ }
453
+ if (quoteCount > 0) {
454
+ score += quoteCount % 2 === 0 ? 8 : -8;
455
+ }
456
+ } else {
457
+ score += /^\[\s*\]$/.test(trimmed) ? 8 : 4;
458
+ if (colonCount > 0) {
459
+ score += 4;
460
+ }
461
+ }
462
+ if (commaCount > 0) {
463
+ score += 4;
464
+ }
465
+ const structural = countStructuralChars(trimmed);
466
+ const ratio = structural / Math.max(trimmed.length, 1);
467
+ if (ratio >= 0.04 && ratio <= 0.9) {
468
+ score += 8;
469
+ } else {
470
+ score -= 5;
471
+ }
472
+ return score;
473
+ }
474
+ function boundaryScore(input, start, end) {
475
+ const before = start > 0 ? input[start - 1] : "";
476
+ const after = end < input.length ? input[end] : "";
477
+ let score = 0;
478
+ if (isBoundary(before)) {
479
+ score += 10;
480
+ }
481
+ if (isBoundary(after)) {
482
+ score += 10;
483
+ }
484
+ return score;
485
+ }
486
+ function isBoundary(char) {
487
+ if (!char) {
488
+ return true;
489
+ }
490
+ return /[\s,.;:!?`"'()[\]{}<>]/.test(char);
491
+ }
492
+ function lengthScore(length) {
493
+ return Math.min(120, Math.floor(Math.sqrt(length) * 6));
494
+ }
495
+ function passesShapeFilter(candidate) {
496
+ if (candidate.source === "raw") {
497
+ return true;
498
+ }
499
+ if (candidate.source === "fenced") {
500
+ return candidate.shapeScore >= 0;
501
+ }
502
+ return candidate.shapeScore >= 35;
503
+ }
504
+ function buildParseHint(content, allowRepair, hintMaxLength) {
505
+ if (content.length > hintMaxLength) {
506
+ return null;
507
+ }
508
+ try {
509
+ return {
510
+ success: true,
511
+ parsed: JSON.parse(content),
512
+ repaired: null,
513
+ usedRepair: false,
514
+ stage: "parse",
515
+ error: ""
516
+ };
517
+ } catch (directError) {
518
+ if (!allowRepair) {
519
+ return {
520
+ success: false,
521
+ parsed: null,
522
+ repaired: null,
523
+ usedRepair: false,
524
+ stage: "parse",
525
+ error: toErrorMessage(directError)
526
+ };
527
+ }
528
+ let repaired;
529
+ try {
530
+ repaired = jsonrepair(content);
531
+ } catch (repairError) {
532
+ return {
533
+ success: false,
534
+ parsed: null,
535
+ repaired: null,
536
+ usedRepair: true,
537
+ stage: "repair",
538
+ error: toErrorMessage(repairError)
539
+ };
540
+ }
541
+ try {
542
+ return {
543
+ success: true,
544
+ parsed: JSON.parse(repaired),
545
+ repaired,
546
+ usedRepair: true,
547
+ stage: "parse",
548
+ error: ""
549
+ };
550
+ } catch (parseError) {
551
+ return {
552
+ success: false,
553
+ parsed: null,
554
+ repaired,
555
+ usedRepair: true,
556
+ stage: "parse",
557
+ error: toErrorMessage(parseError || directError)
558
+ };
559
+ }
560
+ }
561
+ }
562
+ function resolveExtractionHeuristics(input) {
563
+ const merged = {
564
+ ...DEFAULT_EXTRACTION_HEURISTICS,
565
+ ...input
566
+ };
567
+ const firstPassMin = normalizeInteger(merged.firstPassMin, DEFAULT_EXTRACTION_HEURISTICS.firstPassMin);
568
+ const firstPassCap = Math.max(firstPassMin, normalizeInteger(merged.firstPassCap, DEFAULT_EXTRACTION_HEURISTICS.firstPassCap));
569
+ const secondPassMin = normalizeInteger(merged.secondPassMin, DEFAULT_EXTRACTION_HEURISTICS.secondPassMin);
570
+ const secondPassCap = Math.max(secondPassMin, normalizeInteger(merged.secondPassCap, DEFAULT_EXTRACTION_HEURISTICS.secondPassCap));
571
+ return {
572
+ firstPassMin,
573
+ firstPassCap,
574
+ firstPassMultiplier: normalizeInteger(merged.firstPassMultiplier, DEFAULT_EXTRACTION_HEURISTICS.firstPassMultiplier),
575
+ secondPassMin,
576
+ secondPassCap,
577
+ secondPassMultiplier: normalizeInteger(merged.secondPassMultiplier, DEFAULT_EXTRACTION_HEURISTICS.secondPassMultiplier),
578
+ hintMaxLength: normalizeInteger(merged.hintMaxLength, DEFAULT_EXTRACTION_HEURISTICS.hintMaxLength)
579
+ };
580
+ }
581
+ function normalizeInteger(value, fallback) {
582
+ if (typeof value !== "number" || !Number.isFinite(value)) {
583
+ return fallback;
584
+ }
585
+ return Math.max(0, Math.floor(value));
586
+ }
587
+ function parseHintBonus(hint) {
588
+ if (hint.success) {
589
+ return hint.usedRepair ? 70 : 120;
590
+ }
591
+ return hint.usedRepair ? -20 : -10;
592
+ }
593
+ function hasBalancedJsonDelimiters(input) {
594
+ const stack = [];
595
+ let inString = false;
596
+ let quote = null;
597
+ let escaped = false;
598
+ for (let i = 0;i < input.length; i += 1) {
599
+ const char = input[i];
600
+ if (inString) {
601
+ if (escaped) {
602
+ escaped = false;
603
+ continue;
604
+ }
605
+ if (char === "\\") {
606
+ escaped = true;
607
+ continue;
608
+ }
609
+ if (char === quote) {
610
+ inString = false;
611
+ quote = null;
612
+ }
613
+ continue;
614
+ }
615
+ if (char === '"' || char === "'" || char === "`") {
616
+ inString = true;
617
+ quote = char;
618
+ continue;
619
+ }
620
+ if (char === "{" || char === "[") {
621
+ stack.push(char);
622
+ continue;
623
+ }
624
+ if (char === "}" || char === "]") {
625
+ const expected = char === "}" ? "{" : "[";
626
+ const top = stack.pop();
627
+ if (top !== expected) {
628
+ return false;
629
+ }
630
+ }
631
+ }
632
+ return !inString && stack.length === 0;
633
+ }
634
+ function countChar(input, expected) {
635
+ let count = 0;
636
+ for (let i = 0;i < input.length; i += 1) {
637
+ if (input[i] === expected) {
638
+ count += 1;
639
+ }
640
+ }
641
+ return count;
642
+ }
643
+ function countStructuralChars(input) {
644
+ let count = 0;
645
+ for (let i = 0;i < input.length; i += 1) {
646
+ const char = input[i];
647
+ if (char === "{" || char === "}" || char === "[" || char === "]" || char === ":" || char === "," || char === '"') {
648
+ count += 1;
649
+ }
650
+ }
651
+ return count;
652
+ }
653
+ function sortCandidates(candidates) {
654
+ return [...candidates].sort((a, b) => {
655
+ if (b.score !== a.score) {
656
+ return b.score - a.score;
657
+ }
658
+ if (a.start !== b.start) {
659
+ return a.start - b.start;
660
+ }
661
+ return a.end - b.end;
662
+ });
663
+ }
664
+ function dedupeCandidates(candidates) {
665
+ const seen = new Set;
666
+ return candidates.filter((candidate) => {
667
+ const key = candidate.content.trim();
668
+ if (!key || seen.has(key)) {
669
+ return false;
670
+ }
671
+ seen.add(key);
672
+ return true;
673
+ });
674
+ }
675
+ function clamp(value, min, max) {
676
+ return Math.max(min, Math.min(max, Math.floor(value)));
677
+ }
678
+ // src/schema.ts
679
+ function formatZodSchemaLikeTypeScript(schema) {
680
+ return formatType(schema, 0, new WeakSet);
681
+ }
682
+ function formatType(schema, depth, seen) {
683
+ const unwrapped = unwrap(schema);
684
+ let result = formatCore(unwrapped.schema, depth, seen);
685
+ if (unwrapped.nullable) {
686
+ result = `${result} | null`;
687
+ }
688
+ return result;
689
+ }
690
+ function unwrap(schema) {
691
+ let current = schema;
692
+ let optional = false;
693
+ let nullable = false;
694
+ while (true) {
695
+ const typeName = current?._def?.typeName;
696
+ if (!typeName) {
697
+ break;
698
+ }
699
+ if (typeName === "ZodOptional") {
700
+ optional = true;
701
+ current = current._def?.innerType ?? current;
702
+ continue;
703
+ }
704
+ if (typeName === "ZodDefault") {
705
+ optional = true;
706
+ current = current._def?.innerType ?? current;
707
+ continue;
708
+ }
709
+ if (typeName === "ZodNullable") {
710
+ nullable = true;
711
+ current = current._def?.innerType ?? current;
712
+ continue;
713
+ }
714
+ if (typeName === "ZodEffects") {
715
+ current = current._def?.schema ?? current;
716
+ continue;
717
+ }
718
+ if (typeName === "ZodBranded") {
719
+ current = current._def?.type ?? current;
720
+ continue;
721
+ }
722
+ if (typeName === "ZodCatch") {
723
+ current = current._def?.innerType ?? current;
724
+ continue;
725
+ }
726
+ if (typeName === "ZodReadonly") {
727
+ current = current._def?.innerType ?? current;
728
+ continue;
729
+ }
730
+ if (typeName === "ZodPipeline") {
731
+ current = current._def?.out ?? current;
732
+ continue;
733
+ }
734
+ break;
735
+ }
736
+ return {
737
+ schema: current,
738
+ optional,
739
+ nullable
740
+ };
741
+ }
742
+ function formatCore(schema, depth, seen) {
743
+ if (seen.has(schema)) {
744
+ return "unknown";
745
+ }
746
+ seen.add(schema);
747
+ const typeName = schema?._def?.typeName;
748
+ switch (typeName) {
749
+ case "ZodString":
750
+ return "string";
751
+ case "ZodNumber":
752
+ return isIntegerNumber(schema) ? "int" : "number";
753
+ case "ZodBoolean":
754
+ return "boolean";
755
+ case "ZodBigInt":
756
+ return "bigint";
757
+ case "ZodDate":
758
+ return "Date";
759
+ case "ZodUndefined":
760
+ return "undefined";
761
+ case "ZodNull":
762
+ return "null";
763
+ case "ZodAny":
764
+ return "any";
765
+ case "ZodUnknown":
766
+ return "unknown";
767
+ case "ZodNever":
768
+ return "never";
769
+ case "ZodVoid":
770
+ return "void";
771
+ case "ZodLiteral": {
772
+ const value = schema._def?.value;
773
+ return JSON.stringify(value);
774
+ }
775
+ case "ZodEnum": {
776
+ const values = schema._def?.values ?? [];
777
+ return values.map((value) => JSON.stringify(value)).join(" | ") || "string";
778
+ }
779
+ case "ZodNativeEnum": {
780
+ const values = Object.values(schema._def?.values ?? {});
781
+ const unique = [...new Set(values.filter((value) => typeof value !== "string" || Number.isNaN(Number(value))))];
782
+ return unique.map((value) => JSON.stringify(value)).join(" | ") || "string";
783
+ }
784
+ case "ZodArray": {
785
+ const inner = formatType(schema._def?.type ?? schema, depth, seen);
786
+ return requiresParentheses(inner) ? `(${inner})[]` : `${inner}[]`;
787
+ }
788
+ case "ZodTuple": {
789
+ const items = (schema._def?.items ?? []).map((item) => formatType(item, depth, seen));
790
+ return `[${items.join(", ")}]`;
791
+ }
792
+ case "ZodUnion": {
793
+ const options = (schema._def?.options ?? []).map((option) => formatType(option, depth, seen));
794
+ return options.join(" | ") || "unknown";
795
+ }
796
+ case "ZodDiscriminatedUnion": {
797
+ const options = Array.from((schema._def?.options ?? new Map).values()).map((option) => formatType(option, depth, seen));
798
+ return options.join(" | ") || "unknown";
799
+ }
800
+ case "ZodIntersection": {
801
+ const left = formatType(schema._def?.left ?? schema, depth, seen);
802
+ const right = formatType(schema._def?.right ?? schema, depth, seen);
803
+ return `${left} & ${right}`;
804
+ }
805
+ case "ZodRecord": {
806
+ const keyType = formatType(schema._def?.keyType ?? schema, depth, seen);
807
+ const valueType = formatType(schema._def?.valueType ?? schema, depth, seen);
808
+ return `Record<${keyType}, ${valueType}>`;
809
+ }
810
+ case "ZodMap": {
811
+ const keyType = formatType(schema._def?.keyType ?? schema, depth, seen);
812
+ const valueType = formatType(schema._def?.valueType ?? schema, depth, seen);
813
+ return `Map<${keyType}, ${valueType}>`;
814
+ }
815
+ case "ZodSet": {
816
+ const valueType = formatType(schema._def?.valueType ?? schema, depth, seen);
817
+ return `Set<${valueType}>`;
818
+ }
819
+ case "ZodObject":
820
+ return formatObject(schema, depth, seen);
821
+ case "ZodLazy":
822
+ return "unknown";
823
+ default:
824
+ return "unknown";
825
+ }
826
+ }
827
+ function formatObject(schema, depth, seen) {
828
+ const indent = " ".repeat(depth);
829
+ const innerIndent = " ".repeat(depth + 1);
830
+ const rawShape = schema._def?.shape;
831
+ const shape = typeof rawShape === "function" ? rawShape() : rawShape ?? {};
832
+ const entries = Object.entries(shape);
833
+ if (entries.length === 0) {
834
+ return "{}";
835
+ }
836
+ const lines = entries.map(([key, value]) => {
837
+ const unwrapped = unwrap(value);
838
+ const type = formatType(unwrapped.schema, depth + 1, seen);
839
+ const typeWithNull = unwrapped.nullable ? `${type} | null` : type;
840
+ const description = readSchemaDescription(value);
841
+ const descriptionComment = description ? ` // ${description}` : "";
842
+ return `${innerIndent}${formatKey(key)}: ${typeWithNull},${descriptionComment}`;
843
+ });
844
+ return `{
845
+ ${lines.join(`
846
+ `)}
847
+ ${indent}}`;
848
+ }
849
+ function formatKey(key) {
850
+ return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(key) ? key : JSON.stringify(key);
851
+ }
852
+ function requiresParentheses(typeText) {
853
+ return typeText.includes(" | ") || typeText.includes(" & ");
854
+ }
855
+ function isIntegerNumber(schema) {
856
+ const checks = schema._def?.checks ?? [];
857
+ return checks.some((check) => check.kind === "int");
858
+ }
859
+ function readSchemaDescription(schema) {
860
+ let current = schema;
861
+ while (current?._def?.typeName) {
862
+ const direct = current._def.description;
863
+ if (typeof direct === "string" && direct.trim().length > 0) {
864
+ return sanitizeDescription(direct);
865
+ }
866
+ const fallback = current.description;
867
+ if (typeof fallback === "string" && fallback.trim().length > 0) {
868
+ return sanitizeDescription(fallback);
869
+ }
870
+ const typeName = current._def.typeName;
871
+ if (typeName === "ZodOptional" || typeName === "ZodDefault" || typeName === "ZodNullable") {
872
+ current = current._def.innerType ?? current;
873
+ continue;
874
+ }
875
+ if (typeName === "ZodEffects") {
876
+ current = current._def.schema ?? current;
877
+ continue;
878
+ }
879
+ if (typeName === "ZodBranded") {
880
+ current = current._def.type ?? current;
881
+ continue;
882
+ }
883
+ if (typeName === "ZodCatch" || typeName === "ZodReadonly") {
884
+ current = current._def.innerType ?? current;
885
+ continue;
886
+ }
887
+ if (typeName === "ZodPipeline") {
888
+ current = current._def.out ?? current;
889
+ continue;
890
+ }
891
+ break;
892
+ }
893
+ return;
894
+ }
895
+ function sanitizeDescription(value) {
896
+ return value.replace(/\s+/g, " ").trim();
897
+ }
898
+
899
+ // src/format.ts
900
+ var DEFAULT_SCHEMA_INSTRUCTION = "Strictly follow this schema:";
901
+ function withFormat(schema, options = {}) {
902
+ const schemaType = formatZodSchemaLikeTypeScript(schema);
903
+ const instruction = resolveSchemaInstruction(options.schemaInstruction);
904
+ return [instruction, schemaType].join(`
905
+ `);
906
+ }
907
+ function formatPrompt(schema, task, options = {}) {
908
+ const trimmedTask = task.trim();
909
+ if (trimmedTask.length === 0) {
910
+ return withFormat(schema, options);
911
+ }
912
+ return [withFormat(schema, options), "", trimmedTask].join(`
913
+ `);
914
+ }
915
+ function resolveSchemaInstruction(instruction) {
916
+ const trimmed = instruction?.trim();
917
+ return trimmed && trimmed.length > 0 ? trimmed : DEFAULT_SCHEMA_INSTRUCTION;
918
+ }
919
+ // src/think.ts
920
+ var THINK_TAG_NAME = "think";
921
+ function sanitizeThink(input) {
922
+ const thinkBlocks = [];
923
+ const diagnostics = {
924
+ unterminatedCount: 0,
925
+ nestedCount: 0,
926
+ hiddenChars: 0
927
+ };
928
+ const visibleParts = [];
929
+ let cursor = 0;
930
+ let searchFrom = 0;
931
+ let depth = 0;
932
+ let blockStart = -1;
933
+ let blockContentStart = -1;
934
+ while (searchFrom < input.length) {
935
+ const token = findNextThinkTag(input, searchFrom);
936
+ if (!token) {
937
+ break;
938
+ }
939
+ searchFrom = token.end;
940
+ if (token.type === "open") {
941
+ if (depth === 0) {
942
+ visibleParts.push(input.slice(cursor, token.start));
943
+ blockStart = token.start;
944
+ blockContentStart = token.end;
945
+ } else {
946
+ diagnostics.nestedCount += 1;
947
+ }
948
+ depth += 1;
949
+ continue;
950
+ }
951
+ if (depth === 0) {
952
+ continue;
953
+ }
954
+ depth -= 1;
955
+ if (depth > 0) {
956
+ continue;
957
+ }
958
+ const raw = input.slice(blockStart, token.end);
959
+ visibleParts.push(maskKeepingLineBreaks(raw));
960
+ thinkBlocks.push({
961
+ id: `think:${thinkBlocks.length}`,
962
+ content: input.slice(blockContentStart, token.start).trim(),
963
+ raw,
964
+ start: blockStart,
965
+ end: token.end
966
+ });
967
+ diagnostics.hiddenChars += countHiddenChars(raw);
968
+ cursor = token.end;
969
+ blockStart = -1;
970
+ blockContentStart = -1;
971
+ }
972
+ if (depth > 0 && blockStart >= 0) {
973
+ const raw = input.slice(blockStart);
974
+ visibleParts.push(maskKeepingLineBreaks(raw));
975
+ thinkBlocks.push({
976
+ id: `think:${thinkBlocks.length}`,
977
+ content: input.slice(blockContentStart).trim(),
978
+ raw,
979
+ start: blockStart,
980
+ end: input.length
981
+ });
982
+ diagnostics.hiddenChars += countHiddenChars(raw);
983
+ diagnostics.unterminatedCount += 1;
984
+ cursor = input.length;
985
+ }
986
+ if (cursor < input.length) {
987
+ visibleParts.push(input.slice(cursor));
988
+ }
989
+ return {
990
+ visibleText: visibleParts.join(""),
991
+ thinkBlocks,
992
+ diagnostics
993
+ };
994
+ }
995
+ function findNextThinkTag(input, from) {
996
+ let cursor = from;
997
+ while (cursor < input.length) {
998
+ const lt = input.indexOf("<", cursor);
999
+ if (lt < 0) {
1000
+ return null;
1001
+ }
1002
+ const token = parseThinkTagAt(input, lt);
1003
+ if (token) {
1004
+ return token;
1005
+ }
1006
+ cursor = lt + 1;
1007
+ }
1008
+ return null;
1009
+ }
1010
+ function parseThinkTagAt(input, index) {
1011
+ if (input[index] !== "<") {
1012
+ return null;
1013
+ }
1014
+ let cursor = index + 1;
1015
+ let closing = false;
1016
+ if (input[cursor] === "/") {
1017
+ closing = true;
1018
+ cursor += 1;
1019
+ }
1020
+ if (!matchesIgnoreCase(input, cursor, THINK_TAG_NAME)) {
1021
+ return null;
1022
+ }
1023
+ cursor += THINK_TAG_NAME.length;
1024
+ const next = input[cursor];
1025
+ if (next && isIdentifierChar(next)) {
1026
+ return null;
1027
+ }
1028
+ if (closing) {
1029
+ while (cursor < input.length && isWhitespace(input[cursor])) {
1030
+ cursor += 1;
1031
+ }
1032
+ if (input[cursor] !== ">") {
1033
+ return null;
1034
+ }
1035
+ return {
1036
+ type: "close",
1037
+ start: index,
1038
+ end: cursor + 1
1039
+ };
1040
+ }
1041
+ const tagEnd = findTagEnd(input, cursor);
1042
+ return {
1043
+ type: "open",
1044
+ start: index,
1045
+ end: tagEnd >= 0 ? tagEnd : input.length
1046
+ };
1047
+ }
1048
+ function findTagEnd(input, from) {
1049
+ let quote = null;
1050
+ for (let i = from;i < input.length; i += 1) {
1051
+ const char = input[i];
1052
+ if (quote) {
1053
+ if (char === quote && input[i - 1] !== "\\") {
1054
+ quote = null;
1055
+ }
1056
+ continue;
1057
+ }
1058
+ if (char === '"' || char === "'") {
1059
+ quote = char;
1060
+ continue;
1061
+ }
1062
+ if (char === ">") {
1063
+ return i + 1;
1064
+ }
1065
+ }
1066
+ return -1;
1067
+ }
1068
+ function matchesIgnoreCase(input, index, expected) {
1069
+ if (index + expected.length > input.length) {
1070
+ return false;
1071
+ }
1072
+ return input.slice(index, index + expected.length).toLowerCase() === expected;
1073
+ }
1074
+ function isIdentifierChar(char) {
1075
+ return /[a-zA-Z0-9:_-]/.test(char);
1076
+ }
1077
+ function countHiddenChars(value) {
1078
+ let count = 0;
1079
+ for (let i = 0;i < value.length; i += 1) {
1080
+ const char = value[i];
1081
+ if (char !== `
1082
+ ` && char !== "\r") {
1083
+ count += 1;
1084
+ }
1085
+ }
1086
+ return count;
1087
+ }
1088
+ function maskKeepingLineBreaks(value) {
1089
+ return value.replace(/[^\r\n]/g, " ");
1090
+ }
1091
+ // src/providers/stream-utils.ts
1092
+ async function consumeSSE(response, onEvent) {
1093
+ if (!response.body) {
1094
+ return;
1095
+ }
1096
+ const reader = response.body.getReader();
1097
+ const decoder = new TextDecoder;
1098
+ let buffer = "";
1099
+ while (true) {
1100
+ const { done, value } = await reader.read();
1101
+ if (done) {
1102
+ break;
1103
+ }
1104
+ buffer += decoder.decode(value, { stream: true });
1105
+ while (true) {
1106
+ const boundary = findSSEBoundary(buffer);
1107
+ if (boundary < 0) {
1108
+ break;
1109
+ }
1110
+ const rawEvent = buffer.slice(0, boundary);
1111
+ buffer = buffer.slice(boundary + (buffer.startsWith(`\r
1112
+ \r
1113
+ `, boundary) ? 4 : 2));
1114
+ const dataLines = rawEvent.split(/\r?\n/).filter((line) => line.startsWith("data:")).map((line) => line.slice(5).trim());
1115
+ if (dataLines.length === 0) {
1116
+ continue;
1117
+ }
1118
+ onEvent(dataLines.join(`
1119
+ `));
1120
+ }
1121
+ }
1122
+ const remainder = buffer.trim();
1123
+ if (remainder.length > 0) {
1124
+ const dataLines = remainder.split(/\r?\n/).filter((line) => line.startsWith("data:")).map((line) => line.slice(5).trim());
1125
+ if (dataLines.length > 0) {
1126
+ onEvent(dataLines.join(`
1127
+ `));
1128
+ }
1129
+ }
1130
+ }
1131
+ function findSSEBoundary(buffer) {
1132
+ const crlfIndex = buffer.indexOf(`\r
1133
+ \r
1134
+ `);
1135
+ const lfIndex = buffer.indexOf(`
1136
+
1137
+ `);
1138
+ if (crlfIndex >= 0 && lfIndex >= 0) {
1139
+ return Math.min(crlfIndex, lfIndex);
1140
+ }
1141
+ return Math.max(crlfIndex, lfIndex);
1142
+ }
1143
+
1144
+ // src/providers/mcp-runtime.ts
1145
+ var DEFAULT_MAX_TOOL_ROUNDS = 8;
1146
+ async function resolveMCPToolset(clients) {
1147
+ if (!Array.isArray(clients) || clients.length === 0) {
1148
+ return {
1149
+ tools: [],
1150
+ byName: new Map
1151
+ };
1152
+ }
1153
+ const listed = [];
1154
+ for (const client of clients) {
1155
+ let cursor;
1156
+ do {
1157
+ const page = await client.listTools(cursor ? { cursor } : undefined);
1158
+ for (const tool of page.tools ?? []) {
1159
+ listed.push({ client, tool });
1160
+ }
1161
+ cursor = page.nextCursor;
1162
+ } while (cursor);
1163
+ }
1164
+ const collisions = countNameCollisions(listed.map((entry) => entry.tool.name));
1165
+ const tools = [];
1166
+ const byName = new Map;
1167
+ for (const entry of listed) {
1168
+ const name = collisions.get(entry.tool.name) > 1 ? `${sanitizeToolName(entry.client.id)}__${sanitizeToolName(entry.tool.name)}` : sanitizeToolName(entry.tool.name);
1169
+ const resolved = {
1170
+ name,
1171
+ remoteName: entry.tool.name,
1172
+ description: describeTool(entry.client.id, entry.tool, collisions.get(entry.tool.name) > 1),
1173
+ inputSchema: normalizeInputSchema(entry.tool.inputSchema),
1174
+ clientId: entry.client.id,
1175
+ client: entry.client
1176
+ };
1177
+ tools.push(resolved);
1178
+ byName.set(name, resolved);
1179
+ }
1180
+ return {
1181
+ tools,
1182
+ byName
1183
+ };
1184
+ }
1185
+ function toProviderFunctionTools(toolset) {
1186
+ if (toolset.tools.length === 0) {
1187
+ return;
1188
+ }
1189
+ return toolset.tools.map((tool) => ({
1190
+ type: "function",
1191
+ function: {
1192
+ name: tool.name,
1193
+ description: tool.description,
1194
+ parameters: tool.inputSchema
1195
+ }
1196
+ }));
1197
+ }
1198
+ async function executeMCPToolCalls(calls, toolset, context) {
1199
+ const out = [];
1200
+ for (const call of calls) {
1201
+ const callId = call.id;
1202
+ const toolName = call.name;
1203
+ if (!callId || !toolName) {
1204
+ throw new Error("Received a function tool call without id or name.");
1205
+ }
1206
+ const tool = toolset.byName.get(toolName);
1207
+ if (!tool) {
1208
+ throw new Error(`No MCP tool registered for "${toolName}".`);
1209
+ }
1210
+ const parsedArguments = parseToolArguments(call.arguments);
1211
+ const args = isRecord(parsedArguments) ? parsedArguments : {};
1212
+ const metadata = {
1213
+ id: callId,
1214
+ type: call.type ?? "function",
1215
+ name: toolName,
1216
+ arguments: parsedArguments
1217
+ };
1218
+ const startedAt = new Date().toISOString();
1219
+ const startedAtMs = Date.now();
1220
+ try {
1221
+ const output = await tool.client.callTool({
1222
+ name: tool.remoteName,
1223
+ arguments: args
1224
+ });
1225
+ metadata.output = output;
1226
+ const execution = {
1227
+ callId,
1228
+ type: metadata.type,
1229
+ name: toolName,
1230
+ clientId: tool.clientId,
1231
+ remoteName: tool.remoteName,
1232
+ arguments: parsedArguments,
1233
+ output,
1234
+ round: context.round,
1235
+ provider: context.provider,
1236
+ model: context.model,
1237
+ handledLocally: true,
1238
+ startedAt,
1239
+ durationMs: Date.now() - startedAtMs
1240
+ };
1241
+ emitToolExecution(context.request, execution);
1242
+ out.push({ call: metadata, execution });
1243
+ } catch (error) {
1244
+ const message = error instanceof Error ? error.message : String(error);
1245
+ metadata.error = message;
1246
+ const execution = {
1247
+ callId,
1248
+ type: metadata.type,
1249
+ name: toolName,
1250
+ clientId: tool.clientId,
1251
+ remoteName: tool.remoteName,
1252
+ arguments: parsedArguments,
1253
+ error: message,
1254
+ round: context.round,
1255
+ provider: context.provider,
1256
+ model: context.model,
1257
+ handledLocally: true,
1258
+ startedAt,
1259
+ durationMs: Date.now() - startedAtMs
1260
+ };
1261
+ emitToolExecution(context.request, execution);
1262
+ out.push({ call: metadata, execution });
1263
+ }
1264
+ }
1265
+ return out;
1266
+ }
1267
+ function hasMCPClients(clients) {
1268
+ return Array.isArray(clients) && clients.length > 0;
1269
+ }
1270
+ function normalizeMaxToolRounds(value) {
1271
+ if (!Number.isFinite(value)) {
1272
+ return DEFAULT_MAX_TOOL_ROUNDS;
1273
+ }
1274
+ return Math.max(0, Math.floor(value));
1275
+ }
1276
+ function parseToolArguments(value) {
1277
+ if (typeof value !== "string") {
1278
+ return value ?? {};
1279
+ }
1280
+ try {
1281
+ return JSON.parse(value);
1282
+ } catch {
1283
+ return {};
1284
+ }
1285
+ }
1286
+ function stringifyToolOutput(value) {
1287
+ if (typeof value === "string") {
1288
+ return value;
1289
+ }
1290
+ return JSON.stringify(value ?? null);
1291
+ }
1292
+ function formatToolExecutionDebugLine(execution) {
1293
+ const status = execution.error ? "error" : "ok";
1294
+ const scope = [execution.provider, execution.model].filter(Boolean).join("/") || "unknown";
1295
+ const toolRef = execution.clientId ? `${execution.clientId}:${execution.name ?? "unknown"}` : execution.name ?? "unknown";
1296
+ const duration = typeof execution.durationMs === "number" ? ` ${execution.durationMs}ms` : "";
1297
+ const base = `[tool:mcp:${status}] ${scope} ${toolRef}#${execution.callId}${duration}`;
1298
+ if (execution.error) {
1299
+ return `${base} -> ${execution.error}`;
1300
+ }
1301
+ return base;
1302
+ }
1303
+ function emitToolExecution(request, execution) {
1304
+ request.onToolExecution?.(execution);
1305
+ const debug = resolveToolDebugOptions(request.toolDebug);
1306
+ if (!debug.enabled) {
1307
+ return;
1308
+ }
1309
+ debug.logger(formatToolExecutionDebugLine(execution));
1310
+ if (debug.includeRequest) {
1311
+ debug.logger(formatToolExecutionRequestDebugLine(execution, debug));
1312
+ }
1313
+ if (debug.includeResult && (!execution.error || debug.includeResultOnError)) {
1314
+ debug.logger(formatToolExecutionResultDebugLine(execution, debug));
1315
+ }
1316
+ }
1317
+ function resolveToolDebugOptions(value) {
1318
+ if (value === true) {
1319
+ return {
1320
+ enabled: true,
1321
+ logger: (line) => console.log(line),
1322
+ includeRequest: true,
1323
+ includeResult: true,
1324
+ includeResultOnError: true,
1325
+ pretty: false
1326
+ };
1327
+ }
1328
+ if (value === undefined || value === false) {
1329
+ return {
1330
+ enabled: false,
1331
+ logger: () => {
1332
+ return;
1333
+ },
1334
+ includeRequest: false,
1335
+ includeResult: false,
1336
+ includeResultOnError: false,
1337
+ pretty: false
1338
+ };
1339
+ }
1340
+ return {
1341
+ enabled: value.enabled ?? true,
1342
+ logger: value.logger ?? ((line) => console.log(line)),
1343
+ includeRequest: value.includeRequest ?? true,
1344
+ includeResult: value.includeResult ?? true,
1345
+ includeResultOnError: value.includeResultOnError ?? true,
1346
+ pretty: value.pretty ?? false
1347
+ };
1348
+ }
1349
+ function formatToolExecutionRequestDebugLine(execution, debug) {
1350
+ const scope = [execution.provider, execution.model].filter(Boolean).join("/") || "unknown";
1351
+ const toolRef = execution.clientId ? `${execution.clientId}:${execution.name ?? "unknown"}` : execution.name ?? "unknown";
1352
+ const payload = formatDebugPayload(execution.arguments, debug.pretty);
1353
+ return `[tool:mcp:request] ${scope} ${toolRef}#${execution.callId} arguments=${payload}`;
1354
+ }
1355
+ function formatToolExecutionResultDebugLine(execution, debug) {
1356
+ const scope = [execution.provider, execution.model].filter(Boolean).join("/") || "unknown";
1357
+ const toolRef = execution.clientId ? `${execution.clientId}:${execution.name ?? "unknown"}` : execution.name ?? "unknown";
1358
+ if (execution.error) {
1359
+ const payload2 = formatDebugPayload({ error: execution.error }, debug.pretty);
1360
+ return `[tool:mcp:result:error] ${scope} ${toolRef}#${execution.callId} output=${payload2}`;
1361
+ }
1362
+ const payload = formatDebugPayload(execution.output, debug.pretty);
1363
+ return `[tool:mcp:result:ok] ${scope} ${toolRef}#${execution.callId} output=${payload}`;
1364
+ }
1365
+ function formatDebugPayload(value, pretty) {
1366
+ if (value === undefined) {
1367
+ return "undefined";
1368
+ }
1369
+ try {
1370
+ const serialized = JSON.stringify(value, null, pretty ? 2 : 0);
1371
+ return serialized ?? "undefined";
1372
+ } catch {
1373
+ return String(value);
1374
+ }
1375
+ }
1376
+ function countNameCollisions(names) {
1377
+ const out = new Map;
1378
+ for (const name of names) {
1379
+ out.set(name, (out.get(name) ?? 0) + 1);
1380
+ }
1381
+ return out;
1382
+ }
1383
+ function normalizeInputSchema(schema) {
1384
+ if (!isRecord(schema)) {
1385
+ return {
1386
+ type: "object",
1387
+ properties: {}
1388
+ };
1389
+ }
1390
+ const out = { ...schema };
1391
+ if (out.type === undefined) {
1392
+ out.type = "object";
1393
+ }
1394
+ if (!isRecord(out.properties)) {
1395
+ out.properties = {};
1396
+ }
1397
+ return out;
1398
+ }
1399
+ function describeTool(clientId, tool, hasCollision) {
1400
+ const prefix = hasCollision ? `[${clientId}] ` : "";
1401
+ if (tool.description && tool.description.length > 0) {
1402
+ return `${prefix}${tool.description}`;
1403
+ }
1404
+ if (prefix.length > 0) {
1405
+ return `${prefix}${tool.name}`;
1406
+ }
1407
+ return;
1408
+ }
1409
+ function sanitizeToolName(input) {
1410
+ const sanitized = input.replace(/[^A-Za-z0-9_]/g, "_").replace(/_+/g, "_");
1411
+ const trimmed = sanitized.replace(/^_+/, "").replace(/_+$/, "");
1412
+ if (!trimmed) {
1413
+ return "tool";
1414
+ }
1415
+ if (/^[0-9]/.test(trimmed)) {
1416
+ return `tool_${trimmed}`;
1417
+ }
1418
+ return trimmed;
1419
+ }
1420
+ function isRecord(value) {
1421
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1422
+ }
1423
+
1424
+ // src/providers/utils.ts
1425
+ function normalizeBaseURL(baseURL) {
1426
+ return baseURL.endsWith("/") ? baseURL : `${baseURL}/`;
1427
+ }
1428
+ function buildURL(baseURL, path) {
1429
+ return new URL(path, normalizeBaseURL(baseURL)).toString();
1430
+ }
1431
+ function safeJSONParse(input) {
1432
+ try {
1433
+ return JSON.parse(input);
1434
+ } catch {
1435
+ return null;
1436
+ }
1437
+ }
1438
+ function cleanUndefined(input) {
1439
+ const out = {};
1440
+ for (const [key, value] of Object.entries(input)) {
1441
+ if (value !== undefined) {
1442
+ out[key] = value;
1443
+ }
1444
+ }
1445
+ return out;
1446
+ }
1447
+ function isRecord2(value) {
1448
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1449
+ }
1450
+ function pickString(value) {
1451
+ return typeof value === "string" && value.length > 0 ? value : undefined;
1452
+ }
1453
+ function toFiniteNumber(value) {
1454
+ return typeof value === "number" && Number.isFinite(value) ? value : undefined;
1455
+ }
1456
+ function mergeUsage(base, next) {
1457
+ if (!base && !next) {
1458
+ return;
1459
+ }
1460
+ const inputTokens = addOptional(base?.inputTokens, next?.inputTokens);
1461
+ const outputTokens = addOptional(base?.outputTokens, next?.outputTokens);
1462
+ const totalTokens = addOptional(base?.totalTokens, next?.totalTokens);
1463
+ const cost = addOptional(base?.cost, next?.cost);
1464
+ const merged = {};
1465
+ if (inputTokens !== undefined) {
1466
+ merged.inputTokens = inputTokens;
1467
+ }
1468
+ if (outputTokens !== undefined) {
1469
+ merged.outputTokens = outputTokens;
1470
+ }
1471
+ if (totalTokens !== undefined) {
1472
+ merged.totalTokens = totalTokens;
1473
+ }
1474
+ if (cost !== undefined) {
1475
+ merged.cost = cost;
1476
+ }
1477
+ return Object.keys(merged).length > 0 ? merged : undefined;
1478
+ }
1479
+ function addOptional(a, b) {
1480
+ if (a === undefined && b === undefined) {
1481
+ return;
1482
+ }
1483
+ return (a ?? 0) + (b ?? 0);
1484
+ }
1485
+
1486
+ // src/providers/openai-compatible.ts
1487
+ function createOpenAICompatibleAdapter(options) {
1488
+ const fetcher = options.fetcher ?? fetch;
1489
+ const path = options.path ?? "/v1/chat/completions";
1490
+ const responsesPath = options.responsesPath ?? "/v1/responses";
1491
+ return {
1492
+ provider: "openai-compatible",
1493
+ model: options.model,
1494
+ async complete(request) {
1495
+ return completeOpenAIRequest(options, fetcher, path, responsesPath, request);
1496
+ },
1497
+ async stream(request, callbacks = {}) {
1498
+ const usesResponses = shouldUseResponsesAPI(options, request);
1499
+ const usesMCP = hasMCPClients(request.mcpClients);
1500
+ if (usesResponses || usesMCP) {
1501
+ return streamViaComplete(callbacks, () => completeOpenAIRequest(options, fetcher, path, responsesPath, request));
1502
+ }
1503
+ const response = await fetcher(buildURL(options.baseURL, path), {
1504
+ method: "POST",
1505
+ headers: buildHeaders(options),
1506
+ body: JSON.stringify(cleanUndefined({
1507
+ ...options.defaultBody,
1508
+ ...request.body,
1509
+ model: options.model,
1510
+ messages: buildMessages(request),
1511
+ temperature: request.temperature,
1512
+ max_tokens: request.maxTokens,
1513
+ stream: true
1514
+ }))
1515
+ });
1516
+ if (!response.ok) {
1517
+ const message = await response.text();
1518
+ throw new Error(`HTTP ${response.status}: ${message}`);
1519
+ }
1520
+ callbacks.onStart?.();
1521
+ let text = "";
1522
+ let usage;
1523
+ let finishReason;
1524
+ await consumeSSE(response, (data) => {
1525
+ if (data === "[DONE]") {
1526
+ return;
1527
+ }
1528
+ const json = safeJSONParse(data);
1529
+ if (!isRecord2(json)) {
1530
+ return;
1531
+ }
1532
+ const delta = pickAssistantDelta(json);
1533
+ const chunkUsage = pickUsage(json);
1534
+ const chunkFinishReason = pickFinishReason(json);
1535
+ usage = mergeUsage(usage, chunkUsage);
1536
+ if (chunkFinishReason) {
1537
+ finishReason = chunkFinishReason;
1538
+ }
1539
+ if (delta) {
1540
+ text += delta;
1541
+ callbacks.onToken?.(delta);
1542
+ }
1543
+ if (delta || chunkUsage || chunkFinishReason) {
1544
+ const chunk = {
1545
+ textDelta: delta,
1546
+ raw: json,
1547
+ usage: chunkUsage,
1548
+ finishReason: chunkFinishReason
1549
+ };
1550
+ callbacks.onChunk?.(chunk);
1551
+ }
1552
+ });
1553
+ const out = { text, usage, finishReason };
1554
+ callbacks.onComplete?.(out);
1555
+ return out;
1556
+ }
1557
+ };
1558
+ }
1559
+ async function completeOpenAIRequest(options, fetcher, chatPath, responsesPath, request) {
1560
+ const usesResponses = shouldUseResponsesAPI(options, request);
1561
+ const usesMCP = hasMCPClients(request.mcpClients);
1562
+ if (usesResponses) {
1563
+ if (usesMCP) {
1564
+ return completeWithResponsesAPIWithMCP(options, fetcher, responsesPath, request);
1565
+ }
1566
+ return completeWithResponsesAPIPassThrough(options, fetcher, responsesPath, request);
1567
+ }
1568
+ if (usesMCP) {
1569
+ return completeWithChatCompletionsWithMCP(options, fetcher, chatPath, request);
1570
+ }
1571
+ return completeWithChatCompletionsPassThrough(options, fetcher, chatPath, request);
1572
+ }
1573
+ async function completeWithChatCompletionsPassThrough(options, fetcher, path, request) {
1574
+ const response = await fetcher(buildURL(options.baseURL, path), {
1575
+ method: "POST",
1576
+ headers: buildHeaders(options),
1577
+ body: JSON.stringify(cleanUndefined({
1578
+ ...options.defaultBody,
1579
+ ...request.body,
1580
+ model: options.model,
1581
+ messages: buildMessages(request),
1582
+ temperature: request.temperature,
1583
+ max_tokens: request.maxTokens,
1584
+ stream: false
1585
+ }))
1586
+ });
1587
+ if (!response.ok) {
1588
+ const message = await response.text();
1589
+ throw new Error(`HTTP ${response.status}: ${message}`);
1590
+ }
1591
+ const payload = await response.json();
1592
+ const assistantMessage = pickAssistantMessage(payload);
1593
+ if (!assistantMessage) {
1594
+ throw new Error("No assistant message in OpenAI-compatible response.");
1595
+ }
1596
+ const toolCalls = pickChatToolCalls(payload);
1597
+ return {
1598
+ text: pickAssistantText(payload),
1599
+ raw: payload,
1600
+ usage: pickUsage(payload),
1601
+ finishReason: pickFinishReason(payload),
1602
+ toolCalls: toolCalls.length > 0 ? toolCalls : undefined
1603
+ };
1604
+ }
1605
+ async function completeWithChatCompletionsWithMCP(options, fetcher, path, request) {
1606
+ const mcpToolset = await resolveMCPToolset(request.mcpClients);
1607
+ const transportTools = toProviderFunctionTools(mcpToolset);
1608
+ const maxToolRounds = normalizeMaxToolRounds(request.maxToolRounds ?? options.defaultMaxToolRounds);
1609
+ let messages = buildMessages(request);
1610
+ let aggregatedUsage;
1611
+ let finishReason;
1612
+ let lastPayload;
1613
+ const toolCalls = [];
1614
+ const toolExecutions = [];
1615
+ for (let round = 1;round <= maxToolRounds + 1; round += 1) {
1616
+ const response = await fetcher(buildURL(options.baseURL, path), {
1617
+ method: "POST",
1618
+ headers: buildHeaders(options),
1619
+ body: JSON.stringify(cleanUndefined({
1620
+ ...options.defaultBody,
1621
+ ...request.body,
1622
+ model: options.model,
1623
+ messages,
1624
+ temperature: request.temperature,
1625
+ max_tokens: request.maxTokens,
1626
+ tools: transportTools,
1627
+ tool_choice: request.toolChoice,
1628
+ parallel_tool_calls: request.parallelToolCalls
1629
+ }))
1630
+ });
1631
+ if (!response.ok) {
1632
+ const message = await response.text();
1633
+ throw new Error(`HTTP ${response.status}: ${message}`);
1634
+ }
1635
+ const payload = await response.json();
1636
+ lastPayload = payload;
1637
+ aggregatedUsage = mergeUsage(aggregatedUsage, pickUsage(payload));
1638
+ finishReason = pickFinishReason(payload);
1639
+ const assistantMessage = pickAssistantMessage(payload);
1640
+ const calledTools = pickChatToolCalls(payload);
1641
+ if (!assistantMessage) {
1642
+ throw new Error("No assistant message in OpenAI-compatible response.");
1643
+ }
1644
+ if (calledTools.length === 0) {
1645
+ return {
1646
+ text: pickAssistantText(payload),
1647
+ raw: payload,
1648
+ usage: aggregatedUsage,
1649
+ finishReason,
1650
+ toolCalls: toolCalls.length > 0 ? toolCalls : undefined,
1651
+ toolExecutions: toolExecutions.length > 0 ? toolExecutions : undefined
1652
+ };
1653
+ }
1654
+ if (round > maxToolRounds) {
1655
+ throw new Error(`Tool call loop exceeded maxToolRounds (${maxToolRounds}).`);
1656
+ }
1657
+ const outputs = await executeMCPToolCalls(calledTools, mcpToolset, {
1658
+ round,
1659
+ request,
1660
+ provider: "openai-compatible",
1661
+ model: options.model
1662
+ });
1663
+ toolCalls.push(...outputs.map((entry) => entry.call));
1664
+ toolExecutions.push(...outputs.map((entry) => entry.execution));
1665
+ const toolMessages = outputs.map((entry) => ({
1666
+ role: "tool",
1667
+ tool_call_id: entry.call.id,
1668
+ content: stringifyToolOutput(entry.call.error ? { error: entry.call.error } : entry.call.output)
1669
+ }));
1670
+ messages = [...messages, assistantMessage, ...toolMessages];
1671
+ }
1672
+ return {
1673
+ text: pickAssistantText(lastPayload ?? {}),
1674
+ raw: lastPayload,
1675
+ usage: aggregatedUsage,
1676
+ finishReason,
1677
+ toolCalls: toolCalls.length > 0 ? toolCalls : undefined,
1678
+ toolExecutions: toolExecutions.length > 0 ? toolExecutions : undefined
1679
+ };
1680
+ }
1681
+ async function completeWithResponsesAPIPassThrough(options, fetcher, path, request) {
1682
+ const body = isRecord2(request.body) ? request.body : undefined;
1683
+ const response = await fetcher(buildURL(options.baseURL, path), {
1684
+ method: "POST",
1685
+ headers: buildHeaders(options),
1686
+ body: JSON.stringify(cleanUndefined({
1687
+ ...options.defaultBody,
1688
+ ...request.body,
1689
+ model: options.model,
1690
+ input: buildResponsesInput(request),
1691
+ previous_response_id: pickString(body?.previous_response_id),
1692
+ temperature: request.temperature,
1693
+ max_output_tokens: request.maxTokens
1694
+ }))
1695
+ });
1696
+ if (!response.ok) {
1697
+ const message = await response.text();
1698
+ throw new Error(`HTTP ${response.status}: ${message}`);
1699
+ }
1700
+ const payload = await response.json();
1701
+ const toolCalls = pickResponsesToolCalls(payload);
1702
+ return {
1703
+ text: pickResponsesText(payload) || pickAssistantText(payload),
1704
+ raw: payload,
1705
+ usage: pickUsage(payload),
1706
+ finishReason: pickResponsesFinishReason(payload) ?? pickFinishReason(payload),
1707
+ toolCalls: toolCalls.length > 0 ? toolCalls : undefined
1708
+ };
1709
+ }
1710
+ async function completeWithResponsesAPIWithMCP(options, fetcher, path, request) {
1711
+ const mcpToolset = await resolveMCPToolset(request.mcpClients);
1712
+ const transportTools = toResponsesTools(toProviderFunctionTools(mcpToolset));
1713
+ const maxToolRounds = normalizeMaxToolRounds(request.maxToolRounds ?? options.defaultMaxToolRounds);
1714
+ let input = buildResponsesInput(request);
1715
+ let previousResponseId = pickString(isRecord2(request.body) ? request.body.previous_response_id : undefined);
1716
+ let aggregatedUsage;
1717
+ let finishReason;
1718
+ let lastPayload;
1719
+ const executedToolCalls = [];
1720
+ const toolExecutions = [];
1721
+ for (let round = 1;round <= maxToolRounds + 1; round += 1) {
1722
+ const response = await fetcher(buildURL(options.baseURL, path), {
1723
+ method: "POST",
1724
+ headers: buildHeaders(options),
1725
+ body: JSON.stringify(cleanUndefined({
1726
+ ...options.defaultBody,
1727
+ ...request.body,
1728
+ model: options.model,
1729
+ input,
1730
+ previous_response_id: previousResponseId,
1731
+ temperature: request.temperature,
1732
+ max_output_tokens: request.maxTokens,
1733
+ tools: transportTools,
1734
+ tool_choice: request.toolChoice,
1735
+ parallel_tool_calls: request.parallelToolCalls
1736
+ }))
1737
+ });
1738
+ if (!response.ok) {
1739
+ const message = await response.text();
1740
+ throw new Error(`HTTP ${response.status}: ${message}`);
1741
+ }
1742
+ const payload = await response.json();
1743
+ lastPayload = payload;
1744
+ aggregatedUsage = mergeUsage(aggregatedUsage, pickUsage(payload));
1745
+ finishReason = pickResponsesFinishReason(payload) ?? finishReason;
1746
+ const providerToolCalls = pickResponsesToolCalls(payload);
1747
+ const functionCalls = providerToolCalls.filter((toolCall) => toolCall.type === "function" && typeof toolCall.id === "string" && typeof toolCall.name === "string");
1748
+ if (functionCalls.length === 0) {
1749
+ const text = pickResponsesText(payload) || pickAssistantText(payload);
1750
+ return {
1751
+ text,
1752
+ raw: payload,
1753
+ usage: aggregatedUsage,
1754
+ finishReason,
1755
+ toolCalls: executedToolCalls.length > 0 ? executedToolCalls : undefined,
1756
+ toolExecutions: toolExecutions.length > 0 ? toolExecutions : undefined
1757
+ };
1758
+ }
1759
+ if (round > maxToolRounds) {
1760
+ throw new Error(`Tool call loop exceeded maxToolRounds (${maxToolRounds}).`);
1761
+ }
1762
+ const outputs = await executeMCPToolCalls(functionCalls, mcpToolset, {
1763
+ round,
1764
+ request,
1765
+ provider: "openai-compatible",
1766
+ model: options.model
1767
+ });
1768
+ executedToolCalls.push(...outputs.map((entry) => entry.call));
1769
+ toolExecutions.push(...outputs.map((entry) => entry.execution));
1770
+ input = outputs.map((entry) => ({
1771
+ type: "function_call_output",
1772
+ call_id: entry.call.id,
1773
+ output: stringifyToolOutput(entry.call.error ? { error: entry.call.error } : entry.call.output)
1774
+ }));
1775
+ previousResponseId = pickString(payload.id);
1776
+ }
1777
+ return {
1778
+ text: pickResponsesText(lastPayload ?? {}) || pickAssistantText(lastPayload ?? {}),
1779
+ raw: lastPayload,
1780
+ usage: aggregatedUsage,
1781
+ finishReason,
1782
+ toolCalls: executedToolCalls.length > 0 ? executedToolCalls : undefined,
1783
+ toolExecutions: toolExecutions.length > 0 ? toolExecutions : undefined
1784
+ };
1785
+ }
1786
+ function shouldUseResponsesAPI(options, request) {
1787
+ if (options.path?.includes("/responses")) {
1788
+ return true;
1789
+ }
1790
+ const body = request.body;
1791
+ if (!isRecord2(body)) {
1792
+ return false;
1793
+ }
1794
+ return "input" in body || "previous_response_id" in body;
1795
+ }
1796
+ function buildHeaders(options) {
1797
+ return {
1798
+ "content-type": "application/json",
1799
+ ...options.apiKey ? { authorization: `Bearer ${options.apiKey}` } : {},
1800
+ ...options.headers
1801
+ };
1802
+ }
1803
+ function buildMessages(request) {
1804
+ const messages = [];
1805
+ if (request.systemPrompt) {
1806
+ messages.push({ role: "system", content: request.systemPrompt });
1807
+ }
1808
+ messages.push({ role: "user", content: request.prompt });
1809
+ return messages;
1810
+ }
1811
+ function buildResponsesInput(request) {
1812
+ if (isRecord2(request.body) && "input" in request.body) {
1813
+ return request.body.input;
1814
+ }
1815
+ const input = [];
1816
+ if (request.systemPrompt) {
1817
+ input.push({
1818
+ role: "system",
1819
+ content: request.systemPrompt
1820
+ });
1821
+ }
1822
+ input.push({
1823
+ role: "user",
1824
+ content: request.prompt
1825
+ });
1826
+ return input;
1827
+ }
1828
+ function toResponsesTools(tools) {
1829
+ if (!Array.isArray(tools) || tools.length === 0) {
1830
+ return;
1831
+ }
1832
+ return tools.map((tool) => {
1833
+ if (tool.type === "function" && isRecord2(tool.function)) {
1834
+ const functionTool = tool.function;
1835
+ return {
1836
+ type: "function",
1837
+ name: functionTool.name,
1838
+ description: functionTool.description,
1839
+ parameters: functionTool.parameters,
1840
+ strict: functionTool.strict
1841
+ };
1842
+ }
1843
+ return { ...tool };
1844
+ });
1845
+ }
1846
+ function pickChatToolCalls(payload) {
1847
+ const message = pickAssistantMessage(payload);
1848
+ if (!message) {
1849
+ return [];
1850
+ }
1851
+ const toolCalls = message.tool_calls;
1852
+ if (!Array.isArray(toolCalls)) {
1853
+ return [];
1854
+ }
1855
+ return toolCalls.map((entry) => {
1856
+ if (!isRecord2(entry)) {
1857
+ return { id: "", type: "function" };
1858
+ }
1859
+ const fn = isRecord2(entry.function) ? entry.function : undefined;
1860
+ return {
1861
+ id: pickString(entry.id) ?? "",
1862
+ type: pickString(entry.type) ?? "function",
1863
+ name: pickString(fn?.name),
1864
+ arguments: fn?.arguments
1865
+ };
1866
+ });
1867
+ }
1868
+ function pickResponsesToolCalls(payload) {
1869
+ const output = payload.output;
1870
+ if (!Array.isArray(output)) {
1871
+ return [];
1872
+ }
1873
+ const calls = [];
1874
+ for (const item of output) {
1875
+ if (!isRecord2(item)) {
1876
+ continue;
1877
+ }
1878
+ const type = pickString(item.type);
1879
+ if (type === "function_call") {
1880
+ calls.push({
1881
+ id: pickString(item.call_id) ?? pickString(item.id) ?? "",
1882
+ type: "function",
1883
+ name: pickString(item.name),
1884
+ arguments: item.arguments
1885
+ });
1886
+ continue;
1887
+ }
1888
+ if (type?.includes("mcp") || type?.includes("tool")) {
1889
+ calls.push({
1890
+ id: pickString(item.id) ?? pickString(item.call_id) ?? "",
1891
+ type,
1892
+ name: pickString(item.name),
1893
+ arguments: item.arguments
1894
+ });
1895
+ }
1896
+ }
1897
+ return calls;
1898
+ }
1899
+ function pickAssistantMessage(payload) {
1900
+ const choices = payload.choices;
1901
+ if (!Array.isArray(choices) || choices.length === 0) {
1902
+ return;
1903
+ }
1904
+ const first = choices[0];
1905
+ if (!isRecord2(first)) {
1906
+ return;
1907
+ }
1908
+ const message = first.message;
1909
+ if (!isRecord2(message)) {
1910
+ return;
1911
+ }
1912
+ return message;
1913
+ }
1914
+ function pickAssistantDelta(payload) {
1915
+ const choices = payload.choices;
1916
+ if (!Array.isArray(choices) || choices.length === 0) {
1917
+ return "";
1918
+ }
1919
+ const first = choices[0];
1920
+ if (!isRecord2(first)) {
1921
+ return "";
1922
+ }
1923
+ const delta = first.delta;
1924
+ if (!isRecord2(delta)) {
1925
+ return "";
1926
+ }
1927
+ const content = delta.content;
1928
+ if (typeof content === "string") {
1929
+ return content;
1930
+ }
1931
+ if (Array.isArray(content)) {
1932
+ return content.map((part) => {
1933
+ if (!isRecord2(part)) {
1934
+ return "";
1935
+ }
1936
+ const text = part.text;
1937
+ return typeof text === "string" ? text : "";
1938
+ }).join("");
1939
+ }
1940
+ return "";
1941
+ }
1942
+ function pickResponsesText(payload) {
1943
+ const outputText = payload.output_text;
1944
+ if (typeof outputText === "string") {
1945
+ return outputText;
1946
+ }
1947
+ const output = payload.output;
1948
+ if (!Array.isArray(output)) {
1949
+ return "";
1950
+ }
1951
+ return output.map((item) => {
1952
+ if (!isRecord2(item)) {
1953
+ return "";
1954
+ }
1955
+ if (typeof item.text === "string") {
1956
+ return item.text;
1957
+ }
1958
+ const content = item.content;
1959
+ if (!Array.isArray(content)) {
1960
+ return "";
1961
+ }
1962
+ return content.map((part) => {
1963
+ if (!isRecord2(part)) {
1964
+ return "";
1965
+ }
1966
+ if (typeof part.text === "string") {
1967
+ return part.text;
1968
+ }
1969
+ if (typeof part.output_text === "string") {
1970
+ return part.output_text;
1971
+ }
1972
+ return "";
1973
+ }).join("");
1974
+ }).join("");
1975
+ }
1976
+ function pickAssistantText(payload) {
1977
+ const message = pickAssistantMessage(payload);
1978
+ if (message) {
1979
+ const content = message.content;
1980
+ if (typeof content === "string") {
1981
+ return content;
1982
+ }
1983
+ if (Array.isArray(content)) {
1984
+ return content.map((part) => {
1985
+ if (typeof part === "string") {
1986
+ return part;
1987
+ }
1988
+ if (!isRecord2(part)) {
1989
+ return "";
1990
+ }
1991
+ const text = part.text;
1992
+ return typeof text === "string" ? text : "";
1993
+ }).join("");
1994
+ }
1995
+ }
1996
+ const choices = payload.choices;
1997
+ if (Array.isArray(choices) && choices.length > 0 && isRecord2(choices[0])) {
1998
+ const legacyText = choices[0].text;
1999
+ if (typeof legacyText === "string") {
2000
+ return legacyText;
2001
+ }
2002
+ }
2003
+ return "";
2004
+ }
2005
+ function pickUsage(payload) {
2006
+ const usage = payload.usage;
2007
+ if (!isRecord2(usage)) {
2008
+ return;
2009
+ }
2010
+ const promptTokens = toFiniteNumber(usage.prompt_tokens ?? usage.input_tokens);
2011
+ const completionTokens = toFiniteNumber(usage.completion_tokens ?? usage.output_tokens);
2012
+ const totalTokens = toFiniteNumber(usage.total_tokens);
2013
+ return {
2014
+ inputTokens: promptTokens,
2015
+ outputTokens: completionTokens,
2016
+ totalTokens
2017
+ };
2018
+ }
2019
+ function pickFinishReason(payload) {
2020
+ const choices = payload.choices;
2021
+ if (!Array.isArray(choices) || choices.length === 0 || !isRecord2(choices[0])) {
2022
+ return;
2023
+ }
2024
+ const reason = choices[0].finish_reason;
2025
+ return typeof reason === "string" && reason.length > 0 ? reason : undefined;
2026
+ }
2027
+ function pickResponsesFinishReason(payload) {
2028
+ const reason = payload.status;
2029
+ if (typeof reason === "string" && reason.length > 0) {
2030
+ return reason;
2031
+ }
2032
+ return;
2033
+ }
2034
+ async function streamViaComplete(callbacks, complete) {
2035
+ callbacks.onStart?.();
2036
+ const response = await complete();
2037
+ if (response.text.length > 0) {
2038
+ callbacks.onToken?.(response.text);
2039
+ }
2040
+ callbacks.onChunk?.({
2041
+ textDelta: response.text,
2042
+ raw: response.raw,
2043
+ done: true,
2044
+ usage: response.usage,
2045
+ finishReason: response.finishReason
2046
+ });
2047
+ callbacks.onComplete?.(response);
2048
+ return response;
2049
+ }
2050
+
2051
+ // src/providers/anthropic-compatible.ts
2052
+ var DEFAULT_ANTHROPIC_MAX_TOKENS = 1024;
2053
+ var DEFAULT_ANTHROPIC_VERSION = "2023-06-01";
2054
+ function createAnthropicCompatibleAdapter(options) {
2055
+ const fetcher = options.fetcher ?? fetch;
2056
+ const path = options.path ?? "/v1/messages";
2057
+ return {
2058
+ provider: "anthropic-compatible",
2059
+ model: options.model,
2060
+ async complete(request) {
2061
+ if (hasMCPClients(request.mcpClients)) {
2062
+ return completeWithMCPToolLoop(options, fetcher, path, request);
2063
+ }
2064
+ return completePassThrough(options, fetcher, path, request);
2065
+ },
2066
+ async stream(request, callbacks = {}) {
2067
+ if (hasMCPClients(request.mcpClients)) {
2068
+ return streamViaComplete2(callbacks, () => this.complete(request));
2069
+ }
2070
+ const response = await fetcher(buildURL(options.baseURL, path), {
2071
+ method: "POST",
2072
+ headers: buildHeaders2(options),
2073
+ body: JSON.stringify(cleanUndefined({
2074
+ ...options.defaultBody,
2075
+ ...request.body,
2076
+ model: options.model,
2077
+ system: request.systemPrompt,
2078
+ messages: [{ role: "user", content: request.prompt }],
2079
+ temperature: request.temperature,
2080
+ max_tokens: resolveMaxTokens(request.maxTokens, options.defaultMaxTokens),
2081
+ stream: true
2082
+ }))
2083
+ });
2084
+ if (!response.ok) {
2085
+ const message = await response.text();
2086
+ throw new Error(`HTTP ${response.status}: ${message}`);
2087
+ }
2088
+ callbacks.onStart?.();
2089
+ let text = "";
2090
+ let usage;
2091
+ let finishReason;
2092
+ await consumeSSE(response, (data) => {
2093
+ if (data === "[DONE]") {
2094
+ return;
2095
+ }
2096
+ const json = safeJSONParse(data);
2097
+ if (!isRecord2(json)) {
2098
+ return;
2099
+ }
2100
+ const delta = pickAnthropicDelta(json);
2101
+ const chunkUsage = pickUsage2(json);
2102
+ const chunkFinishReason = pickFinishReason2(json);
2103
+ usage = mergeUsage(usage, chunkUsage);
2104
+ if (chunkFinishReason) {
2105
+ finishReason = chunkFinishReason;
2106
+ }
2107
+ if (delta) {
2108
+ text += delta;
2109
+ callbacks.onToken?.(delta);
2110
+ }
2111
+ if (delta || chunkUsage || chunkFinishReason) {
2112
+ const chunk = {
2113
+ textDelta: delta,
2114
+ raw: json,
2115
+ usage: chunkUsage,
2116
+ finishReason: chunkFinishReason
2117
+ };
2118
+ callbacks.onChunk?.(chunk);
2119
+ }
2120
+ });
2121
+ const out = { text, usage, finishReason };
2122
+ callbacks.onComplete?.(out);
2123
+ return out;
2124
+ }
2125
+ };
2126
+ }
2127
+ async function completePassThrough(options, fetcher, path, request) {
2128
+ const response = await fetcher(buildURL(options.baseURL, path), {
2129
+ method: "POST",
2130
+ headers: buildHeaders2(options),
2131
+ body: JSON.stringify(cleanUndefined({
2132
+ ...options.defaultBody,
2133
+ ...request.body,
2134
+ model: options.model,
2135
+ system: request.systemPrompt,
2136
+ messages: [{ role: "user", content: request.prompt }],
2137
+ temperature: request.temperature,
2138
+ max_tokens: resolveMaxTokens(request.maxTokens, options.defaultMaxTokens),
2139
+ stream: false
2140
+ }))
2141
+ });
2142
+ if (!response.ok) {
2143
+ const message = await response.text();
2144
+ throw new Error(`HTTP ${response.status}: ${message}`);
2145
+ }
2146
+ const data = await response.json();
2147
+ const text = extractAnthropicText(data);
2148
+ const toolCalls = pickAnthropicToolCalls(data);
2149
+ if (!text && toolCalls.length === 0) {
2150
+ throw new Error("No assistant text in Anthropic-compatible response.");
2151
+ }
2152
+ return {
2153
+ text,
2154
+ raw: data,
2155
+ usage: pickUsage2(data),
2156
+ finishReason: pickFinishReason2(data),
2157
+ toolCalls: toolCalls.length > 0 ? toolCalls : undefined
2158
+ };
2159
+ }
2160
+ async function completeWithMCPToolLoop(options, fetcher, path, request) {
2161
+ const mcpToolset = await resolveMCPToolset(request.mcpClients);
2162
+ const tools = toAnthropicTools(toProviderFunctionTools(mcpToolset));
2163
+ const maxToolRounds = normalizeMaxToolRounds(request.maxToolRounds ?? options.defaultMaxToolRounds);
2164
+ let messages = [{ role: "user", content: request.prompt }];
2165
+ let aggregatedUsage;
2166
+ let finishReason;
2167
+ let lastPayload;
2168
+ const toolCalls = [];
2169
+ const toolExecutions = [];
2170
+ for (let round = 1;round <= maxToolRounds + 1; round += 1) {
2171
+ const response = await fetcher(buildURL(options.baseURL, path), {
2172
+ method: "POST",
2173
+ headers: buildHeaders2(options),
2174
+ body: JSON.stringify(cleanUndefined({
2175
+ ...options.defaultBody,
2176
+ ...request.body,
2177
+ model: options.model,
2178
+ system: request.systemPrompt,
2179
+ messages,
2180
+ temperature: request.temperature,
2181
+ max_tokens: resolveMaxTokens(request.maxTokens, options.defaultMaxTokens),
2182
+ tools,
2183
+ tool_choice: toAnthropicToolChoice(request.toolChoice),
2184
+ stream: false
2185
+ }))
2186
+ });
2187
+ if (!response.ok) {
2188
+ const message = await response.text();
2189
+ throw new Error(`HTTP ${response.status}: ${message}`);
2190
+ }
2191
+ const payload = await response.json();
2192
+ lastPayload = payload;
2193
+ aggregatedUsage = mergeUsage(aggregatedUsage, pickUsage2(payload));
2194
+ finishReason = pickFinishReason2(payload);
2195
+ const content = Array.isArray(payload.content) ? payload.content : [];
2196
+ const calledTools = pickAnthropicToolCalls(payload).filter((call) => call.type === "function");
2197
+ if (calledTools.length === 0) {
2198
+ return {
2199
+ text: extractAnthropicText(payload),
2200
+ raw: payload,
2201
+ usage: aggregatedUsage,
2202
+ finishReason,
2203
+ toolCalls: toolCalls.length > 0 ? toolCalls : undefined,
2204
+ toolExecutions: toolExecutions.length > 0 ? toolExecutions : undefined
2205
+ };
2206
+ }
2207
+ if (round > maxToolRounds) {
2208
+ throw new Error(`Tool call loop exceeded maxToolRounds (${maxToolRounds}).`);
2209
+ }
2210
+ const toolResultContent = [];
2211
+ const outputs = await executeMCPToolCalls(calledTools, mcpToolset, {
2212
+ round,
2213
+ request,
2214
+ provider: "anthropic-compatible",
2215
+ model: options.model
2216
+ });
2217
+ toolCalls.push(...outputs.map((entry) => entry.call));
2218
+ toolExecutions.push(...outputs.map((entry) => entry.execution));
2219
+ for (const entry of outputs) {
2220
+ toolResultContent.push({
2221
+ type: "tool_result",
2222
+ tool_use_id: entry.call.id,
2223
+ ...entry.call.error ? { is_error: true } : {},
2224
+ content: stringifyToolOutput(entry.call.error ? { error: entry.call.error } : entry.call.output)
2225
+ });
2226
+ }
2227
+ messages = [
2228
+ ...messages,
2229
+ { role: "assistant", content },
2230
+ { role: "user", content: toolResultContent }
2231
+ ];
2232
+ }
2233
+ return {
2234
+ text: extractAnthropicText(lastPayload ?? {}),
2235
+ raw: lastPayload,
2236
+ usage: aggregatedUsage,
2237
+ finishReason,
2238
+ toolCalls: toolCalls.length > 0 ? toolCalls : undefined,
2239
+ toolExecutions: toolExecutions.length > 0 ? toolExecutions : undefined
2240
+ };
2241
+ }
2242
+ function buildHeaders2(options) {
2243
+ return {
2244
+ "content-type": "application/json",
2245
+ ...options.apiKey ? { "x-api-key": options.apiKey } : {},
2246
+ "anthropic-version": options.version ?? DEFAULT_ANTHROPIC_VERSION,
2247
+ ...options.headers
2248
+ };
2249
+ }
2250
+ function resolveMaxTokens(value, fallback) {
2251
+ const requested = toFiniteNumber(value);
2252
+ if (requested !== undefined && requested > 0) {
2253
+ return Math.floor(requested);
2254
+ }
2255
+ const configured = toFiniteNumber(fallback);
2256
+ if (configured !== undefined && configured > 0) {
2257
+ return Math.floor(configured);
2258
+ }
2259
+ return DEFAULT_ANTHROPIC_MAX_TOKENS;
2260
+ }
2261
+ function extractAnthropicText(payload) {
2262
+ const content = payload.content;
2263
+ if (!Array.isArray(content)) {
2264
+ return "";
2265
+ }
2266
+ return content.map((part) => {
2267
+ if (!isRecord2(part) || part.type !== "text") {
2268
+ return "";
2269
+ }
2270
+ const text = part.text;
2271
+ return typeof text === "string" ? text : "";
2272
+ }).join("");
2273
+ }
2274
+ function pickAnthropicToolCalls(payload) {
2275
+ const content = payload.content;
2276
+ if (!Array.isArray(content)) {
2277
+ return [];
2278
+ }
2279
+ const calls = [];
2280
+ for (const part of content) {
2281
+ if (!isRecord2(part) || part.type !== "tool_use") {
2282
+ continue;
2283
+ }
2284
+ calls.push({
2285
+ id: pickString(part.id) ?? "",
2286
+ type: "function",
2287
+ name: pickString(part.name),
2288
+ arguments: part.input
2289
+ });
2290
+ }
2291
+ return calls;
2292
+ }
2293
+ function pickAnthropicDelta(payload) {
2294
+ const deltaObject = payload.delta;
2295
+ if (isRecord2(deltaObject) && typeof deltaObject.text === "string") {
2296
+ return deltaObject.text;
2297
+ }
2298
+ const contentBlock = payload.content_block;
2299
+ if (isRecord2(contentBlock) && typeof contentBlock.text === "string") {
2300
+ return contentBlock.text;
2301
+ }
2302
+ return "";
2303
+ }
2304
+ function pickUsage2(payload) {
2305
+ const fromUsage = extractUsageObject(payload.usage);
2306
+ if (fromUsage) {
2307
+ return fromUsage;
2308
+ }
2309
+ if (isRecord2(payload.message)) {
2310
+ const nested = extractUsageObject(payload.message.usage);
2311
+ if (nested) {
2312
+ return nested;
2313
+ }
2314
+ }
2315
+ if (isRecord2(payload.delta)) {
2316
+ const nested = extractUsageObject(payload.delta.usage);
2317
+ if (nested) {
2318
+ return nested;
2319
+ }
2320
+ }
2321
+ return;
2322
+ }
2323
+ function pickFinishReason2(payload) {
2324
+ const direct = payload.stop_reason;
2325
+ if (typeof direct === "string" && direct.length > 0) {
2326
+ return direct;
2327
+ }
2328
+ if (isRecord2(payload.delta)) {
2329
+ const reason = payload.delta.stop_reason;
2330
+ if (typeof reason === "string" && reason.length > 0) {
2331
+ return reason;
2332
+ }
2333
+ }
2334
+ if (isRecord2(payload.message)) {
2335
+ const reason = payload.message.stop_reason;
2336
+ if (typeof reason === "string" && reason.length > 0) {
2337
+ return reason;
2338
+ }
2339
+ }
2340
+ return;
2341
+ }
2342
+ function extractUsageObject(value) {
2343
+ if (!isRecord2(value)) {
2344
+ return;
2345
+ }
2346
+ return {
2347
+ inputTokens: toFiniteNumber(value.input_tokens ?? value.prompt_tokens),
2348
+ outputTokens: toFiniteNumber(value.output_tokens ?? value.completion_tokens),
2349
+ totalTokens: toFiniteNumber(value.total_tokens)
2350
+ };
2351
+ }
2352
+ function toAnthropicTools(tools) {
2353
+ if (!Array.isArray(tools) || tools.length === 0) {
2354
+ return;
2355
+ }
2356
+ return tools.filter((tool) => tool.type === "function" && isRecord2(tool.function)).map((tool) => {
2357
+ const functionTool = tool.function;
2358
+ return {
2359
+ name: functionTool.name,
2360
+ description: functionTool.description,
2361
+ input_schema: functionTool.parameters ?? { type: "object", properties: {} }
2362
+ };
2363
+ });
2364
+ }
2365
+ function toAnthropicToolChoice(value) {
2366
+ if (value === undefined) {
2367
+ return;
2368
+ }
2369
+ if (value === "required") {
2370
+ return { type: "any" };
2371
+ }
2372
+ if (isRecord2(value) && value.type === "function") {
2373
+ const maybeFn = value.function;
2374
+ if (isRecord2(maybeFn)) {
2375
+ const name = pickString(maybeFn.name);
2376
+ if (name) {
2377
+ return { type: "tool", name };
2378
+ }
2379
+ }
2380
+ }
2381
+ return value;
2382
+ }
2383
+ async function streamViaComplete2(callbacks, complete) {
2384
+ callbacks.onStart?.();
2385
+ const response = await complete();
2386
+ if (response.text.length > 0) {
2387
+ callbacks.onToken?.(response.text);
2388
+ }
2389
+ callbacks.onChunk?.({
2390
+ textDelta: response.text,
2391
+ raw: response.raw,
2392
+ done: true,
2393
+ usage: response.usage,
2394
+ finishReason: response.finishReason
2395
+ });
2396
+ callbacks.onComplete?.(response);
2397
+ return response;
2398
+ }
2399
+
2400
+ // src/providers/registry.ts
2401
+ class InMemoryProviderRegistry {
2402
+ factories = new Map;
2403
+ register(kind, factory) {
2404
+ this.factories.set(kind, factory);
2405
+ }
2406
+ create(kind, options) {
2407
+ const factory = this.factories.get(kind);
2408
+ if (!factory) {
2409
+ throw new Error(`Unknown provider kind: ${kind}`);
2410
+ }
2411
+ return factory(options);
2412
+ }
2413
+ has(kind) {
2414
+ return this.factories.has(kind);
2415
+ }
2416
+ list() {
2417
+ return [...this.factories.keys()].sort();
2418
+ }
2419
+ }
2420
+ function createProviderRegistry() {
2421
+ return new InMemoryProviderRegistry;
2422
+ }
2423
+ function registerBuiltinProviders(registry) {
2424
+ registry.register("openai-compatible", createOpenAICompatibleAdapter);
2425
+ registry.register("anthropic-compatible", createAnthropicCompatibleAdapter);
2426
+ return registry;
2427
+ }
2428
+ function createDefaultProviderRegistry() {
2429
+ return registerBuiltinProviders(createProviderRegistry());
2430
+ }
2431
+ function createModelAdapter(config, registry = createDefaultProviderRegistry()) {
2432
+ const providerOptions = buildProviderOptions(config);
2433
+ return registry.create(config.provider, providerOptions);
2434
+ }
2435
+ function buildProviderOptions(config) {
2436
+ const transport = {
2437
+ ...config.transport,
2438
+ baseURL: config.transport?.baseURL ?? config.baseURL,
2439
+ apiKey: config.transport?.apiKey ?? config.apiKey
2440
+ };
2441
+ if (config.provider === "openai-compatible") {
2442
+ const options = {
2443
+ model: config.model,
2444
+ baseURL: transport.baseURL ?? "https://api.openai.com",
2445
+ apiKey: transport.apiKey,
2446
+ path: transport.path,
2447
+ headers: transport.headers,
2448
+ defaultBody: transport.defaultBody,
2449
+ fetcher: transport.fetcher
2450
+ };
2451
+ return options;
2452
+ }
2453
+ if (config.provider === "anthropic-compatible") {
2454
+ const options = {
2455
+ model: config.model,
2456
+ baseURL: transport.baseURL ?? "https://api.anthropic.com",
2457
+ apiKey: transport.apiKey,
2458
+ path: transport.path,
2459
+ headers: transport.headers,
2460
+ version: transport.version,
2461
+ defaultBody: transport.defaultBody,
2462
+ fetcher: transport.fetcher
2463
+ };
2464
+ return options;
2465
+ }
2466
+ return {
2467
+ model: config.model,
2468
+ ...transport,
2469
+ ...config.options ?? {}
2470
+ };
2471
+ }
2472
+
2473
+ // src/structured.ts
2474
+ import { jsonrepair as jsonrepair3 } from "jsonrepair";
2475
+
2476
+ // src/outdent.ts
2477
+ var DEFAULT_OPTIONS = {
2478
+ trimLeadingNewline: true,
2479
+ trimTrailingNewline: true,
2480
+ newline: null
2481
+ };
2482
+ function isIndentChar(char) {
2483
+ return char === " " || char === "\t";
2484
+ }
2485
+ function normalizeNewlines(text, newline) {
2486
+ let result = "";
2487
+ for (let index = 0;index < text.length; index += 1) {
2488
+ const char = text[index];
2489
+ if (char === "\r") {
2490
+ if (text[index + 1] === `
2491
+ `) {
2492
+ index += 1;
2493
+ }
2494
+ result += newline;
2495
+ continue;
2496
+ }
2497
+ if (char === `
2498
+ `) {
2499
+ result += newline;
2500
+ continue;
2501
+ }
2502
+ result += char;
2503
+ }
2504
+ return result;
2505
+ }
2506
+ function detectIndentationFromFirstSegment(segment) {
2507
+ for (let index = 0;index < segment.length; index += 1) {
2508
+ if (segment.charAt(index) !== `
2509
+ `) {
2510
+ continue;
2511
+ }
2512
+ let cursor = index + 1;
2513
+ let indentation = 0;
2514
+ while (cursor < segment.length && isIndentChar(segment.charAt(cursor))) {
2515
+ indentation += 1;
2516
+ cursor += 1;
2517
+ }
2518
+ if (cursor === segment.length) {
2519
+ return indentation;
2520
+ }
2521
+ if (segment.charAt(cursor) !== `
2522
+ `) {
2523
+ return indentation;
2524
+ }
2525
+ }
2526
+ return 0;
2527
+ }
2528
+ function removeIndentAfterNewlines(segment, indentation) {
2529
+ if (indentation <= 0) {
2530
+ return segment;
2531
+ }
2532
+ let result = "";
2533
+ for (let index = 0;index < segment.length; index += 1) {
2534
+ const char = segment.charAt(index);
2535
+ result += char;
2536
+ if (char !== `
2537
+ `) {
2538
+ continue;
2539
+ }
2540
+ let removed = 0;
2541
+ while (index + 1 < segment.length && removed < indentation && isIndentChar(segment.charAt(index + 1))) {
2542
+ index += 1;
2543
+ removed += 1;
2544
+ }
2545
+ }
2546
+ return result;
2547
+ }
2548
+ function trimLeadingNewline(segment) {
2549
+ let index = 0;
2550
+ while (index < segment.length && isIndentChar(segment.charAt(index))) {
2551
+ index += 1;
2552
+ }
2553
+ if (segment.charAt(index) === `
2554
+ `) {
2555
+ return segment.slice(index + 1);
2556
+ }
2557
+ return segment;
2558
+ }
2559
+ function trimTrailingNewline(segment) {
2560
+ let end = segment.length;
2561
+ while (end > 0 && isIndentChar(segment.charAt(end - 1))) {
2562
+ end -= 1;
2563
+ }
2564
+ if (end > 0 && segment.charAt(end - 1) === `
2565
+ `) {
2566
+ return segment.slice(0, end - 1);
2567
+ }
2568
+ return segment;
2569
+ }
2570
+ function processSegments(inputSegments, options) {
2571
+ const segments = inputSegments.map((segment) => {
2572
+ if (typeof options.newline === "string") {
2573
+ return normalizeNewlines(segment, options.newline);
2574
+ }
2575
+ return segment;
2576
+ });
2577
+ const indentation = detectIndentationFromFirstSegment(segments[0] ?? "");
2578
+ const outdented = segments.map((segment) => removeIndentAfterNewlines(segment, indentation));
2579
+ if (options.trimLeadingNewline && outdented.length > 0) {
2580
+ outdented[0] = trimLeadingNewline(outdented[0] ?? "");
2581
+ }
2582
+ if (options.trimTrailingNewline && outdented.length > 0) {
2583
+ const lastIndex = outdented.length - 1;
2584
+ outdented[lastIndex] = trimTrailingNewline(outdented[lastIndex] ?? "");
2585
+ }
2586
+ return outdented;
2587
+ }
2588
+ function concatTemplate(strings, values) {
2589
+ let output = "";
2590
+ for (let index = 0;index < strings.length; index += 1) {
2591
+ output += strings[index] ?? "";
2592
+ if (index < values.length) {
2593
+ output += String(values[index]);
2594
+ }
2595
+ }
2596
+ return output;
2597
+ }
2598
+ function createOutdent(options = {}) {
2599
+ const resolvedOptions = {
2600
+ ...DEFAULT_OPTIONS,
2601
+ ...options
2602
+ };
2603
+ const outdent = (strings, ...values) => {
2604
+ const processed = processSegments(strings, resolvedOptions);
2605
+ return concatTemplate(processed, values);
2606
+ };
2607
+ outdent.string = (input) => {
2608
+ return processSegments([input], resolvedOptions)[0] ?? "";
2609
+ };
2610
+ return outdent;
2611
+ }
2612
+
2613
+ // src/parse.ts
2614
+ import { jsonrepair as jsonrepair2 } from "jsonrepair";
2615
+ function parseLLMOutput(output, schema, options = {}) {
2616
+ const sanitized = sanitizeThink(output);
2617
+ const parseOptions = {
2618
+ repair: options.repair ?? true,
2619
+ maxCandidates: options.maxCandidates ?? 5,
2620
+ acceptArrays: options.acceptArrays ?? true,
2621
+ extraction: options.extraction,
2622
+ onTrace: options.onTrace
2623
+ };
2624
+ const candidates = extractJsonCandidates(sanitized.visibleText, {
2625
+ maxCandidates: parseOptions.maxCandidates,
2626
+ acceptArrays: parseOptions.acceptArrays,
2627
+ allowRepairHints: parseOptions.repair,
2628
+ heuristics: parseOptions.extraction
2629
+ });
2630
+ emitTrace(parseOptions.onTrace, {
2631
+ stage: "extract",
2632
+ level: "info",
2633
+ message: `Extracted ${candidates.length} candidate(s).`,
2634
+ details: {
2635
+ maxCandidates: parseOptions.maxCandidates,
2636
+ thinkBlocks: sanitized.thinkBlocks.length,
2637
+ thinkDiagnostics: sanitized.diagnostics
2638
+ }
2639
+ });
2640
+ const errors = [];
2641
+ const diagnostics = [];
2642
+ let bestIssues = [];
2643
+ let bestCandidate = candidates[0] ?? null;
2644
+ let bestParsed = null;
2645
+ let bestRepaired = null;
2646
+ for (const candidate of candidates) {
2647
+ const parseAttempt = parseAttemptFromHint(candidate.parseHint, parseOptions.repair) ?? tryParseJsonCandidate(candidate.content, parseOptions.repair);
2648
+ if (!parseAttempt.success) {
2649
+ const diagnostic = {
2650
+ candidateId: candidate.id,
2651
+ source: candidate.source,
2652
+ usedRepair: parseAttempt.usedRepair,
2653
+ parseSuccess: false,
2654
+ validationSuccess: false,
2655
+ selected: false,
2656
+ stage: parseAttempt.stage,
2657
+ message: parseAttempt.error
2658
+ };
2659
+ diagnostics.push(diagnostic);
2660
+ errors.push({
2661
+ stage: parseAttempt.stage,
2662
+ message: parseAttempt.error,
2663
+ candidateId: candidate.id
2664
+ });
2665
+ emitTrace(parseOptions.onTrace, {
2666
+ stage: parseAttempt.stage,
2667
+ level: "error",
2668
+ message: parseAttempt.error,
2669
+ candidateId: candidate.id
2670
+ });
2671
+ continue;
2672
+ }
2673
+ emitTrace(parseOptions.onTrace, {
2674
+ stage: "parse",
2675
+ level: "info",
2676
+ message: parseAttempt.usedRepair ? "Candidate parsed after repair." : "Candidate parsed without repair.",
2677
+ candidateId: candidate.id,
2678
+ details: {
2679
+ usedRepair: parseAttempt.usedRepair
2680
+ }
2681
+ });
2682
+ const validated = schema.safeParse(parseAttempt.parsed);
2683
+ if (validated.success) {
2684
+ const selectedDiagnostic = {
2685
+ candidateId: candidate.id,
2686
+ source: candidate.source,
2687
+ usedRepair: parseAttempt.usedRepair,
2688
+ parseSuccess: true,
2689
+ validationSuccess: true,
2690
+ selected: true,
2691
+ stage: "success"
2692
+ };
2693
+ diagnostics.push(selectedDiagnostic);
2694
+ emitTrace(parseOptions.onTrace, {
2695
+ stage: "result",
2696
+ level: "info",
2697
+ message: `Validation succeeded on candidate ${candidate.id}.`,
2698
+ candidateId: candidate.id
2699
+ });
2700
+ return {
2701
+ success: true,
2702
+ data: validated.data,
2703
+ raw: output,
2704
+ sanitizedRaw: sanitized.visibleText,
2705
+ thinkBlocks: sanitized.thinkBlocks,
2706
+ thinkDiagnostics: sanitized.diagnostics,
2707
+ parsed: parseAttempt.parsed,
2708
+ candidate,
2709
+ repaired: parseAttempt.repaired,
2710
+ candidates,
2711
+ diagnostics,
2712
+ errors,
2713
+ zodIssues: []
2714
+ };
2715
+ }
2716
+ const issues = validated.error.issues;
2717
+ const message = formatZodIssues(issues);
2718
+ const validationDiagnostic = {
2719
+ candidateId: candidate.id,
2720
+ source: candidate.source,
2721
+ usedRepair: parseAttempt.usedRepair,
2722
+ parseSuccess: true,
2723
+ validationSuccess: false,
2724
+ selected: false,
2725
+ stage: "validate",
2726
+ message,
2727
+ zodIssues: issues
2728
+ };
2729
+ diagnostics.push(validationDiagnostic);
2730
+ if (bestIssues.length === 0 || issues.length < bestIssues.length) {
2731
+ bestIssues = issues;
2732
+ bestCandidate = candidate;
2733
+ bestParsed = parseAttempt.parsed;
2734
+ bestRepaired = parseAttempt.repaired;
2735
+ }
2736
+ errors.push({
2737
+ stage: "validate",
2738
+ message,
2739
+ candidateId: candidate.id,
2740
+ details: issues
2741
+ });
2742
+ emitTrace(parseOptions.onTrace, {
2743
+ stage: "validate",
2744
+ level: "error",
2745
+ message: `Validation failed on candidate ${candidate.id}.`,
2746
+ candidateId: candidate.id,
2747
+ details: {
2748
+ issuesCount: issues.length
2749
+ }
2750
+ });
2751
+ }
2752
+ if (candidates.length === 0) {
2753
+ const message = "No JSON candidate was extracted.";
2754
+ errors.push({
2755
+ stage: "extract",
2756
+ message
2757
+ });
2758
+ emitTrace(parseOptions.onTrace, {
2759
+ stage: "extract",
2760
+ level: "error",
2761
+ message
2762
+ });
2763
+ }
2764
+ markSelectedDiagnostic(diagnostics, bestCandidate?.id ?? null);
2765
+ emitTrace(parseOptions.onTrace, {
2766
+ stage: "result",
2767
+ level: "error",
2768
+ message: "No candidate could be validated.",
2769
+ details: {
2770
+ candidateCount: candidates.length,
2771
+ errors: errors.length
2772
+ }
2773
+ });
2774
+ return {
2775
+ success: false,
2776
+ data: null,
2777
+ raw: output,
2778
+ sanitizedRaw: sanitized.visibleText,
2779
+ thinkBlocks: sanitized.thinkBlocks,
2780
+ thinkDiagnostics: sanitized.diagnostics,
2781
+ parsed: bestParsed,
2782
+ candidate: bestCandidate,
2783
+ repaired: bestRepaired,
2784
+ candidates,
2785
+ diagnostics,
2786
+ errors,
2787
+ zodIssues: bestIssues
2788
+ };
2789
+ }
2790
+ function tryParseJsonCandidate(input, allowRepair) {
2791
+ try {
2792
+ return {
2793
+ success: true,
2794
+ parsed: JSON.parse(input),
2795
+ repaired: null,
2796
+ usedRepair: false,
2797
+ stage: "parse",
2798
+ error: ""
2799
+ };
2800
+ } catch (directError) {
2801
+ if (!allowRepair) {
2802
+ return {
2803
+ success: false,
2804
+ parsed: null,
2805
+ repaired: null,
2806
+ usedRepair: false,
2807
+ stage: "parse",
2808
+ error: toErrorMessage(directError)
2809
+ };
2810
+ }
2811
+ }
2812
+ let repaired;
2813
+ try {
2814
+ repaired = jsonrepair2(input);
2815
+ } catch (repairError) {
2816
+ return {
2817
+ success: false,
2818
+ parsed: null,
2819
+ repaired: null,
2820
+ usedRepair: true,
2821
+ stage: "repair",
2822
+ error: toErrorMessage(repairError)
2823
+ };
2824
+ }
2825
+ try {
2826
+ return {
2827
+ success: true,
2828
+ parsed: JSON.parse(repaired),
2829
+ repaired,
2830
+ usedRepair: true,
2831
+ stage: "parse",
2832
+ error: ""
2833
+ };
2834
+ } catch (parseError) {
2835
+ return {
2836
+ success: false,
2837
+ parsed: null,
2838
+ repaired,
2839
+ usedRepair: true,
2840
+ stage: "parse",
2841
+ error: toErrorMessage(parseError)
2842
+ };
2843
+ }
2844
+ }
2845
+ function parseAttemptFromHint(hint, allowRepair) {
2846
+ if (!hint) {
2847
+ return null;
2848
+ }
2849
+ if (hint.success) {
2850
+ if (hint.usedRepair && !allowRepair) {
2851
+ return null;
2852
+ }
2853
+ return {
2854
+ success: true,
2855
+ parsed: hint.parsed,
2856
+ repaired: hint.repaired,
2857
+ usedRepair: hint.usedRepair,
2858
+ stage: "parse",
2859
+ error: ""
2860
+ };
2861
+ }
2862
+ if (hint.usedRepair) {
2863
+ if (!allowRepair) {
2864
+ return null;
2865
+ }
2866
+ return {
2867
+ success: false,
2868
+ parsed: null,
2869
+ repaired: hint.repaired,
2870
+ usedRepair: true,
2871
+ stage: hint.stage,
2872
+ error: hint.error
2873
+ };
2874
+ }
2875
+ if (allowRepair) {
2876
+ return null;
2877
+ }
2878
+ return {
2879
+ success: false,
2880
+ parsed: null,
2881
+ repaired: null,
2882
+ usedRepair: false,
2883
+ stage: "parse",
2884
+ error: hint.error
2885
+ };
2886
+ }
2887
+ function markSelectedDiagnostic(diagnostics, selectedCandidateId) {
2888
+ if (!selectedCandidateId) {
2889
+ return;
2890
+ }
2891
+ for (const diagnostic of diagnostics) {
2892
+ if (diagnostic.candidateId === selectedCandidateId) {
2893
+ diagnostic.selected = true;
2894
+ return;
2895
+ }
2896
+ }
2897
+ }
2898
+ function emitTrace(onTrace, event) {
2899
+ onTrace?.(event);
2900
+ }
2901
+ function formatZodIssues(issues) {
2902
+ if (issues.length === 0) {
2903
+ return "Validation failed without details.";
2904
+ }
2905
+ return issues.map((issue) => {
2906
+ const path = issue.path.length > 0 ? issue.path.join(".") : "<root>";
2907
+ return `${path}: ${issue.message}`;
2908
+ }).join(`
2909
+ `);
2910
+ }
2911
+
2912
+ // src/utils/debug-colors.ts
2913
+ var ANSI = {
2914
+ reset: "\x1B[0m",
2915
+ bold: "\x1B[1m",
2916
+ cyan: "\x1B[36m",
2917
+ yellow: "\x1B[33m",
2918
+ green: "\x1B[32m",
2919
+ red: "\x1B[31m",
2920
+ dim: "\x1B[2m"
2921
+ };
2922
+ function color(config, text, tone) {
2923
+ if (!config.colors) {
2924
+ return text;
2925
+ }
2926
+ return `${ANSI[tone]}${text}${ANSI.reset}`;
2927
+ }
2928
+ function dim(config, text) {
2929
+ if (!config.colors) {
2930
+ return text;
2931
+ }
2932
+ return `${ANSI.dim}${text}${ANSI.reset}`;
2933
+ }
2934
+ function title(config, text) {
2935
+ if (!config.colors) {
2936
+ return text;
2937
+ }
2938
+ return `${ANSI.bold}${text}${ANSI.reset}`;
2939
+ }
2940
+
2941
+ // src/structured.ts
2942
+ class StructuredParseError extends Error {
2943
+ name = "StructuredParseError";
2944
+ raw;
2945
+ thinkBlocks;
2946
+ candidates;
2947
+ zodIssues;
2948
+ repairLog;
2949
+ attempt;
2950
+ constructor(input) {
2951
+ super(input.message ?? `Structured parsing failed after ${input.attempt} attempt(s).`);
2952
+ this.raw = input.raw;
2953
+ this.thinkBlocks = input.thinkBlocks;
2954
+ this.candidates = input.candidates;
2955
+ this.zodIssues = input.zodIssues;
2956
+ this.repairLog = input.repairLog;
2957
+ this.attempt = input.attempt;
2958
+ }
2959
+ }
2960
+ var DEFAULT_STRUCTURED_OBJECT_INSTRUCTION = "Return exactly one strict JSON object.";
2961
+ var DEFAULT_STRUCTURED_STYLE_INSTRUCTION = "No prose. No markdown.";
2962
+ var DEFAULT_SELF_HEAL_PROMPT_TEXT = {
2963
+ fixInstruction: "Fix the following output so it validates against the schema.",
2964
+ returnInstruction: "Return only the corrected JSON object.",
2965
+ noIssuesMessage: "No detailed validation issues",
2966
+ validationErrorsLabel: "Validation errors:",
2967
+ rawOutputLabel: "Raw output to fix:",
2968
+ contextLabel: "Self-heal context JSON:"
2969
+ };
2970
+ var DEFAULT_SELF_HEAL_FIX_INSTRUCTION = DEFAULT_SELF_HEAL_PROMPT_TEXT.fixInstruction;
2971
+ var DEFAULT_SELF_HEAL_RETURN_INSTRUCTION = DEFAULT_SELF_HEAL_PROMPT_TEXT.returnInstruction;
2972
+ var DEFAULT_SELF_HEAL_NO_ISSUES_MESSAGE = DEFAULT_SELF_HEAL_PROMPT_TEXT.noIssuesMessage;
2973
+ var DEFAULT_SELF_HEAL_VALIDATION_LABEL = DEFAULT_SELF_HEAL_PROMPT_TEXT.validationErrorsLabel;
2974
+ var DEFAULT_SELF_HEAL_RAW_OUTPUT_LABEL = DEFAULT_SELF_HEAL_PROMPT_TEXT.rawOutputLabel;
2975
+ var DEFAULT_SELF_HEAL_CONTEXT_LABEL = DEFAULT_SELF_HEAL_PROMPT_TEXT.contextLabel;
2976
+ var DEFAULT_SELF_HEAL_PROTOCOL = "extrait.self-heal.v2";
2977
+ var DEFAULT_SELF_HEAL_MAX_CONTEXT_CHARS = 12000;
2978
+ var DEFAULT_SELF_HEAL_STOP_ON_NO_PROGRESS = true;
2979
+ var DEFAULT_SELF_HEAL_MAX_ERRORS = 8;
2980
+ var DEFAULT_SELF_HEAL_MAX_DIAGNOSTICS = 8;
2981
+ var structuredOutdent = createOutdent({
2982
+ trimLeadingNewline: true,
2983
+ trimTrailingNewline: true,
2984
+ newline: `
2985
+ `
2986
+ });
2987
+ var DEFAULT_STRICT_PARSE_OPTIONS = {
2988
+ repair: false,
2989
+ maxCandidates: 3,
2990
+ acceptArrays: true
2991
+ };
2992
+ var DEFAULT_LOOSE_PARSE_OPTIONS = {
2993
+ repair: true,
2994
+ maxCandidates: 5,
2995
+ acceptArrays: true
2996
+ };
2997
+ var DEFAULT_SELF_HEAL_BY_MODE = {
2998
+ loose: { enabled: true, maxAttempts: 1 },
2999
+ strict: { enabled: false, maxAttempts: 0 }
3000
+ };
3001
+ function buildDefaultStructuredPrompt(task, options = {}) {
3002
+ const objectInstruction = resolvePromptLine(options.objectInstruction, DEFAULT_STRUCTURED_OBJECT_INSTRUCTION);
3003
+ const styleInstruction = resolvePromptLine(options.styleInstruction, DEFAULT_STRUCTURED_STYLE_INSTRUCTION);
3004
+ return [task.trim(), "", objectInstruction, styleInstruction].join(`
3005
+ `);
3006
+ }
3007
+ function buildSelfHealPrompt(input) {
3008
+ const text = resolveSelfHealPromptText(input.text);
3009
+ const issueText = input.issues.length > 0 ? formatZodIssues(input.issues) : text.noIssuesMessage;
3010
+ const outputFormat = withFormat(input.schema, {
3011
+ schemaInstruction: input.schemaInstruction
3012
+ });
3013
+ const maxContextChars = normalizePositiveInt(input.maxContextChars, DEFAULT_SELF_HEAL_MAX_CONTEXT_CHARS);
3014
+ const selectedOutput = input.selectedOutput ?? input.rawOutput;
3015
+ const contextPayload = {
3016
+ protocol: DEFAULT_SELF_HEAL_PROTOCOL,
3017
+ attempt: input.attempt,
3018
+ previousAttempt: input.previousAttempt,
3019
+ selectedInput: input.selectedInput ?? "raw",
3020
+ issueSummary: issueText,
3021
+ validationIssues: input.issues.map((issue) => ({
3022
+ path: formatIssuePath(issue.path),
3023
+ code: issue.code,
3024
+ message: issue.message,
3025
+ expected: "expected" in issue ? issue.expected : undefined,
3026
+ received: "received" in issue ? issue.received : undefined
3027
+ })),
3028
+ parserErrors: (input.parserErrors ?? []).map((error) => ({
3029
+ stage: error.stage,
3030
+ message: error.message,
3031
+ candidateId: error.candidateId
3032
+ })),
3033
+ diagnostics: (input.diagnostics ?? []).map((diagnostic) => ({
3034
+ candidateId: diagnostic.candidateId,
3035
+ stage: diagnostic.stage,
3036
+ usedRepair: diagnostic.usedRepair,
3037
+ message: diagnostic.message
3038
+ })),
3039
+ repairLog: input.repairLog ?? [],
3040
+ selectedOutput: truncateForPrompt(selectedOutput, maxContextChars),
3041
+ sanitizedOutput: input.sanitizedOutput ? truncateForPrompt(input.sanitizedOutput, maxContextChars) : undefined,
3042
+ rawOutput: truncateForPrompt(input.rawOutput, maxContextChars)
3043
+ };
3044
+ return [
3045
+ text.fixInstruction,
3046
+ text.returnInstruction,
3047
+ "",
3048
+ outputFormat,
3049
+ "",
3050
+ text.validationErrorsLabel,
3051
+ issueText,
3052
+ "",
3053
+ text.rawOutputLabel,
3054
+ truncateForPrompt(input.rawOutput, maxContextChars),
3055
+ "",
3056
+ text.contextLabel,
3057
+ JSON.stringify(contextPayload, null, 2)
3058
+ ].join(`
3059
+ `);
3060
+ }
3061
+ async function structured(adapter, schemaOrOptions, promptInput, callOptions) {
3062
+ const normalized = normalizeStructuredInput(schemaOrOptions, promptInput, callOptions);
3063
+ const mode = normalized.mode ?? "loose";
3064
+ const selfHealConfig = normalizeSelfHealConfig(normalized.selfHeal, mode);
3065
+ const parseOptions = mergeParseOptions(mode, normalized.parse);
3066
+ const streamConfig = normalizeStreamConfig(normalized.stream);
3067
+ const debugConfig = normalizeDebugConfig(normalized.debug);
3068
+ const attempts = [];
3069
+ const useOutdent = normalized.outdent ?? true;
3070
+ const resolvedPrompt = applyPromptOutdent(resolvePrompt(normalized.prompt, { mode }), useOutdent);
3071
+ const resolvedSystemPrompt = applyOutdentToOptionalPrompt(normalized.systemPrompt, useOutdent);
3072
+ const prompt = shouldInjectFormat(resolvedPrompt.prompt, normalized.schemaInstruction) ? formatPrompt(normalized.schema, resolvedPrompt.prompt, {
3073
+ schemaInstruction: normalized.schemaInstruction
3074
+ }) : resolvedPrompt.prompt.trim();
3075
+ const systemPrompt = mergeSystemPrompts(resolvedPrompt.systemPrompt, resolvedSystemPrompt);
3076
+ const first = await executeAttempt(adapter, {
3077
+ prompt,
3078
+ schema: normalized.schema,
3079
+ parseOptions,
3080
+ stream: streamConfig,
3081
+ request: normalized.request,
3082
+ systemPrompt,
3083
+ observe: normalized.observe,
3084
+ debug: debugConfig,
3085
+ attemptNumber: 1,
3086
+ selfHeal: false,
3087
+ selfHealEnabled: selfHealConfig.enabled
3088
+ });
3089
+ attempts.push(first.trace);
3090
+ if (first.trace.success) {
3091
+ return buildSuccessResult(first.trace.parsed.data, attempts);
3092
+ }
3093
+ if (!selfHealConfig.enabled) {
3094
+ throw toStructuredError(attempts.at(-1));
3095
+ }
3096
+ for (let index = 0;index < selfHealConfig.maxAttempts; index += 1) {
3097
+ const previous = attempts.at(-1);
3098
+ if (!previous) {
3099
+ break;
3100
+ }
3101
+ const attemptNumber = index + 2;
3102
+ emitObserve(normalized.observe, {
3103
+ stage: "self-heal",
3104
+ attempt: attemptNumber,
3105
+ selfHeal: true,
3106
+ message: "Starting self-heal attempt.",
3107
+ details: {
3108
+ previousIssues: previous.zodIssues.length
3109
+ }
3110
+ });
3111
+ const selfHealSource = resolveSelfHealSource(previous);
3112
+ const repairPrompt = buildSelfHealPrompt({
3113
+ rawOutput: previous.raw,
3114
+ issues: previous.zodIssues,
3115
+ schema: normalized.schema,
3116
+ schemaInstruction: normalized.schemaInstruction,
3117
+ selectedOutput: selfHealSource.text,
3118
+ selectedInput: selfHealSource.kind,
3119
+ sanitizedOutput: previous.parsed.sanitizedRaw,
3120
+ parserErrors: previous.parsed.errors.slice(0, DEFAULT_SELF_HEAL_MAX_ERRORS).map((error) => ({
3121
+ stage: error.stage,
3122
+ message: error.message,
3123
+ candidateId: error.candidateId
3124
+ })),
3125
+ diagnostics: previous.parsed.diagnostics.slice(0, DEFAULT_SELF_HEAL_MAX_DIAGNOSTICS).map((diagnostic) => ({
3126
+ candidateId: diagnostic.candidateId,
3127
+ stage: diagnostic.stage,
3128
+ usedRepair: diagnostic.usedRepair,
3129
+ message: diagnostic.message
3130
+ })),
3131
+ repairLog: previous.repairLog,
3132
+ attempt: attemptNumber,
3133
+ previousAttempt: previous.attempt,
3134
+ maxContextChars: selfHealConfig.maxContextChars
3135
+ });
3136
+ const healed = await executeAttempt(adapter, {
3137
+ prompt: repairPrompt,
3138
+ schema: normalized.schema,
3139
+ parseOptions,
3140
+ stream: streamConfig,
3141
+ request: normalized.request,
3142
+ systemPrompt,
3143
+ observe: normalized.observe,
3144
+ debug: debugConfig,
3145
+ attemptNumber,
3146
+ selfHeal: true,
3147
+ selfHealEnabled: selfHealConfig.enabled
3148
+ });
3149
+ attempts.push(healed.trace);
3150
+ if (healed.trace.success) {
3151
+ return buildSuccessResult(healed.trace.parsed.data, attempts);
3152
+ }
3153
+ if (selfHealConfig.stopOnNoProgress && isSelfHealStalled(previous, healed.trace)) {
3154
+ emitObserve(normalized.observe, {
3155
+ stage: "self-heal",
3156
+ attempt: attemptNumber,
3157
+ selfHeal: true,
3158
+ message: "Stopping self-heal: no progress detected.",
3159
+ details: {
3160
+ previousIssues: previous.zodIssues.length,
3161
+ currentIssues: healed.trace.zodIssues.length
3162
+ }
3163
+ });
3164
+ break;
3165
+ }
3166
+ }
3167
+ throw toStructuredError(attempts.at(-1));
3168
+ }
3169
+ function normalizeStructuredInput(schemaOrOptions, promptInput, callOptions) {
3170
+ if (isStructuredOptions(schemaOrOptions)) {
3171
+ return schemaOrOptions;
3172
+ }
3173
+ if (!promptInput) {
3174
+ throw new Error("Missing prompt in structured(adapter, schema, prompt, options?) call.");
3175
+ }
3176
+ return {
3177
+ ...callOptions ?? {},
3178
+ schema: schemaOrOptions,
3179
+ prompt: promptInput
3180
+ };
3181
+ }
3182
+ function isStructuredOptions(value) {
3183
+ return typeof value === "object" && value !== null && "schema" in value && "prompt" in value;
3184
+ }
3185
+ function resolvePrompt(prompt, context) {
3186
+ const resolved = typeof prompt === "function" ? prompt(context) : prompt;
3187
+ return normalizePromptValue(resolved, context);
3188
+ }
3189
+ function normalizePromptValue(value, context) {
3190
+ if (typeof value === "string") {
3191
+ return {
3192
+ prompt: value
3193
+ };
3194
+ }
3195
+ if (isPromptResolver(value)) {
3196
+ return normalizePromptPayload(value.resolvePrompt(context));
3197
+ }
3198
+ return normalizePromptPayload(value);
3199
+ }
3200
+ function isPromptResolver(value) {
3201
+ return typeof value === "object" && value !== null && "resolvePrompt" in value && typeof value.resolvePrompt === "function";
3202
+ }
3203
+ function normalizePromptPayload(value) {
3204
+ if (typeof value.prompt !== "string") {
3205
+ throw new Error("Structured prompt payload must include a string prompt.");
3206
+ }
3207
+ return {
3208
+ prompt: value.prompt,
3209
+ systemPrompt: typeof value.systemPrompt === "string" ? value.systemPrompt : undefined
3210
+ };
3211
+ }
3212
+ function applyPromptOutdent(payload, enabled) {
3213
+ if (!enabled) {
3214
+ return payload;
3215
+ }
3216
+ return {
3217
+ prompt: structuredOutdent.string(payload.prompt),
3218
+ systemPrompt: applyOutdentToOptionalPrompt(payload.systemPrompt, enabled)
3219
+ };
3220
+ }
3221
+ function applyOutdentToOptionalPrompt(value, enabled) {
3222
+ if (!enabled || typeof value !== "string") {
3223
+ return value;
3224
+ }
3225
+ return structuredOutdent.string(value);
3226
+ }
3227
+ function mergeSystemPrompts(primary, secondary) {
3228
+ const prompts = [primary, secondary].map((value) => value?.trim()).filter((value) => Boolean(value));
3229
+ if (prompts.length === 0) {
3230
+ return;
3231
+ }
3232
+ return prompts.join(`
3233
+
3234
+ `);
3235
+ }
3236
+ function shouldInjectFormat(prompt, schemaInstruction) {
3237
+ const instruction = resolveSchemaInstruction(schemaInstruction);
3238
+ return !prompt.trimStart().startsWith(instruction);
3239
+ }
3240
+ function mergeParseOptions(mode, options) {
3241
+ const defaults = mode === "strict" ? DEFAULT_STRICT_PARSE_OPTIONS : DEFAULT_LOOSE_PARSE_OPTIONS;
3242
+ return {
3243
+ ...defaults,
3244
+ ...options
3245
+ };
3246
+ }
3247
+ function normalizeSelfHealConfig(option, mode) {
3248
+ if (typeof option === "number") {
3249
+ const maxAttempts = Math.max(0, Math.floor(option));
3250
+ return {
3251
+ enabled: maxAttempts > 0,
3252
+ maxAttempts,
3253
+ stopOnNoProgress: DEFAULT_SELF_HEAL_STOP_ON_NO_PROGRESS,
3254
+ maxContextChars: DEFAULT_SELF_HEAL_MAX_CONTEXT_CHARS
3255
+ };
3256
+ }
3257
+ if (typeof option === "boolean") {
3258
+ return {
3259
+ enabled: option,
3260
+ maxAttempts: option ? 1 : 0,
3261
+ stopOnNoProgress: DEFAULT_SELF_HEAL_STOP_ON_NO_PROGRESS,
3262
+ maxContextChars: DEFAULT_SELF_HEAL_MAX_CONTEXT_CHARS
3263
+ };
3264
+ }
3265
+ if (!option) {
3266
+ const modeDefaults = mode === "loose" ? DEFAULT_SELF_HEAL_BY_MODE.loose : DEFAULT_SELF_HEAL_BY_MODE.strict;
3267
+ return {
3268
+ enabled: modeDefaults.enabled,
3269
+ maxAttempts: modeDefaults.maxAttempts,
3270
+ stopOnNoProgress: DEFAULT_SELF_HEAL_STOP_ON_NO_PROGRESS,
3271
+ maxContextChars: DEFAULT_SELF_HEAL_MAX_CONTEXT_CHARS
3272
+ };
3273
+ }
3274
+ const enabled = option.enabled ?? true;
3275
+ const stopOnNoProgress = option.stopOnNoProgress ?? DEFAULT_SELF_HEAL_STOP_ON_NO_PROGRESS;
3276
+ const maxContextChars = normalizePositiveInt(option.maxContextChars, DEFAULT_SELF_HEAL_MAX_CONTEXT_CHARS);
3277
+ return {
3278
+ enabled,
3279
+ maxAttempts: enabled ? Math.max(1, option.maxAttempts ?? 1) : 0,
3280
+ stopOnNoProgress,
3281
+ maxContextChars
3282
+ };
3283
+ }
3284
+ function resolvePromptLine(value, fallback) {
3285
+ const trimmed = value?.trim();
3286
+ return trimmed && trimmed.length > 0 ? trimmed : fallback;
3287
+ }
3288
+ function resolveSelfHealPromptText(text) {
3289
+ return {
3290
+ fixInstruction: resolvePromptLine(text?.fixInstruction, DEFAULT_SELF_HEAL_PROMPT_TEXT.fixInstruction),
3291
+ returnInstruction: resolvePromptLine(text?.returnInstruction, DEFAULT_SELF_HEAL_PROMPT_TEXT.returnInstruction),
3292
+ noIssuesMessage: resolvePromptLine(text?.noIssuesMessage, DEFAULT_SELF_HEAL_PROMPT_TEXT.noIssuesMessage),
3293
+ validationErrorsLabel: resolvePromptLine(text?.validationErrorsLabel, DEFAULT_SELF_HEAL_PROMPT_TEXT.validationErrorsLabel),
3294
+ rawOutputLabel: resolvePromptLine(text?.rawOutputLabel, DEFAULT_SELF_HEAL_PROMPT_TEXT.rawOutputLabel),
3295
+ contextLabel: resolvePromptLine(text?.contextLabel, DEFAULT_SELF_HEAL_PROMPT_TEXT.contextLabel)
3296
+ };
3297
+ }
3298
+ function normalizePositiveInt(value, fallback) {
3299
+ if (typeof value !== "number" || !Number.isFinite(value)) {
3300
+ return fallback;
3301
+ }
3302
+ const normalized = Math.floor(value);
3303
+ return normalized > 0 ? normalized : fallback;
3304
+ }
3305
+ function truncateForPrompt(value, maxChars) {
3306
+ if (value.length <= maxChars) {
3307
+ return value;
3308
+ }
3309
+ const marker = `
3310
+ ...[truncated ${value.length - maxChars} chars]`;
3311
+ const head = Math.max(1, maxChars - marker.length);
3312
+ return `${value.slice(0, head)}${marker}`;
3313
+ }
3314
+ function formatIssuePath(path) {
3315
+ if (path.length === 0) {
3316
+ return "$";
3317
+ }
3318
+ let out = "$";
3319
+ for (const segment of path) {
3320
+ if (typeof segment === "number") {
3321
+ out += `[${segment}]`;
3322
+ continue;
3323
+ }
3324
+ if (/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(segment)) {
3325
+ out += `.${segment}`;
3326
+ continue;
3327
+ }
3328
+ out += `["${segment.replace(/"/g, "\\\"")}"]`;
3329
+ }
3330
+ return out;
3331
+ }
3332
+ function resolveSelfHealSource(attempt) {
3333
+ const candidate = attempt.parsed.candidate?.content?.trim();
3334
+ if (candidate) {
3335
+ return {
3336
+ kind: "candidate",
3337
+ text: candidate
3338
+ };
3339
+ }
3340
+ const sanitized = attempt.parsed.sanitizedRaw?.trim();
3341
+ if (sanitized) {
3342
+ return {
3343
+ kind: "sanitized",
3344
+ text: sanitized
3345
+ };
3346
+ }
3347
+ return {
3348
+ kind: "raw",
3349
+ text: attempt.raw
3350
+ };
3351
+ }
3352
+ function isSelfHealStalled(previous, current) {
3353
+ if (current.success) {
3354
+ return false;
3355
+ }
3356
+ if (current.zodIssues.length < previous.zodIssues.length) {
3357
+ return false;
3358
+ }
3359
+ if (current.parsed.errors.length < previous.parsed.errors.length) {
3360
+ return false;
3361
+ }
3362
+ return buildSelfHealFailureFingerprint(previous) === buildSelfHealFailureFingerprint(current);
3363
+ }
3364
+ function buildSelfHealFailureFingerprint(attempt) {
3365
+ const issues = attempt.zodIssues.map((issue) => `${formatIssuePath(issue.path)}:${issue.code}:${normalizeWhitespace(issue.message)}`).sort().join("|");
3366
+ const errors = attempt.parsed.errors.map((error) => `${error.stage}:${error.candidateId ?? "-"}:${normalizeWhitespace(error.message)}`).sort().join("|");
3367
+ const source = normalizeWhitespace(resolveSelfHealSource(attempt).text).slice(0, 512);
3368
+ return [issues, errors, source].join("::");
3369
+ }
3370
+ function normalizeWhitespace(value) {
3371
+ return value.replace(/\s+/g, " ").trim();
3372
+ }
3373
+ function normalizeStreamConfig(option) {
3374
+ if (typeof option === "boolean") {
3375
+ return {
3376
+ enabled: option
3377
+ };
3378
+ }
3379
+ if (!option) {
3380
+ return {
3381
+ enabled: false
3382
+ };
3383
+ }
3384
+ return {
3385
+ enabled: option.enabled ?? true,
3386
+ onData: option.onData,
3387
+ to: option.to
3388
+ };
3389
+ }
3390
+ function normalizeDebugConfig(option) {
3391
+ if (typeof option === "boolean") {
3392
+ return {
3393
+ enabled: option,
3394
+ colors: true,
3395
+ logger: (line) => console.log(line)
3396
+ };
3397
+ }
3398
+ if (!option) {
3399
+ return {
3400
+ enabled: false,
3401
+ colors: true,
3402
+ logger: (line) => console.log(line)
3403
+ };
3404
+ }
3405
+ return {
3406
+ enabled: option.enabled ?? true,
3407
+ colors: option.colors ?? true,
3408
+ logger: option.logger ?? ((line) => console.log(line))
3409
+ };
3410
+ }
3411
+ async function executeAttempt(adapter, input) {
3412
+ const response = await callModel(adapter, {
3413
+ prompt: input.prompt,
3414
+ systemPrompt: input.systemPrompt,
3415
+ request: input.request,
3416
+ stream: input.stream,
3417
+ observe: input.observe,
3418
+ debug: input.debug,
3419
+ attempt: input.attemptNumber,
3420
+ selfHeal: input.selfHeal,
3421
+ selfHealEnabled: input.selfHealEnabled
3422
+ });
3423
+ const parsed = parseWithObserve(response.text, input.schema, input.parseOptions, {
3424
+ observe: input.observe,
3425
+ attempt: input.attemptNumber,
3426
+ selfHeal: input.selfHeal
3427
+ });
3428
+ const trace = {
3429
+ attempt: input.attemptNumber,
3430
+ selfHeal: input.selfHeal,
3431
+ via: response.via,
3432
+ raw: response.text,
3433
+ thinkBlocks: parsed.thinkBlocks,
3434
+ json: parsed.parsed,
3435
+ candidates: parsed.candidates.map((candidate) => candidate.content),
3436
+ repairLog: collectRepairLog(parsed),
3437
+ zodIssues: parsed.zodIssues,
3438
+ success: parsed.success,
3439
+ usage: response.usage,
3440
+ finishReason: response.finishReason,
3441
+ parsed
3442
+ };
3443
+ return {
3444
+ response,
3445
+ trace
3446
+ };
3447
+ }
3448
+ async function callModel(adapter, options) {
3449
+ const requestPayload = {
3450
+ prompt: options.prompt,
3451
+ systemPrompt: options.systemPrompt,
3452
+ temperature: options.request?.temperature,
3453
+ maxTokens: options.request?.maxTokens,
3454
+ mcpClients: options.request?.mcpClients,
3455
+ toolChoice: options.request?.toolChoice,
3456
+ parallelToolCalls: options.request?.parallelToolCalls,
3457
+ maxToolRounds: options.request?.maxToolRounds,
3458
+ onToolExecution: options.request?.onToolExecution,
3459
+ toolDebug: options.request?.toolDebug,
3460
+ body: options.request?.body
3461
+ };
3462
+ emitDebugRequest(options.debug, {
3463
+ provider: adapter.provider,
3464
+ model: adapter.model,
3465
+ attempt: options.attempt,
3466
+ selfHealAttempt: options.selfHeal,
3467
+ selfHealEnabled: options.selfHealEnabled,
3468
+ stream: options.stream.enabled && !!adapter.stream,
3469
+ requestPayload
3470
+ });
3471
+ emitObserve(options.observe, {
3472
+ stage: "llm.request",
3473
+ attempt: options.attempt,
3474
+ selfHeal: options.selfHeal,
3475
+ message: "Sending LLM request.",
3476
+ details: {
3477
+ provider: adapter.provider,
3478
+ model: adapter.model,
3479
+ stream: options.stream.enabled && !!adapter.stream
3480
+ }
3481
+ });
3482
+ if (options.stream.enabled && adapter.stream) {
3483
+ let latestUsage;
3484
+ let latestFinishReason;
3485
+ let streamedRaw = "";
3486
+ let sawToken = false;
3487
+ let lastDataFingerprint;
3488
+ const emitStreamingData = (raw, done, usage2, finishReason2) => {
3489
+ const data = parseStreamingStructuredData(raw);
3490
+ if (data === null && !done) {
3491
+ return;
3492
+ }
3493
+ const fingerprint = toStreamDataFingerprint(data ?? null);
3494
+ if (!done && fingerprint === lastDataFingerprint) {
3495
+ return;
3496
+ }
3497
+ lastDataFingerprint = fingerprint;
3498
+ options.stream.onData?.({
3499
+ data: data ?? null,
3500
+ raw,
3501
+ done,
3502
+ usage: usage2,
3503
+ finishReason: finishReason2
3504
+ });
3505
+ emitObserve(options.observe, {
3506
+ stage: "llm.stream.data",
3507
+ attempt: options.attempt,
3508
+ selfHeal: options.selfHeal,
3509
+ message: done ? "Streaming structured data completed." : "Streaming structured data updated.",
3510
+ details: {
3511
+ done,
3512
+ finishReason: finishReason2
3513
+ }
3514
+ });
3515
+ };
3516
+ const handleTextDelta = (delta) => {
3517
+ if (!delta) {
3518
+ return;
3519
+ }
3520
+ streamedRaw += delta;
3521
+ if (options.stream.to === "stdout") {
3522
+ process.stdout.write(delta);
3523
+ }
3524
+ emitObserve(options.observe, {
3525
+ stage: "llm.stream.delta",
3526
+ attempt: options.attempt,
3527
+ selfHeal: options.selfHeal,
3528
+ message: "Received stream delta.",
3529
+ details: {
3530
+ chars: delta.length
3531
+ }
3532
+ });
3533
+ emitStreamingData(streamedRaw, false);
3534
+ };
3535
+ const response2 = await adapter.stream(requestPayload, {
3536
+ onToken: (token) => {
3537
+ sawToken = true;
3538
+ handleTextDelta(token);
3539
+ },
3540
+ onChunk: (chunk) => {
3541
+ if (!sawToken && chunk.textDelta) {
3542
+ handleTextDelta(chunk.textDelta);
3543
+ }
3544
+ if (chunk.usage) {
3545
+ latestUsage = mergeUsage2(latestUsage, chunk.usage);
3546
+ }
3547
+ if (chunk.finishReason) {
3548
+ latestFinishReason = chunk.finishReason;
3549
+ }
3550
+ }
3551
+ });
3552
+ const finalText = typeof response2.text === "string" && response2.text.length > 0 ? response2.text : streamedRaw;
3553
+ const usage = mergeUsage2(latestUsage, response2.usage);
3554
+ const finishReason = response2.finishReason ?? latestFinishReason;
3555
+ emitStreamingData(finalText, true, usage, finishReason);
3556
+ emitObserve(options.observe, {
3557
+ stage: "llm.response",
3558
+ attempt: options.attempt,
3559
+ selfHeal: options.selfHeal,
3560
+ message: "Streaming response completed.",
3561
+ details: {
3562
+ via: "stream",
3563
+ chars: finalText.length,
3564
+ finishReason
3565
+ }
3566
+ });
3567
+ emitDebugResponse(options.debug, {
3568
+ attempt: options.attempt,
3569
+ selfHealAttempt: options.selfHeal,
3570
+ selfHealEnabled: options.selfHealEnabled,
3571
+ via: "stream",
3572
+ responseText: finalText,
3573
+ usage,
3574
+ finishReason
3575
+ });
3576
+ return {
3577
+ text: finalText,
3578
+ via: "stream",
3579
+ usage,
3580
+ finishReason
3581
+ };
3582
+ }
3583
+ const response = await adapter.complete(requestPayload);
3584
+ emitObserve(options.observe, {
3585
+ stage: "llm.response",
3586
+ attempt: options.attempt,
3587
+ selfHeal: options.selfHeal,
3588
+ message: "Completion response received.",
3589
+ details: {
3590
+ via: "complete",
3591
+ chars: response.text.length,
3592
+ finishReason: response.finishReason
3593
+ }
3594
+ });
3595
+ emitDebugResponse(options.debug, {
3596
+ attempt: options.attempt,
3597
+ selfHealAttempt: options.selfHeal,
3598
+ selfHealEnabled: options.selfHealEnabled,
3599
+ via: "complete",
3600
+ responseText: response.text,
3601
+ usage: response.usage,
3602
+ finishReason: response.finishReason
3603
+ });
3604
+ return {
3605
+ text: response.text,
3606
+ via: "complete",
3607
+ usage: response.usage,
3608
+ finishReason: response.finishReason
3609
+ };
3610
+ }
3611
+ function parseStreamingStructuredData(raw) {
3612
+ const sanitized = sanitizeThink(raw);
3613
+ const start = findFirstJsonRootStart(sanitized.visibleText);
3614
+ if (start < 0) {
3615
+ return null;
3616
+ }
3617
+ const candidate = sanitized.visibleText.slice(start).trim();
3618
+ if (!candidate) {
3619
+ return null;
3620
+ }
3621
+ try {
3622
+ const repaired = jsonrepair3(candidate);
3623
+ const parsed = JSON.parse(repaired);
3624
+ if (typeof parsed !== "object" || parsed === null) {
3625
+ return null;
3626
+ }
3627
+ return parsed;
3628
+ } catch {
3629
+ return null;
3630
+ }
3631
+ }
3632
+ function findFirstJsonRootStart(input) {
3633
+ let inString = false;
3634
+ let escaped = false;
3635
+ for (let index = 0;index < input.length; index += 1) {
3636
+ const char = input[index];
3637
+ if (!char) {
3638
+ continue;
3639
+ }
3640
+ if (inString) {
3641
+ if (escaped) {
3642
+ escaped = false;
3643
+ continue;
3644
+ }
3645
+ if (char === "\\") {
3646
+ escaped = true;
3647
+ continue;
3648
+ }
3649
+ if (char === '"') {
3650
+ inString = false;
3651
+ }
3652
+ continue;
3653
+ }
3654
+ if (char === '"') {
3655
+ inString = true;
3656
+ continue;
3657
+ }
3658
+ if (char === "{" || char === "[") {
3659
+ return index;
3660
+ }
3661
+ }
3662
+ const objectStart = input.indexOf("{");
3663
+ const arrayStart = input.indexOf("[");
3664
+ if (objectStart < 0) {
3665
+ return arrayStart;
3666
+ }
3667
+ if (arrayStart < 0) {
3668
+ return objectStart;
3669
+ }
3670
+ return Math.min(objectStart, arrayStart);
3671
+ }
3672
+ function toStreamDataFingerprint(value) {
3673
+ try {
3674
+ return JSON.stringify(value);
3675
+ } catch {
3676
+ return "__unserializable__";
3677
+ }
3678
+ }
3679
+ function parseWithObserve(output, schema, parseOptions, context) {
3680
+ const userParseTrace = parseOptions.onTrace;
3681
+ return parseLLMOutput(output, schema, {
3682
+ ...parseOptions,
3683
+ onTrace: (event) => {
3684
+ userParseTrace?.(event);
3685
+ emitObserve(context.observe, {
3686
+ stage: "parse",
3687
+ attempt: context.attempt,
3688
+ selfHeal: context.selfHeal,
3689
+ message: event.message,
3690
+ details: {
3691
+ level: event.level,
3692
+ stage: event.stage,
3693
+ candidateId: event.candidateId,
3694
+ details: event.details
3695
+ }
3696
+ });
3697
+ }
3698
+ });
3699
+ }
3700
+ function collectRepairLog(parsed) {
3701
+ const logs = parsed.diagnostics.filter((diagnostic) => diagnostic.usedRepair).map((diagnostic) => diagnostic.message).filter((value) => typeof value === "string" && value.trim().length > 0);
3702
+ return [...new Set(logs)];
3703
+ }
3704
+ function buildSuccessResult(data, attempts) {
3705
+ const final = attempts.at(-1);
3706
+ return {
3707
+ data,
3708
+ raw: final?.raw ?? "",
3709
+ thinkBlocks: final?.thinkBlocks ?? [],
3710
+ json: final?.json ?? null,
3711
+ attempts,
3712
+ usage: aggregateUsage(attempts),
3713
+ finishReason: final?.finishReason
3714
+ };
3715
+ }
3716
+ function aggregateUsage(attempts) {
3717
+ let usage;
3718
+ for (const attempt of attempts) {
3719
+ usage = mergeUsage2(usage, attempt.usage);
3720
+ }
3721
+ return usage;
3722
+ }
3723
+ function mergeUsage2(base, next) {
3724
+ if (!base && !next) {
3725
+ return;
3726
+ }
3727
+ return {
3728
+ inputTokens: (base?.inputTokens ?? 0) + (next?.inputTokens ?? 0),
3729
+ outputTokens: (base?.outputTokens ?? 0) + (next?.outputTokens ?? 0),
3730
+ totalTokens: (base?.totalTokens ?? 0) + (next?.totalTokens ?? 0),
3731
+ cost: (base?.cost ?? 0) + (next?.cost ?? 0)
3732
+ };
3733
+ }
3734
+ function toStructuredError(attempt) {
3735
+ if (!attempt) {
3736
+ return new StructuredParseError({
3737
+ message: "Structured parsing failed before any model response.",
3738
+ raw: "",
3739
+ thinkBlocks: [],
3740
+ candidates: [],
3741
+ zodIssues: [],
3742
+ repairLog: [],
3743
+ attempt: 0
3744
+ });
3745
+ }
3746
+ return new StructuredParseError({
3747
+ raw: attempt.raw,
3748
+ thinkBlocks: attempt.thinkBlocks,
3749
+ candidates: attempt.candidates,
3750
+ zodIssues: attempt.zodIssues,
3751
+ repairLog: attempt.repairLog,
3752
+ attempt: attempt.attempt
3753
+ });
3754
+ }
3755
+ function emitObserve(observe, event) {
3756
+ observe?.(event);
3757
+ }
3758
+ function emitDebugRequest(config, input) {
3759
+ const requestBody = input.requestPayload.body !== undefined ? JSON.stringify(input.requestPayload.body, null, 2) : "(none)";
3760
+ const lines = [
3761
+ color(config, title(config, [
3762
+ "[structured][request]",
3763
+ `attempt=${input.attempt}`,
3764
+ `selfHealEnabled=${input.selfHealEnabled}`,
3765
+ `selfHealAttempt=${input.selfHealAttempt}`
3766
+ ].join(" ")), "cyan"),
3767
+ dim(config, [
3768
+ `provider=${input.provider ?? "unknown"}`,
3769
+ `model=${input.model ?? "unknown"}`,
3770
+ `stream=${input.stream}`
3771
+ ].join(" ")),
3772
+ color(config, "prompt:", "yellow"),
3773
+ input.requestPayload.prompt,
3774
+ color(config, "systemPrompt:", "yellow"),
3775
+ input.requestPayload.systemPrompt ?? "(none)",
3776
+ color(config, "request.body:", "yellow"),
3777
+ requestBody
3778
+ ];
3779
+ emitDebug(config, lines.join(`
3780
+ `));
3781
+ }
3782
+ function emitDebugResponse(config, input) {
3783
+ const lines = [
3784
+ color(config, title(config, [
3785
+ "[structured][response]",
3786
+ `attempt=${input.attempt}`,
3787
+ `selfHealEnabled=${input.selfHealEnabled}`,
3788
+ `selfHealAttempt=${input.selfHealAttempt}`
3789
+ ].join(" ")), "green"),
3790
+ dim(config, [
3791
+ `via=${input.via}`,
3792
+ `chars=${input.responseText.length}`,
3793
+ `finishReason=${input.finishReason ?? "unknown"}`,
3794
+ `usage=${JSON.stringify(input.usage ?? {})}`
3795
+ ].join(" ")),
3796
+ color(config, "text:", "yellow"),
3797
+ input.responseText
3798
+ ];
3799
+ emitDebug(config, lines.join(`
3800
+ `));
3801
+ }
3802
+ function emitDebug(config, message) {
3803
+ if (!config.enabled) {
3804
+ return;
3805
+ }
3806
+ config.logger(message);
3807
+ }
3808
+
3809
+ // src/llm.ts
3810
+ function createLLM(config, registry = createDefaultProviderRegistry()) {
3811
+ const adapter = createModelAdapter(config, registry);
3812
+ const defaults = config.defaults;
3813
+ return {
3814
+ adapter,
3815
+ provider: adapter.provider,
3816
+ model: adapter.model,
3817
+ async structured(schema, prompt, options) {
3818
+ const merged = mergeStructuredOptions(defaults, options);
3819
+ return structured(adapter, schema, prompt, merged);
3820
+ }
3821
+ };
3822
+ }
3823
+ function mergeStructuredOptions(defaults, overrides) {
3824
+ if (!defaults && !overrides) {
3825
+ return {};
3826
+ }
3827
+ return {
3828
+ ...defaults,
3829
+ ...overrides,
3830
+ parse: {
3831
+ ...defaults?.parse ?? {},
3832
+ ...overrides?.parse ?? {}
3833
+ },
3834
+ request: {
3835
+ ...defaults?.request ?? {},
3836
+ ...overrides?.request ?? {}
3837
+ },
3838
+ stream: mergeObjectLike(defaults?.stream, overrides?.stream),
3839
+ selfHeal: mergeObjectLike(defaults?.selfHeal, overrides?.selfHeal),
3840
+ debug: mergeObjectLike(defaults?.debug, overrides?.debug)
3841
+ };
3842
+ }
3843
+ function mergeObjectLike(defaults, overrides) {
3844
+ if (overrides === undefined) {
3845
+ return defaults;
3846
+ }
3847
+ if (defaults === undefined) {
3848
+ return overrides;
3849
+ }
3850
+ if (!isPlainObject(defaults) || !isPlainObject(overrides)) {
3851
+ return overrides;
3852
+ }
3853
+ return {
3854
+ ...defaults,
3855
+ ...overrides
3856
+ };
3857
+ }
3858
+ function isPlainObject(value) {
3859
+ return typeof value === "object" && value !== null && !Array.isArray(value);
3860
+ }
3861
+ // src/mcp.ts
3862
+ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
3863
+ import {
3864
+ StdioClientTransport
3865
+ } from "@modelcontextprotocol/sdk/client/stdio.js";
3866
+ import {
3867
+ StreamableHTTPClientTransport
3868
+ } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
3869
+ async function createMCPClient(options) {
3870
+ const client = new Client(toImplementation(options.clientInfo), {
3871
+ capabilities: {}
3872
+ });
3873
+ const transport = createTransport(options.transport);
3874
+ await client.connect(transport);
3875
+ return wrapMCPClient({
3876
+ id: options.id,
3877
+ client,
3878
+ transport
3879
+ });
3880
+ }
3881
+ function wrapMCPClient(options) {
3882
+ return {
3883
+ id: options.id,
3884
+ sdkClient: options.client,
3885
+ transport: options.transport,
3886
+ async listTools(params) {
3887
+ const response = await options.client.listTools(params);
3888
+ return {
3889
+ tools: response.tools.map((tool) => ({
3890
+ name: tool.name,
3891
+ description: tool.description,
3892
+ inputSchema: tool.inputSchema
3893
+ })),
3894
+ nextCursor: response.nextCursor
3895
+ };
3896
+ },
3897
+ async callTool(params) {
3898
+ return options.client.callTool(params);
3899
+ },
3900
+ async close() {
3901
+ await options.client.close();
3902
+ if (options.transport) {
3903
+ await options.transport.close();
3904
+ }
3905
+ }
3906
+ };
3907
+ }
3908
+ function createTransport(config) {
3909
+ if (config.type === "stdio") {
3910
+ const stdio = {
3911
+ command: config.command,
3912
+ args: config.args,
3913
+ env: config.env,
3914
+ stderr: config.stderr,
3915
+ cwd: config.cwd
3916
+ };
3917
+ return new StdioClientTransport(stdio);
3918
+ }
3919
+ if (config.type === "streamable-http") {
3920
+ const url = typeof config.url === "string" ? new URL(config.url) : config.url;
3921
+ return new StreamableHTTPClientTransport(url, config.options);
3922
+ }
3923
+ return config.transport;
3924
+ }
3925
+ function toImplementation(clientInfo) {
3926
+ return {
3927
+ name: clientInfo?.name ?? "extrait-mcp-client",
3928
+ version: clientInfo?.version ?? "0.1.0"
3929
+ };
3930
+ }
3931
+ // src/prompt.ts
3932
+ function toPromptString(value) {
3933
+ if (value === null || value === undefined) {
3934
+ return "";
3935
+ }
3936
+ if (typeof value === "string") {
3937
+ return value;
3938
+ }
3939
+ if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") {
3940
+ return String(value);
3941
+ }
3942
+ try {
3943
+ return JSON.stringify(value, null, 2) ?? "";
3944
+ } catch {
3945
+ return String(value);
3946
+ }
3947
+ }
3948
+ var dedent = createOutdent({
3949
+ trimLeadingNewline: true,
3950
+ trimTrailingNewline: true,
3951
+ newline: `
3952
+ `
3953
+ });
3954
+ function isBlankLine(line) {
3955
+ for (let index = 0;index < line.length; index += 1) {
3956
+ const char = line[index];
3957
+ if (char !== " " && char !== "\t") {
3958
+ return false;
3959
+ }
3960
+ }
3961
+ return true;
3962
+ }
3963
+ function stripOuterBlankLines(text) {
3964
+ const lines = text.split(`
3965
+ `);
3966
+ let start = 0;
3967
+ while (start < lines.length && isBlankLine(lines[start] ?? "")) {
3968
+ start += 1;
3969
+ }
3970
+ let end = lines.length - 1;
3971
+ while (end >= start && isBlankLine(lines[end] ?? "")) {
3972
+ end -= 1;
3973
+ }
3974
+ if (start > end) {
3975
+ return "";
3976
+ }
3977
+ return lines.slice(start, end + 1).join(`
3978
+ `);
3979
+ }
3980
+ function renderPromptTemplate(strings, values) {
3981
+ return stripOuterBlankLines(dedent(strings, ...values.map(toPromptString)));
3982
+ }
3983
+ function isTemplateStringsArray(value) {
3984
+ return Array.isArray(value) && "raw" in value;
3985
+ }
3986
+ function toPromptMessage(input, values) {
3987
+ if (typeof input === "string") {
3988
+ return input;
3989
+ }
3990
+ return renderPromptTemplate(input, values);
3991
+ }
3992
+ function joinMessages(messages) {
3993
+ return messages.join(`
3994
+
3995
+ `);
3996
+ }
3997
+
3998
+ class PromptMessageBuilderImpl {
3999
+ systemMessages = [];
4000
+ userMessages = [];
4001
+ system(input, ...values) {
4002
+ const message = toPromptMessage(input, values);
4003
+ if (message.length > 0) {
4004
+ this.systemMessages.push(message);
4005
+ }
4006
+ return this;
4007
+ }
4008
+ user(input, ...values) {
4009
+ const message = toPromptMessage(input, values);
4010
+ if (message.length > 0) {
4011
+ this.userMessages.push(message);
4012
+ }
4013
+ return this;
4014
+ }
4015
+ build() {
4016
+ const prompt = joinMessages(this.userMessages);
4017
+ const systemPrompt = joinMessages(this.systemMessages);
4018
+ return {
4019
+ prompt,
4020
+ systemPrompt: systemPrompt.length > 0 ? systemPrompt : undefined
4021
+ };
4022
+ }
4023
+ resolvePrompt(_context) {
4024
+ return this.build();
4025
+ }
4026
+ }
4027
+ function createPromptMessageBuilder() {
4028
+ return new PromptMessageBuilderImpl;
4029
+ }
4030
+ function prompt(input, ...values) {
4031
+ if (isTemplateStringsArray(input)) {
4032
+ return renderPromptTemplate(input, values);
4033
+ }
4034
+ return createPromptMessageBuilder();
4035
+ }
4036
+ // src/schema-builder.ts
4037
+ import { z } from "zod";
4038
+ var NAME_SYMBOL = Symbol.for("extrait.schema.name");
4039
+ var didPatchZod = false;
4040
+ function ensurePatchedZod() {
4041
+ if (didPatchZod) {
4042
+ return;
4043
+ }
4044
+ const zodNumberPrototype = z.ZodNumber.prototype;
4045
+ if (!zodNumberPrototype.coerce) {
4046
+ zodNumberPrototype.coerce = function coerceNumber() {
4047
+ const coerced = z.coerce.number();
4048
+ return coerced;
4049
+ };
4050
+ }
4051
+ didPatchZod = true;
4052
+ }
4053
+ ensurePatchedZod();
4054
+ var s = {
4055
+ schema(name, schema) {
4056
+ return setSchemaName(schema, name);
4057
+ },
4058
+ string() {
4059
+ return z.string();
4060
+ },
4061
+ number() {
4062
+ return z.number();
4063
+ },
4064
+ boolean() {
4065
+ return z.boolean();
4066
+ },
4067
+ array(schema) {
4068
+ return z.array(schema);
4069
+ },
4070
+ object(shape) {
4071
+ return z.object(shape);
4072
+ }
4073
+ };
4074
+ function setSchemaName(schema, name) {
4075
+ schema[NAME_SYMBOL] = name;
4076
+ return schema;
4077
+ }
4078
+ function getSchemaName(schema) {
4079
+ return schema[NAME_SYMBOL];
4080
+ }
4081
+ function inspectSchemaMetadata(schema) {
4082
+ const requiredFields = [];
4083
+ const defaults = {};
4084
+ const fieldDescriptions = {};
4085
+ const objectShape = getObjectShape(schema);
4086
+ if (objectShape) {
4087
+ for (const [fieldName, fieldSchema] of Object.entries(objectShape)) {
4088
+ const { optional } = unwrap2(fieldSchema);
4089
+ if (!optional) {
4090
+ requiredFields.push(fieldName);
4091
+ }
4092
+ const defaultValue = readDefaultValue(fieldSchema);
4093
+ if (defaultValue !== undefined) {
4094
+ defaults[fieldName] = defaultValue;
4095
+ }
4096
+ const description = readSchemaDescription2(fieldSchema);
4097
+ if (description) {
4098
+ fieldDescriptions[fieldName] = description;
4099
+ }
4100
+ }
4101
+ }
4102
+ return {
4103
+ name: getSchemaName(schema),
4104
+ description: readSchemaDescription2(schema),
4105
+ requiredFields,
4106
+ defaults,
4107
+ fieldDescriptions
4108
+ };
4109
+ }
4110
+ function inferSchemaExample(schema) {
4111
+ const objectShape = getObjectShape(schema);
4112
+ if (!objectShape) {
4113
+ const fallback = readDefaultValue(schema);
4114
+ if (fallback !== undefined) {
4115
+ return fallback;
4116
+ }
4117
+ return null;
4118
+ }
4119
+ const out = {};
4120
+ for (const [fieldName, fieldSchema] of Object.entries(objectShape)) {
4121
+ const defaultValue = readDefaultValue(fieldSchema);
4122
+ if (defaultValue !== undefined) {
4123
+ out[fieldName] = defaultValue;
4124
+ }
4125
+ }
4126
+ return Object.keys(out).length > 0 ? out : null;
4127
+ }
4128
+ function getObjectShape(schema) {
4129
+ const unwrapped = unwrap2(schema).schema;
4130
+ const typeName = unwrapped._def?.typeName;
4131
+ if (typeName !== "ZodObject") {
4132
+ return null;
4133
+ }
4134
+ const rawShape = unwrapped._def?.shape;
4135
+ if (typeof rawShape === "function") {
4136
+ return rawShape();
4137
+ }
4138
+ return rawShape ?? null;
4139
+ }
4140
+ function readDefaultValue(schema) {
4141
+ let current = schema;
4142
+ while (current?._def?.typeName) {
4143
+ const typeName = current._def.typeName;
4144
+ if (typeName === "ZodDefault") {
4145
+ const raw = current._def.defaultValue;
4146
+ if (typeof raw === "function") {
4147
+ try {
4148
+ return raw();
4149
+ } catch {
4150
+ return;
4151
+ }
4152
+ }
4153
+ return raw;
4154
+ }
4155
+ if (typeName === "ZodOptional" || typeName === "ZodNullable" || typeName === "ZodCatch" || typeName === "ZodReadonly") {
4156
+ current = current._def.innerType ?? current;
4157
+ continue;
4158
+ }
4159
+ if (typeName === "ZodEffects") {
4160
+ current = current._def.schema ?? current;
4161
+ continue;
4162
+ }
4163
+ if (typeName === "ZodBranded") {
4164
+ current = current._def.type ?? current;
4165
+ continue;
4166
+ }
4167
+ if (typeName === "ZodPipeline") {
4168
+ current = current._def.out ?? current;
4169
+ continue;
4170
+ }
4171
+ return;
4172
+ }
4173
+ return;
4174
+ }
4175
+ function readSchemaDescription2(schema) {
4176
+ let current = schema;
4177
+ while (current?._def?.typeName) {
4178
+ const raw = current._def.description;
4179
+ if (typeof raw === "string" && raw.trim().length > 0) {
4180
+ return raw.trim();
4181
+ }
4182
+ const fallback = current.description;
4183
+ if (typeof fallback === "string" && fallback.trim().length > 0) {
4184
+ return fallback.trim();
4185
+ }
4186
+ const typeName = current._def.typeName;
4187
+ if (typeName === "ZodOptional" || typeName === "ZodDefault" || typeName === "ZodNullable") {
4188
+ current = current._def.innerType ?? current;
4189
+ continue;
4190
+ }
4191
+ if (typeName === "ZodCatch" || typeName === "ZodReadonly") {
4192
+ current = current._def.innerType ?? current;
4193
+ continue;
4194
+ }
4195
+ if (typeName === "ZodEffects") {
4196
+ current = current._def.schema ?? current;
4197
+ continue;
4198
+ }
4199
+ if (typeName === "ZodBranded") {
4200
+ current = current._def.type ?? current;
4201
+ continue;
4202
+ }
4203
+ if (typeName === "ZodPipeline") {
4204
+ current = current._def.out ?? current;
4205
+ continue;
4206
+ }
4207
+ break;
4208
+ }
4209
+ return;
4210
+ }
4211
+ function unwrap2(schema) {
4212
+ let current = schema;
4213
+ let optional = false;
4214
+ while (current?._def?.typeName) {
4215
+ const typeName = current._def.typeName;
4216
+ if (typeName === "ZodOptional" || typeName === "ZodDefault") {
4217
+ optional = true;
4218
+ current = current._def.innerType ?? current;
4219
+ continue;
4220
+ }
4221
+ if (typeName === "ZodNullable" || typeName === "ZodCatch" || typeName === "ZodReadonly") {
4222
+ current = current._def.innerType ?? current;
4223
+ continue;
4224
+ }
4225
+ if (typeName === "ZodEffects") {
4226
+ current = current._def.schema ?? current;
4227
+ continue;
4228
+ }
4229
+ if (typeName === "ZodBranded") {
4230
+ current = current._def.type ?? current;
4231
+ continue;
4232
+ }
4233
+ if (typeName === "ZodPipeline") {
4234
+ current = current._def.out ?? current;
4235
+ continue;
4236
+ }
4237
+ break;
4238
+ }
4239
+ return {
4240
+ schema: current,
4241
+ optional
4242
+ };
4243
+ }
4244
+ export {
4245
+ wrapMCPClient,
4246
+ withFormat,
4247
+ structured,
4248
+ sanitizeThink,
4249
+ s,
4250
+ resolveSchemaInstruction,
4251
+ registerBuiltinProviders,
4252
+ prompt,
4253
+ parseLLMOutput,
4254
+ inspectSchemaMetadata,
4255
+ inferSchemaExample,
4256
+ formatZodIssues,
4257
+ formatPrompt,
4258
+ extractMarkdownCodeBlocks,
4259
+ extractJsonCandidates,
4260
+ extractFirstMarkdownCode,
4261
+ createProviderRegistry,
4262
+ createOpenAICompatibleAdapter,
4263
+ createModelAdapter,
4264
+ createMCPClient,
4265
+ createLLM,
4266
+ createDefaultProviderRegistry,
4267
+ createAnthropicCompatibleAdapter,
4268
+ buildSelfHealPrompt,
4269
+ buildDefaultStructuredPrompt,
4270
+ StructuredParseError,
4271
+ DEFAULT_STRUCTURED_STYLE_INSTRUCTION,
4272
+ DEFAULT_STRUCTURED_OBJECT_INSTRUCTION,
4273
+ DEFAULT_STRICT_PARSE_OPTIONS,
4274
+ DEFAULT_SELF_HEAL_VALIDATION_LABEL,
4275
+ DEFAULT_SELF_HEAL_STOP_ON_NO_PROGRESS,
4276
+ DEFAULT_SELF_HEAL_RETURN_INSTRUCTION,
4277
+ DEFAULT_SELF_HEAL_RAW_OUTPUT_LABEL,
4278
+ DEFAULT_SELF_HEAL_PROTOCOL,
4279
+ DEFAULT_SELF_HEAL_NO_ISSUES_MESSAGE,
4280
+ DEFAULT_SELF_HEAL_MAX_CONTEXT_CHARS,
4281
+ DEFAULT_SELF_HEAL_FIX_INSTRUCTION,
4282
+ DEFAULT_SELF_HEAL_CONTEXT_LABEL,
4283
+ DEFAULT_SELF_HEAL_BY_MODE,
4284
+ DEFAULT_SCHEMA_INSTRUCTION,
4285
+ DEFAULT_MAX_TOOL_ROUNDS,
4286
+ DEFAULT_LOOSE_PARSE_OPTIONS,
4287
+ DEFAULT_EXTRACTION_HEURISTICS,
4288
+ DEFAULT_ANTHROPIC_VERSION,
4289
+ DEFAULT_ANTHROPIC_MAX_TOKENS
4290
+ };