extrait 0.1.1 → 0.2.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/README.md +4 -0
- package/dist/index.cjs +49 -25
- package/dist/index.d.ts +1 -1
- package/dist/index.js +49 -25
- package/dist/providers/mcp-runtime.d.ts +1 -0
- package/dist/types.d.ts +2 -0
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -215,6 +215,10 @@ const result = await llm.structured(
|
|
|
215
215
|
onToolExecution: (execution) => {
|
|
216
216
|
console.log(execution.name, execution.durationMs);
|
|
217
217
|
},
|
|
218
|
+
// Optional: transform tool output before it is sent back to the LLM
|
|
219
|
+
transformToolOutput: (output, execution) => {
|
|
220
|
+
return { ...output, source: execution.name };
|
|
221
|
+
},
|
|
218
222
|
},
|
|
219
223
|
}
|
|
220
224
|
);
|
package/dist/index.cjs
CHANGED
|
@@ -298,6 +298,9 @@ function isOnlyWhitespace(value) {
|
|
|
298
298
|
}
|
|
299
299
|
|
|
300
300
|
// src/extract.ts
|
|
301
|
+
var RE_EMPTY_OBJECT = /^\{\s*\}$/;
|
|
302
|
+
var RE_EMPTY_ARRAY = /^\[\s*\]$/;
|
|
303
|
+
var RE_BOUNDARY_CHAR = /[\s,.;:!?`"'()\[\]{}<>]/;
|
|
301
304
|
var DEFAULT_EXTRACTION_HEURISTICS = {
|
|
302
305
|
firstPassMin: 12,
|
|
303
306
|
firstPassCap: 30,
|
|
@@ -522,7 +525,7 @@ function jsonShapeScore(content, acceptArrays) {
|
|
|
522
525
|
const commaCount = countChar(trimmed, ",");
|
|
523
526
|
const quoteCount = countChar(trimmed, '"');
|
|
524
527
|
if (root === "{") {
|
|
525
|
-
if (
|
|
528
|
+
if (RE_EMPTY_OBJECT.test(trimmed)) {
|
|
526
529
|
score += 12;
|
|
527
530
|
} else if (colonCount > 0) {
|
|
528
531
|
score += 22;
|
|
@@ -533,7 +536,7 @@ function jsonShapeScore(content, acceptArrays) {
|
|
|
533
536
|
score += quoteCount % 2 === 0 ? 8 : -8;
|
|
534
537
|
}
|
|
535
538
|
} else {
|
|
536
|
-
score +=
|
|
539
|
+
score += RE_EMPTY_ARRAY.test(trimmed) ? 8 : 4;
|
|
537
540
|
if (colonCount > 0) {
|
|
538
541
|
score += 4;
|
|
539
542
|
}
|
|
@@ -566,7 +569,7 @@ function isBoundary(char) {
|
|
|
566
569
|
if (!char) {
|
|
567
570
|
return true;
|
|
568
571
|
}
|
|
569
|
-
return
|
|
572
|
+
return RE_BOUNDARY_CHAR.test(char);
|
|
570
573
|
}
|
|
571
574
|
function lengthScore(length) {
|
|
572
575
|
return Math.min(120, Math.floor(Math.sqrt(length) * 6));
|
|
@@ -755,6 +758,8 @@ function clamp(value, min, max) {
|
|
|
755
758
|
return Math.max(min, Math.min(max, Math.floor(value)));
|
|
756
759
|
}
|
|
757
760
|
// src/schema.ts
|
|
761
|
+
var RE_SIMPLE_IDENTIFIER = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
|
|
762
|
+
var RE_WHITESPACE = /\s+/g;
|
|
758
763
|
function formatZodSchemaLikeTypeScript(schema) {
|
|
759
764
|
return formatType(schema, 0, new WeakSet);
|
|
760
765
|
}
|
|
@@ -926,7 +931,7 @@ ${lines.join(`
|
|
|
926
931
|
${indent}}`;
|
|
927
932
|
}
|
|
928
933
|
function formatKey(key) {
|
|
929
|
-
return
|
|
934
|
+
return RE_SIMPLE_IDENTIFIER.test(key) ? key : JSON.stringify(key);
|
|
930
935
|
}
|
|
931
936
|
function requiresParentheses(typeText) {
|
|
932
937
|
return typeText.includes(" | ") || typeText.includes(" & ");
|
|
@@ -972,7 +977,7 @@ function readSchemaDescription(schema) {
|
|
|
972
977
|
return;
|
|
973
978
|
}
|
|
974
979
|
function sanitizeDescription(value) {
|
|
975
|
-
return value.replace(
|
|
980
|
+
return value.replace(RE_WHITESPACE, " ").trim();
|
|
976
981
|
}
|
|
977
982
|
|
|
978
983
|
// src/format.ts
|
|
@@ -997,6 +1002,8 @@ function resolveSchemaInstruction(instruction) {
|
|
|
997
1002
|
}
|
|
998
1003
|
// src/think.ts
|
|
999
1004
|
var THINK_TAG_NAME = "think";
|
|
1005
|
+
var RE_IDENTIFIER_CHAR = /[a-zA-Z0-9:_-]/;
|
|
1006
|
+
var RE_NON_LINE_BREAK = /[^\r\n]/g;
|
|
1000
1007
|
function sanitizeThink(input) {
|
|
1001
1008
|
const thinkBlocks = [];
|
|
1002
1009
|
const diagnostics = {
|
|
@@ -1095,6 +1102,9 @@ function parseThinkTagAt(input, index) {
|
|
|
1095
1102
|
if (input[cursor] === "/") {
|
|
1096
1103
|
closing = true;
|
|
1097
1104
|
cursor += 1;
|
|
1105
|
+
while (cursor < input.length && isWhitespace(input[cursor])) {
|
|
1106
|
+
cursor += 1;
|
|
1107
|
+
}
|
|
1098
1108
|
}
|
|
1099
1109
|
if (!matchesIgnoreCase(input, cursor, THINK_TAG_NAME)) {
|
|
1100
1110
|
return null;
|
|
@@ -1151,7 +1161,7 @@ function matchesIgnoreCase(input, index, expected) {
|
|
|
1151
1161
|
return input.slice(index, index + expected.length).toLowerCase() === expected;
|
|
1152
1162
|
}
|
|
1153
1163
|
function isIdentifierChar(char) {
|
|
1154
|
-
return
|
|
1164
|
+
return RE_IDENTIFIER_CHAR.test(char);
|
|
1155
1165
|
}
|
|
1156
1166
|
function countHiddenChars(value) {
|
|
1157
1167
|
let count = 0;
|
|
@@ -1165,9 +1175,10 @@ function countHiddenChars(value) {
|
|
|
1165
1175
|
return count;
|
|
1166
1176
|
}
|
|
1167
1177
|
function maskKeepingLineBreaks(value) {
|
|
1168
|
-
return value.replace(
|
|
1178
|
+
return value.replace(RE_NON_LINE_BREAK, " ");
|
|
1169
1179
|
}
|
|
1170
1180
|
// src/providers/stream-utils.ts
|
|
1181
|
+
var RE_LINE_ENDING = /\r?\n/;
|
|
1171
1182
|
async function consumeSSE(response, onEvent) {
|
|
1172
1183
|
if (!response.body) {
|
|
1173
1184
|
return;
|
|
@@ -1190,7 +1201,7 @@ async function consumeSSE(response, onEvent) {
|
|
|
1190
1201
|
buffer = buffer.slice(boundary + (buffer.startsWith(`\r
|
|
1191
1202
|
\r
|
|
1192
1203
|
`, boundary) ? 4 : 2));
|
|
1193
|
-
const dataLines = rawEvent.split(
|
|
1204
|
+
const dataLines = rawEvent.split(RE_LINE_ENDING).filter((line) => line.startsWith("data:")).map((line) => line.slice(5).trim());
|
|
1194
1205
|
if (dataLines.length === 0) {
|
|
1195
1206
|
continue;
|
|
1196
1207
|
}
|
|
@@ -1200,7 +1211,7 @@ async function consumeSSE(response, onEvent) {
|
|
|
1200
1211
|
}
|
|
1201
1212
|
const remainder = buffer.trim();
|
|
1202
1213
|
if (remainder.length > 0) {
|
|
1203
|
-
const dataLines = remainder.split(
|
|
1214
|
+
const dataLines = remainder.split(RE_LINE_ENDING).filter((line) => line.startsWith("data:")).map((line) => line.slice(5).trim());
|
|
1204
1215
|
if (dataLines.length > 0) {
|
|
1205
1216
|
onEvent(dataLines.join(`
|
|
1206
1217
|
`));
|
|
@@ -1301,20 +1312,25 @@ async function executeMCPToolCalls(calls, toolset, context) {
|
|
|
1301
1312
|
name: tool.remoteName,
|
|
1302
1313
|
arguments: args
|
|
1303
1314
|
});
|
|
1304
|
-
|
|
1305
|
-
const execution = {
|
|
1315
|
+
const executionContext = {
|
|
1306
1316
|
callId,
|
|
1307
|
-
type:
|
|
1317
|
+
type: call.type ?? "function",
|
|
1308
1318
|
name: toolName,
|
|
1309
1319
|
clientId: tool.clientId,
|
|
1310
1320
|
remoteName: tool.remoteName,
|
|
1311
1321
|
arguments: parsedArguments,
|
|
1312
|
-
output,
|
|
1313
1322
|
round: context.round,
|
|
1314
1323
|
provider: context.provider,
|
|
1315
1324
|
model: context.model,
|
|
1316
1325
|
handledLocally: true,
|
|
1317
1326
|
startedAt,
|
|
1327
|
+
error: undefined
|
|
1328
|
+
};
|
|
1329
|
+
const transformedOutput = context.request.transformToolOutput ? await context.request.transformToolOutput(output, executionContext) : output;
|
|
1330
|
+
metadata.output = transformedOutput;
|
|
1331
|
+
const execution = {
|
|
1332
|
+
...executionContext,
|
|
1333
|
+
output: transformedOutput,
|
|
1318
1334
|
durationMs: Date.now() - startedAtMs
|
|
1319
1335
|
};
|
|
1320
1336
|
emitToolExecution(context.request, execution);
|
|
@@ -1485,13 +1501,18 @@ function describeTool(clientId, tool, hasCollision) {
|
|
|
1485
1501
|
}
|
|
1486
1502
|
return;
|
|
1487
1503
|
}
|
|
1504
|
+
var RE_NON_ALPHANUMERIC = /[^A-Za-z0-9_]/g;
|
|
1505
|
+
var RE_MULTIPLE_UNDERSCORES = /_+/g;
|
|
1506
|
+
var RE_LEADING_UNDERSCORES = /^_+/;
|
|
1507
|
+
var RE_TRAILING_UNDERSCORES = /_+$/;
|
|
1508
|
+
var RE_STARTS_WITH_DIGIT = /^[0-9]/;
|
|
1488
1509
|
function sanitizeToolName(input) {
|
|
1489
|
-
const sanitized = input.replace(
|
|
1490
|
-
const trimmed = sanitized.replace(
|
|
1510
|
+
const sanitized = input.replace(RE_NON_ALPHANUMERIC, "_").replace(RE_MULTIPLE_UNDERSCORES, "_");
|
|
1511
|
+
const trimmed = sanitized.replace(RE_LEADING_UNDERSCORES, "").replace(RE_TRAILING_UNDERSCORES, "");
|
|
1491
1512
|
if (!trimmed) {
|
|
1492
1513
|
return "tool";
|
|
1493
1514
|
}
|
|
1494
|
-
if (
|
|
1515
|
+
if (RE_STARTS_WITH_DIGIT.test(trimmed)) {
|
|
1495
1516
|
return `tool_${trimmed}`;
|
|
1496
1517
|
}
|
|
1497
1518
|
return trimmed;
|
|
@@ -1682,8 +1703,6 @@ async function completeWithChatCompletionsPassThrough(options, fetcher, path, re
|
|
|
1682
1703
|
};
|
|
1683
1704
|
}
|
|
1684
1705
|
async function completeWithChatCompletionsWithMCP(options, fetcher, path, request) {
|
|
1685
|
-
const mcpToolset = await resolveMCPToolset(request.mcpClients);
|
|
1686
|
-
const transportTools = toProviderFunctionTools(mcpToolset);
|
|
1687
1706
|
const maxToolRounds = normalizeMaxToolRounds(request.maxToolRounds ?? options.defaultMaxToolRounds);
|
|
1688
1707
|
let messages = buildMessages(request);
|
|
1689
1708
|
let aggregatedUsage;
|
|
@@ -1692,6 +1711,8 @@ async function completeWithChatCompletionsWithMCP(options, fetcher, path, reques
|
|
|
1692
1711
|
const toolCalls = [];
|
|
1693
1712
|
const toolExecutions = [];
|
|
1694
1713
|
for (let round = 1;round <= maxToolRounds + 1; round += 1) {
|
|
1714
|
+
const mcpToolset = await resolveMCPToolset(request.mcpClients);
|
|
1715
|
+
const transportTools = toProviderFunctionTools(mcpToolset);
|
|
1695
1716
|
const response = await fetcher(buildURL(options.baseURL, path), {
|
|
1696
1717
|
method: "POST",
|
|
1697
1718
|
headers: buildHeaders(options),
|
|
@@ -1787,8 +1808,6 @@ async function completeWithResponsesAPIPassThrough(options, fetcher, path, reque
|
|
|
1787
1808
|
};
|
|
1788
1809
|
}
|
|
1789
1810
|
async function completeWithResponsesAPIWithMCP(options, fetcher, path, request) {
|
|
1790
|
-
const mcpToolset = await resolveMCPToolset(request.mcpClients);
|
|
1791
|
-
const transportTools = toResponsesTools(toProviderFunctionTools(mcpToolset));
|
|
1792
1811
|
const maxToolRounds = normalizeMaxToolRounds(request.maxToolRounds ?? options.defaultMaxToolRounds);
|
|
1793
1812
|
let input = buildResponsesInput(request);
|
|
1794
1813
|
let previousResponseId = pickString(isRecord2(request.body) ? request.body.previous_response_id : undefined);
|
|
@@ -1798,6 +1817,8 @@ async function completeWithResponsesAPIWithMCP(options, fetcher, path, request)
|
|
|
1798
1817
|
const executedToolCalls = [];
|
|
1799
1818
|
const toolExecutions = [];
|
|
1800
1819
|
for (let round = 1;round <= maxToolRounds + 1; round += 1) {
|
|
1820
|
+
const mcpToolset = await resolveMCPToolset(request.mcpClients);
|
|
1821
|
+
const transportTools = toResponsesTools(toProviderFunctionTools(mcpToolset));
|
|
1801
1822
|
const response = await fetcher(buildURL(options.baseURL, path), {
|
|
1802
1823
|
method: "POST",
|
|
1803
1824
|
headers: buildHeaders(options),
|
|
@@ -2237,8 +2258,6 @@ async function completePassThrough(options, fetcher, path, request) {
|
|
|
2237
2258
|
};
|
|
2238
2259
|
}
|
|
2239
2260
|
async function completeWithMCPToolLoop(options, fetcher, path, request) {
|
|
2240
|
-
const mcpToolset = await resolveMCPToolset(request.mcpClients);
|
|
2241
|
-
const tools = toAnthropicTools(toProviderFunctionTools(mcpToolset));
|
|
2242
2261
|
const maxToolRounds = normalizeMaxToolRounds(request.maxToolRounds ?? options.defaultMaxToolRounds);
|
|
2243
2262
|
let messages = [{ role: "user", content: request.prompt }];
|
|
2244
2263
|
let aggregatedUsage;
|
|
@@ -2247,6 +2266,8 @@ async function completeWithMCPToolLoop(options, fetcher, path, request) {
|
|
|
2247
2266
|
const toolCalls = [];
|
|
2248
2267
|
const toolExecutions = [];
|
|
2249
2268
|
for (let round = 1;round <= maxToolRounds + 1; round += 1) {
|
|
2269
|
+
const mcpToolset = await resolveMCPToolset(request.mcpClients);
|
|
2270
|
+
const tools = toAnthropicTools(toProviderFunctionTools(mcpToolset));
|
|
2250
2271
|
const response = await fetcher(buildURL(options.baseURL, path), {
|
|
2251
2272
|
method: "POST",
|
|
2252
2273
|
headers: buildHeaders2(options),
|
|
@@ -3056,6 +3077,9 @@ var DEFAULT_SELF_HEAL_PROTOCOL = "extrait.self-heal.v2";
|
|
|
3056
3077
|
var DEFAULT_SELF_HEAL_MAX_CONTEXT_CHARS = 12000;
|
|
3057
3078
|
var DEFAULT_SELF_HEAL_STOP_ON_NO_PROGRESS = true;
|
|
3058
3079
|
var DEFAULT_SELF_HEAL_MAX_ERRORS = 8;
|
|
3080
|
+
var RE_SIMPLE_IDENTIFIER2 = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
|
|
3081
|
+
var RE_ESCAPE_QUOTE = /"/g;
|
|
3082
|
+
var RE_WHITESPACE2 = /\s+/g;
|
|
3059
3083
|
var DEFAULT_SELF_HEAL_MAX_DIAGNOSTICS = 8;
|
|
3060
3084
|
var structuredOutdent = createOutdent({
|
|
3061
3085
|
trimLeadingNewline: true,
|
|
@@ -3400,11 +3424,11 @@ function formatIssuePath(path) {
|
|
|
3400
3424
|
out += `[${segment}]`;
|
|
3401
3425
|
continue;
|
|
3402
3426
|
}
|
|
3403
|
-
if (
|
|
3427
|
+
if (RE_SIMPLE_IDENTIFIER2.test(segment)) {
|
|
3404
3428
|
out += `.${segment}`;
|
|
3405
3429
|
continue;
|
|
3406
3430
|
}
|
|
3407
|
-
out += `["${segment.replace(
|
|
3431
|
+
out += `["${segment.replace(RE_ESCAPE_QUOTE, "\\\"")}"]`;
|
|
3408
3432
|
}
|
|
3409
3433
|
return out;
|
|
3410
3434
|
}
|
|
@@ -3447,7 +3471,7 @@ function buildSelfHealFailureFingerprint(attempt) {
|
|
|
3447
3471
|
return [issues, errors, source].join("::");
|
|
3448
3472
|
}
|
|
3449
3473
|
function normalizeWhitespace(value) {
|
|
3450
|
-
return value.replace(
|
|
3474
|
+
return value.replace(RE_WHITESPACE2, " ").trim();
|
|
3451
3475
|
}
|
|
3452
3476
|
function normalizeStreamConfig(option) {
|
|
3453
3477
|
if (typeof option === "boolean") {
|
package/dist/index.d.ts
CHANGED
|
@@ -12,4 +12,4 @@ export { createOpenAICompatibleAdapter, type OpenAICompatibleAdapterOptions, } f
|
|
|
12
12
|
export { createAnthropicCompatibleAdapter, DEFAULT_ANTHROPIC_MAX_TOKENS, DEFAULT_ANTHROPIC_VERSION, type AnthropicCompatibleAdapterOptions, } from "./providers/anthropic-compatible";
|
|
13
13
|
export { DEFAULT_MAX_TOOL_ROUNDS } from "./providers/mcp-runtime";
|
|
14
14
|
export { createDefaultProviderRegistry, createModelAdapter, createProviderRegistry, registerBuiltinProviders, type BuiltinProviderKind, type ModelAdapterConfig, type ProviderFactory, type ProviderRegistry, type ProviderTransportConfig, } from "./providers/registry";
|
|
15
|
-
export type { CandidateDiagnostics, ExtractJsonCandidatesOptions, ExtractionCandidate, ExtractionHeuristicsOptions, ExtractionParseHint, HTTPHeaders, LLMAdapter, LLMRequest, LLMResponse, LLMStreamCallbacks, LLMStreamChunk, LLMToolCall, LLMToolDebugOptions, LLMToolExecution, LLMToolChoice, MCPCallToolParams, MCPListToolsResult, MCPToolClient, MCPToolDescriptor, MCPToolSchema, LLMUsage, MarkdownCodeBlock, MarkdownCodeOptions, ParseLLMOutputOptions, ParseLLMOutputResult, ParseTraceEvent, PipelineError, StructuredAttempt, StructuredCallOptions, StructuredDebugOptions, StructuredError, StructuredMode, StructuredOptions, StructuredPromptBuilder, StructuredPromptContext, StructuredPromptPayload, StructuredPromptResolver, StructuredPromptValue, StructuredResult, StructuredStreamData, StructuredStreamEvent, StructuredStreamInput, StructuredStreamOptions, StructuredSelfHealInput, ThinkDiagnostics, ThinkBlock, StructuredTraceEvent, } from "./types";
|
|
15
|
+
export type { CandidateDiagnostics, ExtractJsonCandidatesOptions, ExtractionCandidate, ExtractionHeuristicsOptions, ExtractionParseHint, HTTPHeaders, LLMAdapter, LLMRequest, LLMResponse, LLMStreamCallbacks, LLMStreamChunk, LLMToolCall, LLMToolDebugOptions, LLMToolExecution, LLMToolOutputTransformer, LLMToolChoice, MCPCallToolParams, MCPListToolsResult, MCPToolClient, MCPToolDescriptor, MCPToolSchema, LLMUsage, MarkdownCodeBlock, MarkdownCodeOptions, ParseLLMOutputOptions, ParseLLMOutputResult, ParseTraceEvent, PipelineError, StructuredAttempt, StructuredCallOptions, StructuredDebugOptions, StructuredError, StructuredMode, StructuredOptions, StructuredPromptBuilder, StructuredPromptContext, StructuredPromptPayload, StructuredPromptResolver, StructuredPromptValue, StructuredResult, StructuredStreamData, StructuredStreamEvent, StructuredStreamInput, StructuredStreamOptions, StructuredSelfHealInput, ThinkDiagnostics, ThinkBlock, StructuredTraceEvent, } from "./types";
|
package/dist/index.js
CHANGED
|
@@ -219,6 +219,9 @@ function isOnlyWhitespace(value) {
|
|
|
219
219
|
}
|
|
220
220
|
|
|
221
221
|
// src/extract.ts
|
|
222
|
+
var RE_EMPTY_OBJECT = /^\{\s*\}$/;
|
|
223
|
+
var RE_EMPTY_ARRAY = /^\[\s*\]$/;
|
|
224
|
+
var RE_BOUNDARY_CHAR = /[\s,.;:!?`"'()\[\]{}<>]/;
|
|
222
225
|
var DEFAULT_EXTRACTION_HEURISTICS = {
|
|
223
226
|
firstPassMin: 12,
|
|
224
227
|
firstPassCap: 30,
|
|
@@ -443,7 +446,7 @@ function jsonShapeScore(content, acceptArrays) {
|
|
|
443
446
|
const commaCount = countChar(trimmed, ",");
|
|
444
447
|
const quoteCount = countChar(trimmed, '"');
|
|
445
448
|
if (root === "{") {
|
|
446
|
-
if (
|
|
449
|
+
if (RE_EMPTY_OBJECT.test(trimmed)) {
|
|
447
450
|
score += 12;
|
|
448
451
|
} else if (colonCount > 0) {
|
|
449
452
|
score += 22;
|
|
@@ -454,7 +457,7 @@ function jsonShapeScore(content, acceptArrays) {
|
|
|
454
457
|
score += quoteCount % 2 === 0 ? 8 : -8;
|
|
455
458
|
}
|
|
456
459
|
} else {
|
|
457
|
-
score +=
|
|
460
|
+
score += RE_EMPTY_ARRAY.test(trimmed) ? 8 : 4;
|
|
458
461
|
if (colonCount > 0) {
|
|
459
462
|
score += 4;
|
|
460
463
|
}
|
|
@@ -487,7 +490,7 @@ function isBoundary(char) {
|
|
|
487
490
|
if (!char) {
|
|
488
491
|
return true;
|
|
489
492
|
}
|
|
490
|
-
return
|
|
493
|
+
return RE_BOUNDARY_CHAR.test(char);
|
|
491
494
|
}
|
|
492
495
|
function lengthScore(length) {
|
|
493
496
|
return Math.min(120, Math.floor(Math.sqrt(length) * 6));
|
|
@@ -676,6 +679,8 @@ function clamp(value, min, max) {
|
|
|
676
679
|
return Math.max(min, Math.min(max, Math.floor(value)));
|
|
677
680
|
}
|
|
678
681
|
// src/schema.ts
|
|
682
|
+
var RE_SIMPLE_IDENTIFIER = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
|
|
683
|
+
var RE_WHITESPACE = /\s+/g;
|
|
679
684
|
function formatZodSchemaLikeTypeScript(schema) {
|
|
680
685
|
return formatType(schema, 0, new WeakSet);
|
|
681
686
|
}
|
|
@@ -847,7 +852,7 @@ ${lines.join(`
|
|
|
847
852
|
${indent}}`;
|
|
848
853
|
}
|
|
849
854
|
function formatKey(key) {
|
|
850
|
-
return
|
|
855
|
+
return RE_SIMPLE_IDENTIFIER.test(key) ? key : JSON.stringify(key);
|
|
851
856
|
}
|
|
852
857
|
function requiresParentheses(typeText) {
|
|
853
858
|
return typeText.includes(" | ") || typeText.includes(" & ");
|
|
@@ -893,7 +898,7 @@ function readSchemaDescription(schema) {
|
|
|
893
898
|
return;
|
|
894
899
|
}
|
|
895
900
|
function sanitizeDescription(value) {
|
|
896
|
-
return value.replace(
|
|
901
|
+
return value.replace(RE_WHITESPACE, " ").trim();
|
|
897
902
|
}
|
|
898
903
|
|
|
899
904
|
// src/format.ts
|
|
@@ -918,6 +923,8 @@ function resolveSchemaInstruction(instruction) {
|
|
|
918
923
|
}
|
|
919
924
|
// src/think.ts
|
|
920
925
|
var THINK_TAG_NAME = "think";
|
|
926
|
+
var RE_IDENTIFIER_CHAR = /[a-zA-Z0-9:_-]/;
|
|
927
|
+
var RE_NON_LINE_BREAK = /[^\r\n]/g;
|
|
921
928
|
function sanitizeThink(input) {
|
|
922
929
|
const thinkBlocks = [];
|
|
923
930
|
const diagnostics = {
|
|
@@ -1016,6 +1023,9 @@ function parseThinkTagAt(input, index) {
|
|
|
1016
1023
|
if (input[cursor] === "/") {
|
|
1017
1024
|
closing = true;
|
|
1018
1025
|
cursor += 1;
|
|
1026
|
+
while (cursor < input.length && isWhitespace(input[cursor])) {
|
|
1027
|
+
cursor += 1;
|
|
1028
|
+
}
|
|
1019
1029
|
}
|
|
1020
1030
|
if (!matchesIgnoreCase(input, cursor, THINK_TAG_NAME)) {
|
|
1021
1031
|
return null;
|
|
@@ -1072,7 +1082,7 @@ function matchesIgnoreCase(input, index, expected) {
|
|
|
1072
1082
|
return input.slice(index, index + expected.length).toLowerCase() === expected;
|
|
1073
1083
|
}
|
|
1074
1084
|
function isIdentifierChar(char) {
|
|
1075
|
-
return
|
|
1085
|
+
return RE_IDENTIFIER_CHAR.test(char);
|
|
1076
1086
|
}
|
|
1077
1087
|
function countHiddenChars(value) {
|
|
1078
1088
|
let count = 0;
|
|
@@ -1086,9 +1096,10 @@ function countHiddenChars(value) {
|
|
|
1086
1096
|
return count;
|
|
1087
1097
|
}
|
|
1088
1098
|
function maskKeepingLineBreaks(value) {
|
|
1089
|
-
return value.replace(
|
|
1099
|
+
return value.replace(RE_NON_LINE_BREAK, " ");
|
|
1090
1100
|
}
|
|
1091
1101
|
// src/providers/stream-utils.ts
|
|
1102
|
+
var RE_LINE_ENDING = /\r?\n/;
|
|
1092
1103
|
async function consumeSSE(response, onEvent) {
|
|
1093
1104
|
if (!response.body) {
|
|
1094
1105
|
return;
|
|
@@ -1111,7 +1122,7 @@ async function consumeSSE(response, onEvent) {
|
|
|
1111
1122
|
buffer = buffer.slice(boundary + (buffer.startsWith(`\r
|
|
1112
1123
|
\r
|
|
1113
1124
|
`, boundary) ? 4 : 2));
|
|
1114
|
-
const dataLines = rawEvent.split(
|
|
1125
|
+
const dataLines = rawEvent.split(RE_LINE_ENDING).filter((line) => line.startsWith("data:")).map((line) => line.slice(5).trim());
|
|
1115
1126
|
if (dataLines.length === 0) {
|
|
1116
1127
|
continue;
|
|
1117
1128
|
}
|
|
@@ -1121,7 +1132,7 @@ async function consumeSSE(response, onEvent) {
|
|
|
1121
1132
|
}
|
|
1122
1133
|
const remainder = buffer.trim();
|
|
1123
1134
|
if (remainder.length > 0) {
|
|
1124
|
-
const dataLines = remainder.split(
|
|
1135
|
+
const dataLines = remainder.split(RE_LINE_ENDING).filter((line) => line.startsWith("data:")).map((line) => line.slice(5).trim());
|
|
1125
1136
|
if (dataLines.length > 0) {
|
|
1126
1137
|
onEvent(dataLines.join(`
|
|
1127
1138
|
`));
|
|
@@ -1222,20 +1233,25 @@ async function executeMCPToolCalls(calls, toolset, context) {
|
|
|
1222
1233
|
name: tool.remoteName,
|
|
1223
1234
|
arguments: args
|
|
1224
1235
|
});
|
|
1225
|
-
|
|
1226
|
-
const execution = {
|
|
1236
|
+
const executionContext = {
|
|
1227
1237
|
callId,
|
|
1228
|
-
type:
|
|
1238
|
+
type: call.type ?? "function",
|
|
1229
1239
|
name: toolName,
|
|
1230
1240
|
clientId: tool.clientId,
|
|
1231
1241
|
remoteName: tool.remoteName,
|
|
1232
1242
|
arguments: parsedArguments,
|
|
1233
|
-
output,
|
|
1234
1243
|
round: context.round,
|
|
1235
1244
|
provider: context.provider,
|
|
1236
1245
|
model: context.model,
|
|
1237
1246
|
handledLocally: true,
|
|
1238
1247
|
startedAt,
|
|
1248
|
+
error: undefined
|
|
1249
|
+
};
|
|
1250
|
+
const transformedOutput = context.request.transformToolOutput ? await context.request.transformToolOutput(output, executionContext) : output;
|
|
1251
|
+
metadata.output = transformedOutput;
|
|
1252
|
+
const execution = {
|
|
1253
|
+
...executionContext,
|
|
1254
|
+
output: transformedOutput,
|
|
1239
1255
|
durationMs: Date.now() - startedAtMs
|
|
1240
1256
|
};
|
|
1241
1257
|
emitToolExecution(context.request, execution);
|
|
@@ -1406,13 +1422,18 @@ function describeTool(clientId, tool, hasCollision) {
|
|
|
1406
1422
|
}
|
|
1407
1423
|
return;
|
|
1408
1424
|
}
|
|
1425
|
+
var RE_NON_ALPHANUMERIC = /[^A-Za-z0-9_]/g;
|
|
1426
|
+
var RE_MULTIPLE_UNDERSCORES = /_+/g;
|
|
1427
|
+
var RE_LEADING_UNDERSCORES = /^_+/;
|
|
1428
|
+
var RE_TRAILING_UNDERSCORES = /_+$/;
|
|
1429
|
+
var RE_STARTS_WITH_DIGIT = /^[0-9]/;
|
|
1409
1430
|
function sanitizeToolName(input) {
|
|
1410
|
-
const sanitized = input.replace(
|
|
1411
|
-
const trimmed = sanitized.replace(
|
|
1431
|
+
const sanitized = input.replace(RE_NON_ALPHANUMERIC, "_").replace(RE_MULTIPLE_UNDERSCORES, "_");
|
|
1432
|
+
const trimmed = sanitized.replace(RE_LEADING_UNDERSCORES, "").replace(RE_TRAILING_UNDERSCORES, "");
|
|
1412
1433
|
if (!trimmed) {
|
|
1413
1434
|
return "tool";
|
|
1414
1435
|
}
|
|
1415
|
-
if (
|
|
1436
|
+
if (RE_STARTS_WITH_DIGIT.test(trimmed)) {
|
|
1416
1437
|
return `tool_${trimmed}`;
|
|
1417
1438
|
}
|
|
1418
1439
|
return trimmed;
|
|
@@ -1603,8 +1624,6 @@ async function completeWithChatCompletionsPassThrough(options, fetcher, path, re
|
|
|
1603
1624
|
};
|
|
1604
1625
|
}
|
|
1605
1626
|
async function completeWithChatCompletionsWithMCP(options, fetcher, path, request) {
|
|
1606
|
-
const mcpToolset = await resolveMCPToolset(request.mcpClients);
|
|
1607
|
-
const transportTools = toProviderFunctionTools(mcpToolset);
|
|
1608
1627
|
const maxToolRounds = normalizeMaxToolRounds(request.maxToolRounds ?? options.defaultMaxToolRounds);
|
|
1609
1628
|
let messages = buildMessages(request);
|
|
1610
1629
|
let aggregatedUsage;
|
|
@@ -1613,6 +1632,8 @@ async function completeWithChatCompletionsWithMCP(options, fetcher, path, reques
|
|
|
1613
1632
|
const toolCalls = [];
|
|
1614
1633
|
const toolExecutions = [];
|
|
1615
1634
|
for (let round = 1;round <= maxToolRounds + 1; round += 1) {
|
|
1635
|
+
const mcpToolset = await resolveMCPToolset(request.mcpClients);
|
|
1636
|
+
const transportTools = toProviderFunctionTools(mcpToolset);
|
|
1616
1637
|
const response = await fetcher(buildURL(options.baseURL, path), {
|
|
1617
1638
|
method: "POST",
|
|
1618
1639
|
headers: buildHeaders(options),
|
|
@@ -1708,8 +1729,6 @@ async function completeWithResponsesAPIPassThrough(options, fetcher, path, reque
|
|
|
1708
1729
|
};
|
|
1709
1730
|
}
|
|
1710
1731
|
async function completeWithResponsesAPIWithMCP(options, fetcher, path, request) {
|
|
1711
|
-
const mcpToolset = await resolveMCPToolset(request.mcpClients);
|
|
1712
|
-
const transportTools = toResponsesTools(toProviderFunctionTools(mcpToolset));
|
|
1713
1732
|
const maxToolRounds = normalizeMaxToolRounds(request.maxToolRounds ?? options.defaultMaxToolRounds);
|
|
1714
1733
|
let input = buildResponsesInput(request);
|
|
1715
1734
|
let previousResponseId = pickString(isRecord2(request.body) ? request.body.previous_response_id : undefined);
|
|
@@ -1719,6 +1738,8 @@ async function completeWithResponsesAPIWithMCP(options, fetcher, path, request)
|
|
|
1719
1738
|
const executedToolCalls = [];
|
|
1720
1739
|
const toolExecutions = [];
|
|
1721
1740
|
for (let round = 1;round <= maxToolRounds + 1; round += 1) {
|
|
1741
|
+
const mcpToolset = await resolveMCPToolset(request.mcpClients);
|
|
1742
|
+
const transportTools = toResponsesTools(toProviderFunctionTools(mcpToolset));
|
|
1722
1743
|
const response = await fetcher(buildURL(options.baseURL, path), {
|
|
1723
1744
|
method: "POST",
|
|
1724
1745
|
headers: buildHeaders(options),
|
|
@@ -2158,8 +2179,6 @@ async function completePassThrough(options, fetcher, path, request) {
|
|
|
2158
2179
|
};
|
|
2159
2180
|
}
|
|
2160
2181
|
async function completeWithMCPToolLoop(options, fetcher, path, request) {
|
|
2161
|
-
const mcpToolset = await resolveMCPToolset(request.mcpClients);
|
|
2162
|
-
const tools = toAnthropicTools(toProviderFunctionTools(mcpToolset));
|
|
2163
2182
|
const maxToolRounds = normalizeMaxToolRounds(request.maxToolRounds ?? options.defaultMaxToolRounds);
|
|
2164
2183
|
let messages = [{ role: "user", content: request.prompt }];
|
|
2165
2184
|
let aggregatedUsage;
|
|
@@ -2168,6 +2187,8 @@ async function completeWithMCPToolLoop(options, fetcher, path, request) {
|
|
|
2168
2187
|
const toolCalls = [];
|
|
2169
2188
|
const toolExecutions = [];
|
|
2170
2189
|
for (let round = 1;round <= maxToolRounds + 1; round += 1) {
|
|
2190
|
+
const mcpToolset = await resolveMCPToolset(request.mcpClients);
|
|
2191
|
+
const tools = toAnthropicTools(toProviderFunctionTools(mcpToolset));
|
|
2171
2192
|
const response = await fetcher(buildURL(options.baseURL, path), {
|
|
2172
2193
|
method: "POST",
|
|
2173
2194
|
headers: buildHeaders2(options),
|
|
@@ -2977,6 +2998,9 @@ var DEFAULT_SELF_HEAL_PROTOCOL = "extrait.self-heal.v2";
|
|
|
2977
2998
|
var DEFAULT_SELF_HEAL_MAX_CONTEXT_CHARS = 12000;
|
|
2978
2999
|
var DEFAULT_SELF_HEAL_STOP_ON_NO_PROGRESS = true;
|
|
2979
3000
|
var DEFAULT_SELF_HEAL_MAX_ERRORS = 8;
|
|
3001
|
+
var RE_SIMPLE_IDENTIFIER2 = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
|
|
3002
|
+
var RE_ESCAPE_QUOTE = /"/g;
|
|
3003
|
+
var RE_WHITESPACE2 = /\s+/g;
|
|
2980
3004
|
var DEFAULT_SELF_HEAL_MAX_DIAGNOSTICS = 8;
|
|
2981
3005
|
var structuredOutdent = createOutdent({
|
|
2982
3006
|
trimLeadingNewline: true,
|
|
@@ -3321,11 +3345,11 @@ function formatIssuePath(path) {
|
|
|
3321
3345
|
out += `[${segment}]`;
|
|
3322
3346
|
continue;
|
|
3323
3347
|
}
|
|
3324
|
-
if (
|
|
3348
|
+
if (RE_SIMPLE_IDENTIFIER2.test(segment)) {
|
|
3325
3349
|
out += `.${segment}`;
|
|
3326
3350
|
continue;
|
|
3327
3351
|
}
|
|
3328
|
-
out += `["${segment.replace(
|
|
3352
|
+
out += `["${segment.replace(RE_ESCAPE_QUOTE, "\\\"")}"]`;
|
|
3329
3353
|
}
|
|
3330
3354
|
return out;
|
|
3331
3355
|
}
|
|
@@ -3368,7 +3392,7 @@ function buildSelfHealFailureFingerprint(attempt) {
|
|
|
3368
3392
|
return [issues, errors, source].join("::");
|
|
3369
3393
|
}
|
|
3370
3394
|
function normalizeWhitespace(value) {
|
|
3371
|
-
return value.replace(
|
|
3395
|
+
return value.replace(RE_WHITESPACE2, " ").trim();
|
|
3372
3396
|
}
|
|
3373
3397
|
function normalizeStreamConfig(option) {
|
|
3374
3398
|
if (typeof option === "boolean") {
|
|
@@ -36,3 +36,4 @@ export declare function normalizeMaxToolRounds(value: number | undefined): numbe
|
|
|
36
36
|
export declare function parseToolArguments(value: unknown): unknown;
|
|
37
37
|
export declare function stringifyToolOutput(value: unknown): string;
|
|
38
38
|
export declare function formatToolExecutionDebugLine(execution: LLMToolExecution): string;
|
|
39
|
+
export declare function sanitizeToolName(input: string): string;
|
package/dist/types.d.ts
CHANGED
|
@@ -129,6 +129,7 @@ export interface LLMRequest {
|
|
|
129
129
|
parallelToolCalls?: boolean;
|
|
130
130
|
maxToolRounds?: number;
|
|
131
131
|
onToolExecution?: (execution: LLMToolExecution) => void;
|
|
132
|
+
transformToolOutput?: LLMToolOutputTransformer;
|
|
132
133
|
toolDebug?: boolean | LLMToolDebugOptions;
|
|
133
134
|
body?: Record<string, unknown>;
|
|
134
135
|
}
|
|
@@ -189,6 +190,7 @@ export interface LLMToolExecution {
|
|
|
189
190
|
startedAt: string;
|
|
190
191
|
durationMs?: number;
|
|
191
192
|
}
|
|
193
|
+
export type LLMToolOutputTransformer = (output: unknown, execution: Omit<LLMToolExecution, "output" | "durationMs">) => unknown | Promise<unknown>;
|
|
192
194
|
export interface LLMToolDebugOptions {
|
|
193
195
|
enabled?: boolean;
|
|
194
196
|
logger?: (line: string) => void;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "extrait",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -34,16 +34,16 @@
|
|
|
34
34
|
"build:types": "bunx tsc -p tsconfig.build.json",
|
|
35
35
|
"lint": "bunx tsc -p tsconfig.lint.json",
|
|
36
36
|
"prepublishOnly": "bun run lint && bun run build && bun run build:types",
|
|
37
|
-
"test": "bun test",
|
|
37
|
+
"test": "bun test tests/ --reporter=dots --only-failures",
|
|
38
38
|
"typecheck": "bunx tsc --noEmit"
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
41
|
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
42
|
-
"jsonrepair": "^3.13.
|
|
43
|
-
"zod": "^3.
|
|
42
|
+
"jsonrepair": "^3.13.2",
|
|
43
|
+
"zod": "^3.25.76"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
46
46
|
"@types/bun": "latest",
|
|
47
|
-
"typescript": "^5"
|
|
47
|
+
"typescript": "^5.9.3"
|
|
48
48
|
}
|
|
49
49
|
}
|