@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.
@@ -1221,6 +1221,14 @@ function vendorOfModel(modelId) {
1221
1221
  return void 0;
1222
1222
  }
1223
1223
 
1224
+ // ../../foundation-ai/dist/esm/utils/image-mime.js
1225
+ function normalizeImageMime(mimeType) {
1226
+ return mimeType === "image/jpg" ? "image/jpeg" : mimeType;
1227
+ }
1228
+ function stripImageDataUrl(data) {
1229
+ return data.replace(/^data:[^,]*;base64,/, "");
1230
+ }
1231
+
1224
1232
  // ../../foundation-ai/dist/esm/utils/temperature.js
1225
1233
  var DEFAULT_ANCHOR = 0.5;
1226
1234
  function scaleTemperature(normalized, { defaultTemp, maxTemp }) {
@@ -1829,8 +1837,28 @@ function anthropicThinking(model, policy) {
1829
1837
  if (policy === "auto") {
1830
1838
  return supportsAdaptiveThinking(model) ? adaptive : void 0;
1831
1839
  }
1840
+ if (policy !== void 0) {
1841
+ return supportsAdaptiveThinking(model) ? adaptive : void 0;
1842
+ }
1832
1843
  return defaultsToThinking ? adaptive : void 0;
1833
1844
  }
1845
+ function supportsEffort(model) {
1846
+ return model === "claude-fable-5" || model === "claude-opus-4-8" || model === "claude-sonnet-5";
1847
+ }
1848
+ var ANTHROPIC_EFFORT = {
1849
+ minimal: "low",
1850
+ low: "low",
1851
+ medium: "medium",
1852
+ high: "high",
1853
+ max: "max"
1854
+ };
1855
+ function anthropicEffort(model, policy) {
1856
+ if (policy === void 0 || policy === "auto" || policy === "off")
1857
+ return void 0;
1858
+ if (!supportsEffort(model) || !supportsAdaptiveThinking(model))
1859
+ return void 0;
1860
+ return ANTHROPIC_EFFORT[policy];
1861
+ }
1834
1862
  var ANTHROPIC_PROVIDER_KEY = "anthropic";
1835
1863
  var ResponseTruncatedError = class extends Error {
1836
1864
  constructor(model, maxTokens, outputTokens, toolNames) {
@@ -1855,11 +1883,42 @@ var AnthropicTransport = class _AnthropicTransport {
1855
1883
  warnIfThinkingUnclampable(policy) {
1856
1884
  if (this.warnedThinkingClamped)
1857
1885
  return;
1858
- const clamped = policy === "off" && thinkingIsMandatory(this.model) || policy === "auto" && !supportsAdaptiveThinking(this.model);
1859
- if (!clamped)
1886
+ const message = this.clampWarning(policy);
1887
+ if (!message)
1860
1888
  return;
1861
1889
  this.warnedThinkingClamped = true;
1862
- 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.`);
1890
+ logger.warn(message);
1891
+ }
1892
+ /**
1893
+ * The warning for a policy this model cannot honour, or `undefined` when it can.
1894
+ *
1895
+ * An exhaustive `switch` with a `never` check rather than a condition chain: a new
1896
+ * {@link ChatThinkingPolicy} member must be considered HERE or the build fails. The condition
1897
+ * chain this replaced would have let every graded level clamp in silence, which is the one
1898
+ * outcome the type's own docs promise callers never happens.
1899
+ */
1900
+ clampWarning(policy) {
1901
+ switch (policy) {
1902
+ case void 0:
1903
+ return void 0;
1904
+ case "off":
1905
+ 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;
1906
+ case "auto":
1907
+ 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.`;
1908
+ case "minimal":
1909
+ case "low":
1910
+ case "medium":
1911
+ case "high":
1912
+ case "max":
1913
+ if (!supportsAdaptiveThinking(this.model) || !supportsEffort(this.model)) {
1914
+ 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.`;
1915
+ }
1916
+ return void 0;
1917
+ default: {
1918
+ const exhaustive = policy;
1919
+ return exhaustive;
1920
+ }
1921
+ }
1863
1922
  }
1864
1923
  constructor(config = {}) {
1865
1924
  var _a, _b, _c, _d;
@@ -1921,9 +1980,7 @@ var AnthropicTransport = class _AnthropicTransport {
1921
1980
  const useNative = responseSchema != null && supportsNativeStructuredOutput(this.model);
1922
1981
  const useForcedTool = responseSchema != null && !useNative;
1923
1982
  if (useNative) {
1924
- body.output_config = {
1925
- format: { type: "json_schema", schema: responseSchema }
1926
- };
1983
+ body.output_config = Object.assign(Object.assign({}, body.output_config), { format: { type: "json_schema", schema: responseSchema } });
1927
1984
  } else if (useForcedTool) {
1928
1985
  body.tools = [
1929
1986
  {
@@ -1947,7 +2004,7 @@ var AnthropicTransport = class _AnthropicTransport {
1947
2004
  // ── ChatTransport (multi-turn chat) ────────────────────────────────────
1948
2005
  sendChatMessage(history, userMessage, options) {
1949
2006
  return __awaiter(this, void 0, void 0, function* () {
1950
- var _a, _b, _c, _d;
2007
+ var _a, _b, _c, _d, _e;
1951
2008
  const reachableModels = /* @__PURE__ */ new Set([
1952
2009
  this.model,
1953
2010
  ...((_a = options === null || options === void 0 ? void 0 : options.fallbacks) !== null && _a !== void 0 ? _a : []).map((f) => f.model)
@@ -1955,15 +2012,16 @@ var AnthropicTransport = class _AnthropicTransport {
1955
2012
  const messages = this.toAnthropicMessages(history, userMessage, options === null || options === void 0 ? void 0 : options.attachments, reachableModels);
1956
2013
  const body = {
1957
2014
  model: this.model,
1958
- max_tokens: this.maxTokens,
2015
+ // Per-turn override wins over the instance default; see ChatRequestOptions.maxTokens.
2016
+ max_tokens: (_b = options === null || options === void 0 ? void 0 : options.maxTokens) !== null && _b !== void 0 ? _b : this.maxTokens,
1959
2017
  messages
1960
2018
  };
1961
2019
  if (options === null || options === void 0 ? void 0 : options.systemPrompt)
1962
2020
  body.system = options.systemPrompt;
1963
- if ((_b = options === null || options === void 0 ? void 0 : options.tools) === null || _b === void 0 ? void 0 : _b.length) {
2021
+ if ((_c = options === null || options === void 0 ? void 0 : options.tools) === null || _c === void 0 ? void 0 : _c.length) {
1964
2022
  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 } : {}));
1965
2023
  }
1966
- if ((_c = body.tools) === null || _c === void 0 ? void 0 : _c.length) {
2024
+ if ((_d = body.tools) === null || _d === void 0 ? void 0 : _d.length) {
1967
2025
  const toolChoice = toAnthropicToolChoice(options === null || options === void 0 ? void 0 : options.toolChoice);
1968
2026
  if (toolChoice)
1969
2027
  body.tool_choice = toolChoice;
@@ -1972,6 +2030,9 @@ var AnthropicTransport = class _AnthropicTransport {
1972
2030
  const thinking = anthropicThinking(this.model, options === null || options === void 0 ? void 0 : options.thinkingPolicy);
1973
2031
  if (thinking)
1974
2032
  body.thinking = thinking;
2033
+ const effort = anthropicEffort(this.model, options === null || options === void 0 ? void 0 : options.thinkingPolicy);
2034
+ if (effort)
2035
+ body.output_config = Object.assign(Object.assign({}, body.output_config), { effort });
1975
2036
  const thinkingEnabled = (thinking === null || thinking === void 0 ? void 0 : thinking.type) === "adaptive";
1976
2037
  if ((options === null || options === void 0 ? void 0 : options.temperature) != null && !rejectsSamplingParams(this.model) && !thinkingEnabled) {
1977
2038
  body.temperature = scaleTemperature(options.temperature, {
@@ -1980,11 +2041,9 @@ var AnthropicTransport = class _AnthropicTransport {
1980
2041
  });
1981
2042
  }
1982
2043
  if ((options === null || options === void 0 ? void 0 : options.responseSchema) && supportsNativeStructuredOutput(this.model)) {
1983
- body.output_config = {
1984
- format: { type: "json_schema", schema: options.responseSchema }
1985
- };
2044
+ body.output_config = Object.assign(Object.assign({}, body.output_config), { format: { type: "json_schema", schema: options.responseSchema } });
1986
2045
  }
1987
- if ((_d = options === null || options === void 0 ? void 0 : options.fallbacks) === null || _d === void 0 ? void 0 : _d.length) {
2046
+ if ((_e = options === null || options === void 0 ? void 0 : options.fallbacks) === null || _e === void 0 ? void 0 : _e.length) {
1988
2047
  body.fallbacks = options.fallbacks.map((f) => f.maxTokens != null ? { model: f.model, max_tokens: f.maxTokens } : { model: f.model });
1989
2048
  }
1990
2049
  if (options === null || options === void 0 ? void 0 : options.cachePolicy) {
@@ -1994,7 +2053,7 @@ var AnthropicTransport = class _AnthropicTransport {
1994
2053
  this.appendTailContext(body, options.tailContext);
1995
2054
  }
1996
2055
  const response = yield this.post(body, options === null || options === void 0 ? void 0 : options.signal);
1997
- return this.fromAnthropicResponse(response);
2056
+ return this.fromAnthropicResponse(response, body.max_tokens);
1998
2057
  });
1999
2058
  }
2000
2059
  /**
@@ -2087,7 +2146,7 @@ var AnthropicTransport = class _AnthropicTransport {
2087
2146
  * the payload tidy.
2088
2147
  */
2089
2148
  toAnthropicMessages(history, userMessage, attachments, reachableModels = /* @__PURE__ */ new Set([this.model])) {
2090
- var _a, _b, _c;
2149
+ var _a, _b, _c, _d;
2091
2150
  const messages = [];
2092
2151
  const pushBlock = (role, block) => {
2093
2152
  const last = messages[messages.length - 1];
@@ -2097,18 +2156,54 @@ var AnthropicTransport = class _AnthropicTransport {
2097
2156
  }
2098
2157
  messages.push({ role, content: [block] });
2099
2158
  };
2159
+ const splitAttachments = (atts) => {
2160
+ const images = [];
2161
+ const texts = [];
2162
+ for (const att of atts) {
2163
+ if (att.kind === "image") {
2164
+ images.push({
2165
+ type: "image",
2166
+ source: {
2167
+ type: "base64",
2168
+ media_type: normalizeImageMime(att.mimeType),
2169
+ data: stripImageDataUrl(att.data)
2170
+ }
2171
+ });
2172
+ } else {
2173
+ texts.push({ type: "text", text: `[File: ${att.name}]
2174
+ ${att.content}` });
2175
+ }
2176
+ }
2177
+ return { images, texts };
2178
+ };
2100
2179
  for (const msg of history) {
2101
2180
  if (msg.role === "system" || msg.role === "system-event" || msg.role === "synthetic-user" || msg.role === "compacted-summary" || msg.category === "reasoning" || msg.category === "narration" || msg.thinking)
2102
2181
  continue;
2103
2182
  if (msg.toolResult) {
2183
+ const resultImages = (_a = msg.toolResult.attachments) !== null && _a !== void 0 ? _a : [];
2104
2184
  pushBlock("user", {
2105
2185
  type: "tool_result",
2106
2186
  tool_use_id: msg.toolResult.toolCallId,
2107
- content: msg.toolResult.content
2187
+ // Bare string when there are no images — the historical shape, byte for byte.
2188
+ content: resultImages.length ? [
2189
+ // Text block only when there IS text. A handler can return an image with no
2190
+ // caption (`{ content: '', attachments: [img] }` — the mainline vision-agent
2191
+ // "just look at this" shape), and Anthropic 400s an empty text block, live and on
2192
+ // replay. Guarded like every user-turn path; the array is then image-only.
2193
+ ...msg.toolResult.content ? [{ type: "text", text: msg.toolResult.content }] : [],
2194
+ ...resultImages.map((att) => ({
2195
+ type: "image",
2196
+ source: {
2197
+ type: "base64",
2198
+ media_type: normalizeImageMime(att.mimeType),
2199
+ data: stripImageDataUrl(att.data)
2200
+ }
2201
+ }))
2202
+ ] : msg.toolResult.content
2108
2203
  });
2109
2204
  continue;
2110
2205
  }
2111
- if ((_a = msg.toolCalls) === null || _a === void 0 ? void 0 : _a.length) {
2206
+ if ((_b = msg.toolCalls) === null || _b === void 0 ? void 0 : _b.length) {
2112
2207
  for (const block of this.reasoningToReplay(msg.toolCalls[0], reachableModels)) {
2113
2208
  pushBlock("assistant", block);
2114
2209
  }
@@ -2120,30 +2215,33 @@ var AnthropicTransport = class _AnthropicTransport {
2120
2215
  type: "tool_use",
2121
2216
  id: tc.id,
2122
2217
  name: tc.name,
2123
- input: (_b = tc.args) !== null && _b !== void 0 ? _b : {}
2218
+ input: (_c = tc.args) !== null && _c !== void 0 ? _c : {}
2124
2219
  });
2125
2220
  }
2126
2221
  continue;
2127
2222
  }
2128
2223
  const role = msg.role === "user" ? "user" : "assistant";
2129
- if (role === "user" && ((_c = msg.attachments) === null || _c === void 0 ? void 0 : _c.length)) {
2130
- pushBlock(role, { type: "text", text: msg.content });
2131
- for (const att of msg.attachments) {
2132
- pushBlock(role, { type: "text", text: `[File: ${att.name}]
2133
- ${att.content}` });
2134
- }
2224
+ if (role === "user" && ((_d = msg.attachments) === null || _d === void 0 ? void 0 : _d.length)) {
2225
+ const { images, texts } = splitAttachments(msg.attachments);
2226
+ for (const image of images)
2227
+ pushBlock(role, image);
2228
+ if (msg.content)
2229
+ pushBlock(role, { type: "text", text: msg.content });
2230
+ for (const text of texts)
2231
+ pushBlock(role, text);
2135
2232
  } else if (msg.content) {
2136
2233
  pushBlock(role, { type: "text", text: msg.content });
2137
2234
  }
2138
2235
  }
2139
2236
  if (userMessage || (attachments === null || attachments === void 0 ? void 0 : attachments.length)) {
2237
+ const { images, texts } = splitAttachments(attachments !== null && attachments !== void 0 ? attachments : []);
2238
+ for (const image of images)
2239
+ pushBlock("user", image);
2140
2240
  if (userMessage) {
2141
2241
  pushBlock("user", { type: "text", text: userMessage });
2142
2242
  }
2143
- for (const att of attachments !== null && attachments !== void 0 ? attachments : []) {
2144
- pushBlock("user", { type: "text", text: `[File: ${att.name}]
2145
- ${att.content}` });
2146
- }
2243
+ for (const text of texts)
2244
+ pushBlock("user", text);
2147
2245
  }
2148
2246
  return messages;
2149
2247
  }
@@ -2250,7 +2348,7 @@ ${att.content}` });
2250
2348
  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;
2251
2349
  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);
2252
2350
  }
2253
- fromAnthropicResponse(response) {
2351
+ fromAnthropicResponse(response, effectiveMaxTokens = this.maxTokens) {
2254
2352
  var _a, _b, _c, _d, _e, _f, _g;
2255
2353
  let inputTokens;
2256
2354
  let outputTokens;
@@ -2327,7 +2425,7 @@ ${att.content}` });
2327
2425
  toolCalls[0].providerMetadata = Object.assign(Object.assign({}, toolCalls[0].providerMetadata), { [ANTHROPIC_PROVIDER_KEY]: state });
2328
2426
  }
2329
2427
  if (response.stop_reason === "max_tokens" && toolCalls.length > 0) {
2330
- throw new ResponseTruncatedError(this.model, this.maxTokens, outputTokens, toolCalls.map((tc) => tc.name));
2428
+ throw new ResponseTruncatedError(this.model, effectiveMaxTokens, outputTokens, toolCalls.map((tc) => tc.name));
2331
2429
  }
2332
2430
  const reasoning = thoughtParts.join("") || void 0;
2333
2431
  const base = toolCalls.length > 0 ? { role: "assistant", content: textParts.join(""), reasoning, toolCalls } : { role: "assistant", content: textParts.join(""), reasoning };
@@ -2653,7 +2751,43 @@ var GEMINI_THINKING_DISABLEABLE = [
2653
2751
  "gemini-2.5-flash",
2654
2752
  "gemini-2.5-flash-lite"
2655
2753
  ];
2754
+ var GEMINI_MEDIA_RESOLUTION_TIERS = [
2755
+ "gemini-3.5-flash",
2756
+ "gemini-3.1-flash-lite",
2757
+ "gemini-3.1-pro-preview"
2758
+ ];
2759
+ var GEMINI_THINKING_LEVEL_TIERS = [
2760
+ "gemini-3.5-flash",
2761
+ "gemini-3.1-flash-lite",
2762
+ "gemini-3.1-pro-preview"
2763
+ ];
2764
+ var GEMINI_THINKING_LEVEL = {
2765
+ minimal: "minimal",
2766
+ low: "low",
2767
+ medium: "medium",
2768
+ high: "high",
2769
+ max: "high"
2770
+ };
2771
+ var GEMINI_COARSE_LEVEL_TIERS = [
2772
+ "gemini-3.5-flash",
2773
+ "gemini-3.1-flash-lite",
2774
+ "gemini-3.1-pro-preview"
2775
+ ];
2776
+ var GEMINI_COARSE_LEVEL = {
2777
+ minimal: "low",
2778
+ low: "low",
2779
+ medium: "high",
2780
+ high: "high"
2781
+ };
2782
+ var isGradedLevel = (p) => p !== void 0 && p !== "auto" && p !== "off";
2656
2783
  function geminiThinkingConfig(model, policy) {
2784
+ if (isGradedLevel(policy) && GEMINI_THINKING_LEVEL_TIERS.includes(model)) {
2785
+ const level = GEMINI_THINKING_LEVEL[policy];
2786
+ return {
2787
+ includeThoughts: true,
2788
+ thinkingLevel: GEMINI_COARSE_LEVEL_TIERS.includes(model) ? GEMINI_COARSE_LEVEL[level] : level
2789
+ };
2790
+ }
2657
2791
  if (policy === "off" && GEMINI_THINKING_DISABLEABLE.includes(model)) {
2658
2792
  return { includeThoughts: false, thinkingBudget: 0 };
2659
2793
  }
@@ -2664,6 +2798,24 @@ function geminiThinkingConfig(model, policy) {
2664
2798
  }
2665
2799
  var SKIP_SIGNATURE = "skip_thought_signature_validator";
2666
2800
  var GEMINI_PROVIDER_KEY = "gemini";
2801
+ function splitAttachmentParts(atts) {
2802
+ const imageParts = [];
2803
+ const textParts = [];
2804
+ for (const att of atts) {
2805
+ if (att.kind === "image") {
2806
+ imageParts.push({
2807
+ inlineData: {
2808
+ mimeType: normalizeImageMime(att.mimeType),
2809
+ data: stripImageDataUrl(att.data)
2810
+ }
2811
+ });
2812
+ } else {
2813
+ textParts.push({ text: `[File: ${att.name}]
2814
+ ${att.content}` });
2815
+ }
2816
+ }
2817
+ return { imageParts, textParts };
2818
+ }
2667
2819
  var MalformedFunctionCallError = class extends Error {
2668
2820
  constructor(finishMessage) {
2669
2821
  super("Gemini returned MALFORMED_FUNCTION_CALL");
@@ -2673,21 +2825,32 @@ var MalformedFunctionCallError = class extends Error {
2673
2825
  };
2674
2826
  var GeminiTransport = class _GeminiTransport {
2675
2827
  /**
2676
- * Warn once when a requested policy is silently clamped. Turning thinking off is a *cost*
2677
- * decision, so a caller who asked for it and kept paying for reasoning tokens needs to hear
2678
- * about it but only once, not per turn.
2828
+ * Warn once when a requested policy cannot be honoured on this model a *cost* decision the
2829
+ * caller made that the wire silently reversed, so they hear about it, but only once, not per
2830
+ * turn. Two shapes reach here (the Anthropic twin warns on the same two):
2679
2831
  *
2680
- * Only `'off'` can actually be denied. `'auto'` is already what an omitted budget produces on
2681
- * every model here dynamic thinking is the documented default so warning that it was
2682
- * "ignored" would be false, and latching on it would spend the one warning the genuinely
2683
- * unhonourable `'off'` needs.
2832
+ * - `'off'` on a tier that can't disable thinking only the flash tiers accept a zero thinking
2833
+ * budget; everything else keeps paying for reasoning it was told to stop. (`'auto'` is NOT this
2834
+ * case: it is already what an omitted budget produces on every model here, so warning it was
2835
+ * "ignored" would be false and would spend the one warning the genuine cases need.)
2836
+ * - A graded depth (`minimal`…`max`) on a 2.5 tier — those speak the numeric-budget dialect,
2837
+ * not the Gemini 3 `thinkingLevel` enum, so the level is dropped and the turn runs at the
2838
+ * default posture. Without this, a caller asking for `high` on `gemini-2.5-pro` and quietly
2839
+ * getting the default is left to wonder why.
2684
2840
  */
2685
2841
  warnIfThinkingUnclampable(policy) {
2686
- if (policy !== "off" || GEMINI_THINKING_DISABLEABLE.includes(this.model) || this.warnedThinkingClamped) {
2842
+ if (this.warnedThinkingClamped) {
2687
2843
  return;
2688
2844
  }
2689
- this.warnedThinkingClamped = true;
2690
- 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.`);
2845
+ if (policy === "off" && !GEMINI_THINKING_DISABLEABLE.includes(this.model)) {
2846
+ this.warnedThinkingClamped = true;
2847
+ 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.`);
2848
+ return;
2849
+ }
2850
+ if (isGradedLevel(policy) && !GEMINI_THINKING_LEVEL_TIERS.includes(this.model)) {
2851
+ this.warnedThinkingClamped = true;
2852
+ 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.`);
2853
+ }
2691
2854
  }
2692
2855
  constructor(config = {}) {
2693
2856
  var _a, _b, _c;
@@ -2744,7 +2907,7 @@ var GeminiTransport = class _GeminiTransport {
2744
2907
  // ── ChatTransport (multi-turn chat) ────────────────────────────────────
2745
2908
  sendChatMessage(history, userMessage, options) {
2746
2909
  return __awaiter(this, void 0, void 0, function* () {
2747
- var _a, _b;
2910
+ var _a, _b, _c;
2748
2911
  const contents = this.toGeminiContents(history, userMessage, options === null || options === void 0 ? void 0 : options.attachments);
2749
2912
  if (options === null || options === void 0 ? void 0 : options.tailContext) {
2750
2913
  contents.push({ role: "user", parts: [{ text: options.tailContext }] });
@@ -2762,13 +2925,33 @@ var GeminiTransport = class _GeminiTransport {
2762
2925
  const toolConfig = tools ? toGeminiToolConfig(options === null || options === void 0 ? void 0 : options.toolChoice) : void 0;
2763
2926
  this.warnIfThinkingUnclampable(options === null || options === void 0 ? void 0 : options.thinkingPolicy);
2764
2927
  const generationConfig = { thinkingConfig: geminiThinkingConfig(this.model, options === null || options === void 0 ? void 0 : options.thinkingPolicy) };
2928
+ if ((options === null || options === void 0 ? void 0 : options.maxTokens) != null) {
2929
+ generationConfig.maxOutputTokens = options.maxTokens;
2930
+ }
2931
+ const resolutionHint = [
2932
+ ...(_b = options === null || options === void 0 ? void 0 : options.attachments) !== null && _b !== void 0 ? _b : [],
2933
+ ...history.flatMap((m) => {
2934
+ var _a2;
2935
+ return (_a2 = m.attachments) !== null && _a2 !== void 0 ? _a2 : [];
2936
+ }),
2937
+ // Tool-result images too: `ChatToolResult.attachments` is typed ChatImageAttachment[], so
2938
+ // `detail` is offerable there, and a render handed back by a tool is the whole point of
2939
+ // the tool-result image path. Omitting this source made the field silently inert there.
2940
+ ...history.flatMap((m) => {
2941
+ var _a2, _b2;
2942
+ return (_b2 = (_a2 = m.toolResult) === null || _a2 === void 0 ? void 0 : _a2.attachments) !== null && _b2 !== void 0 ? _b2 : [];
2943
+ })
2944
+ ].find((att) => att.kind === "image" && att.detail);
2945
+ if ((resolutionHint === null || resolutionHint === void 0 ? void 0 : resolutionHint.kind) === "image" && resolutionHint.detail && GEMINI_MEDIA_RESOLUTION_TIERS.includes(this.model)) {
2946
+ generationConfig.mediaResolution = `MEDIA_RESOLUTION_${resolutionHint.detail.toUpperCase()}`;
2947
+ }
2765
2948
  if ((options === null || options === void 0 ? void 0 : options.temperature) != null) {
2766
2949
  generationConfig.temperature = scaleTemperature(options.temperature, {
2767
2950
  defaultTemp: GEMINI_DEFAULT_TEMPERATURE,
2768
2951
  maxTemp: GEMINI_MAX_TEMPERATURE
2769
2952
  });
2770
2953
  }
2771
- const offeredToolNames = new Set(((_b = options === null || options === void 0 ? void 0 : options.tools) !== null && _b !== void 0 ? _b : []).map((t) => t.name));
2954
+ const offeredToolNames = new Set(((_c = options === null || options === void 0 ? void 0 : options.tools) !== null && _c !== void 0 ? _c : []).map((t) => t.name));
2772
2955
  const schemaCoexistsWithTools = this.model.startsWith("gemini-3");
2773
2956
  const applyResponseSchema = (options === null || options === void 0 ? void 0 : options.responseSchema) != null && (!tools || schemaCoexistsWithTools);
2774
2957
  const response = yield this.post(Object.assign({
@@ -2779,7 +2962,7 @@ var GeminiTransport = class _GeminiTransport {
2779
2962
  toolConfig,
2780
2963
  generationConfig
2781
2964
  }, applyResponseSchema ? { responseSchema: toGeminiSchema(options.responseSchema) } : {}), options === null || options === void 0 ? void 0 : options.signal);
2782
- return this.fromGeminiResponse(response, offeredToolNames);
2965
+ return this.fromGeminiResponse(response, offeredToolNames, generationConfig.maxOutputTokens);
2783
2966
  });
2784
2967
  }
2785
2968
  /**
@@ -2807,7 +2990,7 @@ var GeminiTransport = class _GeminiTransport {
2807
2990
  return costUsd;
2808
2991
  }
2809
2992
  toGeminiContents(history, userMessage, attachments) {
2810
- var _a, _b, _c;
2993
+ var _a, _b, _c, _d;
2811
2994
  const contents = [];
2812
2995
  const toolCallNameById = /* @__PURE__ */ new Map();
2813
2996
  for (const msg of history) {
@@ -2827,7 +3010,16 @@ var GeminiTransport = class _GeminiTransport {
2827
3010
  }
2828
3011
  ]
2829
3012
  });
2830
- } else if ((_b = msg.toolCalls) === null || _b === void 0 ? void 0 : _b.length) {
3013
+ const resultImages = (_b = msg.toolResult.attachments) !== null && _b !== void 0 ? _b : [];
3014
+ if (resultImages.length) {
3015
+ contents.push({
3016
+ role: "user",
3017
+ parts: resultImages.map((att) => ({
3018
+ inlineData: { mimeType: normalizeImageMime(att.mimeType), data: att.data }
3019
+ }))
3020
+ });
3021
+ }
3022
+ } else if ((_c = msg.toolCalls) === null || _c === void 0 ? void 0 : _c.length) {
2831
3023
  for (const tc of msg.toolCalls) {
2832
3024
  toolCallNameById.set(tc.id, tc.name);
2833
3025
  }
@@ -2846,22 +3038,22 @@ var GeminiTransport = class _GeminiTransport {
2846
3038
  return part;
2847
3039
  })
2848
3040
  });
2849
- } else if (role === "user" && ((_c = msg.attachments) === null || _c === void 0 ? void 0 : _c.length)) {
2850
- const msgAttachmentParts = msg.attachments.map((att) => ({
2851
- text: `[File: ${att.name}]
2852
- ${att.content}`
2853
- }));
2854
- contents.push({ role: "user", parts: [{ text: msg.content }, ...msgAttachmentParts] });
3041
+ } else if (role === "user" && ((_d = msg.attachments) === null || _d === void 0 ? void 0 : _d.length)) {
3042
+ const { imageParts: imageParts2, textParts: textParts2 } = splitAttachmentParts(msg.attachments);
3043
+ contents.push({
3044
+ role: "user",
3045
+ parts: [...imageParts2, ...msg.content ? [{ text: msg.content }] : [], ...textParts2]
3046
+ });
2855
3047
  } else {
2856
3048
  contents.push({ role, parts: [{ text: msg.content }] });
2857
3049
  }
