@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
@@ -1058,6 +1058,13 @@ var SUPPORTED_ANTHROPIC_MODEL_IDS = [
1058
1058
  "claude-sonnet-4-6",
1059
1059
  "claude-haiku-4-5-20251001"
1060
1060
  ];
1061
+ function vendorOfModel(modelId) {
1062
+ if (SUPPORTED_ANTHROPIC_MODEL_IDS.includes(modelId))
1063
+ return "anthropic";
1064
+ if (SUPPORTED_GEMINI_MODEL_IDS.includes(modelId))
1065
+ return "gemini";
1066
+ return void 0;
1067
+ }
1061
1068
 
1062
1069
  // ../../foundation-ai/dist/esm/utils/temperature.js
1063
1070
  var DEFAULT_ANCHOR = 0.5;
@@ -1066,6 +1073,106 @@ function scaleTemperature(normalized, { defaultTemp, maxTemp }) {
1066
1073
  return t <= DEFAULT_ANCHOR ? t / DEFAULT_ANCHOR * defaultTemp : defaultTemp + (t - DEFAULT_ANCHOR) / (1 - DEFAULT_ANCHOR) * (maxTemp - defaultTemp);
1067
1074
  }
1068
1075
 
1076
+ // ../../foundation-ai/dist/esm/utils/token-cost.js
1077
+ var TOKENS_PER_MILLION = 1e6;
1078
+ var ANTHROPIC_PRICING = {
1079
+ "claude-haiku-4-5-20251001": { promptPerMillion: 1, candidatePerMillion: 5 },
1080
+ // Fable 5 — Anthropic's most capable widely-released model; priced above Opus tier.
1081
+ "claude-fable-5": { promptPerMillion: 10, candidatePerMillion: 50 },
1082
+ // Opus 4.7 / 4.8 — same $5 / $25 per MTok. Stated per model rather than shared through a
1083
+ // fall-through, so neither is ever "whatever was left over".
1084
+ "claude-opus-4-8": { promptPerMillion: 5, candidatePerMillion: 25 },
1085
+ "claude-opus-4-7": { promptPerMillion: 5, candidatePerMillion: 25 },
1086
+ // Sonnet 5 is $2 / $10 — its OWN tier, and CHEAPER than the older Sonnet 4.6 below.
1087
+ //
1088
+ // This launched as an introductory rate through 2026-08-31, and an earlier version of this
1089
+ // table deliberately charged the $3 / $15 standard rate instead, on the reasoning that the
1090
+ // introductory period would lapse. Anthropic has since confirmed $2 / $10 as the permanent
1091
+ // standard price and cancelled the scheduled increase, so that reasoning is dead and the old
1092
+ // figure over-charged every Sonnet 5 request by 50%.
1093
+ // https://platform.claude.com/docs/en/about-claude/pricing#model-pricing
1094
+ "claude-sonnet-5": { promptPerMillion: 2, candidatePerMillion: 10 },
1095
+ // Sonnet 4.6 remains on the older $3 / $15 Sonnet tier — the newer model is the cheaper one.
1096
+ "claude-sonnet-4-6": { promptPerMillion: 3, candidatePerMillion: 15 }
1097
+ };
1098
+ function anthropicRatesFor(model) {
1099
+ return ANTHROPIC_PRICING[model];
1100
+ }
1101
+ var ANTHROPIC_CACHE_READ_MULTIPLIER = 0.1;
1102
+ var ANTHROPIC_CACHE_WRITE_5M_MULTIPLIER = 1.25;
1103
+ var ANTHROPIC_CACHE_WRITE_1H_MULTIPLIER = 2;
1104
+ function anthropicTokenCost(model, usage) {
1105
+ const { promptPerMillion, candidatePerMillion } = anthropicRatesFor(model);
1106
+ const m = TOKENS_PER_MILLION;
1107
+ const cacheWrite5mTokens = Math.max(0, usage.cacheWriteTokens - usage.cacheWrite1hTokens);
1108
+ const promptCost = usage.uncachedInputTokens / m * promptPerMillion;
1109
+ const cacheReadCost = usage.cacheReadTokens / m * promptPerMillion * ANTHROPIC_CACHE_READ_MULTIPLIER;
1110
+ const cacheWriteCost = cacheWrite5mTokens / m * promptPerMillion * ANTHROPIC_CACHE_WRITE_5M_MULTIPLIER + usage.cacheWrite1hTokens / m * promptPerMillion * ANTHROPIC_CACHE_WRITE_1H_MULTIPLIER;
1111
+ const candidateCost = usage.outputTokens / m * candidatePerMillion;
1112
+ const cacheReadFull = usage.cacheReadTokens / m * promptPerMillion;
1113
+ const cacheWriteFull = usage.cacheWriteTokens / m * promptPerMillion;
1114
+ return {
1115
+ costUsd: promptCost + cacheReadCost + cacheWriteCost + candidateCost,
1116
+ savedUsd: cacheReadFull - cacheReadCost + (cacheWriteFull - cacheWriteCost),
1117
+ breakdown: {
1118
+ promptUsd: promptCost,
1119
+ cacheReadUsd: cacheReadCost,
1120
+ cacheWriteUsd: cacheWriteCost,
1121
+ candidateUsd: candidateCost
1122
+ }
1123
+ };
1124
+ }
1125
+ var GEMINI_LONG_CONTEXT_THRESHOLD = 2e5;
1126
+ var GEMINI_CACHED_INPUT_MULTIPLIER = 0.1;
1127
+ var GEMINI_PRICING = {
1128
+ "gemini-2.5-flash-lite": {
1129
+ kind: "flat",
1130
+ rate: { promptPerMillion: 0.1, candidatePerMillion: 0.4 }
1131
+ },
1132
+ "gemini-2.5-flash": { kind: "flat", rate: { promptPerMillion: 0.3, candidatePerMillion: 2.5 } },
1133
+ "gemini-2.5-pro": {
1134
+ kind: "tiered",
1135
+ standard: { promptPerMillion: 1.25, candidatePerMillion: 10 },
1136
+ longContext: { promptPerMillion: 2.5, candidatePerMillion: 15 }
1137
+ },
1138
+ "gemini-3.1-flash-lite": {
1139
+ kind: "flat",
1140
+ rate: { promptPerMillion: 0.25, candidatePerMillion: 1.5 }
1141
+ },
1142
+ "gemini-3.5-flash": { kind: "flat", rate: { promptPerMillion: 1.5, candidatePerMillion: 9 } },
1143
+ "gemini-3.1-pro-preview": {
1144
+ kind: "tiered",
1145
+ standard: { promptPerMillion: 2, candidatePerMillion: 12 },
1146
+ longContext: { promptPerMillion: 4, candidatePerMillion: 18 }
1147
+ }
1148
+ };
1149
+ function geminiRatesFor(model, promptTokens) {
1150
+ const pricing = GEMINI_PRICING[model];
1151
+ if (pricing.kind === "flat") {
1152
+ return pricing.rate;
1153
+ }
1154
+ return promptTokens > GEMINI_LONG_CONTEXT_THRESHOLD ? pricing.longContext : pricing.standard;
1155
+ }
1156
+ function geminiTokenCost(model, usage) {
1157
+ const { promptPerMillion, candidatePerMillion } = geminiRatesFor(model, usage.promptTokens);
1158
+ const m = TOKENS_PER_MILLION;
1159
+ const uncachedPromptTokens = Math.max(0, usage.promptTokens - usage.cachedTokens);
1160
+ const promptCost = uncachedPromptTokens / m * promptPerMillion;
1161
+ const cacheReadCost = usage.cachedTokens / m * promptPerMillion * GEMINI_CACHED_INPUT_MULTIPLIER;
1162
+ const candidateCost = (usage.candidateTokens + usage.thoughtTokens) / m * candidatePerMillion;
1163
+ return {
1164
+ costUsd: promptCost + cacheReadCost + candidateCost,
1165
+ // Implicit caching has no write premium, so this is always >= 0.
1166
+ savedUsd: usage.cachedTokens / m * promptPerMillion - cacheReadCost,
1167
+ breakdown: {
1168
+ promptUsd: promptCost,
1169
+ cacheReadUsd: cacheReadCost,
1170
+ cacheWriteUsd: 0,
1171
+ candidateUsd: candidateCost
1172
+ }
1173
+ };
1174
+ }
1175
+
1069
1176
  // ../../foundation-ai/dist/esm/utils/tool-schema.js
