@genesislcap/ai-assistant 15.7.3-alpha-e7d4aa5.0 → 15.8.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 (43) hide show
  1. package/dist/ai-assistant.api.json +191 -1
  2. package/dist/ai-assistant.d.ts +155 -1
  3. package/dist/custom-elements.json +114 -2
  4. package/dist/dts/components/settings-modal/settings-modal.styles.d.ts.map +1 -1
  5. package/dist/dts/components/settings-modal/settings-modal.styles.test.d.ts +2 -0
  6. package/dist/dts/components/settings-modal/settings-modal.styles.test.d.ts.map +1 -0
  7. package/dist/dts/components/settings-modal/settings-modal.template.d.ts +22 -0
  8. package/dist/dts/components/settings-modal/settings-modal.template.d.ts.map +1 -1
  9. package/dist/dts/components/settings-modal/settings-modal.template.test.d.ts +2 -0
  10. package/dist/dts/components/settings-modal/settings-modal.template.test.d.ts.map +1 -0
  11. package/dist/dts/main/budget-meter.test.d.ts +2 -0
  12. package/dist/dts/main/budget-meter.test.d.ts.map +1 -0
  13. package/dist/dts/main/main.d.ts +113 -2
  14. package/dist/dts/main/main.d.ts.map +1 -1
  15. package/dist/dts/main/main.types.d.ts +25 -0
  16. package/dist/dts/main/main.types.d.ts.map +1 -1
  17. package/dist/dts/state/ai-assistant-slice.d.ts +47 -0
  18. package/dist/dts/state/ai-assistant-slice.d.ts.map +1 -1
  19. package/dist/dts/state/session-store.d.ts +4 -0
  20. package/dist/dts/state/session-store.d.ts.map +1 -1
  21. package/dist/dts/utils/format-usd.d.ts +24 -0
  22. package/dist/dts/utils/format-usd.d.ts.map +1 -0
  23. package/dist/esm/components/settings-modal/settings-modal.styles.js +33 -3
  24. package/dist/esm/components/settings-modal/settings-modal.styles.test.js +80 -0
  25. package/dist/esm/components/settings-modal/settings-modal.template.js +64 -14
  26. package/dist/esm/components/settings-modal/settings-modal.template.test.js +91 -0
  27. package/dist/esm/main/budget-meter.test.js +317 -0
  28. package/dist/esm/main/main.js +189 -3
  29. package/dist/esm/state/ai-assistant-slice.js +38 -0
  30. package/dist/esm/utils/format-usd.js +23 -0
  31. package/dist/tsconfig.tsbuildinfo +1 -1
  32. package/docs/migration-GENC-1464.md +63 -0
  33. package/docs/styling.md +23 -0
  34. package/package.json +17 -17
  35. package/src/components/settings-modal/settings-modal.styles.test.ts +94 -0
  36. package/src/components/settings-modal/settings-modal.styles.ts +33 -3
  37. package/src/components/settings-modal/settings-modal.template.test.ts +124 -0
  38. package/src/components/settings-modal/settings-modal.template.ts +82 -13
  39. package/src/main/budget-meter.test.ts +427 -0
  40. package/src/main/main.ts +198 -3
  41. package/src/main/main.types.ts +26 -0
  42. package/src/state/ai-assistant-slice.ts +74 -0
  43. package/src/utils/format-usd.ts +26 -0
