@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
@@ -1565,6 +1565,8 @@ ${att.content}` });
1565
1565
  var _a, _b, _c, _d, _e, _f, _g, _h;
1566
1566
  let inputTokens;
1567
1567
  let outputTokens;
1568
+ let cacheReadTokens;
1569
+ let cacheWriteTokens;
1568
1570
  let cost;
1569
1571
  if (response.usage) {
1570
1572
  const uncachedInput = (_a = response.usage.input_tokens) !== null && _a !== void 0 ? _a : 0;
@@ -1574,6 +1576,8 @@ ${att.content}` });
1574
1576
  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;
1575
1577
  cost = this.logTokenUsage(uncachedInput, (_f = response.usage.output_tokens) !== null && _f !== void 0 ? _f : 0, cacheRead, cacheCreation, cacheCreation1h);
1576
1578
  inputTokens = uncachedInput + cacheRead + cacheCreation;
1579
+ cacheReadTokens = cacheRead;
1580
+ cacheWriteTokens = cacheCreation;
1577
1581
  if (response.usage.output_tokens != null) {
1578
1582
  outputTokens = response.usage.output_tokens;
1579
1583
  }
@@ -1604,6 +1608,10 @@ ${att.content}` });
1604
1608
  base.inputTokens = inputTokens;
1605
1609
  if (outputTokens != null)
1606
1610
  base.outputTokens = outputTokens;
1611
+ if (cacheReadTokens != null)
1612
+ base.cacheReadTokens = cacheReadTokens;
1613
+ if (cacheWriteTokens != null)
1614
+ base.cacheWriteTokens = cacheWriteTokens;
1607
1615
  if (cost != null)
1608
1616
  base.cost = cost;
1609
1617
  if (response.stop_reason === "max_tokens") {
@@ -2130,28 +2138,45 @@ ${att.content}`
2130
2138
  }
2131
2139
  return contents;
2132
2140
  }
2141
+ /**
2142
+ * Map Gemini's `usageMetadata` onto the provider-agnostic usage fields, pricing
2143
+ * the call as a side effect (`logTokenUsage`). Returns an empty object when the
2144
+ * response carries no usage — every field is optional, so an absent count stays
2145
+ * `undefined` rather than being reported as a measured 0.
2146
+ *
2147
+ * Split out of `fromGeminiResponse` because it is self-contained bookkeeping
2148
+ * that reads better alone — and because folding it inline pushed that method
2149
+ * past the complexity ceiling.
2150
+ */
2151
+ usageFromGemini(usageMetadata) {
2152
+ var _a, _b, _c, _d, _e, _f;
2153
+ if (!usageMetadata)
2154
+ return {};
2155
+ const usage = usageMetadata;
2156
+ 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);
2157
+ return {
2158
+ inputTokens: usage.promptTokenCount,
2159
+ // Thinking tokens are generated output, billed at the candidate rate, but
2160
+ // reported in a field disjoint from `candidatesTokenCount`. Fold them into the
2161
+ // provider-agnostic `outputTokens` so it reflects the true generated total —
2162
+ // matching Anthropic, whose `output_tokens` already includes them. Left
2163
+ // `undefined` only when the provider reported neither count.
2164
+ 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,
2165
+ thoughtsTokens: usage.thoughtsTokenCount,
2166
+ // Gemini reports cache HITS only — implicit caching bills no separate write, so
2167
+ // `cacheWriteTokens` is left unset rather than 0 (there is no write bucket to
2168
+ // measure, which is not the same as measuring 0). Already counted inside
2169
+ // `promptTokenCount`, so this breaks that total down rather than adding to it.
2170
+ cacheReadTokens: usage.cachedContentTokenCount,
2171
+ cost
2172
+ };
2173
+ }
2133
2174
  fromGeminiResponse(response, offeredToolNames = /* @__PURE__ */ new Set()) {
2134
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
2135
- let inputTokens;
2136
- let outputTokens;
2137
- let thoughtsTokens;
2138
- let cost;
2139
- if (response.usageMetadata) {
2140
- const usage = response.usageMetadata;
2141
- 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);
2142
- if (usage.promptTokenCount != null) {
2143
- inputTokens = usage.promptTokenCount;
2144
- }
2145
- if (usage.thoughtsTokenCount != null) {
2146
- thoughtsTokens = usage.thoughtsTokenCount;
2147
- }
2148
- if (usage.candidatesTokenCount != null || usage.thoughtsTokenCount != null) {
2149
- outputTokens = ((_e = usage.candidatesTokenCount) !== null && _e !== void 0 ? _e : 0) + ((_f = usage.thoughtsTokenCount) !== null && _f !== void 0 ? _f : 0);
2150
- }
2151
- }
2175
+ var _a, _b, _c, _d;
2176
+ const { inputTokens, outputTokens, thoughtsTokens, cacheReadTokens, cost } = this.usageFromGemini(response.usageMetadata);
2152
2177
  const candidates = response === null || response === void 0 ? void 0 : response.candidates;
