@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
@@ -1,32 +1,174 @@
1
1
  import { assert, createLogicSuite } from '@genesislcap/foundation-testing';
2
- import { appendCostSessionRecord, clearCostSessionHistory, costHistoryStorageKey, formatCostSessionDate, loadCostSessionHistory, removeCostSessionRecord, saveCostSessionHistory, } from './cost-session-history';
2
+ import { clearCostSessionHistory, costHistoryStorageKey, formatCostSessionDate, loadCostSessionHistory, resolveBankedUsage, saveCostSessionHistory, sortRecordsByRecency, upsertRecord, } from './cost-session-history';
3
+ import { emptyUsage } from './sum-usage';
3
4
  const scope = `test-${Date.now()}`;
4
- const sampleRecord = (overrides = {}) => (Object.assign({ id: 'rec-1', title: 'Equity Options Pricer', endedAt: '2026-06-14T12:00:00.000Z', costUsd: 1.86, tokensConsumed: 1400000, models: [{ model: 'claude-sonnet-4-6', provider: 'anthropic' }] }, overrides));
5
+ const usage = (costUsd) => ({
6
+ costUsd,
7
+ uncachedInputTokens: 100000,
8
+ cacheReadTokens: 1200000,
9
+ cacheWriteTokens: 60000,
10
+ outputTokens: 40000,
11
+ });
12
+ const sampleRecord = (overrides = {}) => (Object.assign({ id: 'rec-1', projectKey: 'proj-1', title: 'Equity Options Pricer', updatedAt: '2026-06-14T12:00:00.000Z', usage: usage(1.86),
13
+ // Present by default so `delete record.banked` in the legacy-row cases actually removes
14
+ // something, and so the storage round-trip covers it.
15
+ banked: usage(0.5), models: [{ model: 'claude-sonnet-4-6', provider: 'anthropic' }] }, overrides));
5
16
  const suite = createLogicSuite('costSessionHistory');
6
17
  suite('uses a scoped storage key', () => {
7
18
  assert.is(costHistoryStorageKey('my-app'), 'genesis-ai-assistant:cost-history:my-app');
8
19
  });