@@ -93,6 +93,32 @@ export const AiAssistantEvent = {
93
93
  */
94
94
  export type AiAssistantEvent = (typeof AiAssistantEvent)[keyof typeof AiAssistantEvent];
95
95
 
96
+ /**
97
+ * One rendered row of the settings-modal budget meter (GENC-1464) — a vendor
98
+ * that currently has budget figures, pre-formatted so the template stays a pure
99
+ * binding layer (and so tests can pin the exact text a user reads).
100
+ *
101
+ * @beta
102
+ */
103
+ export interface SettingsBudgetRow {
104
+ /** The vendor the row describes (an `AIProviderType` member). */
105
+ vendor: string;
106
+ /** Display name from `VENDOR_LABELS`, e.g. `Anthropic`. */
107
+ label: string;
108
+ /**
109
+ * The real figures — `$45.00 / $50.00 (90%)`. The percentage is NOT clamped:
110
+ * spend can genuinely overshoot the cap (the last allowed call may cross it),
111
+ * and the text tells the truth about it.
112
+ */
113
+ figures: string;
114
+ /**
115
+ * The bar's value, clamped to `0..100` — a progress bar past its end renders
116
+ * nonsense, so only the bar clamps (mirroring the context-usage indicator's
117
+ * `Math.min(100, …)`).
118
+ */
119
+ barValue: number;
120
+ }
121
+
96
122
  /**
97
123
  * State of the chat suggestions feature.
98
124
  *
@@ -27,6 +27,23 @@ export interface InputOverride {
27
27
  mode: ChatInputDuringExecutionMode;
28
28
  }
29
29
 
30
+ /**
31
+ * One vendor's AI-spend figures, as fed by the host from the budget endpoint
32
+ * (GENC-1464 — the settings-modal budget meter).
33
+ *
34
+ * Dollars, not a percentage: the meter's text shows the real figures (spend CAN
35
+ * exceed the cap — the last allowed call may overshoot it), and only the bar
36
+ * clamps. Storing a pre-computed percentage would destroy the overshoot.
37
+ *
38
+ * @beta
39
+ */
40
+ export interface VendorBudgetFigures {
41
+ /** The vendor's budget cap in USD. Finite and `>= 0`. */
42
+ budgetUsd: number;
43
+ /** Metered spend against that cap in USD. Finite and `>= 0`; may exceed the cap. */
44
+ spentUsd: number;
45
+ }
46
+
30
47
  /**
31
48
  * Shape of a single AI assistant session's serializable state.
32
49
  *
@@ -182,6 +199,20 @@ export interface AiAssistantSessionState {
182
199
  * copy composed from the vendor's display name.
183
200
  */
184
201
  blockedVendorReasons: Partial<Record<AIProviderType, string>>;
202
+ /**
203
+ * Per-vendor AI-spend figures for the settings-modal budget meter (GENC-1464),
204
+ * fed by the host via the element's `setVendorBudget`. A vendor with no entry
205
+ * renders no meter row — which is also how "unlimited" is expressed: the host
206
+ * simply never feeds a vendor the server marks unlimited.
207
+ *
208
+ * NOT persisted to the session snapshot, deliberately. The server is
209
+ * authoritative for spend and the host re-feeds on boot (and after each turn),
210
+ * so a persisted copy could only ever show stale dollars across a reload —
211
+ * wrong money is worse than a beat of no meter. The snapshot assembler
212
+ * (`session-snapshot.ts`) builds from an explicit field list, so absence is
213
+ * structural, not an accident of serialization.
214
+ */
215
+ vendorBudgets: Partial<Record<AIProviderType, VendorBudgetFigures>>;
185
216
  /** Draft text in the input box — preserved across pop-in/pop-out cycles. */
186
217
  inputValue: string;
187
218
  /** Live trace from a currently-executing sub-agent. Cleared on sub-agent-stop. */
@@ -249,6 +280,7 @@ export function createDefaultSessionState(): AiAssistantSessionState {
249
280
  blockedVendors: [],
250
281
  sweptVendors: [],
251
282
  blockedVendorReasons: {},
283
+ vendorBudgets: {},
252
284
  inputValue: '',
253
285
  liveSubAgentTrace: [],
254
286
  liveSubAgentName: null,
@@ -427,6 +459,41 @@ export const aiAssistantSlice = createSlice({
427
459
  delete state.blockedVendorReasons[vendor];
428
460
  }
429
461
  },
462
+ /**
463
+ * Store (or clear, with `null`) one vendor's budget figures for the meter.
464
+ *
465
+ * Validation lives at the element boundary (`setVendorBudget` on the
466
+ * element), not here — the reducer is the storage layer and trusts its one
467
+ * caller, exactly as `setVendorBlocked` above trusts its.
468
+ *
469
+ * Idempotent by VALUE, not just by effect: re-feeding the figures a vendor
470
+ * already holds leaves the stored object untouched. The documented feed
471
+ * re-runs after every turn, so without this every turn would publish a new
472
+ * store reference for the same dollars and re-render a meter that has not
473
+ * changed.
474
+ */
475
+ setVendorBudget(
476
+ state,
477
+ action: PayloadAction<{ vendor: AIProviderType; figures: VendorBudgetFigures | null }>,
478
+ ) {
479
+ const { vendor, figures } = action.payload;
480
+ if (figures === null) {
481
+ delete state.vendorBudgets[vendor];
482
+ return;
483
+ }
484
+ const existing = state.vendorBudgets[vendor];
485
+ if (
486
+ existing &&
487
+ existing.budgetUsd === figures.budgetUsd &&
488
+ existing.spentUsd === figures.spentUsd
489
+ ) {
490
+ return;
491
+ }
492
+ // A fresh object rather than the caller's: the store may freeze what it
493
+ // holds, and adopting the host's object would freeze state the host still
494
+ // owns (and let later host mutations bypass the store).
495
+ state.vendorBudgets[vendor] = { budgetUsd: figures.budgetUsd, spentUsd: figures.spentUsd };
496
+ },
430
497
  setInputValue(state, action: PayloadAction<string>) {
431
498
  state.inputValue = action.payload;
432
499
  },
@@ -522,6 +589,13 @@ export const aiAssistantSlice = createSlice({
522
589
  blockedVendors: state.blockedVendors,
523
590
  sweptVendors: state.sweptVendors,
524
591
  blockedVendorReasons: state.blockedVendorReasons,
592
+ // Category 2, beside the blocked latch: the budget figures are a fact
593
+ // about the USER'S DEPLOYMENT (what the server meters), not about this
594
+ // conversation — "Clear" refills no budget, so wiping them would blank
595
+ // the meter until the host's next re-feed while the dollars are
596
+ // unchanged. Opposite side from `contextTokens`, which IS conversation
597
+ // state (a new chat genuinely has an empty context window).
598
+ vendorBudgets: state.vendorBudgets,
525
599
  } satisfies Partial<AiAssistantSessionState>;
526
600
  Object.assign(state, createDefaultSessionState(), preservedAcrossReset);
527
601
  },
@@ -0,0 +1,26 @@
1
+ /**
2
+ * The ONE place this package renders a USD amount as text.
3
+ *
4
+ * Extracted (GENC-1464 budget meter) from the ad-hoc `.toFixed(2)` call sites
5
+ * that had grown in `formatBlockedReason` and the settings modal's cost blocks,
6
+ * so the blocked banner, the session-cost figures and the budget meter cannot
7
+ * drift to different precisions. Two decimals always — money columns jitter
8
+ * when "$5.1" sits above "$5.10".
9
+ *
10
+ * @internal
11
+ */
12
+
13
+ /** Decimal places for every USD figure this package renders. */
14
+ export const USD_DECIMALS = 2;
15
+
16
+ /**
17
+ * The bare amount, no currency sign — `(12.3) => "12.30"`.
18
+ *
19
+ * Exists for the settings modal's cost blocks, where the `$` is a separately
20
+ * styled `<span class="prefix">` and folding it into the text would change the
21
+ * DOM those styles target.
22
+ */
23
+ export const formatUsdAmount = (value: number): string => value.toFixed(USD_DECIMALS);
24
+
25
+ /** A signed USD figure — `(12.3) => "$12.30"`. */
26
+ export const formatUsd = (value: number): string => `$${formatUsdAmount(value)}`;