extrait 0.5.6 → 0.6.1

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
@@ -52,6 +52,7 @@ __export(exports_src, {
52
52
  inspectSchemaMetadata: () => inspectSchemaMetadata,
53
53
  inferSchemaExample: () => inferSchemaExample,
54
54
  images: () => images,
55
+ generate: () => generate,
55
56
  formatZodIssues: () => formatZodIssues,
56
57
  formatPrompt: () => formatPrompt,
57
58
  extractMarkdownCodeBlocks: () => extractMarkdownCodeBlocks,
@@ -1552,7 +1553,15 @@ function normalizeBaseURL(baseURL) {
1552
1553
  return baseURL.endsWith("/") ? baseURL : `${baseURL}/`;
1553
1554
  }
1554
1555
  function buildURL(baseURL, path) {
1555
- return new URL(path, normalizeBaseURL(baseURL)).toString();
1556
+ try {
1557
+ return new URL(path).toString();
1558
+ } catch {}
1559
+ const base = new URL(normalizeBaseURL(baseURL));
1560
+ const resolvedPath = new URL(path, "http://provider-path.local");
1561
+ base.pathname = mergePathnames(base.pathname, resolvedPath.pathname);
1562
+ base.search = resolvedPath.search;
1563
+ base.hash = resolvedPath.hash;
1564
+ return base.toString();
1556
1565
  }
1557
1566
  function safeJSONParse(input) {
1558
1567
  try {
@@ -1627,6 +1636,36 @@ function addOptional(a, b) {
1627
1636
  }
1628
1637
  return (a ?? 0) + (b ?? 0);
1629
1638
  }
1639
+ function mergePathnames(basePathname, pathPathname) {
1640
+ const baseSegments = splitPathSegments(basePathname);
1641
+ const pathSegments = splitPathSegments(pathPathname);
1642
+ const overlap = findPathOverlap(baseSegments, pathSegments);
1643
+ const mergedSegments = [...baseSegments, ...pathSegments.slice(overlap)];
1644
+ if (mergedSegments.length === 0) {
1645
+ return "/";
1646
+ }
1647
+ const mergedPathname = `/${mergedSegments.join("/")}`;
1648
+ return pathPathname.endsWith("/") && pathPathname !== "/" ? `${mergedPathname}/` : mergedPathname;
1649
+ }
1650
+ function splitPathSegments(pathname) {
1651
+ return pathname.split("/").filter((segment) => segment.length > 0);
1652
+ }
1653
+ function findPathOverlap(baseSegments, pathSegments) {
1654
+ const maxOverlap = Math.min(baseSegments.length, pathSegments.length);
1655
+ for (let size = maxOverlap;size > 0; size -= 1) {
1656
+ let matches = true;
1657
+ for (let index = 0;index < size; index += 1) {
1658
+ if (baseSegments[baseSegments.length - size + index] !== pathSegments[index]) {
1659
+ matches = false;
1660
+ break;
1661
+ }
1662
+ }
1663
+ if (matches) {
1664
+ return size;
1665
+ }
1666
+ }
1667
+ return 0;
1668
+ }
1630
1669
 
1631
1670
  // src/providers/openai-compatible.ts
1632
1671
  function createOpenAICompatibleAdapter(options) {
@@ -1661,6 +1700,7 @@ function createOpenAICompatibleAdapter(options) {
1661
1700
  model: options.model,
1662
1701
  messages: buildMessages(request),
1663
1702
  temperature: request.temperature,
1703
+ reasoning_effort: toOpenAIReasoningEffort(request.reasoningEffort),
1664
1704
  max_tokens: request.maxTokens,
1665
1705
  stream: true
1666
1706
  })),
@@ -1672,6 +1712,7 @@ function createOpenAICompatibleAdapter(options) {
1672
1712
  }
1673
1713
  callbacks.onStart?.();
1674
1714
  let text = "";
1715
+ let reasoning = "";
1675
1716
  let usage;
1676
1717
  let finishReason;
1677
1718
  await consumeSSE(response, (data) => {
@@ -1683,6 +1724,7 @@ function createOpenAICompatibleAdapter(options) {
1683
1724
  return;
1684
1725
  }
1685
1726
  const delta = pickAssistantDelta(json);
1727
+ const reasoningDelta = pickAssistantReasoningDelta(json);
1686
1728
  const chunkUsage = pickUsage(json);
1687
1729
  const chunkFinishReason = pickFinishReason(json);
1688
1730
  usage = preferLatestUsage(usage, chunkUsage);
@@ -1693,9 +1735,13 @@ function createOpenAICompatibleAdapter(options) {
1693
1735
  text += delta;
1694
1736
  callbacks.onToken?.(delta);
1695
1737
  }
1696
- if (delta || chunkUsage || chunkFinishReason) {
1738
+ if (reasoningDelta) {
1739
+ reasoning += reasoningDelta;
1740
+ }
1741
+ if (delta || reasoningDelta || chunkUsage || chunkFinishReason) {
1697
1742
  const chunk = {
1698
1743
  textDelta: delta,
1744
+ reasoningDelta: reasoningDelta || undefined,
1699
1745
  raw: json,
1700
1746
  usage: chunkUsage,
1701
1747
  finishReason: chunkFinishReason
@@ -1703,7 +1749,12 @@ function createOpenAICompatibleAdapter(options) {
1703
1749
  callbacks.onChunk?.(chunk);
1704
1750
  }
1705
1751
  });
1706
- const out = { text, usage, finishReason };
1752
+ const out = {
1753
+ text,
1754
+ reasoning: reasoning.length > 0 ? reasoning : undefined,
1755
+ usage,
1756
+ finishReason
1757
+ };
1707
1758
  callbacks.onComplete?.(out);
1708
1759
  return out;
1709
1760
  },
@@ -1763,6 +1814,7 @@ async function completeWithChatCompletionsPassThrough(options, fetcher, path, re
1763
1814
  model: options.model,
1764
1815
  messages: buildMessages(request),
1765
1816
  temperature: request.temperature,
1817
+ reasoning_effort: toOpenAIReasoningEffort(request.reasoningEffort),
1766
1818
  max_tokens: request.maxTokens,
1767
1819
  stream: false
1768
1820
  })),
@@ -1772,20 +1824,41 @@ async function completeWithChatCompletionsPassThrough(options, fetcher, path, re
1772
1824
  const message = await response.text();
1773
1825
  throw new Error(`HTTP ${response.status}: ${message}`);
1774
1826
  }
1775
- const payload = await response.json();
1827
+ const payload = await parseOpenAICompatibleJSONResponse(response, "Failed to parse OpenAI-compatible chat completion response");
1776
1828
  const assistantMessage = pickAssistantMessage(payload);
1777
1829
  if (!assistantMessage) {
1778
1830
  throw new Error("No assistant message in OpenAI-compatible response.");
1779
1831
  }
1780
1832
  const toolCalls = pickChatToolCalls(payload);
1833
+ const reasoning = pickAssistantReasoning(payload);
1781
1834
  return {
1782
1835
  text: pickAssistantText(payload),
1836
+ reasoning: reasoning.length > 0 ? reasoning : undefined,
1783
1837
  raw: payload,
1784
1838
  usage: pickUsage(payload),
1785
1839
  finishReason: pickFinishReason(payload),
1786
1840
  toolCalls: toolCalls.length > 0 ? toolCalls : undefined
1787
1841
  };
1788
1842
  }
1843
+ async function parseOpenAICompatibleJSONResponse(response, context) {
1844
+ const rawBody = await response.text();
1845
+ try {
1846
+ return JSON.parse(rawBody);
1847
+ } catch (error) {
1848
+ const message = error instanceof Error ? error.message : String(error);
1849
+ throw new Error(`${context} (HTTP ${response.status}): ${message}. Raw body: ${formatResponseBodyForError(rawBody)}`);
1850
+ }
1851
+ }
1852
+ function formatResponseBodyForError(rawBody, maxLength = 2000) {
1853
+ const normalized = rawBody.trim();
1854
+ if (normalized.length === 0) {
1855
+ return "[empty body]";
1856
+ }
1857
+ if (normalized.length <= maxLength) {
1858
+ return normalized;
1859
+ }
1860
+ return `${normalized.slice(0, maxLength)}...[truncated ${normalized.length - maxLength} chars]`;
1861
+ }
1789
1862
  async function completeWithChatCompletionsWithMCP(options, fetcher, path, request) {
1790
1863
  const maxToolRounds = normalizeMaxToolRounds(request.maxToolRounds ?? options.defaultMaxToolRounds);
1791
1864
  let messages = buildMessages(request);
@@ -1806,6 +1879,7 @@ async function completeWithChatCompletionsWithMCP(options, fetcher, path, reques
1806
1879
  model: options.model,
1807
1880
  messages,
1808
1881
  temperature: request.temperature,
1882
+ reasoning_effort: toOpenAIReasoningEffort(request.reasoningEffort),
1809
1883
  max_tokens: request.maxTokens,
1810
1884
  tools: transportTools,
1811
1885
  tool_choice: request.toolChoice,
@@ -1827,8 +1901,10 @@ async function completeWithChatCompletionsWithMCP(options, fetcher, path, reques
1827
1901
  throw new Error("No assistant message in OpenAI-compatible response.");
1828
1902
  }
1829
1903
  if (calledTools.length === 0) {
1904
+ const reasoning = pickAssistantReasoning(payload);
1830
1905
  return {
1831
1906
  text: pickAssistantText(payload),
1907
+ reasoning: reasoning.length > 0 ? reasoning : undefined,
1832
1908
  raw: payload,
1833
1909
  usage: aggregatedUsage,
1834
1910
  finishReason,
@@ -1856,6 +1932,10 @@ async function completeWithChatCompletionsWithMCP(options, fetcher, path, reques
1856
1932
  }
1857
1933
  return {
1858
1934
  text: pickAssistantText(lastPayload ?? {}),
1935
+ reasoning: (() => {
1936
+ const value = pickAssistantReasoning(lastPayload ?? {});
1937
+ return value.length > 0 ? value : undefined;
1938
+ })(),
1859
1939
  raw: lastPayload,
1860
1940
  usage: aggregatedUsage,
1861
1941
  finishReason,
@@ -1875,6 +1955,7 @@ async function completeWithResponsesAPIPassThrough(options, fetcher, path, reque
1875
1955
  input: buildResponsesInput(request),
1876
1956
  previous_response_id: pickString(body?.previous_response_id),
1877
1957
  temperature: request.temperature,
1958
+ reasoning_effort: toOpenAIReasoningEffort(request.reasoningEffort),
1878
1959
  max_output_tokens: request.maxTokens
1879
1960
  })),
1880
1961
  signal: request.signal
@@ -1915,6 +1996,7 @@ async function completeWithResponsesAPIWithMCP(options, fetcher, path, request)
1915
1996
  input,
1916
1997
  previous_response_id: previousResponseId,
1917
1998
  temperature: request.temperature,
1999
+ reasoning_effort: toOpenAIReasoningEffort(request.reasoningEffort),
1918
2000
  max_output_tokens: request.maxTokens,
1919
2001
  tools: transportTools,
1920
2002
  tool_choice: request.toolChoice,
@@ -1979,6 +2061,8 @@ async function streamWithChatCompletionsWithMCP(options, fetcher, path, request,
1979
2061
  const executedToolCalls = [];
1980
2062
  const toolExecutions = [];
1981
2063
  callbacks.onStart?.();
2064
+ let lastRoundText = "";
2065
+ let lastRoundReasoning = "";
1982
2066
  for (let round = 1;round <= maxToolRounds + 1; round += 1) {
1983
2067
  const mcpToolset = await resolveMCPToolset(request.mcpClients);
1984
2068
  const transportTools = toProviderFunctionTools(mcpToolset);
@@ -1991,6 +2075,7 @@ async function streamWithChatCompletionsWithMCP(options, fetcher, path, request,
1991
2075
  model: options.model,
1992
2076
  messages,
1993
2077
  temperature: request.temperature,
2078
+ reasoning_effort: toOpenAIReasoningEffort(request.reasoningEffort),
1994
2079
  max_tokens: request.maxTokens,
1995
2080
  tools: transportTools,
1996
2081
  tool_choice: request.toolChoice,
@@ -2004,9 +2089,11 @@ async function streamWithChatCompletionsWithMCP(options, fetcher, path, request,
2004
2089
  throw new Error(`HTTP ${response.status}: ${message}`);
2005
2090
  }
2006
2091
  let roundText = "";
2092
+ let roundReasoning = "";
2007
2093
  let roundUsage;
2008
2094
  let roundFinishReason;
2009
2095
  const streamedToolCalls = new Map;
2096
+ let reasoningFieldName;
2010
2097
  await consumeSSE(response, (data) => {
2011
2098
  if (data === "[DONE]") {
2012
2099
  return;
@@ -2017,6 +2104,7 @@ async function streamWithChatCompletionsWithMCP(options, fetcher, path, request,
2017
2104
  }
2018
2105
  lastPayload = json;
2019
2106
  const delta = pickAssistantDelta(json);
2107
+ const reasoningDelta = pickAssistantReasoningDelta(json);
2020
2108
  const chunkUsage = pickUsage(json);
2021
2109
  const chunkFinishReason = pickFinishReason(json);
2022
2110
  collectOpenAIStreamToolCalls(json, streamedToolCalls);
@@ -2028,9 +2116,14 @@ async function streamWithChatCompletionsWithMCP(options, fetcher, path, request,
2028
2116
  roundText += delta;
2029
2117
  callbacks.onToken?.(delta);
2030
2118
  }
2031
- if (delta || chunkUsage || chunkFinishReason) {
2119
+ if (reasoningDelta) {
2120
+ roundReasoning += reasoningDelta;
2121
+ reasoningFieldName ??= pickAssistantReasoningDeltaFieldName(json);
2122
+ }
2123
+ if (delta || reasoningDelta || chunkUsage || chunkFinishReason) {
2032
2124
  const chunk = {
2033
2125
  textDelta: delta,
2126
+ reasoningDelta: reasoningDelta || undefined,
2034
2127
  raw: json,
2035
2128
  usage: chunkUsage,
2036
2129
  finishReason: chunkFinishReason
@@ -2046,6 +2139,7 @@ async function streamWithChatCompletionsWithMCP(options, fetcher, path, request,
2046
2139
  if (calledTools.length === 0) {
2047
2140
  const out2 = {
2048
2141
  text: roundText,
2142
+ reasoning: roundReasoning.length > 0 ? roundReasoning : undefined,
2049
2143
  raw: lastPayload,
2050
2144
  usage: aggregatedUsage,
2051
2145
  finishReason,
@@ -2066,7 +2160,12 @@ async function streamWithChatCompletionsWithMCP(options, fetcher, path, request,
2066
2160
  });
2067
2161
  executedToolCalls.push(...outputs.map((entry) => entry.call));
2068
2162
  toolExecutions.push(...outputs.map((entry) => entry.execution));
2069
- const assistantMessage = buildOpenAIAssistantToolMessage(roundText, calledTools);
2163
+ lastRoundText = roundText;
2164
+ lastRoundReasoning = roundReasoning;
2165
+ const assistantMessage = buildOpenAIAssistantToolMessage(roundText, calledTools, {
2166
+ reasoning: roundReasoning,
2167
+ reasoningFieldName
2168
+ });
2070
2169
  const toolMessages = outputs.map((entry) => ({
2071
2170
  role: "tool",
2072
2171
  tool_call_id: entry.call.id,
@@ -2075,7 +2174,8 @@ async function streamWithChatCompletionsWithMCP(options, fetcher, path, request,
2075
2174
  messages = [...messages, assistantMessage, ...toolMessages];
2076
2175
  }
2077
2176
  const out = {
2078
- text: "",
2177
+ text: lastRoundText,
2178
+ reasoning: lastRoundReasoning.length > 0 ? lastRoundReasoning : undefined,
2079
2179
  raw: lastPayload,
2080
2180
  usage: aggregatedUsage,
2081
2181
  finishReason,
@@ -2097,6 +2197,7 @@ async function streamWithResponsesAPIPassThrough(options, fetcher, path, request
2097
2197
  input: buildResponsesInput(request),
2098
2198
  previous_response_id: pickString(body?.previous_response_id),
2099
2199
  temperature: request.temperature,
2200
+ reasoning_effort: toOpenAIReasoningEffort(request.reasoningEffort),
2100
2201
  max_output_tokens: request.maxTokens,
2101
2202
  stream: true
2102
2203
  })),
@@ -2177,6 +2278,7 @@ async function streamWithResponsesAPIWithMCP(options, fetcher, path, request, ca
2177
2278
  input,
2178
2279
  previous_response_id: previousResponseId,
2179
2280
  temperature: request.temperature,
2281
+ reasoning_effort: toOpenAIReasoningEffort(request.reasoningEffort),
2180
2282
  max_output_tokens: request.maxTokens,
2181
2283
  tools: transportTools,
2182
2284
  tool_choice: request.toolChoice,
@@ -2343,6 +2445,12 @@ function toResponsesTools(tools) {
2343
2445
  return { ...tool };
2344
2446
  });
2345
2447
  }
2448
+ function toOpenAIReasoningEffort(value) {
2449
+ if (!value) {
2450
+ return;
2451
+ }
2452
+ return value === "max" ? "xhigh" : value;
2453
+ }
2346
2454
  function pickChatToolCalls(payload) {
2347
2455
  const message = pickAssistantMessage(payload);
2348
2456
  if (!message) {
@@ -2424,20 +2532,50 @@ function pickAssistantDelta(payload) {
2424
2532
  if (!isRecord2(delta)) {
2425
2533
  return "";
2426
2534
  }
2427
- const content = delta.content;
2428
- if (typeof content === "string") {
2429
- return content;
2535
+ return pickTextFromOpenAIContent(delta.content);
2536
+ }
2537
+ function pickAssistantReasoning(payload) {
2538
+ const message = pickAssistantMessage(payload);
2539
+ if (!message) {
2540
+ return "";
2430
2541
  }
2431
- if (Array.isArray(content)) {
2432
- return content.map((part) => {
2433
- if (!isRecord2(part)) {
2434
- return "";
2435
- }
2436
- const text = part.text;
2437
- return typeof text === "string" ? text : "";
2438
- }).join("");
2542
+ return pickReasoningText(message);
2543
+ }
2544
+ function pickAssistantReasoningDelta(payload) {
2545
+ const choices = payload.choices;
2546
+ if (!Array.isArray(choices) || choices.length === 0) {
2547
+ return "";
2439
2548
  }
2440
- return "";
2549
+ const first = choices[0];
2550
+ if (!isRecord2(first)) {
2551
+ return "";
2552
+ }
2553
+ const delta = first.delta;
2554
+ if (!isRecord2(delta)) {
2555
+ return "";
2556
+ }
2557
+ return pickReasoningText(delta);
2558
+ }
2559
+ function pickAssistantReasoningDeltaFieldName(payload) {
2560
+ const choices = payload.choices;
2561
+ if (!Array.isArray(choices) || choices.length === 0) {
2562
+ return;
2563
+ }
2564
+ const first = choices[0];
2565
+ if (!isRecord2(first)) {
2566
+ return;
2567
+ }
2568
+ const delta = first.delta;
2569
+ if (!isRecord2(delta)) {
2570
+ return;
2571
+ }
2572
+ if (hasTextLikeValue(delta.reasoning)) {
2573
+ return "reasoning";
2574
+ }
2575
+ if (hasTextLikeValue(delta.reasoning_content)) {
2576
+ return "reasoning_content";
2577
+ }
2578
+ return;
2441
2579
  }
2442
2580
  function collectOpenAIStreamToolCalls(payload, state) {
2443
2581
  const choices = payload.choices;
@@ -2486,8 +2624,8 @@ function buildOpenAIStreamToolCalls(state) {
2486
2624
  arguments: entry.argumentsText.length > 0 ? entry.argumentsText : {}
2487
2625
  }));
2488
2626
  }
2489
- function buildOpenAIAssistantToolMessage(text, toolCalls) {
2490
- return {
2627
+ function buildOpenAIAssistantToolMessage(text, toolCalls, reasoning) {
2628
+ const message = {
2491
2629
  role: "assistant",
2492
2630
  content: text,
2493
2631
  tool_calls: toolCalls.map((call) => ({
@@ -2499,6 +2637,10 @@ function buildOpenAIAssistantToolMessage(text, toolCalls) {
2499
2637
  }
2500
2638
  }))
2501
2639
  };
2640
+ if (reasoning?.reasoning && reasoning.reasoning.length > 0) {
2641
+ message[reasoning.reasoningFieldName ?? "reasoning"] = reasoning.reasoning;
2642
+ }
2643
+ return message;
2502
2644
  }
2503
2645
  function pickResponsesStreamPayload(payload) {
2504
2646
  if (isRecord2(payload.response)) {
@@ -2660,21 +2802,9 @@ function pickResponsesText(payload) {
2660
2802
  function pickAssistantText(payload) {
2661
2803
  const message = pickAssistantMessage(payload);
2662
2804
  if (message) {
2663
- const content = message.content;
2664
- if (typeof content === "string") {
2665
- return content;
2666
- }
2667
- if (Array.isArray(content)) {
2668
- return content.map((part) => {
2669
- if (typeof part === "string") {
2670
- return part;
2671
- }
2672
- if (!isRecord2(part)) {
2673
- return "";
2674
- }
2675
- const text = part.text;
2676
- return typeof text === "string" ? text : "";
2677
- }).join("");
2805
+ const text = pickTextFromOpenAIContent(message.content);
2806
+ if (text.length > 0) {
2807
+ return text;
2678
2808
  }
2679
2809
  }
2680
2810
  const choices = payload.choices;
@@ -2686,6 +2816,36 @@ function pickAssistantText(payload) {
2686
2816
  }
2687
2817
  return "";
2688
2818
  }
2819
+ function pickReasoningText(value) {
2820
+ return pickTextLike(value.reasoning) || pickTextLike(value.reasoning_content);
2821
+ }
2822
+ function pickTextFromOpenAIContent(value) {
2823
+ return pickTextLike(value);
2824
+ }
2825
+ function pickTextLike(value) {
2826
+ if (typeof value === "string") {
2827
+ return value;
2828
+ }
2829
+ if (Array.isArray(value)) {
2830
+ return value.map((part) => pickTextLikePart(part)).join("");
2831
+ }
2832
+ if (!isRecord2(value)) {
2833
+ return "";
2834
+ }
2835
+ return pickTextLikePart(value);
2836
+ }
2837
+ function pickTextLikePart(value) {
2838
+ if (typeof value === "string") {
2839
+ return value;
2840
+ }
2841
+ if (!isRecord2(value)) {
2842
+ return "";
2843
+ }
2844
+ return pickString(value.text) ?? pickString(value.output_text) ?? pickString(value.reasoning) ?? pickString(value.reasoning_content) ?? (Array.isArray(value.content) ? value.content.map((part) => pickTextLikePart(part)).join("") : "");
2845
+ }
2846
+ function hasTextLikeValue(value) {
2847
+ return pickTextLike(value).length > 0;
2848
+ }
2689
2849
  function pickUsage(payload) {
2690
2850
  const usage = payload.usage;
2691
2851
  if (!isRecord2(usage)) {
@@ -2739,14 +2899,13 @@ function createAnthropicCompatibleAdapter(options) {
2739
2899
  const response = await fetcher(buildURL(options.baseURL, path), {
2740
2900
  method: "POST",
2741
2901
  headers: buildHeaders2(options),
2742
- body: JSON.stringify(cleanUndefined({
2902
+ body: JSON.stringify(buildAnthropicRequestBody(options, request, {
2743
2903
  ...options.defaultBody,
2744
2904
  ...request.body,
2745
2905
  model: options.model,
2746
2906
  system: input.systemPrompt,
2747
2907
  messages: input.messages,
2748
2908
  temperature: request.temperature,
2749
- max_tokens: resolveMaxTokens(request.maxTokens, options.defaultMaxTokens),
2750
2909
  stream: true
2751
2910
  })),
2752
2911
  signal: request.signal
@@ -2802,14 +2961,13 @@ async function completePassThrough(options, fetcher, path, request) {
2802
2961
  const response = await fetcher(buildURL(options.baseURL, path), {
2803
2962
  method: "POST",
2804
2963
  headers: buildHeaders2(options),
2805
- body: JSON.stringify(cleanUndefined({
2964
+ body: JSON.stringify(buildAnthropicRequestBody(options, request, {
2806
2965
  ...options.defaultBody,
2807
2966
  ...request.body,
2808
2967
  model: options.model,
2809
2968
  system: input.systemPrompt,
2810
2969
  messages: input.messages,
2811
2970
  temperature: request.temperature,
2812
- max_tokens: resolveMaxTokens(request.maxTokens, options.defaultMaxTokens),
2813
2971
  stream: false
2814
2972
  })),
2815
2973
  signal: request.signal
@@ -2847,14 +3005,13 @@ async function completeWithMCPToolLoop(options, fetcher, path, request) {
2847
3005
  const response = await fetcher(buildURL(options.baseURL, path), {
2848
3006
  method: "POST",
2849
3007
  headers: buildHeaders2(options),
2850
- body: JSON.stringify(cleanUndefined({
3008
+ body: JSON.stringify(buildAnthropicRequestBody(options, request, {
2851
3009
  ...options.defaultBody,
2852
3010
  ...request.body,
2853
3011
  model: options.model,
2854
3012
  system: input.systemPrompt,
2855
3013
  messages,
2856
3014
  temperature: request.temperature,
2857
- max_tokens: resolveMaxTokens(request.maxTokens, options.defaultMaxTokens),
2858
3015
  tools,
2859
3016
  tool_choice: toAnthropicToolChoice(request.toolChoice),
2860
3017
  stream: false
@@ -2932,14 +3089,13 @@ async function streamWithMCPToolLoop(options, fetcher, path, request, callbacks)
2932
3089
  const response = await fetcher(buildURL(options.baseURL, path), {
2933
3090
  method: "POST",
2934
3091
  headers: buildHeaders2(options),
2935
- body: JSON.stringify(cleanUndefined({
3092
+ body: JSON.stringify(buildAnthropicRequestBody(options, request, {
2936
3093
  ...options.defaultBody,
2937
3094
  ...request.body,
2938
3095
  model: options.model,
2939
3096
  system: input.systemPrompt,
2940
3097
  messages,
2941
3098
  temperature: request.temperature,
2942
- max_tokens: resolveMaxTokens(request.maxTokens, options.defaultMaxTokens),
2943
3099
  tools,
2944
3100
  tool_choice: toAnthropicToolChoice(request.toolChoice),
2945
3101
  stream: true
@@ -3047,6 +3203,21 @@ function buildHeaders2(options) {
3047
3203
  ...options.headers
3048
3204
  };
3049
3205
  }
3206
+ function buildAnthropicRequestBody(options, request, body) {
3207
+ const bodyOutputConfig = isRecord2(body.output_config) ? body.output_config : undefined;
3208
+ const bodyThinking = body.thinking;
3209
+ const hasExplicitThinking = Object.prototype.hasOwnProperty.call(body, "thinking");
3210
+ const reasoningEffort = request.reasoningEffort;
3211
+ return cleanUndefined({
3212
+ ...body,
3213
+ max_tokens: resolveMaxTokens(request.maxTokens, options.defaultMaxTokens),
3214
+ output_config: reasoningEffort ? cleanUndefined({
3215
+ ...bodyOutputConfig,
3216
+ effort: reasoningEffort
3217
+ }) : bodyOutputConfig,
3218
+ thinking: reasoningEffort ? hasExplicitThinking ? bodyThinking : { type: "adaptive" } : bodyThinking
3219
+ });
3220
+ }
3050
3221
  function resolveAnthropicInput(request) {
3051
3222
  if (Array.isArray(request.messages) && request.messages.length > 0) {
3052
3223
  return toAnthropicInput(request.messages);
@@ -3401,8 +3572,34 @@ function buildProviderOptions(config) {
3401
3572
  };
3402
3573
  }
3403
3574
 
3404
- // src/structured.ts
3405
- var import_jsonrepair3 = require("jsonrepair");
3575
+ // src/utils/debug-colors.ts
3576
+ var ANSI = {
3577
+ reset: "\x1B[0m",
3578
+ bold: "\x1B[1m",
3579
+ cyan: "\x1B[36m",
3580
+ yellow: "\x1B[33m",
3581
+ green: "\x1B[32m",
3582
+ red: "\x1B[31m",
3583
+ dim: "\x1B[2m"
3584
+ };
3585
+ function color(config, text, tone) {
3586
+ if (!config.colors) {
3587
+ return text;
3588
+ }
3589
+ return `${ANSI[tone]}${text}${ANSI.reset}`;
3590
+ }
3591
+ function dim(config, text) {
3592
+ if (!config.colors) {
3593
+ return text;
3594
+ }
3595
+ return `${ANSI.dim}${text}${ANSI.reset}`;
3596
+ }
3597
+ function title(config, text) {
3598
+ if (!config.colors) {
3599
+ return text;
3600
+ }
3601
+ return `${ANSI.bold}${text}${ANSI.reset}`;
3602
+ }
3406
3603
 
3407
3604
  // src/outdent.ts
3408
3605
  var DEFAULT_OPTIONS = {
@@ -3541,123 +3738,682 @@ function createOutdent(options = {}) {
3541
3738
  return outdent;
3542
3739
  }
3543
3740
 
3544
- // src/parse.ts
3545
- var import_jsonrepair2 = require("jsonrepair");
3546
- function parseLLMOutput(output, schema, options = {}) {
3547
- const sanitized = sanitizeThink(output);
3548
- const parseOptions = {
3549
- repair: options.repair ?? true,
3550
- maxCandidates: options.maxCandidates ?? 5,
3551
- acceptArrays: options.acceptArrays ?? true,
3552
- extraction: options.extraction,
3553
- onTrace: options.onTrace
3554
- };
3555
- const candidates = extractJsonCandidates(sanitized.visibleText, {
3556
- maxCandidates: parseOptions.maxCandidates,
3557
- acceptArrays: parseOptions.acceptArrays,
3558
- allowRepairHints: parseOptions.repair,
3559
- heuristics: parseOptions.extraction
3560
- });
3561
- emitTrace(parseOptions.onTrace, {
3562
- stage: "extract",
3563
- level: "info",
3564
- message: `Extracted ${candidates.length} candidate(s).`,
3565
- details: {
3566
- maxCandidates: parseOptions.maxCandidates,
3567
- thinkBlocks: sanitized.thinkBlocks.length,
3568
- thinkDiagnostics: sanitized.diagnostics
3569
- }
3570
- });
3571
- const errors = [];
3572
- const diagnostics = [];
3573
- let bestIssues = [];
3574
- let bestCandidate = candidates[0] ?? null;
3575
- let bestParsed = null;
3576
- let bestRepaired = null;
3577
- for (const candidate of candidates) {
3578
- const parseAttempt = parseAttemptFromHint(candidate.parseHint, parseOptions.repair) ?? tryParseJsonCandidate(candidate.content, parseOptions.repair);
3579
- if (!parseAttempt.success) {
3580
- const diagnostic = {
3581
- candidateId: candidate.id,
3582
- source: candidate.source,
3583
- usedRepair: parseAttempt.usedRepair,
3584
- parseSuccess: false,
3585
- validationSuccess: false,
3586
- selected: false,
3587
- stage: parseAttempt.stage,
3588
- message: parseAttempt.error
3589
- };
3590
- diagnostics.push(diagnostic);
3591
- errors.push({
3592
- stage: parseAttempt.stage,
3593
- message: parseAttempt.error,
3594
- candidateId: candidate.id
3595
- });
3596
- emitTrace(parseOptions.onTrace, {
3597
- stage: parseAttempt.stage,
3598
- level: "error",
3599
- message: parseAttempt.error,
3600
- candidateId: candidate.id
3601
- });
3602
- continue;
3603
- }
3604
- emitTrace(parseOptions.onTrace, {
3605
- stage: "parse",
3606
- level: "info",
3607
- message: parseAttempt.usedRepair ? "Candidate parsed after repair." : "Candidate parsed without repair.",
3608
- candidateId: candidate.id,
3609
- details: {
3610
- usedRepair: parseAttempt.usedRepair
3611
- }
3612
- });
3613
- const validated = schema.safeParse(parseAttempt.parsed);
3614
- if (validated.success) {
3615
- const selectedDiagnostic = {
3616
- candidateId: candidate.id,
3617
- source: candidate.source,
3618
- usedRepair: parseAttempt.usedRepair,
3619
- parseSuccess: true,
3620
- validationSuccess: true,
3621
- selected: true,
3622
- stage: "success"
3623
- };
3624
- diagnostics.push(selectedDiagnostic);
3625
- emitTrace(parseOptions.onTrace, {
3626
- stage: "result",
3627
- level: "info",
3628
- message: `Validation succeeded on candidate ${candidate.id}.`,
3629
- candidateId: candidate.id
3630
- });
3631
- return {
3632
- success: true,
3633
- data: validated.data,
3634
- raw: output,
3635
- sanitizedRaw: sanitized.visibleText,
3636
- thinkBlocks: sanitized.thinkBlocks,
3637
- thinkDiagnostics: sanitized.diagnostics,
3638
- parsed: parseAttempt.parsed,
3639
- candidate,
3640
- repaired: parseAttempt.repaired,
3641
- candidates,
3642
- diagnostics,
3643
- errors,
3644
- zodIssues: []
3645
- };
3646
- }
3647
- const issues = validated.error.issues;
3648
- const message = formatZodIssues(issues);
3649
- const validationDiagnostic = {
3650
- candidateId: candidate.id,
3651
- source: candidate.source,
3652
- usedRepair: parseAttempt.usedRepair,
3653
- parseSuccess: true,
3654
- validationSuccess: false,
3655
- selected: false,
3656
- stage: "validate",
3657
- message,
3658
- zodIssues: issues
3741
+ // src/generate-shared.ts
3742
+ var sharedOutdent = createOutdent({
3743
+ trimLeadingNewline: true,
3744
+ trimTrailingNewline: true,
3745
+ newline: `
3746
+ `
3747
+ });
3748
+ var RE_THINK_TAGS = /<\/?think\s*>/gi;
3749
+ function resolvePrompt(prompt, context) {
3750
+ const resolved = typeof prompt === "function" ? prompt(context) : prompt;
3751
+ return normalizePromptValue(resolved, context);
3752
+ }
3753
+ function normalizePromptValue(value, _context) {
3754
+ if (typeof value === "string") {
3755
+ return {
3756
+ prompt: value
3659
3757
  };
3660
- diagnostics.push(validationDiagnostic);
3758
+ }
3759
+ if (isPromptResolver(value)) {
3760
+ return normalizePromptPayload(value.resolvePrompt(_context));
3761
+ }
3762
+ return normalizePromptPayload(value);
3763
+ }
3764
+ function normalizePromptPayload(value) {
3765
+ const prompt = typeof value.prompt === "string" ? value.prompt : undefined;
3766
+ const messages = Array.isArray(value.messages) ? value.messages.filter(isLLMMessage) : undefined;
3767
+ if ((!prompt || prompt.trim().length === 0) && (!messages || messages.length === 0)) {
3768
+ throw new Error("Structured prompt payload must include a non-empty prompt or messages.");
3769
+ }
3770
+ return {
3771
+ prompt,
3772
+ systemPrompt: typeof value.systemPrompt === "string" ? value.systemPrompt : undefined,
3773
+ messages: messages && messages.length > 0 ? messages.map((message) => ({ ...message })) : undefined
3774
+ };
3775
+ }
3776
+ function applyPromptOutdent(payload, enabled) {
3777
+ if (!enabled) {
3778
+ return payload;
3779
+ }
3780
+ return {
3781
+ prompt: typeof payload.prompt === "string" ? sharedOutdent.string(payload.prompt) : undefined,
3782
+ systemPrompt: applyOutdentToOptionalPrompt(payload.systemPrompt, enabled),
3783
+ messages: payload.messages?.map((message) => ({
3784
+ ...message,
3785
+ content: typeof message.content === "string" ? sharedOutdent.string(message.content) : message.content
3786
+ }))
3787
+ };
3788
+ }
3789
+ function applyOutdentToOptionalPrompt(value, enabled) {
3790
+ if (!enabled || typeof value !== "string") {
3791
+ return value;
3792
+ }
3793
+ return sharedOutdent.string(value);
3794
+ }
3795
+ function mergeSystemPrompts(primary, secondary) {
3796
+ const prompts = [primary, secondary].map((value) => value?.trim()).filter((value) => Boolean(value));
3797
+ if (prompts.length === 0) {
3798
+ return;
3799
+ }
3800
+ return prompts.join(`
3801
+
3802
+ `);
3803
+ }
3804
+ function normalizeStreamConfig(option) {
3805
+ if (typeof option === "boolean") {
3806
+ return {
3807
+ enabled: option
3808
+ };
3809
+ }
3810
+ if (!option) {
3811
+ return {
3812
+ enabled: false
3813
+ };
3814
+ }
3815
+ return {
3816
+ enabled: option.enabled ?? true,
3817
+ onData: option.onData,
3818
+ to: option.to
3819
+ };
3820
+ }
3821
+ function normalizeDebugConfig(option) {
3822
+ if (typeof option === "boolean") {
3823
+ return {
3824
+ enabled: option,
3825
+ colors: true,
3826
+ verbose: false,
3827
+ logger: (line) => console.log(line)
3828
+ };
3829
+ }
3830
+ if (!option) {
3831
+ return {
3832
+ enabled: false,
3833
+ colors: true,
3834
+ verbose: false,
3835
+ logger: (line) => console.log(line)
3836
+ };
3837
+ }
3838
+ return {
3839
+ enabled: option.enabled ?? true,
3840
+ colors: option.colors ?? true,
3841
+ verbose: option.verbose ?? false,
3842
+ logger: option.logger ?? ((line) => console.log(line))
3843
+ };
3844
+ }
3845
+ function withToolTimeout(client, toolTimeoutMs) {
3846
+ return {
3847
+ id: client.id,
3848
+ listTools: client.listTools.bind(client),
3849
+ close: client.close?.bind(client),
3850
+ async callTool(params) {
3851
+ let timeoutId;
3852
+ const timeoutPromise = new Promise((_, reject) => {
3853
+ timeoutId = setTimeout(() => reject(new Error(`Tool call timed out after ${toolTimeoutMs}ms`)), toolTimeoutMs);
3854
+ });
3855
+ try {
3856
+ return await Promise.race([client.callTool(params), timeoutPromise]);
3857
+ } finally {
3858
+ clearTimeout(timeoutId);
3859
+ }
3860
+ }
3861
+ };
3862
+ }
3863
+ function applyToolTimeout(clients, toolTimeoutMs) {
3864
+ return clients.map((client) => withToolTimeout(client, toolTimeoutMs));
3865
+ }
3866
+ async function callModel(adapter, options) {
3867
+ const requestSignal = options.request?.signal ?? (options.timeout?.request !== undefined ? AbortSignal.timeout(options.timeout.request) : undefined);
3868
+ const requestPayload = {
3869
+ prompt: options.prompt,
3870
+ messages: options.messages,
3871
+ systemPrompt: options.systemPrompt,
3872
+ temperature: options.request?.temperature,
3873
+ reasoningEffort: options.request?.reasoningEffort,
3874
+ maxTokens: options.request?.maxTokens,
3875
+ mcpClients: options.request?.mcpClients,
3876
+ toolChoice: options.request?.toolChoice,
3877
+ parallelToolCalls: options.request?.parallelToolCalls,
3878
+ maxToolRounds: options.request?.maxToolRounds,
3879
+ onToolExecution: options.request?.onToolExecution,
3880
+ transformToolOutput: options.request?.transformToolOutput,
3881
+ transformToolArguments: options.request?.transformToolArguments,
3882
+ transformToolCallParams: options.request?.transformToolCallParams,
3883
+ unknownToolError: options.request?.unknownToolError,
3884
+ toolDebug: options.request?.toolDebug,
3885
+ body: options.request?.body,
3886
+ signal: requestSignal
3887
+ };
3888
+ emitDebugRequest(options.debug, {
3889
+ label: options.debugLabel,
3890
+ provider: adapter.provider,
3891
+ model: adapter.model,
3892
+ attempt: options.attempt,
3893
+ selfHealAttempt: options.selfHeal,
3894
+ selfHealEnabled: options.selfHealEnabled,
3895
+ stream: options.stream.enabled && !!adapter.stream,
3896
+ requestPayload
3897
+ });
3898
+ options.observe?.(options.buildEvent({
3899
+ stage: "llm.request",
3900
+ message: "Sending LLM request.",
3901
+ details: {
3902
+ provider: adapter.provider,
3903
+ model: adapter.model,
3904
+ stream: options.stream.enabled && !!adapter.stream
3905
+ }
3906
+ }));
3907
+ if (options.stream.enabled && adapter.stream) {
3908
+ let latestUsage;
3909
+ let latestFinishReason;
3910
+ let streamedProviderText = "";
3911
+ let streamedDedicatedReasoning = "";
3912
+ let lastSnapshotFingerprint;
3913
+ let previousSnapshotText = "";
3914
+ let previousSnapshotReasoning = "";
3915
+ const emitStreamingData = (done, usage2, finishReason2) => {
3916
+ const normalized2 = normalizeModelOutput(streamedProviderText, streamedDedicatedReasoning);
3917
+ const snapshot = options.buildSnapshot(normalized2);
3918
+ const fingerprint = toStreamDataFingerprint(snapshot);
3919
+ if (!done && fingerprint === lastSnapshotFingerprint) {
3920
+ return;
3921
+ }
3922
+ const delta = {
3923
+ text: normalized2.text.startsWith(previousSnapshotText) ? normalized2.text.slice(previousSnapshotText.length) : "",
3924
+ reasoning: normalized2.reasoning.startsWith(previousSnapshotReasoning) ? normalized2.reasoning.slice(previousSnapshotReasoning.length) : ""
3925
+ };
3926
+ lastSnapshotFingerprint = fingerprint;
3927
+ previousSnapshotText = normalized2.text;
3928
+ previousSnapshotReasoning = normalized2.reasoning;
3929
+ options.stream.onData?.({
3930
+ delta,
3931
+ snapshot,
3932
+ done,
3933
+ usage: usage2,
3934
+ finishReason: finishReason2
3935
+ });
3936
+ if (options.stream.to === "stdout" && delta.text) {
3937
+ process.stdout.write(delta.text);
3938
+ }
3939
+ options.observe?.(options.buildEvent({
3940
+ stage: "llm.stream.data",
3941
+ message: done ? "Streaming response completed." : "Streaming response updated.",
3942
+ details: {
3943
+ done,
3944
+ finishReason: finishReason2
3945
+ }
3946
+ }));
3947
+ };
3948
+ const handleTextDelta = (delta) => {
3949
+ if (!delta) {
3950
+ return;
3951
+ }
3952
+ streamedProviderText += delta;
3953
+ options.observe?.(options.buildEvent({
3954
+ stage: "llm.stream.delta",
3955
+ message: "Received stream delta.",
3956
+ details: {
3957
+ chars: delta.length
3958
+ }
3959
+ }));
3960
+ emitStreamingData(false);
3961
+ };
3962
+ const handleReasoningDelta = (delta) => {
3963
+ if (!delta) {
3964
+ return;
3965
+ }
3966
+ streamedDedicatedReasoning += delta;
3967
+ emitStreamingData(false);
3968
+ };
3969
+ const response2 = await adapter.stream(requestPayload, {
3970
+ onChunk: (chunk) => {
3971
+ if (chunk.textDelta) {
3972
+ handleTextDelta(chunk.textDelta);
3973
+ }
3974
+ if (chunk.reasoningDelta) {
3975
+ handleReasoningDelta(chunk.reasoningDelta);
3976
+ }
3977
+ if (chunk.usage) {
3978
+ latestUsage = preferLatestUsage(latestUsage, chunk.usage);
3979
+ }
3980
+ if (chunk.finishReason) {
3981
+ latestFinishReason = chunk.finishReason;
3982
+ }
3983
+ }
3984
+ });
3985
+ streamedProviderText = typeof response2.text === "string" ? response2.text : streamedProviderText;
3986
+ streamedDedicatedReasoning = typeof response2.reasoning === "string" ? response2.reasoning : streamedDedicatedReasoning;
3987
+ const finalNormalized = normalizeModelOutput(streamedProviderText, streamedDedicatedReasoning);
3988
+ const usage = preferLatestUsage(latestUsage, response2.usage);
3989
+ const finishReason = response2.finishReason ?? latestFinishReason;
3990
+ emitStreamingData(true, usage, finishReason);
3991
+ options.observe?.(options.buildEvent({
3992
+ stage: "llm.response",
3993
+ message: "Streaming response completed.",
3994
+ details: {
3995
+ via: "stream",
3996
+ chars: finalNormalized.parseSource.length,
3997
+ finishReason
3998
+ }
3999
+ }));
4000
+ emitDebugResponse(options.debug, {
4001
+ label: options.debugLabel,
4002
+ attempt: options.attempt,
4003
+ selfHealAttempt: options.selfHeal,
4004
+ selfHealEnabled: options.selfHealEnabled,
4005
+ via: "stream",
4006
+ text: finalNormalized.text,
4007
+ reasoning: finalNormalized.reasoning,
4008
+ parseSource: finalNormalized.parseSource,
4009
+ usage,
4010
+ finishReason
4011
+ });
4012
+ return {
4013
+ text: finalNormalized.text,
4014
+ reasoning: finalNormalized.reasoning,
4015
+ thinkBlocks: finalNormalized.thinkBlocks,
4016
+ parseSource: finalNormalized.parseSource,
4017
+ via: "stream",
4018
+ usage,
4019
+ finishReason
4020
+ };
4021
+ }
4022
+ const response = await adapter.complete(requestPayload);
4023
+ const normalized = normalizeModelOutput(response.text, response.reasoning);
4024
+ options.observe?.(options.buildEvent({
4025
+ stage: "llm.response",
4026
+ message: "Completion response received.",
4027
+ details: {
4028
+ via: "complete",
4029
+ chars: normalized.parseSource.length,
4030
+ finishReason: response.finishReason
4031
+ }
4032
+ }));
4033
+ emitDebugResponse(options.debug, {
4034
+ label: options.debugLabel,
4035
+ attempt: options.attempt,
4036
+ selfHealAttempt: options.selfHeal,
4037
+ selfHealEnabled: options.selfHealEnabled,
4038
+ via: "complete",
4039
+ text: normalized.text,
4040
+ reasoning: normalized.reasoning,
4041
+ parseSource: normalized.parseSource,
4042
+ usage: response.usage,
4043
+ finishReason: response.finishReason
4044
+ });
4045
+ return {
4046
+ text: normalized.text,
4047
+ reasoning: normalized.reasoning,
4048
+ thinkBlocks: normalized.thinkBlocks,
4049
+ parseSource: normalized.parseSource,
4050
+ via: "complete",
4051
+ usage: response.usage,
4052
+ finishReason: response.finishReason
4053
+ };
4054
+ }
4055
+ function normalizeModelOutput(text, dedicatedReasoning) {
4056
+ const sanitized = sanitizeThink(text);
4057
+ const visibleText = stripThinkBlocks(text, sanitized.thinkBlocks);
4058
+ const reasoning = joinReasoningSegments([
4059
+ dedicatedReasoning,
4060
+ ...sanitized.thinkBlocks.map((block) => block.content)
4061
+ ]);
4062
+ return {
4063
+ text: visibleText,
4064
+ reasoning,
4065
+ thinkBlocks: sanitized.thinkBlocks,
4066
+ parseSource: composeParseSource(visibleText, reasoning)
4067
+ };
4068
+ }
4069
+ function composeParseSource(text, reasoning) {
4070
+ if (typeof reasoning !== "string" || reasoning.length === 0) {
4071
+ return text;
4072
+ }
4073
+ const sanitized = reasoning.replace(RE_THINK_TAGS, "");
4074
+ if (sanitized.length === 0) {
4075
+ return text;
4076
+ }
4077
+ return `<think>${sanitized}</think>${text}`;
4078
+ }
4079
+ function aggregateUsage(attempts) {
4080
+ let usage;
4081
+ for (const attempt of attempts) {
4082
+ usage = mergeUsage2(usage, attempt.usage);
4083
+ }
4084
+ return usage;
4085
+ }
4086
+ function mergeUsage2(base, next) {
4087
+ if (!base && !next) {
4088
+ return;
4089
+ }
4090
+ return {
4091
+ inputTokens: (base?.inputTokens ?? 0) + (next?.inputTokens ?? 0),
4092
+ outputTokens: (base?.outputTokens ?? 0) + (next?.outputTokens ?? 0),
4093
+ totalTokens: (base?.totalTokens ?? 0) + (next?.totalTokens ?? 0),
4094
+ cost: (base?.cost ?? 0) + (next?.cost ?? 0)
4095
+ };
4096
+ }
4097
+ function isPromptResolver(value) {
4098
+ return typeof value === "object" && value !== null && "resolvePrompt" in value && typeof value.resolvePrompt === "function";
4099
+ }
4100
+ function isLLMMessage(value) {
4101
+ if (typeof value !== "object" || value === null) {
4102
+ return false;
4103
+ }
4104
+ const candidate = value;
4105
+ if (candidate.role !== "system" && candidate.role !== "user" && candidate.role !== "assistant" && candidate.role !== "tool") {
4106
+ return false;
4107
+ }
4108
+ return "content" in candidate;
4109
+ }
4110
+ function joinReasoningSegments(parts) {
4111
+ return parts.map((value) => value?.trim()).filter((value) => Boolean(value)).join(`
4112
+
4113
+ `);
4114
+ }
4115
+ function stripThinkBlocks(text, thinkBlocks) {
4116
+ if (thinkBlocks.length === 0) {
4117
+ return text;
4118
+ }
4119
+ let output = "";
4120
+ let cursor = 0;
4121
+ for (const block of thinkBlocks) {
4122
+ output += text.slice(cursor, block.start);
4123
+ cursor = block.end;
4124
+ }
4125
+ output += text.slice(cursor);
4126
+ return output;
4127
+ }
4128
+ function toStreamDataFingerprint(value) {
4129
+ try {
4130
+ return JSON.stringify(value);
4131
+ } catch {
4132
+ return "__unserializable__";
4133
+ }
4134
+ }
4135
+ function emitDebugRequest(config, input) {
4136
+ const requestBody = input.requestPayload.body !== undefined ? JSON.stringify(input.requestPayload.body, null, 2) : "(none)";
4137
+ const requestMessages = input.requestPayload.messages !== undefined ? JSON.stringify(input.requestPayload.messages, null, 2) : "(none)";
4138
+ const lines = [
4139
+ color(config, title(config, [
4140
+ `[${input.label}][request]`,
4141
+ `attempt=${input.attempt}`,
4142
+ `selfHealEnabled=${input.selfHealEnabled}`,
4143
+ `selfHealAttempt=${input.selfHealAttempt}`
4144
+ ].join(" ")), "cyan"),
4145
+ dim(config, [
4146
+ `provider=${input.provider ?? "unknown"}`,
4147
+ `model=${input.model ?? "unknown"}`,
4148
+ `stream=${input.stream}`
4149
+ ].join(" ")),
4150
+ color(config, "prompt:", "yellow"),
4151
+ input.requestPayload.prompt ?? "(none)",
4152
+ color(config, "messages:", "yellow"),
4153
+ requestMessages,
4154
+ color(config, "systemPrompt:", "yellow"),
4155
+ input.requestPayload.systemPrompt ?? "(none)",
4156
+ color(config, "request.body:", "yellow"),
4157
+ requestBody
4158
+ ];
4159
+ emitDebug(config, lines.join(`
4160
+ `));
4161
+ }
4162
+ function emitDebugResponse(config, input) {
4163
+ const text = input.text.length > 0 ? input.text : "(none)";
4164
+ const reasoning = input.reasoning.length > 0 ? input.reasoning : "(none)";
4165
+ const metadata = [
4166
+ `via=${input.via}`,
4167
+ `textChars=${input.text.length}`,
4168
+ `reasoningChars=${input.reasoning.length}`
4169
+ ];
4170
+ if (config.verbose) {
4171
+ metadata.push(`parseSourceChars=${input.parseSource.length}`);
4172
+ }
4173
+ metadata.push(`finishReason=${input.finishReason ?? "unknown"}`, `usage=${JSON.stringify(input.usage ?? {})}`);
4174
+ const lines = [
4175
+ color(config, title(config, [
4176
+ `[${input.label}][response]`,
4177
+ `attempt=${input.attempt}`,
4178
+ `selfHealEnabled=${input.selfHealEnabled}`,
4179
+ `selfHealAttempt=${input.selfHealAttempt}`
4180
+ ].join(" ")), "green"),
4181
+ dim(config, metadata.join(" ")),
4182
+ color(config, "text:", "yellow"),
4183
+ text,
4184
+ color(config, "reasoning:", "yellow"),
4185
+ reasoning
4186
+ ];
4187
+ if (config.verbose) {
4188
+ lines.push(color(config, "parseSource:", "yellow"), input.parseSource);
4189
+ }
4190
+ emitDebug(config, lines.join(`
4191
+ `));
4192
+ }
4193
+ function emitDebug(config, message) {
4194
+ if (!config.enabled) {
4195
+ return;
4196
+ }
4197
+ config.logger(message);
4198
+ }
4199
+
4200
+ // src/generate.ts
4201
+ async function generate(adapter, promptOrOptions, callOptions) {
4202
+ const normalized = normalizeGenerateInput(promptOrOptions, callOptions);
4203
+ const useOutdent = normalized.outdent ?? true;
4204
+ const streamConfig = normalizeStreamConfig(normalized.stream);
4205
+ const debugConfig = normalizeDebugConfig(normalized.debug);
4206
+ const resolvedPrompt = applyPromptOutdent(resolvePrompt(normalized.prompt, { mode: "loose" }), useOutdent);
4207
+ const resolvedSystemPrompt = applyOutdentToOptionalPrompt(normalized.systemPrompt, useOutdent);
4208
+ const preparedPrompt = prepareGeneratePromptPayload(resolvedPrompt, resolvedSystemPrompt);
4209
+ const resolvedRequest = normalized.timeout?.tool !== undefined && normalized.request?.mcpClients !== undefined ? {
4210
+ ...normalized.request,
4211
+ mcpClients: applyToolTimeout(normalized.request.mcpClients, normalized.timeout.tool)
4212
+ } : normalized.request;
4213
+ const response = await callModel(adapter, {
4214
+ prompt: preparedPrompt.prompt,
4215
+ messages: preparedPrompt.messages,
4216
+ systemPrompt: preparedPrompt.systemPrompt,
4217
+ request: resolvedRequest,
4218
+ stream: streamConfig,
4219
+ observe: normalized.observe,
4220
+ buildEvent: ({ stage, message, details }) => ({
4221
+ stage,
4222
+ attempt: 1,
4223
+ message,
4224
+ details
4225
+ }),
4226
+ buildSnapshot: (model) => ({
4227
+ text: model.text,
4228
+ reasoning: model.reasoning
4229
+ }),
4230
+ debug: debugConfig,
4231
+ debugLabel: "generate",
4232
+ attempt: 1,
4233
+ selfHeal: false,
4234
+ selfHealEnabled: false,
4235
+ timeout: normalized.timeout
4236
+ });
4237
+ const attempt = {
4238
+ attempt: 1,
4239
+ via: response.via,
4240
+ text: response.text,
4241
+ reasoning: response.reasoning,
4242
+ usage: response.usage,
4243
+ finishReason: response.finishReason
4244
+ };
4245
+ const attempts = [attempt];
4246
+ normalized.observe?.({
4247
+ stage: "result",
4248
+ attempt: 1,
4249
+ message: "Text generation completed.",
4250
+ details: {
4251
+ via: response.via,
4252
+ finishReason: response.finishReason
4253
+ }
4254
+ });
4255
+ return {
4256
+ text: attempt.text,
4257
+ reasoning: attempt.reasoning,
4258
+ attempts,
4259
+ usage: aggregateUsage(attempts),
4260
+ finishReason: attempt.finishReason
4261
+ };
4262
+ }
4263
+ function normalizeGenerateInput(promptOrOptions, callOptions) {
4264
+ if (isGenerateOptions(promptOrOptions)) {
4265
+ return promptOrOptions;
4266
+ }
4267
+ if (!promptOrOptions) {
4268
+ throw new Error("Missing prompt in generate(adapter, prompt, options?) call.");
4269
+ }
4270
+ return {
4271
+ ...callOptions ?? {},
4272
+ prompt: promptOrOptions
4273
+ };
4274
+ }
4275
+ function isGenerateOptions(value) {
4276
+ return typeof value === "object" && value !== null && "prompt" in value;
4277
+ }
4278
+ function prepareGeneratePromptPayload(payload, systemPrompt) {
4279
+ if (Array.isArray(payload.messages) && payload.messages.length > 0) {
4280
+ const messages = payload.messages.map((message) => ({ ...message }));
4281
+ const mergedSystemPrompt = mergeSystemPrompts(payload.systemPrompt, systemPrompt);
4282
+ const systemMessages = mergedSystemPrompt ? [{ role: "system", content: mergedSystemPrompt }] : [];
4283
+ return {
4284
+ messages: [...systemMessages, ...messages]
4285
+ };
4286
+ }
4287
+ const resolvedPrompt = payload.prompt?.trim();
4288
+ if (!resolvedPrompt) {
4289
+ throw new Error("Structured prompt payload must include a non-empty prompt or messages.");
4290
+ }
4291
+ return {
4292
+ prompt: resolvedPrompt,
4293
+ systemPrompt: mergeSystemPrompts(payload.systemPrompt, systemPrompt)
4294
+ };
4295
+ }
4296
+
4297
+ // src/structured.ts
4298
+ var import_jsonrepair3 = require("jsonrepair");
4299
+
4300
+ // src/parse.ts
4301
+ var import_jsonrepair2 = require("jsonrepair");
4302
+ function parseLLMOutput(output, schema, options = {}) {
4303
+ const sanitized = sanitizeThink(output);
4304
+ const parseOptions = {
4305
+ repair: options.repair ?? true,
4306
+ maxCandidates: options.maxCandidates ?? 5,
4307
+ acceptArrays: options.acceptArrays ?? true,
4308
+ extraction: options.extraction,
4309
+ onTrace: options.onTrace
4310
+ };
4311
+ const candidates = extractJsonCandidates(sanitized.visibleText, {
4312
+ maxCandidates: parseOptions.maxCandidates,
4313
+ acceptArrays: parseOptions.acceptArrays,
4314
+ allowRepairHints: parseOptions.repair,
4315
+ heuristics: parseOptions.extraction
4316
+ });
4317
+ emitTrace(parseOptions.onTrace, {
4318
+ stage: "extract",
4319
+ level: "info",
4320
+ message: `Extracted ${candidates.length} candidate(s).`,
4321
+ details: {
4322
+ maxCandidates: parseOptions.maxCandidates,
4323
+ thinkBlocks: sanitized.thinkBlocks.length,
4324
+ thinkDiagnostics: sanitized.diagnostics
4325
+ }
4326
+ });
4327
+ const errors = [];
4328
+ const diagnostics = [];
4329
+ let bestIssues = [];
4330
+ let bestCandidate = candidates[0] ?? null;
4331
+ let bestParsed = null;
4332
+ let bestRepaired = null;
4333
+ for (const candidate of candidates) {
4334
+ const parseAttempt = parseAttemptFromHint(candidate.parseHint, parseOptions.repair) ?? tryParseJsonCandidate(candidate.content, parseOptions.repair);
4335
+ if (!parseAttempt.success) {
4336
+ const diagnostic = {
4337
+ candidateId: candidate.id,
4338
+ source: candidate.source,
4339
+ usedRepair: parseAttempt.usedRepair,
4340
+ parseSuccess: false,
4341
+ validationSuccess: false,
4342
+ selected: false,
4343
+ stage: parseAttempt.stage,
4344
+ message: parseAttempt.error
4345
+ };
4346
+ diagnostics.push(diagnostic);
4347
+ errors.push({
4348
+ stage: parseAttempt.stage,
4349
+ message: parseAttempt.error,
4350
+ candidateId: candidate.id
4351
+ });
4352
+ emitTrace(parseOptions.onTrace, {
4353
+ stage: parseAttempt.stage,
4354
+ level: "error",
4355
+ message: parseAttempt.error,
4356
+ candidateId: candidate.id
4357
+ });
4358
+ continue;
4359
+ }
4360
+ emitTrace(parseOptions.onTrace, {
4361
+ stage: "parse",
4362
+ level: "info",
4363
+ message: parseAttempt.usedRepair ? "Candidate parsed after repair." : "Candidate parsed without repair.",
4364
+ candidateId: candidate.id,
4365
+ details: {
4366
+ usedRepair: parseAttempt.usedRepair
4367
+ }
4368
+ });
4369
+ const validated = schema.safeParse(parseAttempt.parsed);
4370
+ if (validated.success) {
4371
+ const selectedDiagnostic = {
4372
+ candidateId: candidate.id,
4373
+ source: candidate.source,
4374
+ usedRepair: parseAttempt.usedRepair,
4375
+ parseSuccess: true,
4376
+ validationSuccess: true,
4377
+ selected: true,
4378
+ stage: "success"
4379
+ };
4380
+ diagnostics.push(selectedDiagnostic);
4381
+ emitTrace(parseOptions.onTrace, {
4382
+ stage: "result",
4383
+ level: "info",
4384
+ message: `Validation succeeded on candidate ${candidate.id}.`,
4385
+ candidateId: candidate.id
4386
+ });
4387
+ return {
4388
+ success: true,
4389
+ data: validated.data,
4390
+ raw: output,
4391
+ sanitizedRaw: sanitized.visibleText,
4392
+ thinkBlocks: sanitized.thinkBlocks,
4393
+ thinkDiagnostics: sanitized.diagnostics,
4394
+ parsed: parseAttempt.parsed,
4395
+ candidate,
4396
+ repaired: parseAttempt.repaired,
4397
+ candidates,
4398
+ diagnostics,
4399
+ errors,
4400
+ zodIssues: []
4401
+ };
4402
+ }
4403
+ const issues = validated.error.issues;
4404
+ const message = formatZodIssues(issues);
4405
+ const validationDiagnostic = {
4406
+ candidateId: candidate.id,
4407
+ source: candidate.source,
4408
+ usedRepair: parseAttempt.usedRepair,
4409
+ parseSuccess: true,
4410
+ validationSuccess: false,
4411
+ selected: false,
4412
+ stage: "validate",
4413
+ message,
4414
+ zodIssues: issues
4415
+ };
4416
+ diagnostics.push(validationDiagnostic);
3661
4417
  if (bestIssues.length === 0 || issues.length < bestIssues.length) {
3662
4418
  bestIssues = issues;
3663
4419
  bestCandidate = candidate;
@@ -3840,48 +4596,19 @@ function formatZodIssues(issues) {
3840
4596
  `);
3841
4597
  }
3842
4598
 
3843
- // src/utils/debug-colors.ts
3844
- var ANSI = {
3845
- reset: "\x1B[0m",
3846
- bold: "\x1B[1m",
3847
- cyan: "\x1B[36m",
3848
- yellow: "\x1B[33m",
3849
- green: "\x1B[32m",
3850
- red: "\x1B[31m",
3851
- dim: "\x1B[2m"
3852
- };
3853
- function color(config, text, tone) {
3854
- if (!config.colors) {
3855
- return text;
3856
- }
3857
- return `${ANSI[tone]}${text}${ANSI.reset}`;
3858
- }
3859
- function dim(config, text) {
3860
- if (!config.colors) {
3861
- return text;
3862
- }
3863
- return `${ANSI.dim}${text}${ANSI.reset}`;
3864
- }
3865
- function title(config, text) {
3866
- if (!config.colors) {
3867
- return text;
3868
- }
3869
- return `${ANSI.bold}${text}${ANSI.reset}`;
3870
- }
3871
-
3872
4599
  // src/structured.ts
3873
4600
  class StructuredParseError extends Error {
3874
4601
  name = "StructuredParseError";
3875
- raw;
3876
- thinkBlocks;
4602
+ text;
4603
+ reasoning;
3877
4604
  candidates;
3878
4605
  zodIssues;
3879
4606
  repairLog;
3880
4607
  attempt;
3881
4608
  constructor(input) {
3882
4609
  super(input.message ?? `Structured parsing failed after ${input.attempt} attempt(s).`);
3883
- this.raw = input.raw;
3884
- this.thinkBlocks = input.thinkBlocks;
4610
+ this.text = input.text;
4611
+ this.reasoning = input.reasoning;
3885
4612
  this.candidates = input.candidates;
3886
4613
  this.zodIssues = input.zodIssues;
3887
4614
  this.repairLog = input.repairLog;
@@ -3912,12 +4639,6 @@ var RE_SIMPLE_IDENTIFIER2 = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
3912
4639
  var RE_ESCAPE_QUOTE = /"/g;
3913
4640
  var RE_WHITESPACE2 = /\s+/g;
3914
4641
  var DEFAULT_SELF_HEAL_MAX_DIAGNOSTICS = 8;
3915
- var structuredOutdent = createOutdent({
3916
- trimLeadingNewline: true,
3917
- trimTrailingNewline: true,
3918
- newline: `
3919
- `
3920
- });
3921
4642
  var DEFAULT_STRICT_PARSE_OPTIONS = {
3922
4643
  repair: false,
3923
4644
  maxCandidates: 3,
@@ -4047,7 +4768,7 @@ async function structured(adapter, schemaOrOptions, promptInput, callOptions) {
4047
4768
  });
4048
4769
  const selfHealSource = resolveSelfHealSource(previous);
4049
4770
  const repairPrompt = buildSelfHealPrompt({
4050
- rawOutput: previous.raw,
4771
+ rawOutput: composeParseSource(previous.text, previous.reasoning),
4051
4772
  issues: previous.zodIssues,
4052
4773
  schema: normalized.schema,
4053
4774
  schemaInstruction: normalized.schemaInstruction,
@@ -4120,74 +4841,6 @@ function normalizeStructuredInput(schemaOrOptions, promptInput, callOptions) {
4120
4841
  function isStructuredOptions(value) {
4121
4842
  return typeof value === "object" && value !== null && "schema" in value && "prompt" in value;
4122
4843
  }
4123
- function resolvePrompt(prompt, context) {
4124
- const resolved = typeof prompt === "function" ? prompt(context) : prompt;
4125
- return normalizePromptValue(resolved, context);
4126
- }
4127
- function normalizePromptValue(value, context) {
4128
- if (typeof value === "string") {
4129
- return {
4130
- prompt: value
4131
- };
4132
- }
4133
- if (isPromptResolver(value)) {
4134
- return normalizePromptPayload(value.resolvePrompt(context));
4135
- }
4136
- return normalizePromptPayload(value);
4137
- }
4138
- function isPromptResolver(value) {
4139
- return typeof value === "object" && value !== null && "resolvePrompt" in value && typeof value.resolvePrompt === "function";
4140
- }
4141
- function normalizePromptPayload(value) {
4142
- const prompt = typeof value.prompt === "string" ? value.prompt : undefined;
4143
- const messages = Array.isArray(value.messages) ? value.messages.filter(isLLMMessage) : undefined;
4144
- if ((!prompt || prompt.trim().length === 0) && (!messages || messages.length === 0)) {
4145
- throw new Error("Structured prompt payload must include a non-empty prompt or messages.");
4146
- }
4147
- return {
4148
- prompt,
4149
- systemPrompt: typeof value.systemPrompt === "string" ? value.systemPrompt : undefined,
4150
- messages: messages && messages.length > 0 ? messages.map((message) => ({ ...message })) : undefined
4151
- };
4152
- }
4153
- function applyPromptOutdent(payload, enabled) {
4154
- if (!enabled) {
4155
- return payload;
4156
- }
4157
- return {
4158
- prompt: typeof payload.prompt === "string" ? structuredOutdent.string(payload.prompt) : undefined,
4159
- systemPrompt: applyOutdentToOptionalPrompt(payload.systemPrompt, enabled),
4160
- messages: payload.messages?.map((message) => ({
4161
- ...message,
4162
- content: typeof message.content === "string" ? structuredOutdent.string(message.content) : message.content
4163
- }))
4164
- };
4165
- }
4166
- function isLLMMessage(value) {
4167
- if (typeof value !== "object" || value === null) {
4168
- return false;
4169
- }
4170
- const candidate = value;
4171
- if (candidate.role !== "system" && candidate.role !== "user" && candidate.role !== "assistant" && candidate.role !== "tool") {
4172
- return false;
4173
- }
4174
- return "content" in candidate;
4175
- }
4176
- function applyOutdentToOptionalPrompt(value, enabled) {
4177
- if (!enabled || typeof value !== "string") {
4178
- return value;
4179
- }
4180
- return structuredOutdent.string(value);
4181
- }
4182
- function mergeSystemPrompts(primary, secondary) {
4183
- const prompts = [primary, secondary].map((value) => value?.trim()).filter((value) => Boolean(value));
4184
- if (prompts.length === 0) {
4185
- return;
4186
- }
4187
- return prompts.join(`
4188
-
4189
- `);
4190
- }
4191
4844
  function prepareStructuredPromptPayload(payload, systemPrompt, schema, schemaInstruction) {
4192
4845
  if (Array.isArray(payload.messages) && payload.messages.length > 0) {
4193
4846
  const messages = payload.messages.map((message) => ({ ...message }));
@@ -4368,7 +5021,7 @@ function resolveSelfHealSource(attempt) {
4368
5021
  }
4369
5022
  return {
4370
5023
  kind: "raw",
4371
- text: attempt.raw
5024
+ text: composeParseSource(attempt.text, attempt.reasoning)
4372
5025
  };
4373
5026
  }
4374
5027
  function isSelfHealStalled(previous, current) {
@@ -4378,60 +5031,22 @@ function isSelfHealStalled(previous, current) {
4378
5031
  if (current.zodIssues.length < previous.zodIssues.length) {
4379
5032
  return false;
4380
5033
  }
4381
- if (current.parsed.errors.length < previous.parsed.errors.length) {
4382
- return false;
4383
- }
4384
- return buildSelfHealFailureFingerprint(previous) === buildSelfHealFailureFingerprint(current);
4385
- }
4386
- function buildSelfHealFailureFingerprint(attempt) {
4387
- const issues = attempt.zodIssues.map((issue) => `${formatIssuePath(issue.path)}:${issue.code}:${normalizeWhitespace(issue.message)}`).sort().join("|");
4388
- const errors = attempt.parsed.errors.map((error) => `${error.stage}:${error.candidateId ?? "-"}:${normalizeWhitespace(error.message)}`).sort().join("|");
4389
- const source = normalizeWhitespace(resolveSelfHealSource(attempt).text).slice(0, 512);
4390
- return [issues, errors, source].join("::");
4391
- }
4392
- function normalizeWhitespace(value) {
4393
- return value.replace(RE_WHITESPACE2, " ").trim();
4394
- }
4395
- function normalizeStreamConfig(option) {
4396
- if (typeof option === "boolean") {
4397
- return {
4398
- enabled: option
4399
- };
4400
- }
4401
- if (!option) {
4402
- return {
4403
- enabled: false
4404
- };
4405
- }
4406
- return {
4407
- enabled: option.enabled ?? true,
4408
- onData: option.onData,
4409
- to: option.to
4410
- };
4411
- }
4412
- function normalizeDebugConfig(option) {
4413
- if (typeof option === "boolean") {
4414
- return {
4415
- enabled: option,
4416
- colors: true,
4417
- logger: (line) => console.log(line)
4418
- };
4419
- }
4420
- if (!option) {
4421
- return {
4422
- enabled: false,
4423
- colors: true,
4424
- logger: (line) => console.log(line)
4425
- };
5034
+ if (current.parsed.errors.length < previous.parsed.errors.length) {
5035
+ return false;
4426
5036
  }
4427
- return {
4428
- enabled: option.enabled ?? true,
4429
- colors: option.colors ?? true,
4430
- logger: option.logger ?? ((line) => console.log(line))
4431
- };
5037
+ return buildSelfHealFailureFingerprint(previous) === buildSelfHealFailureFingerprint(current);
5038
+ }
5039
+ function buildSelfHealFailureFingerprint(attempt) {
5040
+ const issues = attempt.zodIssues.map((issue) => `${formatIssuePath(issue.path)}:${issue.code}:${normalizeWhitespace(issue.message)}`).sort().join("|");
5041
+ const errors = attempt.parsed.errors.map((error) => `${error.stage}:${error.candidateId ?? "-"}:${normalizeWhitespace(error.message)}`).sort().join("|");
5042
+ const source = normalizeWhitespace(resolveSelfHealSource(attempt).text).slice(0, 512);
5043
+ return [issues, errors, source].join("::");
5044
+ }
5045
+ function normalizeWhitespace(value) {
5046
+ return value.replace(RE_WHITESPACE2, " ").trim();
4432
5047
  }
4433
5048
  async function executeAttempt(adapter, input) {
4434
- const response = await callModel(adapter, {
5049
+ const response = await callModel2(adapter, {
4435
5050
  prompt: input.prompt,
4436
5051
  messages: input.messages,
4437
5052
  systemPrompt: input.systemPrompt,
@@ -4444,7 +5059,7 @@ async function executeAttempt(adapter, input) {
4444
5059
  selfHealEnabled: input.selfHealEnabled,
4445
5060
  timeout: input.timeout
4446
5061
  });
4447
- const parsed = parseWithObserve(response.text, input.schema, input.parseOptions, {
5062
+ const parsed = parseWithObserve(response.parseSource, input.schema, input.parseOptions, {
4448
5063
  observe: input.observe,
4449
5064
  attempt: input.attemptNumber,
4450
5065
  selfHeal: input.selfHeal
@@ -4453,8 +5068,8 @@ async function executeAttempt(adapter, input) {
4453
5068
  attempt: input.attemptNumber,
4454
5069
  selfHeal: input.selfHeal,
4455
5070
  via: response.via,
4456
- raw: response.text,
4457
- thinkBlocks: parsed.thinkBlocks,
5071
+ text: response.text,
5072
+ reasoning: response.reasoning,
4458
5073
  json: parsed.parsed,
4459
5074
  candidates: parsed.candidates.map((candidate) => candidate.content),
4460
5075
  repairLog: collectRepairLog(parsed),
@@ -4469,199 +5084,26 @@ async function executeAttempt(adapter, input) {
4469
5084
  trace
4470
5085
  };
4471
5086
  }
4472
- function withToolTimeout(client, toolTimeoutMs) {
4473
- return {
4474
- id: client.id,
4475
- listTools: client.listTools.bind(client),
4476
- close: client.close?.bind(client),
4477
- async callTool(params) {
4478
- let timeoutId;
4479
- const timeoutPromise = new Promise((_, reject) => {
4480
- timeoutId = setTimeout(() => reject(new Error(`Tool call timed out after ${toolTimeoutMs}ms`)), toolTimeoutMs);
4481
- });
4482
- try {
4483
- return await Promise.race([client.callTool(params), timeoutPromise]);
4484
- } finally {
4485
- clearTimeout(timeoutId);
4486
- }
4487
- }
4488
- };
4489
- }
4490
- function applyToolTimeout(clients, toolTimeoutMs) {
4491
- return clients.map((client) => withToolTimeout(client, toolTimeoutMs));
4492
- }
4493
- async function callModel(adapter, options) {
4494
- const requestSignal = options.request?.signal ?? (options.timeout?.request !== undefined ? AbortSignal.timeout(options.timeout.request) : undefined);
4495
- const requestPayload = {
4496
- prompt: options.prompt,
4497
- messages: options.messages,
4498
- systemPrompt: options.systemPrompt,
4499
- temperature: options.request?.temperature,
4500
- maxTokens: options.request?.maxTokens,
4501
- mcpClients: options.request?.mcpClients,
4502
- toolChoice: options.request?.toolChoice,
4503
- parallelToolCalls: options.request?.parallelToolCalls,
4504
- maxToolRounds: options.request?.maxToolRounds,
4505
- onToolExecution: options.request?.onToolExecution,
4506
- transformToolOutput: options.request?.transformToolOutput,
4507
- transformToolArguments: options.request?.transformToolArguments,
4508
- transformToolCallParams: options.request?.transformToolCallParams,
4509
- unknownToolError: options.request?.unknownToolError,
4510
- toolDebug: options.request?.toolDebug,
4511
- body: options.request?.body,
4512
- signal: requestSignal
4513
- };
4514
- emitDebugRequest(options.debug, {
4515
- provider: adapter.provider,
4516
- model: adapter.model,
4517
- attempt: options.attempt,
4518
- selfHealAttempt: options.selfHeal,
4519
- selfHealEnabled: options.selfHealEnabled,
4520
- stream: options.stream.enabled && !!adapter.stream,
4521
- requestPayload
4522
- });
4523
- emitObserve(options.observe, {
4524
- stage: "llm.request",
4525
- attempt: options.attempt,
4526
- selfHeal: options.selfHeal,
4527
- message: "Sending LLM request.",
4528
- details: {
4529
- provider: adapter.provider,
4530
- model: adapter.model,
4531
- stream: options.stream.enabled && !!adapter.stream
4532
- }
4533
- });
4534
- if (options.stream.enabled && adapter.stream) {
4535
- let latestUsage;
4536
- let latestFinishReason;
4537
- let streamedRaw = "";
4538
- let sawToken = false;
4539
- let lastDataFingerprint;
4540
- const emitStreamingData = (raw, done, usage2, finishReason2) => {
4541
- const data = parseStreamingStructuredData(raw);
4542
- if (data === null && !done) {
4543
- return;
4544
- }
4545
- const fingerprint = toStreamDataFingerprint(data ?? null);
4546
- if (!done && fingerprint === lastDataFingerprint) {
4547
- return;
4548
- }
4549
- lastDataFingerprint = fingerprint;
4550
- options.stream.onData?.({
4551
- data: data ?? null,
4552
- raw,
4553
- done,
4554
- usage: usage2,
4555
- finishReason: finishReason2
4556
- });
4557
- emitObserve(options.observe, {
4558
- stage: "llm.stream.data",
4559
- attempt: options.attempt,
4560
- selfHeal: options.selfHeal,
4561
- message: done ? "Streaming structured data completed." : "Streaming structured data updated.",
4562
- details: {
4563
- done,
4564
- finishReason: finishReason2
4565
- }
4566
- });
4567
- };
4568
- const handleTextDelta = (delta) => {
4569
- if (!delta) {
4570
- return;
4571
- }
4572
- streamedRaw += delta;
4573
- if (options.stream.to === "stdout") {
4574
- process.stdout.write(delta);
4575
- }
4576
- emitObserve(options.observe, {
4577
- stage: "llm.stream.delta",
4578
- attempt: options.attempt,
4579
- selfHeal: options.selfHeal,
4580
- message: "Received stream delta.",
4581
- details: {
4582
- chars: delta.length
4583
- }
4584
- });
4585
- emitStreamingData(streamedRaw, false);
4586
- };
4587
- const response2 = await adapter.stream(requestPayload, {
4588
- onToken: (token) => {
4589
- sawToken = true;
4590
- handleTextDelta(token);
4591
- },
4592
- onChunk: (chunk) => {
4593
- if (!sawToken && chunk.textDelta) {
4594
- handleTextDelta(chunk.textDelta);
4595
- }
4596
- if (chunk.usage) {
4597
- latestUsage = preferLatestUsage(latestUsage, chunk.usage);
4598
- }
4599
- if (chunk.finishReason) {
4600
- latestFinishReason = chunk.finishReason;
4601
- }
4602
- }
4603
- });
4604
- const finalText = typeof response2.text === "string" && response2.text.length > 0 ? response2.text : streamedRaw;
4605
- const usage = preferLatestUsage(latestUsage, response2.usage);
4606
- const finishReason = response2.finishReason ?? latestFinishReason;
4607
- emitStreamingData(finalText, true, usage, finishReason);
4608
- emitObserve(options.observe, {
4609
- stage: "llm.response",
5087
+ async function callModel2(adapter, options) {
5088
+ return callModel(adapter, {
5089
+ ...options,
5090
+ buildEvent: ({ stage, message, details }) => ({
5091
+ stage,
4610
5092
  attempt: options.attempt,
4611
5093
  selfHeal: options.selfHeal,
4612
- message: "Streaming response completed.",
4613
- details: {
4614
- via: "stream",
4615
- chars: finalText.length,
4616
- finishReason
4617
- }
4618
- });
4619
- emitDebugResponse(options.debug, {
4620
- attempt: options.attempt,
4621
- selfHealAttempt: options.selfHeal,
4622
- selfHealEnabled: options.selfHealEnabled,
4623
- via: "stream",
4624
- responseText: finalText,
4625
- usage,
4626
- finishReason
4627
- });
4628
- return {
4629
- text: finalText,
4630
- via: "stream",
4631
- usage,
4632
- finishReason
4633
- };
4634
- }
4635
- const response = await adapter.complete(requestPayload);
4636
- emitObserve(options.observe, {
4637
- stage: "llm.response",
4638
- attempt: options.attempt,
4639
- selfHeal: options.selfHeal,
4640
- message: "Completion response received.",
4641
- details: {
4642
- via: "complete",
4643
- chars: response.text.length,
4644
- finishReason: response.finishReason
4645
- }
4646
- });
4647
- emitDebugResponse(options.debug, {
4648
- attempt: options.attempt,
4649
- selfHealAttempt: options.selfHeal,
4650
- selfHealEnabled: options.selfHealEnabled,
4651
- via: "complete",
4652
- responseText: response.text,
4653
- usage: response.usage,
4654
- finishReason: response.finishReason
5094
+ message,
5095
+ details
5096
+ }),
5097
+ buildSnapshot: (normalized) => ({
5098
+ text: normalized.text,
5099
+ reasoning: normalized.reasoning,
5100
+ data: parseStreamingStructuredData(normalized.parseSource) ?? null
5101
+ }),
5102
+ debugLabel: "structured"
4655
5103
  });
4656
- return {
4657
- text: response.text,
4658
- via: "complete",
4659
- usage: response.usage,
4660
- finishReason: response.finishReason
4661
- };
4662
5104
  }
4663
- function parseStreamingStructuredData(raw) {
4664
- const sanitized = sanitizeThink(raw);
5105
+ function parseStreamingStructuredData(parseSource) {
5106
+ const sanitized = sanitizeThink(parseSource);
4665
5107
  const start = findFirstJsonRootStart(sanitized.visibleText);
4666
5108
  if (start < 0) {
4667
5109
  return null;
@@ -4721,13 +5163,6 @@ function findFirstJsonRootStart(input) {
4721
5163
  }
4722
5164
  return Math.min(objectStart, arrayStart);
4723
5165
  }
4724
- function toStreamDataFingerprint(value) {
4725
- try {
4726
- return JSON.stringify(value);
4727
- } catch {
4728
- return "__unserializable__";
4729
- }
4730
- }
4731
5166
  function parseWithObserve(output, schema, parseOptions, context) {
4732
5167
  const userParseTrace = parseOptions.onTrace;
4733
5168
  return parseLLMOutput(output, schema, {
@@ -4757,38 +5192,20 @@ function buildSuccessResult(data, attempts) {
4757
5192
  const final = attempts.at(-1);
4758
5193
  return {
4759
5194
  data,
4760
- raw: final?.raw ?? "",
4761
- thinkBlocks: final?.thinkBlocks ?? [],
5195
+ text: final?.text ?? "",
5196
+ reasoning: final?.reasoning ?? "",
4762
5197
  json: final?.json ?? null,
4763
5198
  attempts,
4764
5199
  usage: aggregateUsage(attempts),
4765
5200
  finishReason: final?.finishReason
4766
5201
  };
4767
5202
  }
4768
- function aggregateUsage(attempts) {
4769
- let usage;
4770
- for (const attempt of attempts) {
4771
- usage = mergeUsage2(usage, attempt.usage);
4772
- }
4773
- return usage;
4774
- }
4775
- function mergeUsage2(base, next) {
4776
- if (!base && !next) {
4777
- return;
4778
- }
4779
- return {
4780
- inputTokens: (base?.inputTokens ?? 0) + (next?.inputTokens ?? 0),
4781
- outputTokens: (base?.outputTokens ?? 0) + (next?.outputTokens ?? 0),
4782
- totalTokens: (base?.totalTokens ?? 0) + (next?.totalTokens ?? 0),
4783
- cost: (base?.cost ?? 0) + (next?.cost ?? 0)
4784
- };
4785
- }
4786
5203
  function toStructuredError(attempt) {
4787
5204
  if (!attempt) {
4788
5205
  return new StructuredParseError({
4789
5206
  message: "Structured parsing failed before any model response.",
4790
- raw: "",
4791
- thinkBlocks: [],
5207
+ text: "",
5208
+ reasoning: "",
4792
5209
  candidates: [],
4793
5210
  zodIssues: [],
4794
5211
  repairLog: [],
@@ -4796,8 +5213,8 @@ function toStructuredError(attempt) {
4796
5213
  });
4797
5214
  }
4798
5215
  return new StructuredParseError({
4799
- raw: attempt.raw,
4800
- thinkBlocks: attempt.thinkBlocks,
5216
+ text: attempt.text,
5217
+ reasoning: attempt.reasoning,
4801
5218
  candidates: attempt.candidates,
4802
5219
  zodIssues: attempt.zodIssues,
4803
5220
  repairLog: attempt.repairLog,
@@ -4807,59 +5224,6 @@ function toStructuredError(attempt) {
4807
5224
  function emitObserve(observe, event) {
4808
5225
  observe?.(event);
4809
5226
  }
4810
- function emitDebugRequest(config, input) {
4811
- const requestBody = input.requestPayload.body !== undefined ? JSON.stringify(input.requestPayload.body, null, 2) : "(none)";
4812
- const requestMessages = input.requestPayload.messages !== undefined ? JSON.stringify(input.requestPayload.messages, null, 2) : "(none)";
4813
- const lines = [
4814
- color(config, title(config, [
4815
- "[structured][request]",
4816
- `attempt=${input.attempt}`,
4817
- `selfHealEnabled=${input.selfHealEnabled}`,
4818
- `selfHealAttempt=${input.selfHealAttempt}`
4819
- ].join(" ")), "cyan"),
4820
- dim(config, [
4821
- `provider=${input.provider ?? "unknown"}`,
4822
- `model=${input.model ?? "unknown"}`,
4823
- `stream=${input.stream}`
4824
- ].join(" ")),
4825
- color(config, "prompt:", "yellow"),
4826
- input.requestPayload.prompt ?? "(none)",
4827
- color(config, "messages:", "yellow"),
4828
- requestMessages,
4829
- color(config, "systemPrompt:", "yellow"),
4830
- input.requestPayload.systemPrompt ?? "(none)",
4831
- color(config, "request.body:", "yellow"),
4832
- requestBody
4833
- ];
4834
- emitDebug(config, lines.join(`
4835
- `));
4836
- }
4837
- function emitDebugResponse(config, input) {
4838
- const lines = [
4839
- color(config, title(config, [
4840
- "[structured][response]",
4841
- `attempt=${input.attempt}`,
4842
- `selfHealEnabled=${input.selfHealEnabled}`,
4843
- `selfHealAttempt=${input.selfHealAttempt}`
4844
- ].join(" ")), "green"),
4845
- dim(config, [
4846
- `via=${input.via}`,
4847
- `chars=${input.responseText.length}`,
4848
- `finishReason=${input.finishReason ?? "unknown"}`,
4849
- `usage=${JSON.stringify(input.usage ?? {})}`
4850
- ].join(" ")),
4851
- color(config, "text:", "yellow"),
4852
- input.responseText
4853
- ];
4854
- emitDebug(config, lines.join(`
4855
- `));
4856
- }
4857
- function emitDebug(config, message) {
4858
- if (!config.enabled) {
4859
- return;
4860
- }
4861
- config.logger(message);
4862
- }
4863
5227
 
4864
5228
  // src/llm.ts
4865
5229
  function createLLM(config, registry = createDefaultProviderRegistry()) {
@@ -4873,6 +5237,17 @@ function createLLM(config, registry = createDefaultProviderRegistry()) {
4873
5237
  const merged = mergeStructuredOptions(defaults, options);
4874
5238
  return structured(adapter, schema, prompt, merged);
4875
5239
  },
5240
+ async generate(promptOrOptions, options) {
5241
+ if (isGenerateOptions2(promptOrOptions)) {
5242
+ const merged2 = {
5243
+ ...mergeGenerateOptions(defaults, promptOrOptions),
5244
+ prompt: promptOrOptions.prompt
5245
+ };
5246
+ return generate(adapter, merged2);
5247
+ }
5248
+ const merged = mergeGenerateOptions(defaults, options);
5249
+ return generate(adapter, promptOrOptions, merged);
5250
+ },
4876
5251
  async embed(input, options = {}) {
4877
5252
  if (!adapter.embed) {
4878
5253
  throw new Error(`Provider "${adapter.provider ?? "unknown"}" does not support embeddings.`);
@@ -4902,6 +5277,26 @@ function mergeStructuredOptions(defaults, overrides) {
4902
5277
  timeout: mergeObjectLike(defaults?.timeout, overrides?.timeout)
4903
5278
  };
4904
5279
  }
5280
+ function mergeGenerateOptions(defaults, overrides) {
5281
+ if (!defaults && !overrides) {
5282
+ return {};
5283
+ }
5284
+ return {
5285
+ outdent: overrides?.outdent ?? defaults?.outdent,
5286
+ systemPrompt: overrides?.systemPrompt ?? defaults?.systemPrompt,
5287
+ request: {
5288
+ ...defaults?.request ?? {},
5289
+ ...overrides?.request ?? {}
5290
+ },
5291
+ stream: mergeObjectLike(defaults?.stream, overrides?.stream),
5292
+ debug: mergeObjectLike(defaults?.debug, overrides?.debug),
5293
+ timeout: mergeObjectLike(defaults?.timeout, overrides?.timeout),
5294
+ observe: overrides?.observe ?? defaults?.observe
5295
+ };
5296
+ }
5297
+ function isGenerateOptions2(value) {
5298
+ return typeof value === "object" && value !== null && "prompt" in value;
5299
+ }
4905
5300
  function mergeObjectLike(defaults, overrides) {
4906
5301
  if (overrides === undefined) {
4907
5302
  return defaults;