@genesislcap/ai-assistant 15.11.0 → 15.12.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.
Files changed (50) hide show
  1. package/dist/ai-assistant.api.json +135 -0
  2. package/dist/ai-assistant.d.ts +75 -3
  3. package/dist/chat-driver.cjs +540 -117
  4. package/dist/chat-driver.cjs.map +4 -4
  5. package/dist/chat-driver.mjs +524 -116
  6. package/dist/chat-driver.mjs.map +4 -4
  7. package/dist/custom-elements.json +1822 -1483
  8. package/dist/dts/chat-driver-node.d.ts +5 -2
  9. package/dist/dts/chat-driver-node.d.ts.map +1 -1
  10. package/dist/dts/components/chat-driver/chat-driver.d.ts +20 -3
  11. package/dist/dts/components/chat-driver/chat-driver.d.ts.map +1 -1
  12. package/dist/dts/components/chat-driver/chat-driver.thinking-policy.test.d.ts +2 -0
  13. package/dist/dts/components/chat-driver/chat-driver.thinking-policy.test.d.ts.map +1 -0
  14. package/dist/dts/components/chat-driver/chat-driver.trace-capture.test.d.ts +2 -0
  15. package/dist/dts/components/chat-driver/chat-driver.trace-capture.test.d.ts.map +1 -0
  16. package/dist/dts/config/config.d.ts +39 -2
  17. package/dist/dts/config/config.d.ts.map +1 -1
  18. package/dist/dts/config/define-stateful-agent.d.ts +15 -1
  19. package/dist/dts/config/define-stateful-agent.d.ts.map +1 -1
  20. package/dist/dts/main/main.template.d.ts.map +1 -1
  21. package/dist/dts/utils/strip-agent-handlers.d.ts +1 -1
  22. package/dist/dts/utils/sum-usage.d.ts +37 -4
  23. package/dist/dts/utils/sum-usage.d.ts.map +1 -1
  24. package/dist/dts/utils/usage-rows.d.ts +102 -0
  25. package/dist/dts/utils/usage-rows.d.ts.map +1 -0
  26. package/dist/dts/utils/usage-rows.test.d.ts +2 -0
  27. package/dist/dts/utils/usage-rows.test.d.ts.map +1 -0
  28. package/dist/esm/chat-driver-node.js +36 -1
  29. package/dist/esm/components/chat-driver/chat-driver.js +73 -10
  30. package/dist/esm/components/chat-driver/chat-driver.thinking-policy.test.js +137 -0
  31. package/dist/esm/components/chat-driver/chat-driver.trace-capture.test.js +200 -0
  32. package/dist/esm/config/define-stateful-agent.js +11 -0
  33. package/dist/esm/main/main.template.js +20 -1
  34. package/dist/esm/utils/strip-agent-handlers.js +1 -1
  35. package/dist/esm/utils/sum-usage.js +37 -4
  36. package/dist/esm/utils/usage-rows.js +90 -0
  37. package/dist/esm/utils/usage-rows.test.js +189 -0
  38. package/dist/tsconfig.tsbuildinfo +1 -1
  39. package/package.json +17 -17
  40. package/src/chat-driver-node.ts +58 -0
  41. package/src/components/chat-driver/chat-driver.thinking-policy.test.ts +185 -0
  42. package/src/components/chat-driver/chat-driver.trace-capture.test.ts +251 -0
  43. package/src/components/chat-driver/chat-driver.ts +90 -10
  44. package/src/config/config.ts +50 -1
  45. package/src/config/define-stateful-agent.ts +37 -0
  46. package/src/main/main.template.ts +19 -1
  47. package/src/utils/strip-agent-handlers.ts +1 -1
  48. package/src/utils/sum-usage.ts +37 -4
  49. package/src/utils/usage-rows.test.ts +237 -0
  50. package/src/utils/usage-rows.ts +187 -0
@@ -19,10 +19,15 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
19
19
  // src/chat-driver-node.ts
20
20
  var chat_driver_node_exports = {};
