extrait 0.1.2 → 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/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 (/^\{\s*\}$/.test(trimmed)) {
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 += /^\[\s*\]$/.test(trimmed) ? 8 : 4;
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 /[\s,.;:!?`"'()[\]{}<>]/.test(char);
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 /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(key) ? key : JSON.stringify(key);
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(/\s+/g, " ").trim();
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 /[a-zA-Z0-9:_-]/.test(char);
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(/[^\r\n]/g, " ");
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(/\r?\n/).filter((line) => line.startsWith("data:")).map((line) => line.slice(5).trim());
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(/\r?\n/).filter((line) => line.startsWith("data:")).map((line) => line.slice(5).trim());
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
  `));
@@ -1490,13 +1501,18 @@ function describeTool(clientId, tool, hasCollision) {
1490
1501
  }
1491
1502
  return;
1492
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]/;
1493
1509
  function sanitizeToolName(input) {
1494
- const sanitized = input.replace(/[^A-Za-z0-9_]/g, "_").replace(/_+/g, "_");
1495
- const trimmed = sanitized.replace(/^_+/, "").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, "");
1496
1512
  if (!trimmed) {
1497
1513
  return "tool";
1498
1514
  }
1499
- if (/^[0-9]/.test(trimmed)) {
1515
+ if (RE_STARTS_WITH_DIGIT.test(trimmed)) {
1500
1516
  return `tool_${trimmed}`;
1501
1517
  }
1502
1518
  return trimmed;
@@ -1687,8 +1703,6 @@ async function completeWithChatCompletionsPassThrough(options, fetcher, path, re
1687
1703
  };
1688
1704
  }
