@ljoukov/llm 0.1.2 → 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/dist/index.js CHANGED
@@ -1518,10 +1518,13 @@ function convertGooglePartsToLlmParts(parts) {
1518
1518
  function assertLlmRole(value) {
1519
1519
  switch (value) {
1520
1520
  case "user":
1521
- case "model":
1521
+ case "assistant":
1522
1522
  case "system":
1523
+ case "developer":
1523
1524
  case "tool":
1524
1525
  return value;
1526
+ case "model":
1527
+ return "assistant";
1525
1528
  default:
1526
1529
  throw new Error(`Unsupported LLM role: ${String(value)}`);
1527
1530
  }
@@ -1551,8 +1554,9 @@ function toGeminiPart(part) {
1551
1554
  }
1552
1555
  }
1553
1556
  function convertLlmContentToGeminiContent(content) {
1557
+ const role = content.role === "assistant" ? "model" : "user";
1554
1558
  return {
1555
- role: content.role,
1559
+ role,
1556
1560
  parts: content.parts.map(toGeminiPart)
1557
1561
  };
1558
1562
  }
@@ -1598,6 +1602,20 @@ function isInlineImageMime(mimeType) {
1598
1602
  }
1599
1603
  return mimeType.startsWith("image/");
1600
1604
  }
1605
+ function guessInlineDataFilename(mimeType) {
1606
+ switch (mimeType) {
1607
+ case "application/pdf":
1608
+ return "document.pdf";
1609
+ case "application/json":
1610
+ return "data.json";
1611
+ case "text/markdown":
1612
+ return "document.md";
1613
+ case "text/plain":
1614
+ return "document.txt";
1615
+ default:
1616
+ return "attachment.bin";
1617
+ }
1618
+ }
1601
1619
  function mergeConsecutiveTextParts(parts) {
1602
1620
  if (parts.length === 0) {
1603
1621
  return [];
@@ -1709,28 +1727,350 @@ function parseJsonFromLlmText(rawText) {
1709
1727
  const repairedText = escapeNewlinesInStrings(cleanedText);
1710
1728
  return JSON.parse(repairedText);
1711
1729
  }
1712
- function resolveTextContents(input) {
1713
- if ("contents" in input) {
1714
- return input.contents;
1730
+ function parsePartialJsonFromLlmText(rawText) {
1731
+ const jsonStart = extractJsonStartText(rawText);
1732
+ if (!jsonStart) {
1733
+ return null;
1734
+ }
1735
+ try {
1736
+ return parsePartialJson(jsonStart);
1737
+ } catch {
1738
+ return null;
1739
+ }
1740
+ }
1741
+ function extractJsonStartText(rawText) {
1742
+ let text = rawText.trimStart();
1743
+ if (text.startsWith("```")) {
1744
+ text = text.replace(/^```[a-zA-Z0-9_-]*\s*\n?/, "");
1745
+ }
1746
+ const objIndex = text.indexOf("{");
1747
+ const arrIndex = text.indexOf("[");
1748
+ let start = -1;
1749
+ if (objIndex !== -1 && arrIndex !== -1) {
1750
+ start = Math.min(objIndex, arrIndex);
1751
+ } else {
1752
+ start = objIndex !== -1 ? objIndex : arrIndex;
1753
+ }
1754
+ if (start === -1) {
1755
+ return null;
1756
+ }
1757
+ return text.slice(start);
1758
+ }
1759
+ function parsePartialJson(text) {
1760
+ let i = 0;
1761
+ const len = text.length;
1762
+ const isWhitespace = (char) => char === " " || char === "\n" || char === "\r" || char === " ";
1763
+ const skipWhitespace = () => {
1764
+ while (i < len && isWhitespace(text[i] ?? "")) {
1765
+ i += 1;
1766
+ }
1767
+ };
1768
+ const parseString = () => {
1769
+ if (text[i] !== '"') {
1770
+ return null;
1771
+ }
1772
+ i += 1;
1773
+ let value = "";
1774
+ while (i < len) {
1775
+ const ch = text[i] ?? "";
1776
+ if (ch === '"') {
1777
+ i += 1;
1778
+ return { value, complete: true };
1779
+ }
1780
+ if (ch === "\\") {
1781
+ if (i + 1 >= len) {
1782
+ return { value, complete: false };
1783
+ }
1784
+ const esc = text[i + 1] ?? "";
1785
+ switch (esc) {
1786
+ case '"':
1787
+ case "\\":
1788
+ case "/":
1789
+ value += esc;
1790
+ i += 2;
1791
+ continue;
1792
+ case "b":
1793
+ value += "\b";
1794
+ i += 2;
1795
+ continue;
1796
+ case "f":
1797
+ value += "\f";
1798
+ i += 2;
1799
+ continue;
1800
+ case "n":
1801
+ value += "\n";
1802
+ i += 2;
1803
+ continue;
1804
+ case "r":
1805
+ value += "\r";
1806
+ i += 2;
1807
+ continue;
1808
+ case "t":
1809
+ value += " ";
1810
+ i += 2;
1811
+ continue;
1812
+ case "u": {
1813
+ if (i + 5 >= len) {
1814
+ return { value, complete: false };
1815
+ }
1816
+ const hex = text.slice(i + 2, i + 6);
1817
+ if (!/^[0-9a-fA-F]{4}$/u.test(hex)) {
1818
+ value += "u";
1819
+ i += 2;
1820
+ continue;
1821
+ }
1822
+ value += String.fromCharCode(Number.parseInt(hex, 16));
1823
+ i += 6;
1824
+ continue;
1825
+ }
1826
+ default:
1827
+ value += esc;
1828
+ i += 2;
1829
+ continue;
1830
+ }
1831
+ }
1832
+ value += ch;
1833
+ i += 1;
1834
+ }
1835
+ return { value, complete: false };
1836
+ };
1837
+ const parseNumber = () => {
1838
+ const start = i;
1839
+ while (i < len) {
1840
+ const ch = text[i] ?? "";
1841
+ if (isWhitespace(ch) || ch === "," || ch === "}" || ch === "]") {
1842
+ break;
1843
+ }
1844
+ i += 1;
1845
+ }
1846
+ const raw = text.slice(start, i);
1847
+ if (!/^-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?$/u.test(raw)) {
1848
+ i = start;
1849
+ return null;
1850
+ }
1851
+ return { value: Number(raw), complete: true };
1852
+ };
1853
+ const parseLiteral = () => {
1854
+ if (text.startsWith("true", i)) {
1855
+ i += 4;
1856
+ return { value: true, complete: true };
1857
+ }
1858
+ if (text.startsWith("false", i)) {
1859
+ i += 5;
1860
+ return { value: false, complete: true };
1861
+ }
1862
+ if (text.startsWith("null", i)) {
1863
+ i += 4;
1864
+ return { value: null, complete: true };
1865
+ }
1866
+ return null;
1867
+ };
1868
+ skipWhitespace();
1869
+ const first = text[i];
1870
+ if (first !== "{" && first !== "[") {
1871
+ return null;
1715
1872
  }
1873
+ const root = first === "{" ? {} : [];
1874
+ const stack = first === "{" ? [{ type: "object", value: root, state: "keyOrEnd" }] : [{ type: "array", value: root, state: "valueOrEnd" }];
1875
+ i += 1;
1876
+ while (stack.length > 0) {
1877
+ skipWhitespace();
1878
+ if (i >= len) {
1879
+ break;
1880
+ }
1881
+ const ctx = stack[stack.length - 1];
1882
+ if (!ctx) {
1883
+ break;
1884
+ }
1885
+ const ch = text[i] ?? "";
1886
+ if (ctx.type === "object") {
1887
+ if (ctx.state === "keyOrEnd") {
1888
+ if (ch === "}") {
1889
+ i += 1;
1890
+ stack.pop();
1891
+ continue;
1892
+ }
1893
+ if (ch === ",") {
1894
+ i += 1;
1895
+ continue;
1896
+ }
1897
+ if (ch !== '"') {
1898
+ break;
1899
+ }
1900
+ const key = parseString();
1901
+ if (!key) {
1902
+ break;
1903
+ }
1904
+ if (!key.complete) {
1905
+ break;
1906
+ }
1907
+ ctx.key = key.value;
1908
+ ctx.state = "colon";
1909
+ continue;
1910
+ }
1911
+ if (ctx.state === "colon") {
1912
+ if (ch === ":") {
1913
+ i += 1;
1914
+ ctx.state = "value";
1915
+ continue;
1916
+ }
1917
+ break;
1918
+ }
1919
+ if (ctx.state === "value") {
1920
+ if (ch === "}") {
1921
+ i += 1;
1922
+ ctx.key = void 0;
1923
+ stack.pop();
1924
+ continue;
1925
+ }
1926
+ if (ch === ",") {
1927
+ i += 1;
1928
+ ctx.key = void 0;
1929
+ ctx.state = "keyOrEnd";
1930
+ continue;
1931
+ }
1932
+ const key = ctx.key;
1933
+ if (!key) {
1934
+ break;
1935
+ }
1936
+ if (ch === "{" || ch === "[") {
1937
+ const container = ch === "{" ? {} : [];
1938
+ ctx.value[key] = container;
1939
+ ctx.key = void 0;
1940
+ ctx.state = "commaOrEnd";
1941
+ stack.push(
1942
+ ch === "{" ? { type: "object", value: container, state: "keyOrEnd" } : { type: "array", value: container, state: "valueOrEnd" }
1943
+ );
1944
+ i += 1;
1945
+ continue;
1946
+ }
1947
+ let primitive = null;
1948
+ if (ch === '"') {
1949
+ primitive = parseString();
1950
+ } else if (ch === "-" || ch >= "0" && ch <= "9") {
1951
+ primitive = parseNumber();
1952
+ } else {
1953
+ primitive = parseLiteral();
1954
+ }
1955
+ if (!primitive) {
1956
+ break;
1957
+ }
1958
+ ctx.value[key] = primitive.value;
1959
+ ctx.key = void 0;
1960
+ ctx.state = "commaOrEnd";
1961
+ if (!primitive.complete) {
1962
+ break;
1963
+ }
1964
+ continue;
1965
+ }
1966
+ if (ctx.state === "commaOrEnd") {
1967
+ if (ch === ",") {
1968
+ i += 1;
1969
+ ctx.state = "keyOrEnd";
1970
+ continue;
1971
+ }
1972
+ if (ch === "}") {
1973
+ i += 1;
1974
+ stack.pop();
1975
+ continue;
1976
+ }
1977
+ break;
1978
+ }
1979
+ } else {
1980
+ if (ctx.state === "valueOrEnd") {
1981
+ if (ch === "]") {
1982
+ i += 1;
1983
+ stack.pop();
1984
+ continue;
1985
+ }
1986
+ if (ch === ",") {
1987
+ i += 1;
1988
+ continue;
1989
+ }
1990
+ if (ch === "{" || ch === "[") {
1991
+ const container = ch === "{" ? {} : [];
1992
+ ctx.value.push(container);
1993
+ ctx.state = "commaOrEnd";
1994
+ stack.push(
1995
+ ch === "{" ? { type: "object", value: container, state: "keyOrEnd" } : { type: "array", value: container, state: "valueOrEnd" }
1996
+ );
1997
+ i += 1;
1998
+ continue;
1999
+ }
2000
+ let primitive = null;
2001
+ if (ch === '"') {
2002
+ primitive = parseString();
2003
+ } else if (ch === "-" || ch >= "0" && ch <= "9") {
2004
+ primitive = parseNumber();
2005
+ } else {
2006
+ primitive = parseLiteral();
2007
+ }
2008
+ if (!primitive) {
2009
+ break;
2010
+ }
2011
+ ctx.value.push(primitive.value);
2012
+ ctx.state = "commaOrEnd";
2013
+ if (!primitive.complete) {
2014
+ break;
2015
+ }
2016
+ continue;
2017
+ }
2018
+ if (ctx.state === "commaOrEnd") {
2019
+ if (ch === ",") {
2020
+ i += 1;
2021
+ ctx.state = "valueOrEnd";
2022
+ continue;
2023
+ }
2024
+ if (ch === "]") {
2025
+ i += 1;
2026
+ stack.pop();
2027
+ continue;
2028
+ }
2029
+ break;
2030
+ }
2031
+ }
2032
+ }
2033
+ return root;
2034
+ }
2035
+ function resolveTextContents(input) {
1716
2036
  const contents = [];
1717
- if (input.systemPrompt) {
2037
+ if (input.instructions) {
2038
+ const instructions = input.instructions.trim();
2039
+ if (instructions.length > 0) {
2040
+ contents.push({
2041
+ role: "system",
2042
+ parts: [{ type: "text", text: instructions }]
2043
+ });
2044
+ }
2045
+ }
2046
+ if (typeof input.input === "string") {
1718
2047
  contents.push({
1719
- role: "system",
1720
- parts: [{ type: "text", text: input.systemPrompt }]
2048
+ role: "user",
2049
+ parts: [{ type: "text", text: input.input }]
2050
+ });
2051
+ return contents;
2052
+ }
2053
+ for (const message of input.input) {
2054
+ const parts = typeof message.content === "string" ? [{ type: "text", text: message.content }] : message.content;
2055
+ contents.push({
2056
+ role: message.role,
2057
+ parts: parts.map(
2058
+ (part) => part.type === "text" ? {
2059
+ type: "text",
2060
+ text: part.text,
2061
+ thought: "thought" in part && part.thought === true ? true : void 0
2062
+ } : { type: "inlineData", data: part.data, mimeType: part.mimeType }
2063
+ )
1721
2064
  });
1722
2065
  }
1723
- contents.push({
1724
- role: "user",
1725
- parts: [{ type: "text", text: input.prompt }]
1726
- });
1727
2066
  return contents;
1728
2067
  }
1729
2068
  function toOpenAiInput(contents) {
1730
2069
  const OPENAI_ROLE_FROM_LLM = {
1731
2070
  user: "user",
1732
- model: "assistant",
2071
+ assistant: "assistant",
1733
2072
  system: "system",
2073
+ developer: "developer",
1734
2074
  tool: "assistant"
1735
2075
  };
1736
2076
  return contents.map((content) => {
@@ -1740,9 +2080,18 @@ function toOpenAiInput(contents) {
1740
2080
  parts.push({ type: "input_text", text: part.text });
1741
2081
  continue;
1742
2082
  }
1743
- const mimeType = part.mimeType ?? "application/octet-stream";
1744
- const dataUrl = `data:${mimeType};base64,${part.data}`;
1745
- parts.push({ type: "input_image", image_url: dataUrl, detail: "auto" });
2083
+ const mimeType = part.mimeType;
2084
+ if (isInlineImageMime(mimeType)) {
2085
+ const dataUrl = `data:${mimeType};base64,${part.data}`;
2086
+ parts.push({ type: "input_image", image_url: dataUrl, detail: "auto" });
2087
+ continue;
2088
+ }
2089
+ const fileData = decodeInlineDataBuffer(part.data).toString("base64");
2090
+ parts.push({
2091
+ type: "input_file",
2092
+ filename: guessInlineDataFilename(mimeType),
2093
+ file_data: fileData
2094
+ });
1746
2095
  }
1747
2096
  if (parts.length === 1 && parts[0]?.type === "input_text" && typeof parts[0].text === "string") {
1748
2097
  return {
@@ -1760,7 +2109,7 @@ function toChatGptInput(contents) {
1760
2109
  const instructionsParts = [];
1761
2110
  const input = [];
1762
2111
  for (const content of contents) {
1763
- if (content.role === "system") {
2112
+ if (content.role === "system" || content.role === "developer") {
1764
2113
  for (const part of content.parts) {
1765
2114
  if (part.type === "text") {
1766
2115
  instructionsParts.push(part.text);
@@ -1768,7 +2117,7 @@ function toChatGptInput(contents) {
1768
2117
  }
1769
2118
  continue;
1770
2119
  }
1771
- const isAssistant = content.role === "model";
2120
+ const isAssistant = content.role === "assistant" || content.role === "tool";
1772
2121
  const parts = [];
1773
2122
  for (const part of content.parts) {
1774
2123
  if (part.type === "text") {
@@ -1778,19 +2127,29 @@ function toChatGptInput(contents) {
1778
2127
  });
1779
2128
  continue;
1780
2129
  }
1781
- const mimeType = part.mimeType ?? "application/octet-stream";
1782
- const dataUrl = `data:${mimeType};base64,${part.data}`;
1783
2130
  if (isAssistant) {
2131
+ const mimeType = part.mimeType ?? "application/octet-stream";
1784
2132
  parts.push({
1785
2133
  type: "output_text",
1786
- text: `[image:${mimeType}]`
2134
+ text: isInlineImageMime(part.mimeType) ? `[image:${mimeType}]` : `[file:${mimeType}]`
1787
2135
  });
1788
2136
  } else {
1789
- parts.push({
1790
- type: "input_image",
1791
- image_url: dataUrl,
1792
- detail: "auto"
1793
- });
2137
+ if (isInlineImageMime(part.mimeType)) {
2138
+ const mimeType = part.mimeType ?? "application/octet-stream";
2139
+ const dataUrl = `data:${mimeType};base64,${part.data}`;
2140
+ parts.push({
2141
+ type: "input_image",
2142
+ image_url: dataUrl,
2143
+ detail: "auto"
2144
+ });
2145
+ } else {
2146
+ const fileData = decodeInlineDataBuffer(part.data).toString("base64");
2147
+ parts.push({
2148
+ type: "input_file",
2149
+ filename: guessInlineDataFilename(part.mimeType),
2150
+ file_data: fileData
2151
+ });
2152
+ }
1794
2153
  }
1795
2154
  }
1796
2155
  if (parts.length === 0) {
@@ -2478,7 +2837,7 @@ async function runTextCall(params) {
2478
2837
  });
2479
2838
  }
2480
2839
  const mergedParts = mergeConsecutiveTextParts(responseParts);
2481
- const content = mergedParts.length > 0 ? { role: responseRole ?? "model", parts: mergedParts } : void 0;
2840
+ const content = mergedParts.length > 0 ? { role: responseRole ?? "assistant", parts: mergedParts } : void 0;
2482
2841
  const { text, thoughts } = extractTextByChannel(content);
2483
2842
  const costUsd = estimateCallCostUsd({
2484
2843
  modelId: modelVersion,
@@ -2528,8 +2887,7 @@ async function generateText(request) {
2528
2887
  }
2529
2888
  return await call.result;
2530
2889
  }
2531
- async function generateJson(request) {
2532
- const maxAttempts = Math.max(1, Math.floor(request.maxAttempts ?? 2));
2890
+ function buildJsonSchemaConfig(request) {
2533
2891
  const schemaName = (request.openAiSchemaName ?? "llm-response").trim() || "llm-response";
2534
2892
  const providerInfo = resolveProvider(request.model);
2535
2893
  const isOpenAiVariant = providerInfo.provider === "openai" || providerInfo.provider === "chatgpt";
@@ -2541,31 +2899,143 @@ async function generateJson(request) {
2541
2899
  if (isOpenAiVariant && !isJsonSchemaObject(responseJsonSchema)) {
2542
2900
  throw new Error("OpenAI structured outputs require a JSON object schema at the root.");
2543
2901
  }
2544
- const openAiTextFormat = providerInfo.provider === "openai" ? {
2902
+ const openAiTextFormat = isOpenAiVariant ? {
2545
2903
  type: "json_schema",
2546
2904
  name: schemaName,
2547
2905
  strict: true,
2548
2906
  schema: normalizeOpenAiSchema(responseJsonSchema)
2549
2907
  } : void 0;
2908
+ return { providerInfo, responseJsonSchema, openAiTextFormat };
2909
+ }
2910
+ function streamJson(request) {
2911
+ const queue = createAsyncQueue();
2912
+ const abortController = new AbortController();
2913
+ const resolveAbortSignal = () => {
2914
+ if (!request.signal) {
2915
+ return abortController.signal;
2916
+ }
2917
+ if (request.signal.aborted) {
2918
+ abortController.abort(request.signal.reason);
2919
+ } else {
2920
+ request.signal.addEventListener(
2921
+ "abort",
2922
+ () => abortController.abort(request.signal?.reason),
2923
+ {
2924
+ once: true
2925
+ }
2926
+ );
2927
+ }
2928
+ return abortController.signal;
2929
+ };
2930
+ const result = (async () => {
2931
+ const signal = resolveAbortSignal();
2932
+ const maxAttempts = Math.max(1, Math.floor(request.maxAttempts ?? 2));
2933
+ const { providerInfo, responseJsonSchema, openAiTextFormat } = buildJsonSchemaConfig(request);
2934
+ const streamMode = request.streamMode ?? "partial";
2935
+ const failures = [];
2936
+ let openAiTextFormatForAttempt = openAiTextFormat;
2937
+ for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
2938
+ let rawText = "";
2939
+ let lastPartial = "";
2940
+ try {
2941
+ const call = streamText({
2942
+ model: request.model,
2943
+ input: request.input,
2944
+ instructions: request.instructions,
2945
+ tools: request.tools,
2946
+ responseMimeType: request.responseMimeType ?? "application/json",
2947
+ responseJsonSchema,
2948
+ openAiReasoningEffort: request.openAiReasoningEffort,
2949
+ ...openAiTextFormatForAttempt ? { openAiTextFormat: openAiTextFormatForAttempt } : {},
2950
+ signal
2951
+ });
2952
+ try {
2953
+ for await (const event of call.events) {
2954
+ queue.push(event);
2955
+ if (event.type === "delta" && event.channel === "response") {
2956
+ rawText += event.text;
2957
+ if (streamMode === "partial") {
2958
+ const partial = parsePartialJsonFromLlmText(rawText);
2959
+ if (partial !== null) {
2960
+ const serialized = JSON.stringify(partial);
2961
+ if (serialized !== lastPartial) {
2962
+ lastPartial = serialized;
2963
+ queue.push({
2964
+ type: "json",
2965
+ stage: "partial",
2966
+ value: partial
2967
+ });
2968
+ }
2969
+ }
2970
+ }
2971
+ }
2972
+ }
2973
+ } catch (streamError) {
2974
+ await call.result.catch(() => void 0);
2975
+ throw streamError;
2976
+ }
2977
+ const result2 = await call.result;
2978
+ rawText = rawText || result2.text;
2979
+ const cleanedText = normalizeJsonText(rawText);
2980
+ const repairedText = escapeNewlinesInStrings(cleanedText);
2981
+ const payload = JSON.parse(repairedText);
2982
+ const normalized = typeof request.normalizeJson === "function" ? request.normalizeJson(payload) : payload;
2983
+ const parsed = request.schema.parse(normalized);
2984
+ queue.push({ type: "json", stage: "final", value: parsed });
2985
+ queue.close();
2986
+ return { value: parsed, rawText, result: result2 };
2987
+ } catch (error) {
2988
+ const handled = error instanceof Error ? error : new Error(String(error));
2989
+ failures.push({ attempt, rawText, error: handled });
2990
+ if (providerInfo.provider === "chatgpt" && openAiTextFormatForAttempt) {
2991
+ openAiTextFormatForAttempt = void 0;
2992
+ }
2993
+ if (attempt >= maxAttempts) {
2994
+ throw new LlmJsonCallError(`LLM JSON call failed after ${attempt} attempt(s)`, failures);
2995
+ }
2996
+ }
2997
+ }
2998
+ throw new LlmJsonCallError("LLM JSON call failed", failures);
2999
+ })().catch((error) => {
3000
+ const err = error instanceof Error ? error : new Error(String(error));
3001
+ queue.fail(err);
3002
+ throw err;
3003
+ });
3004
+ return {
3005
+ events: queue.iterable,
3006
+ result,
3007
+ abort: () => abortController.abort()
3008
+ };
3009
+ }
3010
+ async function generateJson(request) {
3011
+ const maxAttempts = Math.max(1, Math.floor(request.maxAttempts ?? 2));
3012
+ const { providerInfo, responseJsonSchema, openAiTextFormat } = buildJsonSchemaConfig(request);
3013
+ let openAiTextFormatForAttempt = openAiTextFormat;
2550
3014
  const failures = [];
2551
3015
  for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
2552
3016
  let rawText = "";
2553
3017
  try {
2554
- const contents = resolveTextContents(request);
2555
3018
  const call = streamText({
2556
3019
  model: request.model,
2557
- contents,
3020
+ input: request.input,
3021
+ instructions: request.instructions,
2558
3022
  tools: request.tools,
2559
3023
  responseMimeType: request.responseMimeType ?? "application/json",
2560
3024
  responseJsonSchema,
2561
3025
  openAiReasoningEffort: request.openAiReasoningEffort,
2562
- ...openAiTextFormat ? { openAiTextFormat } : {},
3026
+ ...openAiTextFormatForAttempt ? { openAiTextFormat: openAiTextFormatForAttempt } : {},
2563
3027
  signal: request.signal
2564
3028
  });
2565
- for await (const event of call.events) {
2566
- if (event.type === "delta" && event.channel === "response") {
2567
- rawText += event.text;
3029
+ try {
3030
+ for await (const event of call.events) {
3031
+ request.onEvent?.(event);
3032
+ if (event.type === "delta" && event.channel === "response") {
3033
+ rawText += event.text;
3034
+ }
2568
3035
  }
3036
+ } catch (streamError) {
3037
+ await call.result.catch(() => void 0);
3038
+ throw streamError;
2569
3039
  }
2570
3040
  const result = await call.result;
2571
3041
  rawText = rawText || result.text;
@@ -2578,6 +3048,9 @@ async function generateJson(request) {
2578
3048
  } catch (error) {
2579
3049
  const handled = error instanceof Error ? error : new Error(String(error));
2580
3050
  failures.push({ attempt, rawText, error: handled });
3051
+ if (providerInfo.provider === "chatgpt" && openAiTextFormatForAttempt) {
3052
+ openAiTextFormatForAttempt = void 0;
3053
+ }
2581
3054
  if (attempt >= maxAttempts) {
2582
3055
  throw new LlmJsonCallError(`LLM JSON call failed after ${attempt} attempt(s)`, failures);
2583
3056
  }
@@ -3163,7 +3636,7 @@ async function gradeGeneratedImage(params) {
3163
3636
  ];
3164
3637
  const { value } = await generateJson({
3165
3638
  model: params.model,
3166
- contents: [{ role: "user", parts }],
3639
+ input: [{ role: "user", content: parts }],
3167
3640
  schema: IMAGE_GRADE_SCHEMA
3168
3641
  });
3169
3642
  return value;
@@ -3240,7 +3713,7 @@ async function generateImages(request) {
3240
3713
  lines.push(`\\nPlease make all ${pending.length} remaining images.`);
3241
3714
  return [{ type: "text", text: lines.join("\\n") }];
3242
3715
  };
3243
- const contents = [{ role: "user", parts: buildInitialPromptParts() }];
3716
+ const inputMessages = [{ role: "user", content: buildInitialPromptParts() }];
3244
3717
  const orderedEntries = [...promptEntries];
3245
3718
  const resolvedImages = /* @__PURE__ */ new Map();
3246
3719
  const removeResolvedEntries = (resolved) => {
@@ -3260,7 +3733,7 @@ async function generateImages(request) {
3260
3733
  for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
3261
3734
  const result = await generateText({
3262
3735
  model: request.model,
3263
- contents,
3736
+ input: inputMessages,
3264
3737
  responseModalities: ["IMAGE", "TEXT"],
3265
3738
  imageAspectRatio: request.imageAspectRatio,
3266
3739
  imageSize: request.imageSize ?? "2K"
@@ -3307,8 +3780,11 @@ async function generateImages(request) {
3307
3780
  if (promptEntries.length === 0) {
3308
3781
  break;
3309
3782
  }
3310
- contents.push(result.content);
3311
- contents.push({ role: "user", parts: buildContinuationPromptParts(promptEntries) });
3783
+ inputMessages.push({
3784
+ role: "assistant",
3785
+ content: result.content.parts
3786
+ });
3787
+ inputMessages.push({ role: "user", content: buildContinuationPromptParts(promptEntries) });
3312
3788
  }
3313
3789
  const orderedImages = [];
3314
3790
  for (const entry of orderedEntries) {
@@ -3404,6 +3880,7 @@ export {
3404
3880
  refreshChatGptOauthToken,
3405
3881
  runToolLoop,
3406
3882
  sanitisePartForLogging,
3883
+ streamJson,
3407
3884
  streamText,
3408
3885
  stripCodexCitationMarkers,
3409
3886
  toGeminiJsonSchema,