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