2858
3050
  }
2859
- const attachmentParts = (attachments !== null && attachments !== void 0 ? attachments : []).map((att) => ({
2860
- text: `[File: ${att.name}]
2861
- ${att.content}`
2862
- }));
2863
- if (userMessage || attachmentParts.length) {
2864
- contents.push({ role: "user", parts: [{ text: userMessage }, ...attachmentParts] });
3051
+ const { imageParts, textParts } = splitAttachmentParts(attachments !== null && attachments !== void 0 ? attachments : []);
3052
+ if (userMessage || imageParts.length || textParts.length) {
3053
+ contents.push({
3054
+ role: "user",
3055
+ parts: [...imageParts, ...userMessage ? [{ text: userMessage }] : [], ...textParts]
3056
+ });
2865
3057
  }
2866
3058
  return contents;
2867
3059
  }
@@ -2898,7 +3090,7 @@ ${att.content}`
2898
3090
  cost
2899
3091
  };
2900
3092
  }
2901
- fromGeminiResponse(response, offeredToolNames = /* @__PURE__ */ new Set()) {
3093
+ fromGeminiResponse(response, offeredToolNames = /* @__PURE__ */ new Set(), effectiveMaxTokens) {
2902
3094
  var _a, _b, _c, _d;
2903
3095
  const { inputTokens, outputTokens, thoughtsTokens, cacheReadTokens, cost } = this.usageFromGemini(response.usageMetadata);
2904
3096
  const candidates = response === null || response === void 0 ? void 0 : response.candidates;
@@ -2945,7 +3137,7 @@ ${att.content}`
2945
3137
  textParts: textParts.length