1070
1177
  var SCHEMA_MAP_KEYS = ["properties", "patternProperties", "$defs", "definitions"];
1071
1178
  var SCHEMA_LIST_KEYS = ["anyOf", "oneOf", "allOf", "prefixItems"];
@@ -1467,32 +1574,32 @@ function assertSupportedAnthropicModel(model) {
1467
1574
  throw new Error(`AnthropicTransport: unsupported model "${model}". Use one of: ${SUPPORTED_ANTHROPIC_MODEL_IDS.join(", ")}.`);
1468
1575
  }
1469
1576
  }
1470
- function estimatedAnthropicRatesUsdPerMillion(model) {
1471
- if (model === "claude-haiku-4-5-20251001") {
1472
- return { promptPerMillion: 1, candidatePerMillion: 5 };
1473
- }
1474
- if (model === "claude-fable-5") {
1475
- return { promptPerMillion: 10, candidatePerMillion: 50 };
1476
- }
1477
- if (model === "claude-sonnet-5" || model === "claude-sonnet-4-6") {
1478
- return { promptPerMillion: 3, candidatePerMillion: 15 };
1479
- }
1480
- return { promptPerMillion: 5, candidatePerMillion: 25 };
1481
- }
1482
1577
  function rejectsSamplingParams(model) {
1483
1578
  return model === "claude-fable-5" || model === "claude-opus-4-8" || model === "claude-opus-4-7" || model === "claude-sonnet-5";
1484
1579
  }
1485
1580
  function supportsNativeStructuredOutput(model) {
1486
1581
  return model === "claude-fable-5" || model === "claude-opus-4-8" || model === "claude-sonnet-5" || model === "claude-haiku-4-5-20251001";
1487
1582
  }
