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/LICENSE +21 -0
- package/README.md +247 -0
- package/dist/extract.d.ts +3 -0
- package/dist/format.d.ts +8 -0
- package/dist/index.cjs +4318 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.js +4290 -0
- package/dist/llm.d.ts +13 -0
- package/dist/markdown.d.ts +3 -0
- package/dist/mcp.d.ts +40 -0
- package/dist/outdent.d.ts +10 -0
- package/dist/parse.d.ts +4 -0
- package/dist/prompt.d.ts +10 -0
- package/dist/providers/anthropic-compatible.d.ts +16 -0
- package/dist/providers/mcp-runtime.d.ts +38 -0
- package/dist/providers/openai-compatible.d.ts +13 -0
- package/dist/providers/registry.d.ts +32 -0
- package/dist/providers/stream-utils.d.ts +2 -0
- package/dist/providers/utils.d.ts +9 -0
- package/dist/schema-builder.d.ts +25 -0
- package/dist/schema.d.ts +2 -0
- package/dist/structured.d.ts +86 -0
- package/dist/think.d.ts +7 -0
- package/dist/types.d.ts +311 -0
- package/dist/utils/common.d.ts +2 -0
- package/dist/utils/debug-colors.d.ts +7 -0
- package/package.json +42 -0
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
|
+
}
|