@genesislcap/ai-assistant 15.4.1 → 15.6.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 (98) hide show
  1. package/dist/ai-assistant.api.json +571 -82
  2. package/dist/ai-assistant.d.ts +336 -36
  3. package/dist/chat-driver.cjs +104 -26
  4. package/dist/chat-driver.cjs.map +3 -3
  5. package/dist/chat-driver.mjs +104 -26
  6. package/dist/chat-driver.mjs.map +3 -3
  7. package/dist/custom-elements.json +314 -36
  8. package/dist/dts/components/chat-driver/chat-driver.d.ts +12 -0
  9. package/dist/dts/components/chat-driver/chat-driver.d.ts.map +1 -1
  10. package/dist/dts/components/settings-modal/settings-modal.styles.d.ts.map +1 -1
  11. package/dist/dts/components/settings-modal/settings-modal.template.d.ts +9 -2
  12. package/dist/dts/components/settings-modal/settings-modal.template.d.ts.map +1 -1
  13. package/dist/dts/index.d.ts +1 -0
  14. package/dist/dts/index.d.ts.map +1 -1
  15. package/dist/dts/main/cost-session-banking.test.d.ts +2 -0
  16. package/dist/dts/main/cost-session-banking.test.d.ts.map +1 -0
  17. package/dist/dts/main/main.d.ts +184 -24
  18. package/dist/dts/main/main.d.ts.map +1 -1
  19. package/dist/dts/provider/assistant-app-settings.d.ts +30 -5
  20. package/dist/dts/provider/assistant-app-settings.d.ts.map +1 -1
  21. package/dist/dts/state/ai-assistant-slice.d.ts +11 -8
  22. package/dist/dts/state/ai-assistant-slice.d.ts.map +1 -1
  23. package/dist/dts/state/persistence/session-persistence-provider.d.ts +23 -0
  24. package/dist/dts/state/persistence/session-persistence-provider.d.ts.map +1 -1
  25. package/dist/dts/state/persistence/session-snapshot.d.ts.map +1 -1
  26. package/dist/dts/state/session-store.d.ts +1 -2
  27. package/dist/dts/state/session-store.d.ts.map +1 -1
  28. package/dist/dts/styles/settings-section.d.ts +29 -0
  29. package/dist/dts/styles/settings-section.d.ts.map +1 -0
  30. package/dist/dts/utils/condense-history.d.ts +14 -0
  31. package/dist/dts/utils/condense-history.d.ts.map +1 -1
  32. package/dist/dts/utils/cost-session-history.d.ts +103 -12
  33. package/dist/dts/utils/cost-session-history.d.ts.map +1 -1
  34. package/dist/dts/utils/resolve-cost-history-config.d.ts +9 -3
  35. package/dist/dts/utils/resolve-cost-history-config.d.ts.map +1 -1
  36. package/dist/dts/utils/sum-costs.d.ts.map +1 -1
  37. package/dist/dts/utils/sum-tokens.d.ts +8 -8
  38. package/dist/dts/utils/sum-tokens.d.ts.map +1 -1
  39. package/dist/dts/utils/sum-usage.d.ts +59 -0
  40. package/dist/dts/utils/sum-usage.d.ts.map +1 -0
  41. package/dist/dts/utils/sum-usage.test.d.ts +2 -0
  42. package/dist/dts/utils/sum-usage.test.d.ts.map +1 -0
  43. package/dist/esm/components/chat-driver/chat-driver.js +9 -1
  44. package/dist/esm/components/settings-modal/settings-modal.styles.js +237 -18
  45. package/dist/esm/components/settings-modal/settings-modal.template.js +229 -73
  46. package/dist/esm/index.js +1 -0
  47. package/dist/esm/main/cost-session-banking.test.js +308 -0
  48. package/dist/esm/main/main.js +424 -71
  49. package/dist/esm/state/ai-assistant-slice.js +11 -8
  50. package/dist/esm/state/ai-assistant-slice.test.js +12 -5
  51. package/dist/esm/state/debug-event-log.js +2 -2
  52. package/dist/esm/state/persistence/session-persistence.integration.test.js +5 -1
  53. package/dist/esm/state/persistence/session-persister.js +2 -2
  54. package/dist/esm/state/persistence/session-persister.test.js +10 -1
  55. package/dist/esm/state/persistence/session-snapshot.js +6 -2
  56. package/dist/esm/state/persistence/session-snapshot.test.js +4 -1
  57. package/dist/esm/state/persistence/stateful-restore.e2e.test.js +10 -1
  58. package/dist/esm/styles/settings-section.js +39 -0
  59. package/dist/esm/utils/condense-history.js +18 -5
  60. package/dist/esm/utils/condense-history.test.js +67 -0
  61. package/dist/esm/utils/cost-session-history.js +92 -15
  62. package/dist/esm/utils/cost-session-history.test.js +155 -13
  63. package/dist/esm/utils/resolve-cost-history-config.js +2 -1
  64. package/dist/esm/utils/sum-costs.js +2 -13
  65. package/dist/esm/utils/sum-tokens.js +10 -27
  66. package/dist/esm/utils/sum-tokens.test.js +1 -5
  67. package/dist/esm/utils/sum-usage.js +123 -0
  68. package/dist/esm/utils/sum-usage.test.js +120 -0
  69. package/dist/tsconfig.tsbuildinfo +1 -1
  70. package/package.json +17 -17
  71. package/src/components/chat-driver/chat-driver.ts +21 -0
  72. package/src/components/settings-modal/settings-modal.styles.ts +237 -18
  73. package/src/components/settings-modal/settings-modal.template.ts +270 -81
  74. package/src/index.ts +1 -0
  75. package/src/main/cost-session-banking.test.ts +407 -0
  76. package/src/main/main.ts +433 -68
  77. package/src/provider/assistant-app-settings.ts +31 -5
  78. package/src/state/ai-assistant-slice.test.ts +12 -5
  79. package/src/state/ai-assistant-slice.ts +21 -13
  80. package/src/state/debug-event-log.ts +2 -2
  81. package/src/state/persistence/session-persistence-provider.ts +24 -0
  82. package/src/state/persistence/session-persistence.integration.test.ts +8 -1
  83. package/src/state/persistence/session-persister.test.ts +10 -1
  84. package/src/state/persistence/session-persister.ts +2 -2
  85. package/src/state/persistence/session-snapshot.test.ts +4 -1
  86. package/src/state/persistence/session-snapshot.ts +5 -1
  87. package/src/state/persistence/stateful-restore.e2e.test.ts +9 -1
  88. package/src/styles/settings-section.ts +40 -0
  89. package/src/utils/condense-history.test.ts +103 -0
  90. package/src/utils/condense-history.ts +33 -3
  91. package/src/utils/cost-session-history.test.ts +187 -16
  92. package/src/utils/cost-session-history.ts +142 -23
  93. package/src/utils/resolve-cost-history-config.ts +10 -3
  94. package/src/utils/sum-costs.ts +2 -9
  95. package/src/utils/sum-tokens.test.ts +1 -11
  96. package/src/utils/sum-tokens.ts +10 -26
  97. package/src/utils/sum-usage.test.ts +140 -0
  98. package/src/utils/sum-usage.ts +130 -0