1488
- function anthropicThinking(model) {
1489
- if (model !== "claude-sonnet-5" && model !== "claude-fable-5")
1490
- return void 0;
1491
- return { type: "adaptive", display: "summarized" };
1583
+ function supportsAdaptiveThinking(model) {
1584
+ return model !== "claude-haiku-4-5-20251001";
1492
1585
  }
1493
- var ANTHROPIC_CACHE_READ_MULTIPLIER = 0.1;
1494
- var ANTHROPIC_CACHE_WRITE_5M_MULTIPLIER = 1.25;
1495
- var ANTHROPIC_CACHE_WRITE_1H_MULTIPLIER = 2;
1586
+ function thinkingIsMandatory(model) {
1587
+ return model === "claude-fable-5";
1588
+ }
1589
+ function anthropicThinking(model, policy) {
1590
+ const defaultsToThinking = model === "claude-sonnet-5" || thinkingIsMandatory(model);
1591
+ const adaptive = { type: "adaptive", display: "summarized" };
1592
+ if (policy === "off") {
1593
+ if (thinkingIsMandatory(model))
1594
+ return adaptive;
1595
+ return defaultsToThinking ? { type: "disabled" } : void 0;
1596
+ }
1597
+ if (policy === "auto") {
1598
+ return supportsAdaptiveThinking(model) ? adaptive : void 0;
1599
+ }
1600
+ return defaultsToThinking ? adaptive : void 0;
1601
+ }
1602
+ var ANTHROPIC_PROVIDER_KEY = "anthropic";
1496
1603
  var ResponseTruncatedError = class extends Error {
1497
1604
  constructor(model, maxTokens, outputTokens, toolNames) {
1498
1605
  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.");
@@ -1504,10 +1611,30 @@ var ResponseTruncatedError = class extends Error {
1504
1611
  }
1505
1612
  };
1506
1613
  var AnthropicTransport = class _AnthropicTransport {
1614
+ /**
1615
+ * Warn once when a requested policy is silently clamped, in EITHER direction — which is what
1616
+ * `ChatThinkingPolicy` promises callers. Both directions cost the caller something they asked
1617
+ * for and would otherwise get no signal about: `'off'` on a model that always thinks keeps
1618
+ * billing reasoning as output, and `'auto'` on a model without adaptive support means an agent
1619
+ * that asked to reason quietly does not. (Gemini's twin deliberately stays silent on `'auto'`,
1620
+ * but only because dynamic thinking is already the default there; Haiku defaults to none, so
1621
+ * the same silence would hide a real difference.)
1622
+ */
1623
+ warnIfThinkingUnclampable(policy) {
1624
+ if (this.warnedThinkingClamped)
1625
+ return;
1626
+ const clamped = policy === "off" && thinkingIsMandatory(this.model) || policy === "auto" && !supportsAdaptiveThinking(this.model);
1627
+ if (!clamped)
1628
+ return;
1629
+ this.warnedThinkingClamped = true;
1630
+ 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.`);
1631
+ }
1507
1632
  constructor(config = {}) {
1508
1633
  var _a, _b, _c, _d;
1509
1634
  this.lifetimeCostUsd = 0;
1510
1635
  this.lifetimeSavingsUsd = 0;
1636
+ this.warnedThinkingClamped = false;
1637
+ this.warnedUnknownServingModels = /* @__PURE__ */ new Set();
1511
1638
  const model = (_a = config.model) !== null && _a !== void 0 ? _a : DEFAULT_MODEL;
1512
1639
  assertSupportedAnthropicModel(model);
1513
1640
  this.model = model;
@@ -1588,8 +1715,12 @@ var AnthropicTransport = class _AnthropicTransport {
1588
1715
  // ── ChatTransport (multi-turn chat) ────────────────────────────────────
1589
1716
  sendChatMessage(history, userMessage, options) {
1590
1717
  return __awaiter(this, void 0, void 0, function* () {
1591
- var _a, _b, _c;
1592
- const messages = this.toAnthropicMessages(history, userMessage, options === null || options === void 0 ? void 0 : options.attachments);
1718
+ var _a, _b, _c, _d;
1719
+ const reachableModels = /* @__PURE__ */ new Set([
1720
+ this.model,
1721
+ ...((_a = options === null || options === void 0 ? void 0 : options.fallbacks) !== null && _a !== void 0 ? _a : []).map((f) => f.model)
1722
+ ]);
1723
+ const messages = this.toAnthropicMessages(history, userMessage, options === null || options === void 0 ? void 0 : options.attachments, reachableModels);
1593
1724
  const body = {
1594
1725
  model: this.model,
1595
1726
  max_tokens: this.maxTokens,
@@ -1597,18 +1728,20 @@ var AnthropicTransport = class _AnthropicTransport {
1597
1728
  };
1598
1729
  if (options === null || options === void 0 ? void 0 : options.systemPrompt)
1599
1730
  body.system = options.systemPrompt;
1600
- if ((_a = options === null || options === void 0 ? void 0 : options.tools) === null || _a === void 0 ? void 0 : _a.length) {
1731
+ if ((_b = options === null || options === void 0 ? void 0 : options.tools) === null || _b === void 0 ? void 0 : _b.length) {
1601
1732
  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 } : {}));
1602
1733
  }
1603
- if ((_b = body.tools) === null || _b === void 0 ? void 0 : _b.length) {
1734
+ if ((_c = body.tools) === null || _c === void 0 ? void 0 : _c.length) {
1604
1735
  const toolChoice = toAnthropicToolChoice(options === null || options === void 0 ? void 0 : options.toolChoice);
1605
1736
  if (toolChoice)
1606
1737
  body.tool_choice = toolChoice;
1607
1738
  }
1608
- const thinking = anthropicThinking(this.model);
1739
+ this.warnIfThinkingUnclampable(options === null || options === void 0 ? void 0 : options.thinkingPolicy);
1740
+ const thinking = anthropicThinking(this.model, options === null || options === void 0 ? void 0 : options.thinkingPolicy);
1609
1741
  if (thinking)
1610
1742
  body.thinking = thinking;
1611
- if ((options === null || options === void 0 ? void 0 : options.temperature) != null && !rejectsSamplingParams(this.model)) {
1743
+ const thinkingEnabled = (thinking === null || thinking === void 0 ? void 0 : thinking.type) === "adaptive";
1744
+ if ((options === null || options === void 0 ? void 0 : options.temperature) != null && !rejectsSamplingParams(this.model) && !thinkingEnabled) {
1612
1745
  body.temperature = scaleTemperature(options.temperature, {
1613
1746
  defaultTemp: ANTHROPIC_DEFAULT_TEMPERATURE,
1614
1747
  maxTemp: ANTHROPIC_MAX_TEMPERATURE
@@ -1619,7 +1752,7 @@ var AnthropicTransport = class _AnthropicTransport {
1619
1752
  format: { type: "json_schema", schema: options.responseSchema }
1620
1753
  };
1621
1754
  }
1622
- if ((_c = options === null || options === void 0 ? void 0 : options.fallbacks) === null || _c === void 0 ? void 0 : _c.length) {
1755
+ if ((_d = options === null || options === void 0 ? void 0 : options.fallbacks) === null || _d === void 0 ? void 0 : _d.length) {
1623
1756
  body.fallbacks = options.fallbacks.map((f) => f.maxTokens != null ? { model: f.model, max_tokens: f.maxTokens } : { model: f.model });
1624
1757
  }
1625
1758
  if (options === null || options === void 0 ? void 0 : options.cachePolicy) {
@@ -1690,31 +1823,27 @@ var AnthropicTransport = class _AnthropicTransport {
1690
1823
  * and returns the per-call total so the caller can attach it to the response
1691
1824
  * message.
1692
1825
  */
1693
- logTokenUsage(promptTokens, candidateTokens, cacheReadTokens, cacheCreationTokens, cacheCreation1hTokens) {
1694
- const { promptPerMillion, candidatePerMillion } = estimatedAnthropicRatesUsdPerMillion(this.model);
1695
- const m = _AnthropicTransport.TOKENS_PER_MILLION;
1696
- const cacheCreation5mTokens = Math.max(0, cacheCreationTokens - cacheCreation1hTokens);
1697
- const promptCost = promptTokens / m * promptPerMillion;
1698
- const cacheReadCost = cacheReadTokens / m * promptPerMillion * ANTHROPIC_CACHE_READ_MULTIPLIER;
1699
- const cacheWriteCost = cacheCreation5mTokens / m * promptPerMillion * ANTHROPIC_CACHE_WRITE_5M_MULTIPLIER + cacheCreation1hTokens / m * promptPerMillion * ANTHROPIC_CACHE_WRITE_1H_MULTIPLIER;
1700
- const candidateCost = candidateTokens / m * candidatePerMillion;
1701
- const totalCost = promptCost + cacheReadCost + cacheWriteCost + candidateCost;
1702
- this.lifetimeCostUsd += totalCost;
1703
- const cacheReadFull = cacheReadTokens / m * promptPerMillion;
1704
- const cacheWriteFull = cacheCreationTokens / m * promptPerMillion;
1705
- const saved = cacheReadFull - cacheReadCost + (cacheWriteFull - cacheWriteCost);
1706
- this.lifetimeSavingsUsd += saved;
1826
+ logTokenUsage(model, promptTokens, candidateTokens, cacheReadTokens, cacheCreationTokens, cacheCreation1hTokens) {
1827
+ const { costUsd, savedUsd, breakdown } = anthropicTokenCost(model, {
1828
+ uncachedInputTokens: promptTokens,
1829
+ outputTokens: candidateTokens,
1830
+ cacheReadTokens,
1831
+ cacheWriteTokens: cacheCreationTokens,
1832
+ cacheWrite1hTokens: cacheCreation1hTokens
1833
+ });
1834
+ this.lifetimeCostUsd += costUsd;
1835
+ this.lifetimeSavingsUsd += savedUsd;
1707
1836
  const dp = _AnthropicTransport.COST_DECIMAL_PLACES;
1708
- console.log(`--- Anthropic Token Usage (${this.model}) ---`);
1709
- console.log(`Prompt Tokens: ${promptTokens} ($${promptCost.toFixed(dp)})`);
1710
- console.log(`Cache Read: ${cacheReadTokens} ($${cacheReadCost.toFixed(dp)})`);
1711
- console.log(`Cache Write: ${cacheCreationTokens} ($${cacheWriteCost.toFixed(dp)})`);
1712
- console.log(`Candidate Tokens: ${candidateTokens} ($${candidateCost.toFixed(dp)})`);
1713
- console.log(`Total Cost: $${totalCost.toFixed(dp)}`);
1714
- console.log(`Cache Saved: $${saved.toFixed(dp)} (lifetime $${this.lifetimeSavingsUsd.toFixed(dp)})`);
1837
+ console.log(`--- Anthropic Token Usage (${model}) ---`);
1838
+ console.log(`Prompt Tokens: ${promptTokens} ($${breakdown.promptUsd.toFixed(dp)})`);
1839
+ console.log(`Cache Read: ${cacheReadTokens} ($${breakdown.cacheReadUsd.toFixed(dp)})`);
1840
+ console.log(`Cache Write: ${cacheCreationTokens} ($${breakdown.cacheWriteUsd.toFixed(dp)})`);
1841
+ console.log(`Candidate Tokens: ${candidateTokens} ($${breakdown.candidateUsd.toFixed(dp)})`);
1842
+ console.log(`Total Cost: $${costUsd.toFixed(dp)}`);
1843
+ console.log(`Cache Saved: $${savedUsd.toFixed(dp)} (lifetime $${this.lifetimeSavingsUsd.toFixed(dp)})`);
1715
1844
  console.log(`Lifetime Cost: $${this.lifetimeCostUsd.toFixed(dp)}`);
1716
1845
  console.log("--------------------------");
1717
- return totalCost;
1846
+ return costUsd;
1718
1847
  }
1719
1848
  /**
1720
1849
  * Convert the internal `ChatMessage[]` history into Anthropic's message format.
@@ -1725,7 +1854,7 @@ var AnthropicTransport = class _AnthropicTransport {
1725
1854
  * Consecutive same-role turns are merged by the API but we merge here to keep
1726
1855
  * the payload tidy.
1727
1856
  */
1728
- toAnthropicMessages(history, userMessage, attachments) {
1857
+ toAnthropicMessages(history, userMessage, attachments, reachableModels = /* @__PURE__ */ new Set([this.model])) {
1729
1858
  var _a, _b, _c;
1730
1859
  const messages = [];
1731
1860
  const pushBlock = (role, block) => {
@@ -1748,6 +1877,9 @@ var AnthropicTransport = class _AnthropicTransport {
1748
1877
  continue;
1749
1878
  }
1750
1879
  if ((_a = msg.toolCalls) === null || _a === void 0 ? void 0 : _a.length) {
1880
+ for (const block of this.reasoningToReplay(msg.toolCalls[0], reachableModels)) {
1881
+ pushBlock("assistant", block);
1882
+ }
1751
1883
  if (msg.content) {
1752
1884
  pushBlock("assistant", { type: "text", text: msg.content });
1753
1885
  }
@@ -1783,20 +1915,140 @@ ${att.content}` });
1783
1915
  }
1784
1916
  return messages;
1785
1917
  }
