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.js
CHANGED
|
@@ -1463,7 +1463,15 @@ function normalizeBaseURL(baseURL) {
|
|
|
1463
1463
|
return baseURL.endsWith("/") ? baseURL : `${baseURL}/`;
|
|
1464
1464
|
}
|
|
1465
1465
|
function buildURL(baseURL, path) {
|
|
1466
|
-
|
|
1466
|
+
try {
|
|
1467
|
+
return new URL(path).toString();
|
|
1468
|
+
} catch {}
|
|
1469
|
+
const base = new URL(normalizeBaseURL(baseURL));
|
|
1470
|
+
const resolvedPath = new URL(path, "http://provider-path.local");
|
|
1471
|
+
base.pathname = mergePathnames(base.pathname, resolvedPath.pathname);
|
|
1472
|
+
base.search = resolvedPath.search;
|
|
1473
|
+
base.hash = resolvedPath.hash;
|
|
1474
|
+
return base.toString();
|
|
1467
1475
|
}
|
|
1468
1476
|
function safeJSONParse(input) {
|
|
1469
1477
|
try {
|
|
@@ -1538,6 +1546,36 @@ function addOptional(a, b) {
|
|
|
1538
1546
|
}
|
|
1539
1547
|
return (a ?? 0) + (b ?? 0);
|
|
1540
1548
|
}
|
|
1549
|
+
function mergePathnames(basePathname, pathPathname) {
|
|
1550
|
+
const baseSegments = splitPathSegments(basePathname);
|
|
1551
|
+
const pathSegments = splitPathSegments(pathPathname);
|
|
1552
|
+
const overlap = findPathOverlap(baseSegments, pathSegments);
|
|
1553
|
+
const mergedSegments = [...baseSegments, ...pathSegments.slice(overlap)];
|
|
1554
|
+
if (mergedSegments.length === 0) {
|
|
1555
|
+
return "/";
|
|
1556
|
+
}
|
|
1557
|
+
const mergedPathname = `/${mergedSegments.join("/")}`;
|
|
1558
|
+
return pathPathname.endsWith("/") && pathPathname !== "/" ? `${mergedPathname}/` : mergedPathname;
|
|
1559
|
+
}
|
|
1560
|
+
function splitPathSegments(pathname) {
|
|
1561
|
+
return pathname.split("/").filter((segment) => segment.length > 0);
|
|
1562
|
+
}
|
|
1563
|
+
function findPathOverlap(baseSegments, pathSegments) {
|
|
1564
|
+
const maxOverlap = Math.min(baseSegments.length, pathSegments.length);
|
|
1565
|
+
for (let size = maxOverlap;size > 0; size -= 1) {
|
|
1566
|
+
let matches = true;
|
|
1567
|
+
for (let index = 0;index < size; index += 1) {
|
|
1568
|
+
if (baseSegments[baseSegments.length - size + index] !== pathSegments[index]) {
|
|
1569
|
+
matches = false;
|
|
1570
|
+
break;
|
|
1571
|
+
}
|
|
1572
|
+
}
|
|
1573
|
+
if (matches) {
|
|
1574
|
+
return size;
|
|
1575
|
+
}
|
|
1576
|
+
}
|
|
1577
|
+
return 0;
|
|
1578
|
+
}
|
|
1541
1579
|
|
|
1542
1580
|
// src/providers/openai-compatible.ts
|
|
1543
1581
|
function createOpenAICompatibleAdapter(options) {
|
|
@@ -1572,6 +1610,7 @@ function createOpenAICompatibleAdapter(options) {
|
|
|
1572
1610
|
model: options.model,
|
|
1573
1611
|
messages: buildMessages(request),
|
|
1574
1612
|
temperature: request.temperature,
|
|
1613
|
+
reasoning_effort: toOpenAIReasoningEffort(request.reasoningEffort),
|
|
1575
1614
|
max_tokens: request.maxTokens,
|
|
1576
1615
|
stream: true
|
|
1577
1616
|
})),
|
|
@@ -1583,6 +1622,7 @@ function createOpenAICompatibleAdapter(options) {
|
|
|
1583
1622
|
}
|
|
1584
1623
|
callbacks.onStart?.();
|
|
1585
1624
|
let text = "";
|
|
1625
|
+
let reasoning = "";
|
|
1586
1626
|
let usage;
|
|
1587
1627
|
let finishReason;
|
|
1588
1628
|
await consumeSSE(response, (data) => {
|
|
@@ -1594,6 +1634,7 @@ function createOpenAICompatibleAdapter(options) {
|
|
|
1594
1634
|
return;
|
|
1595
1635
|
}
|
|
1596
1636
|
const delta = pickAssistantDelta(json);
|
|
1637
|
+
const reasoningDelta = pickAssistantReasoningDelta(json);
|
|
1597
1638
|
const chunkUsage = pickUsage(json);
|
|
1598
1639
|
const chunkFinishReason = pickFinishReason(json);
|
|
1599
1640
|
usage = preferLatestUsage(usage, chunkUsage);
|
|
@@ -1604,9 +1645,13 @@ function createOpenAICompatibleAdapter(options) {
|
|
|
1604
1645
|
text += delta;
|
|
1605
1646
|
callbacks.onToken?.(delta);
|
|
1606
1647
|
}
|
|
1607
|
-
if (
|
|
1648
|
+
if (reasoningDelta) {
|
|
1649
|
+
reasoning += reasoningDelta;
|
|
1650
|
+
}
|
|
1651
|
+
if (delta || reasoningDelta || chunkUsage || chunkFinishReason) {
|
|
1608
1652
|
const chunk = {
|
|
1609
1653
|
textDelta: delta,
|
|
1654
|
+
reasoningDelta: reasoningDelta || undefined,
|
|
1610
1655
|
raw: json,
|
|
1611
1656
|
usage: chunkUsage,
|
|
1612
1657
|
finishReason: chunkFinishReason
|
|
@@ -1614,7 +1659,12 @@ function createOpenAICompatibleAdapter(options) {
|
|
|
1614
1659
|
callbacks.onChunk?.(chunk);
|
|
1615
1660
|
}
|
|
1616
1661
|
});
|
|
1617
|
-
const out = {
|
|
1662
|
+
const out = {
|
|
1663
|
+
text,
|
|
1664
|
+
reasoning: reasoning.length > 0 ? reasoning : undefined,
|
|
1665
|
+
usage,
|
|
1666
|
+
finishReason
|
|
1667
|
+
};
|
|
1618
1668
|
callbacks.onComplete?.(out);
|
|
1619
1669
|
return out;
|
|
1620
1670
|
},
|
|
@@ -1674,6 +1724,7 @@ async function completeWithChatCompletionsPassThrough(options, fetcher, path, re
|
|
|
1674
1724
|
model: options.model,
|
|
1675
1725
|
messages: buildMessages(request),
|
|
1676
1726
|
temperature: request.temperature,
|
|
1727
|
+
reasoning_effort: toOpenAIReasoningEffort(request.reasoningEffort),
|
|
1677
1728
|
max_tokens: request.maxTokens,
|
|
1678
1729
|
stream: false
|
|
1679
1730
|
})),
|
|
@@ -1683,20 +1734,41 @@ async function completeWithChatCompletionsPassThrough(options, fetcher, path, re
|
|
|
1683
1734
|
const message = await response.text();
|
|
1684
1735
|
throw new Error(`HTTP ${response.status}: ${message}`);
|
|
1685
1736
|
}
|
|
1686
|
-
const payload = await response
|
|
1737
|
+
const payload = await parseOpenAICompatibleJSONResponse(response, "Failed to parse OpenAI-compatible chat completion response");
|
|
1687
1738
|
const assistantMessage = pickAssistantMessage(payload);
|
|
1688
1739
|
if (!assistantMessage) {
|
|
1689
1740
|
throw new Error("No assistant message in OpenAI-compatible response.");
|
|
1690
1741
|
}
|
|
1691
1742
|
const toolCalls = pickChatToolCalls(payload);
|
|
1743
|
+
const reasoning = pickAssistantReasoning(payload);
|
|
1692
1744
|
return {
|
|
1693
1745
|
text: pickAssistantText(payload),
|
|
1746
|
+
reasoning: reasoning.length > 0 ? reasoning : undefined,
|
|
1694
1747
|
raw: payload,
|
|
1695
1748
|
usage: pickUsage(payload),
|
|
1696
1749
|
finishReason: pickFinishReason(payload),
|
|
1697
1750
|
toolCalls: toolCalls.length > 0 ? toolCalls : undefined
|
|
1698
1751
|
};
|
|
1699
1752
|
}
|
|
1753
|
+
async function parseOpenAICompatibleJSONResponse(response, context) {
|
|
1754
|
+
const rawBody = await response.text();
|
|
1755
|
+
try {
|
|
1756
|
+
return JSON.parse(rawBody);
|
|
1757
|
+
} catch (error) {
|
|
1758
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1759
|
+
throw new Error(`${context} (HTTP ${response.status}): ${message}. Raw body: ${formatResponseBodyForError(rawBody)}`);
|
|
1760
|
+
}
|
|
1761
|
+
}
|
|
1762
|
+
function formatResponseBodyForError(rawBody, maxLength = 2000) {
|
|
1763
|
+
const normalized = rawBody.trim();
|
|
1764
|
+
if (normalized.length === 0) {
|
|
1765
|
+
return "[empty body]";
|
|
1766
|
+
}
|
|
1767
|
+
if (normalized.length <= maxLength) {
|
|
1768
|
+
return normalized;
|
|
1769
|
+
}
|
|
1770
|
+
return `${normalized.slice(0, maxLength)}...[truncated ${normalized.length - maxLength} chars]`;
|
|
1771
|
+
}
|
|
1700
1772
|
async function completeWithChatCompletionsWithMCP(options, fetcher, path, request) {
|
|
1701
1773
|
const maxToolRounds = normalizeMaxToolRounds(request.maxToolRounds ?? options.defaultMaxToolRounds);
|
|
1702
1774
|
let messages = buildMessages(request);
|
|
@@ -1717,6 +1789,7 @@ async function completeWithChatCompletionsWithMCP(options, fetcher, path, reques
|
|
|
1717
1789
|
model: options.model,
|
|
1718
1790
|
messages,
|
|
1719
1791
|
temperature: request.temperature,
|
|
1792
|
+
reasoning_effort: toOpenAIReasoningEffort(request.reasoningEffort),
|
|
1720
1793
|
max_tokens: request.maxTokens,
|
|
1721
1794
|
tools: transportTools,
|
|
1722
1795
|
tool_choice: request.toolChoice,
|
|
@@ -1738,8 +1811,10 @@ async function completeWithChatCompletionsWithMCP(options, fetcher, path, reques
|
|
|
1738
1811
|
throw new Error("No assistant message in OpenAI-compatible response.");
|
|
1739
1812
|
}
|
|
1740
1813
|
if (calledTools.length === 0) {
|
|
1814
|
+
const reasoning = pickAssistantReasoning(payload);
|
|
1741
1815
|
return {
|
|
1742
1816
|
text: pickAssistantText(payload),
|
|
1817
|
+
reasoning: reasoning.length > 0 ? reasoning : undefined,
|
|
1743
1818
|
raw: payload,
|
|
1744
1819
|
usage: aggregatedUsage,
|
|
1745
1820
|
finishReason,
|
|
@@ -1767,6 +1842,10 @@ async function completeWithChatCompletionsWithMCP(options, fetcher, path, reques
|
|
|
1767
1842
|
}
|
|
1768
1843
|
return {
|
|
1769
1844
|
text: pickAssistantText(lastPayload ?? {}),
|
|
1845
|
+
reasoning: (() => {
|
|
1846
|
+
const value = pickAssistantReasoning(lastPayload ?? {});
|
|
1847
|
+
return value.length > 0 ? value : undefined;
|
|
1848
|
+
})(),
|
|
1770
1849
|
raw: lastPayload,
|
|
1771
1850
|
usage: aggregatedUsage,
|
|
1772
1851
|
finishReason,
|
|
@@ -1786,6 +1865,7 @@ async function completeWithResponsesAPIPassThrough(options, fetcher, path, reque
|
|
|
1786
1865
|
input: buildResponsesInput(request),
|
|
1787
1866
|
previous_response_id: pickString(body?.previous_response_id),
|
|
1788
1867
|
temperature: request.temperature,
|
|
1868
|
+
reasoning_effort: toOpenAIReasoningEffort(request.reasoningEffort),
|
|
1789
1869
|
max_output_tokens: request.maxTokens
|
|
1790
1870
|
})),
|
|
1791
1871
|
signal: request.signal
|
|
@@ -1826,6 +1906,7 @@ async function completeWithResponsesAPIWithMCP(options, fetcher, path, request)
|
|
|
1826
1906
|
input,
|
|
1827
1907
|
previous_response_id: previousResponseId,
|
|
1828
1908
|
temperature: request.temperature,
|
|
1909
|
+
reasoning_effort: toOpenAIReasoningEffort(request.reasoningEffort),
|
|
1829
1910
|
max_output_tokens: request.maxTokens,
|
|
1830
1911
|
tools: transportTools,
|
|
1831
1912
|
tool_choice: request.toolChoice,
|
|
@@ -1890,6 +1971,8 @@ async function streamWithChatCompletionsWithMCP(options, fetcher, path, request,
|
|
|
1890
1971
|
const executedToolCalls = [];
|
|
1891
1972
|
const toolExecutions = [];
|
|
1892
1973
|
callbacks.onStart?.();
|
|
1974
|
+
let lastRoundText = "";
|
|
1975
|
+
let lastRoundReasoning = "";
|
|
1893
1976
|
for (let round = 1;round <= maxToolRounds + 1; round += 1) {
|
|
1894
1977
|
const mcpToolset = await resolveMCPToolset(request.mcpClients);
|
|
1895
1978
|
const transportTools = toProviderFunctionTools(mcpToolset);
|
|
@@ -1902,6 +1985,7 @@ async function streamWithChatCompletionsWithMCP(options, fetcher, path, request,
|
|
|
1902
1985
|
model: options.model,
|
|
1903
1986
|
messages,
|
|
1904
1987
|
temperature: request.temperature,
|
|
1988
|
+
reasoning_effort: toOpenAIReasoningEffort(request.reasoningEffort),
|
|
1905
1989
|
max_tokens: request.maxTokens,
|
|
1906
1990
|
tools: transportTools,
|
|
1907
1991
|
tool_choice: request.toolChoice,
|
|
@@ -1915,9 +1999,11 @@ async function streamWithChatCompletionsWithMCP(options, fetcher, path, request,
|
|
|
1915
1999
|
throw new Error(`HTTP ${response.status}: ${message}`);
|
|
1916
2000
|
}
|
|
1917
2001
|
let roundText = "";
|
|
2002
|
+
let roundReasoning = "";
|
|
1918
2003
|
let roundUsage;
|
|
1919
2004
|
let roundFinishReason;
|
|
1920
2005
|
const streamedToolCalls = new Map;
|
|
2006
|
+
let reasoningFieldName;
|
|
1921
2007
|
await consumeSSE(response, (data) => {
|
|
1922
2008
|
if (data === "[DONE]") {
|
|
1923
2009
|
return;
|
|
@@ -1928,6 +2014,7 @@ async function streamWithChatCompletionsWithMCP(options, fetcher, path, request,
|
|
|
1928
2014
|
}
|
|
1929
2015
|
lastPayload = json;
|
|
1930
2016
|
const delta = pickAssistantDelta(json);
|
|
2017
|
+
const reasoningDelta = pickAssistantReasoningDelta(json);
|
|
1931
2018
|
const chunkUsage = pickUsage(json);
|
|
1932
2019
|
const chunkFinishReason = pickFinishReason(json);
|
|
1933
2020
|
collectOpenAIStreamToolCalls(json, streamedToolCalls);
|
|
@@ -1939,9 +2026,14 @@ async function streamWithChatCompletionsWithMCP(options, fetcher, path, request,
|
|
|
1939
2026
|
roundText += delta;
|
|
1940
2027
|
callbacks.onToken?.(delta);
|
|
1941
2028
|
}
|
|
1942
|
-
if (
|
|
2029
|
+
if (reasoningDelta) {
|
|
2030
|
+
roundReasoning += reasoningDelta;
|
|
2031
|
+
reasoningFieldName ??= pickAssistantReasoningDeltaFieldName(json);
|
|
2032
|
+
}
|
|
2033
|
+
if (delta || reasoningDelta || chunkUsage || chunkFinishReason) {
|
|
1943
2034
|
const chunk = {
|
|
1944
2035
|
textDelta: delta,
|
|
2036
|
+
reasoningDelta: reasoningDelta || undefined,
|
|
1945
2037
|
raw: json,
|
|
1946
2038
|
usage: chunkUsage,
|
|
1947
2039
|
finishReason: chunkFinishReason
|
|
@@ -1957,6 +2049,7 @@ async function streamWithChatCompletionsWithMCP(options, fetcher, path, request,
|
|
|
1957
2049
|
if (calledTools.length === 0) {
|
|
1958
2050
|
const out2 = {
|
|
1959
2051
|
text: roundText,
|
|
2052
|
+
reasoning: roundReasoning.length > 0 ? roundReasoning : undefined,
|
|
1960
2053
|
raw: lastPayload,
|
|
1961
2054
|
usage: aggregatedUsage,
|
|
1962
2055
|
finishReason,
|
|
@@ -1977,7 +2070,12 @@ async function streamWithChatCompletionsWithMCP(options, fetcher, path, request,
|
|
|
1977
2070
|
});
|
|
1978
2071
|
executedToolCalls.push(...outputs.map((entry) => entry.call));
|
|
1979
2072
|
toolExecutions.push(...outputs.map((entry) => entry.execution));
|
|
1980
|
-
|
|
2073
|
+
lastRoundText = roundText;
|
|
2074
|
+
lastRoundReasoning = roundReasoning;
|
|
2075
|
+
const assistantMessage = buildOpenAIAssistantToolMessage(roundText, calledTools, {
|
|
2076
|
+
reasoning: roundReasoning,
|
|
2077
|
+
reasoningFieldName
|
|
2078
|
+
});
|
|
1981
2079
|
const toolMessages = outputs.map((entry) => ({
|
|
1982
2080
|
role: "tool",
|
|
1983
2081
|
tool_call_id: entry.call.id,
|
|
@@ -1986,7 +2084,8 @@ async function streamWithChatCompletionsWithMCP(options, fetcher, path, request,
|
|
|
1986
2084
|
messages = [...messages, assistantMessage, ...toolMessages];
|
|
1987
2085
|
}
|
|
1988
2086
|
const out = {
|
|
1989
|
-
text:
|
|
2087
|
+
text: lastRoundText,
|
|
2088
|
+
reasoning: lastRoundReasoning.length > 0 ? lastRoundReasoning : undefined,
|
|
1990
2089
|
raw: lastPayload,
|
|
1991
2090
|
usage: aggregatedUsage,
|
|
1992
2091
|
finishReason,
|
|
@@ -2008,6 +2107,7 @@ async function streamWithResponsesAPIPassThrough(options, fetcher, path, request
|
|
|
2008
2107
|
input: buildResponsesInput(request),
|
|
2009
2108
|
previous_response_id: pickString(body?.previous_response_id),
|
|
2010
2109
|
temperature: request.temperature,
|
|
2110
|
+
reasoning_effort: toOpenAIReasoningEffort(request.reasoningEffort),
|
|
2011
2111
|
max_output_tokens: request.maxTokens,
|
|
2012
2112
|
stream: true
|
|
2013
2113
|
})),
|
|
@@ -2088,6 +2188,7 @@ async function streamWithResponsesAPIWithMCP(options, fetcher, path, request, ca
|
|
|
2088
2188
|
input,
|
|
2089
2189
|
previous_response_id: previousResponseId,
|
|
2090
2190
|
temperature: request.temperature,
|
|
2191
|
+
reasoning_effort: toOpenAIReasoningEffort(request.reasoningEffort),
|
|
2091
2192
|
max_output_tokens: request.maxTokens,
|
|
2092
2193
|
tools: transportTools,
|
|
2093
2194
|
tool_choice: request.toolChoice,
|
|
@@ -2254,6 +2355,12 @@ function toResponsesTools(tools) {
|
|
|
2254
2355
|
return { ...tool };
|
|
2255
2356
|
});
|
|
2256
2357
|
}
|
|
2358
|
+
function toOpenAIReasoningEffort(value) {
|
|
2359
|
+
if (!value) {
|
|
2360
|
+
return;
|
|
2361
|
+
}
|
|
2362
|
+
return value === "max" ? "xhigh" : value;
|
|
2363
|
+
}
|
|
2257
2364
|
function pickChatToolCalls(payload) {
|
|
2258
2365
|
const message = pickAssistantMessage(payload);
|
|
2259
2366
|
if (!message) {
|
|
@@ -2335,20 +2442,50 @@ function pickAssistantDelta(payload) {
|
|
|
2335
2442
|
if (!isRecord2(delta)) {
|
|
2336
2443
|
return "";
|
|
2337
2444
|
}
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2445
|
+
return pickTextFromOpenAIContent(delta.content);
|
|
2446
|
+
}
|
|
2447
|
+
function pickAssistantReasoning(payload) {
|
|
2448
|
+
const message = pickAssistantMessage(payload);
|
|
2449
|
+
if (!message) {
|
|
2450
|
+
return "";
|
|
2341
2451
|
}
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
return typeof text === "string" ? text : "";
|
|
2349
|
-
}).join("");
|
|
2452
|
+
return pickReasoningText(message);
|
|
2453
|
+
}
|
|
2454
|
+
function pickAssistantReasoningDelta(payload) {
|
|
2455
|
+
const choices = payload.choices;
|
|
2456
|
+
if (!Array.isArray(choices) || choices.length === 0) {
|
|
2457
|
+
return "";
|
|
2350
2458
|
}
|
|
2351
|
-
|
|
2459
|
+
const first = choices[0];
|
|
2460
|
+
if (!isRecord2(first)) {
|
|
2461
|
+
return "";
|
|
2462
|
+
}
|
|
2463
|
+
const delta = first.delta;
|
|
2464
|
+
if (!isRecord2(delta)) {
|
|
2465
|
+
return "";
|
|
2466
|
+
}
|
|
2467
|
+
return pickReasoningText(delta);
|
|
2468
|
+
}
|
|
2469
|
+
function pickAssistantReasoningDeltaFieldName(payload) {
|
|
2470
|
+
const choices = payload.choices;
|
|
2471
|
+
if (!Array.isArray(choices) || choices.length === 0) {
|
|
2472
|
+
return;
|
|
2473
|
+
}
|
|
2474
|
+
const first = choices[0];
|
|
2475
|
+
if (!isRecord2(first)) {
|
|
2476
|
+
return;
|
|
2477
|
+
}
|
|
2478
|
+
const delta = first.delta;
|
|
2479
|
+
if (!isRecord2(delta)) {
|
|
2480
|
+
return;
|
|
2481
|
+
}
|
|
2482
|
+
if (hasTextLikeValue(delta.reasoning)) {
|
|
2483
|
+
return "reasoning";
|
|
2484
|
+
}
|
|
2485
|
+
if (hasTextLikeValue(delta.reasoning_content)) {
|
|
2486
|
+
return "reasoning_content";
|
|
2487
|
+
}
|
|
2488
|
+
return;
|
|
2352
2489
|
}
|
|
2353
2490
|
function collectOpenAIStreamToolCalls(payload, state) {
|
|
2354
2491
|
const choices = payload.choices;
|
|
@@ -2397,8 +2534,8 @@ function buildOpenAIStreamToolCalls(state) {
|
|
|
2397
2534
|
arguments: entry.argumentsText.length > 0 ? entry.argumentsText : {}
|
|
2398
2535
|
}));
|
|
2399
2536
|
}
|
|
2400
|
-
function buildOpenAIAssistantToolMessage(text, toolCalls) {
|
|
2401
|
-
|
|
2537
|
+
function buildOpenAIAssistantToolMessage(text, toolCalls, reasoning) {
|
|
2538
|
+
const message = {
|
|
2402
2539
|
role: "assistant",
|
|
2403
2540
|
content: text,
|
|
2404
2541
|
tool_calls: toolCalls.map((call) => ({
|
|
@@ -2410,6 +2547,10 @@ function buildOpenAIAssistantToolMessage(text, toolCalls) {
|
|
|
2410
2547
|
}
|
|
2411
2548
|
}))
|
|
2412
2549
|
};
|
|
2550
|
+
if (reasoning?.reasoning && reasoning.reasoning.length > 0) {
|
|
2551
|
+
message[reasoning.reasoningFieldName ?? "reasoning"] = reasoning.reasoning;
|
|
2552
|
+
}
|
|
2553
|
+
return message;
|
|
2413
2554
|
}
|
|
2414
2555
|
function pickResponsesStreamPayload(payload) {
|
|
2415
2556
|
if (isRecord2(payload.response)) {
|
|
@@ -2571,21 +2712,9 @@ function pickResponsesText(payload) {
|
|
|
2571
2712
|
function pickAssistantText(payload) {
|
|
2572
2713
|
const message = pickAssistantMessage(payload);
|
|
2573
2714
|
if (message) {
|
|
2574
|
-
const
|
|
2575
|
-
if (
|
|
2576
|
-
return
|
|
2577
|
-
}
|
|
2578
|
-
if (Array.isArray(content)) {
|
|
2579
|
-
return content.map((part) => {
|
|
2580
|
-
if (typeof part === "string") {
|
|
2581
|
-
return part;
|
|
2582
|
-
}
|
|
2583
|
-
if (!isRecord2(part)) {
|
|
2584
|
-
return "";
|
|
2585
|
-
}
|
|
2586
|
-
const text = part.text;
|
|
2587
|
-
return typeof text === "string" ? text : "";
|
|
2588
|
-
}).join("");
|
|
2715
|
+
const text = pickTextFromOpenAIContent(message.content);
|
|
2716
|
+
if (text.length > 0) {
|
|
2717
|
+
return text;
|
|
2589
2718
|
}
|
|
2590
2719
|
}
|
|
2591
2720
|
const choices = payload.choices;
|
|
@@ -2597,6 +2726,36 @@ function pickAssistantText(payload) {
|
|
|
2597
2726
|
}
|
|
2598
2727
|
return "";
|
|
2599
2728
|
}
|
|
2729
|
+
function pickReasoningText(value) {
|
|
2730
|
+
return pickTextLike(value.reasoning) || pickTextLike(value.reasoning_content);
|
|
2731
|
+
}
|
|
2732
|
+
function pickTextFromOpenAIContent(value) {
|
|
2733
|
+
return pickTextLike(value);
|
|
2734
|
+
}
|
|
2735
|
+
function pickTextLike(value) {
|
|
2736
|
+
if (typeof value === "string") {
|
|
2737
|
+
return value;
|
|
2738
|
+
}
|
|
2739
|
+
if (Array.isArray(value)) {
|
|
2740
|
+
return value.map((part) => pickTextLikePart(part)).join("");
|
|
2741
|
+
}
|
|
2742
|
+
if (!isRecord2(value)) {
|
|
2743
|
+
return "";
|
|
2744
|
+
}
|
|
2745
|
+
return pickTextLikePart(value);
|
|
2746
|
+
}
|
|
2747
|
+
function pickTextLikePart(value) {
|
|
2748
|
+
if (typeof value === "string") {
|
|
2749
|
+
return value;
|
|
2750
|
+
}
|
|
2751
|
+
if (!isRecord2(value)) {
|
|
2752
|
+
return "";
|
|
2753
|
+
}
|
|
2754
|
+
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("") : "");
|
|
2755
|
+
}
|
|
2756
|
+
function hasTextLikeValue(value) {
|
|
2757
|
+
return pickTextLike(value).length > 0;
|
|
2758
|
+
}
|
|
2600
2759
|
function pickUsage(payload) {
|
|
2601
2760
|
const usage = payload.usage;
|
|
2602
2761
|
if (!isRecord2(usage)) {
|
|
@@ -2650,14 +2809,13 @@ function createAnthropicCompatibleAdapter(options) {
|
|
|
2650
2809
|
const response = await fetcher(buildURL(options.baseURL, path), {
|
|
2651
2810
|
method: "POST",
|
|
2652
2811
|
headers: buildHeaders2(options),
|
|
2653
|
-
body: JSON.stringify(
|
|
2812
|
+
body: JSON.stringify(buildAnthropicRequestBody(options, request, {
|
|
2654
2813
|
...options.defaultBody,
|
|
2655
2814
|
...request.body,
|
|
2656
2815
|
model: options.model,
|
|
2657
2816
|
system: input.systemPrompt,
|
|
2658
2817
|
messages: input.messages,
|
|
2659
2818
|
temperature: request.temperature,
|
|
2660
|
-
max_tokens: resolveMaxTokens(request.maxTokens, options.defaultMaxTokens),
|
|
2661
2819
|
stream: true
|
|
2662
2820
|
})),
|
|
2663
2821
|
signal: request.signal
|
|
@@ -2713,14 +2871,13 @@ async function completePassThrough(options, fetcher, path, request) {
|
|
|
2713
2871
|
const response = await fetcher(buildURL(options.baseURL, path), {
|
|
2714
2872
|
method: "POST",
|
|
2715
2873
|
headers: buildHeaders2(options),
|
|
2716
|
-
body: JSON.stringify(
|
|
2874
|
+
body: JSON.stringify(buildAnthropicRequestBody(options, request, {
|
|
2717
2875
|
...options.defaultBody,
|
|
2718
2876
|
...request.body,
|
|
2719
2877
|
model: options.model,
|
|
2720
2878
|
system: input.systemPrompt,
|
|
2721
2879
|
messages: input.messages,
|
|
2722
2880
|
temperature: request.temperature,
|
|
2723
|
-
max_tokens: resolveMaxTokens(request.maxTokens, options.defaultMaxTokens),
|
|
2724
2881
|
stream: false
|
|
2725
2882
|
})),
|
|
2726
2883
|
signal: request.signal
|
|
@@ -2758,14 +2915,13 @@ async function completeWithMCPToolLoop(options, fetcher, path, request) {
|
|
|
2758
2915
|
const response = await fetcher(buildURL(options.baseURL, path), {
|
|
2759
2916
|
method: "POST",
|
|
2760
2917
|
headers: buildHeaders2(options),
|
|
2761
|
-
body: JSON.stringify(
|
|
2918
|
+
body: JSON.stringify(buildAnthropicRequestBody(options, request, {
|
|
2762
2919
|
...options.defaultBody,
|
|
2763
2920
|
...request.body,
|
|
2764
2921
|
model: options.model,
|
|
2765
2922
|
system: input.systemPrompt,
|
|
2766
2923
|
messages,
|
|
2767
2924
|
temperature: request.temperature,
|
|
2768
|
-
max_tokens: resolveMaxTokens(request.maxTokens, options.defaultMaxTokens),
|
|
2769
2925
|
tools,
|
|
2770
2926
|
tool_choice: toAnthropicToolChoice(request.toolChoice),
|
|
2771
2927
|
stream: false
|
|
@@ -2843,14 +2999,13 @@ async function streamWithMCPToolLoop(options, fetcher, path, request, callbacks)
|
|
|
2843
2999
|
const response = await fetcher(buildURL(options.baseURL, path), {
|
|
2844
3000
|
method: "POST",
|
|
2845
3001
|
headers: buildHeaders2(options),
|
|
2846
|
-
body: JSON.stringify(
|
|
3002
|
+
body: JSON.stringify(buildAnthropicRequestBody(options, request, {
|
|
2847
3003
|
...options.defaultBody,
|
|
2848
3004
|
...request.body,
|
|
2849
3005
|
model: options.model,
|
|
2850
3006
|
system: input.systemPrompt,
|
|
2851
3007
|
messages,
|
|
2852
3008
|
temperature: request.temperature,
|
|
2853
|
-
max_tokens: resolveMaxTokens(request.maxTokens, options.defaultMaxTokens),
|
|
2854
3009
|
tools,
|
|
2855
3010
|
tool_choice: toAnthropicToolChoice(request.toolChoice),
|
|
2856
3011
|
stream: true
|
|
@@ -2958,6 +3113,21 @@ function buildHeaders2(options) {
|
|
|
2958
3113
|
...options.headers
|
|
2959
3114
|
};
|
|
2960
3115
|
}
|
|
3116
|
+
function buildAnthropicRequestBody(options, request, body) {
|
|
3117
|
+
const bodyOutputConfig = isRecord2(body.output_config) ? body.output_config : undefined;
|
|
3118
|
+
const bodyThinking = body.thinking;
|
|
3119
|
+
const hasExplicitThinking = Object.prototype.hasOwnProperty.call(body, "thinking");
|
|
3120
|
+
const reasoningEffort = request.reasoningEffort;
|
|
3121
|
+
return cleanUndefined({
|
|
3122
|
+
...body,
|
|
3123
|
+
max_tokens: resolveMaxTokens(request.maxTokens, options.defaultMaxTokens),
|
|
3124
|
+
output_config: reasoningEffort ? cleanUndefined({
|
|
3125
|
+
...bodyOutputConfig,
|
|
3126
|
+
effort: reasoningEffort
|
|
3127
|
+
}) : bodyOutputConfig,
|
|
3128
|
+
thinking: reasoningEffort ? hasExplicitThinking ? bodyThinking : { type: "adaptive" } : bodyThinking
|
|
3129
|
+
});
|
|
3130
|
+
}
|
|
2961
3131
|
function resolveAnthropicInput(request) {
|
|
2962
3132
|
if (Array.isArray(request.messages) && request.messages.length > 0) {
|
|
2963
3133
|
return toAnthropicInput(request.messages);
|
|
@@ -3312,8 +3482,34 @@ function buildProviderOptions(config) {
|
|
|
3312
3482
|
};
|
|
3313
3483
|
}
|
|
3314
3484
|
|
|
3315
|
-
// src/
|
|
3316
|
-
|
|
3485
|
+
// src/utils/debug-colors.ts
|
|
3486
|
+
var ANSI = {
|
|
3487
|
+
reset: "\x1B[0m",
|
|
3488
|
+
bold: "\x1B[1m",
|
|
3489
|
+
cyan: "\x1B[36m",
|
|
3490
|
+
yellow: "\x1B[33m",
|
|
3491
|
+
green: "\x1B[32m",
|
|
3492
|
+
red: "\x1B[31m",
|
|
3493
|
+
dim: "\x1B[2m"
|
|
3494
|
+
};
|
|
3495
|
+
function color(config, text, tone) {
|
|
3496
|
+
if (!config.colors) {
|
|
3497
|
+
return text;
|
|
3498
|
+
}
|
|
3499
|
+
return `${ANSI[tone]}${text}${ANSI.reset}`;
|
|
3500
|
+
}
|
|
3501
|
+
function dim(config, text) {
|
|
3502
|
+
if (!config.colors) {
|
|
3503
|
+
return text;
|
|
3504
|
+
}
|
|
3505
|
+
return `${ANSI.dim}${text}${ANSI.reset}`;
|
|
3506
|
+
}
|
|
3507
|
+
function title(config, text) {
|
|
3508
|
+
if (!config.colors) {
|
|
3509
|
+
return text;
|
|
3510
|
+
}
|
|
3511
|
+
return `${ANSI.bold}${text}${ANSI.reset}`;
|
|
3512
|
+
}
|
|
3317
3513
|
|
|
3318
3514
|
// src/outdent.ts
|
|
3319
3515
|
var DEFAULT_OPTIONS = {
|
|
@@ -3452,123 +3648,682 @@ function createOutdent(options = {}) {
|
|
|
3452
3648
|
return outdent;
|
|
3453
3649
|
}
|
|
3454
3650
|
|
|
3455
|
-
// src/
|
|
3456
|
-
|
|
3457
|
-
|
|
3458
|
-
|
|
3459
|
-
|
|
3460
|
-
|
|
3461
|
-
|
|
3462
|
-
|
|
3463
|
-
|
|
3464
|
-
|
|
3465
|
-
|
|
3466
|
-
|
|
3467
|
-
|
|
3468
|
-
|
|
3469
|
-
|
|
3470
|
-
|
|
3471
|
-
});
|
|
3472
|
-
emitTrace(parseOptions.onTrace, {
|
|
3473
|
-
stage: "extract",
|
|
3474
|
-
level: "info",
|
|
3475
|
-
message: `Extracted ${candidates.length} candidate(s).`,
|
|
3476
|
-
details: {
|
|
3477
|
-
maxCandidates: parseOptions.maxCandidates,
|
|
3478
|
-
thinkBlocks: sanitized.thinkBlocks.length,
|
|
3479
|
-
thinkDiagnostics: sanitized.diagnostics
|
|
3480
|
-
}
|
|
3481
|
-
});
|
|
3482
|
-
const errors = [];
|
|
3483
|
-
const diagnostics = [];
|
|
3484
|
-
let bestIssues = [];
|
|
3485
|
-
let bestCandidate = candidates[0] ?? null;
|
|
3486
|
-
let bestParsed = null;
|
|
3487
|
-
let bestRepaired = null;
|
|
3488
|
-
for (const candidate of candidates) {
|
|
3489
|
-
const parseAttempt = parseAttemptFromHint(candidate.parseHint, parseOptions.repair) ?? tryParseJsonCandidate(candidate.content, parseOptions.repair);
|
|
3490
|
-
if (!parseAttempt.success) {
|
|
3491
|
-
const diagnostic = {
|
|
3492
|
-
candidateId: candidate.id,
|
|
3493
|
-
source: candidate.source,
|
|
3494
|
-
usedRepair: parseAttempt.usedRepair,
|
|
3495
|
-
parseSuccess: false,
|
|
3496
|
-
validationSuccess: false,
|
|
3497
|
-
selected: false,
|
|
3498
|
-
stage: parseAttempt.stage,
|
|
3499
|
-
message: parseAttempt.error
|
|
3500
|
-
};
|
|
3501
|
-
diagnostics.push(diagnostic);
|
|
3502
|
-
errors.push({
|
|
3503
|
-
stage: parseAttempt.stage,
|
|
3504
|
-
message: parseAttempt.error,
|
|
3505
|
-
candidateId: candidate.id
|
|
3506
|
-
});
|
|
3507
|
-
emitTrace(parseOptions.onTrace, {
|
|
3508
|
-
stage: parseAttempt.stage,
|
|
3509
|
-
level: "error",
|
|
3510
|
-
message: parseAttempt.error,
|
|
3511
|
-
candidateId: candidate.id
|
|
3512
|
-
});
|
|
3513
|
-
continue;
|
|
3514
|
-
}
|
|
3515
|
-
emitTrace(parseOptions.onTrace, {
|
|
3516
|
-
stage: "parse",
|
|
3517
|
-
level: "info",
|
|
3518
|
-
message: parseAttempt.usedRepair ? "Candidate parsed after repair." : "Candidate parsed without repair.",
|
|
3519
|
-
candidateId: candidate.id,
|
|
3520
|
-
details: {
|
|
3521
|
-
usedRepair: parseAttempt.usedRepair
|
|
3522
|
-
}
|
|
3523
|
-
});
|
|
3524
|
-
const validated = schema.safeParse(parseAttempt.parsed);
|
|
3525
|
-
if (validated.success) {
|
|
3526
|
-
const selectedDiagnostic = {
|
|
3527
|
-
candidateId: candidate.id,
|
|
3528
|
-
source: candidate.source,
|
|
3529
|
-
usedRepair: parseAttempt.usedRepair,
|
|
3530
|
-
parseSuccess: true,
|
|
3531
|
-
validationSuccess: true,
|
|
3532
|
-
selected: true,
|
|
3533
|
-
stage: "success"
|
|
3534
|
-
};
|
|
3535
|
-
diagnostics.push(selectedDiagnostic);
|
|
3536
|
-
emitTrace(parseOptions.onTrace, {
|
|
3537
|
-
stage: "result",
|
|
3538
|
-
level: "info",
|
|
3539
|
-
message: `Validation succeeded on candidate ${candidate.id}.`,
|
|
3540
|
-
candidateId: candidate.id
|
|
3541
|
-
});
|
|
3542
|
-
return {
|
|
3543
|
-
success: true,
|
|
3544
|
-
data: validated.data,
|
|
3545
|
-
raw: output,
|
|
3546
|
-
sanitizedRaw: sanitized.visibleText,
|
|
3547
|
-
thinkBlocks: sanitized.thinkBlocks,
|
|
3548
|
-
thinkDiagnostics: sanitized.diagnostics,
|
|
3549
|
-
parsed: parseAttempt.parsed,
|
|
3550
|
-
candidate,
|
|
3551
|
-
repaired: parseAttempt.repaired,
|
|
3552
|
-
candidates,
|
|
3553
|
-
diagnostics,
|
|
3554
|
-
errors,
|
|
3555
|
-
zodIssues: []
|
|
3556
|
-
};
|
|
3557
|
-
}
|
|
3558
|
-
const issues = validated.error.issues;
|
|
3559
|
-
const message = formatZodIssues(issues);
|
|
3560
|
-
const validationDiagnostic = {
|
|
3561
|
-
candidateId: candidate.id,
|
|
3562
|
-
source: candidate.source,
|
|
3563
|
-
usedRepair: parseAttempt.usedRepair,
|
|
3564
|
-
parseSuccess: true,
|
|
3565
|
-
validationSuccess: false,
|
|
3566
|
-
selected: false,
|
|
3567
|
-
stage: "validate",
|
|
3568
|
-
message,
|
|
3569
|
-
zodIssues: issues
|
|
3651
|
+
// src/generate-shared.ts
|
|
3652
|
+
var sharedOutdent = createOutdent({
|
|
3653
|
+
trimLeadingNewline: true,
|
|
3654
|
+
trimTrailingNewline: true,
|
|
3655
|
+
newline: `
|
|
3656
|
+
`
|
|
3657
|
+
});
|
|
3658
|
+
var RE_THINK_TAGS = /<\/?think\s*>/gi;
|
|
3659
|
+
function resolvePrompt(prompt, context) {
|
|
3660
|
+
const resolved = typeof prompt === "function" ? prompt(context) : prompt;
|
|
3661
|
+
return normalizePromptValue(resolved, context);
|
|
3662
|
+
}
|
|
3663
|
+
function normalizePromptValue(value, _context) {
|
|
3664
|
+
if (typeof value === "string") {
|
|
3665
|
+
return {
|
|
3666
|
+
prompt: value
|
|
3570
3667
|
};
|
|
3571
|
-
|
|
3668
|
+
}
|
|
3669
|
+
if (isPromptResolver(value)) {
|
|
3670
|
+
return normalizePromptPayload(value.resolvePrompt(_context));
|
|
3671
|
+
}
|
|
3672
|
+
return normalizePromptPayload(value);
|
|
3673
|
+
}
|
|
3674
|
+
function normalizePromptPayload(value) {
|
|
3675
|
+
const prompt = typeof value.prompt === "string" ? value.prompt : undefined;
|
|
3676
|
+
const messages = Array.isArray(value.messages) ? value.messages.filter(isLLMMessage) : undefined;
|
|
3677
|
+
if ((!prompt || prompt.trim().length === 0) && (!messages || messages.length === 0)) {
|
|
3678
|
+
throw new Error("Structured prompt payload must include a non-empty prompt or messages.");
|
|
3679
|
+
}
|
|
3680
|
+
return {
|
|
3681
|
+
prompt,
|
|
3682
|
+
systemPrompt: typeof value.systemPrompt === "string" ? value.systemPrompt : undefined,
|
|
3683
|
+
messages: messages && messages.length > 0 ? messages.map((message) => ({ ...message })) : undefined
|
|
3684
|
+
};
|
|
3685
|
+
}
|
|
3686
|
+
function applyPromptOutdent(payload, enabled) {
|
|
3687
|
+
if (!enabled) {
|
|
3688
|
+
return payload;
|
|
3689
|
+
}
|
|
3690
|
+
return {
|
|
3691
|
+
prompt: typeof payload.prompt === "string" ? sharedOutdent.string(payload.prompt) : undefined,
|
|
3692
|
+
systemPrompt: applyOutdentToOptionalPrompt(payload.systemPrompt, enabled),
|
|
3693
|
+
messages: payload.messages?.map((message) => ({
|
|
3694
|
+
...message,
|
|
3695
|
+
content: typeof message.content === "string" ? sharedOutdent.string(message.content) : message.content
|
|
3696
|
+
}))
|
|
3697
|
+
};
|
|
3698
|
+
}
|
|
3699
|
+
function applyOutdentToOptionalPrompt(value, enabled) {
|
|
3700
|
+
if (!enabled || typeof value !== "string") {
|
|
3701
|
+
return value;
|
|
3702
|
+
}
|
|
3703
|
+
return sharedOutdent.string(value);
|
|
3704
|
+
}
|
|
3705
|
+
function mergeSystemPrompts(primary, secondary) {
|
|
3706
|
+
const prompts = [primary, secondary].map((value) => value?.trim()).filter((value) => Boolean(value));
|
|
3707
|
+
if (prompts.length === 0) {
|
|
3708
|
+
return;
|
|
3709
|
+
}
|
|
3710
|
+
return prompts.join(`
|
|
3711
|
+
|
|
3712
|
+
`);
|
|
3713
|
+
}
|
|
3714
|
+
function normalizeStreamConfig(option) {
|
|
3715
|
+
if (typeof option === "boolean") {
|
|
3716
|
+
return {
|
|
3717
|
+
enabled: option
|
|
3718
|
+
};
|
|
3719
|
+
}
|
|
3720
|
+
if (!option) {
|
|
3721
|
+
return {
|
|
3722
|
+
enabled: false
|
|
3723
|
+
};
|
|
3724
|
+
}
|
|
3725
|
+
return {
|
|
3726
|
+
enabled: option.enabled ?? true,
|
|
3727
|
+
onData: option.onData,
|
|
3728
|
+
to: option.to
|
|
3729
|
+
};
|
|
3730
|
+
}
|
|
3731
|
+
function normalizeDebugConfig(option) {
|
|
3732
|
+
if (typeof option === "boolean") {
|
|
3733
|
+
return {
|
|
3734
|
+
enabled: option,
|
|
3735
|
+
colors: true,
|
|
3736
|
+
verbose: false,
|
|
3737
|
+
logger: (line) => console.log(line)
|
|
3738
|
+
};
|
|
3739
|
+
}
|
|
3740
|
+
if (!option) {
|
|
3741
|
+
return {
|
|
3742
|
+
enabled: false,
|
|
3743
|
+
colors: true,
|
|
3744
|
+
verbose: false,
|
|
3745
|
+
logger: (line) => console.log(line)
|
|
3746
|
+
};
|
|
3747
|
+
}
|
|
3748
|
+
return {
|
|
3749
|
+
enabled: option.enabled ?? true,
|
|
3750
|
+
colors: option.colors ?? true,
|
|
3751
|
+
verbose: option.verbose ?? false,
|
|
3752
|
+
logger: option.logger ?? ((line) => console.log(line))
|
|
3753
|
+
};
|
|
3754
|
+
}
|
|
3755
|
+
function withToolTimeout(client, toolTimeoutMs) {
|
|
3756
|
+
return {
|
|
3757
|
+
id: client.id,
|
|
3758
|
+
listTools: client.listTools.bind(client),
|
|
3759
|
+
close: client.close?.bind(client),
|
|
3760
|
+
async callTool(params) {
|
|
3761
|
+
let timeoutId;
|
|
3762
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
3763
|
+
timeoutId = setTimeout(() => reject(new Error(`Tool call timed out after ${toolTimeoutMs}ms`)), toolTimeoutMs);
|
|
3764
|
+
});
|
|
3765
|
+
try {
|
|
3766
|
+
return await Promise.race([client.callTool(params), timeoutPromise]);
|
|
3767
|
+
} finally {
|
|
3768
|
+
clearTimeout(timeoutId);
|
|
3769
|
+
}
|
|
3770
|
+
}
|
|
3771
|
+
};
|
|
3772
|
+
}
|
|
3773
|
+
function applyToolTimeout(clients, toolTimeoutMs) {
|
|
3774
|
+
return clients.map((client) => withToolTimeout(client, toolTimeoutMs));
|
|
3775
|
+
}
|
|
3776
|
+
async function callModel(adapter, options) {
|
|
3777
|
+
const requestSignal = options.request?.signal ?? (options.timeout?.request !== undefined ? AbortSignal.timeout(options.timeout.request) : undefined);
|
|
3778
|
+
const requestPayload = {
|
|
3779
|
+
prompt: options.prompt,
|
|
3780
|
+
messages: options.messages,
|
|
3781
|
+
systemPrompt: options.systemPrompt,
|
|
3782
|
+
temperature: options.request?.temperature,
|
|
3783
|
+
reasoningEffort: options.request?.reasoningEffort,
|
|
3784
|
+
maxTokens: options.request?.maxTokens,
|
|
3785
|
+
mcpClients: options.request?.mcpClients,
|
|
3786
|
+
toolChoice: options.request?.toolChoice,
|
|
3787
|
+
parallelToolCalls: options.request?.parallelToolCalls,
|
|
3788
|
+
maxToolRounds: options.request?.maxToolRounds,
|
|
3789
|
+
onToolExecution: options.request?.onToolExecution,
|
|
3790
|
+
transformToolOutput: options.request?.transformToolOutput,
|
|
3791
|
+
transformToolArguments: options.request?.transformToolArguments,
|
|
3792
|
+
transformToolCallParams: options.request?.transformToolCallParams,
|
|
3793
|
+
unknownToolError: options.request?.unknownToolError,
|
|
3794
|
+
toolDebug: options.request?.toolDebug,
|
|
3795
|
+
body: options.request?.body,
|
|
3796
|
+
signal: requestSignal
|
|
3797
|
+
};
|
|
3798
|
+
emitDebugRequest(options.debug, {
|
|
3799
|
+
label: options.debugLabel,
|
|
3800
|
+
provider: adapter.provider,
|
|
3801
|
+
model: adapter.model,
|
|
3802
|
+
attempt: options.attempt,
|
|
3803
|
+
selfHealAttempt: options.selfHeal,
|
|
3804
|
+
selfHealEnabled: options.selfHealEnabled,
|
|
3805
|
+
stream: options.stream.enabled && !!adapter.stream,
|
|
3806
|
+
requestPayload
|
|
3807
|
+
});
|
|
3808
|
+
options.observe?.(options.buildEvent({
|
|
3809
|
+
stage: "llm.request",
|
|
3810
|
+
message: "Sending LLM request.",
|
|
3811
|
+
details: {
|
|
3812
|
+
provider: adapter.provider,
|
|
3813
|
+
model: adapter.model,
|
|
3814
|
+
stream: options.stream.enabled && !!adapter.stream
|
|
3815
|
+
}
|
|
3816
|
+
}));
|
|
3817
|
+
if (options.stream.enabled && adapter.stream) {
|
|
3818
|
+
let latestUsage;
|
|
3819
|
+
let latestFinishReason;
|
|
3820
|
+
let streamedProviderText = "";
|
|
3821
|
+
let streamedDedicatedReasoning = "";
|
|
3822
|
+
let lastSnapshotFingerprint;
|
|
3823
|
+
let previousSnapshotText = "";
|
|
3824
|
+
let previousSnapshotReasoning = "";
|
|
3825
|
+
const emitStreamingData = (done, usage2, finishReason2) => {
|
|
3826
|
+
const normalized2 = normalizeModelOutput(streamedProviderText, streamedDedicatedReasoning);
|
|
3827
|
+
const snapshot = options.buildSnapshot(normalized2);
|
|
3828
|
+
const fingerprint = toStreamDataFingerprint(snapshot);
|
|
3829
|
+
if (!done && fingerprint === lastSnapshotFingerprint) {
|
|
3830
|
+
return;
|
|
3831
|
+
}
|
|
3832
|
+
const delta = {
|
|
3833
|
+
text: normalized2.text.startsWith(previousSnapshotText) ? normalized2.text.slice(previousSnapshotText.length) : "",
|
|
3834
|
+
reasoning: normalized2.reasoning.startsWith(previousSnapshotReasoning) ? normalized2.reasoning.slice(previousSnapshotReasoning.length) : ""
|
|
3835
|
+
};
|
|
3836
|
+
lastSnapshotFingerprint = fingerprint;
|
|
3837
|
+
previousSnapshotText = normalized2.text;
|
|
3838
|
+
previousSnapshotReasoning = normalized2.reasoning;
|
|
3839
|
+
options.stream.onData?.({
|
|
3840
|
+
delta,
|
|
3841
|
+
snapshot,
|
|
3842
|
+
done,
|
|
3843
|
+
usage: usage2,
|
|
3844
|
+
finishReason: finishReason2
|
|
3845
|
+
});
|
|
3846
|
+
if (options.stream.to === "stdout" && delta.text) {
|
|
3847
|
+
process.stdout.write(delta.text);
|
|
3848
|
+
}
|
|
3849
|
+
options.observe?.(options.buildEvent({
|
|
3850
|
+
stage: "llm.stream.data",
|
|
3851
|
+
message: done ? "Streaming response completed." : "Streaming response updated.",
|
|
3852
|
+
details: {
|
|
3853
|
+
done,
|
|
3854
|
+
finishReason: finishReason2
|
|
3855
|
+
}
|
|
3856
|
+
}));
|
|
3857
|
+
};
|
|
3858
|
+
const handleTextDelta = (delta) => {
|
|
3859
|
+
if (!delta) {
|
|
3860
|
+
return;
|
|
3861
|
+
}
|
|
3862
|
+
streamedProviderText += delta;
|
|
3863
|
+
options.observe?.(options.buildEvent({
|
|
3864
|
+
stage: "llm.stream.delta",
|
|
3865
|
+
message: "Received stream delta.",
|
|
3866
|
+
details: {
|
|
3867
|
+
chars: delta.length
|
|
3868
|
+
}
|
|
3869
|
+
}));
|
|
3870
|
+
emitStreamingData(false);
|
|
3871
|
+
};
|
|
3872
|
+
const handleReasoningDelta = (delta) => {
|
|
3873
|
+
if (!delta) {
|
|
3874
|
+
return;
|
|
3875
|
+
}
|
|
3876
|
+
streamedDedicatedReasoning += delta;
|
|
3877
|
+
emitStreamingData(false);
|
|
3878
|
+
};
|
|
3879
|
+
const response2 = await adapter.stream(requestPayload, {
|
|
3880
|
+
onChunk: (chunk) => {
|
|
3881
|
+
if (chunk.textDelta) {
|
|
3882
|
+
handleTextDelta(chunk.textDelta);
|
|
3883
|
+
}
|
|
3884
|
+
if (chunk.reasoningDelta) {
|
|
3885
|
+
handleReasoningDelta(chunk.reasoningDelta);
|
|
3886
|
+
}
|
|
3887
|
+
if (chunk.usage) {
|
|
3888
|
+
latestUsage = preferLatestUsage(latestUsage, chunk.usage);
|
|
3889
|
+
}
|
|
3890
|
+
if (chunk.finishReason) {
|
|
3891
|
+
latestFinishReason = chunk.finishReason;
|
|
3892
|
+
}
|
|
3893
|
+
}
|
|
3894
|
+
});
|
|
3895
|
+
streamedProviderText = typeof response2.text === "string" ? response2.text : streamedProviderText;
|
|
3896
|
+
streamedDedicatedReasoning = typeof response2.reasoning === "string" ? response2.reasoning : streamedDedicatedReasoning;
|
|
3897
|
+
const finalNormalized = normalizeModelOutput(streamedProviderText, streamedDedicatedReasoning);
|
|
3898
|
+
const usage = preferLatestUsage(latestUsage, response2.usage);
|
|
3899
|
+
const finishReason = response2.finishReason ?? latestFinishReason;
|
|
3900
|
+
emitStreamingData(true, usage, finishReason);
|
|
3901
|
+
options.observe?.(options.buildEvent({
|
|
3902
|
+
stage: "llm.response",
|
|
3903
|
+
message: "Streaming response completed.",
|
|
3904
|
+
details: {
|
|
3905
|
+
via: "stream",
|
|
3906
|
+
chars: finalNormalized.parseSource.length,
|
|
3907
|
+
finishReason
|
|
3908
|
+
}
|
|
3909
|
+
}));
|
|
3910
|
+
emitDebugResponse(options.debug, {
|
|
3911
|
+
label: options.debugLabel,
|
|
3912
|
+
attempt: options.attempt,
|
|
3913
|
+
selfHealAttempt: options.selfHeal,
|
|
3914
|
+
selfHealEnabled: options.selfHealEnabled,
|
|
3915
|
+
via: "stream",
|
|
3916
|
+
text: finalNormalized.text,
|
|
3917
|
+
reasoning: finalNormalized.reasoning,
|
|
3918
|
+
parseSource: finalNormalized.parseSource,
|
|
3919
|
+
usage,
|
|
3920
|
+
finishReason
|
|
3921
|
+
});
|
|
3922
|
+
return {
|
|
3923
|
+
text: finalNormalized.text,
|
|
3924
|
+
reasoning: finalNormalized.reasoning,
|
|
3925
|
+
thinkBlocks: finalNormalized.thinkBlocks,
|
|
3926
|
+
parseSource: finalNormalized.parseSource,
|
|
3927
|
+
via: "stream",
|
|
3928
|
+
usage,
|
|
3929
|
+
finishReason
|
|
3930
|
+
};
|
|
3931
|
+
}
|
|
3932
|
+
const response = await adapter.complete(requestPayload);
|
|
3933
|
+
const normalized = normalizeModelOutput(response.text, response.reasoning);
|
|
3934
|
+
options.observe?.(options.buildEvent({
|
|
3935
|
+
stage: "llm.response",
|
|
3936
|
+
message: "Completion response received.",
|
|
3937
|
+
details: {
|
|
3938
|
+
via: "complete",
|
|
3939
|
+
chars: normalized.parseSource.length,
|
|
3940
|
+
finishReason: response.finishReason
|
|
3941
|
+
}
|
|
3942
|
+
}));
|
|
3943
|
+
emitDebugResponse(options.debug, {
|
|
3944
|
+
label: options.debugLabel,
|
|
3945
|
+
attempt: options.attempt,
|
|
3946
|
+
selfHealAttempt: options.selfHeal,
|
|
3947
|
+
selfHealEnabled: options.selfHealEnabled,
|
|
3948
|
+
via: "complete",
|
|
3949
|
+
text: normalized.text,
|
|
3950
|
+
reasoning: normalized.reasoning,
|
|
3951
|
+
parseSource: normalized.parseSource,
|
|
3952
|
+
usage: response.usage,
|
|
3953
|
+
finishReason: response.finishReason
|
|
3954
|
+
});
|
|
3955
|
+
return {
|
|
3956
|
+
text: normalized.text,
|
|
3957
|
+
reasoning: normalized.reasoning,
|
|
3958
|
+
thinkBlocks: normalized.thinkBlocks,
|
|
3959
|
+
parseSource: normalized.parseSource,
|
|
3960
|
+
via: "complete",
|
|
3961
|
+
usage: response.usage,
|
|
3962
|
+
finishReason: response.finishReason
|
|
3963
|
+
};
|
|
3964
|
+
}
|
|
3965
|
+
function normalizeModelOutput(text, dedicatedReasoning) {
|
|
3966
|
+
const sanitized = sanitizeThink(text);
|
|
3967
|
+
const visibleText = stripThinkBlocks(text, sanitized.thinkBlocks);
|
|
3968
|
+
const reasoning = joinReasoningSegments([
|
|
3969
|
+
dedicatedReasoning,
|
|
3970
|
+
...sanitized.thinkBlocks.map((block) => block.content)
|
|
3971
|
+
]);
|
|
3972
|
+
return {
|
|
3973
|
+
text: visibleText,
|
|
3974
|
+
reasoning,
|
|
3975
|
+
thinkBlocks: sanitized.thinkBlocks,
|
|
3976
|
+
parseSource: composeParseSource(visibleText, reasoning)
|
|
3977
|
+
};
|
|
3978
|
+
}
|
|
3979
|
+
function composeParseSource(text, reasoning) {
|
|
3980
|
+
if (typeof reasoning !== "string" || reasoning.length === 0) {
|
|
3981
|
+
return text;
|
|
3982
|
+
}
|
|
3983
|
+
const sanitized = reasoning.replace(RE_THINK_TAGS, "");
|
|
3984
|
+
if (sanitized.length === 0) {
|
|
3985
|
+
return text;
|
|
3986
|
+
}
|
|
3987
|
+
return `<think>${sanitized}</think>${text}`;
|
|
3988
|
+
}
|
|
3989
|
+
function aggregateUsage(attempts) {
|
|
3990
|
+
let usage;
|
|
3991
|
+
for (const attempt of attempts) {
|
|
3992
|
+
usage = mergeUsage2(usage, attempt.usage);
|
|
3993
|
+
}
|
|
3994
|
+
return usage;
|
|
3995
|
+
}
|
|
3996
|
+
function mergeUsage2(base, next) {
|
|
3997
|
+
if (!base && !next) {
|
|
3998
|
+
return;
|
|
3999
|
+
}
|
|
4000
|
+
return {
|
|
4001
|
+
inputTokens: (base?.inputTokens ?? 0) + (next?.inputTokens ?? 0),
|
|
4002
|
+
outputTokens: (base?.outputTokens ?? 0) + (next?.outputTokens ?? 0),
|
|
4003
|
+
totalTokens: (base?.totalTokens ?? 0) + (next?.totalTokens ?? 0),
|
|
4004
|
+
cost: (base?.cost ?? 0) + (next?.cost ?? 0)
|
|
4005
|
+
};
|
|
4006
|
+
}
|
|
4007
|
+
function isPromptResolver(value) {
|
|
4008
|
+
return typeof value === "object" && value !== null && "resolvePrompt" in value && typeof value.resolvePrompt === "function";
|
|
4009
|
+
}
|
|
4010
|
+
function isLLMMessage(value) {
|
|
4011
|
+
if (typeof value !== "object" || value === null) {
|
|
4012
|
+
return false;
|
|
4013
|
+
}
|
|
4014
|
+
const candidate = value;
|
|
4015
|
+
if (candidate.role !== "system" && candidate.role !== "user" && candidate.role !== "assistant" && candidate.role !== "tool") {
|
|
4016
|
+
return false;
|
|
4017
|
+
}
|
|
4018
|
+
return "content" in candidate;
|
|
4019
|
+
}
|
|
4020
|
+
function joinReasoningSegments(parts) {
|
|
4021
|
+
return parts.map((value) => value?.trim()).filter((value) => Boolean(value)).join(`
|
|
4022
|
+
|
|
4023
|
+
`);
|
|
4024
|
+
}
|
|
4025
|
+
function stripThinkBlocks(text, thinkBlocks) {
|
|
4026
|
+
if (thinkBlocks.length === 0) {
|
|
4027
|
+
return text;
|
|
4028
|
+
}
|
|
4029
|
+
let output = "";
|
|
4030
|
+
let cursor = 0;
|
|
4031
|
+
for (const block of thinkBlocks) {
|
|
4032
|
+
output += text.slice(cursor, block.start);
|
|
4033
|
+
cursor = block.end;
|
|
4034
|
+
}
|
|
4035
|
+
output += text.slice(cursor);
|
|
4036
|
+
return output;
|
|
4037
|
+
}
|
|
4038
|
+
function toStreamDataFingerprint(value) {
|
|
4039
|
+
try {
|
|
4040
|
+
return JSON.stringify(value);
|
|
4041
|
+
} catch {
|
|
4042
|
+
return "__unserializable__";
|
|
4043
|
+
}
|
|
4044
|
+
}
|
|
4045
|
+
function emitDebugRequest(config, input) {
|
|
4046
|
+
const requestBody = input.requestPayload.body !== undefined ? JSON.stringify(input.requestPayload.body, null, 2) : "(none)";
|
|
4047
|
+
const requestMessages = input.requestPayload.messages !== undefined ? JSON.stringify(input.requestPayload.messages, null, 2) : "(none)";
|
|
4048
|
+
const lines = [
|
|
4049
|
+
color(config, title(config, [
|
|
4050
|
+
`[${input.label}][request]`,
|
|
4051
|
+
`attempt=${input.attempt}`,
|
|
4052
|
+
`selfHealEnabled=${input.selfHealEnabled}`,
|
|
4053
|
+
`selfHealAttempt=${input.selfHealAttempt}`
|
|
4054
|
+
].join(" ")), "cyan"),
|
|
4055
|
+
dim(config, [
|
|
4056
|
+
`provider=${input.provider ?? "unknown"}`,
|
|
4057
|
+
`model=${input.model ?? "unknown"}`,
|
|
4058
|
+
`stream=${input.stream}`
|
|
4059
|
+
].join(" ")),
|
|
4060
|
+
color(config, "prompt:", "yellow"),
|
|
4061
|
+
input.requestPayload.prompt ?? "(none)",
|
|
4062
|
+
color(config, "messages:", "yellow"),
|
|
4063
|
+
requestMessages,
|
|
4064
|
+
color(config, "systemPrompt:", "yellow"),
|
|
4065
|
+
input.requestPayload.systemPrompt ?? "(none)",
|
|
4066
|
+
color(config, "request.body:", "yellow"),
|
|
4067
|
+
requestBody
|
|
4068
|
+
];
|
|
4069
|
+
emitDebug(config, lines.join(`
|
|
4070
|
+
`));
|
|
4071
|
+
}
|
|
4072
|
+
function emitDebugResponse(config, input) {
|
|
4073
|
+
const text = input.text.length > 0 ? input.text : "(none)";
|
|
4074
|
+
const reasoning = input.reasoning.length > 0 ? input.reasoning : "(none)";
|
|
4075
|
+
const metadata = [
|
|
4076
|
+
`via=${input.via}`,
|
|
4077
|
+
`textChars=${input.text.length}`,
|
|
4078
|
+
`reasoningChars=${input.reasoning.length}`
|
|
4079
|
+
];
|
|
4080
|
+
if (config.verbose) {
|
|
4081
|
+
metadata.push(`parseSourceChars=${input.parseSource.length}`);
|
|
4082
|
+
}
|
|
4083
|
+
metadata.push(`finishReason=${input.finishReason ?? "unknown"}`, `usage=${JSON.stringify(input.usage ?? {})}`);
|
|
4084
|
+
const lines = [
|
|
4085
|
+
color(config, title(config, [
|
|
4086
|
+
`[${input.label}][response]`,
|
|
4087
|
+
`attempt=${input.attempt}`,
|
|
4088
|
+
`selfHealEnabled=${input.selfHealEnabled}`,
|
|
4089
|
+
`selfHealAttempt=${input.selfHealAttempt}`
|
|
4090
|
+
].join(" ")), "green"),
|
|
4091
|
+
dim(config, metadata.join(" ")),
|
|
4092
|
+
color(config, "text:", "yellow"),
|
|
4093
|
+
text,
|
|
4094
|
+
color(config, "reasoning:", "yellow"),
|
|
4095
|
+
reasoning
|
|
4096
|
+
];
|
|
4097
|
+
if (config.verbose) {
|
|
4098
|
+
lines.push(color(config, "parseSource:", "yellow"), input.parseSource);
|
|
4099
|
+
}
|
|
4100
|
+
emitDebug(config, lines.join(`
|
|
4101
|
+
`));
|
|
4102
|
+
}
|
|
4103
|
+
function emitDebug(config, message) {
|
|
4104
|
+
if (!config.enabled) {
|
|
4105
|
+
return;
|
|
4106
|
+
}
|
|
4107
|
+
config.logger(message);
|
|
4108
|
+
}
|
|
4109
|
+
|
|
4110
|
+
// src/generate.ts
|
|
4111
|
+
async function generate(adapter, promptOrOptions, callOptions) {
|
|
4112
|
+
const normalized = normalizeGenerateInput(promptOrOptions, callOptions);
|
|
4113
|
+
const useOutdent = normalized.outdent ?? true;
|
|
4114
|
+
const streamConfig = normalizeStreamConfig(normalized.stream);
|
|
4115
|
+
const debugConfig = normalizeDebugConfig(normalized.debug);
|
|
4116
|
+
const resolvedPrompt = applyPromptOutdent(resolvePrompt(normalized.prompt, { mode: "loose" }), useOutdent);
|
|
4117
|
+
const resolvedSystemPrompt = applyOutdentToOptionalPrompt(normalized.systemPrompt, useOutdent);
|
|
4118
|
+
const preparedPrompt = prepareGeneratePromptPayload(resolvedPrompt, resolvedSystemPrompt);
|
|
4119
|
+
const resolvedRequest = normalized.timeout?.tool !== undefined && normalized.request?.mcpClients !== undefined ? {
|
|
4120
|
+
...normalized.request,
|
|
4121
|
+
mcpClients: applyToolTimeout(normalized.request.mcpClients, normalized.timeout.tool)
|
|
4122
|
+
} : normalized.request;
|
|
4123
|
+
const response = await callModel(adapter, {
|
|
4124
|
+
prompt: preparedPrompt.prompt,
|
|
4125
|
+
messages: preparedPrompt.messages,
|
|
4126
|
+
systemPrompt: preparedPrompt.systemPrompt,
|
|
4127
|
+
request: resolvedRequest,
|
|
4128
|
+
stream: streamConfig,
|
|
4129
|
+
observe: normalized.observe,
|
|
4130
|
+
buildEvent: ({ stage, message, details }) => ({
|
|
4131
|
+
stage,
|
|
4132
|
+
attempt: 1,
|
|
4133
|
+
message,
|
|
4134
|
+
details
|
|
4135
|
+
}),
|
|
4136
|
+
buildSnapshot: (model) => ({
|
|
4137
|
+
text: model.text,
|
|
4138
|
+
reasoning: model.reasoning
|
|
4139
|
+
}),
|
|
4140
|
+
debug: debugConfig,
|
|
4141
|
+
debugLabel: "generate",
|
|
4142
|
+
attempt: 1,
|
|
4143
|
+
selfHeal: false,
|
|
4144
|
+
selfHealEnabled: false,
|
|
4145
|
+
timeout: normalized.timeout
|
|
4146
|
+
});
|
|
4147
|
+
const attempt = {
|
|
4148
|
+
attempt: 1,
|
|
4149
|
+
via: response.via,
|
|
4150
|
+
text: response.text,
|
|
4151
|
+
reasoning: response.reasoning,
|
|
4152
|
+
usage: response.usage,
|
|
4153
|
+
finishReason: response.finishReason
|
|
4154
|
+
};
|
|
4155
|
+
const attempts = [attempt];
|
|
4156
|
+
normalized.observe?.({
|
|
4157
|
+
stage: "result",
|
|
4158
|
+
attempt: 1,
|
|
4159
|
+
message: "Text generation completed.",
|
|
4160
|
+
details: {
|
|
4161
|
+
via: response.via,
|
|
4162
|
+
finishReason: response.finishReason
|
|
4163
|
+
}
|
|
4164
|
+
});
|
|
4165
|
+
return {
|
|
4166
|
+
text: attempt.text,
|
|
4167
|
+
reasoning: attempt.reasoning,
|
|
4168
|
+
attempts,
|
|
4169
|
+
usage: aggregateUsage(attempts),
|
|
4170
|
+
finishReason: attempt.finishReason
|
|
4171
|
+
};
|
|
4172
|
+
}
|
|
4173
|
+
function normalizeGenerateInput(promptOrOptions, callOptions) {
|
|
4174
|
+
if (isGenerateOptions(promptOrOptions)) {
|
|
4175
|
+
return promptOrOptions;
|
|
4176
|
+
}
|
|
4177
|
+
if (!promptOrOptions) {
|
|
4178
|
+
throw new Error("Missing prompt in generate(adapter, prompt, options?) call.");
|
|
4179
|
+
}
|
|
4180
|
+
return {
|
|
4181
|
+
...callOptions ?? {},
|
|
4182
|
+
prompt: promptOrOptions
|
|
4183
|
+
};
|
|
4184
|
+
}
|
|
4185
|
+
function isGenerateOptions(value) {
|
|
4186
|
+
return typeof value === "object" && value !== null && "prompt" in value;
|
|
4187
|
+
}
|
|
4188
|
+
function prepareGeneratePromptPayload(payload, systemPrompt) {
|
|
4189
|
+
if (Array.isArray(payload.messages) && payload.messages.length > 0) {
|
|
4190
|
+
const messages = payload.messages.map((message) => ({ ...message }));
|
|
4191
|
+
const mergedSystemPrompt = mergeSystemPrompts(payload.systemPrompt, systemPrompt);
|
|
4192
|
+
const systemMessages = mergedSystemPrompt ? [{ role: "system", content: mergedSystemPrompt }] : [];
|
|
4193
|
+
return {
|
|
4194
|
+
messages: [...systemMessages, ...messages]
|
|
4195
|
+
};
|
|
4196
|
+
}
|
|
4197
|
+
const resolvedPrompt = payload.prompt?.trim();
|
|
4198
|
+
if (!resolvedPrompt) {
|
|
4199
|
+
throw new Error("Structured prompt payload must include a non-empty prompt or messages.");
|
|
4200
|
+
}
|
|
4201
|
+
return {
|
|
4202
|
+
prompt: resolvedPrompt,
|
|
4203
|
+
systemPrompt: mergeSystemPrompts(payload.systemPrompt, systemPrompt)
|
|
4204
|
+
};
|
|
4205
|
+
}
|
|
4206
|
+
|
|
4207
|
+
// src/structured.ts
|
|
4208
|
+
import { jsonrepair as jsonrepair3 } from "jsonrepair";
|
|
4209
|
+
|
|
4210
|
+
// src/parse.ts
|
|
4211
|
+
import { jsonrepair as jsonrepair2 } from "jsonrepair";
|
|
4212
|
+
function parseLLMOutput(output, schema, options = {}) {
|
|
4213
|
+
const sanitized = sanitizeThink(output);
|
|
4214
|
+
const parseOptions = {
|
|
4215
|
+
repair: options.repair ?? true,
|
|
4216
|
+
maxCandidates: options.maxCandidates ?? 5,
|
|
4217
|
+
acceptArrays: options.acceptArrays ?? true,
|
|
4218
|
+
extraction: options.extraction,
|
|
4219
|
+
onTrace: options.onTrace
|
|
4220
|
+
};
|
|
4221
|
+
const candidates = extractJsonCandidates(sanitized.visibleText, {
|
|
4222
|
+
maxCandidates: parseOptions.maxCandidates,
|
|
4223
|
+
acceptArrays: parseOptions.acceptArrays,
|
|
4224
|
+
allowRepairHints: parseOptions.repair,
|
|
4225
|
+
heuristics: parseOptions.extraction
|
|
4226
|
+
});
|
|
4227
|
+
emitTrace(parseOptions.onTrace, {
|
|
4228
|
+
stage: "extract",
|
|
4229
|
+
level: "info",
|
|
4230
|
+
message: `Extracted ${candidates.length} candidate(s).`,
|
|
4231
|
+
details: {
|
|
4232
|
+
maxCandidates: parseOptions.maxCandidates,
|
|
4233
|
+
thinkBlocks: sanitized.thinkBlocks.length,
|
|
4234
|
+
thinkDiagnostics: sanitized.diagnostics
|
|
4235
|
+
}
|
|
4236
|
+
});
|
|
4237
|
+
const errors = [];
|
|
4238
|
+
const diagnostics = [];
|
|
4239
|
+
let bestIssues = [];
|
|
4240
|
+
let bestCandidate = candidates[0] ?? null;
|
|
4241
|
+
let bestParsed = null;
|
|
4242
|
+
let bestRepaired = null;
|
|
4243
|
+
for (const candidate of candidates) {
|
|
4244
|
+
const parseAttempt = parseAttemptFromHint(candidate.parseHint, parseOptions.repair) ?? tryParseJsonCandidate(candidate.content, parseOptions.repair);
|
|
4245
|
+
if (!parseAttempt.success) {
|
|
4246
|
+
const diagnostic = {
|
|
4247
|
+
candidateId: candidate.id,
|
|
4248
|
+
source: candidate.source,
|
|
4249
|
+
usedRepair: parseAttempt.usedRepair,
|
|
4250
|
+
parseSuccess: false,
|
|
4251
|
+
validationSuccess: false,
|
|
4252
|
+
selected: false,
|
|
4253
|
+
stage: parseAttempt.stage,
|
|
4254
|
+
message: parseAttempt.error
|
|
4255
|
+
};
|
|
4256
|
+
diagnostics.push(diagnostic);
|
|
4257
|
+
errors.push({
|
|
4258
|
+
stage: parseAttempt.stage,
|
|
4259
|
+
message: parseAttempt.error,
|
|
4260
|
+
candidateId: candidate.id
|
|
4261
|
+
});
|
|
4262
|
+
emitTrace(parseOptions.onTrace, {
|
|
4263
|
+
stage: parseAttempt.stage,
|
|
4264
|
+
level: "error",
|
|
4265
|
+
message: parseAttempt.error,
|
|
4266
|
+
candidateId: candidate.id
|
|
4267
|
+
});
|
|
4268
|
+
continue;
|
|
4269
|
+
}
|
|
4270
|
+
emitTrace(parseOptions.onTrace, {
|
|
4271
|
+
stage: "parse",
|
|
4272
|
+
level: "info",
|
|
4273
|
+
message: parseAttempt.usedRepair ? "Candidate parsed after repair." : "Candidate parsed without repair.",
|
|
4274
|
+
candidateId: candidate.id,
|
|
4275
|
+
details: {
|
|
4276
|
+
usedRepair: parseAttempt.usedRepair
|
|
4277
|
+
}
|
|
4278
|
+
});
|
|
4279
|
+
const validated = schema.safeParse(parseAttempt.parsed);
|
|
4280
|
+
if (validated.success) {
|
|
4281
|
+
const selectedDiagnostic = {
|
|
4282
|
+
candidateId: candidate.id,
|
|
4283
|
+
source: candidate.source,
|
|
4284
|
+
usedRepair: parseAttempt.usedRepair,
|
|
4285
|
+
parseSuccess: true,
|
|
4286
|
+
validationSuccess: true,
|
|
4287
|
+
selected: true,
|
|
4288
|
+
stage: "success"
|
|
4289
|
+
};
|
|
4290
|
+
diagnostics.push(selectedDiagnostic);
|
|
4291
|
+
emitTrace(parseOptions.onTrace, {
|
|
4292
|
+
stage: "result",
|
|
4293
|
+
level: "info",
|
|
4294
|
+
message: `Validation succeeded on candidate ${candidate.id}.`,
|
|
4295
|
+
candidateId: candidate.id
|
|
4296
|
+
});
|
|
4297
|
+
return {
|
|
4298
|
+
success: true,
|
|
4299
|
+
data: validated.data,
|
|
4300
|
+
raw: output,
|
|
4301
|
+
sanitizedRaw: sanitized.visibleText,
|
|
4302
|
+
thinkBlocks: sanitized.thinkBlocks,
|
|
4303
|
+
thinkDiagnostics: sanitized.diagnostics,
|
|
4304
|
+
parsed: parseAttempt.parsed,
|
|
4305
|
+
candidate,
|
|
4306
|
+
repaired: parseAttempt.repaired,
|
|
4307
|
+
candidates,
|
|
4308
|
+
diagnostics,
|
|
4309
|
+
errors,
|
|
4310
|
+
zodIssues: []
|
|
4311
|
+
};
|
|
4312
|
+
}
|
|
4313
|
+
const issues = validated.error.issues;
|
|
4314
|
+
const message = formatZodIssues(issues);
|
|
4315
|
+
const validationDiagnostic = {
|
|
4316
|
+
candidateId: candidate.id,
|
|
4317
|
+
source: candidate.source,
|
|
4318
|
+
usedRepair: parseAttempt.usedRepair,
|
|
4319
|
+
parseSuccess: true,
|
|
4320
|
+
validationSuccess: false,
|
|
4321
|
+
selected: false,
|
|
4322
|
+
stage: "validate",
|
|
4323
|
+
message,
|
|
4324
|
+
zodIssues: issues
|
|
4325
|
+
};
|
|
4326
|
+
diagnostics.push(validationDiagnostic);
|
|
3572
4327
|
if (bestIssues.length === 0 || issues.length < bestIssues.length) {
|
|
3573
4328
|
bestIssues = issues;
|
|
3574
4329
|
bestCandidate = candidate;
|
|
@@ -3751,48 +4506,19 @@ function formatZodIssues(issues) {
|
|
|
3751
4506
|
`);
|
|
3752
4507
|
}
|
|
3753
4508
|
|
|
3754
|
-
// src/utils/debug-colors.ts
|
|
3755
|
-
var ANSI = {
|
|
3756
|
-
reset: "\x1B[0m",
|
|
3757
|
-
bold: "\x1B[1m",
|
|
3758
|
-
cyan: "\x1B[36m",
|
|
3759
|
-
yellow: "\x1B[33m",
|
|
3760
|
-
green: "\x1B[32m",
|
|
3761
|
-
red: "\x1B[31m",
|
|
3762
|
-
dim: "\x1B[2m"
|
|
3763
|
-
};
|
|
3764
|
-
function color(config, text, tone) {
|
|
3765
|
-
if (!config.colors) {
|
|
3766
|
-
return text;
|
|
3767
|
-
}
|
|
3768
|
-
return `${ANSI[tone]}${text}${ANSI.reset}`;
|
|
3769
|
-
}
|
|
3770
|
-
function dim(config, text) {
|
|
3771
|
-
if (!config.colors) {
|
|
3772
|
-
return text;
|
|
3773
|
-
}
|
|
3774
|
-
return `${ANSI.dim}${text}${ANSI.reset}`;
|
|
3775
|
-
}
|
|
3776
|
-
function title(config, text) {
|
|
3777
|
-
if (!config.colors) {
|
|
3778
|
-
return text;
|
|
3779
|
-
}
|
|
3780
|
-
return `${ANSI.bold}${text}${ANSI.reset}`;
|
|
3781
|
-
}
|
|
3782
|
-
|
|
3783
4509
|
// src/structured.ts
|
|
3784
4510
|
class StructuredParseError extends Error {
|
|
3785
4511
|
name = "StructuredParseError";
|
|
3786
|
-
|
|
3787
|
-
|
|
4512
|
+
text;
|
|
4513
|
+
reasoning;
|
|
3788
4514
|
candidates;
|
|
3789
4515
|
zodIssues;
|
|
3790
4516
|
repairLog;
|
|
3791
4517
|
attempt;
|
|
3792
4518
|
constructor(input) {
|
|
3793
4519
|
super(input.message ?? `Structured parsing failed after ${input.attempt} attempt(s).`);
|
|
3794
|
-
this.
|
|
3795
|
-
this.
|
|
4520
|
+
this.text = input.text;
|
|
4521
|
+
this.reasoning = input.reasoning;
|
|
3796
4522
|
this.candidates = input.candidates;
|
|
3797
4523
|
this.zodIssues = input.zodIssues;
|
|
3798
4524
|
this.repairLog = input.repairLog;
|
|
@@ -3823,12 +4549,6 @@ var RE_SIMPLE_IDENTIFIER2 = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
|
|
|
3823
4549
|
var RE_ESCAPE_QUOTE = /"/g;
|
|
3824
4550
|
var RE_WHITESPACE2 = /\s+/g;
|
|
3825
4551
|
var DEFAULT_SELF_HEAL_MAX_DIAGNOSTICS = 8;
|
|
3826
|
-
var structuredOutdent = createOutdent({
|
|
3827
|
-
trimLeadingNewline: true,
|
|
3828
|
-
trimTrailingNewline: true,
|
|
3829
|
-
newline: `
|
|
3830
|
-
`
|
|
3831
|
-
});
|
|
3832
4552
|
var DEFAULT_STRICT_PARSE_OPTIONS = {
|
|
3833
4553
|
repair: false,
|
|
3834
4554
|
maxCandidates: 3,
|
|
@@ -3958,7 +4678,7 @@ async function structured(adapter, schemaOrOptions, promptInput, callOptions) {
|
|
|
3958
4678
|
});
|
|
3959
4679
|
const selfHealSource = resolveSelfHealSource(previous);
|
|
3960
4680
|
const repairPrompt = buildSelfHealPrompt({
|
|
3961
|
-
rawOutput: previous.
|
|
4681
|
+
rawOutput: composeParseSource(previous.text, previous.reasoning),
|
|
3962
4682
|
issues: previous.zodIssues,
|
|
3963
4683
|
schema: normalized.schema,
|
|
3964
4684
|
schemaInstruction: normalized.schemaInstruction,
|
|
@@ -4031,74 +4751,6 @@ function normalizeStructuredInput(schemaOrOptions, promptInput, callOptions) {
|
|
|
4031
4751
|
function isStructuredOptions(value) {
|
|
4032
4752
|
return typeof value === "object" && value !== null && "schema" in value && "prompt" in value;
|
|
4033
4753
|
}
|
|
4034
|
-
function resolvePrompt(prompt, context) {
|
|
4035
|
-
const resolved = typeof prompt === "function" ? prompt(context) : prompt;
|
|
4036
|
-
return normalizePromptValue(resolved, context);
|
|
4037
|
-
}
|
|
4038
|
-
function normalizePromptValue(value, context) {
|
|
4039
|
-
if (typeof value === "string") {
|
|
4040
|
-
return {
|
|
4041
|
-
prompt: value
|
|
4042
|
-
};
|
|
4043
|
-
}
|
|
4044
|
-
if (isPromptResolver(value)) {
|
|
4045
|
-
return normalizePromptPayload(value.resolvePrompt(context));
|
|
4046
|
-
}
|
|
4047
|
-
return normalizePromptPayload(value);
|
|
4048
|
-
}
|
|
4049
|
-
function isPromptResolver(value) {
|
|
4050
|
-
return typeof value === "object" && value !== null && "resolvePrompt" in value && typeof value.resolvePrompt === "function";
|
|
4051
|
-
}
|
|
4052
|
-
function normalizePromptPayload(value) {
|
|
4053
|
-
const prompt = typeof value.prompt === "string" ? value.prompt : undefined;
|
|
4054
|
-
const messages = Array.isArray(value.messages) ? value.messages.filter(isLLMMessage) : undefined;
|
|
4055
|
-
if ((!prompt || prompt.trim().length === 0) && (!messages || messages.length === 0)) {
|
|
4056
|
-
throw new Error("Structured prompt payload must include a non-empty prompt or messages.");
|
|
4057
|
-
}
|
|
4058
|
-
return {
|
|
4059
|
-
prompt,
|
|
4060
|
-
systemPrompt: typeof value.systemPrompt === "string" ? value.systemPrompt : undefined,
|
|
4061
|
-
messages: messages && messages.length > 0 ? messages.map((message) => ({ ...message })) : undefined
|
|
4062
|
-
};
|
|
4063
|
-
}
|
|
4064
|
-
function applyPromptOutdent(payload, enabled) {
|
|
4065
|
-
if (!enabled) {
|
|
4066
|
-
return payload;
|
|
4067
|
-
}
|
|
4068
|
-
return {
|
|
4069
|
-
prompt: typeof payload.prompt === "string" ? structuredOutdent.string(payload.prompt) : undefined,
|
|
4070
|
-
systemPrompt: applyOutdentToOptionalPrompt(payload.systemPrompt, enabled),
|
|
4071
|
-
messages: payload.messages?.map((message) => ({
|
|
4072
|
-
...message,
|
|
4073
|
-
content: typeof message.content === "string" ? structuredOutdent.string(message.content) : message.content
|
|
4074
|
-
}))
|
|
4075
|
-
};
|
|
4076
|
-
}
|
|
4077
|
-
function isLLMMessage(value) {
|
|
4078
|
-
if (typeof value !== "object" || value === null) {
|
|
4079
|
-
return false;
|
|
4080
|
-
}
|
|
4081
|
-
const candidate = value;
|
|
4082
|
-
if (candidate.role !== "system" && candidate.role !== "user" && candidate.role !== "assistant" && candidate.role !== "tool") {
|
|
4083
|
-
return false;
|
|
4084
|
-
}
|
|
4085
|
-
return "content" in candidate;
|
|
4086
|
-
}
|
|
4087
|
-
function applyOutdentToOptionalPrompt(value, enabled) {
|
|
4088
|
-
if (!enabled || typeof value !== "string") {
|
|
4089
|
-
return value;
|
|
4090
|
-
}
|
|
4091
|
-
return structuredOutdent.string(value);
|
|
4092
|
-
}
|
|
4093
|
-
function mergeSystemPrompts(primary, secondary) {
|
|
4094
|
-
const prompts = [primary, secondary].map((value) => value?.trim()).filter((value) => Boolean(value));
|
|
4095
|
-
if (prompts.length === 0) {
|
|
4096
|
-
return;
|
|
4097
|
-
}
|
|
4098
|
-
return prompts.join(`
|
|
4099
|
-
|
|
4100
|
-
`);
|
|
4101
|
-
}
|
|
4102
4754
|
function prepareStructuredPromptPayload(payload, systemPrompt, schema, schemaInstruction) {
|
|
4103
4755
|
if (Array.isArray(payload.messages) && payload.messages.length > 0) {
|
|
4104
4756
|
const messages = payload.messages.map((message) => ({ ...message }));
|
|
@@ -4279,7 +4931,7 @@ function resolveSelfHealSource(attempt) {
|
|
|
4279
4931
|
}
|
|
4280
4932
|
return {
|
|
4281
4933
|
kind: "raw",
|
|
4282
|
-
text: attempt.
|
|
4934
|
+
text: composeParseSource(attempt.text, attempt.reasoning)
|
|
4283
4935
|
};
|
|
4284
4936
|
}
|
|
4285
4937
|
function isSelfHealStalled(previous, current) {
|
|
@@ -4289,60 +4941,22 @@ function isSelfHealStalled(previous, current) {
|
|
|
4289
4941
|
if (current.zodIssues.length < previous.zodIssues.length) {
|
|
4290
4942
|
return false;
|
|
4291
4943
|
}
|
|
4292
|
-
if (current.parsed.errors.length < previous.parsed.errors.length) {
|
|
4293
|
-
return false;
|
|
4294
|
-
}
|
|
4295
|
-
return buildSelfHealFailureFingerprint(previous) === buildSelfHealFailureFingerprint(current);
|
|
4296
|
-
}
|
|
4297
|
-
function buildSelfHealFailureFingerprint(attempt) {
|
|
4298
|
-
const issues = attempt.zodIssues.map((issue) => `${formatIssuePath(issue.path)}:${issue.code}:${normalizeWhitespace(issue.message)}`).sort().join("|");
|
|
4299
|
-
const errors = attempt.parsed.errors.map((error) => `${error.stage}:${error.candidateId ?? "-"}:${normalizeWhitespace(error.message)}`).sort().join("|");
|
|
4300
|
-
const source = normalizeWhitespace(resolveSelfHealSource(attempt).text).slice(0, 512);
|
|
4301
|
-
return [issues, errors, source].join("::");
|
|
4302
|
-
}
|
|
4303
|
-
function normalizeWhitespace(value) {
|
|
4304
|
-
return value.replace(RE_WHITESPACE2, " ").trim();
|
|
4305
|
-
}
|
|
4306
|
-
function normalizeStreamConfig(option) {
|
|
4307
|
-
if (typeof option === "boolean") {
|
|
4308
|
-
return {
|
|
4309
|
-
enabled: option
|
|
4310
|
-
};
|
|
4311
|
-
}
|
|
4312
|
-
if (!option) {
|
|
4313
|
-
return {
|
|
4314
|
-
enabled: false
|
|
4315
|
-
};
|
|
4316
|
-
}
|
|
4317
|
-
return {
|
|
4318
|
-
enabled: option.enabled ?? true,
|
|
4319
|
-
onData: option.onData,
|
|
4320
|
-
to: option.to
|
|
4321
|
-
};
|
|
4322
|
-
}
|
|
4323
|
-
function normalizeDebugConfig(option) {
|
|
4324
|
-
if (typeof option === "boolean") {
|
|
4325
|
-
return {
|
|
4326
|
-
enabled: option,
|
|
4327
|
-
colors: true,
|
|
4328
|
-
logger: (line) => console.log(line)
|
|
4329
|
-
};
|
|
4330
|
-
}
|
|
4331
|
-
if (!option) {
|
|
4332
|
-
return {
|
|
4333
|
-
enabled: false,
|
|
4334
|
-
colors: true,
|
|
4335
|
-
logger: (line) => console.log(line)
|
|
4336
|
-
};
|
|
4944
|
+
if (current.parsed.errors.length < previous.parsed.errors.length) {
|
|
4945
|
+
return false;
|
|
4337
4946
|
}
|
|
4338
|
-
return
|
|
4339
|
-
|
|
4340
|
-
|
|
4341
|
-
|
|
4342
|
-
};
|
|
4947
|
+
return buildSelfHealFailureFingerprint(previous) === buildSelfHealFailureFingerprint(current);
|
|
4948
|
+
}
|
|
4949
|
+
function buildSelfHealFailureFingerprint(attempt) {
|
|
4950
|
+
const issues = attempt.zodIssues.map((issue) => `${formatIssuePath(issue.path)}:${issue.code}:${normalizeWhitespace(issue.message)}`).sort().join("|");
|
|
4951
|
+
const errors = attempt.parsed.errors.map((error) => `${error.stage}:${error.candidateId ?? "-"}:${normalizeWhitespace(error.message)}`).sort().join("|");
|
|
4952
|
+
const source = normalizeWhitespace(resolveSelfHealSource(attempt).text).slice(0, 512);
|
|
4953
|
+
return [issues, errors, source].join("::");
|
|
4954
|
+
}
|
|
4955
|
+
function normalizeWhitespace(value) {
|
|
4956
|
+
return value.replace(RE_WHITESPACE2, " ").trim();
|
|
4343
4957
|
}
|
|
4344
4958
|
async function executeAttempt(adapter, input) {
|
|
4345
|
-
const response = await
|
|
4959
|
+
const response = await callModel2(adapter, {
|
|
4346
4960
|
prompt: input.prompt,
|
|
4347
4961
|
messages: input.messages,
|
|
4348
4962
|
systemPrompt: input.systemPrompt,
|
|
@@ -4355,7 +4969,7 @@ async function executeAttempt(adapter, input) {
|
|
|
4355
4969
|
selfHealEnabled: input.selfHealEnabled,
|
|
4356
4970
|
timeout: input.timeout
|
|
4357
4971
|
});
|
|
4358
|
-
const parsed = parseWithObserve(response.
|
|
4972
|
+
const parsed = parseWithObserve(response.parseSource, input.schema, input.parseOptions, {
|
|
4359
4973
|
observe: input.observe,
|
|
4360
4974
|
attempt: input.attemptNumber,
|
|
4361
4975
|
selfHeal: input.selfHeal
|
|
@@ -4364,8 +4978,8 @@ async function executeAttempt(adapter, input) {
|
|
|
4364
4978
|
attempt: input.attemptNumber,
|
|
4365
4979
|
selfHeal: input.selfHeal,
|
|
4366
4980
|
via: response.via,
|
|
4367
|
-
|
|
4368
|
-
|
|
4981
|
+
text: response.text,
|
|
4982
|
+
reasoning: response.reasoning,
|
|
4369
4983
|
json: parsed.parsed,
|
|
4370
4984
|
candidates: parsed.candidates.map((candidate) => candidate.content),
|
|
4371
4985
|
repairLog: collectRepairLog(parsed),
|
|
@@ -4380,199 +4994,26 @@ async function executeAttempt(adapter, input) {
|
|
|
4380
4994
|
trace
|
|
4381
4995
|
};
|
|
4382
4996
|
}
|
|
4383
|
-
function
|
|
4384
|
-
return {
|
|
4385
|
-
|
|
4386
|
-
|
|
4387
|
-
|
|
4388
|
-
async callTool(params) {
|
|
4389
|
-
let timeoutId;
|
|
4390
|
-
const timeoutPromise = new Promise((_, reject) => {
|
|
4391
|
-
timeoutId = setTimeout(() => reject(new Error(`Tool call timed out after ${toolTimeoutMs}ms`)), toolTimeoutMs);
|
|
4392
|
-
});
|
|
4393
|
-
try {
|
|
4394
|
-
return await Promise.race([client.callTool(params), timeoutPromise]);
|
|
4395
|
-
} finally {
|
|
4396
|
-
clearTimeout(timeoutId);
|
|
4397
|
-
}
|
|
4398
|
-
}
|
|
4399
|
-
};
|
|
4400
|
-
}
|
|
4401
|
-
function applyToolTimeout(clients, toolTimeoutMs) {
|
|
4402
|
-
return clients.map((client) => withToolTimeout(client, toolTimeoutMs));
|
|
4403
|
-
}
|
|
4404
|
-
async function callModel(adapter, options) {
|
|
4405
|
-
const requestSignal = options.request?.signal ?? (options.timeout?.request !== undefined ? AbortSignal.timeout(options.timeout.request) : undefined);
|
|
4406
|
-
const requestPayload = {
|
|
4407
|
-
prompt: options.prompt,
|
|
4408
|
-
messages: options.messages,
|
|
4409
|
-
systemPrompt: options.systemPrompt,
|
|
4410
|
-
temperature: options.request?.temperature,
|
|
4411
|
-
maxTokens: options.request?.maxTokens,
|
|
4412
|
-
mcpClients: options.request?.mcpClients,
|
|
4413
|
-
toolChoice: options.request?.toolChoice,
|
|
4414
|
-
parallelToolCalls: options.request?.parallelToolCalls,
|
|
4415
|
-
maxToolRounds: options.request?.maxToolRounds,
|
|
4416
|
-
onToolExecution: options.request?.onToolExecution,
|
|
4417
|
-
transformToolOutput: options.request?.transformToolOutput,
|
|
4418
|
-
transformToolArguments: options.request?.transformToolArguments,
|
|
4419
|
-
transformToolCallParams: options.request?.transformToolCallParams,
|
|
4420
|
-
unknownToolError: options.request?.unknownToolError,
|
|
4421
|
-
toolDebug: options.request?.toolDebug,
|
|
4422
|
-
body: options.request?.body,
|
|
4423
|
-
signal: requestSignal
|
|
4424
|
-
};
|
|
4425
|
-
emitDebugRequest(options.debug, {
|
|
4426
|
-
provider: adapter.provider,
|
|
4427
|
-
model: adapter.model,
|
|
4428
|
-
attempt: options.attempt,
|
|
4429
|
-
selfHealAttempt: options.selfHeal,
|
|
4430
|
-
selfHealEnabled: options.selfHealEnabled,
|
|
4431
|
-
stream: options.stream.enabled && !!adapter.stream,
|
|
4432
|
-
requestPayload
|
|
4433
|
-
});
|
|
4434
|
-
emitObserve(options.observe, {
|
|
4435
|
-
stage: "llm.request",
|
|
4436
|
-
attempt: options.attempt,
|
|
4437
|
-
selfHeal: options.selfHeal,
|
|
4438
|
-
message: "Sending LLM request.",
|
|
4439
|
-
details: {
|
|
4440
|
-
provider: adapter.provider,
|
|
4441
|
-
model: adapter.model,
|
|
4442
|
-
stream: options.stream.enabled && !!adapter.stream
|
|
4443
|
-
}
|
|
4444
|
-
});
|
|
4445
|
-
if (options.stream.enabled && adapter.stream) {
|
|
4446
|
-
let latestUsage;
|
|
4447
|
-
let latestFinishReason;
|
|
4448
|
-
let streamedRaw = "";
|
|
4449
|
-
let sawToken = false;
|
|
4450
|
-
let lastDataFingerprint;
|
|
4451
|
-
const emitStreamingData = (raw, done, usage2, finishReason2) => {
|
|
4452
|
-
const data = parseStreamingStructuredData(raw);
|
|
4453
|
-
if (data === null && !done) {
|
|
4454
|
-
return;
|
|
4455
|
-
}
|
|
4456
|
-
const fingerprint = toStreamDataFingerprint(data ?? null);
|
|
4457
|
-
if (!done && fingerprint === lastDataFingerprint) {
|
|
4458
|
-
return;
|
|
4459
|
-
}
|
|
4460
|
-
lastDataFingerprint = fingerprint;
|
|
4461
|
-
options.stream.onData?.({
|
|
4462
|
-
data: data ?? null,
|
|
4463
|
-
raw,
|
|
4464
|
-
done,
|
|
4465
|
-
usage: usage2,
|
|
4466
|
-
finishReason: finishReason2
|
|
4467
|
-
});
|
|
4468
|
-
emitObserve(options.observe, {
|
|
4469
|
-
stage: "llm.stream.data",
|
|
4470
|
-
attempt: options.attempt,
|
|
4471
|
-
selfHeal: options.selfHeal,
|
|
4472
|
-
message: done ? "Streaming structured data completed." : "Streaming structured data updated.",
|
|
4473
|
-
details: {
|
|
4474
|
-
done,
|
|
4475
|
-
finishReason: finishReason2
|
|
4476
|
-
}
|
|
4477
|
-
});
|
|
4478
|
-
};
|
|
4479
|
-
const handleTextDelta = (delta) => {
|
|
4480
|
-
if (!delta) {
|
|
4481
|
-
return;
|
|
4482
|
-
}
|
|
4483
|
-
streamedRaw += delta;
|
|
4484
|
-
if (options.stream.to === "stdout") {
|
|
4485
|
-
process.stdout.write(delta);
|
|
4486
|
-
}
|
|
4487
|
-
emitObserve(options.observe, {
|
|
4488
|
-
stage: "llm.stream.delta",
|
|
4489
|
-
attempt: options.attempt,
|
|
4490
|
-
selfHeal: options.selfHeal,
|
|
4491
|
-
message: "Received stream delta.",
|
|
4492
|
-
details: {
|
|
4493
|
-
chars: delta.length
|
|
4494
|
-
}
|
|
4495
|
-
});
|
|
4496
|
-
emitStreamingData(streamedRaw, false);
|
|
4497
|
-
};
|
|
4498
|
-
const response2 = await adapter.stream(requestPayload, {
|
|
4499
|
-
onToken: (token) => {
|
|
4500
|
-
sawToken = true;
|
|
4501
|
-
handleTextDelta(token);
|
|
4502
|
-
},
|
|
4503
|
-
onChunk: (chunk) => {
|
|
4504
|
-
if (!sawToken && chunk.textDelta) {
|
|
4505
|
-
handleTextDelta(chunk.textDelta);
|
|
4506
|
-
}
|
|
4507
|
-
if (chunk.usage) {
|
|
4508
|
-
latestUsage = preferLatestUsage(latestUsage, chunk.usage);
|
|
4509
|
-
}
|
|
4510
|
-
if (chunk.finishReason) {
|
|
4511
|
-
latestFinishReason = chunk.finishReason;
|
|
4512
|
-
}
|
|
4513
|
-
}
|
|
4514
|
-
});
|
|
4515
|
-
const finalText = typeof response2.text === "string" && response2.text.length > 0 ? response2.text : streamedRaw;
|
|
4516
|
-
const usage = preferLatestUsage(latestUsage, response2.usage);
|
|
4517
|
-
const finishReason = response2.finishReason ?? latestFinishReason;
|
|
4518
|
-
emitStreamingData(finalText, true, usage, finishReason);
|
|
4519
|
-
emitObserve(options.observe, {
|
|
4520
|
-
stage: "llm.response",
|
|
4997
|
+
async function callModel2(adapter, options) {
|
|
4998
|
+
return callModel(adapter, {
|
|
4999
|
+
...options,
|
|
5000
|
+
buildEvent: ({ stage, message, details }) => ({
|
|
5001
|
+
stage,
|
|
4521
5002
|
attempt: options.attempt,
|
|
4522
5003
|
selfHeal: options.selfHeal,
|
|
4523
|
-
message
|
|
4524
|
-
details
|
|
4525
|
-
|
|
4526
|
-
|
|
4527
|
-
|
|
4528
|
-
|
|
4529
|
-
|
|
4530
|
-
|
|
4531
|
-
|
|
4532
|
-
selfHealAttempt: options.selfHeal,
|
|
4533
|
-
selfHealEnabled: options.selfHealEnabled,
|
|
4534
|
-
via: "stream",
|
|
4535
|
-
responseText: finalText,
|
|
4536
|
-
usage,
|
|
4537
|
-
finishReason
|
|
4538
|
-
});
|
|
4539
|
-
return {
|
|
4540
|
-
text: finalText,
|
|
4541
|
-
via: "stream",
|
|
4542
|
-
usage,
|
|
4543
|
-
finishReason
|
|
4544
|
-
};
|
|
4545
|
-
}
|
|
4546
|
-
const response = await adapter.complete(requestPayload);
|
|
4547
|
-
emitObserve(options.observe, {
|
|
4548
|
-
stage: "llm.response",
|
|
4549
|
-
attempt: options.attempt,
|
|
4550
|
-
selfHeal: options.selfHeal,
|
|
4551
|
-
message: "Completion response received.",
|
|
4552
|
-
details: {
|
|
4553
|
-
via: "complete",
|
|
4554
|
-
chars: response.text.length,
|
|
4555
|
-
finishReason: response.finishReason
|
|
4556
|
-
}
|
|
4557
|
-
});
|
|
4558
|
-
emitDebugResponse(options.debug, {
|
|
4559
|
-
attempt: options.attempt,
|
|
4560
|
-
selfHealAttempt: options.selfHeal,
|
|
4561
|
-
selfHealEnabled: options.selfHealEnabled,
|
|
4562
|
-
via: "complete",
|
|
4563
|
-
responseText: response.text,
|
|
4564
|
-
usage: response.usage,
|
|
4565
|
-
finishReason: response.finishReason
|
|
5004
|
+
message,
|
|
5005
|
+
details
|
|
5006
|
+
}),
|
|
5007
|
+
buildSnapshot: (normalized) => ({
|
|
5008
|
+
text: normalized.text,
|
|
5009
|
+
reasoning: normalized.reasoning,
|
|
5010
|
+
data: parseStreamingStructuredData(normalized.parseSource) ?? null
|
|
5011
|
+
}),
|
|
5012
|
+
debugLabel: "structured"
|
|
4566
5013
|
});
|
|
4567
|
-
return {
|
|
4568
|
-
text: response.text,
|
|
4569
|
-
via: "complete",
|
|
4570
|
-
usage: response.usage,
|
|
4571
|
-
finishReason: response.finishReason
|
|
4572
|
-
};
|
|
4573
5014
|
}
|
|
4574
|
-
function parseStreamingStructuredData(
|
|
4575
|
-
const sanitized = sanitizeThink(
|
|
5015
|
+
function parseStreamingStructuredData(parseSource) {
|
|
5016
|
+
const sanitized = sanitizeThink(parseSource);
|
|
4576
5017
|
const start = findFirstJsonRootStart(sanitized.visibleText);
|
|
4577
5018
|
if (start < 0) {
|
|
4578
5019
|
return null;
|
|
@@ -4632,13 +5073,6 @@ function findFirstJsonRootStart(input) {
|
|
|
4632
5073
|
}
|
|
4633
5074
|
return Math.min(objectStart, arrayStart);
|
|
4634
5075
|
}
|
|
4635
|
-
function toStreamDataFingerprint(value) {
|
|
4636
|
-
try {
|
|
4637
|
-
return JSON.stringify(value);
|
|
4638
|
-
} catch {
|
|
4639
|
-
return "__unserializable__";
|
|
4640
|
-
}
|
|
4641
|
-
}
|
|
4642
5076
|
function parseWithObserve(output, schema, parseOptions, context) {
|
|
4643
5077
|
const userParseTrace = parseOptions.onTrace;
|
|
4644
5078
|
return parseLLMOutput(output, schema, {
|
|
@@ -4668,38 +5102,20 @@ function buildSuccessResult(data, attempts) {
|
|
|
4668
5102
|
const final = attempts.at(-1);
|
|
4669
5103
|
return {
|
|
4670
5104
|
data,
|
|
4671
|
-
|
|
4672
|
-
|
|
5105
|
+
text: final?.text ?? "",
|
|
5106
|
+
reasoning: final?.reasoning ?? "",
|
|
4673
5107
|
json: final?.json ?? null,
|
|
4674
5108
|
attempts,
|
|
4675
5109
|
usage: aggregateUsage(attempts),
|
|
4676
5110
|
finishReason: final?.finishReason
|
|
4677
5111
|
};
|
|
4678
5112
|
}
|
|
4679
|
-
function aggregateUsage(attempts) {
|
|
4680
|
-
let usage;
|
|
4681
|
-
for (const attempt of attempts) {
|
|
4682
|
-
usage = mergeUsage2(usage, attempt.usage);
|
|
4683
|
-
}
|
|
4684
|
-
return usage;
|
|
4685
|
-
}
|
|
4686
|
-
function mergeUsage2(base, next) {
|
|
4687
|
-
if (!base && !next) {
|
|
4688
|
-
return;
|
|
4689
|
-
}
|
|
4690
|
-
return {
|
|
4691
|
-
inputTokens: (base?.inputTokens ?? 0) + (next?.inputTokens ?? 0),
|
|
4692
|
-
outputTokens: (base?.outputTokens ?? 0) + (next?.outputTokens ?? 0),
|
|
4693
|
-
totalTokens: (base?.totalTokens ?? 0) + (next?.totalTokens ?? 0),
|
|
4694
|
-
cost: (base?.cost ?? 0) + (next?.cost ?? 0)
|
|
4695
|
-
};
|
|
4696
|
-
}
|
|
4697
5113
|
function toStructuredError(attempt) {
|
|
4698
5114
|
if (!attempt) {
|
|
4699
5115
|
return new StructuredParseError({
|
|
4700
5116
|
message: "Structured parsing failed before any model response.",
|
|
4701
|
-
|
|
4702
|
-
|
|
5117
|
+
text: "",
|
|
5118
|
+
reasoning: "",
|
|
4703
5119
|
candidates: [],
|
|
4704
5120
|
zodIssues: [],
|
|
4705
5121
|
repairLog: [],
|
|
@@ -4707,8 +5123,8 @@ function toStructuredError(attempt) {
|
|
|
4707
5123
|
});
|
|
4708
5124
|
}
|
|
4709
5125
|
return new StructuredParseError({
|
|
4710
|
-
|
|
4711
|
-
|
|
5126
|
+
text: attempt.text,
|
|
5127
|
+
reasoning: attempt.reasoning,
|
|
4712
5128
|
candidates: attempt.candidates,
|
|
4713
5129
|
zodIssues: attempt.zodIssues,
|
|
4714
5130
|
repairLog: attempt.repairLog,
|
|
@@ -4718,59 +5134,6 @@ function toStructuredError(attempt) {
|
|
|
4718
5134
|
function emitObserve(observe, event) {
|
|
4719
5135
|
observe?.(event);
|
|
4720
5136
|
}
|
|
4721
|
-
function emitDebugRequest(config, input) {
|
|
4722
|
-
const requestBody = input.requestPayload.body !== undefined ? JSON.stringify(input.requestPayload.body, null, 2) : "(none)";
|
|
4723
|
-
const requestMessages = input.requestPayload.messages !== undefined ? JSON.stringify(input.requestPayload.messages, null, 2) : "(none)";
|
|
4724
|
-
const lines = [
|
|
4725
|
-
color(config, title(config, [
|
|
4726
|
-
"[structured][request]",
|
|
4727
|
-
`attempt=${input.attempt}`,
|
|
4728
|
-
`selfHealEnabled=${input.selfHealEnabled}`,
|
|
4729
|
-
`selfHealAttempt=${input.selfHealAttempt}`
|
|
4730
|
-
].join(" ")), "cyan"),
|
|
4731
|
-
dim(config, [
|
|
4732
|
-
`provider=${input.provider ?? "unknown"}`,
|
|
4733
|
-
`model=${input.model ?? "unknown"}`,
|
|
4734
|
-
`stream=${input.stream}`
|
|
4735
|
-
].join(" ")),
|
|
4736
|
-
color(config, "prompt:", "yellow"),
|
|
4737
|
-
input.requestPayload.prompt ?? "(none)",
|
|
4738
|
-
color(config, "messages:", "yellow"),
|
|
4739
|
-
requestMessages,
|
|
4740
|
-
color(config, "systemPrompt:", "yellow"),
|
|
4741
|
-
input.requestPayload.systemPrompt ?? "(none)",
|
|
4742
|
-
color(config, "request.body:", "yellow"),
|
|
4743
|
-
requestBody
|
|
4744
|
-
];
|
|
4745
|
-
emitDebug(config, lines.join(`
|
|
4746
|
-
`));
|
|
4747
|
-
}
|
|
4748
|
-
function emitDebugResponse(config, input) {
|
|
4749
|
-
const lines = [
|
|
4750
|
-
color(config, title(config, [
|
|
4751
|
-
"[structured][response]",
|
|
4752
|
-
`attempt=${input.attempt}`,
|
|
4753
|
-
`selfHealEnabled=${input.selfHealEnabled}`,
|
|
4754
|
-
`selfHealAttempt=${input.selfHealAttempt}`
|
|
4755
|
-
].join(" ")), "green"),
|
|
4756
|
-
dim(config, [
|
|
4757
|
-
`via=${input.via}`,
|
|
4758
|
-
`chars=${input.responseText.length}`,
|
|
4759
|
-
`finishReason=${input.finishReason ?? "unknown"}`,
|
|
4760
|
-
`usage=${JSON.stringify(input.usage ?? {})}`
|
|
4761
|
-
].join(" ")),
|
|
4762
|
-
color(config, "text:", "yellow"),
|
|
4763
|
-
input.responseText
|
|
4764
|
-
];
|
|
4765
|
-
emitDebug(config, lines.join(`
|
|
4766
|
-
`));
|
|
4767
|
-
}
|
|
4768
|
-
function emitDebug(config, message) {
|
|
4769
|
-
if (!config.enabled) {
|
|
4770
|
-
return;
|
|
4771
|
-
}
|
|
4772
|
-
config.logger(message);
|
|
4773
|
-
}
|
|
4774
5137
|
|
|
4775
5138
|
// src/llm.ts
|
|
4776
5139
|
function createLLM(config, registry = createDefaultProviderRegistry()) {
|
|
@@ -4784,6 +5147,17 @@ function createLLM(config, registry = createDefaultProviderRegistry()) {
|
|
|
4784
5147
|
const merged = mergeStructuredOptions(defaults, options);
|
|
4785
5148
|
return structured(adapter, schema, prompt, merged);
|
|
4786
5149
|
},
|
|
5150
|
+
async generate(promptOrOptions, options) {
|
|
5151
|
+
if (isGenerateOptions2(promptOrOptions)) {
|
|
5152
|
+
const merged2 = {
|
|
5153
|
+
...mergeGenerateOptions(defaults, promptOrOptions),
|
|
5154
|
+
prompt: promptOrOptions.prompt
|
|
5155
|
+
};
|
|
5156
|
+
return generate(adapter, merged2);
|
|
5157
|
+
}
|
|
5158
|
+
const merged = mergeGenerateOptions(defaults, options);
|
|
5159
|
+
return generate(adapter, promptOrOptions, merged);
|
|
5160
|
+
},
|
|
4787
5161
|
async embed(input, options = {}) {
|
|
4788
5162
|
if (!adapter.embed) {
|
|
4789
5163
|
throw new Error(`Provider "${adapter.provider ?? "unknown"}" does not support embeddings.`);
|
|
@@ -4813,6 +5187,26 @@ function mergeStructuredOptions(defaults, overrides) {
|
|
|
4813
5187
|
timeout: mergeObjectLike(defaults?.timeout, overrides?.timeout)
|
|
4814
5188
|
};
|
|
4815
5189
|
}
|
|
5190
|
+
function mergeGenerateOptions(defaults, overrides) {
|
|
5191
|
+
if (!defaults && !overrides) {
|
|
5192
|
+
return {};
|
|
5193
|
+
}
|
|
5194
|
+
return {
|
|
5195
|
+
outdent: overrides?.outdent ?? defaults?.outdent,
|
|
5196
|
+
systemPrompt: overrides?.systemPrompt ?? defaults?.systemPrompt,
|
|
5197
|
+
request: {
|
|
5198
|
+
...defaults?.request ?? {},
|
|
5199
|
+
...overrides?.request ?? {}
|
|
5200
|
+
},
|
|
5201
|
+
stream: mergeObjectLike(defaults?.stream, overrides?.stream),
|
|
5202
|
+
debug: mergeObjectLike(defaults?.debug, overrides?.debug),
|
|
5203
|
+
timeout: mergeObjectLike(defaults?.timeout, overrides?.timeout),
|
|
5204
|
+
observe: overrides?.observe ?? defaults?.observe
|
|
5205
|
+
};
|
|
5206
|
+
}
|
|
5207
|
+
function isGenerateOptions2(value) {
|
|
5208
|
+
return typeof value === "object" && value !== null && "prompt" in value;
|
|
5209
|
+
}
|
|
4816
5210
|
function mergeObjectLike(defaults, overrides) {
|
|
4817
5211
|
if (overrides === undefined) {
|
|
4818
5212
|
return defaults;
|
|
@@ -5280,6 +5674,7 @@ export {
|
|
|
5280
5674
|
inspectSchemaMetadata,
|
|
5281
5675
|
inferSchemaExample,
|
|
5282
5676
|
images,
|
|
5677
|
+
generate,
|
|
5283
5678
|
formatZodIssues,
|
|
5284
5679
|
formatPrompt,
|
|
5285
5680
|
extractMarkdownCodeBlocks,
|