1689
1705
  async function completeWithChatCompletionsWithMCP(options, fetcher, path, request) {
1690
- const mcpToolset = await resolveMCPToolset(request.mcpClients);
1691
- const transportTools = toProviderFunctionTools(mcpToolset);
1692
1706
  const maxToolRounds = normalizeMaxToolRounds(request.maxToolRounds ?? options.defaultMaxToolRounds);
1693
1707
  let messages = buildMessages(request);
1694
1708
  let aggregatedUsage;
@@ -1697,6 +1711,8 @@ async function completeWithChatCompletionsWithMCP(options, fetcher, path, reques
1697
1711
  const toolCalls = [];
1698
1712
  const toolExecutions = [];
1699
1713
  for (let round = 1;round <= maxToolRounds + 1; round += 1) {
1714
+ const mcpToolset = await resolveMCPToolset(request.mcpClients);
1715
+ const transportTools = toProviderFunctionTools(mcpToolset);
1700
1716
  const response = await fetcher(buildURL(options.baseURL, path), {
1701
1717
  method: "POST",
1702
1718
  headers: buildHeaders(options),
@@ -1792,8 +1808,6 @@ async function completeWithResponsesAPIPassThrough(options, fetcher, path, reque
1792
1808
  };
1793
1809
  }
1794
1810
  async function completeWithResponsesAPIWithMCP(options, fetcher, path, request) {
1795
- const mcpToolset = await resolveMCPToolset(request.mcpClients);
1796
- const transportTools = toResponsesTools(toProviderFunctionTools(mcpToolset));
1797
1811
  const maxToolRounds = normalizeMaxToolRounds(request.maxToolRounds ?? options.defaultMaxToolRounds);
1798
1812
  let input = buildResponsesInput(request);
1799
1813
  let previousResponseId = pickString(isRecord2(request.body) ? request.body.previous_response_id : undefined);
@@ -1803,6 +1817,8 @@ async function completeWithResponsesAPIWithMCP(options, fetcher, path, request)
1803
1817
  const executedToolCalls = [];
1804
1818
  const toolExecutions = [];
1805
1819
  for (let round = 1;round <= maxToolRounds + 1; round += 1) {
1820
+ const mcpToolset = await resolveMCPToolset(request.mcpClients);
1821
+ const transportTools = toResponsesTools(toProviderFunctionTools(mcpToolset));
1806
1822
  const response = await fetcher(buildURL(options.baseURL, path), {
1807
1823
  method: "POST",
1808
1824
  headers: buildHeaders(options),
@@ -2242,8 +2258,6 @@ async function completePassThrough(options, fetcher, path, request) {
2242
2258
  };
2243
2259
  }
2244
2260
  async function completeWithMCPToolLoop(options, fetcher, path, request) {
2245
- const mcpToolset = await resolveMCPToolset(request.mcpClients);
2246
- const tools = toAnthropicTools(toProviderFunctionTools(mcpToolset));
2247
2261
  const maxToolRounds = normalizeMaxToolRounds(request.maxToolRounds ?? options.defaultMaxToolRounds);
2248
2262
  let messages = [{ role: "user", content: request.prompt }];
2249
2263
  let aggregatedUsage;
@@ -2252,6 +2266,8 @@ async function completeWithMCPToolLoop(options, fetcher, path, request) {
2252
2266
  const toolCalls = [];
2253
2267
  const toolExecutions = [];
2254
2268
  for (let round = 1;round <= maxToolRounds + 1; round += 1) {
2269
+ const mcpToolset = await resolveMCPToolset(request.mcpClients);
2270
+ const tools = toAnthropicTools(toProviderFunctionTools(mcpToolset));
2255
2271
  const response = await fetcher(buildURL(options.baseURL, path), {
2256
2272
  method: "POST",
2257
2273
  headers: buildHeaders2(options),
@@ -3061,6 +3077,9 @@ var DEFAULT_SELF_HEAL_PROTOCOL = "extrait.self-heal.v2";
3061
3077
  var DEFAULT_SELF_HEAL_MAX_CONTEXT_CHARS = 12000;
3062
3078
  var DEFAULT_SELF_HEAL_STOP_ON_NO_PROGRESS = true;
3063
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;
3064
3083
  var DEFAULT_SELF_HEAL_MAX_DIAGNOSTICS = 8;
3065
3084
  var structuredOutdent = createOutdent({
3066
3085
  trimLeadingNewline: true,
@@ -3405,11 +3424,11 @@ function formatIssuePath(path) {
3405
3424
  out += `[${segment}]`;
3406
3425
  continue;
3407
3426
  }
3408
- if (/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(segment)) {
3427
+ if (RE_SIMPLE_IDENTIFIER2.test(segment)) {
3409
3428
  out += `.${segment}`;
3410
3429
  continue;
3411
3430
  }
3412
- out += `["${segment.replace(/"/g, "\\\"")}"]`;
3431
+ out += `["${segment.replace(RE_ESCAPE_QUOTE, "\\\"")}"]`;
3413
3432
  }
3414
3433
  return out;
3415
3434
  }
@@ -3452,7 +3471,7 @@ function buildSelfHealFailureFingerprint(attempt) {
3452
3471
  return [issues, errors, source].join("::");
3453
3472
  }
3454
3473
  function normalizeWhitespace(value) {
3455
- return value.replace(/\s+/g, " ").trim();
3474
+ return value.replace(RE_WHITESPACE2, " ").trim();
3456
3475
  }
3457
3476
  function normalizeStreamConfig(option) {
3458
3477
  if (typeof option === "boolean") {
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 (/^\{\s*\}$/.test(trimmed)) {
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 += /^\[\s*\]$/.test(trimmed) ? 8 : 4;
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 /[\s,.;:!?`"'()[\]{}<>]/.test(char);
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 /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(key) ? key : JSON.stringify(key);
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(/\s+/g, " ").trim();
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 /[a-zA-Z0-9:_-]/.test(char);
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(/[^\r\n]/g, " ");
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(/\r?\n/).filter((line) => line.startsWith("data:")).map((line) => line.slice(5).trim());
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(/\r?\n/).filter((line) => line.startsWith("data:")).map((line) => line.slice(5).trim());
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
  `));
@@ -1411,13 +1422,18 @@ function describeTool(clientId, tool, hasCollision) {
1411
1422
  }
1412
1423
  return;
1413
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]/;
1414
1430
  function sanitizeToolName(input) {
1415
- const sanitized = input.replace(/[^A-Za-z0-9_]/g, "_").replace(/_+/g, "_");
1416
- const trimmed = sanitized.replace(/^_+/, "").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, "");
1417
1433
  if (!trimmed) {
1418
1434
  return "tool";
1419
1435
  }
1420
- if (/^[0-9]/.test(trimmed)) {
1436
+ if (RE_STARTS_WITH_DIGIT.test(trimmed)) {
1421
1437
  return `tool_${trimmed}`;
1422
1438
  }
1423
1439
  return trimmed;
@@ -1608,8 +1624,6 @@ async function completeWithChatCompletionsPassThrough(options, fetcher, path, re
1608
1624
  };
1609
1625
  }
1610
1626
  async function completeWithChatCompletionsWithMCP(options, fetcher, path, request) {
1611
- const mcpToolset = await resolveMCPToolset(request.mcpClients);
1612
- const transportTools = toProviderFunctionTools(mcpToolset);
1613
1627
  const maxToolRounds = normalizeMaxToolRounds(request.maxToolRounds ?? options.defaultMaxToolRounds);
1614
1628
  let messages = buildMessages(request);
1615
1629
  let aggregatedUsage;
@@ -1618,6 +1632,8 @@ async function completeWithChatCompletionsWithMCP(options, fetcher, path, reques
1618
1632
  const toolCalls = [];
1619
1633
  const toolExecutions = [];
1620
1634
  for (let round = 1;round <= maxToolRounds + 1; round += 1) {
1635
+ const mcpToolset = await resolveMCPToolset(request.mcpClients);
1636
+ const transportTools = toProviderFunctionTools(mcpToolset);
1621
1637
  const response = await fetcher(buildURL(options.baseURL, path), {
1622
1638
  method: "POST",
1623
1639
  headers: buildHeaders(options),
@@ -1713,8 +1729,6 @@ async function completeWithResponsesAPIPassThrough(options, fetcher, path, reque
1713
1729
  };
1714
1730
  }
1715
1731
  async function completeWithResponsesAPIWithMCP(options, fetcher, path, request) {
1716
- const mcpToolset = await resolveMCPToolset(request.mcpClients);
1717
- const transportTools = toResponsesTools(toProviderFunctionTools(mcpToolset));
1718
1732
  const maxToolRounds = normalizeMaxToolRounds(request.maxToolRounds ?? options.defaultMaxToolRounds);
1719
1733
  let input = buildResponsesInput(request);
1720
1734
  let previousResponseId = pickString(isRecord2(request.body) ? request.body.previous_response_id : undefined);
@@ -1724,6 +1738,8 @@ async function completeWithResponsesAPIWithMCP(options, fetcher, path, request)
1724
1738
  const executedToolCalls = [];
1725
1739
  const toolExecutions = [];
1726
1740
  for (let round = 1;round <= maxToolRounds + 1; round += 1) {
1741
+ const mcpToolset = await resolveMCPToolset(request.mcpClients);
1742
+ const transportTools = toResponsesTools(toProviderFunctionTools(mcpToolset));
1727
1743
  const response = await fetcher(buildURL(options.baseURL, path), {
1728
1744
  method: "POST",
1729
1745
  headers: buildHeaders(options),
@@ -2163,8 +2179,6 @@ async function completePassThrough(options, fetcher, path, request) {
2163
2179
  };
2164
2180
  }
2165
2181
  async function completeWithMCPToolLoop(options, fetcher, path, request) {
2166
- const mcpToolset = await resolveMCPToolset(request.mcpClients);
2167
- const tools = toAnthropicTools(toProviderFunctionTools(mcpToolset));
2168
2182
  const maxToolRounds = normalizeMaxToolRounds(request.maxToolRounds ?? options.defaultMaxToolRounds);
2169
2183
  let messages = [{ role: "user", content: request.prompt }];
2170
2184
  let aggregatedUsage;
@@ -2173,6 +2187,8 @@ async function completeWithMCPToolLoop(options, fetcher, path, request) {
2173
2187
  const toolCalls = [];
2174
2188
  const toolExecutions = [];
2175
2189
  for (let round = 1;round <= maxToolRounds + 1; round += 1) {
2190
+ const mcpToolset = await resolveMCPToolset(request.mcpClients);
2191
+ const tools = toAnthropicTools(toProviderFunctionTools(mcpToolset));
2176
2192
  const response = await fetcher(buildURL(options.baseURL, path), {
2177
2193
  method: "POST",
2178
2194
  headers: buildHeaders2(options),
@@ -2982,6 +2998,9 @@ var DEFAULT_SELF_HEAL_PROTOCOL = "extrait.self-heal.v2";
2982
2998
  var DEFAULT_SELF_HEAL_MAX_CONTEXT_CHARS = 12000;
2983
2999
  var DEFAULT_SELF_HEAL_STOP_ON_NO_PROGRESS = true;
2984
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;
2985
3004
  var DEFAULT_SELF_HEAL_MAX_DIAGNOSTICS = 8;
2986
3005
  var structuredOutdent = createOutdent({
2987
3006
  trimLeadingNewline: true,
@@ -3326,11 +3345,11 @@ function formatIssuePath(path) {
3326
3345
  out += `[${segment}]`;
3327
3346
  continue;
3328
3347
  }
3329
- if (/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(segment)) {
3348
+ if (RE_SIMPLE_IDENTIFIER2.test(segment)) {
3330
3349
  out += `.${segment}`;
3331
3350
  continue;
3332
3351
  }
3333
- out += `["${segment.replace(/"/g, "\\\"")}"]`;
3352
+ out += `["${segment.replace(RE_ESCAPE_QUOTE, "\\\"")}"]`;
3334
3353
  }
3335
3354
  return out;
3336
3355
  }
@@ -3373,7 +3392,7 @@ function buildSelfHealFailureFingerprint(attempt) {
3373
3392
  return [issues, errors, source].join("::");
3374
3393
  }
3375
3394
  function normalizeWhitespace(value) {
3376
- return value.replace(/\s+/g, " ").trim();
3395
+ return value.replace(RE_WHITESPACE2, " ").trim();
3377
3396
  }
3378
3397
  function normalizeStreamConfig(option) {
3379
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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "extrait",
3
- "version": "0.1.2",
3
+ "version": "0.2.0",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",