1918
+ /**
1919
+ * The blocks to replay ahead of a tool call — fallback boundaries then reasoning, or none.
1920
+ *
1921
+ * A `signature` is only valid for the model that produced it, so reasoning captured under a
1922
+ * *different* model is normally dropped: an agent that varies `provider` by state can switch
1923
+ * models mid-loop, and replaying the old model's signatures would send blocks the new one
1924
+ * cannot verify.
1925
+ *
1926
+ * Two ways the producer can be the model that will validate:
1927
+ *
1928
+ * 1. **It is the model we are asking for.** `state.model === this.model` — the ordinary case.
1929
+ * 2. **Sticky routing will send this conversation back to it.** After a conversation falls
1930
+ * back, later requests carrying `fallbacks` go straight to the model that served, without
1931
+ * re-running the one that declined. That is what makes a fallback producer's reasoning
1932
+ * replayable at all (Fable 5 configured, Opus 4.8 serving — the pairing this transport's
1933
+ * own constructor warning recommends). But it holds only while the conversation continues
1934
+ * under the SAME request configuration, which is why `requestedModel` is compared rather
1935
+ * than just checking the chain for the producer.
1936
+ *
1937
+ * That second condition is deliberately narrow. Chain membership alone is too weak: a
1938
+ * fallback target is only *contingently* the server, so an agent that switches `provider` to
1939
+ * a model whose own chain happens to contain the old producer would replay foreign
1940
+ * signatures to whichever model actually answers. Requiring the requested model to be
1941
+ * unchanged separates "this conversation is still going" from "we are somewhere else now".
1942
+ *
1943
+ * State captured before `requestedModel` existed falls back to condition 1 alone, which is
1944
+ * the conservative branch — it can drop reasoning, never misdirect it.
1945
+ *
1946
+ * Boundaries themselves are always echoed: keeping them in place is the documented rule, and
1947
+ * with no thinking blocks around them they are inert rather than harmful.
1948
+ */
1949
+ reasoningToReplay(firstCall, reachableModels) {
1950
+ var _a, _b, _c;
1951
+ const state = (_a = firstCall.providerMetadata) === null || _a === void 0 ? void 0 : _a[ANTHROPIC_PROVIDER_KEY];
1952
+ if (!state)
1953
+ return [];
1954
+ const boundaries = (_b = state.fallbacks) !== null && _b !== void 0 ? _b : [];
1955
+ const stickyToProducer = state.requestedModel === this.model && reachableModels.has(state.model);
1956
+ const vouched = state.model === this.model || stickyToProducer;
1957
+ return [...boundaries, ...vouched ? (_c = state.reasoning) !== null && _c !== void 0 ? _c : [] : []];
1958
+ }
1959
+ /**
1960
+ * The model that actually served this response.
1961
+ *
1962
+ * A server-side `fallbacks` chain re-runs a declined request on another model and
1963
+ * names it in the response's top-level `model`. Pricing must follow that, not the
1964
+ * model we asked for: a Fable 5 request served by Opus 4.8 costed at Fable's
1965
+ * $10/$50 instead of $5/$25 doubles that part of the bill.
1966
+ *
1967
+ * An unrecognised model id is NOT priced at a guessed rate — that is precisely how
1968
+ * a consumer ended up billing Haiku at Sonnet rates. It warns and falls back to the
1969
+ * configured model, which is at least a figure someone chose.
1970
+ */
1971
+ servingModel(response) {
1972
+ const served = response.model;
1973
+ if (!served || served === this.model)
1974
+ return this.model;
1975
+ if (SUPPORTED_ANTHROPIC_MODEL_IDS.includes(served)) {
1976
+ return served;
1977
+ }
1978
+ if (!this.warnedUnknownServingModels.has(served)) {
1979
+ this.warnedUnknownServingModels.add(served);
1980
+ 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.`);
1981
+ }
1982
+ return this.model;
1983
+ }
1984
+ /**
1985
+ * Whether a fallback-chain attempt is a refusal that was **not billed**.
1986
+ *
1987
+ * Anthropic does not charge for a refusal that arrives before any output: the token counts
1988
+ * still appear in `usage`, but they are not on the bill. Pricing them anyway turns the
1989
+ * fallback undercount this reducer was written to fix into an overcount — the documented
1990
+ * example declines with `input_tokens: 535, output_tokens: 0`, which is real money at
1991
+ * Fable 5's prompt rate.
1992
+ *
1993
+ * A **mid-output** refusal *is* billed for the input and whatever it streamed, so output
1994
+ * tokens are the discriminator rather than the refusal itself.
1995
+ *
1996
+ * Declined attempts appear as `type: 'message'`; the attempt that served the turn is
1997
+ * `type: 'fallback_message'`. The serving entry is normally billable — except when every
1998
+ * model in the chain declined, where the last entry is both the serving one and a refusal,
1999
+ * which `lastAndRefused` covers.
2000
+ *
2001
+ * Also called for a response with no chain at all, as `isUnbilledRefusal({}, usage, refused)`:
2002
+ * a direct request that was declined has no `iterations` array, and the same rule applies to
2003
+ * it. That is in fact the common case — `iterations` only appears when `fallbacks` was
2004
+ * configured.
2005
+ */
2006
+ isUnbilledRefusal(attempt, usage, lastAndRefused) {
2007
+ var _a;
2008
+ if (((_a = usage.output_tokens) !== null && _a !== void 0 ? _a : 0) > 0)
2009
+ return false;
2010
+ return attempt.type === "message" || lastAndRefused;
2011
+ }
2012
+ /** Cost one attempt's usage block at `model`'s rates, logging and banking it. */
2013
+ costAttempt(model, usage) {
2014
+ var _a, _b, _c, _d, _e, _f;
2015
+ const cacheRead = (_a = usage.cache_read_input_tokens) !== null && _a !== void 0 ? _a : 0;
2016
+ const breakdown = usage.cache_creation;
2017
+ const cacheCreation1h = (_b = breakdown === null || breakdown === void 0 ? void 0 : breakdown.ephemeral_1h_input_tokens) !== null && _b !== void 0 ? _b : 0;
2018
+ 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;
2019
+ 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);
2020
+ }
1786
2021
  fromAnthropicResponse(response) {
1787
- var _a, _b, _c, _d, _e, _f, _g, _h;
2022
+ var _a, _b, _c, _d, _e, _f, _g;
1788
2023
  let inputTokens;
1789
2024
  let outputTokens;
1790
2025
  let cacheReadTokens;
1791
2026
  let cacheWriteTokens;
1792
2027
  let cost;
2028
+ const servingModel = this.servingModel(response);
1793
2029
  if (response.usage) {
1794
2030
  const uncachedInput = (_a = response.usage.input_tokens) !== null && _a !== void 0 ? _a : 0;
1795
2031
  const cacheRead = (_b = response.usage.cache_read_input_tokens) !== null && _b !== void 0 ? _b : 0;
1796
2032
  const breakdown = response.usage.cache_creation;
1797
2033
  const cacheCreation1h = (_c = breakdown === null || breakdown === void 0 ? void 0 : breakdown.ephemeral_1h_input_tokens) !== null && _c !== void 0 ? _c : 0;
1798
2034
  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;
1799
- cost = this.logTokenUsage(uncachedInput, (_f = response.usage.output_tokens) !== null && _f !== void 0 ? _f : 0, cacheRead, cacheCreation, cacheCreation1h);
2035
+ const attempts = response.usage.iterations;
2036
+ if (attempts === null || attempts === void 0 ? void 0 : attempts.length) {
2037
+ const refused = response.stop_reason === "refusal";
2038
+ cost = attempts.reduce((total, attempt, index) => {
2039
+ var _a2;
2040
+ const usage = (_a2 = attempt.usage) !== null && _a2 !== void 0 ? _a2 : attempt;
2041
+ if (this.isUnbilledRefusal(attempt, usage, index === attempts.length - 1 && refused)) {
2042
+ return total;
2043
+ }
2044
+ const model = attempt.model ? this.servingModel({ model: attempt.model }) : this.model;
2045
+ return total + this.costAttempt(model, usage);
2046
+ }, 0);
2047
+ } else if (this.isUnbilledRefusal({}, response.usage, response.stop_reason === "refusal")) {
2048
+ cost = 0;
2049
+ } else {
2050
+ cost = this.costAttempt(servingModel, response.usage);
2051
+ }
1800
2052
  inputTokens = uncachedInput + cacheRead + cacheCreation;
1801
2053
  cacheReadTokens = cacheRead;
1802
2054
  cacheWriteTokens = cacheCreation;
@@ -1804,23 +2056,44 @@ ${att.content}` });
1804
2056
  outputTokens = response.usage.output_tokens;