@@ -1518,6 +1518,8 @@ ${att.content}` });
1518
1518
  var _a, _b, _c, _d, _e, _f, _g, _h;
1519
1519
  let inputTokens;
1520
1520
  let outputTokens;
1521
+ let cacheReadTokens;
1522
+ let cacheWriteTokens;
1521
1523
  let cost;
1522
1524
  if (response.usage) {
1523
1525
  const uncachedInput = (_a = response.usage.input_tokens) !== null && _a !== void 0 ? _a : 0;
@@ -1527,6 +1529,8 @@ ${att.content}` });
1527
1529
  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;
1528
1530
  cost = this.logTokenUsage(uncachedInput, (_f = response.usage.output_tokens) !== null && _f !== void 0 ? _f : 0, cacheRead, cacheCreation, cacheCreation1h);
1529
1531
  inputTokens = uncachedInput + cacheRead + cacheCreation;
1532
+ cacheReadTokens = cacheRead;
1533
+ cacheWriteTokens = cacheCreation;
1530
1534
  if (response.usage.output_tokens != null) {
1531
1535
  outputTokens = response.usage.output_tokens;
1532
1536
  }
@@ -1557,6 +1561,10 @@ ${att.content}` });
1557
1561
  base.inputTokens = inputTokens;
1558
1562
  if (outputTokens != null)
1559
1563
  base.outputTokens = outputTokens;
1564
+ if (cacheReadTokens != null)
1565
+ base.cacheReadTokens = cacheReadTokens;
1566
+ if (cacheWriteTokens != null)
1567
+ base.cacheWriteTokens = cacheWriteTokens;
1560
1568
  if (cost != null)
1561
1569
  base.cost = cost;
1562
1570
  if (response.stop_reason === "max_tokens") {
@@ -2083,28 +2091,45 @@ ${att.content}`
2083
2091
  }
2084
2092
  return contents;
