@ljoukov/llm 0.1.3 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +95 -31
- package/dist/index.cjs +475 -30
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +53 -6
- package/dist/index.d.ts +53 -6
- package/dist/index.js +474 -30
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -51,6 +51,7 @@ __export(index_exports, {
|
|
|
51
51
|
refreshChatGptOauthToken: () => refreshChatGptOauthToken,
|
|
52
52
|
runToolLoop: () => runToolLoop,
|
|
53
53
|
sanitisePartForLogging: () => sanitisePartForLogging,
|
|
54
|
+
streamJson: () => streamJson,
|
|
54
55
|
streamText: () => streamText,
|
|
55
56
|
stripCodexCitationMarkers: () => stripCodexCitationMarkers,
|
|
56
57
|
toGeminiJsonSchema: () => toGeminiJsonSchema,
|
|
@@ -1575,10 +1576,13 @@ function convertGooglePartsToLlmParts(parts) {
|
|
|
1575
1576
|
function assertLlmRole(value) {
|
|
1576
1577
|
switch (value) {
|
|
1577
1578
|
case "user":
|
|
1578
|
-
case "
|
|
1579
|
+
case "assistant":
|
|
1579
1580
|
case "system":
|
|
1581
|
+
case "developer":
|
|
1580
1582
|
case "tool":
|
|
1581
1583
|
return value;
|
|
1584
|
+
case "model":
|
|
1585
|
+
return "assistant";
|
|
1582
1586
|
default:
|
|
1583
1587
|
throw new Error(`Unsupported LLM role: ${String(value)}`);
|
|
1584
1588
|
}
|
|
@@ -1608,8 +1612,9 @@ function toGeminiPart(part) {
|
|
|
1608
1612
|
}
|
|
1609
1613
|
}
|
|
1610
1614
|
function convertLlmContentToGeminiContent(content) {
|
|
1615
|
+
const role = content.role === "assistant" ? "model" : "user";
|
|
1611
1616
|
return {
|
|
1612
|
-
role
|
|
1617
|
+
role,
|
|
1613
1618
|
parts: content.parts.map(toGeminiPart)
|
|
1614
1619
|
};
|
|
1615
1620
|
}
|
|
@@ -1780,28 +1785,350 @@ function parseJsonFromLlmText(rawText) {
|
|
|
1780
1785
|
const repairedText = escapeNewlinesInStrings(cleanedText);
|
|
1781
1786
|
return JSON.parse(repairedText);
|
|
1782
1787
|
}
|
|
1783
|
-
function
|
|
1784
|
-
|
|
1785
|
-
|
|
1788
|
+
function parsePartialJsonFromLlmText(rawText) {
|
|
1789
|
+
const jsonStart = extractJsonStartText(rawText);
|
|
1790
|
+
if (!jsonStart) {
|
|
1791
|
+
return null;
|
|
1792
|
+
}
|
|
1793
|
+
try {
|
|
1794
|
+
return parsePartialJson(jsonStart);
|
|
1795
|
+
} catch {
|
|
1796
|
+
return null;
|
|
1797
|
+
}
|
|
1798
|
+
}
|
|
1799
|
+
function extractJsonStartText(rawText) {
|
|
1800
|
+
let text = rawText.trimStart();
|
|
1801
|
+
if (text.startsWith("```")) {
|
|
1802
|
+
text = text.replace(/^```[a-zA-Z0-9_-]*\s*\n?/, "");
|
|
1803
|
+
}
|
|
1804
|
+
const objIndex = text.indexOf("{");
|
|
1805
|
+
const arrIndex = text.indexOf("[");
|
|
1806
|
+
let start = -1;
|
|
1807
|
+
if (objIndex !== -1 && arrIndex !== -1) {
|
|
1808
|
+
start = Math.min(objIndex, arrIndex);
|
|
1809
|
+
} else {
|
|
1810
|
+
start = objIndex !== -1 ? objIndex : arrIndex;
|
|
1811
|
+
}
|
|
1812
|
+
if (start === -1) {
|
|
1813
|
+
return null;
|
|
1814
|
+
}
|
|
1815
|
+
return text.slice(start);
|
|
1816
|
+
}
|
|
1817
|
+
function parsePartialJson(text) {
|
|
1818
|
+
let i = 0;
|
|
1819
|
+
const len = text.length;
|
|
1820
|
+
const isWhitespace = (char) => char === " " || char === "\n" || char === "\r" || char === " ";
|
|
1821
|
+
const skipWhitespace = () => {
|
|
1822
|
+
while (i < len && isWhitespace(text[i] ?? "")) {
|
|
1823
|
+
i += 1;
|
|
1824
|
+
}
|
|
1825
|
+
};
|
|
1826
|
+
const parseString = () => {
|
|
1827
|
+
if (text[i] !== '"') {
|
|
1828
|
+
return null;
|
|
1829
|
+
}
|
|
1830
|
+
i += 1;
|
|
1831
|
+
let value = "";
|
|
1832
|
+
while (i < len) {
|
|
1833
|
+
const ch = text[i] ?? "";
|
|
1834
|
+
if (ch === '"') {
|
|
1835
|
+
i += 1;
|
|
1836
|
+
return { value, complete: true };
|
|
1837
|
+
}
|
|
1838
|
+
if (ch === "\\") {
|
|
1839
|
+
if (i + 1 >= len) {
|
|
1840
|
+
return { value, complete: false };
|
|
1841
|
+
}
|
|
1842
|
+
const esc = text[i + 1] ?? "";
|
|
1843
|
+
switch (esc) {
|
|
1844
|
+
case '"':
|
|
1845
|
+
case "\\":
|
|
1846
|
+
case "/":
|
|
1847
|
+
value += esc;
|
|
1848
|
+
i += 2;
|
|
1849
|
+
continue;
|
|
1850
|
+
case "b":
|
|
1851
|
+
value += "\b";
|
|
1852
|
+
i += 2;
|
|
1853
|
+
continue;
|
|
1854
|
+
case "f":
|
|
1855
|
+
value += "\f";
|
|
1856
|
+
i += 2;
|
|
1857
|
+
continue;
|
|
1858
|
+
case "n":
|
|
1859
|
+
value += "\n";
|
|
1860
|
+
i += 2;
|
|
1861
|
+
continue;
|
|
1862
|
+
case "r":
|
|
1863
|
+
value += "\r";
|
|
1864
|
+
i += 2;
|
|
1865
|
+
continue;
|
|
1866
|
+
case "t":
|
|
1867
|
+
value += " ";
|
|
1868
|
+
i += 2;
|
|
1869
|
+
continue;
|
|
1870
|
+
case "u": {
|
|
1871
|
+
if (i + 5 >= len) {
|
|
1872
|
+
return { value, complete: false };
|
|
1873
|
+
}
|
|
1874
|
+
const hex = text.slice(i + 2, i + 6);
|
|
1875
|
+
if (!/^[0-9a-fA-F]{4}$/u.test(hex)) {
|
|
1876
|
+
value += "u";
|
|
1877
|
+
i += 2;
|
|
1878
|
+
continue;
|
|
1879
|
+
}
|
|
1880
|
+
value += String.fromCharCode(Number.parseInt(hex, 16));
|
|
1881
|
+
i += 6;
|
|
1882
|
+
continue;
|
|
1883
|
+
}
|
|
1884
|
+
default:
|
|
1885
|
+
value += esc;
|
|
1886
|
+
i += 2;
|
|
1887
|
+
continue;
|
|
1888
|
+
}
|
|
1889
|
+
}
|
|
1890
|
+
value += ch;
|
|
1891
|
+
i += 1;
|
|
1892
|
+
}
|
|
1893
|
+
return { value, complete: false };
|
|
1894
|
+
};
|
|
1895
|
+
const parseNumber = () => {
|
|
1896
|
+
const start = i;
|
|
1897
|
+
while (i < len) {
|
|
1898
|
+
const ch = text[i] ?? "";
|
|
1899
|
+
if (isWhitespace(ch) || ch === "," || ch === "}" || ch === "]") {
|
|
1900
|
+
break;
|
|
1901
|
+
}
|
|
1902
|
+
i += 1;
|
|
1903
|
+
}
|
|
1904
|
+
const raw = text.slice(start, i);
|
|
1905
|
+
if (!/^-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?$/u.test(raw)) {
|
|
1906
|
+
i = start;
|
|
1907
|
+
return null;
|
|
1908
|
+
}
|
|
1909
|
+
return { value: Number(raw), complete: true };
|
|
1910
|
+
};
|
|
1911
|
+
const parseLiteral = () => {
|
|
1912
|
+
if (text.startsWith("true", i)) {
|
|
1913
|
+
i += 4;
|
|
1914
|
+
return { value: true, complete: true };
|
|
1915
|
+
}
|
|
1916
|
+
if (text.startsWith("false", i)) {
|
|
1917
|
+
i += 5;
|
|
1918
|
+
return { value: false, complete: true };
|
|
1919
|
+
}
|
|
1920
|
+
if (text.startsWith("null", i)) {
|
|
1921
|
+
i += 4;
|
|
1922
|
+
return { value: null, complete: true };
|
|
1923
|
+
}
|
|
1924
|
+
return null;
|
|
1925
|
+
};
|
|
1926
|
+
skipWhitespace();
|
|
1927
|
+
const first = text[i];
|
|
1928
|
+
if (first !== "{" && first !== "[") {
|
|
1929
|
+
return null;
|
|
1930
|
+
}
|
|
1931
|
+
const root = first === "{" ? {} : [];
|
|
1932
|
+
const stack = first === "{" ? [{ type: "object", value: root, state: "keyOrEnd" }] : [{ type: "array", value: root, state: "valueOrEnd" }];
|
|
1933
|
+
i += 1;
|
|
1934
|
+
while (stack.length > 0) {
|
|
1935
|
+
skipWhitespace();
|
|
1936
|
+
if (i >= len) {
|
|
1937
|
+
break;
|
|
1938
|
+
}
|
|
1939
|
+
const ctx = stack[stack.length - 1];
|
|
1940
|
+
if (!ctx) {
|
|
1941
|
+
break;
|
|
1942
|
+
}
|
|
1943
|
+
const ch = text[i] ?? "";
|
|
1944
|
+
if (ctx.type === "object") {
|
|
1945
|
+
if (ctx.state === "keyOrEnd") {
|
|
1946
|
+
if (ch === "}") {
|
|
1947
|
+
i += 1;
|
|
1948
|
+
stack.pop();
|
|
1949
|
+
continue;
|
|
1950
|
+
}
|
|
1951
|
+
if (ch === ",") {
|
|
1952
|
+
i += 1;
|
|
1953
|
+
continue;
|
|
1954
|
+
}
|
|
1955
|
+
if (ch !== '"') {
|
|
1956
|
+
break;
|
|
1957
|
+
}
|
|
1958
|
+
const key = parseString();
|
|
1959
|
+
if (!key) {
|
|
1960
|
+
break;
|
|
1961
|
+
}
|
|
1962
|
+
if (!key.complete) {
|
|
1963
|
+
break;
|
|
1964
|
+
}
|
|
1965
|
+
ctx.key = key.value;
|
|
1966
|
+
ctx.state = "colon";
|
|
1967
|
+
continue;
|
|
1968
|
+
}
|
|
1969
|
+
if (ctx.state === "colon") {
|
|
1970
|
+
if (ch === ":") {
|
|
1971
|
+
i += 1;
|
|
1972
|
+
ctx.state = "value";
|
|
1973
|
+
continue;
|
|
1974
|
+
}
|
|
1975
|
+
break;
|
|
1976
|
+
}
|
|
1977
|
+
if (ctx.state === "value") {
|
|
1978
|
+
if (ch === "}") {
|
|
1979
|
+
i += 1;
|
|
1980
|
+
ctx.key = void 0;
|
|
1981
|
+
stack.pop();
|
|
1982
|
+
continue;
|
|
1983
|
+
}
|
|
1984
|
+
if (ch === ",") {
|
|
1985
|
+
i += 1;
|
|
1986
|
+
ctx.key = void 0;
|
|
1987
|
+
ctx.state = "keyOrEnd";
|
|
1988
|
+
continue;
|
|
1989
|
+
}
|
|
1990
|
+
const key = ctx.key;
|
|
1991
|
+
if (!key) {
|
|
1992
|
+
break;
|
|
1993
|
+
}
|
|
1994
|
+
if (ch === "{" || ch === "[") {
|
|
1995
|
+
const container = ch === "{" ? {} : [];
|
|
1996
|
+
ctx.value[key] = container;
|
|
1997
|
+
ctx.key = void 0;
|
|
1998
|
+
ctx.state = "commaOrEnd";
|
|
1999
|
+
stack.push(
|
|
2000
|
+
ch === "{" ? { type: "object", value: container, state: "keyOrEnd" } : { type: "array", value: container, state: "valueOrEnd" }
|
|
2001
|
+
);
|
|
2002
|
+
i += 1;
|
|
2003
|
+
continue;
|
|
2004
|
+
}
|
|
2005
|
+
let primitive = null;
|
|
2006
|
+
if (ch === '"') {
|
|
2007
|
+
primitive = parseString();
|
|
2008
|
+
} else if (ch === "-" || ch >= "0" && ch <= "9") {
|
|
2009
|
+
primitive = parseNumber();
|
|
2010
|
+
} else {
|
|
2011
|
+
primitive = parseLiteral();
|
|
2012
|
+
}
|
|
2013
|
+
if (!primitive) {
|
|
2014
|
+
break;
|
|
2015
|
+
}
|
|
2016
|
+
ctx.value[key] = primitive.value;
|
|
2017
|
+
ctx.key = void 0;
|
|
2018
|
+
ctx.state = "commaOrEnd";
|
|
2019
|
+
if (!primitive.complete) {
|
|
2020
|
+
break;
|
|
2021
|
+
}
|
|
2022
|
+
continue;
|
|
2023
|
+
}
|
|
2024
|
+
if (ctx.state === "commaOrEnd") {
|
|
2025
|
+
if (ch === ",") {
|
|
2026
|
+
i += 1;
|
|
2027
|
+
ctx.state = "keyOrEnd";
|
|
2028
|
+
continue;
|
|
2029
|
+
}
|
|
2030
|
+
if (ch === "}") {
|
|
2031
|
+
i += 1;
|
|
2032
|
+
stack.pop();
|
|
2033
|
+
continue;
|
|
2034
|
+
}
|
|
2035
|
+
break;
|
|
2036
|
+
}
|
|
2037
|
+
} else {
|
|
2038
|
+
if (ctx.state === "valueOrEnd") {
|
|
2039
|
+
if (ch === "]") {
|
|
2040
|
+
i += 1;
|
|
2041
|
+
stack.pop();
|
|
2042
|
+
continue;
|
|
2043
|
+
}
|
|
2044
|
+
if (ch === ",") {
|
|
2045
|
+
i += 1;
|
|
2046
|
+
continue;
|
|
2047
|
+
}
|
|
2048
|
+
if (ch === "{" || ch === "[") {
|
|
2049
|
+
const container = ch === "{" ? {} : [];
|
|
2050
|
+
ctx.value.push(container);
|
|
2051
|
+
ctx.state = "commaOrEnd";
|
|
2052
|
+
stack.push(
|
|
2053
|
+
ch === "{" ? { type: "object", value: container, state: "keyOrEnd" } : { type: "array", value: container, state: "valueOrEnd" }
|
|
2054
|
+
);
|
|
2055
|
+
i += 1;
|
|
2056
|
+
continue;
|
|
2057
|
+
}
|
|
2058
|
+
let primitive = null;
|
|
2059
|
+
if (ch === '"') {
|
|
2060
|
+
primitive = parseString();
|
|
2061
|
+
} else if (ch === "-" || ch >= "0" && ch <= "9") {
|
|
2062
|
+
primitive = parseNumber();
|
|
2063
|
+
} else {
|
|
2064
|
+
primitive = parseLiteral();
|
|
2065
|
+
}
|
|
2066
|
+
if (!primitive) {
|
|
2067
|
+
break;
|
|
2068
|
+
}
|
|
2069
|
+
ctx.value.push(primitive.value);
|
|
2070
|
+
ctx.state = "commaOrEnd";
|
|
2071
|
+
if (!primitive.complete) {
|
|
2072
|
+
break;
|
|
2073
|
+
}
|
|
2074
|
+
continue;
|
|
2075
|
+
}
|
|
2076
|
+
if (ctx.state === "commaOrEnd") {
|
|
2077
|
+
if (ch === ",") {
|
|
2078
|
+
i += 1;
|
|
2079
|
+
ctx.state = "valueOrEnd";
|
|
2080
|
+
continue;
|
|
2081
|
+
}
|
|
2082
|
+
if (ch === "]") {
|
|
2083
|
+
i += 1;
|
|
2084
|
+
stack.pop();
|
|
2085
|
+
continue;
|
|
2086
|
+
}
|
|
2087
|
+
break;
|
|
2088
|
+
}
|
|
2089
|
+
}
|
|
1786
2090
|
}
|
|
2091
|
+
return root;
|
|
2092
|
+
}
|
|
2093
|
+
function resolveTextContents(input) {
|
|
1787
2094
|
const contents = [];
|
|
1788
|
-
if (input.
|
|
2095
|
+
if (input.instructions) {
|
|
2096
|
+
const instructions = input.instructions.trim();
|
|
2097
|
+
if (instructions.length > 0) {
|
|
2098
|
+
contents.push({
|
|
2099
|
+
role: "system",
|
|
2100
|
+
parts: [{ type: "text", text: instructions }]
|
|
2101
|
+
});
|
|
2102
|
+
}
|
|
2103
|
+
}
|
|
2104
|
+
if (typeof input.input === "string") {
|
|
1789
2105
|
contents.push({
|
|
1790
|
-
role: "
|
|
1791
|
-
parts: [{ type: "text", text: input.
|
|
2106
|
+
role: "user",
|
|
2107
|
+
parts: [{ type: "text", text: input.input }]
|
|
2108
|
+
});
|
|
2109
|
+
return contents;
|
|
2110
|
+
}
|
|
2111
|
+
for (const message of input.input) {
|
|
2112
|
+
const parts = typeof message.content === "string" ? [{ type: "text", text: message.content }] : message.content;
|
|
2113
|
+
contents.push({
|
|
2114
|
+
role: message.role,
|
|
2115
|
+
parts: parts.map(
|
|
2116
|
+
(part) => part.type === "text" ? {
|
|
2117
|
+
type: "text",
|
|
2118
|
+
text: part.text,
|
|
2119
|
+
thought: "thought" in part && part.thought === true ? true : void 0
|
|
2120
|
+
} : { type: "inlineData", data: part.data, mimeType: part.mimeType }
|
|
2121
|
+
)
|
|
1792
2122
|
});
|
|
1793
2123
|
}
|
|
1794
|
-
contents.push({
|
|
1795
|
-
role: "user",
|
|
1796
|
-
parts: [{ type: "text", text: input.prompt }]
|
|
1797
|
-
});
|
|
1798
2124
|
return contents;
|
|
1799
2125
|
}
|
|
1800
2126
|
function toOpenAiInput(contents) {
|
|
1801
2127
|
const OPENAI_ROLE_FROM_LLM = {
|
|
1802
2128
|
user: "user",
|
|
1803
|
-
|
|
2129
|
+
assistant: "assistant",
|
|
1804
2130
|
system: "system",
|
|
2131
|
+
developer: "developer",
|
|
1805
2132
|
tool: "assistant"
|
|
1806
2133
|
};
|
|
1807
2134
|
return contents.map((content) => {
|
|
@@ -1840,7 +2167,7 @@ function toChatGptInput(contents) {
|
|
|
1840
2167
|
const instructionsParts = [];
|
|
1841
2168
|
const input = [];
|
|
1842
2169
|
for (const content of contents) {
|
|
1843
|
-
if (content.role === "system") {
|
|
2170
|
+
if (content.role === "system" || content.role === "developer") {
|
|
1844
2171
|
for (const part of content.parts) {
|
|
1845
2172
|
if (part.type === "text") {
|
|
1846
2173
|
instructionsParts.push(part.text);
|
|
@@ -1848,7 +2175,7 @@ function toChatGptInput(contents) {
|
|
|
1848
2175
|
}
|
|
1849
2176
|
continue;
|
|
1850
2177
|
}
|
|
1851
|
-
const isAssistant = content.role === "
|
|
2178
|
+
const isAssistant = content.role === "assistant" || content.role === "tool";
|
|
1852
2179
|
const parts = [];
|
|
1853
2180
|
for (const part of content.parts) {
|
|
1854
2181
|
if (part.type === "text") {
|
|
@@ -2568,7 +2895,7 @@ async function runTextCall(params) {
|
|
|
2568
2895
|
});
|
|
2569
2896
|
}
|
|
2570
2897
|
const mergedParts = mergeConsecutiveTextParts(responseParts);
|
|
2571
|
-
const content = mergedParts.length > 0 ? { role: responseRole ?? "
|
|
2898
|
+
const content = mergedParts.length > 0 ? { role: responseRole ?? "assistant", parts: mergedParts } : void 0;
|
|
2572
2899
|
const { text, thoughts } = extractTextByChannel(content);
|
|
2573
2900
|
const costUsd = estimateCallCostUsd({
|
|
2574
2901
|
modelId: modelVersion,
|
|
@@ -2618,8 +2945,7 @@ async function generateText(request) {
|
|
|
2618
2945
|
}
|
|
2619
2946
|
return await call.result;
|
|
2620
2947
|
}
|
|
2621
|
-
|
|
2622
|
-
const maxAttempts = Math.max(1, Math.floor(request.maxAttempts ?? 2));
|
|
2948
|
+
function buildJsonSchemaConfig(request) {
|
|
2623
2949
|
const schemaName = (request.openAiSchemaName ?? "llm-response").trim() || "llm-response";
|
|
2624
2950
|
const providerInfo = resolveProvider(request.model);
|
|
2625
2951
|
const isOpenAiVariant = providerInfo.provider === "openai" || providerInfo.provider === "chatgpt";
|
|
@@ -2631,31 +2957,143 @@ async function generateJson(request) {
|
|
|
2631
2957
|
if (isOpenAiVariant && !isJsonSchemaObject(responseJsonSchema)) {
|
|
2632
2958
|
throw new Error("OpenAI structured outputs require a JSON object schema at the root.");
|
|
2633
2959
|
}
|
|
2634
|
-
const openAiTextFormat =
|
|
2960
|
+
const openAiTextFormat = isOpenAiVariant ? {
|
|
2635
2961
|
type: "json_schema",
|
|
2636
2962
|
name: schemaName,
|
|
2637
2963
|
strict: true,
|
|
2638
2964
|
schema: normalizeOpenAiSchema(responseJsonSchema)
|
|
2639
2965
|
} : void 0;
|
|
2966
|
+
return { providerInfo, responseJsonSchema, openAiTextFormat };
|
|
2967
|
+
}
|
|
2968
|
+
function streamJson(request) {
|
|
2969
|
+
const queue = createAsyncQueue();
|
|
2970
|
+
const abortController = new AbortController();
|
|
2971
|
+
const resolveAbortSignal = () => {
|
|
2972
|
+
if (!request.signal) {
|
|
2973
|
+
return abortController.signal;
|
|
2974
|
+
}
|
|
2975
|
+
if (request.signal.aborted) {
|
|
2976
|
+
abortController.abort(request.signal.reason);
|
|
2977
|
+
} else {
|
|
2978
|
+
request.signal.addEventListener(
|
|
2979
|
+
"abort",
|
|
2980
|
+
() => abortController.abort(request.signal?.reason),
|
|
2981
|
+
{
|
|
2982
|
+
once: true
|
|
2983
|
+
}
|
|
2984
|
+
);
|
|
2985
|
+
}
|
|
2986
|
+
return abortController.signal;
|
|
2987
|
+
};
|
|
2988
|
+
const result = (async () => {
|
|
2989
|
+
const signal = resolveAbortSignal();
|
|
2990
|
+
const maxAttempts = Math.max(1, Math.floor(request.maxAttempts ?? 2));
|
|
2991
|
+
const { providerInfo, responseJsonSchema, openAiTextFormat } = buildJsonSchemaConfig(request);
|
|
2992
|
+
const streamMode = request.streamMode ?? "partial";
|
|
2993
|
+
const failures = [];
|
|
2994
|
+
let openAiTextFormatForAttempt = openAiTextFormat;
|
|
2995
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
|
|
2996
|
+
let rawText = "";
|
|
2997
|
+
let lastPartial = "";
|
|
2998
|
+
try {
|
|
2999
|
+
const call = streamText({
|
|
3000
|
+
model: request.model,
|
|
3001
|
+
input: request.input,
|
|
3002
|
+
instructions: request.instructions,
|
|
3003
|
+
tools: request.tools,
|
|
3004
|
+
responseMimeType: request.responseMimeType ?? "application/json",
|
|
3005
|
+
responseJsonSchema,
|
|
3006
|
+
openAiReasoningEffort: request.openAiReasoningEffort,
|
|
3007
|
+
...openAiTextFormatForAttempt ? { openAiTextFormat: openAiTextFormatForAttempt } : {},
|
|
3008
|
+
signal
|
|
3009
|
+
});
|
|
3010
|
+
try {
|
|
3011
|
+
for await (const event of call.events) {
|
|
3012
|
+
queue.push(event);
|
|
3013
|
+
if (event.type === "delta" && event.channel === "response") {
|
|
3014
|
+
rawText += event.text;
|
|
3015
|
+
if (streamMode === "partial") {
|
|
3016
|
+
const partial = parsePartialJsonFromLlmText(rawText);
|
|
3017
|
+
if (partial !== null) {
|
|
3018
|
+
const serialized = JSON.stringify(partial);
|
|
3019
|
+
if (serialized !== lastPartial) {
|
|
3020
|
+
lastPartial = serialized;
|
|
3021
|
+
queue.push({
|
|
3022
|
+
type: "json",
|
|
3023
|
+
stage: "partial",
|
|
3024
|
+
value: partial
|
|
3025
|
+
});
|
|
3026
|
+
}
|
|
3027
|
+
}
|
|
3028
|
+
}
|
|
3029
|
+
}
|
|
3030
|
+
}
|
|
3031
|
+
} catch (streamError) {
|
|
3032
|
+
await call.result.catch(() => void 0);
|
|
3033
|
+
throw streamError;
|
|
3034
|
+
}
|
|
3035
|
+
const result2 = await call.result;
|
|
3036
|
+
rawText = rawText || result2.text;
|
|
3037
|
+
const cleanedText = normalizeJsonText(rawText);
|
|
3038
|
+
const repairedText = escapeNewlinesInStrings(cleanedText);
|
|
3039
|
+
const payload = JSON.parse(repairedText);
|
|
3040
|
+
const normalized = typeof request.normalizeJson === "function" ? request.normalizeJson(payload) : payload;
|
|
3041
|
+
const parsed = request.schema.parse(normalized);
|
|
3042
|
+
queue.push({ type: "json", stage: "final", value: parsed });
|
|
3043
|
+
queue.close();
|
|
3044
|
+
return { value: parsed, rawText, result: result2 };
|
|
3045
|
+
} catch (error) {
|
|
3046
|
+
const handled = error instanceof Error ? error : new Error(String(error));
|
|
3047
|
+
failures.push({ attempt, rawText, error: handled });
|
|
3048
|
+
if (providerInfo.provider === "chatgpt" && openAiTextFormatForAttempt) {
|
|
3049
|
+
openAiTextFormatForAttempt = void 0;
|
|
3050
|
+
}
|
|
3051
|
+
if (attempt >= maxAttempts) {
|
|
3052
|
+
throw new LlmJsonCallError(`LLM JSON call failed after ${attempt} attempt(s)`, failures);
|
|
3053
|
+
}
|
|
3054
|
+
}
|
|
3055
|
+
}
|
|
3056
|
+
throw new LlmJsonCallError("LLM JSON call failed", failures);
|
|
3057
|
+
})().catch((error) => {
|
|
3058
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
3059
|
+
queue.fail(err);
|
|
3060
|
+
throw err;
|
|
3061
|
+
});
|
|
3062
|
+
return {
|
|
3063
|
+
events: queue.iterable,
|
|
3064
|
+
result,
|
|
3065
|
+
abort: () => abortController.abort()
|
|
3066
|
+
};
|
|
3067
|
+
}
|
|
3068
|
+
async function generateJson(request) {
|
|
3069
|
+
const maxAttempts = Math.max(1, Math.floor(request.maxAttempts ?? 2));
|
|
3070
|
+
const { providerInfo, responseJsonSchema, openAiTextFormat } = buildJsonSchemaConfig(request);
|
|
3071
|
+
let openAiTextFormatForAttempt = openAiTextFormat;
|
|
2640
3072
|
const failures = [];
|
|
2641
3073
|
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
|
|
2642
3074
|
let rawText = "";
|
|
2643
3075
|
try {
|
|
2644
|
-
const contents = resolveTextContents(request);
|
|
2645
3076
|
const call = streamText({
|
|
2646
3077
|
model: request.model,
|
|
2647
|
-
|
|
3078
|
+
input: request.input,
|
|
3079
|
+
instructions: request.instructions,
|
|
2648
3080
|
tools: request.tools,
|
|
2649
3081
|
responseMimeType: request.responseMimeType ?? "application/json",
|
|
2650
3082
|
responseJsonSchema,
|
|
2651
3083
|
openAiReasoningEffort: request.openAiReasoningEffort,
|
|
2652
|
-
...
|
|
3084
|
+
...openAiTextFormatForAttempt ? { openAiTextFormat: openAiTextFormatForAttempt } : {},
|
|
2653
3085
|
signal: request.signal
|
|
2654
3086
|
});
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
3087
|
+
try {
|
|
3088
|
+
for await (const event of call.events) {
|
|
3089
|
+
request.onEvent?.(event);
|
|
3090
|
+
if (event.type === "delta" && event.channel === "response") {
|
|
3091
|
+
rawText += event.text;
|
|
3092
|
+
}
|
|
2658
3093
|
}
|
|
3094
|
+
} catch (streamError) {
|
|
3095
|
+
await call.result.catch(() => void 0);
|
|
3096
|
+
throw streamError;
|
|
2659
3097
|
}
|
|
2660
3098
|
const result = await call.result;
|
|
2661
3099
|
rawText = rawText || result.text;
|
|
@@ -2668,6 +3106,9 @@ async function generateJson(request) {
|
|
|
2668
3106
|
} catch (error) {
|
|
2669
3107
|
const handled = error instanceof Error ? error : new Error(String(error));
|
|
2670
3108
|
failures.push({ attempt, rawText, error: handled });
|
|
3109
|
+
if (providerInfo.provider === "chatgpt" && openAiTextFormatForAttempt) {
|
|
3110
|
+
openAiTextFormatForAttempt = void 0;
|
|
3111
|
+
}
|
|
2671
3112
|
if (attempt >= maxAttempts) {
|
|
2672
3113
|
throw new LlmJsonCallError(`LLM JSON call failed after ${attempt} attempt(s)`, failures);
|
|
2673
3114
|
}
|
|
@@ -3253,7 +3694,7 @@ async function gradeGeneratedImage(params) {
|
|
|
3253
3694
|
];
|
|
3254
3695
|
const { value } = await generateJson({
|
|
3255
3696
|
model: params.model,
|
|
3256
|
-
|
|
3697
|
+
input: [{ role: "user", content: parts }],
|
|
3257
3698
|
schema: IMAGE_GRADE_SCHEMA
|
|
3258
3699
|
});
|
|
3259
3700
|
return value;
|
|
@@ -3330,7 +3771,7 @@ async function generateImages(request) {
|
|
|
3330
3771
|
lines.push(`\\nPlease make all ${pending.length} remaining images.`);
|
|
3331
3772
|
return [{ type: "text", text: lines.join("\\n") }];
|
|
3332
3773
|
};
|
|
3333
|
-
const
|
|
3774
|
+
const inputMessages = [{ role: "user", content: buildInitialPromptParts() }];
|
|
3334
3775
|
const orderedEntries = [...promptEntries];
|
|
3335
3776
|
const resolvedImages = /* @__PURE__ */ new Map();
|
|
3336
3777
|
const removeResolvedEntries = (resolved) => {
|
|
@@ -3350,7 +3791,7 @@ async function generateImages(request) {
|
|
|
3350
3791
|
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
|
|
3351
3792
|
const result = await generateText({
|
|
3352
3793
|
model: request.model,
|
|
3353
|
-
|
|
3794
|
+
input: inputMessages,
|
|
3354
3795
|
responseModalities: ["IMAGE", "TEXT"],
|
|
3355
3796
|
imageAspectRatio: request.imageAspectRatio,
|
|
3356
3797
|
imageSize: request.imageSize ?? "2K"
|
|
@@ -3397,8 +3838,11 @@ async function generateImages(request) {
|
|
|
3397
3838
|
if (promptEntries.length === 0) {
|
|
3398
3839
|
break;
|
|
3399
3840
|
}
|
|
3400
|
-
|
|
3401
|
-
|
|
3841
|
+
inputMessages.push({
|
|
3842
|
+
role: "assistant",
|
|
3843
|
+
content: result.content.parts
|
|
3844
|
+
});
|
|
3845
|
+
inputMessages.push({ role: "user", content: buildContinuationPromptParts(promptEntries) });
|
|
3402
3846
|
}
|
|
3403
3847
|
const orderedImages = [];
|
|
3404
3848
|
for (const entry of orderedEntries) {
|
|
@@ -3495,6 +3939,7 @@ ${lines}`;
|
|
|
3495
3939
|
refreshChatGptOauthToken,
|
|
3496
3940
|
runToolLoop,
|
|
3497
3941
|
sanitisePartForLogging,
|
|
3942
|
+
streamJson,
|
|
3498
3943
|
streamText,
|
|
3499
3944
|
stripCodexCitationMarkers,
|
|
3500
3945
|
toGeminiJsonSchema,
|