@genesislcap/ai-assistant 15.3.3 → 15.3.4-GENC-1475.2
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.
- package/dist/ai-assistant.api.json +333 -43
- package/dist/ai-assistant.d.ts +185 -19
- package/dist/chat-driver.cjs +94 -22
- package/dist/chat-driver.cjs.map +3 -3
- package/dist/chat-driver.mjs +94 -22
- package/dist/chat-driver.mjs.map +3 -3
- package/dist/custom-elements.json +139 -23
- package/dist/dts/components/chat-driver/chat-driver.d.ts.map +1 -1
- package/dist/dts/components/settings-modal/settings-modal.styles.d.ts.map +1 -1
- package/dist/dts/components/settings-modal/settings-modal.template.d.ts.map +1 -1
- package/dist/dts/main/cost-session-banking.test.d.ts +2 -0
- package/dist/dts/main/cost-session-banking.test.d.ts.map +1 -0
- package/dist/dts/main/main.d.ts +114 -14
- package/dist/dts/main/main.d.ts.map +1 -1
- package/dist/dts/state/ai-assistant-slice.d.ts +11 -8
- package/dist/dts/state/ai-assistant-slice.d.ts.map +1 -1
- package/dist/dts/state/persistence/session-persistence-provider.d.ts +23 -0
- package/dist/dts/state/persistence/session-persistence-provider.d.ts.map +1 -1
- package/dist/dts/state/persistence/session-snapshot.d.ts.map +1 -1
- package/dist/dts/state/session-store.d.ts +1 -2
- package/dist/dts/state/session-store.d.ts.map +1 -1
- package/dist/dts/utils/cost-session-history.d.ts +76 -11
- package/dist/dts/utils/cost-session-history.d.ts.map +1 -1
- package/dist/dts/utils/resolve-cost-history-config.d.ts +6 -0
- package/dist/dts/utils/resolve-cost-history-config.d.ts.map +1 -1
- package/dist/dts/utils/sum-costs.d.ts.map +1 -1
- package/dist/dts/utils/sum-tokens.d.ts +8 -8
- package/dist/dts/utils/sum-tokens.d.ts.map +1 -1
- package/dist/dts/utils/sum-usage.d.ts +59 -0
- package/dist/dts/utils/sum-usage.d.ts.map +1 -0
- package/dist/dts/utils/sum-usage.test.d.ts +2 -0
- package/dist/dts/utils/sum-usage.test.d.ts.map +1 -0
- package/dist/esm/components/chat-driver/chat-driver.js +6 -0
- package/dist/esm/components/settings-modal/settings-modal.styles.js +84 -2
- package/dist/esm/components/settings-modal/settings-modal.template.js +130 -32
- package/dist/esm/main/cost-session-banking.test.js +216 -0
- package/dist/esm/main/main.js +271 -57
- package/dist/esm/state/ai-assistant-slice.js +11 -8
- package/dist/esm/state/ai-assistant-slice.test.js +12 -5
- package/dist/esm/state/debug-event-log.js +2 -2
- package/dist/esm/state/persistence/session-persistence.integration.test.js +5 -1
- package/dist/esm/state/persistence/session-persister.js +2 -2
- package/dist/esm/state/persistence/session-persister.test.js +10 -1
- package/dist/esm/state/persistence/session-snapshot.js +6 -2
- package/dist/esm/state/persistence/session-snapshot.test.js +4 -1
- package/dist/esm/state/persistence/stateful-restore.e2e.test.js +10 -1
- package/dist/esm/utils/cost-session-history.js +63 -13
- package/dist/esm/utils/cost-session-history.test.js +132 -13
- package/dist/esm/utils/resolve-cost-history-config.js +2 -1
- package/dist/esm/utils/sum-costs.js +2 -13
- package/dist/esm/utils/sum-tokens.js +10 -27
- package/dist/esm/utils/sum-tokens.test.js +1 -5
- package/dist/esm/utils/sum-usage.js +123 -0
- package/dist/esm/utils/sum-usage.test.js +120 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +17 -17
- package/src/components/chat-driver/chat-driver.ts +6 -0
- package/src/components/settings-modal/settings-modal.styles.ts +84 -2
- package/src/components/settings-modal/settings-modal.template.ts +144 -33
- package/src/main/cost-session-banking.test.ts +270 -0
- package/src/main/main.ts +279 -56
- package/src/state/ai-assistant-slice.test.ts +12 -5
- package/src/state/ai-assistant-slice.ts +21 -13
- package/src/state/debug-event-log.ts +2 -2
- package/src/state/persistence/session-persistence-provider.ts +24 -0
- package/src/state/persistence/session-persistence.integration.test.ts +8 -1
- package/src/state/persistence/session-persister.test.ts +10 -1
- package/src/state/persistence/session-persister.ts +2 -2
- package/src/state/persistence/session-snapshot.test.ts +4 -1
- package/src/state/persistence/session-snapshot.ts +5 -1
- package/src/state/persistence/stateful-restore.e2e.test.ts +9 -1
- package/src/utils/cost-session-history.test.ts +146 -16
- package/src/utils/cost-session-history.ts +112 -21
- package/src/utils/resolve-cost-history-config.ts +7 -0
- package/src/utils/sum-costs.ts +2 -9
- package/src/utils/sum-tokens.test.ts +1 -11
- package/src/utils/sum-tokens.ts +10 -26
- package/src/utils/sum-usage.test.ts +140 -0
- package/src/utils/sum-usage.ts +130 -0
package/dist/ai-assistant.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { AgentPickerMode } from '@genesislcap/foundation-ai';
|
|
2
|
+
import type { AggregateUsage } from '@genesislcap/foundation-ai';
|
|
2
3
|
import { AIProviderRegistry } from '@genesislcap/foundation-ai';
|
|
3
4
|
import type { AIProviderRegistryStatusEntry } from '@genesislcap/foundation-ai';
|
|
4
5
|
import type { AIProviderType } from '@genesislcap/foundation-ai';
|
|
@@ -1698,21 +1699,58 @@ declare type ChatInteractionEventsMap = {
|
|
|
1698
1699
|
|
|
1699
1700
|
export { ChatToolChoice }
|
|
1700
1701
|
|
|
1701
|
-
/** A model used during a
|
|
1702
|
+
/** A model used during a recorded cost session. */
|
|
1702
1703
|
export declare interface CostSessionModelEntry {
|
|
1703
1704
|
model: string;
|
|
1704
1705
|
/** Vendor id from `getStatus()` (e.g. `'anthropic'`, `'gemini'`). */
|
|
1705
1706
|
provider?: AIProviderType;
|
|
1706
1707
|
}
|
|
1707
1708
|
|
|
1708
|
-
/** Persisted
|
|
1709
|
+
/** Persisted usage record for one project's assistant work. */
|
|
1709
1710
|
export declare interface CostSessionRecord {
|
|
1710
1711
|
id: string;
|
|
1712
|
+
/**
|
|
1713
|
+
* Stable identity of the work this row stands for — the assistant's session key
|
|
1714
|
+
* (a project id, for hosts that key sessions per project). The upsert key: a row
|
|
1715
|
+
* is one project's lifetime usage, so re-reporting the same key REPLACES the row
|
|
1716
|
+
* rather than adding another.
|
|
1717
|
+
*/
|
|
1718
|
+
projectKey: string;
|
|
1711
1719
|
title: string;
|
|
1712
|
-
/**
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1720
|
+
/**
|
|
1721
|
+
* ISO-8601 timestamp of the most recent usage recorded for this row.
|
|
1722
|
+
*
|
|
1723
|
+
* Named for what it is: the row is upserted as spend accrues, so this moves. It is
|
|
1724
|
+
* NOT an end time — a row has no terminal state, because a project can always be
|
|
1725
|
+
* worked on again.
|
|
1726
|
+
*/
|
|
1727
|
+
updatedAt: string;
|
|
1728
|
+
/**
|
|
1729
|
+
* Cost plus the four token buckets — the same shape the live session tile renders, so
|
|
1730
|
+
* a history row can show the identical breakdown instead of a lone merged total that
|
|
1731
|
+
* cannot explain a large token count at a small cost.
|
|
1732
|
+
*
|
|
1733
|
+
* This is the project's **lifetime** usage: `banked` plus whatever the live transcript
|
|
1734
|
+
* currently proves.
|
|
1735
|
+
*/
|
|
1736
|
+
usage: AggregateUsage;
|
|
1737
|
+
/**
|
|
1738
|
+
* Spend that predates the current transcript, and so cannot be re-derived from it.
|
|
1739
|
+
*
|
|
1740
|
+
* Without this a row could only ever report what the open transcript proves. That is
|
|
1741
|
+
* fine when the transcript is restored on load (it *is* the project's history), but
|
|
1742
|
+
* where it starts empty every refresh the row would be rewritten downwards on the next
|
|
1743
|
+
* turn and the project's earlier spend lost. Banking it keeps `usage` a lifetime figure
|
|
1744
|
+
* in both cases.
|
|
1745
|
+
*
|
|
1746
|
+
* Deliberately part of the row rather than element state: two assistant instances (a
|
|
1747
|
+
* docked bubble and a popped-out panel) share one session, and both write this row. A
|
|
1748
|
+
* value derived from the row keeps every write idempotent, whereas an element-held
|
|
1749
|
+
* accumulator would be applied once per instance.
|
|
1750
|
+
*
|
|
1751
|
+
* Optional: rows written before this existed have none, which reads as "nothing banked".
|
|
1752
|
+
*/
|
|
1753
|
+
banked?: AggregateUsage;
|
|
1716
1754
|
models: CostSessionModelEntry[];
|
|
1717
1755
|
}
|
|
1718
1756
|
|
|
@@ -1905,10 +1943,13 @@ export declare interface FallbackAgentConfig extends BaseAgentConfig {
|
|
|
1905
1943
|
*
|
|
1906
1944
|
* Popout/collapse coordination uses `agenticActivityBus` topics `chat-popout` and `chat-popin` — not DOM `CustomEvent`s on this element.
|
|
1907
1945
|
*
|
|
1908
|
-
* **Cost build history:** opt-in via `chatConfig.costHistory.enabled`. When enabled,
|
|
1909
|
-
*
|
|
1910
|
-
* element `id`, or `'default'`)
|
|
1911
|
-
*
|
|
1946
|
+
* **Cost build history:** opt-in via `chatConfig.costHistory.enabled`. When enabled, each
|
|
1947
|
+
* session's usage is kept as exactly ONE row per session key in `localStorage` (scoped by
|
|
1948
|
+
* `costHistory.scope`, the element `id`, or `'default'`), upserted as spend accrues. So a
|
|
1949
|
+
* row is a project's lifetime usage rather than a visit to it, and the Cost tab's total is
|
|
1950
|
+
* the sum of the rows alone — the live session is never added on top, because the active
|
|
1951
|
+
* project's own row already reflects it. Hosts may call
|
|
1952
|
+
* {@link FoundationAiAssistant.finalizeCostSession} to flush early; it is idempotent.
|
|
1912
1953
|
*
|
|
1913
1954
|
* @fires chat-header-mousedown - Fired when the user presses the chat header in `popout-mode="expand"` (for drag-to-popout hosts). Bubbles and composed. detail: `ChatHeaderMouseDownDetail`
|
|
1914
1955
|
* @fires session-cleared - Fired after the session is wiped (the lifecycle menu's Clear). The payload is captured before teardown so the host can clean up any external state the active agent was working on. Bubbles and composed. detail: `SessionClearedDetail`
|
|
@@ -2141,25 +2182,91 @@ export declare class FoundationAiAssistant extends GenesisElement {
|
|
|
2141
2182
|
/** Context window size for the active model, if known. */
|
|
2142
2183
|
get contextLimit(): number | undefined;
|
|
2143
2184
|
set contextLimit(value: number | undefined);
|
|
2144
|
-
/**
|
|
2185
|
+
/**
|
|
2186
|
+
* Cost plus the four token buckets for this session — derived from the transcript
|
|
2187
|
+
* on every change, so it includes sub-agent turns and spend banked by a compaction.
|
|
2188
|
+
*/
|
|
2189
|
+
get sessionUsage(): AggregateUsage;
|
|
2190
|
+
set sessionUsage(value: AggregateUsage);
|
|
2191
|
+
/**
|
|
2192
|
+
* Aggregated USD cost across every chat turn in this session.
|
|
2193
|
+
*
|
|
2194
|
+
* Read-only (unlike previously): it is a projection of `sessionUsage`, and a
|
|
2195
|
+
* host assigning it would have set a figure the next transcript change overwrites.
|
|
2196
|
+
*/
|
|
2145
2197
|
get sessionCostUsd(): number;
|
|
2146
|
-
|
|
2147
|
-
|
|
2198
|
+
/**
|
|
2199
|
+
* Lifetime usage across every project in the history scope — cost and all four token
|
|
2200
|
+
* buckets. What the Cost tab's lifetime section renders.
|
|
2201
|
+
*
|
|
2202
|
+
* Rows ONLY — deliberately not rows + live session. Each row holds a project's lifetime
|
|
2203
|
+
* usage and the active project's row is kept current on every transcript change, so
|
|
2204
|
+
* adding the live total would count this project's spend twice. That double-count is what
|
|
2205
|
+
* made the figure grow on every refresh: a page-hide banked a row while the restored
|
|
2206
|
+
* session still carried the same spend.
|
|
2207
|
+
*/
|
|
2208
|
+
get cumulativeUsage(): AggregateUsage;
|
|
2209
|
+
/** Lifetime USD across every project in the history scope. */
|
|
2148
2210
|
get cumulativeCostUsd(): number;
|
|
2211
|
+
/**
|
|
2212
|
+
* Whether the live session figures amount to this project's whole history, which is true
|
|
2213
|
+
* exactly when the transcript is restored on load. Drives the Cost tab's live labels
|
|
2214
|
+
* ("Project cost" vs "Session cost") so they state the scope rather than imply one.
|
|
2215
|
+
*
|
|
2216
|
+
* Note this is NOT what makes the *rows* lifetime — those bank their pre-transcript spend
|
|
2217
|
+
* (see `CostSessionRecord.banked`) and are lifetime either way.
|
|
2218
|
+
*/
|
|
2219
|
+
get liveUsageIsProjectLifetime(): boolean;
|
|
2149
2220
|
/** Resolved cost tab and build-history settings from `chatConfig.costHistory`. */
|
|
2150
2221
|
get resolvedCostHistory(): ResolvedCostHistoryConfig;
|
|
2151
|
-
/**
|
|
2222
|
+
/**
|
|
2223
|
+
* Total tokens across every chat turn in this session — all four buckets added up.
|
|
2224
|
+
* Read-only; the per-bucket split is on `sessionUsage`.
|
|
2225
|
+
*/
|
|
2152
2226
|
get sessionTokensConsumed(): number;
|
|
2153
|
-
set sessionTokensConsumed(value: number);
|
|
2154
2227
|
/** Label shown on the Cost tab for the in-flight session (app title or first user phrase). */
|
|
2155
2228
|
get liveCostSessionTitle(): string | undefined;
|
|
2156
2229
|
/**
|
|
2157
|
-
*
|
|
2158
|
-
*
|
|
2230
|
+
* Write this session's usage into persisted build history (`localStorage`) as a single
|
|
2231
|
+
* row keyed by the session key, replacing any existing row for that key.
|
|
2232
|
+
*
|
|
2233
|
+
* **Idempotent** — safe to call per turn, on tab close, and on a title change alike.
|
|
2234
|
+
* The name is historical: it does NOT end the session or reset the running totals.
|
|
2235
|
+
* Resetting them is what used to double-count spend, since the row was banked while the
|
|
2236
|
+
* persisted transcript still carried the same usage, so a reload restored it and the
|
|
2237
|
+
* Cost tab counted both.
|
|
2238
|
+
*
|
|
2239
|
+
* No-op unless `chatConfig.costHistory.enabled` is `true`, when the live session has no
|
|
2240
|
+
* usage yet, or when the session has no key to upsert against. Gating on the *live*
|
|
2241
|
+
* figures (not the banked total) means a reload that restores nothing rewrites nothing.
|
|
2159
2242
|
*/
|
|
2160
2243
|
finalizeCostSession(options?: {
|
|
2161
2244
|
title?: string;
|
|
2162
2245
|
}): void;
|
|
2246
|
+
/**
|
|
2247
|
+
* Spend for `projectKey` that predates the current transcript, resolved once per project
|
|
2248
|
+
* and memoised (`_bankedFor`) so it cannot drift mid-session as the row it came from is
|
|
2249
|
+
* rewritten by our own upserts.
|
|
2250
|
+
*
|
|
2251
|
+
* The two cases turn on whether the transcript is authoritative for this project:
|
|
2252
|
+
*
|
|
2253
|
+
* - **Transcript restored** (`persistenceActive`): it already accounts for everything after
|
|
2254
|
+
* the previous row's own banked figure, so carry that figure forward unchanged. Re-banking
|
|
2255
|
+
* `usage` here would add the restored history to itself on every reload.
|
|
2256
|
+
* - **Transcript starts empty**: nothing on screen accounts for the previous total, so the
|
|
2257
|
+
* whole of it becomes banked. Without this the next turn would rewrite the row downwards
|
|
2258
|
+
* to just that turn, losing the project's earlier spend.
|
|
2259
|
+
*/
|
|
2260
|
+
private bankedUsageFor;
|
|
2261
|
+
/**
|
|
2262
|
+
* Discard the banked figure — for when the spend it stands for has been deliberately
|
|
2263
|
+
* thrown away (the user clearing history, or deleting this project's row).
|
|
2264
|
+
*
|
|
2265
|
+
* NOT for reloads. A reload re-reads rows that already include our own upserts, so
|
|
2266
|
+
* re-deriving from them would fold the live session into its own baseline; only a genuine
|
|
2267
|
+
* project change should re-derive, which `bankedUsageFor`'s per-project memo handles.
|
|
2268
|
+
*/
|
|
2269
|
+
private resetBankedUsage;
|
|
2163
2270
|
showConfirmationDialog(confirmationMessage: string, modalParent: HTMLElement, onConfirm: () => void, onCancel?: () => void): void;
|
|
2164
2271
|
/** Remove one entry from persisted build history. */
|
|
2165
2272
|
removeCostSessionFromHistory(id: string): void;
|
|
@@ -2167,6 +2274,22 @@ export declare class FoundationAiAssistant extends GenesisElement {
|
|
|
2167
2274
|
clearCostSessionHistory(): void;
|
|
2168
2275
|
private getCostHistoryScope;
|
|
2169
2276
|
private reloadCostSessionHistory;
|
|
2277
|
+
/**
|
|
2278
|
+
* The persistence provider, but only when it implements BOTH cost-history methods.
|
|
2279
|
+
*
|
|
2280
|
+
* Both or neither: a provider that can save but not load would silently accumulate a
|
|
2281
|
+
* ledger nobody ever reads back, so a partial implementation is treated as absent and
|
|
2282
|
+
* falls back to `localStorage`.
|
|
2283
|
+
*/
|
|
2284
|
+
private costHistoryProvider;
|
|
2285
|
+
/**
|
|
2286
|
+
* Adopt `records` as the live list and write them through to whichever backend owns
|
|
2287
|
+
* cost history — the provider when the host implements it, else `localStorage`.
|
|
2288
|
+
*
|
|
2289
|
+
* Provider writes are fire-and-forget and best-effort: the ledger is accounting, not
|
|
2290
|
+
* conversation content, so a failed write is logged and never surfaced into the chat.
|
|
2291
|
+
*/
|
|
2292
|
+
private persistCostHistory;
|
|
2170
2293
|
/** Finalize the previous session when the host-provided title changes. */
|
|
2171
2294
|
private syncActiveCostSessionTitle;
|
|
2172
2295
|
/** Active model id resolved from the AIProvider, if exposed. */
|
|
@@ -2222,8 +2345,15 @@ export declare class FoundationAiAssistant extends GenesisElement {
|
|
|
2222
2345
|
private unsubAppSettingsProvider?;
|
|
2223
2346
|
/** Title tracked for the in-flight cost session (used when `header-title` changes). */
|
|
2224
2347
|
private _activeCostSessionTitle;
|
|
2225
|
-
/**
|
|
2226
|
-
private
|
|
2348
|
+
/** Memoised pre-transcript spend for `_bankedFor` — see `bankedUsageFor`. */
|
|
2349
|
+
private _bankedUsage;
|
|
2350
|
+
/** Project key `_bankedUsage` was resolved for; `undefined` means "not yet resolved". */
|
|
2351
|
+
private _bankedFor;
|
|
2352
|
+
/**
|
|
2353
|
+
* Whether the row list reflects storage yet. Gates cost writes so a project's banked
|
|
2354
|
+
* spend is never derived from a ledger that simply hasn't arrived.
|
|
2355
|
+
*/
|
|
2356
|
+
private _costHistoryLoaded;
|
|
2227
2357
|
private _handlePageHide;
|
|
2228
2358
|
private haloStartPublished;
|
|
2229
2359
|
/** Fingerprint of the agents array used to build the current driver. Used by agentsChanged to skip spurious rebuilds. */
|
|
@@ -2612,6 +2742,14 @@ export declare class FoundationAiAssistant extends GenesisElement {
|
|
|
2612
2742
|
get settingsCostHistorySectionVisible(): boolean;
|
|
2613
2743
|
/** Whether the built-in Cost tab summary block has anything to show. */
|
|
2614
2744
|
get settingsCostSummaryVisible(): boolean;
|
|
2745
|
+
/**
|
|
2746
|
+
* Whether the lifetime-usage section has anything to say.
|
|
2747
|
+
*
|
|
2748
|
+
* Requires build history: with it off there are no rows, so `cumulativeUsage` falls back to
|
|
2749
|
+
* the live session and the section would restate the block directly below it under a
|
|
2750
|
+
* "lifetime" heading it hasn't earned.
|
|
2751
|
+
*/
|
|
2752
|
+
get settingsCostLifetimeVisible(): boolean;
|
|
2615
2753
|
/** Whether the Cost tab should appear in the settings modal. */
|
|
2616
2754
|
get settingsCostTabVisible(): boolean;
|
|
2617
2755
|
/** Whether session token usage should render on the Cost tab. */
|
|
@@ -3268,6 +3406,12 @@ declare interface ResolvedCostHistoryConfig {
|
|
|
3268
3406
|
showTokens: boolean;
|
|
3269
3407
|
/** Live-session badge label on the Cost tab summary. */
|
|
3270
3408
|
badgeLabel: string;
|
|
3409
|
+
/**
|
|
3410
|
+
* Host-supplied disclosure line for the Cost tab. Stays optional — unlike
|
|
3411
|
+
* `badgeLabel` there is no default, because only the host knows what its `scope`
|
|
3412
|
+
* spans, and the library must not assert it.
|
|
3413
|
+
*/
|
|
3414
|
+
summaryHint?: string;
|
|
3271
3415
|
}
|
|
3272
3416
|
|
|
3273
3417
|
/**
|
|
@@ -3385,6 +3529,28 @@ export declare interface SessionPersistenceProvider {
|
|
|
3385
3529
|
loadPreferences?(sessionKey: string): Promise<SessionPreferences | undefined>;
|
|
3386
3530
|
/** Persist the user's UI preferences for `sessionKey` (kept across `clear`). */
|
|
3387
3531
|
savePreferences?(sessionKey: string, preferences: SessionPreferences): Promise<void>;
|
|
3532
|
+
/**
|
|
3533
|
+
* Scoped cost/build history — the per-project usage rows behind the Cost tab.
|
|
3534
|
+
*
|
|
3535
|
+
* ‼️ **Keyed by SCOPE, not `sessionKey`**, unlike every other method on this interface.
|
|
3536
|
+
* A row holds one project's lifetime usage and the list spans projects, so keying it
|
|
3537
|
+
* per session would defeat the model. The scope comes from
|
|
3538
|
+
* `chatConfig.costHistory.scope`, falling back to the element `id`, then `'default'`.
|
|
3539
|
+
*
|
|
3540
|
+
* Optional: when a provider omits these, the assistant falls back to `localStorage`, so
|
|
3541
|
+
* hosts that don't implement them are unaffected. Implementing them moves the ledger to
|
|
3542
|
+
* the host's own backend, which is what stops it being stranded in one browser.
|
|
3543
|
+
*
|
|
3544
|
+
* Read/written **independently of the `enabled` toggle** (as with preferences and
|
|
3545
|
+
* diagnostics): usage accounting is not conversation content, so a user turning off
|
|
3546
|
+
* chat retention should not lose their cost ledger. Best-effort — a failed write must
|
|
3547
|
+
* never affect the chat.
|
|
3548
|
+
*
|
|
3549
|
+
* `undefined` from `loadCostHistory` means "nothing stored", the same as `[]`.
|
|
3550
|
+
*/
|
|
3551
|
+
loadCostHistory?(scope: string): Promise<CostSessionRecord[] | undefined>;
|
|
3552
|
+
/** Replace the stored row list for `scope`. See {@link SessionPersistenceProvider.loadCostHistory}. */
|
|
3553
|
+
saveCostHistory?(scope: string, records: CostSessionRecord[]): Promise<void>;
|
|
3388
3554
|
/**
|
|
3389
3555
|
* Server-saved diagnostics (GENC-1351 §5.8) — a **forward-only append** stream
|
|
3390
3556
|
* that lets the downloadable debug log span the whole session lifetime, not just
|
package/dist/chat-driver.cjs
CHANGED
|
@@ -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
|
|
2135
|
-
|
|
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 = (
|
|
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: (
|
|
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 = (
|
|
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,
|
|
@@ -2997,6 +3024,46 @@ The above summarizes earlier parts of this conversation that were compacted. Con
|
|
|
2997
3024
|
});
|
|
2998
3025
|
}
|
|
2999
3026
|
|
|
3027
|
+
// src/utils/sum-usage.ts
|
|
3028
|
+
function emptyUsage() {
|
|
3029
|
+
return {
|
|
3030
|
+
costUsd: 0,
|
|
3031
|
+
uncachedInputTokens: 0,
|
|
3032
|
+
cacheReadTokens: 0,
|
|
3033
|
+
cacheWriteTokens: 0,
|
|
3034
|
+
outputTokens: 0
|
|
3035
|
+
};
|
|
3036
|
+
}
|
|
3037
|
+
function sumUsage(messages) {
|
|
3038
|
+
const total = emptyUsage();
|
|
3039
|
+
accumulate(messages, total);
|
|
3040
|
+
return total;
|
|
3041
|
+
}
|
|
3042
|
+
function addInto(into, from) {
|
|
3043
|
+
into.costUsd += from.costUsd;
|
|
3044
|
+
into.uncachedInputTokens += from.uncachedInputTokens;
|
|
3045
|
+
into.cacheReadTokens += from.cacheReadTokens;
|
|
3046
|
+
into.cacheWriteTokens += from.cacheWriteTokens;
|
|
3047
|
+
into.outputTokens += from.outputTokens;
|
|
3048
|
+
}
|
|
3049
|
+
function accumulate(messages, into) {
|
|
3050
|
+
for (const m of messages) {
|
|
3051
|
+
if (m.cost != null) into.costUsd += m.cost;
|
|
3052
|
+
if (m.externalCostUsd != null) into.costUsd += m.externalCostUsd;
|
|
3053
|
+
const cacheRead = m.cacheReadTokens ?? 0;
|
|
3054
|
+
const cacheWrite = m.cacheWriteTokens ?? 0;
|
|
3055
|
+
into.cacheReadTokens += cacheRead;
|
|
3056
|
+
into.cacheWriteTokens += cacheWrite;
|
|
3057
|
+
into.uncachedInputTokens += Math.max(0, (m.inputTokens ?? 0) - cacheRead - cacheWrite);
|
|
3058
|
+
into.outputTokens += m.outputTokens ?? 0;
|
|
3059
|
+
const rolled = m.compaction?.rolledUpUsage;
|
|
3060
|
+
if (rolled) addInto(into, rolled);
|
|
3061
|
+
for (const tc of m.toolCalls ?? []) {
|
|
3062
|
+
if (tc.subAgentTrace) accumulate(tc.subAgentTrace, into);
|
|
3063
|
+
}
|
|
3064
|
+
}
|
|
3065
|
+
}
|
|
3066
|
+
|
|
3000
3067
|
// src/utils/tool-fold.ts
|
|
3001
3068
|
var TOOL_FOLD_SYMBOL = Symbol("toolFold");
|
|
3002
3069
|
|
|
@@ -3744,7 +3811,12 @@ Output format (strict):
|
|
|
3744
3811
|
coveredThroughTimestamp: toCompact[toCompact.length - 1]?.timestamp,
|
|
3745
3812
|
tokensBefore,
|
|
3746
3813
|
model: status?.model,
|
|
3747
|
-
createdAt
|
|
3814
|
+
createdAt,
|
|
3815
|
+
// Bank what the replaced turns spent. `toCompact` is dropped from history
|
|
3816
|
+
// below, taking its per-message cost/token fields with it — so a host total
|
|
3817
|
+
// re-derived from the transcript would otherwise fall by exactly this much
|
|
3818
|
+
// on every compaction. Summarising a turn does not refund what it cost.
|
|
3819
|
+
rolledUpUsage: sumUsage(toCompact)
|
|
3748
3820
|
}
|
|
3749
3821
|
};
|
|
3750
3822
|
this.loadHistory([summaryMessage, ...tail]);
|