21
21
  __export(chat_driver_node_exports, {
22
+ ANTHROPIC_CACHE_READ_MULTIPLIER: () => ANTHROPIC_CACHE_READ_MULTIPLIER,
23
+ ANTHROPIC_CACHE_WRITE_1H_MULTIPLIER: () => ANTHROPIC_CACHE_WRITE_1H_MULTIPLIER,
24
+ ANTHROPIC_CACHE_WRITE_5M_MULTIPLIER: () => ANTHROPIC_CACHE_WRITE_5M_MULTIPLIER,
22
25
  AgenticActivityBus: () => AgenticActivityBus,
23
26
  AnthropicProvider: () => AnthropicProvider,
24
27
  AnthropicTransport: () => AnthropicTransport,
25
28
  ChatDriver: () => ChatDriver,
29
+ GEMINI_CACHED_INPUT_MULTIPLIER: () => GEMINI_CACHED_INPUT_MULTIPLIER,
30
+ GEMINI_LONG_CONTEXT_THRESHOLD: () => GEMINI_LONG_CONTEXT_THRESHOLD,
26
31
  GeminiProvider: () => GeminiProvider,
27
32
  GeminiTransport: () => GeminiTransport,
28
33
  MutableAIProviderRegistry: () => MutableAIProviderRegistry,
@@ -31,17 +36,27 @@ __export(chat_driver_node_exports, {
31
36
  REQUEST_CONTINUATION_TOOL: () => REQUEST_CONTINUATION_TOOL,
32
37
  SUPPORTED_ANTHROPIC_MODEL_IDS: () => SUPPORTED_ANTHROPIC_MODEL_IDS,
33
38
  SUPPORTED_GEMINI_MODEL_IDS: () => SUPPORTED_GEMINI_MODEL_IDS,
39
+ addUsage: () => addUsage,
34
40
  agenticActivityBus: () => agenticActivityBus,
41
+ anthropicRatesFor: () => anthropicRatesFor,
42
+ anthropicTokenCost: () => anthropicTokenCost,
35
43
  assembleDebugLog: () => assembleDebugLog,
36
44
  buildTimelineEntries: () => buildTimelineEntries,
37
45
  clearSession: () => clearSession,
38
46
  defineAgent: () => defineAgent,
39
47
  defineStatefulAgent: () => defineStatefulAgent,
48
+ emptyUsage: () => emptyUsage,
40
49
  friendlyFallbackAgent: () => friendlyFallbackAgent,
50
+ geminiRatesFor: () => geminiRatesFor,
51
+ geminiTokenCost: () => geminiTokenCost,
41
52
  getMetaEvents: () => getMetaEvents,
42
53
  isObservableAIProviderRegistry: () => isObservableAIProviderRegistry,
43
54
  restoreMachine: () => restoreMachine,
44
- strictFallbackAgent: () => strictFallbackAgent
55
+ strictFallbackAgent: () => strictFallbackAgent,
56
+ sumUsage: () => sumUsage,
57
+ totalTokens: () => totalTokens,
58
+ usageRows: () => usageRows,
59
+ vendorOfModel: () => vendorOfModel
45
60
  });
46
61
  module.exports = __toCommonJS(chat_driver_node_exports);
47
62
 
@@ -1105,6 +1120,13 @@ var SUPPORTED_ANTHROPIC_MODEL_IDS = [
1105
1120
  "claude-sonnet-4-6",
1106
1121
  "claude-haiku-4-5-20251001"
1107
1122
  ];
1123
+ function vendorOfModel(modelId) {
1124
+ if (SUPPORTED_ANTHROPIC_MODEL_IDS.includes(modelId))
1125
+ return "anthropic";
1126
+ if (SUPPORTED_GEMINI_MODEL_IDS.includes(modelId))
1127
+ return "gemini";
1128
+ return void 0;
1129
+ }
1108
1130
 
1109
1131
  // ../../foundation-ai/dist/esm/utils/temperature.js
1110
1132
  var DEFAULT_ANCHOR = 0.5;
@@ -1113,6 +1135,106 @@ function scaleTemperature(normalized, { defaultTemp, maxTemp }) {
1113
1135
  return t <= DEFAULT_ANCHOR ? t / DEFAULT_ANCHOR * defaultTemp : defaultTemp + (t - DEFAULT_ANCHOR) / (1 - DEFAULT_ANCHOR) * (maxTemp - defaultTemp);
1114
1136
  }
1115
1137
 
1138
+ // ../../foundation-ai/dist/esm/utils/token-cost.js
1139
+ var TOKENS_PER_MILLION = 1e6;
1140
+ var ANTHROPIC_PRICING = {
1141
+ "claude-haiku-4-5-20251001": { promptPerMillion: 1, candidatePerMillion: 5 },
1142
+ // Fable 5 — Anthropic's most capable widely-released model; priced above Opus tier.
1143
+ "claude-fable-5": { promptPerMillion: 10, candidatePerMillion: 50 },
1144
+ // Opus 4.7 / 4.8 — same $5 / $25 per MTok. Stated per model rather than shared through a
1145
+ // fall-through, so neither is ever "whatever was left over".
1146
+ "claude-opus-4-8": { promptPerMillion: 5, candidatePerMillion: 25 },
1147
+ "claude-opus-4-7": { promptPerMillion: 5, candidatePerMillion: 25 },
1148
+ // Sonnet 5 is $2 / $10 — its OWN tier, and CHEAPER than the older Sonnet 4.6 below.
1149
+ //
1150
+ // This launched as an introductory rate through 2026-08-31, and an earlier version of this
1151
+ // table deliberately charged the $3 / $15 standard rate instead, on the reasoning that the
1152
+ // introductory period would lapse. Anthropic has since confirmed $2 / $10 as the permanent
1153
+ // standard price and cancelled the scheduled increase, so that reasoning is dead and the old
1154
+ // figure over-charged every Sonnet 5 request by 50%.
1155
+ // https://platform.claude.com/docs/en/about-claude/pricing#model-pricing
1156
+ "claude-sonnet-5": { promptPerMillion: 2, candidatePerMillion: 10 },
1157
+ // Sonnet 4.6 remains on the older $3 / $15 Sonnet tier — the newer model is the cheaper one.
1158
+ "claude-sonnet-4-6": { promptPerMillion: 3, candidatePerMillion: 15 }
1159
+ };
1160
+ function anthropicRatesFor(model) {
1161
+ return ANTHROPIC_PRICING[model];
1162
+ }
1163
+ var ANTHROPIC_CACHE_READ_MULTIPLIER = 0.1;
1164
+ var ANTHROPIC_CACHE_WRITE_5M_MULTIPLIER = 1.25;
1165
+ var ANTHROPIC_CACHE_WRITE_1H_MULTIPLIER = 2;
1166
+ function anthropicTokenCost(model, usage) {
1167
+ const { promptPerMillion, candidatePerMillion } = anthropicRatesFor(model);
1168
+ const m = TOKENS_PER_MILLION;
1169
+ const cacheWrite5mTokens = Math.max(0, usage.cacheWriteTokens - usage.cacheWrite1hTokens);
1170
+ const promptCost = usage.uncachedInputTokens / m * promptPerMillion;
1171
+ const cacheReadCost = usage.cacheReadTokens / m * promptPerMillion * ANTHROPIC_CACHE_READ_MULTIPLIER;
1172
+ const cacheWriteCost = cacheWrite5mTokens / m * promptPerMillion * ANTHROPIC_CACHE_WRITE_5M_MULTIPLIER + usage.cacheWrite1hTokens / m * promptPerMillion * ANTHROPIC_CACHE_WRITE_1H_MULTIPLIER;
1173
+ const candidateCost = usage.outputTokens / m * candidatePerMillion;
1174
+ const cacheReadFull = usage.cacheReadTokens / m * promptPerMillion;
1175
+ const cacheWriteFull = usage.cacheWriteTokens / m * promptPerMillion;
1176
+ return {
1177
+ costUsd: promptCost + cacheReadCost + cacheWriteCost + candidateCost,
1178
+ savedUsd: cacheReadFull - cacheReadCost + (cacheWriteFull - cacheWriteCost),
1179
+ breakdown: {
1180
+ promptUsd: promptCost,
1181
+ cacheReadUsd: cacheReadCost,
1182
+ cacheWriteUsd: cacheWriteCost,
1183
+ candidateUsd: candidateCost
1184
+ }
1185
+ };
1186
+ }
1187
+ var GEMINI_LONG_CONTEXT_THRESHOLD = 2e5;
1188
+ var GEMINI_CACHED_INPUT_MULTIPLIER = 0.1;
1189
+ var GEMINI_PRICING = {
1190
+ "gemini-2.5-flash-lite": {
1191
+ kind: "flat",
1192
+ rate: { promptPerMillion: 0.1, candidatePerMillion: 0.4 }
1193
+ },
1194
+ "gemini-2.5-flash": { kind: "flat", rate: { promptPerMillion: 0.3, candidatePerMillion: 2.5 } },
1195
+ "gemini-2.5-pro": {
1196
+ kind: "tiered",
1197
+ standard: { promptPerMillion: 1.25, candidatePerMillion: 10 },
1198
+ longContext: { promptPerMillion: 2.5, candidatePerMillion: 15 }
1199
+ },
1200
+ "gemini-3.1-flash-lite": {
1201
+ kind: "flat",
1202
+ rate: { promptPerMillion: 0.25, candidatePerMillion: 1.5 }
1203
+ },
1204
+ "gemini-3.5-flash": { kind: "flat", rate: { promptPerMillion: 1.5, candidatePerMillion: 9 } },
1205
+ "gemini-3.1-pro-preview": {
1206
+ kind: "tiered",
1207
+ standard: { promptPerMillion: 2, candidatePerMillion: 12 },
1208
+ longContext: { promptPerMillion: 4, candidatePerMillion: 18 }
1209
+ }
1210
+ };
1211
+ function geminiRatesFor(model, promptTokens) {
1212
+ const pricing = GEMINI_PRICING[model];
1213
+ if (pricing.kind === "flat") {
1214
+ return pricing.rate;
1215
+ }
1216
+ return promptTokens > GEMINI_LONG_CONTEXT_THRESHOLD ? pricing.longContext : pricing.standard;
1217
+ }
1218
+ function geminiTokenCost(model, usage) {
1219
+ const { promptPerMillion, candidatePerMillion } = geminiRatesFor(model, usage.promptTokens);
1220
+ const m = TOKENS_PER_MILLION;
1221
+ const uncachedPromptTokens = Math.max(0, usage.promptTokens - usage.cachedTokens);
1222
+ const promptCost = uncachedPromptTokens / m * promptPerMillion;
1223
+ const cacheReadCost = usage.cachedTokens / m * promptPerMillion * GEMINI_CACHED_INPUT_MULTIPLIER;
1224
+ const candidateCost = (usage.candidateTokens + usage.thoughtTokens) / m * candidatePerMillion;
1225
+ return {
1226
+ costUsd: promptCost + cacheReadCost + candidateCost,
1227
+ // Implicit caching has no write premium, so this is always >= 0.
1228
+ savedUsd: usage.cachedTokens / m * promptPerMillion - cacheReadCost,
1229
+ breakdown: {
1230
+ promptUsd: promptCost,
1231
+ cacheReadUsd: cacheReadCost,
1232
+ cacheWriteUsd: 0,
1233
+ candidateUsd: candidateCost
1234
+ }
1235
+ };
1236
+ }
1237
+
1116
1238
  // ../../foundation-ai/dist/esm/utils/tool-schema.js
1117
1239
  var SCHEMA_MAP_KEYS = ["properties", "patternProperties", "$defs", "definitions"];
1118
1240
  var SCHEMA_LIST_KEYS = ["anyOf", "oneOf", "allOf", "prefixItems"];
@@ -1514,32 +1636,32 @@ function assertSupportedAnthropicModel(model) {
1514
1636
  throw new Error(`AnthropicTransport: unsupported model "${model}". Use one of: ${SUPPORTED_ANTHROPIC_MODEL_IDS.join(", ")}.`);
1515
1637
  }
1516
1638
  }
1517
- function estimatedAnthropicRatesUsdPerMillion(model) {
1518
- if (model === "claude-haiku-4-5-20251001") {
1519
- return { promptPerMillion: 1, candidatePerMillion: 5 };
1520
- }
1521
- if (model === "claude-fable-5") {
1522
- return { promptPerMillion: 10, candidatePerMillion: 50 };
1523
- }
1524
- if (model === "claude-sonnet-5" || model === "claude-sonnet-4-6") {
1525
- return { promptPerMillion: 3, candidatePerMillion: 15 };
1526
- }
1527
- return { promptPerMillion: 5, candidatePerMillion: 25 };
1528
- }
1529
1639
  function rejectsSamplingParams(model) {
1530
1640
  return model === "claude-fable-5" || model === "claude-opus-4-8" || model === "claude-opus-4-7" || model === "claude-sonnet-5";
1531
1641
  }
1532
1642
  function supportsNativeStructuredOutput(model) {
1533
1643
  return model === "claude-fable-5" || model === "claude-opus-4-8" || model === "claude-sonnet-5" || model === "claude-haiku-4-5-20251001";
1534
1644
  }
1535
- function anthropicThinking(model) {
1536
- if (model !== "claude-sonnet-5" && model !== "claude-fable-5")
1537
- return void 0;
1538
- return { type: "adaptive", display: "summarized" };
1645
+ function supportsAdaptiveThinking(model) {
1646
+ return model !== "claude-haiku-4-5-20251001";
1539
1647
  }
1540
- var ANTHROPIC_CACHE_READ_MULTIPLIER = 0.1;
1541
- var ANTHROPIC_CACHE_WRITE_5M_MULTIPLIER = 1.25;
1542
- var ANTHROPIC_CACHE_WRITE_1H_MULTIPLIER = 2;
1648
+ function thinkingIsMandatory(model) {
1649
+ return model === "claude-fable-5";
1650
+ }
1651
+ function anthropicThinking(model, policy) {
1652
+ const defaultsToThinking = model === "claude-sonnet-5" || thinkingIsMandatory(model);
1653
+ const adaptive = { type: "adaptive", display: "summarized" };
1654
+ if (policy === "off") {
1655
+ if (thinkingIsMandatory(model))
1656
+ return adaptive;
1657
+ return defaultsToThinking ? { type: "disabled" } : void 0;
1658
+ }
1659
+ if (policy === "auto") {
1660
+ return supportsAdaptiveThinking(model) ? adaptive : void 0;
1661
+ }
1662
+ return defaultsToThinking ? adaptive : void 0;
1663
+ }
1664
+ var ANTHROPIC_PROVIDER_KEY = "anthropic";
1543
1665
  var ResponseTruncatedError = class extends Error {
1544
1666
  constructor(model, maxTokens, outputTokens, toolNames) {
1545
1667
  super(`Response truncated at ${maxTokens != null ? `the max_tokens cap (${maxTokens})` : "the model's output-token limit"} for model ${model}` + (toolNames.length > 0 ? ` while emitting tool call(s): ${toolNames.join(", ")}` : "") + ". The output exceeds the per-response limit \u2014 raise the provider maxTokens (where configurable) or split the work into smaller outputs.");
@@ -1551,10 +1673,30 @@ var ResponseTruncatedError = class extends Error {
1551
1673
  }
1552
1674
  };
1553
1675
  var AnthropicTransport = class _AnthropicTransport {
1676
+ /**
1677
+ * Warn once when a requested policy is silently clamped, in EITHER direction — which is what
1678
+ * `ChatThinkingPolicy` promises callers. Both directions cost the caller something they asked
1679
+ * for and would otherwise get no signal about: `'off'` on a model that always thinks keeps
1680
+ * billing reasoning as output, and `'auto'` on a model without adaptive support means an agent
1681
+ * that asked to reason quietly does not. (Gemini's twin deliberately stays silent on `'auto'`,
1682
+ * but only because dynamic thinking is already the default there; Haiku defaults to none, so
1683
+ * the same silence would hide a real difference.)
1684
+ */
1685
+ warnIfThinkingUnclampable(policy) {
1686
+ if (this.warnedThinkingClamped)
1687
+ return;
1688
+ const clamped = policy === "off" && thinkingIsMandatory(this.model) || policy === "auto" && !supportsAdaptiveThinking(this.model);
1689
+ if (!clamped)
1690
+ return;
1691
+ this.warnedThinkingClamped = true;
1692
+ 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.`);
1693
+ }
1554
1694
  constructor(config = {}) {
1555
1695
  var _a, _b, _c, _d;
1556
1696
  this.lifetimeCostUsd = 0;
1557
1697
  this.lifetimeSavingsUsd = 0;
1698
+ this.warnedThinkingClamped = false;
1699
+ this.warnedUnknownServingModels = /* @__PURE__ */ new Set();
1558
1700
  const model = (_a = config.model) !== null && _a !== void 0 ? _a : DEFAULT_MODEL;
1559
1701
  assertSupportedAnthropicModel(model);
1560
1702
  this.model = model;
@@ -1635,8 +1777,12 @@ var AnthropicTransport = class _AnthropicTransport {
1635
1777
  // ── ChatTransport (multi-turn chat) ────────────────────────────────────
1636
1778
  sendChatMessage(history, userMessage, options) {
1637
1779
  return __awaiter(this, void 0, void 0, function* () {
1638
- var _a, _b, _c;
1639
- const messages = this.toAnthropicMessages(history, userMessage, options === null || options === void 0 ? void 0 : options.attachments);
1780
+ var _a, _b, _c, _d;
1781
+ const reachableModels = /* @__PURE__ */ new Set([
1782
+ this.model,
1783
+ ...((_a = options === null || options === void 0 ? void 0 : options.fallbacks) !== null && _a !== void 0 ? _a : []).map((f) => f.model)
1784
+ ]);
1785
+ const messages = this.toAnthropicMessages(history, userMessage, options === null || options === void 0 ? void 0 : options.attachments, reachableModels);
1640
1786
  const body = {
1641
1787
  model: this.model,
1642
1788
  max_tokens: this.maxTokens,
@@ -1644,18 +1790,20 @@ var AnthropicTransport = class _AnthropicTransport {
1644
1790
  };
1645
1791
  if (options === null || options === void 0 ? void 0 : options.systemPrompt)
1646
1792
  body.system = options.systemPrompt;
1647
- if ((_a = options === null || options === void 0 ? void 0 : options.tools) === null || _a === void 0 ? void 0 : _a.length) {
1793
+ if ((_b = options === null || options === void 0 ? void 0 : options.tools) === null || _b === void 0 ? void 0 : _b.length) {
1648
1794
  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 } : {}));
1649
1795
  }
1650
- if ((_b = body.tools) === null || _b === void 0 ? void 0 : _b.length) {
1796
+ if ((_c = body.tools) === null || _c === void 0 ? void 0 : _c.length) {
1651
1797
  const toolChoice = toAnthropicToolChoice(options === null || options === void 0 ? void 0 : options.toolChoice);
1652
1798
  if (toolChoice)
1653
1799
  body.tool_choice = toolChoice;
1654
1800
  }
1655
- const thinking = anthropicThinking(this.model);
1801
+ this.warnIfThinkingUnclampable(options === null || options === void 0 ? void 0 : options.thinkingPolicy);
1802
+ const thinking = anthropicThinking(this.model, options === null || options === void 0 ? void 0 : options.thinkingPolicy);
1656
1803
  if (thinking)
1657
1804
  body.thinking = thinking;
1658
- if ((options === null || options === void 0 ? void 0 : options.temperature) != null && !rejectsSamplingParams(this.model)) {
1805
+ const thinkingEnabled = (thinking === null || thinking === void 0 ? void 0 : thinking.type) === "adaptive";
1806
+ if ((options === null || options === void 0 ? void 0 : options.temperature) != null && !rejectsSamplingParams(this.model) && !thinkingEnabled) {
1659
1807
  body.temperature = scaleTemperature(options.temperature, {
1660
1808
  defaultTemp: ANTHROPIC_DEFAULT_TEMPERATURE,
1661
1809
  maxTemp: ANTHROPIC_MAX_TEMPERATURE
@@ -1666,7 +1814,7 @@ var AnthropicTransport = class _AnthropicTransport {
1666
1814
  format: { type: "json_schema", schema: options.responseSchema }
1667
1815
  };
1668
1816
  }
1669
- if ((_c = options === null || options === void 0 ? void 0 : options.fallbacks) === null || _c === void 0 ? void 0 : _c.length) {
1817
+ if ((_d = options === null || options === void 0 ? void 0 : options.fallbacks) === null || _d === void 0 ? void 0 : _d.length) {
1670
1818
  body.fallbacks = options.fallbacks.map((f) => f.maxTokens != null ? { model: f.model, max_tokens: f.maxTokens } : { model: f.model });
1671
1819
  }
1672
1820
  if (options === null || options === void 0 ? void 0 : options.cachePolicy) {
@@ -1737,31 +1885,27 @@ var AnthropicTransport = class _AnthropicTransport {
1737
1885
  * and returns the per-call total so the caller can attach it to the response
1738
1886
  * message.
1739
1887
  */
1740
- logTokenUsage(promptTokens, candidateTokens, cacheReadTokens, cacheCreationTokens, cacheCreation1hTokens) {
1741
- const { promptPerMillion, candidatePerMillion } = estimatedAnthropicRatesUsdPerMillion(this.model);
1742
- const m = _AnthropicTransport.TOKENS_PER_MILLION;
1743
- const cacheCreation5mTokens = Math.max(0, cacheCreationTokens - cacheCreation1hTokens);
1744
- const promptCost = promptTokens / m * promptPerMillion;
1745
- const cacheReadCost = cacheReadTokens / m * promptPerMillion * ANTHROPIC_CACHE_READ_MULTIPLIER;
1746
- const cacheWriteCost = cacheCreation5mTokens / m * promptPerMillion * ANTHROPIC_CACHE_WRITE_5M_MULTIPLIER + cacheCreation1hTokens / m * promptPerMillion * ANTHROPIC_CACHE_WRITE_1H_MULTIPLIER;
1747
- const candidateCost = candidateTokens / m * candidatePerMillion;
1748
- const totalCost = promptCost + cacheReadCost + cacheWriteCost + candidateCost;
1749
- this.lifetimeCostUsd += totalCost;
1750
- const cacheReadFull = cacheReadTokens / m * promptPerMillion;
1751
- const cacheWriteFull = cacheCreationTokens / m * promptPerMillion;
1752
- const saved = cacheReadFull - cacheReadCost + (cacheWriteFull - cacheWriteCost);
1753
- this.lifetimeSavingsUsd += saved;
1888
+ logTokenUsage(model, promptTokens, candidateTokens, cacheReadTokens, cacheCreationTokens, cacheCreation1hTokens) {
1889
+ const { costUsd, savedUsd, breakdown } = anthropicTokenCost(model, {
1890
+ uncachedInputTokens: promptTokens,
1891
+ outputTokens: candidateTokens,
1892
+ cacheReadTokens,
1893
+ cacheWriteTokens: cacheCreationTokens,
1894
+ cacheWrite1hTokens: cacheCreation1hTokens
1895
+ });
1896
+ this.lifetimeCostUsd += costUsd;
1897
+ this.lifetimeSavingsUsd += savedUsd;
1754
1898
  const dp = _AnthropicTransport.COST_DECIMAL_PLACES;
1755
- console.log(`--- Anthropic Token Usage (${this.model}) ---`);
1756
- console.log(`Prompt Tokens: ${promptTokens} ($${promptCost.toFixed(dp)})`);
1757
- console.log(`Cache Read: ${cacheReadTokens} ($${cacheReadCost.toFixed(dp)})`);
1758
- console.log(`Cache Write: ${cacheCreationTokens} ($${cacheWriteCost.toFixed(dp)})`);
1759
- console.log(`Candidate Tokens: ${candidateTokens} ($${candidateCost.toFixed(dp)})`);
1760
- console.log(`Total Cost: $${totalCost.toFixed(dp)}`);
1761
- console.log(`Cache Saved: $${saved.toFixed(dp)} (lifetime $${this.lifetimeSavingsUsd.toFixed(dp)})`);
1899
+ console.log(`--- Anthropic Token Usage (${model}) ---`);
1900
+ console.log(`Prompt Tokens: ${promptTokens} ($${breakdown.promptUsd.toFixed(dp)})`);
1901
+ console.log(`Cache Read: ${cacheReadTokens} ($${breakdown.cacheReadUsd.toFixed(dp)})`);
1902
+ console.log(`Cache Write: ${cacheCreationTokens} ($${breakdown.cacheWriteUsd.toFixed(dp)})`);
1903
+ console.log(`Candidate Tokens: ${candidateTokens} ($${breakdown.candidateUsd.toFixed(dp)})`);
1904
+ console.log(`Total Cost: $${costUsd.toFixed(dp)}`);
1905
+ console.log(`Cache Saved: $${savedUsd.toFixed(dp)} (lifetime $${this.lifetimeSavingsUsd.toFixed(dp)})`);
1762
1906
  console.log(`Lifetime Cost: $${this.lifetimeCostUsd.toFixed(dp)}`);
1763
1907
  console.log("--------------------------");
1764
- return totalCost;
1908
+ return costUsd;
1765
1909
  }
1766
1910
  /**
1767
1911
  * Convert the internal `ChatMessage[]` history into Anthropic's message format.
@@ -1772,7 +1916,7 @@ var AnthropicTransport = class _AnthropicTransport {
1772
1916
  * Consecutive same-role turns are merged by the API but we merge here to keep
1773
1917
  * the payload tidy.
1774
1918
  */
1775
- toAnthropicMessages(history, userMessage, attachments) {
1919
+ toAnthropicMessages(history, userMessage, attachments, reachableModels = /* @__PURE__ */ new Set([this.model])) {
1776
1920
  var _a, _b, _c;
1777
1921
  const messages = [];
1778
1922
  const pushBlock = (role, block) => {
@@ -1795,6 +1939,9 @@ var AnthropicTransport = class _AnthropicTransport {
1795
1939
  continue;
1796
1940
  }
1797
1941
  if ((_a = msg.toolCalls) === null || _a === void 0 ? void 0 : _a.length) {
1942
+ for (const block of this.reasoningToReplay(msg.toolCalls[0], reachableModels)) {
1943
+ pushBlock("assistant", block);
1944
+ }
1798
1945
  if (msg.content) {
1799
1946
  pushBlock("assistant", { type: "text", text: msg.content });
1800
1947
  }
@@ -1830,20 +1977,140 @@ ${att.content}` });
1830
1977
  }
1831
1978
  return messages;
1832
1979
  }
1980
+ /**
1981
+ * The blocks to replay ahead of a tool call — fallback boundaries then reasoning, or none.
1982
+ *
1983
+ * A `signature` is only valid for the model that produced it, so reasoning captured under a
1984
+ * *different* model is normally dropped: an agent that varies `provider` by state can switch
1985
+ * models mid-loop, and replaying the old model's signatures would send blocks the new one
1986
+ * cannot verify.
1987
+ *
1988
+ * Two ways the producer can be the model that will validate:
1989
+ *
1990
+ * 1. **It is the model we are asking for.** `state.model === this.model` — the ordinary case.
1991
+ * 2. **Sticky routing will send this conversation back to it.** After a conversation falls
1992
+ * back, later requests carrying `fallbacks` go straight to the model that served, without
1993
+ * re-running the one that declined. That is what makes a fallback producer's reasoning
1994
+ * replayable at all (Fable 5 configured, Opus 4.8 serving — the pairing this transport's
1995
+ * own constructor warning recommends). But it holds only while the conversation continues
1996
+ * under the SAME request configuration, which is why `requestedModel` is compared rather
1997
+ * than just checking the chain for the producer.
1998
+ *
1999
+ * That second condition is deliberately narrow. Chain membership alone is too weak: a
2000
+ * fallback target is only *contingently* the server, so an agent that switches `provider` to
2001
+ * a model whose own chain happens to contain the old producer would replay foreign
2002
+ * signatures to whichever model actually answers. Requiring the requested model to be
2003
+ * unchanged separates "this conversation is still going" from "we are somewhere else now".
2004
+ *
2005
+ * State captured before `requestedModel` existed falls back to condition 1 alone, which is
2006
+ * the conservative branch — it can drop reasoning, never misdirect it.
2007
+ *
2008
+ * Boundaries themselves are always echoed: keeping them in place is the documented rule, and
2009
+ * with no thinking blocks around them they are inert rather than harmful.
2010
+ */
2011
+ reasoningToReplay(firstCall, reachableModels) {
2012
+ var _a, _b, _c;
2013
+ const state = (_a = firstCall.providerMetadata) === null || _a === void 0 ? void 0 : _a[ANTHROPIC_PROVIDER_KEY];
2014
+ if (!state)
2015
+ return [];
2016
+ const boundaries = (_b = state.fallbacks) !== null && _b !== void 0 ? _b : [];
2017
+ const stickyToProducer = state.requestedModel === this.model && reachableModels.has(state.model);
2018
+ const vouched = state.model === this.model || stickyToProducer;
2019
+ return [...boundaries, ...vouched ? (_c = state.reasoning) !== null && _c !== void 0 ? _c : [] : []];
2020
+ }
2021
+ /**
2022
+ * The model that actually served this response.
2023
+ *
2024
+ * A server-side `fallbacks` chain re-runs a declined request on another model and
2025
+ * names it in the response's top-level `model`. Pricing must follow that, not the
2026
+ * model we asked for: a Fable 5 request served by Opus 4.8 costed at Fable's
2027
+ * $10/$50 instead of $5/$25 doubles that part of the bill.
2028
+ *
2029
+ * An unrecognised model id is NOT priced at a guessed rate — that is precisely how
2030
+ * a consumer ended up billing Haiku at Sonnet rates. It warns and falls back to the
2031
+ * configured model, which is at least a figure someone chose.
2032
+ */
2033
+ servingModel(response) {
2034
+ const served = response.model;
2035
+ if (!served || served === this.model)
2036
+ return this.model;
2037
+ if (SUPPORTED_ANTHROPIC_MODEL_IDS.includes(served)) {
2038
+ return served;
2039
+ }
2040
+ if (!this.warnedUnknownServingModels.has(served)) {
2041
+ this.warnedUnknownServingModels.add(served);
2042
+ logger.warn(`AnthropicTransport: response was served by "${served}", which is not a known model \u2014 pricing it at the configured "${this.model}" rate instead of guessing. The reported cost for this request may be wrong; add the model to SUPPORTED_ANTHROPIC_MODEL_IDS.`);
2043
+ }
2044
+ return this.model;
2045
+ }
2046
+ /**
2047
+ * Whether a fallback-chain attempt is a refusal that was **not billed**.
2048
+ *
2049
+ * Anthropic does not charge for a refusal that arrives before any output: the token counts
2050
+ * still appear in `usage`, but they are not on the bill. Pricing them anyway turns the
2051
+ * fallback undercount this reducer was written to fix into an overcount — the documented
2052
+ * example declines with `input_tokens: 535, output_tokens: 0`, which is real money at
2053
+ * Fable 5's prompt rate.
2054
+ *
2055
+ * A **mid-output** refusal *is* billed for the input and whatever it streamed, so output
2056
+ * tokens are the discriminator rather than the refusal itself.
2057
+ *
2058
+ * Declined attempts appear as `type: 'message'`; the attempt that served the turn is
2059
+ * `type: 'fallback_message'`. The serving entry is normally billable — except when every
2060
+ * model in the chain declined, where the last entry is both the serving one and a refusal,
2061
+ * which `lastAndRefused` covers.
2062
+ *
2063
+ * Also called for a response with no chain at all, as `isUnbilledRefusal({}, usage, refused)`:
2064
+ * a direct request that was declined has no `iterations` array, and the same rule applies to
2065
+ * it. That is in fact the common case — `iterations` only appears when `fallbacks` was
2066
+ * configured.
2067
+ */
2068
+ isUnbilledRefusal(attempt, usage, lastAndRefused) {
2069
+ var _a;
2070
+ if (((_a = usage.output_tokens) !== null && _a !== void 0 ? _a : 0) > 0)
2071
+ return false;
2072
+ return attempt.type === "message" || lastAndRefused;
2073
+ }
2074
+ /** Cost one attempt's usage block at `model`'s rates, logging and banking it. */
2075
+ costAttempt(model, usage) {
2076
+ var _a, _b, _c, _d, _e, _f;
2077
+ const cacheRead = (_a = usage.cache_read_input_tokens) !== null && _a !== void 0 ? _a : 0;
2078
+ const breakdown = usage.cache_creation;
2079
+ const cacheCreation1h = (_b = breakdown === null || breakdown === void 0 ? void 0 : breakdown.ephemeral_1h_input_tokens) !== null && _b !== void 0 ? _b : 0;
2080
+ 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;
2081
+ 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);
2082
+ }
1833
2083
  fromAnthropicResponse(response) {
1834
- var _a, _b, _c, _d, _e, _f, _g, _h;
2084
+ var _a, _b, _c, _d, _e, _f, _g;
1835
2085
  let inputTokens;
1836
2086
  let outputTokens;
1837
2087
  let cacheReadTokens;
1838
2088
  let cacheWriteTokens;
1839
2089
  let cost;
2090
+ const servingModel = this.servingModel(response);
1840
2091
  if (response.usage) {
1841
2092
  const uncachedInput = (_a = response.usage.input_tokens) !== null && _a !== void 0 ? _a : 0;
1842
2093
  const cacheRead = (_b = response.usage.cache_read_input_tokens) !== null && _b !== void 0 ? _b : 0;
1843
2094
  const breakdown = response.usage.cache_creation;
1844
2095
  const cacheCreation1h = (_c = breakdown === null || breakdown === void 0 ? void 0 : breakdown.ephemeral_1h_input_tokens) !== null && _c !== void 0 ? _c : 0;
1845
2096
  const cacheCreation = (_d = response.usage.cache_creation_input_tokens) !== null && _d !== void 0 ? _d : breakdown ? ((_e = breakdown.ephemeral_5m_input_tokens) !== null && _e !== void 0 ? _e : 0) + cacheCreation1h : 0;
1846
- cost = this.logTokenUsage(uncachedInput, (_f = response.usage.output_tokens) !== null && _f !== void 0 ? _f : 0, cacheRead, cacheCreation, cacheCreation1h);
2097
+ const attempts = response.usage.iterations;
2098
+ if (attempts === null || attempts === void 0 ? void 0 : attempts.length) {
2099
+ const refused = response.stop_reason === "refusal";
2100
+ cost = attempts.reduce((total, attempt, index) => {
2101
+ var _a2;
2102
+ const usage = (_a2 = attempt.usage) !== null && _a2 !== void 0 ? _a2 : attempt;
2103
+ if (this.isUnbilledRefusal(attempt, usage, index === attempts.length - 1 && refused)) {
2104
+ return total;
2105
+ }
2106
+ const model = attempt.model ? this.servingModel({ model: attempt.model }) : this.model;
2107
+ return total + this.costAttempt(model, usage);
2108
+ }, 0);
2109
+ } else if (this.isUnbilledRefusal({}, response.usage, response.stop_reason === "refusal")) {
2110
+ cost = 0;
2111
+ } else {
2112
+ cost = this.costAttempt(servingModel, response.usage);
2113
+ }
1847
2114
  inputTokens = uncachedInput + cacheRead + cacheCreation;
1848
2115
  cacheReadTokens = cacheRead;
1849
2116
  cacheWriteTokens = cacheCreation;
@@ -1851,23 +2118,44 @@ ${att.content}` });
1851
2118
  outputTokens = response.usage.output_tokens;
1852
2119
  }
1853
2120
  }
1854
- const blocks = (_g = response.content) !== null && _g !== void 0 ? _g : [];
2121
+ const blocks = (_f = response.content) !== null && _f !== void 0 ? _f : [];
1855
2122
  const toolCalls = [];
1856
2123
  const thoughtParts = [];
1857
2124
  const textParts = [];
1858
- for (const block of blocks) {
2125
+ const reasoningBlocks = [];
2126
+ const fallbackIndex = blocks.map((b) => b.type).lastIndexOf("fallback");
2127
+ const fallbackBlocks = blocks.filter((b) => b.type === "fallback");
2128
+ for (const [index, block] of blocks.entries()) {
2129
+ const postBoundary = index > fallbackIndex;
1859
2130
  if (block.type === "tool_use") {
1860
- toolCalls.push({
1861
- id: block.id,
1862
- name: block.name,
1863
- args: (_h = block.input) !== null && _h !== void 0 ? _h : {}
1864
- });
2131
+ if (postBoundary) {
2132
+ toolCalls.push({
2133
+ id: block.id,
2134
+ name: block.name,
2135
+ args: (_g = block.input) !== null && _g !== void 0 ? _g : {}
2136
+ });
2137
+ }
1865
2138
  } else if (block.type === "thinking") {
2139
+ if (postBoundary)
2140
+ reasoningBlocks.push(block);
1866
2141
  thoughtParts.push(block.thinking);
2142
+ } else if (block.type === "redacted_thinking") {
2143
+ if (postBoundary)
2144
+ reasoningBlocks.push(block);
1867
2145
  } else if (block.type === "text") {
1868
2146
  textParts.push(block.text);
1869
2147
  }
1870
2148
  }
2149
+ if (toolCalls.length > 0 && (reasoningBlocks.length > 0 || fallbackBlocks.length > 0)) {
2150
+ const state = {
2151
+ reasoning: reasoningBlocks,
2152
+ model: servingModel,
2153
+ requestedModel: this.model
2154
+ };
2155
+ if (fallbackBlocks.length > 0)
2156
+ state.fallbacks = fallbackBlocks;
2157
+ toolCalls[0].providerMetadata = Object.assign(Object.assign({}, toolCalls[0].providerMetadata), { [ANTHROPIC_PROVIDER_KEY]: state });
2158
+ }
1871
2159
  if (response.stop_reason === "max_tokens" && toolCalls.length > 0) {
1872
2160
  throw new ResponseTruncatedError(this.model, this.maxTokens, outputTokens, toolCalls.map((tc) => tc.name));
1873
2161
  }
@@ -1883,6 +2171,7 @@ ${att.content}` });
1883
2171
  base.cacheWriteTokens = cacheWriteTokens;
