@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/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 "model":
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: content.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 resolveTextContents(input) {
1784
- if ("contents" in input) {
1785
- return input.contents;
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.systemPrompt) {
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: "system",
1791
- parts: [{ type: "text", text: input.systemPrompt }]
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
- model: "assistant",
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 === "model";
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 ?? "model", parts: mergedParts } : void 0;
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
- async function generateJson(request) {
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 = providerInfo.provider === "openai" ? {
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
- contents,
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
- ...openAiTextFormat ? { openAiTextFormat } : {},
3084
+ ...openAiTextFormatForAttempt ? { openAiTextFormat: openAiTextFormatForAttempt } : {},
2653
3085
  signal: request.signal
2654
3086
  });
2655
- for await (const event of call.events) {
2656
- if (event.type === "delta" && event.channel === "response") {
2657
- rawText += event.text;
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
- contents: [{ role: "user", parts }],
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 contents = [{ role: "user", parts: buildInitialPromptParts() }];
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
- contents,
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
- contents.push(result.content);
3401
- contents.push({ role: "user", parts: buildContinuationPromptParts(promptEntries) });
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,