@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
@@ -1,6 +1,13 @@
1
1
  import { css } from '@genesislcap/web-core';
2
+ import { assistantSettingsSectionTitleStyles } from '../../styles/settings-section';
2
3
  /** Styles for the assistant settings modal (tabs, sections, toggles, cost summary). */
3
4
  export const settingsModalStyles = css `
5
+ /*
6
+ * Section headings come from the shared fragment the package also exports, so a host titling
7
+ * its own slotted section applies the same rule rather than a copy of it.
8
+ */
9
+ ${assistantSettingsSectionTitleStyles}
10
+
4
11
  *,
5
12
  *::before,
6
13
  *::after {
@@ -115,16 +122,13 @@ export const settingsModalStyles = css `
115
122
  border-bottom: none;
116
123
  }
117
124
 
118
- .settings-modal-section-title {
125
+ /*
126
+ * Only the spacing. Everything else about this heading comes from the exported fragment
127
+ * above, which the assistant and its hosts share; margin is deliberately not in there,
128
+ * since the gap below a heading depends on the layout around it.
129
+ */
130
+ .ai-settings-section-title {
119
131
  margin: 0 0 calc(var(--design-unit) * 3px);
120
- font-size: 11px;
121
- font-weight: 600;
122
- letter-spacing: 0.08em;
123
- text-transform: uppercase;
124
- color: var(--neutral-foreground-hint);
125
- display: flex;
126
- align-items: center;
127
- gap: calc(var(--design-unit) * 2px);
128
132
  }
129
133
 
130
134
  .settings-modal-section-note {
@@ -158,6 +162,15 @@ export const settingsModalStyles = css `
158
162
  gap: calc(var(--design-unit) * 2px);
159
163
  }
160
164
 
165
+ /*
166
+ * For rows whose control is a button rather than a switch. The base row top-aligns, which suits
167
+ * a switch (it lands level with the title), but a button is tall enough that top-aligning leaves
168
+ * it above the title-plus-description block it acts on.
169
+ */
170
+ .settings-toggle-row.is-centered {
171
+ align-items: center;
172
+ }
173
+
161
174
  .settings-animation-list {
162
175
  display: flex;
163
176
  flex-direction: column;
@@ -235,6 +248,18 @@ export const settingsModalStyles = css `
235
248
  max-width: none;
236
249
  }
237
250
 
251
+ /*
252
+ * Sub-heading over the provider toggle group. Unlike the section headings it sits INSIDE
253
+ * .settings-modal-section-body, so the parent's 4-unit flex gap already separates it from the
254
+ * first toggle — and flex does not collapse margins, so the shared rule's 3-unit margin-bottom
255
+ * would add to that gap rather than replace it, leaving the label adrift from the rows it
256
+ * names. Zero the margin and pull back 2 of the 4 gap units, expressed in the same token so it
257
+ * tracks if that gap changes.
258
+ */
259
+ .settings-app-toggles-title {
260
+ margin: 0 0 calc(var(--design-unit) * -2px);
261
+ }
262
+
238
263
  .settings-app-toggle-block {
239
264
  display: flex;
240
265
  flex-direction: column;
@@ -269,7 +294,8 @@ export const settingsModalStyles = css `
269
294
  color: var(--neutral-foreground-rest);
270
295
  }
271
296
 
272
- .settings-current-build {
297
+ .settings-current-build,
298
+ .settings-lifetime-usage {
273
299
  background: var(--neutral-layer-2);
274
300
  border: 1px solid var(--neutral-stroke-rest);
275
301
  border-radius: calc(var(--control-corner-radius) * 1.5px);
@@ -277,6 +303,28 @@ export const settingsModalStyles = css `
277
303
  margin: calc(var(--design-unit) * 3px) 0;
278
304
  }
279
305
 
306
+ /*
307
+ * Deliberately quieter than the live block: no pulsing badge and no accent, so the
308
+ * eye still lands on what is being built now. It leads only because it is the wider
309
+ * figure, not because it is the more urgent one.
310
+ */
311
+ .settings-lifetime-usage-top {
312
+ display: flex;
313
+ align-items: center;
314
+ flex-wrap: wrap;
315
+ gap: calc(var(--design-unit) * 2px);
316
+ margin-bottom: calc(var(--design-unit) * 3px);
317
+ }
318
+
319
+ .settings-lifetime-label {
320
+ font-size: 10px;
321
+ line-height: 1;
322
+ font-weight: 700;
323
+ text-transform: uppercase;
324
+ letter-spacing: 0.06em;
325
+ color: var(--neutral-foreground-hint);
326
+ }
327
+
280
328
  .settings-current-build-top {
281
329
  display: flex;
282
330
  align-items: center;
@@ -285,20 +333,44 @@ export const settingsModalStyles = css `
285
333
  margin-bottom: calc(var(--design-unit) * 3px);
286
334
  }
287
335
 
336
+ /*
337
+ * Optically centring these 10px caps against the 14px project name beside them. Flex offers
338
+ * only two alignments and neither lands it, because a box centre and a text baseline are
339
+ * different lines:
340
+ * - align-items: center sits the badge baseline about 1.2px high
341
+ * - align-items: baseline matches baselines, but the caps then read as bottom-aligned
342
+ * against the name's ascenders, so the badge looks low
343
+ * The eye wants the midpoint, so: centre the boxes, then translate down by the ~1px of
344
+ * overshoot. This is a nudge, but it corrects a fixed relationship between two font sizes
345
+ * rather than some neighbouring element's layout, so unlike a margin tuned to a sibling it
346
+ * cannot rot when that sibling changes. In em, so it tracks the badge text.
347
+ *
348
+ * display: block, not inline-flex: a flex container has no text baseline of its own and
349
+ * synthesizes one from its first child, which here is the dot, an empty span. As a block box
350
+ * the badge takes the baseline of its first line, i.e. the text.
351
+ */
288
352
  .settings-live-badge {
289
- display: inline-flex;
290
- align-items: center;
291
- gap: calc(var(--design-unit) * 1px);
353
+ display: block;
292
354
  font-size: 10px;
355
+ line-height: 1;
293
356
  font-weight: 700;
294
357
  text-transform: uppercase;
295
358
  letter-spacing: 0.06em;
296
359
  color: var(--accent-fill-rest);
360
+ white-space: nowrap;
361
+ transform: translateY(0.1em);
297
362
  }
298
363
 
299
364
  .settings-live-dot {
365
+ display: inline-block;
300
366
  width: 7px;
301
367
  height: 7px;
368
+
369
+ /* Replaces the flex gap the badge no longer has. */
370
+ margin-right: calc(var(--design-unit) * 1px);
371
+
372
+ /* Centres the dot on the text beside it without disturbing that text's baseline. */
373
+ vertical-align: middle;
302
374
  border-radius: 50%;
303
375
  background: var(--accent-fill-rest);
304
376
  animation: settings-live-pulse 1.6s ease-out infinite;
@@ -320,18 +392,30 @@ export const settingsModalStyles = css `
320
392
 
321
393
  .settings-current-build-name {
322
394
  font-size: 14px;
395
+ line-height: 1;
323
396
  font-weight: 600;
324
397
  }
325
398
 
326
- .settings-current-build-stats {
399
+ /*
400
+ * Cost and the token buckets side by side: one number in its own box, the four buckets
401
+ * sharing the box next to it. Stretch, so the two are the same height however tall the 22px
402
+ * cost value makes the left one.
403
+ */
404
+ .settings-usage-row {
327
405
  display: flex;
328
406
  flex-wrap: wrap;
407
+ align-items: stretch;
329
408
  gap: calc(var(--design-unit) * 3px);
330
409
  margin-bottom: calc(var(--design-unit) * 3px);
331
410
  }
332
411
 
412
+ .settings-usage-row-tokens {
413
+ display: flex;
414
+ flex: 1 1 auto;
415
+ min-width: 0;
416
+ }
417
+
333
418
  .settings-build-stat {
334
- flex: 1;
335
419
  min-width: 0;
336
420
  background: var(--neutral-layer-1);
337
421
  border: 1px solid var(--neutral-stroke-rest);
@@ -359,6 +443,105 @@ export const settingsModalStyles = css `
359
443
  color: var(--accent-fill-rest);
360
444
  }
361
445
 
446
+ /*
447
+ * Per-bucket token split, beneath the headline cost. Small paired label/value cells
448
+ * rather than four more stat cards: they are the detail behind the cost above, so they
449
+ * have to read as subordinate to it rather than compete with it. The four buckets ARE
450
+ * the token figure now — there is no merged total anywhere for them to break down.
451
+ */
452
+ .settings-token-breakdown {
453
+ display: flex;
454
+ flex-direction: column;
455
+
456
+ /*
457
+ * Tight against the buckets below. One design unit rather than two, because each bucket
458
+ * separates its own label from its value by only 2px — at two units the title sat four
459
+ * times further from the bucket labels than they sit from their own numbers, which read
460
+ * as a gap between unrelated blocks instead of a heading over its content.
461
+ */
462
+ gap: calc(var(--design-unit) * 1px);
463
+ margin-bottom: calc(var(--design-unit) * 3px);
464
+ padding: calc(var(--design-unit) * 2px) calc(var(--design-unit) * 3px);
465
+ background: var(--neutral-layer-1);
466
+ border: 1px solid var(--neutral-stroke-rest);
467
+ border-radius: calc(var(--control-corner-radius) * 1px);
468
+ }
469
+
470
+ /*
471
+ * Titles the box, because nothing else in it named the unit. Same styling and position as
472
+ * the cost box's label, so two boxes sitting side by side are titled the same way; the
473
+ * hierarchy against the bucket labels comes from position, not a second type size.
474
+ */
475
+ .settings-token-breakdown-label {
476
+ font-size: 10px;
477
+ line-height: 1;
478
+ font-weight: 600;
479
+ text-transform: uppercase;
480
+ letter-spacing: 0.06em;
481
+ color: var(--neutral-foreground-hint);
482
+ }
483
+
484
+ .settings-token-buckets {
485
+ display: flex;
486
+ flex-wrap: wrap;
487
+ gap: calc(var(--design-unit) * 4px);
488
+ }
489
+
490
+ .settings-token-bucket {
491
+ display: flex;
492
+ min-width: 0;
493
+ flex-direction: column;
494
+ gap: 2px;
495
+ }
496
+
497
+ .settings-token-bucket-label {
498
+ font-size: 10px;
499
+ font-weight: 600;
500
+ text-transform: uppercase;
501
+ letter-spacing: 0.06em;
502
+ color: var(--neutral-foreground-hint);
503
+ }
504
+
505
+ .settings-token-bucket-value {
506
+ font-size: 13px;
507
+ font-weight: 600;
508
+ color: var(--neutral-foreground-rest);
509
+ }
510
+
511
+ /*
512
+ * Cache reads bill at a fraction of the uncached input rate, and in an agentic
513
+ * session they are usually the largest bucket by a wide margin. Muted so the biggest
514
+ * number on the row doesn't read as the dominant cost driver when it is the cheapest.
515
+ */
516
+ .settings-token-bucket.is-discounted .settings-token-bucket-value {
517
+ font-weight: 500;
518
+ color: var(--neutral-foreground-hint);
519
+ }
520
+
521
+ /*
522
+ * Usage-row overrides. Placed after the base rules they refine rather than up beside
523
+ * .settings-usage-row itself, because stylelint's no-descending-specificity rejects a
524
+ * compound selector appearing before the plain one it overrides.
525
+ */
526
+
527
+ /* Sized by its content; the buckets take the rest, since four cells need the width. */
528
+ .settings-usage-row > .settings-build-stat {
529
+ flex: 0 1 auto;
530
+ }
531
+
532
+ .settings-usage-row .settings-token-breakdown {
533
+ flex: 1 1 auto;
534
+
535
+ /* The row owns the spacing now; this centres the column against the taller cost box. */
536
+ justify-content: center;
537
+ margin-bottom: 0;
538
+ }
539
+
540
+ /* Equal columns, so the four buckets line up as a grid rather than bunching left. */
541
+ .settings-usage-row .settings-token-bucket {
542
+ flex: 1 1 0;
543
+ }
544
+
362
545
  .settings-model-tags {
363
546
  display: flex;
364
547
  flex-wrap: wrap;
@@ -431,6 +614,32 @@ export const settingsModalStyles = css `
431
614
  gap: calc(var(--design-unit) * 2px);
432
615
  }
433
616
 
617
+ /*
618
+ * Both of these carry a ?hidden binding (showTokens), and both declare display: flex — which
619
+ * silently beat the UA [hidden] { display: none } rule, because author-origin declarations
620
+ * outrank user-agent origin whatever the specificity. Without this, showTokens: false still
621
+ * rendered the full breakdown in the lifetime block, the live block and every history row.
622
+ * Sibling elements that hide correctly do so only by declaring no display at all. Same
623
+ * treatment as .settings-modal-tab-panel[hidden] near the top of this file.
624
+ */
625
+ .settings-usage-row-tokens[hidden],
626
+ .settings-cost-history-row-content[hidden] {
627
+ display: none !important;
628
+ }
629
+
630
+ /*
631
+ * The buckets inside a history row drop the panel treatment they carry in the summary
632
+ * blocks — the row is already a bordered card, and a second box inside it reads as a
633
+ * nested panel. The adjacent-sibling rule above supplies the separator instead.
634
+ */
635
+ .settings-cost-history-row-breakdown .settings-token-breakdown {
636
+ flex: 1 1 auto;
637
+ margin-bottom: 0;
638
+ padding: 0;
639
+ background: none;
640
+ border: none;
641
+ }
642
+
434
643
  .settings-cost-history-row-content + .settings-cost-history-row-content {
435
644
  border-top: 1px solid var(--neutral-stroke-rest);
436
645
  margin-top: calc(var(--design-unit) * 2px);
@@ -445,8 +654,20 @@ export const settingsModalStyles = css `
445
654
  justify-content: space-between;
446
655
  }
447
656
 
657
+ /*
658
+ * Holds the cost and the delete button as one centred pair. The button used to be a sibling
659
+ * of the whole row body, kept in place by a -2px nudge tuned for when the metrics column had
660
+ * two lines (cost plus a merged token total). Dropping that second line left the nudge
661
+ * compensating for nothing. Centring the two against each other needs no magic number and
662
+ * cannot drift again if the metrics content changes.
663
+ *
664
+ * NB this rule previously set align-items with no display:flex, so it had no effect at all.
665
+ */
448
666
  .settings-cost-history-row-right {
449
- align-items: flex-end;
667
+ display: flex;
668
+ align-items: center;
669
+ gap: calc(var(--design-unit) * 1px);
670
+ flex: 0 0 auto;
450
671
  }
451
672
 
452
673
  .settings-cost-history-row-cost,
@@ -467,7 +688,6 @@ export const settingsModalStyles = css `
467
688
  white-space: nowrap;
468
689
  }
469
690
 
470
- .settings-cost-history-row-tokens,
471
691
  .settings-cost-history-row-date {
472
692
  font-size: 12px;
473
693
  color: var(--neutral-foreground-hint);
@@ -475,7 +695,6 @@ export const settingsModalStyles = css `
475
695
 
476
696
  .settings-cost-history-delete {
477
697
  flex: 0 0 auto;
478
- margin-top: -2px;
479
698
  color: var(--neutral-foreground-hint);
480
699
  }
481
700