1884
2172
  if (cost != null)
1885
2173
  base.cost = cost;
2174
+ base.model = servingModel;
1886
2175
  if (response.stop_reason === "max_tokens") {
1887
2176
  base.responseMeta = { finishReason: "max_tokens" };
1888
2177
  }
@@ -2184,43 +2473,25 @@ function assertSupportedGeminiModel(model) {
2184
2473
  throw new Error(`GeminiTransport: unsupported model "${model}". Use one of: ${SUPPORTED_GEMINI_MODEL_IDS.join(", ")}.`);
2185
2474
  }
2186
2475
  }
2187
- var GEMINI_LONG_CONTEXT_THRESHOLD = 2e5;
2188
- var GEMINI_CACHED_INPUT_MULTIPLIER = 0.1;
2189
- var GEMINI_PRICING = {
2190
- "gemini-2.5-flash-lite": {
2191
- kind: "flat",
2192
- rate: { promptPerMillion: 0.1, candidatePerMillion: 0.4 }
2193
- },
2194
- "gemini-2.5-flash": { kind: "flat", rate: { promptPerMillion: 0.3, candidatePerMillion: 2.5 } },
2195
- "gemini-2.5-pro": {
2196
- kind: "tiered",
2197
- standard: { promptPerMillion: 1.25, candidatePerMillion: 10 },
2198
- longContext: { promptPerMillion: 2.5, candidatePerMillion: 15 }
2199
- },
2200
- "gemini-3.1-flash-lite": {
2201
- kind: "flat",
2202
- rate: { promptPerMillion: 0.25, candidatePerMillion: 1.5 }
2203
- },
2204
- "gemini-3.5-flash": { kind: "flat", rate: { promptPerMillion: 1.5, candidatePerMillion: 9 } },
2205
- "gemini-3.1-pro-preview": {
2206
- kind: "tiered",
2207
- standard: { promptPerMillion: 2, candidatePerMillion: 12 },
2208
- longContext: { promptPerMillion: 4, candidatePerMillion: 18 }
2209
- }
2210
- };
2211
- function estimatedGeminiPaidRatesUsdPerMillion(model, promptTokens) {
2212
- const pricing = GEMINI_PRICING[model];
2213
- if (pricing.kind === "flat") {
2214
- return pricing.rate;
2215
- }
2216
- return promptTokens > GEMINI_LONG_CONTEXT_THRESHOLD ? pricing.longContext : pricing.standard;
2217
- }
2218
2476
  var GEMINI_MODEL_WARNINGS = {
2219
2477
  "gemini-2.5-flash": "GeminiTransport: using gemini-2.5-flash \u2014 higher cost than flash-lite; use for harder reasoning or agent tasks.",
2220
2478
  "gemini-2.5-pro": "GeminiTransport: using gemini-2.5-pro \u2014 significantly higher, prompt-size-tiered cost; reserve for tasks where flash reliability is insufficient.",
2221
2479
  "gemini-3.5-flash": "GeminiTransport: using gemini-3.5-flash \u2014 frontier model, materially higher cost than flash-lite; use for harder reasoning or agent tasks.",
2222
2480
  "gemini-3.1-pro-preview": "GeminiTransport: using gemini-3.1-pro-preview \u2014 PREVIEW model (no stability guarantee, may be withdrawn) with significantly higher, prompt-size-tiered cost; reserve for tasks where flash reliability is insufficient."
2223
2481
  };
