@genesislcap/ai-assistant 15.4.1 → 15.5.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 (91) hide show
  1. package/dist/ai-assistant.api.json +544 -82
  2. package/dist/ai-assistant.d.ts +324 -36
  3. package/dist/chat-driver.cjs +94 -22
  4. package/dist/chat-driver.cjs.map +3 -3
  5. package/dist/chat-driver.mjs +94 -22
  6. package/dist/chat-driver.mjs.map +3 -3
  7. package/dist/custom-elements.json +303 -36
  8. package/dist/dts/components/chat-driver/chat-driver.d.ts.map +1 -1
  9. package/dist/dts/components/settings-modal/settings-modal.styles.d.ts.map +1 -1
  10. package/dist/dts/components/settings-modal/settings-modal.template.d.ts +9 -2
  11. package/dist/dts/components/settings-modal/settings-modal.template.d.ts.map +1 -1
  12. package/dist/dts/index.d.ts +1 -0
  13. package/dist/dts/index.d.ts.map +1 -1
  14. package/dist/dts/main/cost-session-banking.test.d.ts +2 -0
  15. package/dist/dts/main/cost-session-banking.test.d.ts.map +1 -0
  16. package/dist/dts/main/main.d.ts +184 -24
  17. package/dist/dts/main/main.d.ts.map +1 -1
  18. package/dist/dts/provider/assistant-app-settings.d.ts +30 -5
  19. package/dist/dts/provider/assistant-app-settings.d.ts.map +1 -1
  20. package/dist/dts/state/ai-assistant-slice.d.ts +11 -8
  21. package/dist/dts/state/ai-assistant-slice.d.ts.map +1 -1
  22. package/dist/dts/state/persistence/session-persistence-provider.d.ts +23 -0
  23. package/dist/dts/state/persistence/session-persistence-provider.d.ts.map +1 -1
  24. package/dist/dts/state/persistence/session-snapshot.d.ts.map +1 -1
  25. package/dist/dts/state/session-store.d.ts +1 -2
  26. package/dist/dts/state/session-store.d.ts.map +1 -1
  27. package/dist/dts/styles/settings-section.d.ts +29 -0
  28. package/dist/dts/styles/settings-section.d.ts.map +1 -0
  29. package/dist/dts/utils/cost-session-history.d.ts +103 -12
  30. package/dist/dts/utils/cost-session-history.d.ts.map +1 -1
  31. package/dist/dts/utils/resolve-cost-history-config.d.ts +9 -3
  32. package/dist/dts/utils/resolve-cost-history-config.d.ts.map +1 -1
  33. package/dist/dts/utils/sum-costs.d.ts.map +1 -1
  34. package/dist/dts/utils/sum-tokens.d.ts +8 -8
  35. package/dist/dts/utils/sum-tokens.d.ts.map +1 -1
  36. package/dist/dts/utils/sum-usage.d.ts +59 -0
  37. package/dist/dts/utils/sum-usage.d.ts.map +1 -0
  38. package/dist/dts/utils/sum-usage.test.d.ts +2 -0
  39. package/dist/dts/utils/sum-usage.test.d.ts.map +1 -0
  40. package/dist/esm/components/chat-driver/chat-driver.js +6 -0
  41. package/dist/esm/components/settings-modal/settings-modal.styles.js +237 -18
  42. package/dist/esm/components/settings-modal/settings-modal.template.js +229 -73
  43. package/dist/esm/index.js +1 -0
  44. package/dist/esm/main/cost-session-banking.test.js +308 -0
  45. package/dist/esm/main/main.js +424 -71
  46. package/dist/esm/state/ai-assistant-slice.js +11 -8
  47. package/dist/esm/state/ai-assistant-slice.test.js +12 -5
  48. package/dist/esm/state/debug-event-log.js +2 -2
  49. package/dist/esm/state/persistence/session-persistence.integration.test.js +5 -1
  50. package/dist/esm/state/persistence/session-persister.js +2 -2
  51. package/dist/esm/state/persistence/session-persister.test.js +10 -1
  52. package/dist/esm/state/persistence/session-snapshot.js +6 -2
  53. package/dist/esm/state/persistence/session-snapshot.test.js +4 -1
  54. package/dist/esm/state/persistence/stateful-restore.e2e.test.js +10 -1
  55. package/dist/esm/styles/settings-section.js +39 -0
  56. package/dist/esm/utils/cost-session-history.js +92 -15
  57. package/dist/esm/utils/cost-session-history.test.js +155 -13
  58. package/dist/esm/utils/resolve-cost-history-config.js +2 -1
  59. package/dist/esm/utils/sum-costs.js +2 -13
  60. package/dist/esm/utils/sum-tokens.js +10 -27
  61. package/dist/esm/utils/sum-tokens.test.js +1 -5
  62. package/dist/esm/utils/sum-usage.js +123 -0
  63. package/dist/esm/utils/sum-usage.test.js +120 -0
  64. package/dist/tsconfig.tsbuildinfo +1 -1
  65. package/package.json +17 -17
  66. package/src/components/chat-driver/chat-driver.ts +6 -0
  67. package/src/components/settings-modal/settings-modal.styles.ts +237 -18
  68. package/src/components/settings-modal/settings-modal.template.ts +270 -81
  69. package/src/index.ts +1 -0
  70. package/src/main/cost-session-banking.test.ts +407 -0
  71. package/src/main/main.ts +433 -68
  72. package/src/provider/assistant-app-settings.ts +31 -5
  73. package/src/state/ai-assistant-slice.test.ts +12 -5
  74. package/src/state/ai-assistant-slice.ts +21 -13
  75. package/src/state/debug-event-log.ts +2 -2
  76. package/src/state/persistence/session-persistence-provider.ts +24 -0
  77. package/src/state/persistence/session-persistence.integration.test.ts +8 -1
  78. package/src/state/persistence/session-persister.test.ts +10 -1
  79. package/src/state/persistence/session-persister.ts +2 -2
  80. package/src/state/persistence/session-snapshot.test.ts +4 -1
  81. package/src/state/persistence/session-snapshot.ts +5 -1
  82. package/src/state/persistence/stateful-restore.e2e.test.ts +9 -1
  83. package/src/styles/settings-section.ts +40 -0
  84. package/src/utils/cost-session-history.test.ts +187 -16
  85. package/src/utils/cost-session-history.ts +142 -23
  86. package/src/utils/resolve-cost-history-config.ts +10 -3
  87. package/src/utils/sum-costs.ts +2 -9
  88. package/src/utils/sum-tokens.test.ts +1 -11
  89. package/src/utils/sum-tokens.ts +10 -26
  90. package/src/utils/sum-usage.test.ts +140 -0
  91. package/src/utils/sum-usage.ts +130 -0