9
- suite('persists and loads records newest-first via append', () => {
20
+ suite('upsertRecord prepends a project it has not seen', () => {
21
+ var _a, _b;
22
+ const first = upsertRecord([], sampleRecord({ id: 'a', projectKey: 'a', title: 'First' }));
23
+ const both = upsertRecord(first, sampleRecord({ id: 'b', projectKey: 'b', title: 'Second' }));
24
+ assert.is(both.length, 2);
25
+ // Insertion order, not recency — display ordering is sortRecordsByRecency's job.
26
+ assert.is((_a = both[0]) === null || _a === void 0 ? void 0 : _a.id, 'b', 'most recently added is prepended');
27
+ assert.is((_b = both[1]) === null || _b === void 0 ? void 0 : _b.id, 'a');
28
+ });
29
+ suite('upsertRecord replaces a project in place rather than stacking a second row', () => {
10
30
  var _a, _b;
31
+ const seeded = [
32
+ sampleRecord({ id: 'b', projectKey: 'b', usage: usage(2) }),
33
+ sampleRecord({ id: 'a', projectKey: 'a', usage: usage(1) }),
34
+ ];
35
+ // Re-reporting a project must REPLACE its row. Appending here is what made a single
36
+ // project's spend count once per refresh in any total summed over the list.
37
+ const next = upsertRecord(seeded, sampleRecord({ id: 'a', projectKey: 'a', usage: usage(9.5) }));
38
+ assert.is(next.length, 2);
39
+ // Replaced in place rather than moved — the stored array is not a recency ordering.
40
+ assert.is((_a = next[1]) === null || _a === void 0 ? void 0 : _a.projectKey, 'a');
41
+ assert.is((_b = next[1]) === null || _b === void 0 ? void 0 : _b.usage.costUsd, 9.5);
42
+ assert.is(next.reduce((sum, r) => sum + r.usage.costUsd, 0), 11.5);
43
+ });
44
+ suite('round-trips records through storage', () => {
45
+ var _a, _b, _c, _d;
46
+ clearCostSessionHistory(scope);
47
+ const records = upsertRecord([], sampleRecord({ id: 'a', projectKey: 'a', usage: usage(3.5) }));
48
+ saveCostSessionHistory(scope, records);
49
+ const loaded = loadCostSessionHistory(scope);
50
+ assert.is(loaded.length, 1);
51
+ assert.is((_a = loaded[0]) === null || _a === void 0 ? void 0 : _a.usage.costUsd, 3.5);
52
+ assert.is((_b = loaded[0]) === null || _b === void 0 ? void 0 : _b.usage.cacheReadTokens, 1200000, 'buckets survive serialization');
53
+ assert.is((_d = (_c = loaded[0]) === null || _c === void 0 ? void 0 : _c.banked) === null || _d === void 0 ? void 0 : _d.costUsd, 0.5, 'banked survives serialization');
54
+ clearCostSessionHistory(scope);
55
+ });
56
+ suite('discards legacy rows that predate the per-project key', () => {
57
+ var _a;
11
58
  clearCostSessionHistory(scope);
12
- appendCostSessionRecord(scope, sampleRecord({ id: 'a', title: 'First' }));
13
- appendCostSessionRecord(scope, sampleRecord({ id: 'b', title: 'Second' }));
59
+ const legacy = sampleRecord({ id: 'old' });
60
+ delete legacy.projectKey;
61
+ saveCostSessionHistory(scope, [legacy, sampleRecord({ id: 'new' })]);
14
62
  const loaded = loadCostSessionHistory(scope);
15
- assert.is(loaded.length, 2);
16
- assert.is((_a = loaded[0]) === null || _a === void 0 ? void 0 : _a.id, 'b');
17
- assert.is((_b = loaded[1]) === null || _b === void 0 ? void 0 : _b.id, 'a');
63
+ // A keyless row can never be matched to a project, so pooling it with the new rows
64
+ // would permanently double-count whichever project it came from.
65
+ assert.is(loaded.length, 1);
66
+ assert.is((_a = loaded[0]) === null || _a === void 0 ? void 0 : _a.id, 'new');
18
67
  clearCostSessionHistory(scope);
19
68
  });
20
- suite('removes one record by id', () => {
69
+ suite('discards rows whose usage is not fully numeric', () => {
21
70
  var _a;
22
71
  clearCostSessionHistory(scope);
23
- saveCostSessionHistory(scope, [sampleRecord({ id: 'keep' }), sampleRecord({ id: 'drop' })]);
24
- const remaining = removeCostSessionRecord(scope, 'drop');
25
- assert.is(remaining.length, 1);
26
- assert.is((_a = remaining[0]) === null || _a === void 0 ? void 0 : _a.id, 'keep');
72
+ const broken = sampleRecord({ id: 'broken' });
73
+ // A half-shaped `usage` is worse than a missing row: it sums as NaN and poisons every
74
+ // total on the Usage tab, with no clue where the NaN came from.
75
+ delete broken.usage.outputTokens;
76
+ saveCostSessionHistory(scope, [broken, sampleRecord({ id: 'ok', projectKey: 'ok' })]);
77
+ const loaded = loadCostSessionHistory(scope);
78
+ assert.is(loaded.length, 1);
79
+ assert.is((_a = loaded[0]) === null || _a === void 0 ? void 0 : _a.id, 'ok');
80
+ clearCostSessionHistory(scope);
81
+ });
82
+ suite('discards rows whose banked figure is half-shaped', () => {
83
+ var _a;
84
+ clearCostSessionHistory(scope);
85
+ const broken = sampleRecord({ id: 'broken', banked: Object.assign({}, usage(2)) });
86
+ // A malformed `banked` poisons the lifetime total exactly as a malformed `usage` does,
87
+ // since the two are added together to produce it.
88
+ delete broken.banked.cacheWriteTokens;
89
+ saveCostSessionHistory(scope, [broken, sampleRecord({ id: 'ok', projectKey: 'ok' })]);
90
+ const loaded = loadCostSessionHistory(scope);
91
+ assert.is(loaded.length, 1);
92
+ assert.is((_a = loaded[0]) === null || _a === void 0 ? void 0 : _a.id, 'ok');
93
+ clearCostSessionHistory(scope);
94
+ });
95
+ suite('keeps a row whose banked figure is absent', () => {
96
+ clearCostSessionHistory(scope);
97
+ const legacy = sampleRecord({ id: 'legacy' });
98
+ delete legacy.banked;
99
+ saveCostSessionHistory(scope, [legacy]);
100
+ assert.is(loadCostSessionHistory(scope).length, 1, 'absent is valid, unlike half-shaped');
27
101
  clearCostSessionHistory(scope);
28
102
  });
29
103
  suite('formats session dates for display', () => {
30
104
  assert.is(formatCostSessionDate('2026-06-14T12:00:00.000Z'), 'Jun 14, 2026');
31
105
  });
106
+ suite('sortRecordsByRecency puts the most recently worked-on project first', () => {
107
+ const records = [
108
+ sampleRecord({ id: 'old', projectKey: 'old', updatedAt: '2026-01-01T00:00:00.000Z' }),
109
+ sampleRecord({ id: 'newest', projectKey: 'newest', updatedAt: '2026-08-04T09:00:00.000Z' }),
110
+ sampleRecord({ id: 'mid', projectKey: 'mid', updatedAt: '2026-05-01T00:00:00.000Z' }),
111
+ ];
112
+ const sorted = sortRecordsByRecency(records);
113
+ assert.equal(sorted.map((r) => r.id), ['newest', 'mid', 'old']);
114
+ // Read-time ordering, so the caller's array must be left alone.
115
+ assert.equal(records.map((r) => r.id), ['old', 'newest', 'mid'], 'input not mutated');
116
+ });
117
+ suite('sortRecordsByRecency overrides insertion order, which upsert leaves alone', () => {
118
+ var _a, _b;
119
+ // The behaviour this fixes: revisiting a project replaces its row IN PLACE, so insertion order
120
+ // strands it wherever it was first seen. Display order must come from updatedAt instead.
121
+ let records = upsertRecord([], sampleRecord({ id: 'a', projectKey: 'a', updatedAt: '2026-01-01T00:00:00.000Z' }));
122
+ records = upsertRecord(records, sampleRecord({ id: 'b', projectKey: 'b', updatedAt: '2026-02-01T00:00:00.000Z' }));
123
+ // Work on 'a' again — newer than 'b', but upsert keeps it in slot 1.
124
+ records = upsertRecord(records, sampleRecord({ id: 'a', projectKey: 'a', updatedAt: '2026-03-01T00:00:00.000Z' }));
125
+ assert.is((_a = records[1]) === null || _a === void 0 ? void 0 : _a.id, 'a', 'stored order is unchanged by the revisit');
126
+ assert.is((_b = sortRecordsByRecency(records)[0]) === null || _b === void 0 ? void 0 : _b.id, 'a', 'display order tracks the revisit');
127
+ });
128
+ suite('banks nothing for a project with no prior row', () => {
129
+ assert.equal(resolveBankedUsage(undefined, true), emptyUsage());
130
+ assert.equal(resolveBankedUsage(undefined, false), emptyUsage());
131
+ });
132
+ suite('carries the prior banked figure forward when the transcript is authoritative', () => {
133
+ // Restored transcript: it already re-proves everything after the row's own banked figure,
134
+ // so only that figure carries over. Taking `usage` here is the compounding double-count.
135
+ const prior = sampleRecord({ usage: usage(10), banked: Object.assign({}, usage(4)) });
136
+ assert.is(resolveBankedUsage(prior, true).costUsd, 4);
137
+ });
138
+ suite('banks the whole prior total when the transcript starts empty', () => {
139
+ // Nothing on screen accounts for the previous total, so all of it must carry over or the
140
+ // next turn rewrites the row down to just that turn.
141
+ const prior = sampleRecord({ usage: usage(10), banked: Object.assign({}, usage(4)) });
142
+ assert.is(resolveBankedUsage(prior, false).costUsd, 10);
143
+ });
144
+ suite('treats a row predating the banked field as nothing banked', () => {
145
+ const legacy = sampleRecord({ usage: usage(7) });
146
+ delete legacy.banked;
147
+ assert.equal(resolveBankedUsage(legacy, true), emptyUsage());
148
+ // Without persistence the legacy total still carries, so upgrading loses no spend.
149
+ assert.is(resolveBankedUsage(legacy, false).costUsd, 7);
150
+ });
151
+ suite('is stable across repeated reloads — the regression this guards', () => {
152
+ // Simulate the reload cycle: usage = banked + whatever the transcript proves. With a
153
+ // restored transcript (worth 6 on top of 4 banked) the row must land on 10 every time,
154
+ // not 10 → 14 → 18 as it did when a reload re-banked the restored history.
155
+ const transcript = 6;
156
+ let row = sampleRecord({ usage: usage(10), banked: Object.assign({}, usage(4)) });
157
+ for (let reload = 0; reload < 3; reload += 1) {
158
+ const banked = resolveBankedUsage(row, true);
159
+ row = Object.assign(Object.assign({}, row), { banked, usage: Object.assign({}, usage(banked.costUsd + transcript)) });
160
+ assert.is(row.usage.costUsd, 10, `reload ${reload}`);
161
+ }
162
+ });
163
+ suite('accumulates across reloads when the transcript does not persist', () => {
164
+ // The mirror case: each page-load starts from an empty transcript, so the row must grow by
165
+ // whatever that load spends rather than being overwritten by it.
166
+ let row = sampleRecord({ usage: usage(10), banked: Object.assign({}, usage(4)) });
167
+ for (const spend of [2, 3]) {
168
+ const banked = resolveBankedUsage(row, false);
169
+ row = Object.assign(Object.assign({}, row), { banked, usage: Object.assign({}, usage(banked.costUsd + spend)) });
170
+ }
171
+ // 10 banked, +2 → 12; then 12 banked, +3 → 15. Never decreases.
172
+ assert.is(row.usage.costUsd, 15);
173
+ });
32
174
  suite.run();
@@ -1,6 +1,6 @@
1
1
  /** Resolve `chatConfig.costHistory` with defaults applied. */
2
2
  export function resolveCostHistoryConfig(chatConfig) {
3
- var _a, _b, _c;
3
+ var _a, _b, _c, _d;
4
4
  const cost = chatConfig.costHistory;
5
5
  return {
6
6
  enabled: (cost === null || cost === void 0 ? void 0 : cost.enabled) === true,
@@ -9,5 +9,6 @@ export function resolveCostHistoryConfig(chatConfig) {
9
9
  showCost: (cost === null || cost === void 0 ? void 0 : cost.showCost) !== false,
10
10
  showTokens: (cost === null || cost === void 0 ? void 0 : cost.showTokens) !== false,
11
11
  badgeLabel: (_c = cost === null || cost === void 0 ? void 0 : cost.badgeLabel) !== null && _c !== void 0 ? _c : 'Currently building',
12
+ summaryHint: ((_d = cost === null || cost === void 0 ? void 0 : cost.summaryHint) === null || _d === void 0 ? void 0 : _d.trim()) || undefined,
12
13
  };
13
14
  }
@@ -1,3 +1,4 @@
1
+ import { sumUsage } from './sum-usage';
1
2
  /**
2
3
  * Sum cost across a message list: each message's provider-reported LLM `cost`
3
4
  * plus any `externalCostUsd` a widget reported for its own external service
@@ -11,17 +12,5 @@
11
12
  * @internal
12
13
  */
13
14
  export function sumCosts(messages) {
14
- var _a;
15
- let total = 0;
16
- for (const m of messages) {
17
- if (m.cost != null)
18
- total += m.cost;
19
- if (m.externalCostUsd != null)
20
- total += m.externalCostUsd;
21
- for (const tc of (_a = m.toolCalls) !== null && _a !== void 0 ? _a : []) {
22
- if (tc.subAgentTrace)
23
- total += sumCosts(tc.subAgentTrace);
24
- }
25
- }
26
- return total;
15
+ return sumUsage(messages).costUsd;
27
16
  }
@@ -1,25 +1,21 @@
1
1
  import DOMPurify from 'dompurify';
2
- /** Per-message token usage when the provider reports it. */
3
- const messageTokenUsage = (m) => { var _a, _b; return ((_a = m.inputTokens) !== null && _a !== void 0 ? _a : 0) + ((_b = m.outputTokens) !== null && _b !== void 0 ? _b : 0); };
2
+ import { sumUsage, totalTokens } from './sum-usage';
4
3
  /**
5
- * Sum input + output tokens across a message list, recursing into each tool
6
- * call's `subAgentTrace` so sub-agent LLM usage contributes to the total.
4
+ * Total tokens across a message list — the headline figure, with the per-bucket
5
+ * split available from {@link sumUsage} for anything that needs to show *why* a
6
+ * large token count cost little.
7
+ *
8
+ * Identical arithmetic to the old per-message `inputTokens + outputTokens` sum:
9
+ * the three input buckets partition `inputTokens`, so they re-add to it exactly.
10
+ * It does now additionally count usage banked by a compaction, which is the point
11
+ * — that spend used to vanish when the summarised turns left the transcript.
7
12
  *
8
13
  * Returns 0 when no message carries token metadata.
9
14
  *
10
15
  * @internal
11
16
  */
12
17
  export function sumTokens(messages) {
13
- var _a;
14
- let total = 0;
15
- for (const m of messages) {
16
- total += messageTokenUsage(m);
17
- for (const tc of (_a = m.toolCalls) !== null && _a !== void 0 ? _a : []) {
18
- if (tc.subAgentTrace)
19
- total += sumTokens(tc.subAgentTrace);
20
- }
21
- }
22
- return total;
18
+ return totalTokens(sumUsage(messages));
23
19
  }
24
20
  const TOKEN_SCALE_DECIMAL_FACTOR = 10;
25
21
  const TOKENS_PER_MILLION = 1000000;
@@ -49,19 +45,6 @@ const scaledNumericValue = (tokens) => {
49
45
  return roundScaled(tokens / TOKENS_PER_MILLION);
50
46
  return roundScaled(tokens / TOKENS_PER_THOUSAND);
51
47
  };
52
- /**
53
- * Compact plain-text token count (e.g. 1_400_000 → "1.4M").
54
- *
55
- * @internal
56
- */
57
- export function formatCompactTokenCountPlain(tokens) {
58
- const suffix = scaledSuffixLabel(tokens);
59
- if (!suffix)
60
- return tokens.toLocaleString();
61
- const rounded = scaledNumericValue(tokens);
62
- const value = Number.isInteger(rounded) ? String(rounded) : rounded.toFixed(1);
63
- return `${value}${suffix}`;
64
- }
65
48
  /**
66
49
  * Compact display for large token counts (e.g. 1_400_000 → value "1.4" + suffix "M").
67
50
  * Returns HTML markup so the suffix can be styled separately in the settings modal.
@@ -1,5 +1,5 @@
1
1
  import { assert, createLogicSuite } from '@genesislcap/foundation-testing';
2
- import { formatCompactTokenCount, formatCompactTokenCountPlain, formatCompactTokenCountSafe, sumTokens, } from './sum-tokens';
2
+ import { formatCompactTokenCount, formatCompactTokenCountSafe, sumTokens } from './sum-tokens';
3
3
  const assistantMsg = (overrides = {}) => (Object.assign({ role: 'assistant', content: '' }, overrides));
4
4
  const suite = createLogicSuite('sumTokens');
5
5
  suite('returns 0 for an empty message list', () => {
@@ -47,9 +47,5 @@ formatSuite('rolls K suffix up to M when rounding would yield 1000K', () => {
47
47
  formatSuite('safe formatter preserves compact markup', () => {
48
48
  assert.is(formatCompactTokenCountSafe(1400000), '<span class="value">1.4</span><span class="suffix">M</span>');
49
49
  });
50
- formatSuite('plain formatter returns compact text', () => {
51
- assert.is(formatCompactTokenCountPlain(1400000), '1.4M');
52
- assert.is(formatCompactTokenCountPlain(842), '842');
53
- });
54
50
  suite.run();
55
51
  formatSuite.run();
@@ -0,0 +1,123 @@
1
+ /**
2
+ * Zeroed {@link AggregateUsage}. A factory, not a shared const, because callers
3
+ * accumulate into the returned object.
4
+ *
5
+ * @internal
6
+ */
7
+ export function emptyUsage() {
8
+ return {
9
+ costUsd: 0,
10
+ uncachedInputTokens: 0,
11
+ cacheReadTokens: 0,
12
+ cacheWriteTokens: 0,
13
+ outputTokens: 0,
14
+ };
15
+ }
16
+ /**
17
+ * Total tokens processed — the four buckets added up. Safe to sum because the
18
+ * buckets are disjoint (see {@link AggregateUsage}); the equivalent sum over
19
+ * per-message fields would double-count the prompt.
20
+ *
21
+ * Bear in mind what this number is: because every turn resends the conversation,
22
+ * a turn's prompt is counted again in the next turn's. It is billed throughput,
23
+ * NOT the count of distinct tokens in the conversation, so it grows
24
+ * super-linearly with turn count and is expected to dwarf the context size.
25
+ *
26
+ * @internal
27
+ */
28
+ export function totalTokens(usage) {
29
+ return (usage.uncachedInputTokens + usage.cacheReadTokens + usage.cacheWriteTokens + usage.outputTokens);
30
+ }
31
+ /**
32
+ * `a + b`, per bucket, as a new object.
33
+ *
34
+ * Used to lay a row's banked (pre-transcript) spend over what the live transcript
35
+ * proves. Pure rather than in-place because both operands are live state a caller
36
+ * must not mutate.
37
+ *
38
+ * @internal
39
+ */
40
+ export function addUsage(a, b) {
41
+ return {
42
+ costUsd: a.costUsd + b.costUsd,
43
+ uncachedInputTokens: a.uncachedInputTokens + b.uncachedInputTokens,
44
+ cacheReadTokens: a.cacheReadTokens + b.cacheReadTokens,
45
+ cacheWriteTokens: a.cacheWriteTokens + b.cacheWriteTokens,
46
+ outputTokens: a.outputTokens + b.outputTokens,
47
+ };
48
+ }
49
+ /**
50
+ * Sum cost and per-bucket token usage across a message list.
51
+ *
52
+ * One walk produces everything a usage display needs, replacing separate cost and
53
+ * token passes over the same (potentially deep) transcript. Recurses into each
54
+ * tool call's `subAgentTrace`, so LLM work a sub-agent did — possibly on another
55
+ * provider at other rates — lands in the total; the driver embeds a whole
56
+ * sub-agent conversation in the parent's tool result, so this is the only place
57
+ * that spend is visible.
58
+ *
59
+ * Splitting the prompt into buckets is the point: `cost` already has the cache
60
+ * discount applied, but a single token total cannot show that a session was cheap
61
+ * because most of its prompt was a cache hit. The three input buckets are derived
62
+ * from the per-message fields, where `inputTokens` is the whole prompt and the
63
+ * cache fields break it down — so uncached input is the remainder, never an
64
+ * addition.
65
+ *
66
+ * Also counts `compaction.rolledUpUsage`: compaction destroys the messages it
67
+ * summarises, and without their banked usage every total re-derived from the
68
+ * transcript would drop by whatever those turns had already spent.
69
+ *
70
+ * Returns a zeroed total when no message carries usage — providers that report
71
+ * none (e.g. Chrome built-in) are skipped silently rather than guessed at.
72
+ *
73
+ * @internal
74
+ */
75
+ export function sumUsage(messages) {
76
+ const total = emptyUsage();
77
+ accumulate(messages, total);
78
+ return total;
79
+ }
80
+ /** Add `from` into `into`, in place. */
81
+ function addInto(into, from) {
82
+ into.costUsd += from.costUsd;
83
+ into.uncachedInputTokens += from.uncachedInputTokens;
84
+ into.cacheReadTokens += from.cacheReadTokens;
85
+ into.cacheWriteTokens += from.cacheWriteTokens;
86
+ into.outputTokens += from.outputTokens;
87
+ }
88
+ /**
89
+ * Walk `messages`, accumulating into `into`. Mutates a single accumulator rather
90
+ * than merging returned objects — this runs on every transcript change, so the
91
+ * per-message allocation is worth avoiding.
92
+ */
93
+ function accumulate(messages, into) {
94
+ var _a, _b, _c, _d, _e, _f;
95
+ for (const m of messages) {
96
+ // Provider/LLM cost plus any non-LLM cost a widget reported for its own
97
+ // external service calls. Both are real spend on this session.
98
+ if (m.cost != null)
99
+ into.costUsd += m.cost;
100
+ if (m.externalCostUsd != null)
101
+ into.costUsd += m.externalCostUsd;
102
+ const cacheRead = (_a = m.cacheReadTokens) !== null && _a !== void 0 ? _a : 0;
103
+ const cacheWrite = (_b = m.cacheWriteTokens) !== null && _b !== void 0 ? _b : 0;
104
+ into.cacheReadTokens += cacheRead;
105
+ into.cacheWriteTokens += cacheWrite;
106
+ // The uncached remainder of the prompt. Clamped at zero so malformed or
107
+ // hand-edited history whose buckets exceed the prompt total can't drive the
108
+ // session total negative — a wrong-but-plausible number is worse than a
109
+ // conservative one here, since this feeds a cost display.
110
+ into.uncachedInputTokens += Math.max(0, ((_c = m.inputTokens) !== null && _c !== void 0 ? _c : 0) - cacheRead - cacheWrite);
111
+ into.outputTokens += (_d = m.outputTokens) !== null && _d !== void 0 ? _d : 0;
112
+ // Spend banked by a compaction on this summary's behalf. NOTE: deliberately
113
+ // not `compaction.tokensBefore` — that is a one-turn context-window reading,
114
+ // not cumulative spend, and counting it here would inflate the total.
115
+ const rolled = (_e = m.compaction) === null || _e === void 0 ? void 0 : _e.rolledUpUsage;
116
+ if (rolled)
117
+ addInto(into, rolled);
118
+ for (const tc of (_f = m.toolCalls) !== null && _f !== void 0 ? _f : []) {
119
+ if (tc.subAgentTrace)
120
+ accumulate(tc.subAgentTrace, into);
121
+ }
122
+ }
123
+ }
@@ -0,0 +1,120 @@
1
+ import { assert, createLogicSuite } from '@genesislcap/foundation-testing';
2
+ import { addUsage, emptyUsage, sumUsage, totalTokens } from './sum-usage';
3
+ const suite = createLogicSuite('sumUsage');
4
+ const msg = (over = {}) => (Object.assign({ role: 'assistant', content: '' }, over));
5
+ suite('returns zeroes when no message carries usage', () => {
6
+ const usage = sumUsage([msg({ role: 'user', content: 'hi' })]);
7
+ assert.equal(usage, emptyUsage());
8
+ assert.is(totalTokens(usage), 0);
9
+ });
10
+ suite('splits the prompt into three disjoint input buckets', () => {
11
+ // `inputTokens` is the WHOLE prompt; the cache fields break it down. So uncached is
12
+ // the remainder, and the three must re-add to the prompt exactly — that identity is
13
+ // what stops the buckets double-counting the prompt when summed.
14
+ const usage = sumUsage([
15
+ msg({ inputTokens: 1000, cacheReadTokens: 700, cacheWriteTokens: 200, outputTokens: 50 }),
16
+ ]);
17
+ assert.is(usage.uncachedInputTokens, 100);
18
+ assert.is(usage.cacheReadTokens, 700);
19
+ assert.is(usage.cacheWriteTokens, 200);
20
+ assert.is(usage.outputTokens, 50);
21
+ assert.is(usage.uncachedInputTokens + usage.cacheReadTokens + usage.cacheWriteTokens, 1000, 'input buckets re-add to the prompt total');
22
+ assert.is(totalTokens(usage), 1050);
23
+ });
24
+ suite('treats the whole prompt as uncached when no cache split is reported', () => {
25
+ // Providers that report no cache breakdown (and messages persisted before the fields
26
+ // existed) must still contribute their full prompt, not drop it.
27
+ const usage = sumUsage([msg({ inputTokens: 400, outputTokens: 60 })]);
28
+ assert.is(usage.uncachedInputTokens, 400);
29
+ assert.is(usage.cacheReadTokens, 0);
30
+ assert.is(usage.cacheWriteTokens, 0);
31
+ assert.is(totalTokens(usage), 460);
32
+ });
33
+ suite('clamps uncached input at zero when buckets exceed the prompt total', () => {
34
+ // Malformed or hand-edited history must not be able to drive a cost display negative.
35
+ const usage = sumUsage([
36
+ msg({ inputTokens: 100, cacheReadTokens: 90, cacheWriteTokens: 90, outputTokens: 0 }),
37
+ ]);
38
+ assert.is(usage.uncachedInputTokens, 0);
39
+ });
40
+ suite('sums LLM cost and widget-reported external cost', () => {
41
+ const usage = sumUsage([msg({ cost: 0.25 }), msg({ externalCostUsd: 0.1, cost: 0.05 })]);
42
+ assert.is(Math.round(usage.costUsd * 100) / 100, 0.4);
43
+ });
44
+ suite('recurses into sub-agent traces', () => {
45
+ // A sub-agent's whole conversation is embedded in the parent's tool call, so this is
46
+ // the only place its spend is visible to a host total.
47
+ const usage = sumUsage([
48
+ msg({
49
+ toolCalls: [
50
+ {
51
+ id: 'tc-1',
52
+ name: 'delegate',
53
+ args: {},
54
+ subAgentTrace: [
55
+ msg({ cost: 0.2, inputTokens: 300, cacheReadTokens: 100, outputTokens: 40 }),
56
+ ],
57
+ },
58
+ ],
59
+ }),
60
+ ]);
61
+ assert.is(usage.costUsd, 0.2);
62
+ assert.is(usage.uncachedInputTokens, 200);
63
+ assert.is(usage.cacheReadTokens, 100);
64
+ assert.is(usage.outputTokens, 40);
65
+ });
66
+ suite('counts usage banked onto a compaction summary', () => {
67
+ // Compaction destroys the messages it summarises. Without the banked rollup, a total
68
+ // re-derived from the transcript would fall by whatever those turns had spent.
69
+ const before = sumUsage([
70
+ msg({ cost: 0.5, inputTokens: 1000, cacheReadTokens: 400, outputTokens: 100 }),
71
+ msg({ cost: 0.3, inputTokens: 500, outputTokens: 50 }),
72
+ ]);
73
+ const afterCompaction = sumUsage([
74
+ msg({
75
+ role: 'compacted-summary',
76
+ content: 'summary of the above',
77
+ compaction: {
78
+ compactedCount: 2,
79
+ createdAt: '2026-06-14T12:00:00.000Z',
80
+ rolledUpUsage: before,
81
+ },
82
+ }),
83
+ ]);
84
+ assert.equal(afterCompaction, before, 'compaction preserves the running total');
85
+ });
86
+ suite('ignores tokensBefore, which is a context reading rather than spend', () => {
87
+ // `tokensBefore` is one turn's context-window size. Counting it as cumulative spend
88
+ // would silently inflate the session total on every compaction.
89
+ const usage = sumUsage([
90
+ msg({
91
+ role: 'compacted-summary',
92
+ content: 'summary',
93
+ compaction: {
94
+ compactedCount: 3,
95
+ createdAt: '2026-06-14T12:00:00.000Z',
96
+ tokensBefore: 90000,
97
+ },
98
+ }),
99
+ ]);
100
+ assert.is(totalTokens(usage), 0);
101
+ });
102
+ suite('addUsage adds every bucket and mutates neither operand', () => {
103
+ const a = Object.assign(Object.assign({}, emptyUsage()), { costUsd: 1.5, uncachedInputTokens: 100, outputTokens: 10 });
104
+ const b = Object.assign(Object.assign({}, emptyUsage()), { costUsd: 0.25, cacheReadTokens: 900, outputTokens: 5 });
105
+ const sum = addUsage(a, b);
106
+ assert.is(sum.costUsd, 1.75);
107
+ assert.is(sum.uncachedInputTokens, 100);
108
+ assert.is(sum.cacheReadTokens, 900);
109
+ assert.is(sum.outputTokens, 15);
110
+ // Both operands are live state (a row's banked figure and the session total), so a
111
+ // caller must be able to add without either being altered underneath it.
112
+ assert.is(a.costUsd, 1.5);
113
+ assert.is(b.cacheReadTokens, 900);
114
+ });
115
+ suite('addUsage with an empty operand is the identity', () => {
116
+ const a = Object.assign(Object.assign({}, emptyUsage()), { costUsd: 3, cacheWriteTokens: 42 });
117
+ assert.equal(addUsage(a, emptyUsage()), a);
118
+ assert.equal(addUsage(emptyUsage(), a), a);
119
+ });
120
+ suite.run();
@@ -1 +1 @@
1
- {"root":["../src/chat-driver-node.ts","../src/index.ts","../src/channel/ai-activity-bus.ts","../src/channel/ai-activity-channel.ts","../src/components/flowing-waves-indicator.ts","../src/components/halo-overlay.ts","../src/components/plasma-orb-indicator.ts","../src/components/waves-indicator.ts","../src/components/activity-halo/activity-halo.ts","../src/components/agent-picker/agent-picker.constants.ts","../src/components/agent-picker/agent-picker.styles.ts","../src/components/agent-picker/agent-picker.template.ts","../src/components/agent-picker/agent-picker.ts","../src/components/agent-picker/index.ts","../src/components/ai-driver/ai-driver.ts","../src/components/ai-driver/index.ts","../src/components/chat-bubble/chat-bubble.styles.ts","../src/components/chat-bubble/chat-bubble.template.ts","../src/components/chat-bubble/chat-bubble.ts","../src/components/chat-bubble/index.ts","../src/components/chat-driver/align-event-globals.ts","../src/components/chat-driver/chat-driver.compact.test.ts","../src/components/chat-driver/chat-driver.test.ts","../src/components/chat-driver/chat-driver.ts","../src/components/chat-driver/index.ts","../src/components/chat-interaction-wrapper/chat-interaction-wrapper.styles.ts","../src/components/chat-interaction-wrapper/chat-interaction-wrapper.template.ts","../src/components/chat-interaction-wrapper/chat-interaction-wrapper.test.ts","../src/components/chat-interaction-wrapper/chat-interaction-wrapper.ts","../src/components/chat-interaction-wrapper/index.ts","../src/components/chat-markdown/chat-markdown.ts","../src/components/chat-markdown/index.ts","../src/components/orchestrating-driver/index.ts","../src/components/orchestrating-driver/orchestrating-driver.pin.test.ts","../src/components/orchestrating-driver/orchestrating-driver.ts","../src/components/popout-manager/index.ts","../src/components/popout-manager/popout-manager.ts","../src/components/settings-modal/index.ts","../src/components/settings-modal/settings-modal.styles.ts","../src/components/settings-modal/settings-modal.template.ts","../src/config/config.ts","../src/config/define-stateful-agent.test.ts","../src/config/define-stateful-agent.ts","../src/config/fallback-agents.ts","../src/config/index.ts","../src/config/validate-providers.test.ts","../src/config/validate-providers.ts","../src/main/index.ts","../src/main/main.styles.ts","../src/main/main.template.ts","../src/main/main.ts","../src/main/main.types.ts","../src/main/popout-interaction-gate.test.ts","../src/provider/ai-provider-switcher.ts","../src/provider/assistant-app-settings.ts","../src/state/ai-assistant-slice.test.ts","../src/state/ai-assistant-slice.ts","../src/state/debug-event-log.test.ts","../src/state/debug-event-log.ts","../src/state/driver-registry.test.ts","../src/state/driver-registry.ts","../src/state/interaction-context.test.ts","../src/state/interaction-context.ts","../src/state/session-store.ts","../src/state/persistence/build-timeline-entries.ts","../src/state/persistence/diagnostics-cursors.test.ts","../src/state/persistence/diagnostics-cursors.ts","../src/state/persistence/diagnostics.test.ts","../src/state/persistence/diagnostics.ts","../src/state/persistence/index.ts","../src/state/persistence/persister-registry.ts","../src/state/persistence/session-persistence-provider.test.ts","../src/state/persistence/session-persistence-provider.ts","../src/state/persistence/session-persistence.integration.test.ts","../src/state/persistence/session-persister.test.ts","../src/state/persistence/session-persister.ts","../src/state/persistence/session-snapshot.test.ts","../src/state/persistence/session-snapshot.ts","../src/state/persistence/stateful-restore.e2e.test.ts","../src/styles/ai-colours.ts","../src/suggestions/chat-suggestions.ts","../src/tags/index.ts","../src/types/ai-chat-widget.ts","../src/types/interaction-context.ts","../src/utils/animated-panel-toggle.ts","../src/utils/animation-exclusivity.test.ts","../src/utils/animation-exclusivity.ts","../src/utils/collect-session-models.test.ts","../src/utils/collect-session-models.ts","../src/utils/condense-history.test.ts","../src/utils/condense-history.ts","../src/utils/cost-session-history.test.ts","../src/utils/cost-session-history.ts","../src/utils/derive-cost-session-title.test.ts","../src/utils/derive-cost-session-title.ts","../src/utils/flatten-sub-agent-messages.test.ts","../src/utils/flatten-sub-agent-messages.ts","../src/utils/history-transform.test.ts","../src/utils/history-transform.ts","../src/utils/index.ts","../src/utils/logger.ts","../src/utils/message-partition.test.ts","../src/utils/message-partition.ts","../src/utils/resolve-cost-history-config.test.ts","../src/utils/resolve-cost-history-config.ts","../src/utils/resolve-preference-baseline.test.ts","../src/utils/resolve-preference-baseline.ts","../src/utils/strip-agent-handlers.test.ts","../src/utils/strip-agent-handlers.ts","../src/utils/sum-costs.test.ts","../src/utils/sum-costs.ts","../src/utils/sum-tokens.test.ts","../src/utils/sum-tokens.ts","../src/utils/tool-fold.ts","../src/utils/with-timeout.ts"],"version":"5.9.2"}
1
+ {"root":["../src/chat-driver-node.ts","../src/index.ts","../src/channel/ai-activity-bus.ts","../src/channel/ai-activity-channel.ts","../src/components/flowing-waves-indicator.ts","../src/components/halo-overlay.ts","../src/components/plasma-orb-indicator.ts","../src/components/waves-indicator.ts","../src/components/activity-halo/activity-halo.ts","../src/components/agent-picker/agent-picker.constants.ts","../src/components/agent-picker/agent-picker.styles.ts","../src/components/agent-picker/agent-picker.template.ts","../src/components/agent-picker/agent-picker.ts","../src/components/agent-picker/index.ts","../src/components/ai-driver/ai-driver.ts","../src/components/ai-driver/index.ts","../src/components/chat-bubble/chat-bubble.styles.ts","../src/components/chat-bubble/chat-bubble.template.ts","../src/components/chat-bubble/chat-bubble.ts","../src/components/chat-bubble/index.ts","../src/components/chat-driver/align-event-globals.ts","../src/components/chat-driver/chat-driver.compact.test.ts","../src/components/chat-driver/chat-driver.test.ts","../src/components/chat-driver/chat-driver.ts","../src/components/chat-driver/index.ts","../src/components/chat-interaction-wrapper/chat-interaction-wrapper.styles.ts","../src/components/chat-interaction-wrapper/chat-interaction-wrapper.template.ts","../src/components/chat-interaction-wrapper/chat-interaction-wrapper.test.ts","../src/components/chat-interaction-wrapper/chat-interaction-wrapper.ts","../src/components/chat-interaction-wrapper/index.ts","../src/components/chat-markdown/chat-markdown.ts","../src/components/chat-markdown/index.ts","../src/components/orchestrating-driver/index.ts","../src/components/orchestrating-driver/orchestrating-driver.pin.test.ts","../src/components/orchestrating-driver/orchestrating-driver.ts","../src/components/popout-manager/index.ts","../src/components/popout-manager/popout-manager.ts","../src/components/settings-modal/index.ts","../src/components/settings-modal/settings-modal.styles.ts","../src/components/settings-modal/settings-modal.template.ts","../src/config/config.ts","../src/config/define-stateful-agent.test.ts","../src/config/define-stateful-agent.ts","../src/config/fallback-agents.ts","../src/config/index.ts","../src/config/validate-providers.test.ts","../src/config/validate-providers.ts","../src/main/cost-session-banking.test.ts","../src/main/index.ts","../src/main/main.styles.ts","../src/main/main.template.ts","../src/main/main.ts","../src/main/main.types.ts","../src/main/popout-interaction-gate.test.ts","../src/provider/ai-provider-switcher.ts","../src/provider/assistant-app-settings.ts","../src/state/ai-assistant-slice.test.ts","../src/state/ai-assistant-slice.ts","../src/state/debug-event-log.test.ts","../src/state/debug-event-log.ts","../src/state/driver-registry.test.ts","../src/state/driver-registry.ts","../src/state/interaction-context.test.ts","../src/state/interaction-context.ts","../src/state/session-store.ts","../src/state/persistence/build-timeline-entries.ts","../src/state/persistence/diagnostics-cursors.test.ts","../src/state/persistence/diagnostics-cursors.ts","../src/state/persistence/diagnostics.test.ts","../src/state/persistence/diagnostics.ts","../src/state/persistence/index.ts","../src/state/persistence/persister-registry.ts","../src/state/persistence/session-persistence-provider.test.ts","../src/state/persistence/session-persistence-provider.ts","../src/state/persistence/session-persistence.integration.test.ts","../src/state/persistence/session-persister.test.ts","../src/state/persistence/session-persister.ts","../src/state/persistence/session-snapshot.test.ts","../src/state/persistence/session-snapshot.ts","../src/state/persistence/stateful-restore.e2e.test.ts","../src/styles/ai-colours.ts","../src/styles/settings-section.ts","../src/suggestions/chat-suggestions.ts","../src/tags/index.ts","../src/types/ai-chat-widget.ts","../src/types/interaction-context.ts","../src/utils/animated-panel-toggle.ts","../src/utils/animation-exclusivity.test.ts","../src/utils/animation-exclusivity.ts","../src/utils/collect-session-models.test.ts","../src/utils/collect-session-models.ts","../src/utils/condense-history.test.ts","../src/utils/condense-history.ts","../src/utils/cost-session-history.test.ts","../src/utils/cost-session-history.ts","../src/utils/derive-cost-session-title.test.ts","../src/utils/derive-cost-session-title.ts","../src/utils/flatten-sub-agent-messages.test.ts","../src/utils/flatten-sub-agent-messages.ts","../src/utils/history-transform.test.ts","../src/utils/history-transform.ts","../src/utils/index.ts","../src/utils/logger.ts","../src/utils/message-partition.test.ts","../src/utils/message-partition.ts","../src/utils/resolve-cost-history-config.test.ts","../src/utils/resolve-cost-history-config.ts","../src/utils/resolve-preference-baseline.test.ts","../src/utils/resolve-preference-baseline.ts","../src/utils/strip-agent-handlers.test.ts","../src/utils/strip-agent-handlers.ts","../src/utils/sum-costs.test.ts","../src/utils/sum-costs.ts","../src/utils/sum-tokens.test.ts","../src/utils/sum-tokens.ts","../src/utils/sum-usage.test.ts","../src/utils/sum-usage.ts","../src/utils/tool-fold.ts","../src/utils/with-timeout.ts"],"version":"5.9.2"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@genesislcap/ai-assistant",
3
3
  "description": "Genesis AI Assistant micro-frontend",
4
- "version": "15.4.1",
4
+ "version": "15.6.0",
5
5
  "license": "SEE LICENSE IN license.txt",
6
6
  "main": "dist/esm/index.js",
7
7
  "types": "dist/ai-assistant.d.ts",
@@ -73,26 +73,26 @@
73
73
  }
74
74
  },
75
75
  "devDependencies": {
76
- "@genesislcap/foundation-testing": "15.4.1",
77
- "@genesislcap/genx": "15.4.1",
78
- "@genesislcap/rollup-builder": "15.4.1",
79
- "@genesislcap/ts-builder": "15.4.1",
80
- "@genesislcap/uvu-playwright-builder": "15.4.1",
81
- "@genesislcap/vite-builder": "15.4.1",
82
- "@genesislcap/webpack-builder": "15.4.1",
76
+ "@genesislcap/foundation-testing": "15.6.0",
77
+ "@genesislcap/genx": "15.6.0",
78
+ "@genesislcap/rollup-builder": "15.6.0",
79
+ "@genesislcap/ts-builder": "15.6.0",
80
+ "@genesislcap/uvu-playwright-builder": "15.6.0",
81
+ "@genesislcap/vite-builder": "15.6.0",
82
+ "@genesislcap/webpack-builder": "15.6.0",
83
83
  "@types/dompurify": "^3.0.5",
84
84
  "@types/marked": "^5.0.2",
85
85
  "esbuild": "0.25.12"
86
86
  },
87
87
  "dependencies": {
88
- "@genesislcap/foundation-ai": "15.4.1",
89
- "@genesislcap/foundation-logger": "15.4.1",
90
- "@genesislcap/foundation-notifications": "15.4.1",
91
- "@genesislcap/foundation-redux": "15.4.1",
92
- "@genesislcap/foundation-ui": "15.4.1",
93
- "@genesislcap/foundation-utils": "15.4.1",
94
- "@genesislcap/rapid-design-system": "15.4.1",
95
- "@genesislcap/web-core": "15.4.1",
88
+ "@genesislcap/foundation-ai": "15.6.0",
89
+ "@genesislcap/foundation-logger": "15.6.0",
90
+ "@genesislcap/foundation-notifications": "15.6.0",
91
+ "@genesislcap/foundation-redux": "15.6.0",
92
+ "@genesislcap/foundation-ui": "15.6.0",
93
+ "@genesislcap/foundation-utils": "15.6.0",
94
+ "@genesislcap/rapid-design-system": "15.6.0",
95
+ "@genesislcap/web-core": "15.6.0",
96
96
  "dompurify": "^3.3.1",
97
97
  "marked": "^17.0.3"
98
98
  },
@@ -105,5 +105,5 @@
105
105
  "access": "public"
106
106
  },
107
107
  "customElements": "dist/custom-elements.json",
108
- "gitHead": "984a77280362d69ba3eab6256dbb7fe03b8c76b3"
108
+ "gitHead": "dbf070bac8a4bfc2a178e335b1faffb130e8f761"
109
109
  }