2482
+ var GEMINI_THINKING_DISABLEABLE = [
2483
+ "gemini-2.5-flash",
2484
+ "gemini-2.5-flash-lite"
2485
+ ];
2486
+ function geminiThinkingConfig(model, policy) {
2487
+ if (policy === "off" && GEMINI_THINKING_DISABLEABLE.includes(model)) {
2488
+ return { includeThoughts: false, thinkingBudget: 0 };
2489
+ }
2490
+ if (policy === "auto" && GEMINI_THINKING_DISABLEABLE.includes(model)) {
2491
+ return { includeThoughts: true, thinkingBudget: -1 };
2492
+ }
2493
+ return { includeThoughts: true };
2494
+ }
2224
2495
  var SKIP_SIGNATURE = "skip_thought_signature_validator";
2225
2496
  var GEMINI_PROVIDER_KEY = "gemini";
2226
2497
  var MalformedFunctionCallError = class extends Error {
@@ -2231,10 +2502,28 @@ var MalformedFunctionCallError = class extends Error {
2231
2502
  }
2232
2503
  };
2233
2504
  var GeminiTransport = class _GeminiTransport {
2505
+ /**
2506
+ * Warn once when a requested policy is silently clamped. Turning thinking off is a *cost*
2507
+ * decision, so a caller who asked for it and kept paying for reasoning tokens needs to hear
2508
+ * about it — but only once, not per turn.
2509
+ *
2510
+ * Only `'off'` can actually be denied. `'auto'` is already what an omitted budget produces on
2511
+ * every model here — dynamic thinking is the documented default — so warning that it was
2512
+ * "ignored" would be false, and latching on it would spend the one warning the genuinely
2513
+ * unhonourable `'off'` needs.
2514
+ */
2515
+ warnIfThinkingUnclampable(policy) {
2516
+ if (policy !== "off" || GEMINI_THINKING_DISABLEABLE.includes(this.model) || this.warnedThinkingClamped) {
2517
+ return;
2518
+ }
2519
+ this.warnedThinkingClamped = true;
2520
+ 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.`);
2521
+ }
2234
2522
  constructor(config = {}) {
2235
2523
  var _a, _b, _c;
2236
2524
  this.lifetimeCostUsd = 0;
2237
2525
  this.lifetimeSavingsUsd = 0;
2526
+ this.warnedThinkingClamped = false;
2238
2527
  const model = (_a = config.model) !== null && _a !== void 0 ? _a : DEFAULT_MODEL2;
2239
2528
  assertSupportedGeminiModel(model);
2240
2529
  this.model = model;
@@ -2301,7 +2590,8 @@ var GeminiTransport = class _GeminiTransport {
2301
2590
  ] : void 0;
2302
2591
  const systemInstruction = (options === null || options === void 0 ? void 0 : options.systemPrompt) ? { role: "system", parts: [{ text: options.systemPrompt }] } : void 0;
2303
2592
  const toolConfig = tools ? toGeminiToolConfig(options === null || options === void 0 ? void 0 : options.toolChoice) : void 0;
2304
- const generationConfig = { thinkingConfig: { includeThoughts: true } };
2593
+ this.warnIfThinkingUnclampable(options === null || options === void 0 ? void 0 : options.thinkingPolicy);
2594
+ const generationConfig = { thinkingConfig: geminiThinkingConfig(this.model, options === null || options === void 0 ? void 0 : options.thinkingPolicy) };
2305
2595
  if ((options === null || options === void 0 ? void 0 : options.temperature) != null) {
2306
2596
  generationConfig.temperature = scaleTemperature(options.temperature, {
2307
2597
  defaultTemp: GEMINI_DEFAULT_TEMPERATURE,
@@ -2328,25 +2618,23 @@ var GeminiTransport = class _GeminiTransport {
2328
2618
  * message.
2329
2619
  */
2330
2620
  logTokenUsage(promptTokens, candidateTokens, thoughtTokens, cachedTokens) {
2331
- const { promptPerMillion, candidatePerMillion } = estimatedGeminiPaidRatesUsdPerMillion(this.model, promptTokens);
2332
- const m = _GeminiTransport.TOKENS_PER_MILLION;
2333
- const uncachedPromptTokens = Math.max(0, promptTokens - cachedTokens);
2334
- const promptCost = uncachedPromptTokens / m * promptPerMillion;
2335
- const cacheReadCost = cachedTokens / m * promptPerMillion * GEMINI_CACHED_INPUT_MULTIPLIER;
2336
- const candidateCost = (candidateTokens + thoughtTokens) / m * candidatePerMillion;
2337
- const totalCost = promptCost + cacheReadCost + candidateCost;
2338
- this.lifetimeCostUsd += totalCost;
2339
- const saved = cachedTokens / m * promptPerMillion - cacheReadCost;
2340
- this.lifetimeSavingsUsd += saved;
2621
+ const { costUsd, savedUsd, breakdown } = geminiTokenCost(this.model, {
2622
+ promptTokens,
2623
+ candidateTokens,
2624
+ thoughtTokens,
2625
+ cachedTokens
2626
+ });
2627
+ this.lifetimeCostUsd += costUsd;
2628
+ this.lifetimeSavingsUsd += savedUsd;
2341
2629
  const dp = _GeminiTransport.COST_DECIMAL_PLACES;
2342
2630
  console.log(`--- Gemini Token Usage (${this.model}) ---`);
2343
- console.log(`Prompt Tokens: ${promptTokens} (${cachedTokens} cached) ($${promptCost.toFixed(dp)} + $${cacheReadCost.toFixed(dp)})`);
2344
- console.log(`Candidate Tokens: ${candidateTokens} (+${thoughtTokens} thinking) ($${candidateCost.toFixed(dp)})`);
2345
- console.log(`Total Cost: $${totalCost.toFixed(dp)}`);
2346
- console.log(`Cache Saved: $${saved.toFixed(dp)} (lifetime $${this.lifetimeSavingsUsd.toFixed(dp)})`);
2631
+ console.log(`Prompt Tokens: ${promptTokens} (${cachedTokens} cached) ($${breakdown.promptUsd.toFixed(dp)} + $${breakdown.cacheReadUsd.toFixed(dp)})`);
2632
+ console.log(`Candidate Tokens: ${candidateTokens} (+${thoughtTokens} thinking) ($${breakdown.candidateUsd.toFixed(dp)})`);
2633
+ console.log(`Total Cost: $${costUsd.toFixed(dp)}`);
2634
+ console.log(`Cache Saved: $${savedUsd.toFixed(dp)} (lifetime $${this.lifetimeSavingsUsd.toFixed(dp)})`);
2347
2635
  console.log(`Lifetime Cost: $${this.lifetimeCostUsd.toFixed(dp)}`);
2348
2636
  console.log("--------------------------");
2349
- return totalCost;
2637
+ return costUsd;
2350
2638
  }
2351
2639
  toGeminiContents(history, userMessage, attachments) {
2352
2640
  var _a, _b, _c;
@@ -3317,6 +3605,18 @@ function emptyUsage() {
3317
3605
  outputTokens: 0
3318
3606
  };
3319
3607
  }
3608
+ function totalTokens(usage) {
3609
+ return usage.uncachedInputTokens + usage.cacheReadTokens + usage.cacheWriteTokens + usage.outputTokens;
3610
+ }
3611
+ function addUsage(a, b) {
3612
+ return {
3613
+ costUsd: a.costUsd + b.costUsd,
3614
+ uncachedInputTokens: a.uncachedInputTokens + b.uncachedInputTokens,
3615
+ cacheReadTokens: a.cacheReadTokens + b.cacheReadTokens,
3616
+ cacheWriteTokens: a.cacheWriteTokens + b.cacheWriteTokens,
3617
+ outputTokens: a.outputTokens + b.outputTokens
3618
+ };
3619
+ }
3320
3620
  function sumUsage(messages) {
3321
3621
  const total = emptyUsage();
3322
3622
  accumulate(messages, total);
@@ -3805,6 +4105,7 @@ var ChatDriver = class _ChatDriver extends EventTarget {
3805
4105
  this.activeTemperatureInput = config.temperature;
3806
4106
  this.activeToolChoiceInput = config.toolChoice;
3807
4107
  this.activeCachePolicyInput = config.cachePolicy;
4108
+ this.activeThinkingPolicyInput = config.thinkingPolicy;
3808
4109
  this.activeTailContextInput = config.tailContext;
3809
4110
  this.activeResponseSchemaInput = config.responseSchema;
3810
4111
  this.activeFallbacks = config.fallbacks;
@@ -4459,16 +4760,27 @@ Output format (strict):
4459
4760
  * `condenseWhen` can register against the right call (it stamps the clocks
4460
4761
  * straight off the driver). Absent for dispatch paths with no addressable tool
4461
4762
  * call (e.g. the fold-close handler), where `condenseWhen` is a no-op.
4462
- * @param traceCapture - Optional per-invocation slot. When provided, the trace
4463
- * from any sub-agent call is written here rather than to shared instance state,
4464
- * so parallel tool calls each capture their own trace independently.
4763
+ * @param traceCapture - Optional per-tool-call accumulator. When provided, every
4764
+ * sub-agent call's trace is **appended** here rather than written to shared
4765
+ * instance state, so parallel tool calls each capture their own traces
4766
+ * independently.
4767
+ *
4768
+ * Deliberately a list of traces, not one slot. A single handler may call
4769
+ * `requestSubAgent` more than once — a code-driven scheduler dispatching a
4770
+ * dependency graph, a retry of a timed-out child, any fan-out helper — and a
4771
+ * single slot kept only the last, so every other child ran, was billed by the
4772
+ * provider, and then vanished from history. The loss was silent in the worst
4773
+ * way: `sumUsage` and `usageRows` both recurse into the trace, so they summed a
4774
+ * truncated input and still agreed with each other. Measured at 1 of 7 traces
4775
+ * kept on a seven-way fan-out, reporting ~2.3x under the real cost, with the
4776
+ * error growing as the fan-out widens.
4465
4777
  */
4466
4778
  buildHandlerContext(activeToolCallId, traceCapture) {
4467
4779
  return {
4468
4780
  requestInteraction: (componentName, data, options) => this.requestInteraction(componentName, data, options),
4469
4781
  ...this.subAgentsMap.size > 0 && {
4470
4782
  requestSubAgent: (name, options) => this.invokeSubAgent(name, options).then(({ outcome, trace }) => {
4471
- if (traceCapture) traceCapture.trace = trace;
4783
+ if (traceCapture && trace) traceCapture.traces.push(trace);
4472
4784
  return outcome;
4473
4785
  })
4474
4786
  },
@@ -4842,6 +5154,8 @@ Output format (strict):
4842
5154
  let emptyResponseAttempts = 0;
4843
5155
  let setupTransportAttempts = 0;
4844
5156
  let firstLlmCall = !!currentInput;
5157
+ let pinnedThinkingPolicy;
5158
+ let thinkingPolicyPinned = false;
4845
5159
  while (iterations < this.maxToolIterations) {
4846
5160
  iterations += 1;
4847
5161
  this.modelCallSeq += 1;
@@ -4941,7 +5255,8 @@ Output format (strict):
4941
5255
  resolvedToolChoice,
4942
5256
  resolvedCachePolicy,
4943
5257
  resolvedTailContext,
4944
- resolvedResponseSchema
5258
+ resolvedResponseSchema,
5259
+ firstResolvedThinkingPolicy
4945
5260
  ] = (
4946
5261
  // oxlint-disable-next-line no-await-in-loop
4947
5262
  await Promise.all([
@@ -4949,9 +5264,19 @@ Output format (strict):
4949
5264
  this.resolveTurnInput(this.activeToolChoiceInput, promptCtx),
4950
5265
  this.resolveTurnInput(this.activeCachePolicyInput, promptCtx),
4951
5266
  this.resolveTurnInput(this.activeTailContextInput, promptCtx),
4952
- this.resolveTurnInput(this.activeResponseSchemaInput, promptCtx)
5267
+ this.resolveTurnInput(this.activeResponseSchemaInput, promptCtx),
5268
+ // Only consulted on the first iteration (see `pinnedThinkingPolicy`); resolved
5269
+ // alongside the others so a resolver still sees the same turn context.
5270
+ thinkingPolicyPinned ? Promise.resolve(void 0) : this.resolveTurnInput(
5271
+ this.activeThinkingPolicyInput,
5272
+ promptCtx
5273
+ )
4953
5274
  ])
4954
5275
  );
5276
+ if (!thinkingPolicyPinned) {
5277
+ pinnedThinkingPolicy = firstResolvedThinkingPolicy;
5278
+ thinkingPolicyPinned = true;
5279
+ }
4955
5280
  const systemPrompt = resolvedSystemPrompt || void 0;
4956
5281
  const tailBody = [resolvedTailContext, frameworkSystemSuffix].map((part) => (part ?? "").trim()).filter((part) => part.length > 0).join("\n\n");
4957
5282
  const tailContext = tailBody ? `<system-reminder>
@@ -4978,6 +5303,12 @@ ${tailBody}
4978
5303
  // Prompt-cache policy for this turn (Anthropic places breakpoints per scope; Gemini
4979
5304
  // caches implicitly regardless). Undefined → no caching requested.
4980
5305
  cachePolicy: resolvedCachePolicy,
5306
+ // Extended-thinking posture, pinned for the whole tool loop (one assistant turn) rather
5307
+ // than re-resolved per iteration — see `pinnedThinkingPolicy`. Undefined — unset, or the
5308
+ // resolver's answer for this turn — is NOT "off": it leaves the model on its own default,
5309
+ // so agents that never set this are priced exactly as before. Transports clamp models
5310
+ // that can't honour it.
5311
+ thinkingPolicy: pinnedThinkingPolicy,
4981
5312
  // Framed volatile context injected at the message tail (never stored). Undefined → none.
4982
5313
  tailContext,
4983
5314
  // Structured-output schema for this turn (agent/state-resolved). When set, the transport
@@ -5098,7 +5429,9 @@ ${tailBody}
5098
5429
  }
5099
5430
  throw e;
5100
5431
  }