2946
3138
  });
2947
3139
  }
2948
- this.guardMaxTokensTruncation(finishReason, toolCalls, narration, outputTokens);
3140
+ this.guardMaxTokensTruncation(finishReason, toolCalls, narration, outputTokens, effectiveMaxTokens);
2949
3141
  if (inputTokens != null)
2950
3142
  base.inputTokens = inputTokens;
2951
3143
  if (outputTokens != null)
@@ -2983,12 +3175,12 @@ ${att.content}`
2983
3175
  * `responseMeta.finishReason`, for the caller to decide. Usage/cost is logged
2984
3176
  * before this check runs, so the spent tokens stay accounted for.
2985
3177
  */
2986
- guardMaxTokensTruncation(finishReason, toolCalls, narration, outputTokens) {
3178
+ guardMaxTokensTruncation(finishReason, toolCalls, narration, outputTokens, effectiveMaxTokens) {
2987
3179
  if (finishReason !== "MAX_TOKENS")
2988
3180
  return;
2989
3181
  if (toolCalls.length === 0 && narration.trim() !== "")
2990
3182
  return;
2991
- throw new ResponseTruncatedError(this.model, void 0, outputTokens, toolCalls.map((tc) => tc.name));
3183
+ throw new ResponseTruncatedError(this.model, effectiveMaxTokens, outputTokens, toolCalls.map((tc) => tc.name));
2992
3184
  }
2993
3185
  /**
2994
3186
  * Log the full shape of a blank or non-STOP response so its cause is legible
@@ -3549,11 +3741,12 @@ function triggerReason(trigger) {
3549
3741
  return "superseded";
3550
3742
  }
3551
3743
  }
3552
- function condenseStub(target, tool, trigger, origLen, restorable) {
3744
+ function condenseStub(target, tool, trigger, origLen, restorable, imageCount = 0) {
3553
3745
  const what = target === "args" ? "args" : "result";
3554
3746
  const key = trigger.kind === "superseded" ? ` ${trigger.by}` : "";
3555
3747
  const restore = restorable ? "; re-call to restore" : "";
3556
- return `[${tool}${key} \u2014 ${what} elided, ~${origLen} chars (${triggerReason(trigger)})${restore}]`;
3748
+ const images = imageCount > 0 ? ` + ${imageCount} image${imageCount === 1 ? "" : "s"}` : "";
3749
+ return `[${tool}${key} \u2014 ${what} elided, ~${origLen} chars${images} (${triggerReason(trigger)})${restore}]`;
3557
3750
  }
3558
3751
  function triggerLabel(trigger) {
3559
3752
  switch (trigger.kind) {
@@ -3652,12 +3845,14 @@ function applyCondensation(history, policies, ctx, onCondensed) {
3652
3845
  }
3653
3846
  if (msg.toolResult) {
3654
3847
  const entry = policies.get(msg.toolResult.toolCallId);
3655
- const origLen = msg.toolResult.content?.length ?? 0;
3848
+ const textLen = msg.toolResult.content?.length ?? 0;
3849
+ const images = msg.toolResult.attachments ?? [];
3850
+ const origLen = textLen + images.reduce((n, att) => n + (att.data?.length ?? 0), 0);
3656
3851
  const fired = entry?.policy.response ? firstFired(msg.toolResult.toolCallId, entry) : void 0;
3657
3852
  if (entry && fired && origLen >= CONDENSE_MIN_CHARS) {
3658
3853
  const tool = nameById.get(msg.toolResult.toolCallId) ?? msg.toolResult.toolCallId;
3659
3854
  const result = entry.policy.response;
3660
- const content = result === "drop" || result === "pointer" ? condenseStub("response", tool, fired, origLen, result === "pointer") : result.replaceWith;
3855
+ const content = result === "drop" || result === "pointer" ? condenseStub("response", tool, fired, textLen, result === "pointer", images.length) : result.replaceWith;
3661
3856
  if (!entry.reportedResponse) {
3662
3857
  entry.reportedResponse = true;
3663
3858
  onCondensed({
@@ -3670,7 +3865,8 @@ function applyCondensation(history, policies, ctx, onCondensed) {
3670
3865
  tokensSaved: estimateTokensSaved(origLen, content.length)
3671
3866
  });
3672
3867
  }
3673
- return { ...msg, toolResult: { ...msg.toolResult, content } };
3868
+ const { attachments: _elided, ...restOfResult } = msg.toolResult;
3869
+ return { ...msg, toolResult: { ...restOfResult, content } };
3674
3870
  }
3675
3871
  }
3676
3872
  return msg;
@@ -3683,9 +3879,10 @@ function maskToolPayload(msg) {
3683
3879
  return { ...msg, toolCalls: msg.toolCalls.map((tc) => ({ ...tc, args: {} })) };
3684
3880
  }
3685
3881
  if (msg.toolResult) {
3882
+ const { attachments: _masked, ...restOfResult } = msg.toolResult;
3686
3883
  return {
3687
3884
  ...msg,
3688
- toolResult: { ...msg.toolResult, content: "[other agent's tool result omitted]" }
3885
+ toolResult: { ...restOfResult, content: "[other agent's tool result omitted]" }
3689
3886
  };
3690
3887
  }
3691
3888
  return msg;
@@ -3839,6 +4036,15 @@ function accumulate(messages, into) {
3839
4036
  var TOOL_FOLD_SYMBOL = Symbol("toolFold");
3840
4037
 
3841
4038
  // src/components/chat-driver/chat-driver.ts
4039
+ var toolResultImages = (result) => {
4040
+ if (!result || typeof result !== "object") return void 0;
4041
+ const candidate = result.attachments;
4042
+ if (!Array.isArray(candidate) || candidate.length === 0) return void 0;
4043
+ const allImages = candidate.every(
4044
+ (att) => !!att && typeof att === "object" && att.kind === "image"
4045
+ );
4046
+ return allImages ? candidate : void 0;
4047
+ };
3842
4048
  var providerRefusedDetailOf = (e) => ({
3843
4049
  vendorLabel: e.vendorLabel,
3844
4050
  kind: e.kind,
@@ -5969,10 +6175,12 @@ ${tailBody}
5969
6175
  const capturedTrace = () => traceCapture.traces.length ? traceCapture.traces.flat() : void 0;
5970
6176
  try {
5971
6177
  const result = await handler(tc.args, this.buildHandlerContext(tc.id, traceCapture));
5972
- const content = typeof result === "string" ? result : JSON.stringify(result);
6178
+ const resultImages = toolResultImages(result);
6179
+ const content = resultImages ? typeof result.content === "string" ? result.content : JSON.stringify({ ...result, attachments: void 0 }) : typeof result === "string" ? result : JSON.stringify(result);
5973
6180
  executedById.set(tc.id, {
5974
6181
  toolCallId: tc.id,
5975
6182
  content,
6183
+ ...resultImages ? { attachments: resultImages } : {},
5976
6184
  // Concatenated when a handler invoked several children, so none is lost.
5977
6185
  // Stays `undefined` when nothing was captured — readers key off presence,
5978
6186
  // and an empty array is a different claim from "no sub-agent ran".
@@ -6021,7 +6229,14 @@ RECOVERY: this tool failed once \u2014 you may retry it, or take a different val
6021
6229
  this.appendToHistory({
6022
6230
  role: "tool",
6023
6231
  content: "",
6024
- toolResult: { toolCallId: r.toolCallId, content: r.content }
6232
+ toolResult: {
6233
+ toolCallId: r.toolCallId,
6234
+ content: r.content,
6235
+ // Images DO persist, unlike the sub-agent trace above: they are the payload
6236
+ // the model must see on every replay of this turn, not a one-off UI artefact.
6237
+ // `condenseWhen` is what sheds them once they are spent — see condense-history.
6238
+ ...r.attachments ? { attachments: r.attachments } : {}
6239
+ }
6025
6240
  });
6026
6241
  }
6027
6242
  }