@genesislcap/ai-assistant 15.14.2 → 15.15.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.
@@ -1154,6 +1154,14 @@ function vendorOfModel(modelId) {
1154
1154
  return void 0;
1155
1155
  }
1156
1156
 
1157
+ // ../../foundation-ai/dist/esm/utils/image-mime.js
1158
+ function normalizeImageMime(mimeType) {
1159
+ return mimeType === "image/jpg" ? "image/jpeg" : mimeType;
1160
+ }
1161
+ function stripImageDataUrl(data) {
1162
+ return data.replace(/^data:[^,]*;base64,/, "");
1163
+ }
1164
+
1157
1165
  // ../../foundation-ai/dist/esm/utils/temperature.js
1158
1166
  var DEFAULT_ANCHOR = 0.5;
1159
1167
  function scaleTemperature(normalized, { defaultTemp, maxTemp }) {
@@ -1762,8 +1770,28 @@ function anthropicThinking(model, policy) {
1762
1770
  if (policy === "auto") {
1763
1771
  return supportsAdaptiveThinking(model) ? adaptive : void 0;
1764
1772
  }
1773
+ if (policy !== void 0) {
1774
+ return supportsAdaptiveThinking(model) ? adaptive : void 0;
1775
+ }
1765
1776
  return defaultsToThinking ? adaptive : void 0;
1766
1777
  }
1778
+ function supportsEffort(model) {
1779
+ return model === "claude-fable-5" || model === "claude-opus-4-8" || model === "claude-sonnet-5";
1780
+ }
1781
+ var ANTHROPIC_EFFORT = {
1782
+ minimal: "low",
1783
+ low: "low",
1784
+ medium: "medium",
1785
+ high: "high",
1786
+ max: "max"
1787
+ };
1788
+ function anthropicEffort(model, policy) {
1789
+ if (policy === void 0 || policy === "auto" || policy === "off")
1790
+ return void 0;
1791
+ if (!supportsEffort(model) || !supportsAdaptiveThinking(model))
1792
+ return void 0;
1793
+ return ANTHROPIC_EFFORT[policy];
1794
+ }
1767
1795
  var ANTHROPIC_PROVIDER_KEY = "anthropic";
1768
1796
  var ResponseTruncatedError = class extends Error {
1769
1797
  constructor(model, maxTokens, outputTokens, toolNames) {
@@ -1788,11 +1816,42 @@ var AnthropicTransport = class _AnthropicTransport {
1788
1816
  warnIfThinkingUnclampable(policy) {
1789
1817
  if (this.warnedThinkingClamped)
1790
1818
  return;
1791
- const clamped = policy === "off" && thinkingIsMandatory(this.model) || policy === "auto" && !supportsAdaptiveThinking(this.model);
1792
- if (!clamped)
1819
+ const message = this.clampWarning(policy);
1820
+ if (!message)
1793
1821
  return;
1794
1822
  this.warnedThinkingClamped = true;
1795
- logger.warn(policy === "off" ? `AnthropicTransport: thinkingPolicy 'off' ignored \u2014 ${this.model} always thinks and rejects an explicit disable. Reasoning tokens are still billed as output; switch model if you need them gone.` : `AnthropicTransport: thinkingPolicy 'auto' ignored \u2014 ${this.model} does not support adaptive thinking, so this turn runs without reasoning. Use a Sonnet or Opus tier if the agent needs it.`);
1823
+ logger.warn(message);
1824
+ }
1825
+ /**
1826
+ * The warning for a policy this model cannot honour, or `undefined` when it can.
1827
+ *
1828
+ * An exhaustive `switch` with a `never` check rather than a condition chain: a new
1829
+ * {@link ChatThinkingPolicy} member must be considered HERE or the build fails. The condition
1830
+ * chain this replaced would have let every graded level clamp in silence, which is the one
1831
+ * outcome the type's own docs promise callers never happens.
1832
+ */
1833
+ clampWarning(policy) {
1834
+ switch (policy) {
1835
+ case void 0:
1836
+ return void 0;
1837
+ case "off":
1838
+ return thinkingIsMandatory(this.model) ? `AnthropicTransport: thinkingPolicy 'off' ignored \u2014 ${this.model} always thinks and rejects an explicit disable. Reasoning tokens are still billed as output; switch model if you need them gone.` : void 0;
1839
+ case "auto":
1840
+ return supportsAdaptiveThinking(this.model) ? void 0 : `AnthropicTransport: thinkingPolicy 'auto' ignored \u2014 ${this.model} does not support adaptive thinking, so this turn runs without reasoning. Use a Sonnet or Opus tier if the agent needs it.`;
1841
+ case "minimal":
1842
+ case "low":
1843
+ case "medium":
1844
+ case "high":
1845
+ case "max":
1846
+ if (!supportsAdaptiveThinking(this.model) || !supportsEffort(this.model)) {
1847
+ return `AnthropicTransport: thinkingPolicy '${policy}' ignored \u2014 ${this.model} predates output_config.effort, so this turn runs at its default posture. Use Opus 4.8, Sonnet 5, or Fable 5 to set reasoning depth.`;
1848
+ }
1849
+ return void 0;
1850
+ default: {
1851
+ const exhaustive = policy;
1852
+ return exhaustive;
1853
+ }
1854
+ }
1796
1855
  }
1797
1856
  constructor(config = {}) {
1798
1857
  var _a, _b, _c, _d;
@@ -1854,9 +1913,7 @@ var AnthropicTransport = class _AnthropicTransport {
1854
1913
  const useNative = responseSchema != null && supportsNativeStructuredOutput(this.model);
1855
1914
  const useForcedTool = responseSchema != null && !useNative;
1856
1915
  if (useNative) {
1857
- body.output_config = {
1858
- format: { type: "json_schema", schema: responseSchema }
1859
- };
1916
+ body.output_config = Object.assign(Object.assign({}, body.output_config), { format: { type: "json_schema", schema: responseSchema } });
1860
1917
  } else if (useForcedTool) {
1861
1918
  body.tools = [
1862
1919
  {
@@ -1880,7 +1937,7 @@ var AnthropicTransport = class _AnthropicTransport {
1880
1937
  // ── ChatTransport (multi-turn chat) ────────────────────────────────────
1881
1938
  sendChatMessage(history, userMessage, options) {
1882
1939
  return __awaiter(this, void 0, void 0, function* () {
1883
- var _a, _b, _c, _d;
1940
+ var _a, _b, _c, _d, _e;
1884
1941
  const reachableModels = /* @__PURE__ */ new Set([
1885
1942
  this.model,
1886
1943
  ...((_a = options === null || options === void 0 ? void 0 : options.fallbacks) !== null && _a !== void 0 ? _a : []).map((f) => f.model)
@@ -1888,15 +1945,16 @@ var AnthropicTransport = class _AnthropicTransport {
1888
1945
  const messages = this.toAnthropicMessages(history, userMessage, options === null || options === void 0 ? void 0 : options.attachments, reachableModels);
1889
1946
  const body = {
1890
1947
  model: this.model,
1891
- max_tokens: this.maxTokens,
1948
+ // Per-turn override wins over the instance default; see ChatRequestOptions.maxTokens.
1949
+ max_tokens: (_b = options === null || options === void 0 ? void 0 : options.maxTokens) !== null && _b !== void 0 ? _b : this.maxTokens,
1892
1950
  messages
1893
1951
  };
1894
1952
  if (options === null || options === void 0 ? void 0 : options.systemPrompt)
1895
1953
  body.system = options.systemPrompt;
1896
- if ((_b = options === null || options === void 0 ? void 0 : options.tools) === null || _b === void 0 ? void 0 : _b.length) {
1954
+ if ((_c = options === null || options === void 0 ? void 0 : options.tools) === null || _c === void 0 ? void 0 : _c.length) {
1897
1955
  body.tools = options.tools.map((t) => Object.assign({ name: t.name, description: t.description, input_schema: t.enforceSchema ? enforceAnthropicToolSchema(t.parameters, t.name) : t.parameters }, t.enforceSchema ? { strict: true } : {}));
1898
1956
  }
1899
- if ((_c = body.tools) === null || _c === void 0 ? void 0 : _c.length) {
1957
+ if ((_d = body.tools) === null || _d === void 0 ? void 0 : _d.length) {
1900
1958
  const toolChoice = toAnthropicToolChoice(options === null || options === void 0 ? void 0 : options.toolChoice);
1901
1959
  if (toolChoice)
1902
1960
  body.tool_choice = toolChoice;
@@ -1905,6 +1963,9 @@ var AnthropicTransport = class _AnthropicTransport {
1905
1963
  const thinking = anthropicThinking(this.model, options === null || options === void 0 ? void 0 : options.thinkingPolicy);
1906
1964
  if (thinking)
1907
1965
  body.thinking = thinking;
1966
+ const effort = anthropicEffort(this.model, options === null || options === void 0 ? void 0 : options.thinkingPolicy);
1967
+ if (effort)
1968
+ body.output_config = Object.assign(Object.assign({}, body.output_config), { effort });
1908
1969
  const thinkingEnabled = (thinking === null || thinking === void 0 ? void 0 : thinking.type) === "adaptive";
1909
1970
  if ((options === null || options === void 0 ? void 0 : options.temperature) != null && !rejectsSamplingParams(this.model) && !thinkingEnabled) {
1910
1971
  body.temperature = scaleTemperature(options.temperature, {
@@ -1913,11 +1974,9 @@ var AnthropicTransport = class _AnthropicTransport {
1913
1974
  });
1914
1975
  }
1915
1976
  if ((options === null || options === void 0 ? void 0 : options.responseSchema) && supportsNativeStructuredOutput(this.model)) {
1916
- body.output_config = {
1917
- format: { type: "json_schema", schema: options.responseSchema }
1918
- };
1977
+ body.output_config = Object.assign(Object.assign({}, body.output_config), { format: { type: "json_schema", schema: options.responseSchema } });
1919
1978
  }
1920
- if ((_d = options === null || options === void 0 ? void 0 : options.fallbacks) === null || _d === void 0 ? void 0 : _d.length) {
1979
+ if ((_e = options === null || options === void 0 ? void 0 : options.fallbacks) === null || _e === void 0 ? void 0 : _e.length) {
1921
1980
  body.fallbacks = options.fallbacks.map((f) => f.maxTokens != null ? { model: f.model, max_tokens: f.maxTokens } : { model: f.model });
1922
1981
  }
1923
1982
  if (options === null || options === void 0 ? void 0 : options.cachePolicy) {
@@ -1927,7 +1986,7 @@ var AnthropicTransport = class _AnthropicTransport {
1927
1986
  this.appendTailContext(body, options.tailContext);
1928
1987
  }
1929
1988
  const response = yield this.post(body, options === null || options === void 0 ? void 0 : options.signal);
1930
- return this.fromAnthropicResponse(response);
1989
+ return this.fromAnthropicResponse(response, body.max_tokens);
1931
1990
  });
1932
1991
  }
1933
1992
  /**
@@ -2020,7 +2079,7 @@ var AnthropicTransport = class _AnthropicTransport {
2020
2079
  * the payload tidy.
2021
2080
  */
2022
2081
  toAnthropicMessages(history, userMessage, attachments, reachableModels = /* @__PURE__ */ new Set([this.model])) {
2023
- var _a, _b, _c;
2082
+ var _a, _b, _c, _d;
2024
2083
  const messages = [];
2025
2084
  const pushBlock = (role, block) => {
2026
2085
  const last = messages[messages.length - 1];
@@ -2030,18 +2089,54 @@ var AnthropicTransport = class _AnthropicTransport {
2030
2089
  }
2031
2090
  messages.push({ role, content: [block] });
2032
2091
  };
2092
+ const splitAttachments = (atts) => {
2093
+ const images = [];
2094
+ const texts = [];
2095
+ for (const att of atts) {
2096
+ if (att.kind === "image") {
2097
+ images.push({
2098
+ type: "image",
2099
+ source: {
2100
+ type: "base64",
2101
+ media_type: normalizeImageMime(att.mimeType),
2102
+ data: stripImageDataUrl(att.data)
2103
+ }
2104
+ });
2105
+ } else {
2106
+ texts.push({ type: "text", text: `[File: ${att.name}]
2107
+ ${att.content}` });
2108
+ }
2109
+ }
2110
+ return { images, texts };
2111
+ };
2033
2112
  for (const msg of history) {
2034
2113
  if (msg.role === "system" || msg.role === "system-event" || msg.role === "synthetic-user" || msg.role === "compacted-summary" || msg.category === "reasoning" || msg.category === "narration" || msg.thinking)
2035
2114
  continue;
2036
2115
  if (msg.toolResult) {
2116
+ const resultImages = (_a = msg.toolResult.attachments) !== null && _a !== void 0 ? _a : [];
2037
2117
  pushBlock("user", {
2038
2118
  type: "tool_result",
2039
2119
  tool_use_id: msg.toolResult.toolCallId,
2040
- content: msg.toolResult.content
2120
+ // Bare string when there are no images — the historical shape, byte for byte.
2121
+ content: resultImages.length ? [
2122
+ // Text block only when there IS text. A handler can return an image with no
2123
+ // caption (`{ content: '', attachments: [img] }` — the mainline vision-agent
2124
+ // "just look at this" shape), and Anthropic 400s an empty text block, live and on
2125
+ // replay. Guarded like every user-turn path; the array is then image-only.
2126
+ ...msg.toolResult.content ? [{ type: "text", text: msg.toolResult.content }] : [],
2127
+ ...resultImages.map((att) => ({
2128
+ type: "image",
2129
+ source: {
2130
+ type: "base64",
2131
+ media_type: normalizeImageMime(att.mimeType),
2132
+ data: stripImageDataUrl(att.data)
2133
+ }
2134
+ }))
2135
+ ] : msg.toolResult.content
2041
2136
  });
2042
2137
  continue;
2043
2138
  }
2044
- if ((_a = msg.toolCalls) === null || _a === void 0 ? void 0 : _a.length) {
2139
+ if ((_b = msg.toolCalls) === null || _b === void 0 ? void 0 : _b.length) {
2045
2140
  for (const block of this.reasoningToReplay(msg.toolCalls[0], reachableModels)) {
2046
2141
  pushBlock("assistant", block);
2047
2142
  }
@@ -2053,30 +2148,33 @@ var AnthropicTransport = class _AnthropicTransport {
2053
2148
  type: "tool_use",
2054
2149
  id: tc.id,
2055
2150
  name: tc.name,
2056
- input: (_b = tc.args) !== null && _b !== void 0 ? _b : {}
2151
+ input: (_c = tc.args) !== null && _c !== void 0 ? _c : {}
2057
2152
  });
2058
2153
  }
2059
2154
  continue;
2060
2155
  }
2061
2156
  const role = msg.role === "user" ? "user" : "assistant";
2062
- if (role === "user" && ((_c = msg.attachments) === null || _c === void 0 ? void 0 : _c.length)) {
2063
- pushBlock(role, { type: "text", text: msg.content });
2064
- for (const att of msg.attachments) {
2065
- pushBlock(role, { type: "text", text: `[File: ${att.name}]
2066
- ${att.content}` });
2067
- }
2157
+ if (role === "user" && ((_d = msg.attachments) === null || _d === void 0 ? void 0 : _d.length)) {
2158
+ const { images, texts } = splitAttachments(msg.attachments);
2159
+ for (const image of images)
2160
+ pushBlock(role, image);
2161
+ if (msg.content)
2162
+ pushBlock(role, { type: "text", text: msg.content });
2163
+ for (const text of texts)
2164
+ pushBlock(role, text);
2068
2165
  } else if (msg.content) {
2069
2166
  pushBlock(role, { type: "text", text: msg.content });
2070
2167
  }
2071
2168
  }
2072
2169
  if (userMessage || (attachments === null || attachments === void 0 ? void 0 : attachments.length)) {
2170
+ const { images, texts } = splitAttachments(attachments !== null && attachments !== void 0 ? attachments : []);
2171
+ for (const image of images)
2172
+ pushBlock("user", image);
2073
2173
  if (userMessage) {
2074
2174
  pushBlock("user", { type: "text", text: userMessage });
2075
2175
  }
2076
- for (const att of attachments !== null && attachments !== void 0 ? attachments : []) {
2077
- pushBlock("user", { type: "text", text: `[File: ${att.name}]
2078
- ${att.content}` });
2079
- }
2176
+ for (const text of texts)
2177
+ pushBlock("user", text);
2080
2178
  }
2081
2179
  return messages;
2082
2180
  }
@@ -2183,7 +2281,7 @@ ${att.content}` });
2183
2281
  const cacheCreation = (_c = usage.cache_creation_input_tokens) !== null && _c !== void 0 ? _c : breakdown ? ((_d = breakdown.ephemeral_5m_input_tokens) !== null && _d !== void 0 ? _d : 0) + cacheCreation1h : 0;
2184
2282
  return this.logTokenUsage(model, (_e = usage.input_tokens) !== null && _e !== void 0 ? _e : 0, (_f = usage.output_tokens) !== null && _f !== void 0 ? _f : 0, cacheRead, cacheCreation, cacheCreation1h);
2185
2283
  }
2186
- fromAnthropicResponse(response) {
2284
+ fromAnthropicResponse(response, effectiveMaxTokens = this.maxTokens) {
2187
2285
  var _a, _b, _c, _d, _e, _f, _g;
2188
2286
  let inputTokens;
2189
2287
  let outputTokens;
@@ -2260,7 +2358,7 @@ ${att.content}` });
2260
2358
  toolCalls[0].providerMetadata = Object.assign(Object.assign({}, toolCalls[0].providerMetadata), { [ANTHROPIC_PROVIDER_KEY]: state });
2261
2359
  }
2262
2360
  if (response.stop_reason === "max_tokens" && toolCalls.length > 0) {
2263
- throw new ResponseTruncatedError(this.model, this.maxTokens, outputTokens, toolCalls.map((tc) => tc.name));
2361
+ throw new ResponseTruncatedError(this.model, effectiveMaxTokens, outputTokens, toolCalls.map((tc) => tc.name));
2264
2362
  }
2265
2363
  const reasoning = thoughtParts.join("") || void 0;
2266
2364
  const base = toolCalls.length > 0 ? { role: "assistant", content: textParts.join(""), reasoning, toolCalls } : { role: "assistant", content: textParts.join(""), reasoning };
@@ -2586,7 +2684,43 @@ var GEMINI_THINKING_DISABLEABLE = [
2586
2684
  "gemini-2.5-flash",
2587
2685
  "gemini-2.5-flash-lite"
2588
2686
  ];
2687
+ var GEMINI_MEDIA_RESOLUTION_TIERS = [
2688
+ "gemini-3.5-flash",
2689
+ "gemini-3.1-flash-lite",
2690
+ "gemini-3.1-pro-preview"
2691
+ ];
2692
+ var GEMINI_THINKING_LEVEL_TIERS = [
2693
+ "gemini-3.5-flash",
2694
+ "gemini-3.1-flash-lite",
2695
+ "gemini-3.1-pro-preview"
2696
+ ];
2697
+ var GEMINI_THINKING_LEVEL = {
2698
+ minimal: "minimal",
2699
+ low: "low",
2700
+ medium: "medium",
2701
+ high: "high",
2702
+ max: "high"
2703
+ };
2704
+ var GEMINI_COARSE_LEVEL_TIERS = [
2705
+ "gemini-3.5-flash",
2706
+ "gemini-3.1-flash-lite",
2707
+ "gemini-3.1-pro-preview"
2708
+ ];
2709
+ var GEMINI_COARSE_LEVEL = {
2710
+ minimal: "low",
2711
+ low: "low",
2712
+ medium: "high",
2713
+ high: "high"
2714
+ };
2715
+ var isGradedLevel = (p) => p !== void 0 && p !== "auto" && p !== "off";
2589
2716
  function geminiThinkingConfig(model, policy) {
2717
+ if (isGradedLevel(policy) && GEMINI_THINKING_LEVEL_TIERS.includes(model)) {
2718
+ const level = GEMINI_THINKING_LEVEL[policy];
2719
+ return {
2720
+ includeThoughts: true,
2721
+ thinkingLevel: GEMINI_COARSE_LEVEL_TIERS.includes(model) ? GEMINI_COARSE_LEVEL[level] : level
2722
+ };
2723
+ }
2590
2724
  if (policy === "off" && GEMINI_THINKING_DISABLEABLE.includes(model)) {
2591
2725
  return { includeThoughts: false, thinkingBudget: 0 };
2592
2726
  }
@@ -2597,6 +2731,24 @@ function geminiThinkingConfig(model, policy) {
2597
2731
  }
2598
2732
  var SKIP_SIGNATURE = "skip_thought_signature_validator";
2599
2733
  var GEMINI_PROVIDER_KEY = "gemini";
2734
+ function splitAttachmentParts(atts) {
2735
+ const imageParts = [];
2736
+ const textParts = [];
2737
+ for (const att of atts) {
2738
+ if (att.kind === "image") {
2739
+ imageParts.push({
2740
+ inlineData: {
2741
+ mimeType: normalizeImageMime(att.mimeType),
2742
+ data: stripImageDataUrl(att.data)
2743
+ }
2744
+ });
2745
+ } else {
2746
+ textParts.push({ text: `[File: ${att.name}]
2747
+ ${att.content}` });
2748
+ }
2749
+ }
2750
+ return { imageParts, textParts };
2751
+ }
2600
2752
  var MalformedFunctionCallError = class extends Error {
2601
2753
  constructor(finishMessage) {
2602
2754
  super("Gemini returned MALFORMED_FUNCTION_CALL");
@@ -2606,21 +2758,32 @@ var MalformedFunctionCallError = class extends Error {
2606
2758
  };
2607
2759
  var GeminiTransport = class _GeminiTransport {
2608
2760
  /**
2609
- * Warn once when a requested policy is silently clamped. Turning thinking off is a *cost*
2610
- * decision, so a caller who asked for it and kept paying for reasoning tokens needs to hear
2611
- * about it but only once, not per turn.
2761
+ * Warn once when a requested policy cannot be honoured on this model a *cost* decision the
2762
+ * caller made that the wire silently reversed, so they hear about it, but only once, not per
2763
+ * turn. Two shapes reach here (the Anthropic twin warns on the same two):
2612
2764
  *
2613
- * Only `'off'` can actually be denied. `'auto'` is already what an omitted budget produces on
2614
- * every model here dynamic thinking is the documented default so warning that it was
2615
- * "ignored" would be false, and latching on it would spend the one warning the genuinely
2616
- * unhonourable `'off'` needs.
2765
+ * - `'off'` on a tier that can't disable thinking only the flash tiers accept a zero thinking
2766
+ * budget; everything else keeps paying for reasoning it was told to stop. (`'auto'` is NOT this
2767
+ * case: it is already what an omitted budget produces on every model here, so warning it was
2768
+ * "ignored" would be false and would spend the one warning the genuine cases need.)
2769
+ * - A graded depth (`minimal`…`max`) on a 2.5 tier — those speak the numeric-budget dialect,
2770
+ * not the Gemini 3 `thinkingLevel` enum, so the level is dropped and the turn runs at the
2771
+ * default posture. Without this, a caller asking for `high` on `gemini-2.5-pro` and quietly
2772
+ * getting the default is left to wonder why.
2617
2773
  */
2618
2774
  warnIfThinkingUnclampable(policy) {
2619
- if (policy !== "off" || GEMINI_THINKING_DISABLEABLE.includes(this.model) || this.warnedThinkingClamped) {
2775
+ if (this.warnedThinkingClamped) {
2620
2776
  return;
2621
2777
  }
2622
- this.warnedThinkingClamped = true;
2623
- logger.warn(`GeminiTransport: thinkingPolicy 'off' ignored \u2014 ${this.model} runs its default thinking posture and takes no budget we can safely set. Reasoning tokens are still billed at the candidate rate; use a flash tier if you need them gone.`);
2778
+ if (policy === "off" && !GEMINI_THINKING_DISABLEABLE.includes(this.model)) {
2779
+ this.warnedThinkingClamped = true;
2780
+ logger.warn(`GeminiTransport: thinkingPolicy 'off' ignored \u2014 ${this.model} runs its default thinking posture and takes no budget we can safely set. Reasoning tokens are still billed at the candidate rate; use a flash tier if you need them gone.`);
2781
+ return;
2782
+ }
2783
+ if (isGradedLevel(policy) && !GEMINI_THINKING_LEVEL_TIERS.includes(this.model)) {
2784
+ this.warnedThinkingClamped = true;
2785
+ logger.warn(`GeminiTransport: thinkingPolicy '${policy}' ignored \u2014 ${this.model} takes a numeric thinking budget, not the graded 'thinkingLevel' the Gemini 3 tiers use, so this turn runs at its default reasoning posture. Use a Gemini 3 tier to set reasoning depth.`);
2786
+ }
2624
2787
  }
2625
2788
  constructor(config = {}) {
2626
2789
  var _a, _b, _c;
@@ -2677,7 +2840,7 @@ var GeminiTransport = class _GeminiTransport {
2677
2840
  // ── ChatTransport (multi-turn chat) ────────────────────────────────────
2678
2841
  sendChatMessage(history, userMessage, options) {
2679
2842
  return __awaiter(this, void 0, void 0, function* () {
2680
- var _a, _b;
2843
+ var _a, _b, _c;
2681
2844
  const contents = this.toGeminiContents(history, userMessage, options === null || options === void 0 ? void 0 : options.attachments);
2682
2845
  if (options === null || options === void 0 ? void 0 : options.tailContext) {
2683
2846
  contents.push({ role: "user", parts: [{ text: options.tailContext }] });
@@ -2695,13 +2858,33 @@ var GeminiTransport = class _GeminiTransport {
2695
2858
  const toolConfig = tools ? toGeminiToolConfig(options === null || options === void 0 ? void 0 : options.toolChoice) : void 0;
2696
2859
  this.warnIfThinkingUnclampable(options === null || options === void 0 ? void 0 : options.thinkingPolicy);
2697
2860
  const generationConfig = { thinkingConfig: geminiThinkingConfig(this.model, options === null || options === void 0 ? void 0 : options.thinkingPolicy) };
2861
+ if ((options === null || options === void 0 ? void 0 : options.maxTokens) != null) {
2862
+ generationConfig.maxOutputTokens = options.maxTokens;
2863
+ }
2864
+ const resolutionHint = [
2865
+ ...(_b = options === null || options === void 0 ? void 0 : options.attachments) !== null && _b !== void 0 ? _b : [],
2866
+ ...history.flatMap((m) => {
2867
+ var _a2;
2868
+ return (_a2 = m.attachments) !== null && _a2 !== void 0 ? _a2 : [];
2869
+ }),
2870
+ // Tool-result images too: `ChatToolResult.attachments` is typed ChatImageAttachment[], so
2871
+ // `detail` is offerable there, and a render handed back by a tool is the whole point of
2872
+ // the tool-result image path. Omitting this source made the field silently inert there.
2873
+ ...history.flatMap((m) => {
2874
+ var _a2, _b2;
2875
+ return (_b2 = (_a2 = m.toolResult) === null || _a2 === void 0 ? void 0 : _a2.attachments) !== null && _b2 !== void 0 ? _b2 : [];
2876
+ })
2877
+ ].find((att) => att.kind === "image" && att.detail);
2878
+ if ((resolutionHint === null || resolutionHint === void 0 ? void 0 : resolutionHint.kind) === "image" && resolutionHint.detail && GEMINI_MEDIA_RESOLUTION_TIERS.includes(this.model)) {
2879
+ generationConfig.mediaResolution = `MEDIA_RESOLUTION_${resolutionHint.detail.toUpperCase()}`;
2880
+ }
2698
2881
  if ((options === null || options === void 0 ? void 0 : options.temperature) != null) {
2699
2882
  generationConfig.temperature = scaleTemperature(options.temperature, {
2700
2883
  defaultTemp: GEMINI_DEFAULT_TEMPERATURE,
2701
2884
  maxTemp: GEMINI_MAX_TEMPERATURE
2702
2885
  });
2703
2886
  }
2704
- const offeredToolNames = new Set(((_b = options === null || options === void 0 ? void 0 : options.tools) !== null && _b !== void 0 ? _b : []).map((t) => t.name));
2887
+ const offeredToolNames = new Set(((_c = options === null || options === void 0 ? void 0 : options.tools) !== null && _c !== void 0 ? _c : []).map((t) => t.name));
2705
2888
  const schemaCoexistsWithTools = this.model.startsWith("gemini-3");
2706
2889
  const applyResponseSchema = (options === null || options === void 0 ? void 0 : options.responseSchema) != null && (!tools || schemaCoexistsWithTools);
2707
2890
  const response = yield this.post(Object.assign({
@@ -2712,7 +2895,7 @@ var GeminiTransport = class _GeminiTransport {
2712
2895
  toolConfig,
2713
2896
  generationConfig
2714
2897
  }, applyResponseSchema ? { responseSchema: toGeminiSchema(options.responseSchema) } : {}), options === null || options === void 0 ? void 0 : options.signal);
2715
- return this.fromGeminiResponse(response, offeredToolNames);
2898
+ return this.fromGeminiResponse(response, offeredToolNames, generationConfig.maxOutputTokens);
2716
2899
  });
2717
2900
  }
2718
2901
  /**
@@ -2740,7 +2923,7 @@ var GeminiTransport = class _GeminiTransport {
2740
2923
  return costUsd;
2741
2924
  }
2742
2925
  toGeminiContents(history, userMessage, attachments) {
2743
- var _a, _b, _c;
2926
+ var _a, _b, _c, _d;
2744
2927
  const contents = [];
2745
2928
  const toolCallNameById = /* @__PURE__ */ new Map();
2746
2929
  for (const msg of history) {
@@ -2760,7 +2943,16 @@ var GeminiTransport = class _GeminiTransport {
2760
2943
  }
2761
2944
  ]
2762
2945
  });
2763
- } else if ((_b = msg.toolCalls) === null || _b === void 0 ? void 0 : _b.length) {
2946
+ const resultImages = (_b = msg.toolResult.attachments) !== null && _b !== void 0 ? _b : [];
2947
+ if (resultImages.length) {
2948
+ contents.push({
2949
+ role: "user",
2950
+ parts: resultImages.map((att) => ({
2951
+ inlineData: { mimeType: normalizeImageMime(att.mimeType), data: att.data }
2952
+ }))
2953
+ });
2954
+ }
2955
+ } else if ((_c = msg.toolCalls) === null || _c === void 0 ? void 0 : _c.length) {
2764
2956
  for (const tc of msg.toolCalls) {
2765
2957
  toolCallNameById.set(tc.id, tc.name);
2766
2958
  }
@@ -2779,22 +2971,22 @@ var GeminiTransport = class _GeminiTransport {
2779
2971
  return part;
2780
2972
  })
2781
2973
  });
2782
- } else if (role === "user" && ((_c = msg.attachments) === null || _c === void 0 ? void 0 : _c.length)) {
2783
- const msgAttachmentParts = msg.attachments.map((att) => ({
2784
- text: `[File: ${att.name}]
2785
- ${att.content}`
2786
- }));
2787
- contents.push({ role: "user", parts: [{ text: msg.content }, ...msgAttachmentParts] });
2974
+ } else if (role === "user" && ((_d = msg.attachments) === null || _d === void 0 ? void 0 : _d.length)) {
2975
+ const { imageParts: imageParts2, textParts: textParts2 } = splitAttachmentParts(msg.attachments);
2976
+ contents.push({
2977
+ role: "user",
2978
+ parts: [...imageParts2, ...msg.content ? [{ text: msg.content }] : [], ...textParts2]
2979
+ });
2788
2980
  } else {
2789
2981
  contents.push({ role, parts: [{ text: msg.content }] });
2790
2982
  }
2791
2983
  }
2792
- const attachmentParts = (attachments !== null && attachments !== void 0 ? attachments : []).map((att) => ({
2793
- text: `[File: ${att.name}]
2794
- ${att.content}`
2795
- }));
2796
- if (userMessage || attachmentParts.length) {
2797
- contents.push({ role: "user", parts: [{ text: userMessage }, ...attachmentParts] });
2984
+ const { imageParts, textParts } = splitAttachmentParts(attachments !== null && attachments !== void 0 ? attachments : []);
2985
+ if (userMessage || imageParts.length || textParts.length) {
2986
+ contents.push({
2987
+ role: "user",
2988
+ parts: [...imageParts, ...userMessage ? [{ text: userMessage }] : [], ...textParts]
2989
+ });
2798
2990
  }
2799
2991
  return contents;
2800
2992
  }
@@ -2831,7 +3023,7 @@ ${att.content}`
2831
3023
  cost
2832
3024
  };
2833
3025
  }
2834
- fromGeminiResponse(response, offeredToolNames = /* @__PURE__ */ new Set()) {
3026
+ fromGeminiResponse(response, offeredToolNames = /* @__PURE__ */ new Set(), effectiveMaxTokens) {
2835
3027
  var _a, _b, _c, _d;
2836
3028
  const { inputTokens, outputTokens, thoughtsTokens, cacheReadTokens, cost } = this.usageFromGemini(response.usageMetadata);
2837
3029
  const candidates = response === null || response === void 0 ? void 0 : response.candidates;
@@ -2878,7 +3070,7 @@ ${att.content}`
2878
3070
  textParts: textParts.length
2879
3071
  });
2880
3072
  }
2881
- this.guardMaxTokensTruncation(finishReason, toolCalls, narration, outputTokens);
3073
+ this.guardMaxTokensTruncation(finishReason, toolCalls, narration, outputTokens, effectiveMaxTokens);
2882
3074
  if (inputTokens != null)
2883
3075
  base.inputTokens = inputTokens;
2884
3076
  if (outputTokens != null)
@@ -2916,12 +3108,12 @@ ${att.content}`
2916
3108
  * `responseMeta.finishReason`, for the caller to decide. Usage/cost is logged
2917
3109
  * before this check runs, so the spent tokens stay accounted for.
2918
3110
  */
2919
- guardMaxTokensTruncation(finishReason, toolCalls, narration, outputTokens) {
3111
+ guardMaxTokensTruncation(finishReason, toolCalls, narration, outputTokens, effectiveMaxTokens) {
2920
3112
  if (finishReason !== "MAX_TOKENS")
2921
3113
  return;
2922
3114
  if (toolCalls.length === 0 && narration.trim() !== "")
2923
3115
  return;
2924
- throw new ResponseTruncatedError(this.model, void 0, outputTokens, toolCalls.map((tc) => tc.name));
3116
+ throw new ResponseTruncatedError(this.model, effectiveMaxTokens, outputTokens, toolCalls.map((tc) => tc.name));
2925
3117
  }
2926
3118
  /**
2927
3119
  * Log the full shape of a blank or non-STOP response so its cause is legible
@@ -3482,11 +3674,12 @@ function triggerReason(trigger) {
3482
3674
  return "superseded";
3483
3675
  }
3484
3676
  }
3485
- function condenseStub(target, tool, trigger, origLen, restorable) {
3677
+ function condenseStub(target, tool, trigger, origLen, restorable, imageCount = 0) {
3486
3678
  const what = target === "args" ? "args" : "result";
3487
3679
  const key = trigger.kind === "superseded" ? ` ${trigger.by}` : "";
3488
3680
  const restore = restorable ? "; re-call to restore" : "";
3489
- return `[${tool}${key} \u2014 ${what} elided, ~${origLen} chars (${triggerReason(trigger)})${restore}]`;
3681
+ const images = imageCount > 0 ? ` + ${imageCount} image${imageCount === 1 ? "" : "s"}` : "";
3682
+ return `[${tool}${key} \u2014 ${what} elided, ~${origLen} chars${images} (${triggerReason(trigger)})${restore}]`;
3490
3683
  }
3491
3684
  function triggerLabel(trigger) {
3492
3685
  switch (trigger.kind) {
@@ -3585,12 +3778,14 @@ function applyCondensation(history, policies, ctx, onCondensed) {
3585
3778
  }
3586
3779
  if (msg.toolResult) {
3587
3780
  const entry = policies.get(msg.toolResult.toolCallId);
3588
- const origLen = msg.toolResult.content?.length ?? 0;
3781
+ const textLen = msg.toolResult.content?.length ?? 0;
3782
+ const images = msg.toolResult.attachments ?? [];
3783
+ const origLen = textLen + images.reduce((n, att) => n + (att.data?.length ?? 0), 0);
3589
3784
  const fired = entry?.policy.response ? firstFired(msg.toolResult.toolCallId, entry) : void 0;
3590
3785
  if (entry && fired && origLen >= CONDENSE_MIN_CHARS) {
3591
3786
  const tool = nameById.get(msg.toolResult.toolCallId) ?? msg.toolResult.toolCallId;
3592
3787
  const result = entry.policy.response;
3593
- const content = result === "drop" || result === "pointer" ? condenseStub("response", tool, fired, origLen, result === "pointer") : result.replaceWith;
3788
+ const content = result === "drop" || result === "pointer" ? condenseStub("response", tool, fired, textLen, result === "pointer", images.length) : result.replaceWith;
3594
3789
  if (!entry.reportedResponse) {
3595
3790
  entry.reportedResponse = true;
3596
3791
  onCondensed({
@@ -3603,7 +3798,8 @@ function applyCondensation(history, policies, ctx, onCondensed) {
3603
3798
  tokensSaved: estimateTokensSaved(origLen, content.length)
3604
3799
  });
3605
3800
  }
3606
- return { ...msg, toolResult: { ...msg.toolResult, content } };
3801
+ const { attachments: _elided, ...restOfResult } = msg.toolResult;
3802
+ return { ...msg, toolResult: { ...restOfResult, content } };
3607
3803
  }
3608
3804
  }
3609
3805
  return msg;
@@ -3616,9 +3812,10 @@ function maskToolPayload(msg) {
3616
3812
  return { ...msg, toolCalls: msg.toolCalls.map((tc) => ({ ...tc, args: {} })) };
3617
3813
  }
3618
3814
  if (msg.toolResult) {
3815
+ const { attachments: _masked, ...restOfResult } = msg.toolResult;
3619
3816
  return {
3620
3817
  ...msg,
3621
- toolResult: { ...msg.toolResult, content: "[other agent's tool result omitted]" }
3818
+ toolResult: { ...restOfResult, content: "[other agent's tool result omitted]" }
3622
3819
  };
3623
3820
  }
3624
3821
  return msg;
@@ -3772,6 +3969,15 @@ function accumulate(messages, into) {
3772
3969
  var TOOL_FOLD_SYMBOL = Symbol("toolFold");
3773
3970
 
3774
3971
  // src/components/chat-driver/chat-driver.ts
3972
+ var toolResultImages = (result) => {
3973
+ if (!result || typeof result !== "object") return void 0;
3974
+ const candidate = result.attachments;
3975
+ if (!Array.isArray(candidate) || candidate.length === 0) return void 0;
3976
+ const allImages = candidate.every(
3977
+ (att) => !!att && typeof att === "object" && att.kind === "image"
3978
+ );
3979
+ return allImages ? candidate : void 0;
3980
+ };
3775
3981
  var providerRefusedDetailOf = (e) => ({
3776
3982
  vendorLabel: e.vendorLabel,
3777
3983
  kind: e.kind,
@@ -5902,10 +6108,12 @@ ${tailBody}
5902
6108
  const capturedTrace = () => traceCapture.traces.length ? traceCapture.traces.flat() : void 0;
5903
6109
  try {
5904
6110
  const result = await handler(tc.args, this.buildHandlerContext(tc.id, traceCapture));
5905
- const content = typeof result === "string" ? result : JSON.stringify(result);
6111
+ const resultImages = toolResultImages(result);
6112
+ const content = resultImages ? typeof result.content === "string" ? result.content : JSON.stringify({ ...result, attachments: void 0 }) : typeof result === "string" ? result : JSON.stringify(result);
5906
6113
  executedById.set(tc.id, {
5907
6114
  toolCallId: tc.id,
5908
6115
  content,
6116
+ ...resultImages ? { attachments: resultImages } : {},
5909
6117
  // Concatenated when a handler invoked several children, so none is lost.
5910
6118
  // Stays `undefined` when nothing was captured — readers key off presence,
5911
6119
  // and an empty array is a different claim from "no sub-agent ran".
@@ -5954,7 +6162,14 @@ RECOVERY: this tool failed once \u2014 you may retry it, or take a different val
5954
6162
  this.appendToHistory({
5955
6163
  role: "tool",
5956
6164
  content: "",
5957
- toolResult: { toolCallId: r.toolCallId, content: r.content }
6165
+ toolResult: {
6166
+ toolCallId: r.toolCallId,
6167
+ content: r.content,
6168
+ // Images DO persist, unlike the sub-agent trace above: they are the payload
6169
+ // the model must see on every replay of this turn, not a one-off UI artefact.
6170
+ // `condenseWhen` is what sheds them once they are spent — see condense-history.
6171
+ ...r.attachments ? { attachments: r.attachments } : {}
6172
+ }
5958
6173
  });
5959
6174
  }
5960
6175
  }