5101
- if (this.lastResolvedModel !== void 0) response.model = this.lastResolvedModel;
5432
+ if (response.model === void 0 && this.lastResolvedModel !== void 0) {
5433
+ response.model = this.lastResolvedModel;
5434
+ }
5102
5435
  if (this.lastResolvedProvider !== void 0) response.provider = this.lastResolvedProvider;
5103
5436
  if (this.lastResolvedProviderName !== void 0) {
5104
5437
  response.providerName = this.lastResolvedProviderName;
@@ -5306,14 +5639,18 @@ ${tailBody}
5306
5639
  }
5307
5640
  return;
5308
5641
  }
5642
+ const traceCapture = { traces: [] };
5643
+ const capturedTrace = () => traceCapture.traces.length ? traceCapture.traces.flat() : void 0;
5309
5644
  try {
5310
- const traceCapture = {};
5311
5645
  const result = await handler(tc.args, this.buildHandlerContext(tc.id, traceCapture));
5312
5646
  const content = typeof result === "string" ? result : JSON.stringify(result);
5313
5647
  executedById.set(tc.id, {
5314
5648
  toolCallId: tc.id,
5315
5649
  content,
5316
- subAgentTrace: traceCapture.trace
5650
+ // Concatenated when a handler invoked several children, so none is lost.
5651
+ // Stays `undefined` when nothing was captured — readers key off presence,
5652
+ // and an empty array is a different claim from "no sub-agent ran".
5653
+ subAgentTrace: capturedTrace()
5317
5654
  });
5318
5655
  anyRealToolExecuted = true;
5319
5656
  } catch (e) {
@@ -5328,7 +5665,10 @@ ${tailBody}
5328
5665
  // Structured recovery hint so the model retries or routes around a tool
5329
5666
  // failure instead of apologising and giving up.
5330
5667
  content: `Tool error: ${e.message}
5331
- RECOVERY: this tool failed once \u2014 you may retry it, or take a different valid action to make progress. Do NOT abandon the task, ask the user to rephrase, or claim you cannot make changes. If a planning tool failed, retry it or proceed with the information you already have.`
5668
+ RECOVERY: this tool failed once \u2014 you may retry it, or take a different valid action to make progress. Do NOT abandon the task, ask the user to rephrase, or claim you cannot make changes. If a planning tool failed, retry it or proceed with the information you already have.`,
5669
+ // Children that completed before the throw were still billed — keep
5670
+ // their traces so the run's cost stays whole.
5671
+ subAgentTrace: capturedTrace()
5332
5672
  });
5333
5673
  anyRealToolExecuted = true;
5334
5674
  }
@@ -6120,6 +6460,10 @@ function defineStatefulAgent(opts) {
6120
6460
  }
6121
6461
  return opts.cachePolicy({ ...ctx, state });
6122
6462
  } : opts.cachePolicy;
6463
+ const wrappedThinkingPolicy = typeof opts.thinkingPolicy === "function" ? async (ctx) => {
6464
+ if (!state) return void 0;
6465
+ return opts.thinkingPolicy({ ...ctx, state });
6466
+ } : opts.thinkingPolicy;
6123
6467
  const wrappedTailContext = typeof opts.tailContext === "function" ? async (ctx) => {
6124
6468
  if (!state) {
6125
6469
  throw new Error(`Stateful agent "${opts.name}" tailContext called before init`);
@@ -6158,6 +6502,7 @@ function defineStatefulAgent(opts) {
6158
6502
  temperature: wrappedTemperature,
6159
6503
  toolChoice: wrappedToolChoice,
6160
6504
  cachePolicy: wrappedCachePolicy,
6505
+ thinkingPolicy: wrappedThinkingPolicy,
6161
6506
  tailContext: wrappedTailContext,
6162
6507
  onUnresolvedTool: wrappedOnUnresolvedTool,
6163
6508
  resumable: wrappedResumable,
@@ -6219,6 +6564,69 @@ var strictFallbackAgent = {
6219
6564
  systemPrompt: `You are a concise assistant. The user's request is outside your scope. Respond with a single short sentence stating what you can assist with. Do not elaborate. The available specialists are: {{agents}}.`
6220
6565
  };
