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/README.md +159 -9
- package/dist/generate-shared.d.ts +79 -0
- package/dist/generate.d.ts +3 -0
- package/dist/index.cjs +993 -598
- package/dist/index.d.ts +2 -1
- package/dist/index.js +993 -598
- package/dist/llm.d.ts +18 -2
- package/dist/structured.d.ts +4 -4
- package/dist/types.d.ts +76 -8
- package/package.json +4 -4
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
|
-
|
|
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 (
|
|
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 = {
|
|
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
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2535
|
+
return pickTextFromOpenAIContent(delta.content);
|
|
2536
|
+
}
|
|
2537
|
+
function pickAssistantReasoning(payload) {
|
|
2538
|
+
const message = pickAssistantMessage(payload);
|
|
2539
|
+
if (!message) {
|
|
2540
|
+
return "";
|
|
2430
2541
|
}
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
2664
|
-
if (
|
|
2665
|
-
return
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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/
|
|
3405
|
-
var
|
|
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/
|
|
3545
|
-
var
|
|
3546
|
-
|
|
3547
|
-
|
|
3548
|
-
|
|
3549
|
-
|
|
3550
|
-
|
|
3551
|
-
|
|
3552
|
-
|
|
3553
|
-
|
|
3554
|
-
|
|
3555
|
-
|
|
3556
|
-
|
|
3557
|
-
|
|
3558
|
-
|
|
3559
|
-
|
|
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
|
-
|
|
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
|
-
|
|
3876
|
-
|
|
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.
|
|
3884
|
-
this.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
4429
|
-
|
|
4430
|
-
|
|
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
|
|
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.
|
|
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
|
-
|
|
4457
|
-
|
|
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
|
|
4473
|
-
return {
|
|
4474
|
-
|
|
4475
|
-
|
|
4476
|
-
|
|
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
|
|
4613
|
-
details
|
|
4614
|
-
|
|
4615
|
-
|
|
4616
|
-
|
|
4617
|
-
|
|
4618
|
-
|
|
4619
|
-
|
|
4620
|
-
|
|
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(
|
|
4664
|
-
const sanitized = sanitizeThink(
|
|
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
|
-
|
|
4761
|
-
|
|
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
|
-
|
|
4791
|
-
|
|
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
|
-
|
|
4800
|
-
|
|
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;
|