@genesislcap/ai-assistant 15.23.3 → 15.24.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.
- package/dist/ai-assistant.api.json +419 -50
- package/dist/ai-assistant.d.ts +217 -683
- package/dist/chat-driver.cjs.map +1 -1
- package/dist/chat-driver.mjs.map +1 -1
- package/dist/custom-elements.json +310 -609
- package/dist/dts/components/orchestrating-driver/orchestrating-driver.d.ts +16 -0
- package/dist/dts/components/orchestrating-driver/orchestrating-driver.d.ts.map +1 -1
- package/dist/dts/components/settings-modal/index.d.ts +1 -0
- package/dist/dts/components/settings-modal/index.d.ts.map +1 -1
- package/dist/dts/components/settings-modal/settings-modal.d.ts +215 -0
- package/dist/dts/components/settings-modal/settings-modal.d.ts.map +1 -0
- package/dist/dts/components/settings-modal/settings-modal.template.d.ts.map +1 -1
- package/dist/dts/main/attachment-controller.d.ts +66 -0
- package/dist/dts/main/attachment-controller.d.ts.map +1 -0
- package/dist/dts/main/budget-controller.d.ts +368 -0
- package/dist/dts/main/budget-controller.d.ts.map +1 -0
- package/dist/dts/main/composer-menu-controller.d.ts +113 -0
- package/dist/dts/main/composer-menu-controller.d.ts.map +1 -0
- package/dist/dts/main/composer-resize-controller.d.ts +80 -0
- package/dist/dts/main/composer-resize-controller.d.ts.map +1 -0
- package/dist/dts/main/context-gate-controller.d.ts +197 -0
- package/dist/dts/main/context-gate-controller.d.ts.map +1 -0
- package/dist/dts/main/cost-ledger-controller.d.ts +228 -0
- package/dist/dts/main/cost-ledger-controller.d.ts.map +1 -0
- package/dist/dts/main/main.d.ts +201 -722
- package/dist/dts/main/main.d.ts.map +1 -1
- package/dist/dts/main/main.types.d.ts +25 -0
- package/dist/dts/main/main.types.d.ts.map +1 -1
- package/dist/dts/main/meta-snapshot.d.ts +50 -0
- package/dist/dts/main/meta-snapshot.d.ts.map +1 -0
- package/dist/dts/state/persistence/session-persister.d.ts.map +1 -1
- package/dist/esm/components/orchestrating-driver/orchestrating-driver.js +18 -0
- package/dist/esm/components/settings-modal/index.js +1 -0
- package/dist/esm/components/settings-modal/settings-modal.js +436 -0
- package/dist/esm/components/settings-modal/settings-modal.template.js +15 -4
- package/dist/esm/main/attachment-controller.js +254 -0
- package/dist/esm/main/budget-controller.js +745 -0
- package/dist/esm/main/composer-menu-controller.js +234 -0
- package/dist/esm/main/composer-resize-controller.js +119 -0
- package/dist/esm/main/context-gate-controller.js +289 -0
- package/dist/esm/main/cost-ledger-controller.js +498 -0
- package/dist/esm/main/main.js +501 -2268
- package/dist/esm/main/meta-snapshot.js +108 -0
- package/dist/esm/state/persistence/session-persister.js +5 -4
- package/docs/migration-GENC-1580.md +209 -0
- package/package.json +16 -16
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import type { ChatConfig, ChatMessage } from '@genesislcap/foundation-ai';
|
|
2
|
+
import type { ContextReserveResolution } from '../components/ai-driver/ai-driver';
|
|
3
|
+
import type { ContextGuardPolicy } from '../components/chat-driver/chat-driver';
|
|
4
|
+
import type { CompactionProjection } from '../utils/context-tokens';
|
|
5
|
+
import type { CompactionPlanOptions } from '../utils/history-transform';
|
|
6
|
+
import { type ResolvedContextBudget } from '../utils/resolve-context-budget';
|
|
7
|
+
/** What the controller needs from its host, as callbacks so it holds no element reference. */
|
|
8
|
+
export interface ContextGateDeps {
|
|
9
|
+
chatConfig: () => ChatConfig;
|
|
10
|
+
/**
|
|
11
|
+
* The Redux mirror, not the driver's history. Safe rather than sloppy: the two
|
|
12
|
+
* lists only ever drift at the tail, and nothing here inspects the tail.
|
|
13
|
+
*/
|
|
14
|
+
messages: () => readonly ChatMessage[];
|
|
15
|
+
/** The live gauge figure — what the context indicator shows. */
|
|
16
|
+
contextTokens: () => number | undefined;
|
|
17
|
+
/**
|
|
18
|
+
* The figure the GATE is judged against: `contextTokens` as of the last moment
|
|
19
|
+
* no turn was running (GENC-1582). Distinct from the live gauge on purpose — see
|
|
20
|
+
* the element's `_gateContextTokens`. Falls back to the live figure when unset.
|
|
21
|
+
*/
|
|
22
|
+
gateContextTokens: () => number | undefined;
|
|
23
|
+
contextLimit: () => number | undefined;
|
|
24
|
+
/**
|
|
25
|
+
* Headroom the next turn needs and which agent set it, resolved by the driver
|
|
26
|
+
* against live routing state (GENC-1582). A callback because reaching it means
|
|
27
|
+
* going through the driver, which the element owns — this controller never
|
|
28
|
+
* touches a driver (§11.2 constraint 2).
|
|
29
|
+
*/
|
|
30
|
+
contextReserve: () => ContextReserveResolution | undefined;
|
|
31
|
+
/**
|
|
32
|
+
* The preamble the driver actually measured on the last request, when it builds
|
|
33
|
+
* requests at all. Replaces the estimate this controller used to compute (and
|
|
34
|
+
* memoize) from the transcript: a measurement beats a walk, and it is why the
|
|
35
|
+
* `systemOverhead` input to `resolveContextBudget` is gone.
|
|
36
|
+
*/
|
|
37
|
+
preambleTokens: () => number | undefined;
|
|
38
|
+
/** The driver's plan for the exact history `compact()` acts on. */
|
|
39
|
+
compactionPlanFor: (options: CompactionPlanOptions) => CompactionProjection | null;
|
|
40
|
+
/** A budget wall outranks the gate, so the notice getters have to see it. */
|
|
41
|
+
bannerVisible: () => boolean;
|
|
42
|
+
effectiveBlockedReason: () => string;
|
|
43
|
+
/** Non-null while a guided flow owns the turn — a flow is warned, never blocked. */
|
|
44
|
+
flowOwnerAgentName: () => string | null;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* The context gate: how close the conversation is to the model's window, what
|
|
48
|
+
* compacting would reclaim, and the copy that states both (GENC-1567).
|
|
49
|
+
*
|
|
50
|
+
* Owns **no state at all** — every member derives from the callbacks above. That
|
|
51
|
+
* is what makes it the safest of these extractions: there is no field to forget
|
|
52
|
+
* to mark `@observable`, so the observability trap in `docs/code-health.md` §11.4
|
|
53
|
+
* cannot bite here.
|
|
54
|
+
*
|
|
55
|
+
* @internal
|
|
56
|
+
*/
|
|
57
|
+
export declare class ContextGateController {
|
|
58
|
+
private readonly deps;
|
|
59
|
+
constructor(deps: ContextGateDeps);
|
|
60
|
+
/** Window, warn/block thresholds and compaction sizing for the active model. */
|
|
61
|
+
get budget(): ResolvedContextBudget;
|
|
62
|
+
/**
|
|
63
|
+
* What compacting right now would reclaim, or `null` when it would not be worth
|
|
64
|
+
* it. Sized against {@link ContextGateController.budget}, so the plan aims at
|
|
65
|
+
* this model's window rather than a fixed default.
|
|
66
|
+
*
|
|
67
|
+
* Reads `messages` to stay reactive as the conversation grows: the driver's
|
|
68
|
+
* history is not a tracked observable, so every consumer keys its re-evaluation
|
|
69
|
+
* off the Redux mirror.
|
|
70
|
+
*
|
|
71
|
+
* Deliberately NOT memoized on `messages` identity: it runs against the driver's
|
|
72
|
+
* history, which can move without the mirror moving (a mid-turn `condenseWhen`
|
|
73
|
+
* rewrite), so an identity key would risk serving a plan for a transcript the
|
|
74
|
+
* driver no longer has.
|
|
75
|
+
*/
|
|
76
|
+
get plan(): CompactionProjection | null;
|
|
77
|
+
/**
|
|
78
|
+
* Guard policy pushed to the driver, or `undefined` when the host has switched
|
|
79
|
+
* the gate off — so `enabled: false` really does mean no local blocking of any
|
|
80
|
+
* kind, UI or mid-loop.
|
|
81
|
+
*
|
|
82
|
+
* A MARGIN, not a threshold: the driver resolves the window of the provider each
|
|
83
|
+
* call actually goes to. The fallback window rides along because the driver
|
|
84
|
+
* cannot learn it any other way — without it, a host that knows its window while
|
|
85
|
+
* its provider does not report one had `fallbackContextLimit` protecting the
|
|
86
|
+
* composer and nothing else.
|
|
87
|
+
*/
|
|
88
|
+
get guardPolicy(): ContextGuardPolicy | undefined;
|
|
89
|
+
/**
|
|
90
|
+
* How close this conversation is to the model's context window.
|
|
91
|
+
*
|
|
92
|
+
* **Derived, never latched** — the opposite of the budget wall. A wall stays set
|
|
93
|
+
* because nothing the user does fixes it; this clears itself the moment the
|
|
94
|
+
* conversation shrinks, which is precisely what the user is being asked to do.
|
|
95
|
+
*
|
|
96
|
+
* **A flow in progress is never blocked, only warned.** Machine state lives
|
|
97
|
+
* outside the transcript, so a stateful flow is not desynced by compaction — but
|
|
98
|
+
* the summary can still drop detail the flow's own prompt leans on, and blocking
|
|
99
|
+
* mid-journey would strand the user between a flow they cannot finish and a
|
|
100
|
+
* compaction that is a poor idea right there.
|
|
101
|
+
*/
|
|
102
|
+
get gate(): 'ok' | 'warn' | 'blocked';
|
|
103
|
+
/**
|
|
104
|
+
* WHY the composer is blocked, when it is (GENC-1582). `undefined` unless
|
|
105
|
+
* {@link ContextGateController.gate} is `'blocked'`.
|
|
106
|
+
*
|
|
107
|
+
* - `'transcript'` — the conversation has grown into the reserve.
|
|
108
|
+
* - `'model'` — the reserve meets or exceeds the whole window, so `blockAt` is 0
|
|
109
|
+
* and EVERY message is refused, including the first of an empty chat.
|
|
110
|
+
*
|
|
111
|
+
* Keyed off `blockAt === 0`, deliberately NOT off whether a compaction plan
|
|
112
|
+
* exists. A null plan has two unrelated causes — nothing left worth summarizing,
|
|
113
|
+
* versus a window that cannot host the agent at all — and only the second is a
|
|
114
|
+
* statement about the configuration rather than the transcript.
|
|
115
|
+
*
|
|
116
|
+
* **This drives behaviour, not copy.** It reads as though it should also pick the
|
|
117
|
+
* wording, and GENC-1582 had it do so: the `'model'` branch told the user to
|
|
118
|
+
* switch to a model with a larger context window, or pick a different agent.
|
|
119
|
+
* Removed, because the assistant offers no way to do either — the model follows
|
|
120
|
+
* from the host's provider registry and each agent's `provider` field, and the
|
|
121
|
+
* only user-facing lever is the agent picker, which is triple-gated
|
|
122
|
+
* (`agentPicker` enabled, two or more agents, at least one with
|
|
123
|
+
* `manualSelection.enabled`) and commonly absent. Copy that names a control the
|
|
124
|
+
* user does not have is worse than copy that names a remedy which merely turns
|
|
125
|
+
* out to be insufficient, and `contextGateReason` is not host-overridable, so
|
|
126
|
+
* nobody downstream could correct it either. Both causes now read the GENC-1567
|
|
127
|
+
* sentence.
|
|
128
|
+
*
|
|
129
|
+
* What it still decides is `sessionMenuForcedByContext`: the transcript cause
|
|
130
|
+
* forces the session menu open so a user told to compact can reach Compact, and
|
|
131
|
+
* the model cause deliberately does not, because there Compact and Clear are
|
|
132
|
+
* both futile and a menu of dead actions helps nobody.
|
|
133
|
+
*
|
|
134
|
+
* The block itself is correct in both cases and must stay: an agent that
|
|
135
|
+
* measurably cannot complete a turn in this window would otherwise start one and
|
|
136
|
+
* die mid-loop on a provider context error, with partial writes and no clean
|
|
137
|
+
* exit. Refusing up front is the better failure.
|
|
138
|
+
*/
|
|
139
|
+
get blockCause(): 'transcript' | 'model' | undefined;
|
|
140
|
+
/**
|
|
141
|
+
* What the composer notice says about the gate.
|
|
142
|
+
*
|
|
143
|
+
* The copy is a function of whether compaction can actually clear it, which is
|
|
144
|
+
* why it reads {@link ContextGateController.plan} rather than assuming. Telling
|
|
145
|
+
* someone to compact when the projection says it would not free enough — a tail
|
|
146
|
+
* that is one indivisible tool result, say — sends them into a summarizer call
|
|
147
|
+
* that leaves them exactly where they were.
|
|
148
|
+
*/
|
|
149
|
+
get gateReason(): string;
|
|
150
|
+
/**
|
|
151
|
+
* Short label the session-menu pill wears while the gate is active — the primary
|
|
152
|
+
* visible signal, in place of a banner.
|
|
153
|
+
*
|
|
154
|
+
* It states the CONDITION only; the remedy lives in the placeholder once the
|
|
155
|
+
* composer is actually blocked, which is the moment the user needs to be told
|
|
156
|
+
* what to do rather than merely warned.
|
|
157
|
+
*/
|
|
158
|
+
get gateLabel(): string;
|
|
159
|
+
/** Tooltip and hint for the Compact row — why it is or is not available. */
|
|
160
|
+
get compactHint(): string;
|
|
161
|
+
/**
|
|
162
|
+
* Whether the menu's "Compact" action would do anything. Delegates to the
|
|
163
|
+
* driver's plan so the gate uses the exact same history `compact()` acts on,
|
|
164
|
+
* judged against the same budget — one source of truth for both whether a cut is
|
|
165
|
+
* legal and whether it reclaims enough.
|
|
166
|
+
*/
|
|
167
|
+
get compactable(): boolean;
|
|
168
|
+
/**
|
|
169
|
+
* Whether the composer notice has anything to show — a budget wall or the gate.
|
|
170
|
+
* One region for both, so the live region stays single and registered.
|
|
171
|
+
*/
|
|
172
|
+
get noticeVisible(): boolean;
|
|
173
|
+
/**
|
|
174
|
+
* Text for that region. A budget wall outranks the gate: it is the condition the
|
|
175
|
+
* user cannot clear, so it must not be hidden behind advice about compacting that
|
|
176
|
+
* would not restore sending anyway.
|
|
177
|
+
*/
|
|
178
|
+
get noticeText(): string;
|
|
179
|
+
/**
|
|
180
|
+
* Whether the notice region should be announced but not drawn.
|
|
181
|
+
*
|
|
182
|
+
* The gate shows itself visually through the pill's label, the composer outline
|
|
183
|
+
* and the placeholder — a strip of prose on top would be a fourth statement of
|
|
184
|
+
* the same thing. But dropping the region outright would be an accessibility
|
|
185
|
+
* regression: a `role="status"` region announces MUTATIONS to a region already
|
|
186
|
+
* being observed, and it is the only announcement available here, because the
|
|
187
|
+
* composer flips to `disabled` at the same moment and a disabled control is out
|
|
188
|
+
* of the tab order — so neither the placeholder nor an `aria-label` on it is ever
|
|
189
|
+
* voiced.
|
|
190
|
+
*
|
|
191
|
+
* So the region stays mounted, keeps its text and keeps announcing; only the
|
|
192
|
+
* visual treatment is withheld. A budget wall still draws its banner, because
|
|
193
|
+
* that condition has no other visible expression.
|
|
194
|
+
*/
|
|
195
|
+
get noticeSrOnly(): boolean;
|
|
196
|
+
}
|
|
197
|
+
//# sourceMappingURL=context-gate-controller.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context-gate-controller.d.ts","sourceRoot":"","sources":["../../../src/main/context-gate-controller.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AAE1E,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,mCAAmC,CAAC;AAClF,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uCAAuC,CAAC;AAChF,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,yBAAyB,CAAC;AACpE,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AACxE,OAAO,EAGL,KAAK,qBAAqB,EAC3B,MAAM,iCAAiC,CAAC;AAEzC,8FAA8F;AAC9F,MAAM,WAAW,eAAe;IAC9B,UAAU,EAAE,MAAM,UAAU,CAAC;IAC7B;;;OAGG;IACH,QAAQ,EAAE,MAAM,SAAS,WAAW,EAAE,CAAC;IACvC,gEAAgE;IAChE,aAAa,EAAE,MAAM,MAAM,GAAG,SAAS,CAAC;IACxC;;;;OAIG;IACH,iBAAiB,EAAE,MAAM,MAAM,GAAG,SAAS,CAAC;IAC5C,YAAY,EAAE,MAAM,MAAM,GAAG,SAAS,CAAC;IACvC;;;;;OAKG;IACH,cAAc,EAAE,MAAM,wBAAwB,GAAG,SAAS,CAAC;IAC3D;;;;;OAKG;IACH,cAAc,EAAE,MAAM,MAAM,GAAG,SAAS,CAAC;IACzC,mEAAmE;IACnE,iBAAiB,EAAE,CAAC,OAAO,EAAE,qBAAqB,KAAK,oBAAoB,GAAG,IAAI,CAAC;IACnF,6EAA6E;IAC7E,aAAa,EAAE,MAAM,OAAO,CAAC;IAC7B,sBAAsB,EAAE,MAAM,MAAM,CAAC;IACrC,oFAAoF;IACpF,kBAAkB,EAAE,MAAM,MAAM,GAAG,IAAI,CAAC;CACzC;AAED;;;;;;;;;;GAUG;AACH,qBAAa,qBAAqB;IACpB,OAAO,CAAC,QAAQ,CAAC,IAAI;gBAAJ,IAAI,EAAE,eAAe;IAElD,gFAAgF;IAChF,IACI,MAAM,IAAI,qBAAqB,CAclC;IAED;;;;;;;;;;;;;OAaG;IACH,IACI,IAAI,IAAI,oBAAoB,GAAG,IAAI,CAQtC;IAED;;;;;;;;;;OAUG;IACH,IACI,WAAW,IAAI,kBAAkB,GAAG,SAAS,CAMhD;IAED;;;;;;;;;;;;OAYG;IACH,IACI,IAAI,IAAI,IAAI,GAAG,MAAM,GAAG,SAAS,CAWpC;IAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAmCG;IACH,IACI,UAAU,IAAI,YAAY,GAAG,OAAO,GAAG,SAAS,CAGnD;IAED;;;;;;;;OAQG;IACH,IACI,UAAU,IAAI,MAAM,CAyBvB;IAED;;;;;;;OAOG;IACH,IACI,SAAS,IAAI,MAAM,CAStB;IAED,4EAA4E;IAC5E,IACI,WAAW,IAAI,MAAM,CAKxB;IAED;;;;;OAKG;IACH,IACI,WAAW,IAAI,OAAO,CAEzB;IAED;;;OAGG;IACH,IACI,aAAa,IAAI,OAAO,CAE3B;IAED;;;;OAIG;IACH,IACI,UAAU,IAAI,MAAM,CAGvB;IAED;;;;;;;;;;;;;;;OAeG;IACH,IACI,YAAY,IAAI,OAAO,CAE1B;CACF"}
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
import type { AggregateUsage, AIProviderRegistryStatusEntry, ChatConfig, ChatMessage } from '@genesislcap/foundation-ai';
|
|
2
|
+
import type { SessionPersistenceProvider } from '../state/persistence/session-persistence-provider';
|
|
3
|
+
import { type CostSessionModelEntry, type CostSessionRecord } from '../utils/cost-session-history';
|
|
4
|
+
import { type ResolvedCostHistoryConfig } from '../utils/resolve-cost-history-config';
|
|
5
|
+
/** What the controller needs from its host, as callbacks so it holds no element reference. */
|
|
6
|
+
export interface CostLedgerDeps {
|
|
7
|
+
chatConfig: () => ChatConfig;
|
|
8
|
+
/** The transcript, for title derivation and the per-row model list. */
|
|
9
|
+
messages: () => readonly ChatMessage[];
|
|
10
|
+
/** Live session usage — the figure every row is a projection of. */
|
|
11
|
+
sessionUsage: () => AggregateUsage;
|
|
12
|
+
providerStatuses: () => AIProviderRegistryStatusEntry[];
|
|
13
|
+
/**
|
|
14
|
+
* `persistence.enabled && persistence.provider` — whether the transcript on screen
|
|
15
|
+
* speaks for this project's history. The whole banking split turns on it; see
|
|
16
|
+
* {@link CostLedgerController.bankedUsageFor}.
|
|
17
|
+
*/
|
|
18
|
+
persistenceActive: () => boolean;
|
|
19
|
+
/** The *wired* session key, which can lag the attributes during a deferred switch. */
|
|
20
|
+
stateKey: () => string | undefined;
|
|
21
|
+
headerTitle: () => string | undefined;
|
|
22
|
+
/** The host's persistence provider, whose cost-history methods are optional. */
|
|
23
|
+
provider: () => SessionPersistenceProvider | undefined;
|
|
24
|
+
/** The element `id`, the middle fallback for the history scope. */
|
|
25
|
+
elementId: () => string;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* The cost ledger: this session's usage, the persisted per-project build history it
|
|
29
|
+
* upserts into, and the pre-transcript ("banked") spend that makes a row report a
|
|
30
|
+
* project's LIFETIME total rather than whatever is currently on screen (GENC-1475).
|
|
31
|
+
*
|
|
32
|
+
* The invariant the whole file protects: **a row moves forward, never backwards.**
|
|
33
|
+
* Every figure is derived from state rather than incremented, so the several element
|
|
34
|
+
* instances one session sees — layout re-renders, popout, docking — write the same
|
|
35
|
+
* row instead of each adding a contribution. `cost-session-banking.test.ts` pins the
|
|
36
|
+
* cases where earlier attempts got this wrong in both directions.
|
|
37
|
+
*
|
|
38
|
+
* @internal
|
|
39
|
+
*/
|
|
40
|
+
export declare class CostLedgerController {
|
|
41
|
+
private readonly deps;
|
|
42
|
+
/**
|
|
43
|
+
* Persisted rows, newest write wins. Observable because the Usage tab renders them
|
|
44
|
+
* (through the host's `costSessionHistory` and `sortedCostSessionHistory`), and
|
|
45
|
+
* because the derived lifetime totals below have to re-run when a row lands.
|
|
46
|
+
*/
|
|
47
|
+
history: CostSessionRecord[];
|
|
48
|
+
/**
|
|
49
|
+
* Title tracked for the in-flight session, and the session key it accrued under so
|
|
50
|
+
* a title change can tell a rename (same key) from a project switch (key already
|
|
51
|
+
* flipped) — see {@link CostLedgerController.syncActiveTitle}.
|
|
52
|
+
*
|
|
53
|
+
* Not observable: read only by the title getter's own fallback chain, which already
|
|
54
|
+
* depends on `chatConfig` and `messages`, and written only from lifecycle hooks.
|
|
55
|
+
*/
|
|
56
|
+
private activeTitle;
|
|
57
|
+
private activeKey?;
|
|
58
|
+
/**
|
|
59
|
+
* What the last read of the backing store told us, and the two gates every write
|
|
60
|
+
* consults. Neither is observable — nothing renders them.
|
|
61
|
+
*
|
|
62
|
+
* `loaded`: whether the row list reflects storage yet. Gates writes so a project's
|
|
63
|
+
* banked spend is never derived from a ledger that simply hasn't arrived.
|
|
64
|
+
*
|
|
65
|
+
* `unreadable`: set when a provider read failed, latching writes off for this
|
|
66
|
+
* page-load. `saveCostHistory` replaces the whole stored list, so writing anything
|
|
67
|
+
* derived from the empty list we fell back to would destroy rows we never read.
|
|
68
|
+
*
|
|
69
|
+
* Private, though `cost-session-banking.test.ts` drives both to model a provider
|
|
70
|
+
* read that is in flight or has failed. That works because the test reaches them
|
|
71
|
+
* through an `as unknown as { costLedger: … }` structural cast, which erases the
|
|
72
|
+
* class type — so exposing them bought nothing and only put a mutable latch on the
|
|
73
|
+
* surface of an `@internal` controller (PR #2503 review).
|
|
74
|
+
*/
|
|
75
|
+
private loaded;
|
|
76
|
+
private unreadable;
|
|
77
|
+
/** Pending (adopted but unwritten) ledger changes — see {@link CostLedgerController.persist}. */
|
|
78
|
+
private dirty;
|
|
79
|
+
private flushTimer?;
|
|
80
|
+
constructor(deps: CostLedgerDeps);
|
|
81
|
+
/** Resolved cost tab and build-history settings from `chatConfig.costHistory`. */
|
|
82
|
+
get config(): ResolvedCostHistoryConfig;
|
|
83
|
+
/**
|
|
84
|
+
* Distinct models the registry currently resolves to — the live session's model chips.
|
|
85
|
+
*
|
|
86
|
+
* `providerStatuses` carries one entry per registry SLOT, so a vendor configured across several
|
|
87
|
+
* tiers that happen to resolve to the same model reports it once per tier, and the chips
|
|
88
|
+
* rendered it once per tier too. Which tier a model came from is not what these chips are for.
|
|
89
|
+
*
|
|
90
|
+
* Deduped on the label actually displayed, not on `status.model`: a slot whose provider reports
|
|
91
|
+
* no model falls back to the slot's own name, and those are distinct per slot, so keying on the
|
|
92
|
+
* label keeps them from collapsing into one another.
|
|
93
|
+
*/
|
|
94
|
+
get uniqueProviderModels(): CostSessionModelEntry[];
|
|
95
|
+
/**
|
|
96
|
+
* Build history for display: most recently worked on first.
|
|
97
|
+
*
|
|
98
|
+
* The stored order is insertion order (see `upsertRecord`), which leaves a project returned to
|
|
99
|
+
* sitting wherever it was first seen — so ordering is applied here, on read, where it also
|
|
100
|
+
* covers rows a host provider supplied.
|
|
101
|
+
*/
|
|
102
|
+
get sortedHistory(): CostSessionRecord[];
|
|
103
|
+
/**
|
|
104
|
+
* Lifetime usage across every project in the history scope — cost and all four token
|
|
105
|
+
* buckets. What the Usage tab's lifetime section renders.
|
|
106
|
+
*
|
|
107
|
+
* Rows ONLY — deliberately not rows + live session. Each row holds a project's lifetime
|
|
108
|
+
* usage and the active project's row is kept current on every transcript change, so
|
|
109
|
+
* adding the live total would count this project's spend twice. That double-count is what
|
|
110
|
+
* made the figure grow on every refresh: a page-hide banked a row while the restored
|
|
111
|
+
* session still carried the same spend.
|
|
112
|
+
*/
|
|
113
|
+
get cumulativeUsage(): AggregateUsage;
|
|
114
|
+
/** Lifetime USD across every project in the history scope. */
|
|
115
|
+
get cumulativeCostUsd(): number;
|
|
116
|
+
/**
|
|
117
|
+
* Whether the live session figures amount to this project's whole history, which is true
|
|
118
|
+
* exactly when the transcript is restored on load. Drives the Usage tab's live labels
|
|
119
|
+
* ("Project cost" vs "Session cost") so they state the scope rather than imply one.
|
|
120
|
+
*
|
|
121
|
+
* Note this is NOT what makes the *rows* lifetime — those bank their pre-transcript spend
|
|
122
|
+
* (see `CostSessionRecord.banked`) and are lifetime either way.
|
|
123
|
+
*/
|
|
124
|
+
get liveUsageIsProjectLifetime(): boolean;
|
|
125
|
+
/** Label shown on the Usage tab for the in-flight session (app title or first user phrase). */
|
|
126
|
+
get liveTitle(): string | undefined;
|
|
127
|
+
/**
|
|
128
|
+
* Write this session's usage into persisted build history as a single row keyed by the
|
|
129
|
+
* session key, replacing any existing row for that key.
|
|
130
|
+
*
|
|
131
|
+
* **Idempotent** — safe to call per turn, on tab close, and on a title change alike.
|
|
132
|
+
* The name is historical: it does NOT end the session or reset the running totals.
|
|
133
|
+
* Resetting them is what used to double-count spend, since the row was banked while the
|
|
134
|
+
* persisted transcript still carried the same usage, so a reload restored it and the
|
|
135
|
+
* Usage tab counted both.
|
|
136
|
+
*
|
|
137
|
+
* No-op unless `chatConfig.costHistory.enabled` is `true`, when the live session has no
|
|
138
|
+
* usage yet, or when the session has no key to upsert against. Gating on the *live*
|
|
139
|
+
* figures (not the banked total) means a reload that restores nothing rewrites nothing.
|
|
140
|
+
*/
|
|
141
|
+
finalize(options?: {
|
|
142
|
+
title?: string;
|
|
143
|
+
}): void;
|
|
144
|
+
/**
|
|
145
|
+
* Spend for `projectKey` that predates the current transcript, resolved once per SESSION and
|
|
146
|
+
* shared by every element instance wired to it (see `banked-usage-baselines`), so it cannot
|
|
147
|
+
* drift as the row it came from is rewritten by our own upserts.
|
|
148
|
+
*
|
|
149
|
+
* The two cases turn on whether the transcript is authoritative for this project:
|
|
150
|
+
*
|
|
151
|
+
* - **Transcript restored** (`persistenceActive`): it already accounts for everything after
|
|
152
|
+
* the previous row's own banked figure, so carry that figure forward unchanged. Re-banking
|
|
153
|
+
* `usage` here would add the restored history to itself on every reload.
|
|
154
|
+
* - **Transcript starts empty**: nothing on screen accounts for the previous total, so the
|
|
155
|
+
* whole of it becomes banked. Without this the next turn would rewrite the row downwards
|
|
156
|
+
* to just that turn, losing the project's earlier spend.
|
|
157
|
+
*
|
|
158
|
+
* The baseline is deliberately NOT element or controller state. This element is remounted
|
|
159
|
+
* repeatedly inside a single session (the host re-rendering its layout, popping out, docking)
|
|
160
|
+
* and a fresh instance re-deriving would take the second branch against rows that already hold
|
|
161
|
+
* the live transcript — banking the session as its own baseline. Keying on the session instead
|
|
162
|
+
* means a remount inherits the baseline that transcript was paired with.
|
|
163
|
+
*/
|
|
164
|
+
private bankedUsageFor;
|
|
165
|
+
/**
|
|
166
|
+
* Re-baseline this session against an emptied transcript: what came before, plus what the
|
|
167
|
+
* transcript being discarded proved. `discarded` must be read BEFORE the live figures are
|
|
168
|
+
* reset, since that is the only record of the latter.
|
|
169
|
+
*
|
|
170
|
+
* Not the same as dropping the baseline. Dropping it re-derives through `persistenceActive`,
|
|
171
|
+
* and with persistence ON that carries the row's *old* banked figure forward — discarding
|
|
172
|
+
* every turn between the last baseline and the clear. The transcript is gone either way, so
|
|
173
|
+
* the branch that reads it as authoritative no longer applies.
|
|
174
|
+
*
|
|
175
|
+
* Derived from the memoised baseline and the live figure rather than from the project's row,
|
|
176
|
+
* because a row is only as current as the last {@link CostLedgerController.finalize} — which
|
|
177
|
+
* skips its write while the ledger is unloaded, and while the session has no usage yet.
|
|
178
|
+
*/
|
|
179
|
+
rebaselineOnClearedTranscript(key: string, discarded: AggregateUsage): void;
|
|
180
|
+
/** Remove one entry from persisted build history. */
|
|
181
|
+
remove(id: string): void;
|
|
182
|
+
/** Clear all persisted build history for this assistant scope. */
|
|
183
|
+
clear(): void;
|
|
184
|
+
private scope;
|
|
185
|
+
/** Re-read the ledger for the current scope. Runs on connect and on every `chatConfig` rebind. */
|
|
186
|
+
reload(): void;
|
|
187
|
+
/**
|
|
188
|
+
* The persistence provider, but only when it implements BOTH cost-history methods.
|
|
189
|
+
*
|
|
190
|
+
* Both or neither: a provider that can save but not load would silently accumulate a
|
|
191
|
+
* ledger nobody ever reads back, so a partial implementation is treated as absent and
|
|
192
|
+
* falls back to `localStorage`.
|
|
193
|
+
*/
|
|
194
|
+
private costHistoryProvider;
|
|
195
|
+
/**
|
|
196
|
+
* Adopt `records` as the live list, and schedule a write to whichever backend owns cost
|
|
197
|
+
* history — the provider when the host implements it, else `localStorage`.
|
|
198
|
+
*
|
|
199
|
+
* The list is adopted synchronously because that is what the Usage tab renders; only the
|
|
200
|
+
* *write* is deferred. This runs on every usage change (about once per LLM call, plus once
|
|
201
|
+
* per sub-agent turn) and each write serialises the whole ledger, so coalescing cuts a
|
|
202
|
+
* per-turn `localStorage.setItem` or network round-trip down to one per quiet period. Safe
|
|
203
|
+
* because the write is a full replace of idempotent state: the newest call wins and no
|
|
204
|
+
* intermediate state is meaningful. Flushed on `pagehide` and disconnect so nothing is lost.
|
|
205
|
+
*/
|
|
206
|
+
private persist;
|
|
207
|
+
/**
|
|
208
|
+
* Write the pending ledger now. Safe to call when nothing is pending.
|
|
209
|
+
*
|
|
210
|
+
* Provider writes are fire-and-forget and best-effort: the ledger is accounting, not
|
|
211
|
+
* conversation content, so a failed write is logged and never surfaced into the chat. On
|
|
212
|
+
* `pagehide` the `localStorage` path completes synchronously; a provider write is subject to
|
|
213
|
+
* the same unload race as every other network save the assistant makes.
|
|
214
|
+
*/
|
|
215
|
+
flush(): void;
|
|
216
|
+
/**
|
|
217
|
+
* Finalize the previous session when the host-provided title changes — a rename should land on
|
|
218
|
+
* the row under its old name rather than silently relabelling accrued spend.
|
|
219
|
+
*
|
|
220
|
+
* Only when the session key still matches the one that title accrued under. Hosts change
|
|
221
|
+
* `costHistory.title` on project SWITCH too, and if the key has already flipped, this would
|
|
222
|
+
* upsert the outgoing project's live usage onto the incoming project's row as
|
|
223
|
+
* `banked + old live`, inflating it. Under the old append model a mis-keyed write left a stray
|
|
224
|
+
* row; with the upsert it overwrites a real one.
|
|
225
|
+
*/
|
|
226
|
+
syncActiveTitle(): void;
|
|
227
|
+
}
|
|
228
|
+
//# sourceMappingURL=cost-ledger-controller.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cost-ledger-controller.d.ts","sourceRoot":"","sources":["../../../src/main/cost-ledger-controller.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,cAAc,EACd,6BAA6B,EAC7B,UAAU,EACV,WAAW,EACZ,MAAM,4BAA4B,CAAC;AAEpC,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,mDAAmD,CAAC;AAOpG,OAAO,EAQL,KAAK,qBAAqB,EAC1B,KAAK,iBAAiB,EACvB,MAAM,+BAA+B,CAAC;AAMvC,OAAO,EAEL,KAAK,yBAAyB,EAC/B,MAAM,sCAAsC,CAAC;AAW9C,8FAA8F;AAC9F,MAAM,WAAW,cAAc;IAC7B,UAAU,EAAE,MAAM,UAAU,CAAC;IAC7B,uEAAuE;IACvE,QAAQ,EAAE,MAAM,SAAS,WAAW,EAAE,CAAC;IACvC,oEAAoE;IACpE,YAAY,EAAE,MAAM,cAAc,CAAC;IACnC,gBAAgB,EAAE,MAAM,6BAA6B,EAAE,CAAC;IACxD;;;;OAIG;IACH,iBAAiB,EAAE,MAAM,OAAO,CAAC;IACjC,sFAAsF;IACtF,QAAQ,EAAE,MAAM,MAAM,GAAG,SAAS,CAAC;IACnC,WAAW,EAAE,MAAM,MAAM,GAAG,SAAS,CAAC;IACtC,gFAAgF;IAChF,QAAQ,EAAE,MAAM,0BAA0B,GAAG,SAAS,CAAC;IACvD,mEAAmE;IACnE,SAAS,EAAE,MAAM,MAAM,CAAC;CACzB;AAED;;;;;;;;;;;;GAYG;AACH,qBAAa,oBAAoB;IA8CnB,OAAO,CAAC,QAAQ,CAAC,IAAI;IA7CjC;;;;OAIG;IACS,OAAO,EAAE,iBAAiB,EAAE,CAAM;IAE9C;;;;;;;OAOG;IACH,OAAO,CAAC,WAAW,CAAM;IACzB,OAAO,CAAC,SAAS,CAAC,CAAS;IAM3B;;;;;;;;;;;;;;;;OAgBG;IACH,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,UAAU,CAAS;IAC3B,iGAAiG;IACjG,OAAO,CAAC,KAAK,CAAS;IACtB,OAAO,CAAC,UAAU,CAAC,CAAgC;gBAEtB,IAAI,EAAE,cAAc;IAEjD,kFAAkF;IAClF,IACI,MAAM,IAAI,yBAAyB,CAEtC;IAED;;;;;;;;;;OAUG;IACH,IACI,oBAAoB,IAAI,qBAAqB,EAAE,CAiBlD;IAED;;;;;;OAMG;IACH,IACI,aAAa,IAAI,iBAAiB,EAAE,CAEvC;IAED;;;;;;;;;OASG;IACH,IACI,eAAe,IAAI,cAAc,CAKpC;IAED,8DAA8D;IAC9D,IACI,iBAAiB,IAAI,MAAM,CAE9B;IAED;;;;;;;OAOG;IACH,IACI,0BAA0B,IAAI,OAAO,CAExC;IAED,+FAA+F;IAC/F,IACI,SAAS,IAAI,MAAM,GAAG,SAAS,CAKlC;IAED;;;;;;;;;;;;;OAaG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI;IA6C5C;;;;;;;;;;;;;;;;;;;OAmBG;IACH,OAAO,CAAC,cAAc;IAWtB;;;;;;;;;;;;;OAaG;IACH,6BAA6B,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,cAAc,GAAG,IAAI;IAyC3E,qDAAqD;IACrD,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI;IAcxB,kEAAkE;IAClE,KAAK,IAAI,IAAI;IAgBb,OAAO,CAAC,KAAK;IAMb,kGAAkG;IAClG,MAAM,IAAI,IAAI;IAgDd;;;;;;OAMG;IACH,OAAO,CAAC,mBAAmB;IAK3B;;;;;;;;;;OAUG;IACH,OAAO,CAAC,OAAO;IAWf;;;;;;;OAOG;IACH,KAAK,IAAI,IAAI;IAmBb;;;;;;;;;OASG;IACH,eAAe,IAAI,IAAI;CAYxB"}
|