6221
6566
 
6567
+ // src/utils/usage-rows.ts
6568
+ function hasUsage(m) {
6569
+ return m.cost != null || m.externalCostUsd != null || m.inputTokens != null || m.outputTokens != null || m.cacheReadTokens != null || m.cacheWriteTokens != null;
6570
+ }
6571
+ function usageRows(messages) {
6572
+ return collectRows(messages, 0, void 0);
6573
+ }
6574
+ function bucketsOf(usage) {
6575
+ return {
6576
+ uncachedInputTokens: usage.uncachedInputTokens,
6577
+ cacheReadTokens: usage.cacheReadTokens,
6578
+ cacheWriteTokens: usage.cacheWriteTokens,
6579
+ outputTokens: usage.outputTokens
6580
+ };
6581
+ }
6582
+ function collectRows(messages, depth, subAgentOf) {
6583
+ const rows = [];
6584
+ for (const m of messages) {
6585
+ if (hasUsage(m)) {
6586
+ const cacheReadTokens = m.cacheReadTokens ?? 0;
6587
+ const cacheWriteTokens = m.cacheWriteTokens ?? 0;
6588
+ rows.push({
6589
+ source: "request",
6590
+ model: m.model,
6591
+ // The driver stamps `ChatMessage.provider` from the resolved provider's own
6592
+ // status, so it is AUTHORITATIVE and covers vendors the model allowlists do
6593
+ // not — a server-proxied `gpt-5` turn is `provider: 'openai'` on the message
6594
+ // and unknown to `vendorOfModel`. Prefer it; fall back to resolving the model
6595
+ // id only for messages persisted before the field existed. Still left
6596
+ // undefined when neither knows, rather than guessed at.
6597
+ provider: m.provider ?? (m.model ? vendorOfModel(m.model) : void 0),
6598
+ ...m.cost != null && { costUsd: m.cost },
6599
+ ...m.externalCostUsd != null && { externalCostUsd: m.externalCostUsd },
6600
+ // Uncached input is the REMAINDER, matching `sumUsage`: `inputTokens` is the
6601
+ // whole prompt and the cache fields break it down. Clamped for the same
6602
+ // reason — hand-edited history must not drive a total negative.
6603
+ uncachedInputTokens: Math.max(0, (m.inputTokens ?? 0) - cacheReadTokens - cacheWriteTokens),
6604
+ cacheReadTokens,
6605
+ cacheWriteTokens,
6606
+ outputTokens: m.outputTokens ?? 0,
6607
+ ...m.agentName != null && { agentName: m.agentName },
6608
+ subAgentDepth: depth,
6609
+ ...subAgentOf != null && { subAgentOf }
6610
+ });
6611
+ }
6612
+ const rolled = m.compaction?.rolledUpUsage;
6613
+ if (rolled) {
6614
+ rows.push({
6615
+ source: "compaction",
6616
+ ...rolled.costUsd != null && { costUsd: rolled.costUsd },
6617
+ ...bucketsOf(rolled),
6618
+ ...m.agentName != null && { agentName: m.agentName },
6619
+ subAgentDepth: depth,
6620
+ ...subAgentOf != null && { subAgentOf }
6621
+ });
6622
+ }
6623
+ for (const tc of m.toolCalls ?? []) {
6624
+ if (tc.subAgentTrace) rows.push(...collectRows(tc.subAgentTrace, depth + 1, tc.id));
6625
+ }
6626
+ }
6627
+ return rows;
6628
+ }
6629
+
6222
6630
  // src/utils/flatten-sub-agent-messages.ts