2085
2093
  }
2094
+ /**
2095
+ * Map Gemini's `usageMetadata` onto the provider-agnostic usage fields, pricing
2096
+ * the call as a side effect (`logTokenUsage`). Returns an empty object when the
2097
+ * response carries no usage — every field is optional, so an absent count stays
2098
+ * `undefined` rather than being reported as a measured 0.
2099
+ *
2100
+ * Split out of `fromGeminiResponse` because it is self-contained bookkeeping
2101
+ * that reads better alone — and because folding it inline pushed that method
2102
+ * past the complexity ceiling.
2103
+ */
2104
+ usageFromGemini(usageMetadata) {
2105
+ var _a, _b, _c, _d, _e, _f;
2106
+ if (!usageMetadata)
2107
+ return {};
2108
+ const usage = usageMetadata;
2109
+ const cost = this.logTokenUsage((_a = usage.promptTokenCount) !== null && _a !== void 0 ? _a : 0, (_b = usage.candidatesTokenCount) !== null && _b !== void 0 ? _b : 0, (_c = usage.thoughtsTokenCount) !== null && _c !== void 0 ? _c : 0, (_d = usage.cachedContentTokenCount) !== null && _d !== void 0 ? _d : 0);
2110
+ return {
2111
+ inputTokens: usage.promptTokenCount,
2112
+ // Thinking tokens are generated output, billed at the candidate rate, but
2113
+ // reported in a field disjoint from `candidatesTokenCount`. Fold them into the
2114
+ // provider-agnostic `outputTokens` so it reflects the true generated total —
2115
+ // matching Anthropic, whose `output_tokens` already includes them. Left
2116
+ // `undefined` only when the provider reported neither count.
2117
+ outputTokens: usage.candidatesTokenCount != null || usage.thoughtsTokenCount != null ? ((_e = usage.candidatesTokenCount) !== null && _e !== void 0 ? _e : 0) + ((_f = usage.thoughtsTokenCount) !== null && _f !== void 0 ? _f : 0) : void 0,
2118
+ thoughtsTokens: usage.thoughtsTokenCount,
2119
+ // Gemini reports cache HITS only — implicit caching bills no separate write, so
2120
+ // `cacheWriteTokens` is left unset rather than 0 (there is no write bucket to
2121
+ // measure, which is not the same as measuring 0). Already counted inside
2122
+ // `promptTokenCount`, so this breaks that total down rather than adding to it.
2123
+ cacheReadTokens: usage.cachedContentTokenCount,
2124
+ cost
2125
+ };
2126
+ }
2086
2127
  fromGeminiResponse(response, offeredToolNames = /* @__PURE__ */ new Set()) {
2087
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
2088
- let inputTokens;
2089
- let outputTokens;
2090
- let thoughtsTokens;
2091
- let cost;
2092
- if (response.usageMetadata) {
2093
- const usage = response.usageMetadata;
2094
- cost = this.logTokenUsage((_a = usage.promptTokenCount) !== null && _a !== void 0 ? _a : 0, (_b = usage.candidatesTokenCount) !== null && _b !== void 0 ? _b : 0, (_c = usage.thoughtsTokenCount) !== null && _c !== void 0 ? _c : 0, (_d = usage.cachedContentTokenCount) !== null && _d !== void 0 ? _d : 0);
2095
- if (usage.promptTokenCount != null) {
2096
- inputTokens = usage.promptTokenCount;
2097
- }
2098
- if (usage.thoughtsTokenCount != null) {
2099
- thoughtsTokens = usage.thoughtsTokenCount;
2100
- }
2101
- if (usage.candidatesTokenCount != null || usage.thoughtsTokenCount != null) {
2102
- outputTokens = ((_e = usage.candidatesTokenCount) !== null && _e !== void 0 ? _e : 0) + ((_f = usage.thoughtsTokenCount) !== null && _f !== void 0 ? _f : 0);
2103
- }
2104
- }
2128
+ var _a, _b, _c, _d;
2129
+ const { inputTokens, outputTokens, thoughtsTokens, cacheReadTokens, cost } = this.usageFromGemini(response.usageMetadata);
2105
2130
  const candidates = response === null || response === void 0 ? void 0 : response.candidates;
2106
2131
  const firstCandidate = candidates === null || candidates === void 0 ? void 0 : candidates[0];
2107
- const parts = (_h = (_g = firstCandidate === null || firstCandidate === void 0 ? void 0 : firstCandidate.content) === null || _g === void 0 ? void 0 : _g.parts) !== null && _h !== void 0 ? _h : [];
2132
+ const parts = (_b = (_a = firstCandidate === null || firstCandidate === void 0 ? void 0 : firstCandidate.content) === null || _a === void 0 ? void 0 : _a.parts) !== null && _b !== void 0 ? _b : [];
2108
2133
  const toolCalls = [];
2109
2134
  const thoughtParts = [];
2110
2135
  const textParts = [];
@@ -2117,7 +2142,7 @@ ${att.content}`
2117
2142
  toolCalls.push({
2118
2143
  id: crypto.randomUUID(),
2119
2144
  name: part.functionCall.name,
2120
- args: (_j = part.functionCall.args) !== null && _j !== void 0 ? _j : {},
2145
+ args: (_c = part.functionCall.args) !== null && _c !== void 0 ? _c : {},
2121
2146
  providerMetadata: { [GEMINI_PROVIDER_KEY]: state }
2122
2147
  });
2123
2148
  } else if (part.thought && part.text) {
@@ -2151,9 +2176,11 @@ ${att.content}`
2151
2176
  base.inputTokens = inputTokens;
2152
2177
  if (outputTokens != null)
2153
2178
  base.outputTokens = outputTokens;
2179
+ if (cacheReadTokens != null)
2180
+ base.cacheReadTokens = cacheReadTokens;
2154
2181
  if (cost != null)
2155
2182
  base.cost = cost;
2156
- const blockReason = (_k = response.promptFeedback) === null || _k === void 0 ? void 0 : _k.blockReason;
2183
+ const blockReason = (_d = response.promptFeedback) === null || _d === void 0 ? void 0 : _d.blockReason;
2157
2184
  if (finishReason != null || thoughtsTokens != null || blockReason != null) {
2158
2185
  base.responseMeta = {
2159
2186
  finishReason,
@@ -2769,6 +2796,9 @@ function estimateTokensSaved(origLen, stubLen) {
2769
2796
  function applyCondensation(history, policies, ctx, onCondensed) {
2770
2797
  if (policies.size === 0) return history;
2771
2798
  const triggersOf = (entry) => Array.isArray(entry.policy.on) ? entry.policy.on : [entry.policy.on];
2799
+ const batchCalls = Math.max(1, Math.floor(ctx.batchCalls ?? 1));
2800
+ const asOf = batchCalls === 1 ? ctx.modelCall : Math.floor(ctx.modelCall / batchCalls) * batchCalls;
2801
+ const withinBatch = (entry) => batchCalls === 1 || entry.iteration <= asOf;
2772
2802
  const latestByKey = /* @__PURE__ */ new Map();
2773
2803
  const nameById = /* @__PURE__ */ new Map();
2774
2804
  for (const msg of history) {
@@ -2776,7 +2806,7 @@ function applyCondensation(history, policies, ctx, onCondensed) {
2776
2806
  for (const tc of msg.toolCalls) {
2777
2807
  nameById.set(tc.id, tc.name);
2778
2808
  const entry = policies.get(tc.id);
2779
- if (!entry) continue;
2809
+ if (!entry || !withinBatch(entry)) continue;
2780
2810
  for (const t of triggersOf(entry)) {
2781
2811
  if (t.kind === "superseded") latestByKey.set(t.by, tc.id);
2782
2812
  }
@@ -2788,7 +2818,7 @@ function applyCondensation(history, policies, ctx, onCondensed) {
2788
2818
  // past the last allowed view: turns:1 → seen once, then gone. Monotonic
2789
2819
  // clock, so this holds across turn boundaries too.
2790
2820
  case "age":
2791
- return ctx.modelCall - entry.iteration > trig.turns;
2821
+ return asOf - entry.iteration > trig.turns;
2792
2822
  case "turnEnd":
2793
2823
  return ctx.turn > entry.turn;
2794
2824
  case "agentEnd":
@@ -2802,7 +2832,7 @@ function applyCondensation(history, policies, ctx, onCondensed) {
2802
2832
  return latestByKey.get(trig.by) !== toolCallId;
2803
2833
  }
2804
2834
  };
2805
- const firstFired = (toolCallId, entry) => triggersOf(entry).find((t) => triggerFires(toolCallId, entry, t));
2835
+ const firstFired = (toolCallId, entry) => withinBatch(entry) ? triggersOf(entry).find((t) => triggerFires(toolCallId, entry, t)) : void 0;
2806
2836
  return history.map((msg) => {
2807
2837
  if (msg.toolCalls?.length) {
2808
2838
  let changed = false;
@@ -2950,6 +2980,46 @@ The above summarizes earlier parts of this conversation that were compacted. Con
2950
2980
  });
2951
2981
  }
2952
2982
 
2983
+ // src/utils/sum-usage.ts
2984
+ function emptyUsage() {
2985
+ return {
2986
+ costUsd: 0,
2987
+ uncachedInputTokens: 0,
2988
+ cacheReadTokens: 0,
2989
+ cacheWriteTokens: 0,
2990
+ outputTokens: 0
2991
+ };
2992
+ }
2993
+ function sumUsage(messages) {
2994
+ const total = emptyUsage();
2995
+ accumulate(messages, total);
2996
+ return total;
2997
+ }
2998
+ function addInto(into, from) {
2999
+ into.costUsd += from.costUsd;
3000
+ into.uncachedInputTokens += from.uncachedInputTokens;
3001
+ into.cacheReadTokens += from.cacheReadTokens;
3002
+ into.cacheWriteTokens += from.cacheWriteTokens;
3003
+ into.outputTokens += from.outputTokens;
3004
+ }
3005
+ function accumulate(messages, into) {
3006
+ for (const m of messages) {
3007
+ if (m.cost != null) into.costUsd += m.cost;
3008
+ if (m.externalCostUsd != null) into.costUsd += m.externalCostUsd;
3009
+ const cacheRead = m.cacheReadTokens ?? 0;
3010
+ const cacheWrite = m.cacheWriteTokens ?? 0;
3011
+ into.cacheReadTokens += cacheRead;
3012
+ into.cacheWriteTokens += cacheWrite;
3013
+ into.uncachedInputTokens += Math.max(0, (m.inputTokens ?? 0) - cacheRead - cacheWrite);
3014
+ into.outputTokens += m.outputTokens ?? 0;
3015
+ const rolled = m.compaction?.rolledUpUsage;
3016
+ if (rolled) addInto(into, rolled);
3017
+ for (const tc of m.toolCalls ?? []) {
3018
+ if (tc.subAgentTrace) accumulate(tc.subAgentTrace, into);
3019
+ }
3020
+ }
3021
+ }
3022
+
2953
3023
  // src/utils/tool-fold.ts
2954
3024
  var TOOL_FOLD_SYMBOL = Symbol("toolFold");
2955
3025
 
@@ -3143,11 +3213,13 @@ var ChatDriver = class _ChatDriver extends EventTarget {
3143
3213
  primerHistory,
3144
3214
  maxToolIterations = DEFAULT_MAX_TOOL_ITERATIONS,
3145
3215
  maxFoldOperations = DEFAULT_MAX_FOLD_OPERATIONS,
3216
+ condenseBatchCalls = 1,
3146
3217
  maxTurnSnapshots = DEFAULT_MAX_TURN_SNAPSHOTS,
3147
3218
  sessionKey = "",
3148
3219
  activityBus = NOOP_ACTIVITY_BUS
3149
3220
  } = config;
3150
3221
  this.maxToolIterations = maxToolIterations;
3222
+ this.condenseBatchCalls = condenseBatchCalls;
3151
3223
  this.sessionKey = sessionKey;
3152
3224
  this.activityBus = activityBus;
3153
3225
  if (typeof toolHandlers === "function") {
@@ -3697,7 +3769,12 @@ Output format (strict):
3697
3769
  coveredThroughTimestamp: toCompact[toCompact.length - 1]?.timestamp,
3698
3770
  tokensBefore,
3699
3771
  model: status?.model,
3700
- createdAt
3772
+ createdAt,
3773
+ // Bank what the replaced turns spent. `toCompact` is dropped from history
3774
+ // below, taking its per-message cost/token fields with it — so a host total
3775
+ // re-derived from the transcript would otherwise fall by exactly this much
3776
+ // on every compaction. Summarising a turn does not refund what it cost.
3777
+ rolledUpUsage: sumUsage(toCompact)
3701
3778
  }
3702
3779
  };
3703
3780
  this.loadHistory([summaryMessage, ...tail]);
@@ -4381,7 +4458,8 @@ Output format (strict):
4381
4458
  activationEnded: (activation) => activation < this.currentActivation,
4382
4459
  // A call's phase has ended once the app declared a later phase epoch
4383
4460
  // current via `endPhase()`.
4384
- phaseEnded: (phaseEpoch) => phaseEpoch < this.currentPhaseEpoch
4461
+ phaseEnded: (phaseEpoch) => phaseEpoch < this.currentPhaseEpoch,
4462
+ batchCalls: this.condenseBatchCalls
4385
4463
  },
4386
4464
  (detail) => recordMetaEvent(this.sessionKey, "context.condensed", detail)
4387
4465
  );