1805
2057
  }
1806
2058
  }
1807
- const blocks = (_g = response.content) !== null && _g !== void 0 ? _g : [];
2059
+ const blocks = (_f = response.content) !== null && _f !== void 0 ? _f : [];
1808
2060
  const toolCalls = [];
1809
2061
  const thoughtParts = [];
1810
2062
  const textParts = [];
1811
- for (const block of blocks) {
2063
+ const reasoningBlocks = [];
2064
+ const fallbackIndex = blocks.map((b) => b.type).lastIndexOf("fallback");
2065
+ const fallbackBlocks = blocks.filter((b) => b.type === "fallback");
2066
+ for (const [index, block] of blocks.entries()) {
2067
+ const postBoundary = index > fallbackIndex;
1812
2068
  if (block.type === "tool_use") {
1813
- toolCalls.push({
1814
- id: block.id,
1815
- name: block.name,
1816
- args: (_h = block.input) !== null && _h !== void 0 ? _h : {}
1817
- });
2069
+ if (postBoundary) {
2070
+ toolCalls.push({
2071
+ id: block.id,
2072
+ name: block.name,
2073
+ args: (_g = block.input) !== null && _g !== void 0 ? _g : {}
2074
+ });
2075
+ }
1818
2076
  } else if (block.type === "thinking") {
2077
+ if (postBoundary)
2078
+ reasoningBlocks.push(block);
1819
2079
  thoughtParts.push(block.thinking);
2080
+ } else if (block.type === "redacted_thinking") {
2081
+ if (postBoundary)
2082
+ reasoningBlocks.push(block);
1820
2083
  } else if (block.type === "text") {
1821
2084
  textParts.push(block.text);
1822
2085
  }
1823
2086
  }
2087
+ if (toolCalls.length > 0 && (reasoningBlocks.length > 0 || fallbackBlocks.length > 0)) {
2088
+ const state = {
2089
+ reasoning: reasoningBlocks,
2090
+ model: servingModel,
2091
+ requestedModel: this.model
2092
+ };
2093
+ if (fallbackBlocks.length > 0)
2094
+ state.fallbacks = fallbackBlocks;
2095
+ toolCalls[0].providerMetadata = Object.assign(Object.assign({}, toolCalls[0].providerMetadata), { [ANTHROPIC_PROVIDER_KEY]: state });
2096
+ }
1824
2097
  if (response.stop_reason === "max_tokens" && toolCalls.length > 0) {
1825
2098
  throw new ResponseTruncatedError(this.model, this.maxTokens, outputTokens, toolCalls.map((tc) => tc.name));
1826
2099
  }
@@ -1836,6 +2109,7 @@ ${att.content}` });
1836
2109
  base.cacheWriteTokens = cacheWriteTokens;
1837
2110
  if (cost != null)
1838
2111
  base.cost = cost;
2112
+ base.model = servingModel;
1839
2113
  if (response.stop_reason === "max_tokens") {
1840
2114
  base.responseMeta = { finishReason: "max_tokens" };
1841
2115
  }
@@ -2137,43 +2411,25 @@ function assertSupportedGeminiModel(model) {
2137
2411
  throw new Error(`GeminiTransport: unsupported model "${model}". Use one of: ${SUPPORTED_GEMINI_MODEL_IDS.join(", ")}.`);
2138
2412
  }
2139
2413
  }
2140
- var GEMINI_LONG_CONTEXT_THRESHOLD = 2e5;
2141
- var GEMINI_CACHED_INPUT_MULTIPLIER = 0.1;
2142
- var GEMINI_PRICING = {
2143
- "gemini-2.5-flash-lite": {
2144
- kind: "flat",
2145
- rate: { promptPerMillion: 0.1, candidatePerMillion: 0.4 }
2146
- },
2147
- "gemini-2.5-flash": { kind: "flat", rate: { promptPerMillion: 0.3, candidatePerMillion: 2.5 } },
2148
- "gemini-2.5-pro": {
2149
- kind: "tiered",
2150
- standard: { promptPerMillion: 1.25, candidatePerMillion: 10 },
2151
- longContext: { promptPerMillion: 2.5, candidatePerMillion: 15 }
2152
- },
2153
- "gemini-3.1-flash-lite": {
2154
- kind: "flat",
2155
- rate: { promptPerMillion: 0.25, candidatePerMillion: 1.5 }
2156
- },
2157
- "gemini-3.5-flash": { kind: "flat", rate: { promptPerMillion: 1.5, candidatePerMillion: 9 } },
2158
- "gemini-3.1-pro-preview": {
2159
- kind: "tiered",
2160
- standard: { promptPerMillion: 2, candidatePerMillion: 12 },
2161
- longContext: { promptPerMillion: 4, candidatePerMillion: 18 }
2162
- }
2163
- };
2164
- function estimatedGeminiPaidRatesUsdPerMillion(model, promptTokens) {
2165
- const pricing = GEMINI_PRICING[model];
2166
- if (pricing.kind === "flat") {
2167
- return pricing.rate;
2168
- }
2169
- return promptTokens > GEMINI_LONG_CONTEXT_THRESHOLD ? pricing.longContext : pricing.standard;
2170
- }
2171
2414
  var GEMINI_MODEL_WARNINGS = {
2172
2415
  "gemini-2.5-flash": "GeminiTransport: using gemini-2.5-flash \u2014 higher cost than flash-lite; use for harder reasoning or agent tasks.",
2173
2416
  "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.",
2174
2417
  "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.",
2175
2418
  "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."
2176
2419
  };
2420
+ var GEMINI_THINKING_DISABLEABLE = [
2421
+ "gemini-2.5-flash",
2422
+ "gemini-2.5-flash-lite"
2423
+ ];
2424
+ function geminiThinkingConfig(model, policy) {
2425
+ if (policy === "off" && GEMINI_THINKING_DISABLEABLE.includes(model)) {
2426
+ return { includeThoughts: false, thinkingBudget: 0 };
2427
+ }
2428
+ if (policy === "auto" && GEMINI_THINKING_DISABLEABLE.includes(model)) {
2429
+ return { includeThoughts: true, thinkingBudget: -1 };
2430
+ }
2431
+ return { includeThoughts: true };
2432
+ }
2177
2433
  var SKIP_SIGNATURE = "skip_thought_signature_validator";
2178
2434
  var GEMINI_PROVIDER_KEY = "gemini";
2179
2435
  var MalformedFunctionCallError = class extends Error {
@@ -2184,10 +2440,28 @@ var MalformedFunctionCallError = class extends Error {
2184
2440
  }
2185
2441
  };
2186
2442
  var GeminiTransport = class _GeminiTransport {
2443
+ /**
2444
+ * Warn once when a requested policy is silently clamped. Turning thinking off is a *cost*
2445
+ * decision, so a caller who asked for it and kept paying for reasoning tokens needs to hear
2446
+ * about it — but only once, not per turn.
2447
+ *
2448
+ * Only `'off'` can actually be denied. `'auto'` is already what an omitted budget produces on
2449
+ * every model here — dynamic thinking is the documented default — so warning that it was
2450
+ * "ignored" would be false, and latching on it would spend the one warning the genuinely
2451
+ * unhonourable `'off'` needs.
2452
+ */
2453
+ warnIfThinkingUnclampable(policy) {
2454
+ if (policy !== "off" || GEMINI_THINKING_DISABLEABLE.includes(this.model) || this.warnedThinkingClamped) {
2455
+ return;
2456
+ }
2457
+ this.warnedThinkingClamped = true;
2458
+ 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.`);
2459
+ }
2187
2460
  constructor(config = {}) {
2188
2461
  var _a, _b, _c;
2189
2462
  this.lifetimeCostUsd = 0;
2190
2463
  this.lifetimeSavingsUsd = 0;
2464
+ this.warnedThinkingClamped = false;
2191
2465
  const model = (_a = config.model) !== null && _a !== void 0 ? _a : DEFAULT_MODEL2;
2192
2466
  assertSupportedGeminiModel(model);
2193
2467
  this.model = model;
@@ -2254,7 +2528,8 @@ var GeminiTransport = class _GeminiTransport {
2254
2528
  ] : void 0;
2255
2529
  const systemInstruction = (options === null || options === void 0 ? void 0 : options.systemPrompt) ? { role: "system", parts: [{ text: options.systemPrompt }] } : void 0;
2256
2530
  const toolConfig = tools ? toGeminiToolConfig(options === null || options === void 0 ? void 0 : options.toolChoice) : void 0;
2257
- const generationConfig = { thinkingConfig: { includeThoughts: true } };
2531
+ this.warnIfThinkingUnclampable(options === null || options === void 0 ? void 0 : options.thinkingPolicy);
2532
+ const generationConfig = { thinkingConfig: geminiThinkingConfig(this.model, options === null || options === void 0 ? void 0 : options.thinkingPolicy) };
2258
2533
  if ((options === null || options === void 0 ? void 0 : options.temperature) != null) {
2259
2534
  generationConfig.temperature = scaleTemperature(options.temperature, {
2260
2535
  defaultTemp: GEMINI_DEFAULT_TEMPERATURE,
@@ -2281,25 +2556,23 @@ var GeminiTransport = class _GeminiTransport {
2281
2556
  * message.
2282
2557
  */
2283
2558
  logTokenUsage(promptTokens, candidateTokens, thoughtTokens, cachedTokens) {
2284
- const { promptPerMillion, candidatePerMillion } = estimatedGeminiPaidRatesUsdPerMillion(this.model, promptTokens);
2285
- const m = _GeminiTransport.TOKENS_PER_MILLION;
2286
- const uncachedPromptTokens = Math.max(0, promptTokens - cachedTokens);
2287
- const promptCost = uncachedPromptTokens / m * promptPerMillion;
2288
- const cacheReadCost = cachedTokens / m * promptPerMillion * GEMINI_CACHED_INPUT_MULTIPLIER;
2289
- const candidateCost = (candidateTokens + thoughtTokens) / m * candidatePerMillion;
2290
- const totalCost = promptCost + cacheReadCost + candidateCost;
2291
- this.lifetimeCostUsd += totalCost;
2292
- const saved = cachedTokens / m * promptPerMillion - cacheReadCost;
2293
- this.lifetimeSavingsUsd += saved;
2559
+ const { costUsd, savedUsd, breakdown } = geminiTokenCost(this.model, {
2560
+ promptTokens,
2561
+ candidateTokens,
2562
+ thoughtTokens,
2563
+ cachedTokens
2564
+ });
2565
+ this.lifetimeCostUsd += costUsd;
2566
+ this.lifetimeSavingsUsd += savedUsd;
2294
2567
  const dp = _GeminiTransport.COST_DECIMAL_PLACES;
2295
2568
  console.log(`--- Gemini Token Usage (${this.model}) ---`);
2296
- console.log(`Prompt Tokens: ${promptTokens} (${cachedTokens} cached) ($${promptCost.toFixed(dp)} + $${cacheReadCost.toFixed(dp)})`);
2297
- console.log(`Candidate Tokens: ${candidateTokens} (+${thoughtTokens} thinking) ($${candidateCost.toFixed(dp)})`);
2298
- console.log(`Total Cost: $${totalCost.toFixed(dp)}`);
2299
- console.log(`Cache Saved: $${saved.toFixed(dp)} (lifetime $${this.lifetimeSavingsUsd.toFixed(dp)})`);
2569
+ console.log(`Prompt Tokens: ${promptTokens} (${cachedTokens} cached) ($${breakdown.promptUsd.toFixed(dp)} + $${breakdown.cacheReadUsd.toFixed(dp)})`);
2570
+ console.log(`Candidate Tokens: ${candidateTokens} (+${thoughtTokens} thinking) ($${breakdown.candidateUsd.toFixed(dp)})`);
2571
+ console.log(`Total Cost: $${costUsd.toFixed(dp)}`);
2572
+ console.log(`Cache Saved: $${savedUsd.toFixed(dp)} (lifetime $${this.lifetimeSavingsUsd.toFixed(dp)})`);
2300
2573
  console.log(`Lifetime Cost: $${this.lifetimeCostUsd.toFixed(dp)}`);
2301
2574
  console.log("--------------------------");
2302
- return totalCost;
2575
+ return costUsd;
2303
2576
  }
2304
2577
  toGeminiContents(history, userMessage, attachments) {
2305
2578
  var _a, _b, _c;
@@ -3270,6 +3543,18 @@ function emptyUsage() {
3270
3543
  outputTokens: 0
3271
3544
  };
3272
3545
  }
3546
+ function totalTokens(usage) {
3547
+ return usage.uncachedInputTokens + usage.cacheReadTokens + usage.cacheWriteTokens + usage.outputTokens;
3548
+ }
3549
+ function addUsage(a, b) {
3550
+ return {
3551
+ costUsd: a.costUsd + b.costUsd,
3552
+ uncachedInputTokens: a.uncachedInputTokens + b.uncachedInputTokens,
3553
+ cacheReadTokens: a.cacheReadTokens + b.cacheReadTokens,
3554
+ cacheWriteTokens: a.cacheWriteTokens + b.cacheWriteTokens,
3555
+ outputTokens: a.outputTokens + b.outputTokens
3556
+ };
3557
+ }
3273
3558
  function sumUsage(messages) {
3274
3559
  const total = emptyUsage();
3275
3560
  accumulate(messages, total);
@@ -3758,6 +4043,7 @@ var ChatDriver = class _ChatDriver extends EventTarget {
3758
4043
  this.activeTemperatureInput = config.temperature;
3759
4044
  this.activeToolChoiceInput = config.toolChoice;
3760
4045
  this.activeCachePolicyInput = config.cachePolicy;
4046
+ this.activeThinkingPolicyInput = config.thinkingPolicy;
3761
4047
  this.activeTailContextInput = config.tailContext;
3762
4048
  this.activeResponseSchemaInput = config.responseSchema;
3763
4049
  this.activeFallbacks = config.fallbacks;
@@ -4412,16 +4698,27 @@ Output format (strict):
4412
4698
  * `condenseWhen` can register against the right call (it stamps the clocks
4413
4699
  * straight off the driver). Absent for dispatch paths with no addressable tool
4414
4700
  * call (e.g. the fold-close handler), where `condenseWhen` is a no-op.
4415
- * @param traceCapture - Optional per-invocation slot. When provided, the trace
4416
- * from any sub-agent call is written here rather than to shared instance state,
4417
- * so parallel tool calls each capture their own trace independently.
4701
+ * @param traceCapture - Optional per-tool-call accumulator. When provided, every
4702
+ * sub-agent call's trace is **appended** here rather than written to shared
4703
+ * instance state, so parallel tool calls each capture their own traces
4704
+ * independently.
4705
+ *
4706
+ * Deliberately a list of traces, not one slot. A single handler may call
4707
+ * `requestSubAgent` more than once — a code-driven scheduler dispatching a
4708
+ * dependency graph, a retry of a timed-out child, any fan-out helper — and a
4709
+ * single slot kept only the last, so every other child ran, was billed by the
4710
+ * provider, and then vanished from history. The loss was silent in the worst
4711
+ * way: `sumUsage` and `usageRows` both recurse into the trace, so they summed a
4712
+ * truncated input and still agreed with each other. Measured at 1 of 7 traces
4713
+ * kept on a seven-way fan-out, reporting ~2.3x under the real cost, with the
4714
+ * error growing as the fan-out widens.
4418
4715
  */
4419
4716
  buildHandlerContext(activeToolCallId, traceCapture) {
4420
4717
  return {
4421
4718
  requestInteraction: (componentName, data, options) => this.requestInteraction(componentName, data, options),
4422
4719
  ...this.subAgentsMap.size > 0 && {
4423
4720
  requestSubAgent: (name, options) => this.invokeSubAgent(name, options).then(({ outcome, trace }) => {
4424
- if (traceCapture) traceCapture.trace = trace;
4721
+ if (traceCapture && trace) traceCapture.traces.push(trace);
4425
4722
  return outcome;
4426
4723
  })
4427
4724
  },
@@ -4795,6 +5092,8 @@ Output format (strict):
4795
5092
  let emptyResponseAttempts = 0;
4796
5093
  let setupTransportAttempts = 0;
4797
5094
  let firstLlmCall = !!currentInput;
5095
+ let pinnedThinkingPolicy;
5096
+ let thinkingPolicyPinned = false;
4798
5097
  while (iterations < this.maxToolIterations) {
4799
5098
  iterations += 1;
4800
5099
  this.modelCallSeq += 1;
@@ -4894,7 +5193,8 @@ Output format (strict):
4894
5193
  resolvedToolChoice,
4895
5194
  resolvedCachePolicy,
4896
5195
  resolvedTailContext,
4897
- resolvedResponseSchema
5196
+ resolvedResponseSchema,
5197
+ firstResolvedThinkingPolicy
4898
5198
  ] = (
4899
5199
  // oxlint-disable-next-line no-await-in-loop
4900
5200
  await Promise.all([
@@ -4902,9 +5202,19 @@ Output format (strict):
4902
5202
  this.resolveTurnInput(this.activeToolChoiceInput, promptCtx),
4903
5203
  this.resolveTurnInput(this.activeCachePolicyInput, promptCtx),
4904
5204
  this.resolveTurnInput(this.activeTailContextInput, promptCtx),
4905
- this.resolveTurnInput(this.activeResponseSchemaInput, promptCtx)
5205
+ this.resolveTurnInput(this.activeResponseSchemaInput, promptCtx),
5206
+ // Only consulted on the first iteration (see `pinnedThinkingPolicy`); resolved
5207
+ // alongside the others so a resolver still sees the same turn context.
5208
+ thinkingPolicyPinned ? Promise.resolve(void 0) : this.resolveTurnInput(
5209
+ this.activeThinkingPolicyInput,
5210
+ promptCtx
5211
+ )
4906
5212
  ])
4907
5213
  );
5214
+ if (!thinkingPolicyPinned) {
5215
+ pinnedThinkingPolicy = firstResolvedThinkingPolicy;
5216
+ thinkingPolicyPinned = true;
5217
+ }
4908
5218
  const systemPrompt = resolvedSystemPrompt || void 0;
4909
5219
  const tailBody = [resolvedTailContext, frameworkSystemSuffix].map((part) => (part ?? "").trim()).filter((part) => part.length > 0).join("\n\n");
4910
5220
  const tailContext = tailBody ? `<system-reminder>
@@ -4931,6 +5241,12 @@ ${tailBody}
4931
5241
  // Prompt-cache policy for this turn (Anthropic places breakpoints per scope; Gemini
4932
5242
  // caches implicitly regardless). Undefined → no caching requested.
4933
5243
  cachePolicy: resolvedCachePolicy,
5244
+ // Extended-thinking posture, pinned for the whole tool loop (one assistant turn) rather
5245
+ // than re-resolved per iteration — see `pinnedThinkingPolicy`. Undefined — unset, or the
5246
+ // resolver's answer for this turn — is NOT "off": it leaves the model on its own default,
5247
+ // so agents that never set this are priced exactly as before. Transports clamp models
5248
+ // that can't honour it.
5249
+ thinkingPolicy: pinnedThinkingPolicy,
4934
5250
  // Framed volatile context injected at the message tail (never stored). Undefined → none.
4935
5251
  tailContext,
4936
5252
  // Structured-output schema for this turn (agent/state-resolved). When set, the transport
@@ -5051,7 +5367,9 @@ ${tailBody}
5051
5367
  }
5052
5368
  throw e;
5053
5369
  }
5054
- if (this.lastResolvedModel !== void 0) response.model = this.lastResolvedModel;
5370
+ if (response.model === void 0 && this.lastResolvedModel !== void 0) {
5371
+ response.model = this.lastResolvedModel;
5372
+ }
5055
5373
  if (this.lastResolvedProvider !== void 0) response.provider = this.lastResolvedProvider;
5056
5374
  if (this.lastResolvedProviderName !== void 0) {
5057
5375
  response.providerName = this.lastResolvedProviderName;
@@ -5259,14 +5577,18 @@ ${tailBody}
5259
5577
  }
5260
5578
  return;
5261
5579
  }
5580
+ const traceCapture = { traces: [] };
5581
+ const capturedTrace = () => traceCapture.traces.length ? traceCapture.traces.flat() : void 0;
5262
5582
  try {
5263
- const traceCapture = {};
5264
5583
  const result = await handler(tc.args, this.buildHandlerContext(tc.id, traceCapture));
5265
5584
  const content = typeof result === "string" ? result : JSON.stringify(result);
5266
5585
  executedById.set(tc.id, {
5267
5586
  toolCallId: tc.id,
5268
5587
  content,
5269
- subAgentTrace: traceCapture.trace
5588
+ // Concatenated when a handler invoked several children, so none is lost.
5589
+ // Stays `undefined` when nothing was captured — readers key off presence,
5590
+ // and an empty array is a different claim from "no sub-agent ran".
5591
+ subAgentTrace: capturedTrace()
5270
5592
  });
5271
5593
  anyRealToolExecuted = true;
5272
5594
  } catch (e) {
@@ -5281,7 +5603,10 @@ ${tailBody}
5281
5603
  // Structured recovery hint so the model retries or routes around a tool
5282
5604
  // failure instead of apologising and giving up.
5283
5605
  content: `Tool error: ${e.message}
5284
- 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.`
5606
+ 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.`,
5607
+ // Children that completed before the throw were still billed — keep
5608
+ // their traces so the run's cost stays whole.
5609
+ subAgentTrace: capturedTrace()
5285
5610
  });
5286
5611
  anyRealToolExecuted = true;
5287
5612
  }
@@ -6073,6 +6398,10 @@ function defineStatefulAgent(opts) {
6073
6398
  }
6074
6399
  return opts.cachePolicy({ ...ctx, state });
6075
6400
  } : opts.cachePolicy;
6401
+ const wrappedThinkingPolicy = typeof opts.thinkingPolicy === "function" ? async (ctx) => {
6402
+ if (!state) return void 0;
6403
+ return opts.thinkingPolicy({ ...ctx, state });
6404
+ } : opts.thinkingPolicy;
6076
6405
  const wrappedTailContext = typeof opts.tailContext === "function" ? async (ctx) => {
6077
6406
  if (!state) {
6078
6407
  throw new Error(`Stateful agent "${opts.name}" tailContext called before init`);
@@ -6111,6 +6440,7 @@ function defineStatefulAgent(opts) {
6111
6440
  temperature: wrappedTemperature,
6112
6441
  toolChoice: wrappedToolChoice,
6113
6442
  cachePolicy: wrappedCachePolicy,
6443
+ thinkingPolicy: wrappedThinkingPolicy,
6114
6444
  tailContext: wrappedTailContext,
6115
6445
  onUnresolvedTool: wrappedOnUnresolvedTool,
6116
6446
  resumable: wrappedResumable,
@@ -6172,6 +6502,69 @@ var strictFallbackAgent = {
6172
6502
  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}}.`
6173
6503
  };
6174
6504
 
6505
+ // src/utils/usage-rows.ts
6506
+ function hasUsage(m) {
6507
+ return m.cost != null || m.externalCostUsd != null || m.inputTokens != null || m.outputTokens != null || m.cacheReadTokens != null || m.cacheWriteTokens != null;
6508
+ }
6509
+ function usageRows(messages) {
6510
+ return collectRows(messages, 0, void 0);
6511
+ }
6512
+ function bucketsOf(usage) {
6513
+ return {
6514
+ uncachedInputTokens: usage.uncachedInputTokens,
6515
+ cacheReadTokens: usage.cacheReadTokens,
6516
+ cacheWriteTokens: usage.cacheWriteTokens,
6517
+ outputTokens: usage.outputTokens
6518
+ };
6519
+ }
6520
+ function collectRows(messages, depth, subAgentOf) {
6521
+ const rows = [];
6522
+ for (const m of messages) {
6523
+ if (hasUsage(m)) {
6524
+ const cacheReadTokens = m.cacheReadTokens ?? 0;
6525
+ const cacheWriteTokens = m.cacheWriteTokens ?? 0;
6526
+ rows.push({
6527
+ source: "request",
6528
+ model: m.model,
6529
+ // The driver stamps `ChatMessage.provider` from the resolved provider's own
6530
+ // status, so it is AUTHORITATIVE and covers vendors the model allowlists do
6531
+ // not — a server-proxied `gpt-5` turn is `provider: 'openai'` on the message
6532
+ // and unknown to `vendorOfModel`. Prefer it; fall back to resolving the model
6533
+ // id only for messages persisted before the field existed. Still left
6534
+ // undefined when neither knows, rather than guessed at.
6535
+ provider: m.provider ?? (m.model ? vendorOfModel(m.model) : void 0),
6536
+ ...m.cost != null && { costUsd: m.cost },
6537
+ ...m.externalCostUsd != null && { externalCostUsd: m.externalCostUsd },
6538
+ // Uncached input is the REMAINDER, matching `sumUsage`: `inputTokens` is the
6539
+ // whole prompt and the cache fields break it down. Clamped for the same
6540
+ // reason — hand-edited history must not drive a total negative.
6541
+ uncachedInputTokens: Math.max(0, (m.inputTokens ?? 0) - cacheReadTokens - cacheWriteTokens),
6542
+ cacheReadTokens,
6543
+ cacheWriteTokens,
6544
+ outputTokens: m.outputTokens ?? 0,
6545
+ ...m.agentName != null && { agentName: m.agentName },
6546
+ subAgentDepth: depth,
6547
+ ...subAgentOf != null && { subAgentOf }
6548
+ });
6549
+ }
6550
+ const rolled = m.compaction?.rolledUpUsage;
6551
+ if (rolled) {
6552
+ rows.push({
6553
+ source: "compaction",
6554
+ ...rolled.costUsd != null && { costUsd: rolled.costUsd },
6555
+ ...bucketsOf(rolled),
6556
+ ...m.agentName != null && { agentName: m.agentName },
6557
+ subAgentDepth: depth,
6558
+ ...subAgentOf != null && { subAgentOf }
6559
+ });
6560
+ }
6561
+ for (const tc of m.toolCalls ?? []) {
6562
+ if (tc.subAgentTrace) rows.push(...collectRows(tc.subAgentTrace, depth + 1, tc.id));
6563
+ }
6564
+ }
6565
+ return rows;
6566
+ }
6567
+
6175
6568
  // src/utils/flatten-sub-agent-messages.ts
6176
6569
  function flattenSubAgentMessages(messages, prefix = "", depth = 0, subAgentOf) {
6177
6570
  return messages.flatMap((m) => {
@@ -6252,10 +6645,15 @@ function assembleDebugLog(entries, readme) {
6252
6645
  return { readme, timeline, meta: latestMeta?.meta };
6253
6646
  }
6254
6647
  export {
6648
+ ANTHROPIC_CACHE_READ_MULTIPLIER,
6649
+ ANTHROPIC_CACHE_WRITE_1H_MULTIPLIER,
6650
+ ANTHROPIC_CACHE_WRITE_5M_MULTIPLIER,
6255
6651
  AgenticActivityBus,
6256
6652
  AnthropicProvider,
6257
6653
  AnthropicTransport,
6258
6654
  ChatDriver,
6655
+ GEMINI_CACHED_INPUT_MULTIPLIER,
6656
+ GEMINI_LONG_CONTEXT_THRESHOLD,
6259
6657
  GeminiProvider,
6260
6658
  GeminiTransport,
6261
6659
  MutableAIProviderRegistry,
@@ -6264,16 +6662,26 @@ export {
6264
6662
  REQUEST_CONTINUATION_TOOL,
6265
6663
  SUPPORTED_ANTHROPIC_MODEL_IDS,
6266
6664
  SUPPORTED_GEMINI_MODEL_IDS,
6665
+ addUsage,
6267
6666
  agenticActivityBus,
6667
+ anthropicRatesFor,
6668
+ anthropicTokenCost,
6268
6669
  assembleDebugLog,
6269
6670
  buildTimelineEntries,
6270
6671
  clearSession,
6271
6672
  defineAgent,
6272
6673
  defineStatefulAgent,
6674
+ emptyUsage,
6273
6675
  friendlyFallbackAgent,
6676
+ geminiRatesFor,
6677
+ geminiTokenCost,
6274
6678
  getMetaEvents,
6275
6679
  isObservableAIProviderRegistry,
6276
6680
  restoreMachine,
6277
- strictFallbackAgent
6681
+ strictFallbackAgent,
6682
+ sumUsage,
6683
+ totalTokens,
6684
+ usageRows,
6685
+ vendorOfModel
6278
6686
  };
6279
6687
  //# sourceMappingURL=chat-driver.mjs.map