6223
6631
  function flattenSubAgentMessages(messages, prefix = "", depth = 0, subAgentOf) {
6224
6632
  return messages.flatMap((m) => {
@@ -6300,10 +6708,15 @@ function assembleDebugLog(entries, readme) {
6300
6708
  }
6301
6709
  // Annotate the CommonJS export names for ESM import in node:
6302
6710
  0 && (module.exports = {
6711
+ ANTHROPIC_CACHE_READ_MULTIPLIER,
6712
+ ANTHROPIC_CACHE_WRITE_1H_MULTIPLIER,
6713
+ ANTHROPIC_CACHE_WRITE_5M_MULTIPLIER,
6303
6714
  AgenticActivityBus,
6304
6715
  AnthropicProvider,
6305
6716
  AnthropicTransport,
6306
6717
  ChatDriver,
6718
+ GEMINI_CACHED_INPUT_MULTIPLIER,
6719
+ GEMINI_LONG_CONTEXT_THRESHOLD,
6307
6720
  GeminiProvider,
6308
6721
  GeminiTransport,
6309
6722
  MutableAIProviderRegistry,
@@ -6312,16 +6725,26 @@ function assembleDebugLog(entries, readme) {
6312
6725
  REQUEST_CONTINUATION_TOOL,
6313
6726
  SUPPORTED_ANTHROPIC_MODEL_IDS,
6314
6727
  SUPPORTED_GEMINI_MODEL_IDS,
6728
+ addUsage,
6315
6729
  agenticActivityBus,
6730
+ anthropicRatesFor,
6731
+ anthropicTokenCost,
6316
6732
  assembleDebugLog,
6317
6733
  buildTimelineEntries,
6318
6734
  clearSession,
6319
6735
  defineAgent,
6320
6736
  defineStatefulAgent,
6737
+ emptyUsage,
6321
6738
  friendlyFallbackAgent,
6739
+ geminiRatesFor,
6740
+ geminiTokenCost,
6322
6741
  getMetaEvents,
6323
6742
  isObservableAIProviderRegistry,
6324
6743
  restoreMachine,
6325
- strictFallbackAgent
6744
+ strictFallbackAgent,
6745
+ sumUsage,
6746
+ totalTokens,
6747
+ usageRows,
6748
+ vendorOfModel
6326
6749
  });
6327
6750
  //# sourceMappingURL=chat-driver.cjs.map