@@ -11,8 +11,7 @@ declare function makeStore(devTools: boolean | undefined): import("@genesislcap/
11
11
  setSuggestionsState(state: import("immer").WritableDraft<AiAssistantSessionState>, action: import("@reduxjs/toolkit").PayloadAction<import("..").SuggestionsState>): void;
12
12
  setContextTokens(state: import("immer").WritableDraft<AiAssistantSessionState>, action: import("@reduxjs/toolkit").PayloadAction<number | undefined>): void;
13
13
  setContextLimit(state: import("immer").WritableDraft<AiAssistantSessionState>, action: import("@reduxjs/toolkit").PayloadAction<number | undefined>): void;
14
- setSessionCostUsd(state: import("immer").WritableDraft<AiAssistantSessionState>, action: import("@reduxjs/toolkit").PayloadAction<number>): void;
15
- setSessionTokensConsumed(state: import("immer").WritableDraft<AiAssistantSessionState>, action: import("@reduxjs/toolkit").PayloadAction<number>): void;
14
+ setSessionUsage(state: import("immer").WritableDraft<AiAssistantSessionState>, action: import("@reduxjs/toolkit").PayloadAction<import("@genesislcap/foundation-ai").AggregateUsage>): void;
16
15
  setActiveModel(state: import("immer").WritableDraft<AiAssistantSessionState>, action: import("@reduxjs/toolkit").PayloadAction<string | undefined>): void;
17
16
  setActiveProviderName(state: import("immer").WritableDraft<AiAssistantSessionState>, action: import("@reduxjs/toolkit").PayloadAction<string | undefined>): void;
18
17
  setProviderStatuses(state: import("immer").WritableDraft<AiAssistantSessionState>, action: import("@reduxjs/toolkit").PayloadAction<import("@genesislcap/foundation-ai").AIProviderRegistryStatusEntry[]>): void;
@@ -1 +1 @@
1
- {"version":3,"file":"session-store.d.ts","sourceRoot":"","sources":["../../../src/state/session-store.ts"],"names":[],"mappings":"AACA,OAAO,EAGL,KAAK,uBAAuB,EAC7B,MAAM,sBAAsB,CAAC;AAE9B,KAAK,kBAAkB,GAAG,UAAU,CAAC,OAAO,SAAS,CAAC,CAAC;AAIvD,iBAAS,SAAS,CAAC,QAAQ,EAAE,OAAO,GAAG,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;wCAM/C;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,OAAO,GAAG,kBAAkB,CAMnF;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAEpD;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAEpD;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,IAAI,IAAI,CAE5C;AAED,YAAY,EAAE,kBAAkB,EAAE,uBAAuB,EAAE,CAAC"}
1
+ {"version":3,"file":"session-store.d.ts","sourceRoot":"","sources":["../../../src/state/session-store.ts"],"names":[],"mappings":"AACA,OAAO,EAGL,KAAK,uBAAuB,EAC7B,MAAM,sBAAsB,CAAC;AAE9B,KAAK,kBAAkB,GAAG,UAAU,CAAC,OAAO,SAAS,CAAC,CAAC;AAIvD,iBAAS,SAAS,CAAC,QAAQ,EAAE,OAAO,GAAG,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;wCAM/C;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,OAAO,GAAG,kBAAkB,CAMnF;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAEpD;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAEpD;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,IAAI,IAAI,CAE5C;AAED,YAAY,EAAE,kBAAkB,EAAE,uBAAuB,EAAE,CAAC"}
@@ -0,0 +1,29 @@
1
+ import { type ElementStyles } from '@genesislcap/web-core';
2
+ /**
3
+ * Heading style for a settings-modal section, published so a host can title its own section.
4
+ *
5
+ * The assistant renders no heading for the `settings-app` slot — no text and no icon — because
6
+ * only the host knows what that section is. The consequence is that a host supplying one has to
7
+ * reproduce the styling of the built-in headings beside it, and a copied rule drifts the moment
8
+ * either side changes. So the declarations live here, are applied by the assistant's own
9
+ * headings, and are exported for the host to apply to its.
10
+ *
11
+ * Compose it into a component's styles and put `ai-settings-section-title` on the heading:
12
+ *
13
+ * ```ts
14
+ * import { assistantSettingsSectionTitleStyles } from '@genesislcap/ai-assistant';
15
+ *
16
+ * const styles = css`
17
+ * ${assistantSettingsSectionTitleStyles}
18
+ * :host { display: block; }
19
+ * `;
20
+ * ```
21
+ *
22
+ * Carries no margin on purpose: spacing belongs to the surrounding layout, which differs
23
+ * between the assistant's own sections and a slotted one, and is the single declaration hosts
24
+ * had to diverge on when they copied this rule.
25
+ *
26
+ * @beta
27
+ */
28
+ export declare const assistantSettingsSectionTitleStyles: ElementStyles;
29
+ //# sourceMappingURL=settings-section.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"settings-section.d.ts","sourceRoot":"","sources":["../../../src/styles/settings-section.ts"],"names":[],"mappings":"AAAA,OAAO,EAAO,KAAK,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAEhE;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,eAAO,MAAM,mCAAmC,EAAE,aAWjD,CAAC"}
@@ -1,32 +1,123 @@
1
- import type { AIProviderType } from '@genesislcap/foundation-ai';
2
- /** A model used during a finalized cost session. */
1
+ import type { AggregateUsage, AIProviderType } from '@genesislcap/foundation-ai';
2
+ /** A model used during a recorded cost session. */
3
3
  export interface CostSessionModelEntry {
4
4
  model: string;
5
5
  /** Vendor id from `getStatus()` (e.g. `'anthropic'`, `'gemini'`). */
6
6
  provider?: AIProviderType;
7
7
  }
8
- /** Persisted snapshot of a completed assistant cost session. */
8
+ /** Persisted usage record for one project's assistant work. */
9
9
  export interface CostSessionRecord {
10
10
  id: string;
11
+ /**
12
+ * Stable identity of the work this row stands for — the assistant's session key
13
+ * (a project id, for hosts that key sessions per project). The upsert key: a row
14
+ * is one project's lifetime usage, so re-reporting the same key REPLACES the row
15
+ * rather than adding another.
16
+ */
17
+ projectKey: string;
11
18
  title: string;
12
- /** ISO-8601 timestamp when the session was finalized. */
13
- endedAt: string;
14
- costUsd: number;
15
- tokensConsumed: number;
19
+ /**
20
+ * ISO-8601 timestamp of the most recent usage recorded for this row.
21
+ *
22
+ * Named for what it is: the row is upserted as spend accrues, so this moves. It is
23
+ * NOT an end time — a row has no terminal state, because a project can always be
24
+ * worked on again.
25
+ */
26
+ updatedAt: string;
27
+ /**
28
+ * Cost plus the four token buckets — the same shape the live session tile renders, so
29
+ * a history row can show the identical breakdown instead of a lone merged total that
30
+ * cannot explain a large token count at a small cost.
31
+ *
32
+ * This is the project's **lifetime** usage: `banked` plus whatever the live transcript
33
+ * currently proves.
34
+ */
35
+ usage: AggregateUsage;
36
+ /**
37
+ * Spend that predates the current transcript, and so cannot be re-derived from it.
38
+ *
39
+ * Without this a row could only ever report what the open transcript proves. That is
40
+ * fine when the transcript is restored on load (it *is* the project's history), but
41
+ * where it starts empty every refresh the row would be rewritten downwards on the next
42
+ * turn and the project's earlier spend lost. Banking it keeps `usage` a lifetime figure
43
+ * in both cases.
44
+ *
45
+ * Deliberately part of the row rather than element state: two assistant instances (a
46
+ * docked bubble and a popped-out panel) share one session, and both write this row. A
47
+ * value derived from the row keeps every write idempotent, whereas an element-held
48
+ * accumulator would be applied once per instance.
49
+ *
50
+ * Optional: rows written before this existed have none, which reads as "nothing banked".
51
+ */
52
+ banked?: AggregateUsage;
16
53
  models: CostSessionModelEntry[];
17
54
  }
18
55
  /** localStorage key for a host-scoped cost history list. */
19
56
  export declare function costHistoryStorageKey(scope: string): string;
20
- /** Load persisted cost sessions for a scope (newest first). */
57
+ /**
58
+ * Load persisted cost sessions for a scope, in whatever order they were stored — this applies no
59
+ * ordering of its own. Display order is the caller's business; see `sortRecordsByRecency`.
60
+ */
21
61
  export declare function loadCostSessionHistory(scope: string): CostSessionRecord[];
22
62
  /** Replace the persisted list for a scope. */
23
63
  export declare function saveCostSessionHistory(scope: string, records: CostSessionRecord[]): void;
24
- /** Prepend a record and persist. Returns the updated list. */
25
- export declare function appendCostSessionRecord(scope: string, record: CostSessionRecord): CostSessionRecord[];
26
- /** Remove one record by id. Returns the updated list. */
27
- export declare function removeCostSessionRecord(scope: string, id: string): CostSessionRecord[];
64
+ /**
65
+ * Pure upsert `records` with `record` replacing any row sharing its `projectKey`, or
66
+ * prepended when there is none.
67
+ *
68
+ * An upsert, not an append, because a row stands for a **project's lifetime usage** rather
69
+ * than a visit to it. Appending cut a fresh row per page-hide, so one project accumulated
70
+ * a stack of near-identical rows and its spend was counted once per refresh by anything
71
+ * summing the list.
72
+ *
73
+ * The resulting array order is incidental — it is insertion order, so a project returned to
74
+ * stays where it first landed. Do not read it as recency; `sortRecordsByRecency` is what the
75
+ * Usage tab renders through.
76
+ *
77
+ * Storage-agnostic on purpose: the same merge serves the synchronous `localStorage` path
78
+ * and the async provider-backed one, so the two can never drift on what "upsert" means.
79
+ * The caller owns persistence — see the assistant's `persistCostHistory`.
80
+ */
81
+ export declare function upsertRecord(records: readonly CostSessionRecord[], record: CostSessionRecord): CostSessionRecord[];
82
+ /**
83
+ * Rows most-recently-worked-on first, as a new array.
84
+ *
85
+ * Applied at read time rather than baked into the stored order, so it covers rows that came from
86
+ * a host provider as well as from `localStorage`, and so no write path has to maintain it.
87
+ *
88
+ * `updatedAt` is ISO-8601, which sorts lexicographically — no date parsing, and no dependence on
89
+ * the strings being valid dates. Sorting by it rather than by insertion order is what makes the
90
+ * list track use: the active project moves to the top on its first upsert and stays there, since
91
+ * its row is rewritten on every transcript change.
92
+ */
93
+ export declare function sortRecordsByRecency(records: readonly CostSessionRecord[]): CostSessionRecord[];
94
+ /**
95
+ * The spend a project's next row must carry over from before its current transcript.
96
+ *
97
+ * `transcriptIsAuthoritative` means the open transcript already accounts for this project's
98
+ * history — true exactly when a session snapshot is restored on load. The two branches are
99
+ * not interchangeable, and picking the wrong one is silently destructive:
100
+ *
101
+ * - Authoritative: carry the previous row's own `banked` forward. Taking `usage` instead would
102
+ * re-add the restored history on every reload, the compounding double-count this replaced.
103
+ * - Not authoritative: the whole previous total becomes banked, because nothing in the fresh
104
+ * transcript accounts for it. Taking `banked` instead would rewrite the row down to the
105
+ * current page-load and drop the project's earlier spend.
106
+ *
107
+ * Note both branches are idempotent — the result never includes the current transcript — which
108
+ * is what lets two assistant instances sharing a session write the same row.
109
+ */
110
+ export declare function resolveBankedUsage(existing: CostSessionRecord | undefined, transcriptIsAuthoritative: boolean): AggregateUsage;
28
111
  /** Clear all persisted records for a scope. */
29
112
  export declare function clearCostSessionHistory(scope: string): void;
30
113
  /** Display date for a history row (e.g. "Jun 14, 2026"). */
31
114
  export declare function formatCostSessionDate(iso: string): string;
115
+ /**
116
+ * Whether a value is a usable row. Exported because rows arrive by two routes and BOTH need it:
117
+ * `loadCostSessionHistory` filters the `localStorage` path here, and the assistant filters what a
118
+ * host provider returns. A row that skips this check is not merely ignored downstream — a
119
+ * half-shaped `usage` sums to `NaN` through every total, and a missing `updatedAt` throws inside
120
+ * the sort that orders the list, during render.
121
+ */
122
+ export declare function isCostSessionRecord(value: unknown): value is CostSessionRecord;
32
123
  //# sourceMappingURL=cost-session-history.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"cost-session-history.d.ts","sourceRoot":"","sources":["../../../src/utils/cost-session-history.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAEjE,oDAAoD;AACpD,MAAM,WAAW,qBAAqB;IACpC,KAAK,EAAE,MAAM,CAAC;IACd,qEAAqE;IACrE,QAAQ,CAAC,EAAE,cAAc,CAAC;CAC3B;AAED,gEAAgE;AAChE,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,yDAAyD;IACzD,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,cAAc,EAAE,MAAM,CAAC;IACvB,MAAM,EAAE,qBAAqB,EAAE,CAAC;CACjC;AAID,4DAA4D;AAC5D,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAE3D;AAED,+DAA+D;AAC/D,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,MAAM,GAAG,iBAAiB,EAAE,CAWzE;AAED,8CAA8C;AAC9C,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,iBAAiB,EAAE,GAAG,IAAI,CAOxF;AAED,8DAA8D;AAC9D,wBAAgB,uBAAuB,CACrC,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,iBAAiB,GACxB,iBAAiB,EAAE,CAIrB;AAED,yDAAyD;AACzD,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,iBAAiB,EAAE,CAItF;AAED,+CAA+C;AAC/C,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAO3D;AAED,4DAA4D;AAC5D,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAMzD"}
1
+ {"version":3,"file":"cost-session-history.d.ts","sourceRoot":"","sources":["../../../src/utils/cost-session-history.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAGjF,mDAAmD;AACnD,MAAM,WAAW,qBAAqB;IACpC,KAAK,EAAE,MAAM,CAAC;IACd,qEAAqE;IACrE,QAAQ,CAAC,EAAE,cAAc,CAAC;CAC3B;AAED,+DAA+D;AAC/D,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,MAAM,CAAC;IACX;;;;;OAKG;IACH,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd;;;;;;OAMG;IACH,SAAS,EAAE,MAAM,CAAC;IAClB;;;;;;;OAOG;IACH,KAAK,EAAE,cAAc,CAAC;IACtB;;;;;;;;;;;;;;;OAeG;IACH,MAAM,CAAC,EAAE,cAAc,CAAC;IACxB,MAAM,EAAE,qBAAqB,EAAE,CAAC;CACjC;AAqBD,4DAA4D;AAC5D,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAE3D;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,MAAM,GAAG,iBAAiB,EAAE,CAWzE;AAED,8CAA8C;AAC9C,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,iBAAiB,EAAE,GAAG,IAAI,CAOxF;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,YAAY,CAC1B,OAAO,EAAE,SAAS,iBAAiB,EAAE,EACrC,MAAM,EAAE,iBAAiB,GACxB,iBAAiB,EAAE,CAGrB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,SAAS,iBAAiB,EAAE,GAAG,iBAAiB,EAAE,CAE/F;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,iBAAiB,GAAG,SAAS,EACvC,yBAAyB,EAAE,OAAO,GACjC,cAAc,CAIhB;AAED,+CAA+C;AAC/C,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAO3D;AAED,4DAA4D;AAC5D,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAMzD;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,iBAAiB,CAiB9E"}
@@ -7,12 +7,18 @@ export interface ResolvedCostHistoryConfig {
7
7
  title?: string;
8
8
  /** `localStorage` scope key; falls back to element `id` then `'default'`. */
9
9
  scope?: string;
10
- /** Show running session USD on the Cost tab. Default: `true`. */
10
+ /** Show running session USD on the Usage tab. Default: `true`. */
11
11
  showCost: boolean;
12
- /** Show cumulative session tokens on the Cost tab. Default: `true`. */
12
+ /** Show cumulative session tokens on the Usage tab. Default: `true`. */
13
13
  showTokens: boolean;
14
- /** Live-session badge label on the Cost tab summary. */
14
+ /** Live-session badge label on the Usage tab summary. */
15
15
  badgeLabel: string;
16
+ /**
17
+ * Host-supplied disclosure line for the Usage tab. Stays optional — unlike
18
+ * `badgeLabel` there is no default, because only the host knows what its `scope`
19
+ * spans, and the library must not assert it.
20
+ */
21
+ summaryHint?: string;
16
22
  }
17
23
  /** Resolve `chatConfig.costHistory` with defaults applied. */
18
24
  export declare function resolveCostHistoryConfig(chatConfig: ChatConfig): ResolvedCostHistoryConfig;
@@ -1 +1 @@
1
- {"version":3,"file":"resolve-cost-history-config.d.ts","sourceRoot":"","sources":["../../../src/utils/resolve-cost-history-config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC;AAE7D,oEAAoE;AACpE,MAAM,WAAW,yBAAyB;IACxC,8EAA8E;IAC9E,OAAO,EAAE,OAAO,CAAC;IACjB,6DAA6D;IAC7D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,6EAA6E;IAC7E,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,iEAAiE;IACjE,QAAQ,EAAE,OAAO,CAAC;IAClB,uEAAuE;IACvE,UAAU,EAAE,OAAO,CAAC;IACpB,wDAAwD;IACxD,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,8DAA8D;AAC9D,wBAAgB,wBAAwB,CAAC,UAAU,EAAE,UAAU,GAAG,yBAAyB,CAW1F"}
1
+ {"version":3,"file":"resolve-cost-history-config.d.ts","sourceRoot":"","sources":["../../../src/utils/resolve-cost-history-config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC;AAE7D,oEAAoE;AACpE,MAAM,WAAW,yBAAyB;IACxC,8EAA8E;IAC9E,OAAO,EAAE,OAAO,CAAC;IACjB,6DAA6D;IAC7D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,6EAA6E;IAC7E,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,kEAAkE;IAClE,QAAQ,EAAE,OAAO,CAAC;IAClB,wEAAwE;IACxE,UAAU,EAAE,OAAO,CAAC;IACpB,yDAAyD;IACzD,UAAU,EAAE,MAAM,CAAC;IACnB;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,8DAA8D;AAC9D,wBAAgB,wBAAwB,CAAC,UAAU,EAAE,UAAU,GAAG,yBAAyB,CAY1F"}
@@ -1 +1 @@
1
- {"version":3,"file":"sum-costs.d.ts","sourceRoot":"","sources":["../../../src/utils/sum-costs.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AAE9D;;;;;;;;;;;GAWG;AACH,wBAAgB,QAAQ,CAAC,QAAQ,EAAE,SAAS,WAAW,EAAE,GAAG,MAAM,CAUjE"}
1
+ {"version":3,"file":"sum-costs.d.ts","sourceRoot":"","sources":["../../../src/utils/sum-costs.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AAG9D;;;;;;;;;;;GAWG;AACH,wBAAgB,QAAQ,CAAC,QAAQ,EAAE,SAAS,WAAW,EAAE,GAAG,MAAM,CAEjE"}
@@ -1,19 +1,19 @@
1
1
  import type { ChatMessage } from '@genesislcap/foundation-ai';
2
2
  /**
3
- * Sum input + output tokens across a message list, recursing into each tool
4
- * call's `subAgentTrace` so sub-agent LLM usage contributes to the total.
3
+ * Total tokens across a message list — the headline figure, with the per-bucket
4
+ * split available from {@link sumUsage} for anything that needs to show *why* a
5
+ * large token count cost little.
6
+ *
7
+ * Identical arithmetic to the old per-message `inputTokens + outputTokens` sum:
8
+ * the three input buckets partition `inputTokens`, so they re-add to it exactly.
9
+ * It does now additionally count usage banked by a compaction, which is the point
10
+ * — that spend used to vanish when the summarised turns left the transcript.
5
11
  *
6
12
  * Returns 0 when no message carries token metadata.
7
13
  *
8
14
  * @internal
9
15
  */
10
16
  export declare function sumTokens(messages: readonly ChatMessage[]): number;
11
- /**
12
- * Compact plain-text token count (e.g. 1_400_000 → "1.4M").
13
- *
14
- * @internal
15
- */
16
- export declare function formatCompactTokenCountPlain(tokens: number): string;
17
17
  /**
18
18
  * Compact display for large token counts (e.g. 1_400_000 → value "1.4" + suffix "M").
19
19
  * Returns HTML markup so the suffix can be styled separately in the settings modal.
@@ -1 +1 @@
1
- {"version":3,"file":"sum-tokens.d.ts","sourceRoot":"","sources":["../../../src/utils/sum-tokens.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AAM9D;;;;;;;GAOG;AACH,wBAAgB,SAAS,CAAC,QAAQ,EAAE,SAAS,WAAW,EAAE,GAAG,MAAM,CASlE;AAgCD;;;;GAIG;AACH,wBAAgB,4BAA4B,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAMnE;AAED;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAI9D;AAED,0FAA0F;AAC1F,wBAAgB,2BAA2B,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAElE"}
1
+ {"version":3,"file":"sum-tokens.d.ts","sourceRoot":"","sources":["../../../src/utils/sum-tokens.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AAI9D;;;;;;;;;;;;;GAaG;AACH,wBAAgB,SAAS,CAAC,QAAQ,EAAE,SAAS,WAAW,EAAE,GAAG,MAAM,CAElE;AAgCD;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAI9D;AAED,0FAA0F;AAC1F,wBAAgB,2BAA2B,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAElE"}
@@ -0,0 +1,59 @@
1
+ import type { AggregateUsage, ChatMessage } from '@genesislcap/foundation-ai';
2
+ /**
3
+ * Zeroed {@link AggregateUsage}. A factory, not a shared const, because callers
4
+ * accumulate into the returned object.
5
+ *
6
+ * @internal
7
+ */
8
+ export declare function emptyUsage(): AggregateUsage;
9
+ /**
10
+ * Total tokens processed — the four buckets added up. Safe to sum because the
11
+ * buckets are disjoint (see {@link AggregateUsage}); the equivalent sum over
12
+ * per-message fields would double-count the prompt.
13
+ *
14
+ * Bear in mind what this number is: because every turn resends the conversation,
15
+ * a turn's prompt is counted again in the next turn's. It is billed throughput,
16
+ * NOT the count of distinct tokens in the conversation, so it grows
17
+ * super-linearly with turn count and is expected to dwarf the context size.
18
+ *
19
+ * @internal
20
+ */
21
+ export declare function totalTokens(usage: AggregateUsage): number;
22
+ /**
23
+ * `a + b`, per bucket, as a new object.
24
+ *
25
+ * Used to lay a row's banked (pre-transcript) spend over what the live transcript
26
+ * proves. Pure rather than in-place because both operands are live state a caller
27
+ * must not mutate.
28
+ *
29
+ * @internal
30
+ */
31
+ export declare function addUsage(a: AggregateUsage, b: AggregateUsage): AggregateUsage;
32
+ /**
33
+ * Sum cost and per-bucket token usage across a message list.
34
+ *
35
+ * One walk produces everything a usage display needs, replacing separate cost and
36
+ * token passes over the same (potentially deep) transcript. Recurses into each
37
+ * tool call's `subAgentTrace`, so LLM work a sub-agent did — possibly on another
38
+ * provider at other rates — lands in the total; the driver embeds a whole
39
+ * sub-agent conversation in the parent's tool result, so this is the only place
40
+ * that spend is visible.
41
+ *
42
+ * Splitting the prompt into buckets is the point: `cost` already has the cache
43
+ * discount applied, but a single token total cannot show that a session was cheap
44
+ * because most of its prompt was a cache hit. The three input buckets are derived
45
+ * from the per-message fields, where `inputTokens` is the whole prompt and the
46
+ * cache fields break it down — so uncached input is the remainder, never an
47
+ * addition.
48
+ *
49
+ * Also counts `compaction.rolledUpUsage`: compaction destroys the messages it
50
+ * summarises, and without their banked usage every total re-derived from the
51
+ * transcript would drop by whatever those turns had already spent.
52
+ *
53
+ * Returns a zeroed total when no message carries usage — providers that report
54
+ * none (e.g. Chrome built-in) are skipped silently rather than guessed at.
55
+ *
56
+ * @internal
57
+ */
58
+ export declare function sumUsage(messages: readonly ChatMessage[]): AggregateUsage;
59
+ //# sourceMappingURL=sum-usage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sum-usage.d.ts","sourceRoot":"","sources":["../../../src/utils/sum-usage.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AAE9E;;;;;GAKG;AACH,wBAAgB,UAAU,IAAI,cAAc,CAQ3C;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,cAAc,GAAG,MAAM,CAIzD;AAED;;;;;;;;GAQG;AACH,wBAAgB,QAAQ,CAAC,CAAC,EAAE,cAAc,EAAE,CAAC,EAAE,cAAc,GAAG,cAAc,CAQ7E;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,QAAQ,CAAC,QAAQ,EAAE,SAAS,WAAW,EAAE,GAAG,cAAc,CAIzE"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=sum-usage.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sum-usage.test.d.ts","sourceRoot":"","sources":["../../../src/utils/sum-usage.test.ts"],"names":[],"mappings":""}
@@ -7,6 +7,7 @@ import { createInteractionContext, } from '../../state/interaction-context';
7
7
  import { applyCondensation } from '../../utils/condense-history';
8
8
  import { applyHistoryCap, buildCompactionSummaryPrompt, findCompactionCut, normalizeForProvider, } from '../../utils/history-transform';
9
9
  import { logger } from '../../utils/logger';
10
+ import { sumUsage } from '../../utils/sum-usage';
10
11
  import { TOOL_FOLD_SYMBOL } from '../../utils/tool-fold';
11
12
  const DEFAULT_MAX_TOOL_ITERATIONS = 50;
12
13
  const DEFAULT_MAX_FOLD_OPERATIONS = 5;
@@ -907,6 +908,11 @@ export class ChatDriver extends EventTarget {
907
908
  tokensBefore,
908
909
  model: status === null || status === void 0 ? void 0 : status.model,
909
910
  createdAt,
911
+ // Bank what the replaced turns spent. `toCompact` is dropped from history
912
+ // below, taking its per-message cost/token fields with it — so a host total
913
+ // re-derived from the transcript would otherwise fall by exactly this much
914
+ // on every compaction. Summarising a turn does not refund what it cost.
915
+ rolledUpUsage: sumUsage(toCompact),
910
916
  },
911
917
  };
912
918
  this.loadHistory([summaryMessage, ...tail]);