2153
2178
  const firstCandidate = candidates === null || candidates === void 0 ? void 0 : candidates[0];
2154
- 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 : [];
2179
+ 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 : [];
2155
2180
  const toolCalls = [];
2156
2181
  const thoughtParts = [];
2157
2182
  const textParts = [];
@@ -2164,7 +2189,7 @@ ${att.content}`
2164
2189
  toolCalls.push({
2165
2190
  id: crypto.randomUUID(),
2166
2191
  name: part.functionCall.name,
2167
- args: (_j = part.functionCall.args) !== null && _j !== void 0 ? _j : {},
2192
+ args: (_c = part.functionCall.args) !== null && _c !== void 0 ? _c : {},
2168
2193
  providerMetadata: { [GEMINI_PROVIDER_KEY]: state }
2169
2194
  });
2170
2195
  } else if (part.thought && part.text) {
@@ -2198,9 +2223,11 @@ ${att.content}`
2198
2223
  base.inputTokens = inputTokens;
2199
2224
  if (outputTokens != null)
2200
2225
  base.outputTokens = outputTokens;
2226
+ if (cacheReadTokens != null)
2227
+ base.cacheReadTokens = cacheReadTokens;
2201
2228
  if (cost != null)
2202
2229
  base.cost = cost;
2203
- const blockReason = (_k = response.promptFeedback) === null || _k === void 0 ? void 0 : _k.blockReason;
2230
+ const blockReason = (_d = response.promptFeedback) === null || _d === void 0 ? void 0 : _d.blockReason;
2204
2231
  if (finishReason != null || thoughtsTokens != null || blockReason != null) {
2205
2232
  base.responseMeta = {
2206
2233
  finishReason,
@@ -2816,6 +2843,9 @@ function estimateTokensSaved(origLen, stubLen) {
2816
2843
  function applyCondensation(history, policies, ctx, onCondensed) {
2817
2844
  if (policies.size === 0) return history;
2818
2845
  const triggersOf = (entry) => Array.isArray(entry.policy.on) ? entry.policy.on : [entry.policy.on];
2846
+ const batchCalls = Math.max(1, Math.floor(ctx.batchCalls ?? 1));
2847
+ const asOf = batchCalls === 1 ? ctx.modelCall : Math.floor(ctx.modelCall / batchCalls) * batchCalls;
2848
+ const withinBatch = (entry) => batchCalls === 1 || entry.iteration <= asOf;
2819
2849
  const latestByKey = /* @__PURE__ */ new Map();
2820
2850
  const nameById = /* @__PURE__ */ new Map();
2821
2851
  for (const msg of history) {
@@ -2823,7 +2853,7 @@ function applyCondensation(history, policies, ctx, onCondensed) {
2823
2853
  for (const tc of msg.toolCalls) {
2824
2854
  nameById.set(tc.id, tc.name);
2825
2855
  const entry = policies.get(tc.id);
2826
- if (!entry) continue;
2856
+ if (!entry || !withinBatch(entry)) continue;
2827
2857
  for (const t of triggersOf(entry)) {
2828
2858
  if (t.kind === "superseded") latestByKey.set(t.by, tc.id);
2829
2859
  }
@@ -2835,7 +2865,7 @@ function applyCondensation(history, policies, ctx, onCondensed) {
2835
2865
  // past the last allowed view: turns:1 → seen once, then gone. Monotonic
2836
2866
  // clock, so this holds across turn boundaries too.
2837
2867
  case "age":
2838
- return ctx.modelCall - entry.iteration > trig.turns;
2868
+ return asOf - entry.iteration > trig.turns;
2839
2869
  case "turnEnd":
2840
2870
  return ctx.turn > entry.turn;
2841
2871
  case "agentEnd":
@@ -2849,7 +2879,7 @@ function applyCondensation(history, policies, ctx, onCondensed) {
2849
2879
  return latestByKey.get(trig.by) !== toolCallId;
2850
2880
  }
2851
2881
  };
2852
- const firstFired = (toolCallId, entry) => triggersOf(entry).find((t) => triggerFires(toolCallId, entry, t));
2882
+ const firstFired = (toolCallId, entry) => withinBatch(entry) ? triggersOf(entry).find((t) => triggerFires(toolCallId, entry, t)) : void 0;
2853
2883
  return history.map((msg) => {
2854
2884
  if (msg.toolCalls?.length) {
2855
2885
  let changed = false;
@@ -2997,6 +3027,46 @@ The above summarizes earlier parts of this conversation that were compacted. Con
2997
3027
  });
2998
3028
  }
2999
3029
 
3030
+ // src/utils/sum-usage.ts
3031
+ function emptyUsage() {
3032
+ return {
3033
+ costUsd: 0,
3034
+ uncachedInputTokens: 0,
3035
+ cacheReadTokens: 0,
3036
+ cacheWriteTokens: 0,
3037
+ outputTokens: 0
3038
+ };
3039
+ }
3040
+ function sumUsage(messages) {
3041
+ const total = emptyUsage();
3042
+ accumulate(messages, total);
3043
+ return total;
3044
+ }
3045
+ function addInto(into, from) {
3046
+ into.costUsd += from.costUsd;
3047
+ into.uncachedInputTokens += from.uncachedInputTokens;
3048
+ into.cacheReadTokens += from.cacheReadTokens;
3049
+ into.cacheWriteTokens += from.cacheWriteTokens;
3050
+ into.outputTokens += from.outputTokens;
3051
+ }
3052
+ function accumulate(messages, into) {
3053
+ for (const m of messages) {
3054
+ if (m.cost != null) into.costUsd += m.cost;
3055
+ if (m.externalCostUsd != null) into.costUsd += m.externalCostUsd;
3056
+ const cacheRead = m.cacheReadTokens ?? 0;
3057
+ const cacheWrite = m.cacheWriteTokens ?? 0;
3058
+ into.cacheReadTokens += cacheRead;
3059
+ into.cacheWriteTokens += cacheWrite;
3060
+ into.uncachedInputTokens += Math.max(0, (m.inputTokens ?? 0) - cacheRead - cacheWrite);
3061
+ into.outputTokens += m.outputTokens ?? 0;
3062
+ const rolled = m.compaction?.rolledUpUsage;
3063
+ if (rolled) addInto(into, rolled);
3064
+ for (const tc of m.toolCalls ?? []) {
3065
+ if (tc.subAgentTrace) accumulate(tc.subAgentTrace, into);
3066
+ }
3067
+ }
3068
+ }
3069
+
3000
3070
  // src/utils/tool-fold.ts
3001
3071
  var TOOL_FOLD_SYMBOL = Symbol("toolFold");
3002
3072
 
@@ -3190,11 +3260,13 @@ var ChatDriver = class _ChatDriver extends EventTarget {
3190
3260
  primerHistory,
3191
3261
  maxToolIterations = DEFAULT_MAX_TOOL_ITERATIONS,
3192
3262
  maxFoldOperations = DEFAULT_MAX_FOLD_OPERATIONS,
3263
+ condenseBatchCalls = 1,
3193
3264
  maxTurnSnapshots = DEFAULT_MAX_TURN_SNAPSHOTS,
3194
3265
  sessionKey = "",
3195
3266
  activityBus = NOOP_ACTIVITY_BUS
3196
3267
  } = config;
3197
3268
  this.maxToolIterations = maxToolIterations;
3269
+ this.condenseBatchCalls = condenseBatchCalls;
3198
3270
  this.sessionKey = sessionKey;
3199
3271
  this.activityBus = activityBus;
3200
3272
  if (typeof toolHandlers === "function") {
@@ -3744,7 +3816,12 @@ Output format (strict):
3744
3816
  coveredThroughTimestamp: toCompact[toCompact.length - 1]?.timestamp,
3745
3817
  tokensBefore,
3746
3818
  model: status?.model,
3747
- createdAt
3819
+ createdAt,
3820
+ // Bank what the replaced turns spent. `toCompact` is dropped from history
3821
+ // below, taking its per-message cost/token fields with it — so a host total
3822
+ // re-derived from the transcript would otherwise fall by exactly this much
3823
+ // on every compaction. Summarising a turn does not refund what it cost.
3824
+ rolledUpUsage: sumUsage(toCompact)
3748
3825
  }
3749
3826
  };
3750
3827
  this.loadHistory([summaryMessage, ...tail]);
@@ -4428,7 +4505,8 @@ Output format (strict):
4428
4505
  activationEnded: (activation) => activation < this.currentActivation,
4429
4506
  // A call's phase has ended once the app declared a later phase epoch
4430
4507
  // current via `endPhase()`.
4431
- phaseEnded: (phaseEpoch) => phaseEpoch < this.currentPhaseEpoch
4508
+ phaseEnded: (phaseEpoch) => phaseEpoch < this.currentPhaseEpoch,
4509
+ batchCalls: this.condenseBatchCalls
4432
4510
  },
4433
4511
  (detail) => recordMetaEvent(this.sessionKey, "context.condensed", detail)
4434
4512
  );