@genesislcap/ai-assistant 15.19.0 → 15.19.1

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 (288) hide show
  1. package/package.json +17 -18
  2. package/api-extractor.json +0 -11
  3. package/dist/dts/components/chat-driver/chat-driver.compact.test.d.ts +0 -2
  4. package/dist/dts/components/chat-driver/chat-driver.compact.test.d.ts.map +0 -1
  5. package/dist/dts/components/chat-driver/chat-driver.invocation-scope.test.d.ts +0 -2
  6. package/dist/dts/components/chat-driver/chat-driver.invocation-scope.test.d.ts.map +0 -1
  7. package/dist/dts/components/chat-driver/chat-driver.test.d.ts +0 -2
  8. package/dist/dts/components/chat-driver/chat-driver.test.d.ts.map +0 -1
  9. package/dist/dts/components/chat-driver/chat-driver.thinking-policy.test.d.ts +0 -2
  10. package/dist/dts/components/chat-driver/chat-driver.thinking-policy.test.d.ts.map +0 -1
  11. package/dist/dts/components/chat-driver/chat-driver.trace-capture.test.d.ts +0 -2
  12. package/dist/dts/components/chat-driver/chat-driver.trace-capture.test.d.ts.map +0 -1
  13. package/dist/dts/components/chat-driver/chat-driver.turn-usage.test.d.ts +0 -2
  14. package/dist/dts/components/chat-driver/chat-driver.turn-usage.test.d.ts.map +0 -1
  15. package/dist/dts/components/chat-interaction-wrapper/chat-interaction-wrapper.test.d.ts +0 -2
  16. package/dist/dts/components/chat-interaction-wrapper/chat-interaction-wrapper.test.d.ts.map +0 -1
  17. package/dist/dts/components/orchestrating-driver/orchestrating-driver.budget.test.d.ts +0 -2
  18. package/dist/dts/components/orchestrating-driver/orchestrating-driver.budget.test.d.ts.map +0 -1
  19. package/dist/dts/components/orchestrating-driver/orchestrating-driver.cost.test.d.ts +0 -2
  20. package/dist/dts/components/orchestrating-driver/orchestrating-driver.cost.test.d.ts.map +0 -1
  21. package/dist/dts/components/orchestrating-driver/orchestrating-driver.pin.test.d.ts +0 -2
  22. package/dist/dts/components/orchestrating-driver/orchestrating-driver.pin.test.d.ts.map +0 -1
  23. package/dist/dts/components/settings-modal/settings-modal.styles.test.d.ts +0 -2
  24. package/dist/dts/components/settings-modal/settings-modal.styles.test.d.ts.map +0 -1
  25. package/dist/dts/components/settings-modal/settings-modal.template.test.d.ts +0 -2
  26. package/dist/dts/components/settings-modal/settings-modal.template.test.d.ts.map +0 -1
  27. package/dist/dts/config/define-stateful-agent.test.d.ts +0 -2
  28. package/dist/dts/config/define-stateful-agent.test.d.ts.map +0 -1
  29. package/dist/dts/config/validate-providers.test.d.ts +0 -2
  30. package/dist/dts/config/validate-providers.test.d.ts.map +0 -1
  31. package/dist/dts/main/blocked-state.test.d.ts +0 -2
  32. package/dist/dts/main/blocked-state.test.d.ts.map +0 -1
  33. package/dist/dts/main/budget-meter.test.d.ts +0 -2
  34. package/dist/dts/main/budget-meter.test.d.ts.map +0 -1
  35. package/dist/dts/main/cost-session-banking.test.d.ts +0 -2
  36. package/dist/dts/main/cost-session-banking.test.d.ts.map +0 -1
  37. package/dist/dts/main/file-attachments.test.d.ts +0 -2
  38. package/dist/dts/main/file-attachments.test.d.ts.map +0 -1
  39. package/dist/dts/main/interaction-cost.test.d.ts +0 -2
  40. package/dist/dts/main/interaction-cost.test.d.ts.map +0 -1
  41. package/dist/dts/main/main.styles.test.d.ts +0 -2
  42. package/dist/dts/main/main.styles.test.d.ts.map +0 -1
  43. package/dist/dts/main/persistence-broken-sources.test.d.ts +0 -2
  44. package/dist/dts/main/persistence-broken-sources.test.d.ts.map +0 -1
  45. package/dist/dts/main/popout-interaction-gate.test.d.ts +0 -2
  46. package/dist/dts/main/popout-interaction-gate.test.d.ts.map +0 -1
  47. package/dist/dts/state/ai-assistant-slice.test.d.ts +0 -2
  48. package/dist/dts/state/ai-assistant-slice.test.d.ts.map +0 -1
  49. package/dist/dts/state/debug-event-log.test.d.ts +0 -2
  50. package/dist/dts/state/debug-event-log.test.d.ts.map +0 -1
  51. package/dist/dts/state/driver-registry.test.d.ts +0 -2
  52. package/dist/dts/state/driver-registry.test.d.ts.map +0 -1
  53. package/dist/dts/state/interaction-context.test.d.ts +0 -2
  54. package/dist/dts/state/interaction-context.test.d.ts.map +0 -1
  55. package/dist/dts/state/persistence/diagnostics-cursors.test.d.ts +0 -2
  56. package/dist/dts/state/persistence/diagnostics-cursors.test.d.ts.map +0 -1
  57. package/dist/dts/state/persistence/diagnostics.test.d.ts +0 -2
  58. package/dist/dts/state/persistence/diagnostics.test.d.ts.map +0 -1
  59. package/dist/dts/state/persistence/session-persistence-provider.test.d.ts +0 -2
  60. package/dist/dts/state/persistence/session-persistence-provider.test.d.ts.map +0 -1
  61. package/dist/dts/state/persistence/session-persistence.integration.test.d.ts +0 -2
  62. package/dist/dts/state/persistence/session-persistence.integration.test.d.ts.map +0 -1
  63. package/dist/dts/state/persistence/session-persister.test.d.ts +0 -2
  64. package/dist/dts/state/persistence/session-persister.test.d.ts.map +0 -1
  65. package/dist/dts/state/persistence/session-snapshot.test.d.ts +0 -2
  66. package/dist/dts/state/persistence/session-snapshot.test.d.ts.map +0 -1
  67. package/dist/dts/state/persistence/stateful-restore.e2e.test.d.ts +0 -2
  68. package/dist/dts/state/persistence/stateful-restore.e2e.test.d.ts.map +0 -1
  69. package/dist/dts/utils/animation-exclusivity.test.d.ts +0 -2
  70. package/dist/dts/utils/animation-exclusivity.test.d.ts.map +0 -1
  71. package/dist/dts/utils/collect-session-models.test.d.ts +0 -2
  72. package/dist/dts/utils/collect-session-models.test.d.ts.map +0 -1
  73. package/dist/dts/utils/condense-history.test.d.ts +0 -2
  74. package/dist/dts/utils/condense-history.test.d.ts.map +0 -1
  75. package/dist/dts/utils/cost-session-history.test.d.ts +0 -2
  76. package/dist/dts/utils/cost-session-history.test.d.ts.map +0 -1
  77. package/dist/dts/utils/derive-cost-session-title.test.d.ts +0 -2
  78. package/dist/dts/utils/derive-cost-session-title.test.d.ts.map +0 -1
  79. package/dist/dts/utils/flatten-sub-agent-messages.test.d.ts +0 -2
  80. package/dist/dts/utils/flatten-sub-agent-messages.test.d.ts.map +0 -1
  81. package/dist/dts/utils/history-transform.test.d.ts +0 -2
  82. package/dist/dts/utils/history-transform.test.d.ts.map +0 -1
  83. package/dist/dts/utils/message-partition.test.d.ts +0 -2
  84. package/dist/dts/utils/message-partition.test.d.ts.map +0 -1
  85. package/dist/dts/utils/resolve-cost-history-config.test.d.ts +0 -2
  86. package/dist/dts/utils/resolve-cost-history-config.test.d.ts.map +0 -1
  87. package/dist/dts/utils/resolve-preference-baseline.test.d.ts +0 -2
  88. package/dist/dts/utils/resolve-preference-baseline.test.d.ts.map +0 -1
  89. package/dist/dts/utils/strip-agent-handlers.test.d.ts +0 -2
  90. package/dist/dts/utils/strip-agent-handlers.test.d.ts.map +0 -1
  91. package/dist/dts/utils/sum-costs.test.d.ts +0 -2
  92. package/dist/dts/utils/sum-costs.test.d.ts.map +0 -1
  93. package/dist/dts/utils/sum-tokens.test.d.ts +0 -2
  94. package/dist/dts/utils/sum-tokens.test.d.ts.map +0 -1
  95. package/dist/dts/utils/sum-usage.test.d.ts +0 -2
  96. package/dist/dts/utils/sum-usage.test.d.ts.map +0 -1
  97. package/dist/dts/utils/usage-rows.test.d.ts +0 -2
  98. package/dist/dts/utils/usage-rows.test.d.ts.map +0 -1
  99. package/dist/esm/components/chat-driver/chat-driver.compact.test.js +0 -93
  100. package/dist/esm/components/chat-driver/chat-driver.invocation-scope.test.js +0 -238
  101. package/dist/esm/components/chat-driver/chat-driver.test.js +0 -2770
  102. package/dist/esm/components/chat-driver/chat-driver.thinking-policy.test.js +0 -137
  103. package/dist/esm/components/chat-driver/chat-driver.trace-capture.test.js +0 -200
  104. package/dist/esm/components/chat-driver/chat-driver.turn-usage.test.js +0 -268
  105. package/dist/esm/components/chat-interaction-wrapper/chat-interaction-wrapper.test.js +0 -104
  106. package/dist/esm/components/orchestrating-driver/orchestrating-driver.budget.test.js +0 -312
  107. package/dist/esm/components/orchestrating-driver/orchestrating-driver.cost.test.js +0 -76
  108. package/dist/esm/components/orchestrating-driver/orchestrating-driver.pin.test.js +0 -61
  109. package/dist/esm/components/settings-modal/settings-modal.styles.test.js +0 -80
  110. package/dist/esm/components/settings-modal/settings-modal.template.test.js +0 -91
  111. package/dist/esm/config/define-stateful-agent.test.js +0 -53
  112. package/dist/esm/config/validate-providers.test.js +0 -100
  113. package/dist/esm/main/blocked-state.test.js +0 -1065
  114. package/dist/esm/main/budget-meter.test.js +0 -359
  115. package/dist/esm/main/cost-session-banking.test.js +0 -538
  116. package/dist/esm/main/file-attachments.test.js +0 -164
  117. package/dist/esm/main/interaction-cost.test.js +0 -107
  118. package/dist/esm/main/main.styles.test.js +0 -161
  119. package/dist/esm/main/persistence-broken-sources.test.js +0 -180
  120. package/dist/esm/main/popout-interaction-gate.test.js +0 -54
  121. package/dist/esm/state/ai-assistant-slice.test.js +0 -229
  122. package/dist/esm/state/debug-event-log.test.js +0 -117
  123. package/dist/esm/state/driver-registry.test.js +0 -55
  124. package/dist/esm/state/interaction-context.test.js +0 -78
  125. package/dist/esm/state/persistence/diagnostics-cursors.test.js +0 -30
  126. package/dist/esm/state/persistence/diagnostics.test.js +0 -280
  127. package/dist/esm/state/persistence/session-persistence-provider.test.js +0 -110
  128. package/dist/esm/state/persistence/session-persistence.integration.test.js +0 -113
  129. package/dist/esm/state/persistence/session-persister.test.js +0 -613
  130. package/dist/esm/state/persistence/session-snapshot.test.js +0 -256
  131. package/dist/esm/state/persistence/stateful-restore.e2e.test.js +0 -158
  132. package/dist/esm/utils/animation-exclusivity.test.js +0 -52
  133. package/dist/esm/utils/collect-session-models.test.js +0 -53
  134. package/dist/esm/utils/condense-history.test.js +0 -547
  135. package/dist/esm/utils/cost-session-history.test.js +0 -174
  136. package/dist/esm/utils/derive-cost-session-title.test.js +0 -37
  137. package/dist/esm/utils/flatten-sub-agent-messages.test.js +0 -139
  138. package/dist/esm/utils/history-transform.test.js +0 -158
  139. package/dist/esm/utils/message-partition.test.js +0 -90
  140. package/dist/esm/utils/resolve-cost-history-config.test.js +0 -32
  141. package/dist/esm/utils/resolve-preference-baseline.test.js +0 -69
  142. package/dist/esm/utils/strip-agent-handlers.test.js +0 -81
  143. package/dist/esm/utils/sum-costs.test.js +0 -114
  144. package/dist/esm/utils/sum-tokens.test.js +0 -51
  145. package/dist/esm/utils/sum-usage.test.js +0 -164
  146. package/dist/esm/utils/usage-rows.test.js +0 -189
  147. package/dist/tsconfig.tsbuildinfo +0 -1
  148. package/license.txt +0 -46
  149. package/sandbox/tsconfig.json +0 -8
  150. package/src/channel/ai-activity-bus.ts +0 -209
  151. package/src/channel/ai-activity-channel.ts +0 -146
  152. package/src/chat-driver-node.ts +0 -159
  153. package/src/components/activity-halo/activity-halo.ts +0 -106
  154. package/src/components/agent-picker/agent-picker.constants.ts +0 -2
  155. package/src/components/agent-picker/agent-picker.styles.ts +0 -24
  156. package/src/components/agent-picker/agent-picker.template.ts +0 -54
  157. package/src/components/agent-picker/agent-picker.ts +0 -99
  158. package/src/components/agent-picker/index.ts +0 -1
  159. package/src/components/ai-driver/ai-driver.ts +0 -166
  160. package/src/components/ai-driver/index.ts +0 -1
  161. package/src/components/chat-bubble/chat-bubble.styles.ts +0 -194
  162. package/src/components/chat-bubble/chat-bubble.template.ts +0 -67
  163. package/src/components/chat-bubble/chat-bubble.ts +0 -436
  164. package/src/components/chat-bubble/index.ts +0 -1
  165. package/src/components/chat-driver/align-event-globals.ts +0 -23
  166. package/src/components/chat-driver/chat-driver.compact.test.ts +0 -131
  167. package/src/components/chat-driver/chat-driver.invocation-scope.test.ts +0 -285
  168. package/src/components/chat-driver/chat-driver.test.ts +0 -3679
  169. package/src/components/chat-driver/chat-driver.thinking-policy.test.ts +0 -185
  170. package/src/components/chat-driver/chat-driver.trace-capture.test.ts +0 -251
  171. package/src/components/chat-driver/chat-driver.ts +0 -3925
  172. package/src/components/chat-driver/chat-driver.turn-usage.test.ts +0 -362
  173. package/src/components/chat-driver/index.ts +0 -1
  174. package/src/components/chat-interaction-wrapper/chat-interaction-wrapper.styles.ts +0 -7
  175. package/src/components/chat-interaction-wrapper/chat-interaction-wrapper.template.ts +0 -6
  176. package/src/components/chat-interaction-wrapper/chat-interaction-wrapper.test.ts +0 -144
  177. package/src/components/chat-interaction-wrapper/chat-interaction-wrapper.ts +0 -243
  178. package/src/components/chat-interaction-wrapper/index.ts +0 -3
  179. package/src/components/chat-markdown/chat-markdown.ts +0 -89
  180. package/src/components/chat-markdown/index.ts +0 -1
  181. package/src/components/flowing-waves-indicator.ts +0 -286
  182. package/src/components/halo-overlay.ts +0 -184
  183. package/src/components/orchestrating-driver/index.ts +0 -1
  184. package/src/components/orchestrating-driver/orchestrating-driver.budget.test.ts +0 -438
  185. package/src/components/orchestrating-driver/orchestrating-driver.cost.test.ts +0 -107
  186. package/src/components/orchestrating-driver/orchestrating-driver.pin.test.ts +0 -96
  187. package/src/components/orchestrating-driver/orchestrating-driver.ts +0 -813
  188. package/src/components/plasma-orb-indicator.ts +0 -281
  189. package/src/components/popout-manager/index.ts +0 -1
  190. package/src/components/popout-manager/popout-manager.ts +0 -212
  191. package/src/components/settings-modal/index.ts +0 -2
  192. package/src/components/settings-modal/settings-modal.styles.test.ts +0 -94
  193. package/src/components/settings-modal/settings-modal.styles.ts +0 -853
  194. package/src/components/settings-modal/settings-modal.template.test.ts +0 -124
  195. package/src/components/settings-modal/settings-modal.template.ts +0 -943
  196. package/src/components/waves-indicator.ts +0 -241
  197. package/src/config/config.ts +0 -608
  198. package/src/config/define-stateful-agent.test.ts +0 -64
  199. package/src/config/define-stateful-agent.ts +0 -642
  200. package/src/config/fallback-agents.ts +0 -29
  201. package/src/config/index.ts +0 -2
  202. package/src/config/validate-providers.test.ts +0 -148
  203. package/src/config/validate-providers.ts +0 -58
  204. package/src/index.ts +0 -29
  205. package/src/main/blocked-state.test.ts +0 -1444
  206. package/src/main/budget-meter.test.ts +0 -477
  207. package/src/main/cost-session-banking.test.ts +0 -679
  208. package/src/main/file-attachments.test.ts +0 -215
  209. package/src/main/index.ts +0 -3
  210. package/src/main/interaction-cost.test.ts +0 -140
  211. package/src/main/main.styles.test.ts +0 -216
  212. package/src/main/main.styles.ts +0 -995
  213. package/src/main/main.template.ts +0 -1119
  214. package/src/main/main.ts +0 -5432
  215. package/src/main/main.types.ts +0 -225
  216. package/src/main/persistence-broken-sources.test.ts +0 -219
  217. package/src/main/popout-interaction-gate.test.ts +0 -87
  218. package/src/provider/ai-provider-switcher.ts +0 -40
  219. package/src/provider/assistant-app-settings.ts +0 -84
  220. package/src/state/ai-assistant-slice.test.ts +0 -336
  221. package/src/state/ai-assistant-slice.ts +0 -604
  222. package/src/state/debug-event-log.test.ts +0 -154
  223. package/src/state/debug-event-log.ts +0 -358
  224. package/src/state/driver-registry.test.ts +0 -70
  225. package/src/state/driver-registry.ts +0 -79
  226. package/src/state/interaction-context.test.ts +0 -90
  227. package/src/state/interaction-context.ts +0 -101
  228. package/src/state/persistence/build-timeline-entries.ts +0 -66
  229. package/src/state/persistence/diagnostics-cursors.test.ts +0 -53
  230. package/src/state/persistence/diagnostics-cursors.ts +0 -80
  231. package/src/state/persistence/diagnostics.test.ts +0 -346
  232. package/src/state/persistence/diagnostics.ts +0 -220
  233. package/src/state/persistence/index.ts +0 -10
  234. package/src/state/persistence/persister-registry.ts +0 -55
  235. package/src/state/persistence/session-persistence-provider.test.ts +0 -136
  236. package/src/state/persistence/session-persistence-provider.ts +0 -226
  237. package/src/state/persistence/session-persistence.integration.test.ts +0 -141
  238. package/src/state/persistence/session-persister.test.ts +0 -787
  239. package/src/state/persistence/session-persister.ts +0 -917
  240. package/src/state/persistence/session-snapshot.test.ts +0 -352
  241. package/src/state/persistence/session-snapshot.ts +0 -344
  242. package/src/state/persistence/stateful-restore.e2e.test.ts +0 -229
  243. package/src/state/session-store.ts +0 -56
  244. package/src/styles/ai-colours.ts +0 -30
  245. package/src/styles/menu.ts +0 -187
  246. package/src/styles/settings-section.ts +0 -40
  247. package/src/suggestions/chat-suggestions.ts +0 -186
  248. package/src/tags/index.ts +0 -1
  249. package/src/types/ai-chat-widget.ts +0 -67
  250. package/src/types/interaction-context.ts +0 -82
  251. package/src/utils/animated-panel-toggle.ts +0 -62
  252. package/src/utils/animation-exclusivity.test.ts +0 -72
  253. package/src/utils/animation-exclusivity.ts +0 -40
  254. package/src/utils/banked-usage-baselines.ts +0 -90
  255. package/src/utils/collect-session-models.test.ts +0 -82
  256. package/src/utils/collect-session-models.ts +0 -65
  257. package/src/utils/condense-history.test.ts +0 -746
  258. package/src/utils/condense-history.ts +0 -448
  259. package/src/utils/cost-session-history.test.ts +0 -226
  260. package/src/utils/cost-session-history.ts +0 -218
  261. package/src/utils/derive-cost-session-title.test.ts +0 -63
  262. package/src/utils/derive-cost-session-title.ts +0 -76
  263. package/src/utils/flatten-sub-agent-messages.test.ts +0 -163
  264. package/src/utils/flatten-sub-agent-messages.ts +0 -88
  265. package/src/utils/format-usd.ts +0 -26
  266. package/src/utils/history-transform.test.ts +0 -228
  267. package/src/utils/history-transform.ts +0 -163
  268. package/src/utils/index.ts +0 -3
  269. package/src/utils/logger.ts +0 -3
  270. package/src/utils/message-partition.test.ts +0 -129
  271. package/src/utils/message-partition.ts +0 -115
  272. package/src/utils/resolve-cost-history-config.test.ts +0 -38
  273. package/src/utils/resolve-cost-history-config.ts +0 -38
  274. package/src/utils/resolve-preference-baseline.test.ts +0 -84
  275. package/src/utils/resolve-preference-baseline.ts +0 -80
  276. package/src/utils/strip-agent-handlers.test.ts +0 -99
  277. package/src/utils/strip-agent-handlers.ts +0 -53
  278. package/src/utils/sum-costs.test.ts +0 -136
  279. package/src/utils/sum-costs.ts +0 -18
  280. package/src/utils/sum-tokens.test.ts +0 -93
  281. package/src/utils/sum-tokens.ts +0 -68
  282. package/src/utils/sum-usage.test.ts +0 -191
  283. package/src/utils/sum-usage.ts +0 -208
  284. package/src/utils/tool-fold.ts +0 -181
  285. package/src/utils/usage-rows.test.ts +0 -237
  286. package/src/utils/usage-rows.ts +0 -187
  287. package/src/utils/with-timeout.ts +0 -24
  288. package/tsconfig.json +0 -9
@@ -1,186 +0,0 @@
1
- import {
2
- attr,
3
- customElement,
4
- GenesisElement,
5
- observable,
6
- html,
7
- css,
8
- repeat,
9
- when,
10
- } from '@genesislcap/web-core';
11
- import type { SuggestionsState } from '../main/main.types';
12
-
13
- type LoadedState = { status: 'loaded'; suggestions: string[] };
14
-
15
- const PILL_ANIMATION_STAGGER_MS = 70;
16
-
17
- const template = html<ChatSuggestions>`
18
- ${when(
19
- (x) => x.state.status === 'loading',
20
- html<ChatSuggestions>`
21
- <div class="suggestions-container">
22
- ${repeat(
23
- () => Array.from({ length: 3 }),
24
- html`
25
- <div class="suggestion-placeholder"></div>
26
- `,
27
- )}
28
- </div>
29
- `,
30
- )}
31
- ${when(
32
- (x) => x.state.status === 'loaded',
33
- html<ChatSuggestions>`
34
- <div class="suggestions-container">
35
- ${repeat(
36
- (x) => (x.state as LoadedState).suggestions,
37
- html<string, ChatSuggestions>`
38
- <button
39
- class="suggestion-pill"
40
- style="animation-delay: ${(_, c) => c.index * PILL_ANIMATION_STAGGER_MS}ms"
41
- @click="${(suggestion, c) => c.parent.handleSuggestionClick(suggestion)}"
42
- >
43
- ${(suggestion) => suggestion}
44
- </button>
45
- `,
46
- )}
47
- </div>
48
- `,
49
- )}
50
- `;
51
-
52
- const styles = css`
53
- :host {
54
- /* Collapsed unless there is something to render (see hasContent), so the empty
55
- host never occupies space or shows through a consumer's border as a stray
56
- line. */
57
- display: none;
58
- }
59
-
60
- :host([has-content]) {
61
- display: block;
62
- }
63
-
64
- .suggestions-container {
65
- display: flex;
66
- flex-wrap: wrap;
67
- align-items: center;
68
- gap: 6px;
69
- padding: calc(var(--design-unit) * 2px) calc(var(--design-unit) * 3px);
70
- background-color: var(--neutral-layer-2);
71
- animation: suggestions-appear 0.2s ease-out both;
72
- }
73
-
74
- @keyframes suggestions-appear {
75
- from {
76
- opacity: 0;
77
- transform: translateY(6px);
78
- }
79
-
80
- to {
81
- opacity: 1;
82
- transform: translateY(0);
83
- }
84
- }
85
-
86
- .suggestion-pill {
87
- display: inline-flex;
88
- align-items: center;
89
- padding: 5px 14px;
90
- background: color-mix(in srgb, var(--accent-fill-rest) 10%, var(--neutral-layer-3));
91
- color: var(--neutral-foreground-rest);
92
- border: 1px solid color-mix(in srgb, var(--accent-fill-rest) 25%, var(--neutral-stroke-rest));
93
- border-radius: 20px;
94
- font-family: var(--body-font);
95
- font-size: 0.85em;
96
- cursor: pointer;
97
- transition:
98
- background 0.15s ease,
99
- border-color 0.15s ease,
100
- transform 0.15s ease,
101
- box-shadow 0.15s ease;
102
- animation: pill-appear 0.3s ease-out both;
103
- max-width: 100%;
104
- white-space: normal;
105
- text-align: left;
106
- }
107
-
108
- .suggestion-pill:hover {
109
- background: color-mix(in srgb, var(--accent-fill-rest) 20%, var(--neutral-layer-3));
110
- border-color: color-mix(in srgb, var(--accent-fill-rest) 50%, var(--neutral-stroke-rest));
111
- transform: translateY(-1px);
112
- box-shadow: 0 2px 8px color-mix(in srgb, var(--accent-fill-rest) 20%, transparent);
113
- }
114
-
115
- .suggestion-pill:active {
116
- transform: translateY(0);
117
- box-shadow: none;
118
- }
119
-
120
- @keyframes pill-appear {
121
- from {
122
- opacity: 0;
123
- transform: scale(0.88) translateY(4px);
124
- }
125
-
126
- to {
127
- opacity: 1;
128
- transform: scale(1) translateY(0);
129
- }
130
- }
131
-
132
- .suggestion-placeholder {
133
- height: 30px;
134
- width: 110px;
135
- background: var(--neutral-layer-3);
136
- border-radius: 20px;
137
- animation: placeholder-pulse 1.6s ease-in-out infinite;
138
- }
139
-
140
- .suggestion-placeholder:nth-child(2) {
141
- width: 150px;
142
- animation-delay: 0.25s;
143
- }
144
-
145
- .suggestion-placeholder:nth-child(3) {
146
- width: 130px;
147
- animation-delay: 0.5s;
148
- }
149
-
150
- @keyframes placeholder-pulse {
151
- 0%,
152
- 100% {
153
- opacity: 0.35;
154
- }
155
-
156
- 50% {
157
- opacity: 0.65;
158
- }
159
- }
160
- `;
161
-
162
- @customElement({
163
- name: 'chat-suggestions',
164
- template,
165
- styles,
166
- })
167
- export class ChatSuggestions extends GenesisElement {
168
- @observable
169
- state: SuggestionsState = { status: 'idle' };
170
-
171
- /**
172
- * Reflected to a `has-content` attribute so the host can collapse when there is
173
- * nothing to render. True only while loading or when loaded with at least one
174
- * suggestion; idle, errored, and empty-loaded states render nothing.
175
- */
176
- @attr({ attribute: 'has-content', mode: 'boolean' }) hasContent = false;
177
-
178
- stateChanged(): void {
179
- const s = this.state;
180
- this.hasContent = s.status === 'loading' || (s.status === 'loaded' && s.suggestions.length > 0);
181
- }
182
-
183
- handleSuggestionClick(suggestion: string): void {
184
- this.$emit('suggestion-clicked', suggestion);
185
- }
186
- }
package/src/tags/index.ts DELETED
@@ -1 +0,0 @@
1
- export {};
@@ -1,67 +0,0 @@
1
- import type { InteractionResult } from '@genesislcap/foundation-ai';
2
- import type { InteractionContext } from './interaction-context';
3
-
4
- /**
5
- * The two mutually-exclusive states an interaction widget is ever in (GENC-1390).
6
- * Modelling them as a discriminated union makes illegal combinations
7
- * unrepresentable — a resolved widget cannot carry a live `interactionContext` or
8
- * a `shouldAutoFocus`, and a pending widget cannot be read for a `result`.
9
- *
10
- * The host sets the widget's `interaction` on mount and REPLACES it (pending →
11
- * resolved) on completion.
12
- *
13
- * @beta
14
- */
15
- export type AiChatInteraction<TData = unknown, TResult = unknown> =
16
- | {
17
- status: 'pending';
18
- /** Immutable inputs for the interaction. */
19
- data: TData;
20
- /**
21
- * Grab focus on mount? Meaningful ONLY while pending — a resolved widget is a
22
- * history re-render and must never steal focus.
23
- */
24
- shouldAutoFocus: boolean;
25
- /**
26
- * Live, remount-surviving scratch for this interaction. Present ONLY while
27
- * pending — the host disposes it on resolve. See {@link InteractionContext}.
28
- */
29
- interactionContext: InteractionContext;
30
- }
31
- | {
32
- status: 'resolved';
33
- /** Same inputs — the resolved view still reads them. */
34
- data: TData;
35
- /** The interaction outcome. */
36
- result: InteractionResult<TResult>;
37
- };
38
-
39
- /**
40
- * Interface that AI inline interaction components implement. The
41
- * `AiChatInteractionWrapper` sets `interactionId` and `interaction` on the
42
- * rendered element on every (re)mount, and replaces `interaction` (pending →
43
- * resolved) when the interaction completes.
44
- *
45
- * @remarks
46
- * GENC-1390 replaced the previous flat `data` / `resolved` / `shouldAutoFocus`
47
- * surface with the discriminated {@link AiChatInteraction}. This is a breaking
48
- * change on this `@beta` interface — widgets read `this.interaction` (narrowing on
49
- * `status`) instead of the removed flat fields.
50
- *
51
- * @remarks
52
- * A widget talks back by dispatching two events, both caught by the wrapper:
53
- * `interaction-completed` resolves the interaction (once — a second is ignored), and
54
- * `interaction-cost` reports a positive USD delta for paid work done AFTER resolving,
55
- * which repeated calls sum. The second exists for widgets that resolve early so the
56
- * assistant stops waiting, then keep working for the user; without it that spend is
57
- * billed but missing from the total the user sees.
58
- *
59
- * @beta
60
- */
61
- export interface AiChatWidget<TData = unknown, TResult = unknown> {
62
- /** Stable per-interaction UUID; constant across remounts, present in both states. */
63
- interactionId: string;
64
-
65
- /** The current interaction state (pending or resolved). */
66
- interaction: AiChatInteraction<TData, TResult>;
67
- }
@@ -1,82 +0,0 @@
1
- /**
2
- * A live, per-interaction scratch space owned by the chat host (keyed by
3
- * `interactionId`) and handed to a widget on every (re)mount.
4
- *
5
- * WHY: an interaction widget element is destroyed+recreated on layout reflow
6
- * (GoldenLayout rebuilds/reparents on panel add/remove AND on drag), on
7
- * dock/undock, and on transcript re-render (the keyless `repeat`). The element
8
- * cannot hold anything live across that; this object can, because the host keeps
9
- * it alive for the interaction's lifetime, independent of the element. (GENC-1390)
10
- *
11
- * LIVE-ONLY — never persisted. A reload / project switch / session restore wipes
12
- * it; the interaction is then rebuilt purely from the serialisable `data` +
13
- * `result`. Never store here anything that must survive a reload: durable inputs
14
- * belong in the interaction's `data`, the outcome in its resolved payload.
15
- *
16
- * LIFETIME — created lazily when the interaction is requested, reused for every
17
- * remount of that same `interactionId`. Torn down (its `onDispose` callbacks fire
18
- * once) when the interaction RESOLVES or is evicted — NOT when the element
19
- * unmounts. That inversion is the point: a transient unmount (drag, re-render)
20
- * must leave context-owned resources (sockets, timers) untouched; only completion
21
- * ends them.
22
- *
23
- * NOTE: `subscribe`/`notify` are a plain observer list, not a reactivity engine
24
- * and not a FAST dependency. `notify()` renders nothing — it signals the
25
- * currently-mounted element "state changed, re-read", and the element updates its
26
- * own observables and re-renders with whatever primitives it has.
27
- *
28
- * @beta
29
- */
30
- export interface InteractionContext<TState extends object = Record<string, unknown>> {
31
- /** Stable id of the interaction this context belongs to. */
32
- readonly interactionId: string;
33
-
34
- /**
35
- * Durable, remount-surviving source of truth for the interaction — a socket
36
- * handle, the current phase, a half-typed draft. A plain object, deliberately
37
- * NOT reactive: a widget renders from its own observables and reads/writes this
38
- * on mount and on change so the next remount can recover.
39
- */
40
- readonly state: TState;
41
-
42
- /**
43
- * Run `factory` at most once for this interaction's lifetime and memoise the
44
- * result; every later call with the same `key` (including after a remount)
45
- * returns the stored value without re-running. Covers one-shot side effects and
46
- * create-once live resources alike:
47
- *
48
- * ```ts
49
- * ctx.once('open-panel', () => this.dispatchEvent(openPanelEvent)); // fires once, ever
50
- * const ws = ctx.once('ws', () => new TestDataWebSocketService()); // same instance each remount
51
- * ```
52
- *
53
- * Returns `null` once the interaction has ended (resolve/timeout/evict): the
54
- * factory is NOT run, because a resource created on a disposed context would
55
- * never be torn down (its `onDispose` has already fired). A widget that opens a
56
- * live resource here must guard on the resolved state and stop touching `once`
57
- * when the interaction completes — a stray late call (e.g. an async callback
58
- * landing after a `timeoutMs` timeout) gets `null` rather than a leaked resource.
59
- */
60
- once<T>(key: string, factory: () => T): T | null;
61
-
62
- /**
63
- * Signal that `state` changed so subscribers re-read. NOT a renderer —
64
- * just a change ping; the subscribing element updates its own observables.
65
- */
66
- notify(): void;
67
-
68
- /**
69
- * Observe `state` changes. Call in `connectedCallback`; call the returned
70
- * unsubscribe in `disconnectedCallback`.
71
- */
72
- subscribe(listener: () => void): () => void;
73
-
74
- /**
75
- * Register a teardown run exactly once when the interaction ends (resolve or
76
- * evict) — the correct home for closing sockets/timers created via `once`.
77
- * Decoupled from the element's `disconnectedCallback`, which fires on every
78
- * transient unmount and must NOT tear these down. Registering after the context
79
- * has already been disposed runs `fn` immediately.
80
- */
81
- onDispose(fn: () => void): void;
82
- }
@@ -1,62 +0,0 @@
1
- /**
2
- * Drives the open/close lifecycle of a slide-out panel inside a component's
3
- * shadow root, with a tracked close timer instead of an `animationend`
4
- * listener so the panel cannot wedge open if it's unmounted mid-animation
5
- * (pop-in/pop-out, dock change, host disconnect).
6
- *
7
- * @internal
8
- */
9
- export class AnimatedPanelToggle {
10
- private timer?: ReturnType<typeof setTimeout>;
11
-
12
- constructor(
13
- private readonly host: HTMLElement,
14
- private readonly selector: string,
15
- private readonly closeDurationMs: number,
16
- private readonly getOpen: () => boolean,
17
- private readonly setOpen: (v: boolean) => void,
18
- ) {}
19
-
20
- private queryPanel(): HTMLElement | null {
21
- return this.host.shadowRoot?.querySelector(this.selector) as HTMLElement | null;
22
- }
23
-
24
- toggle(): void {
25
- if (this.getOpen()) {
26
- // Capture the panel reference now so the timer callback strips
27
- // `closing` from the same node, regardless of any later remounts.
28
- // Leftover `closing` is what breaks the next open: with
29
- // `animation-fill-mode: forwards` the slide-out keyframe pins the panel
30
- // to opacity 0, so a remount looks like a no-op.
31
- const panel = this.queryPanel();
32
- panel?.classList.add('closing');
33
- if (this.timer != null) clearTimeout(this.timer);
34
- this.timer = setTimeout(() => {
35
- this.timer = undefined;
36
- panel?.classList.remove('closing');
37
- this.setOpen(false);
38
- }, this.closeDurationMs);
39
- } else {
40
- // Re-opened mid-close: cancel the pending flip and strip the closing
41
- // class so the panel snaps back to the open state.
42
- if (this.timer != null) {
43
- clearTimeout(this.timer);
44
- this.timer = undefined;
45
- this.queryPanel()?.classList.remove('closing');
46
- }
47
- this.setOpen(true);
48
- }
49
- }
50
-
51
- /**
52
- * Synchronously finalise an in-flight close — call from
53
- * `disconnectedCallback` so the closed state lands in the store before any
54
- * session ref is cleared.
55
- */
56
- finalize(): void {
57
- if (this.timer == null) return;
58
- clearTimeout(this.timer);
59
- this.timer = undefined;
60
- this.setOpen(false);
61
- }
62
- }
@@ -1,72 +0,0 @@
1
- import { assert, createLogicSuite } from '@genesislcap/foundation-testing';
2
- import type { AiAssistantAnimation } from '../main/main.types';
3
- import { resolveExclusiveLoadingStyle } from './animation-exclusivity';
4
-
5
- const suite = createLogicSuite('resolveExclusiveLoadingStyle');
6
-
7
- suite('leaves a selection with only the dots loading style untouched', () => {
8
- const next: AiAssistantAnimation[] = ['loading', 'halo'];
9
- assert.equal(resolveExclusiveLoadingStyle(next, ['halo']), ['loading', 'halo']);
10
- });
11
-
12
- suite('leaves a selection with only the waves loading style untouched', () => {
13
- const next: AiAssistantAnimation[] = ['waves', 'halo'];
14
- assert.equal(resolveExclusiveLoadingStyle(next, ['halo']), ['waves', 'halo']);
15
- });
16
-
17
- suite('leaves a selection with no loading style untouched', () => {
18
- assert.equal(resolveExclusiveLoadingStyle(['halo'], []), ['halo']);
19
- });
20
-
21
- suite('drops dots when waves was just added on top of dots', () => {
22
- // Previously dots was on; user ticks waves → waves wins, dots removed.
23
- const result = resolveExclusiveLoadingStyle(['loading', 'waves', 'halo'], ['loading', 'halo']);
24
- assert.equal(result, ['waves', 'halo']);
25
- });
26
-
27
- suite('drops waves when dots was just added on top of waves', () => {
28
- // Previously waves was on; user ticks dots → dots wins, waves removed.
29
- const result = resolveExclusiveLoadingStyle(['loading', 'waves', 'halo'], ['waves', 'halo']);
30
- assert.equal(result, ['loading', 'halo']);
31
- });
32
-
33
- suite('preserves the order of the surviving selection', () => {
34
- const result = resolveExclusiveLoadingStyle(['halo', 'loading', 'waves'], ['halo', 'loading']);
35
- // waves was just added → loading dropped; halo and waves keep their order.
36
- assert.equal(result, ['halo', 'waves']);
37
- });
38
-
39
- suite('falls back to dots when both are present with no prior state (e.g. bad config)', () => {
40
- const result = resolveExclusiveLoadingStyle(['loading', 'waves', 'halo'], []);
41
- assert.equal(result, ['loading', 'halo']);
42
- });
43
-
44
- suite('falls back to dots when both were already present (neither newly added)', () => {
45
- const result = resolveExclusiveLoadingStyle(['loading', 'waves'], ['loading', 'waves', 'halo']);
46
- assert.equal(result, ['loading']);
47
- });
48
-
49
- suite('handles an empty selection', () => {
50
- assert.equal(resolveExclusiveLoadingStyle([], ['loading']), []);
51
- });
52
-
53
- suite('switching between any two loading styles keeps only the newly added one', () => {
54
- // flowingWaves was on; user picks plasma → plasma wins, flowingWaves dropped.
55
- assert.equal(
56
- resolveExclusiveLoadingStyle(['flowingWaves', 'plasma', 'halo'], ['flowingWaves', 'halo']),
57
- ['plasma', 'halo'],
58
- );
59
- // plasma was on; user picks the dots → dots win, plasma dropped.
60
- assert.equal(resolveExclusiveLoadingStyle(['plasma', 'loading'], ['plasma']), ['loading']);
61
- });
62
-
63
- suite('drops all but the newly added style when several are somehow selected', () => {
64
- const result = resolveExclusiveLoadingStyle(
65
- ['loading', 'waves', 'flowingWaves', 'plasma'],
66
- ['loading', 'waves', 'plasma'],
67
- );
68
- // flowingWaves is the only one absent from `previous`, so it wins.
69
- assert.equal(result, ['flowingWaves']);
70
- });
71
-
72
- suite.run();
@@ -1,40 +0,0 @@
1
- import type { AiAssistantAnimation } from '../main/main.types';
2
- import { LOADING_STYLE_ANIMATIONS } from '../main/main.types';
3
-
4
- /**
5
- * Enforces that at most one loading-indicator style (dots vs. waves) is enabled
6
- * at a time. The two are alternative presentations of the same "assistant is
7
- * working" state, so selecting one must deselect the other.
8
- *
9
- * The settings control is a multiselect (checkboxes), which permits selecting
10
- * both; this resolver makes the loading-style group behave like a radio group
11
- * by dropping the previously-selected style whenever a new one is added.
12
- *
13
- * @param next - The freshly-selected animation list (e.g. emitted by the
14
- * multiselect, or read from consumer config).
15
- * @param previous - The animation list in effect before this change. Used to
16
- * work out which loading style was just added so the other can be dropped.
17
- * Pass `[]` when resolving an initial/config value with no prior state.
18
- * @returns `next` with at most one loading style retained. When both are
19
- * present and neither is newly added (e.g. a misconfigured `enabled` list),
20
- * the first entry of `LOADING_STYLE_ANIMATIONS` (dots) wins.
21
- *
22
- * @internal
23
- */
24
- export function resolveExclusiveLoadingStyle(
25
- next: AiAssistantAnimation[],
26
- previous: AiAssistantAnimation[] = [],
27
- ): AiAssistantAnimation[] {
28
- const selectedStyles = LOADING_STYLE_ANIMATIONS.filter((style) => next.includes(style));
29
- if (selectedStyles.length <= 1) {
30
- return next;
31
- }
32
- // Both styles selected — keep whichever was just added (absent from
33
- // `previous`); fall back to the first declared style on ambiguity.
34
- const justAdded = selectedStyles.find((style) => !previous.includes(style)) ?? selectedStyles[0];
35
- return next.filter(
36
- (animation) =>
37
- animation === justAdded ||
38
- !(LOADING_STYLE_ANIMATIONS as readonly AiAssistantAnimation[]).includes(animation),
39
- );
40
- }
@@ -1,90 +0,0 @@
1
- import type { AggregateUsage } from '@genesislcap/foundation-ai';
2
-
3
- /**
4
- * Per-session banked baselines for the cost ledger (GENC-1475) — the spend a project had
5
- * accrued *before* the transcript currently in memory. Module-level and keyed by the session
6
- * `stateKey`, exactly like the diagnostics cursors and the meta-event buffer.
7
- *
8
- * The lifetime is the point of this module. A baseline is only meaningful paired with the
9
- * transcript it complements: `row.usage = baseline + whatever that transcript proves`. The
10
- * transcript lives in the per-key session store, so the baseline must live and die with that
11
- * store — which means module state keyed the same way, NOT element state.
12
- *
13
- * Held on the element it silently double-counted. Element instances are created and destroyed
14
- * many times inside one session (the assistant is remounted as the host re-renders its layout,
15
- * and a popped-out bubble and a docked panel can be wired to the same session at once), and each
16
- * new instance re-derived the baseline from the ledger rows. By then those rows contain the
17
- * instance-before-last's own upserts, whose `usage` already includes the live transcript — so
18
- * where the transcript is NOT restored on load (`resolveBankedUsage`'s "bank the whole prior
19
- * total" branch, i.e. persistence off) the live session was banked as its own baseline. Lifetime
20
- * then jumped by the whole session-to-date on every remount while the live figure kept climbing
21
- * normally, which is the "the two totals went up by different amounts" report.
22
- *
23
- * Module state dies with the JS realm, so a real page reload starts clean — exactly right, since
24
- * that is also when the transcript starts over.
25
- *
26
- * @internal
27
- */
28
- const baselinesByKey = new Map<string, AggregateUsage>();
29
-
30
- /**
31
- * The baseline resolved for `key`, or `undefined` when this session has not resolved one yet.
32
- *
33
- * `undefined` rather than an empty usage: "nothing banked" is a legitimate resolved value (a
34
- * project's first ever session), and it must not be mistaken for "not resolved", or every write
35
- * would re-derive and hit the double-count above.
36
- *
37
- * Returns a copy — see `setBankedBaseline` for why both ends of this map are cloned.
38
- *
39
- * @internal
40
- */
41
- export function getBankedBaseline(key: string): AggregateUsage | undefined {
42
- const usage = baselinesByKey.get(key);
43
- return usage ? { ...usage } : undefined;
44
- }
45
-
46
- /**
47
- * Record the baseline for `key`, replacing any previous one.
48
- *
49
- * Stores a copy, and `getBankedBaseline` hands back a copy, so nothing outside this module can
50
- * reach the object the map holds. Both ends have to clone, because a baseline is aliased at both:
51
- * `resolveBankedUsage` returns a *reference into a ledger row* (`row.banked` or `row.usage`),
52
- * which is what gets stored here, and the caller then assigns what it reads back onto the NEXT
53
- * row's `banked` field. Uncloned, one object is reachable from page-global state, two rows, and —
54
- * since the row list is public and gets handed to a host's `saveCostHistory` — from host code.
55
- * Whoever mutated it would silently rewrite this project's baseline for the rest of the page.
56
- *
57
- * A shallow spread is a complete copy: `AggregateUsage` is five flat numbers, no nested state.
58
- *
59
- * @internal
60
- */
61
- export function setBankedBaseline(key: string, usage: AggregateUsage): void {
62
- baselinesByKey.set(key, { ...usage });
63
- }
64
-
65
- /**
66
- * Drop the baseline for `key`, so the next write re-derives it from the ledger rows.
67
- *
68
- * For when the pairing is broken deliberately at the *row* end — the row this baseline came from
69
- * has been deleted, or the whole ledger cleared — and for a session teardown, where the store
70
- * (and with it the transcript) is disposed and a later return re-restores from scratch.
71
- *
72
- * NOT for a mid-session reload of the rows: those rows already include this session's own
73
- * upserts, so re-deriving from them folds the live transcript into its own baseline.
74
- *
75
- * @internal
76
- */
77
- export function deleteBankedBaseline(key: string): void {
78
- baselinesByKey.delete(key);
79
- }
80
-
81
- /**
82
- * Forget every baseline — the page-load boundary, which production gets for free from module
83
- * state dying with the realm. Exists so tests can model separate page loads (and so one test's
84
- * session key cannot leak a baseline into the next).
85
- *
86
- * @internal
87
- */
88
- export function resetBankedBaselines(): void {
89
- baselinesByKey.clear();
90
- }
@@ -1,82 +0,0 @@
1
- import type { AIProviderRegistryStatusEntry, ChatMessage } from '@genesislcap/foundation-ai';
2
- import { assert, createLogicSuite } from '@genesislcap/foundation-testing';
3
- import { collectSessionModels } from './collect-session-models';
4
-
5
- const assistantMsg = (overrides: Partial<ChatMessage> = {}): ChatMessage => ({
6
- role: 'assistant',
7
- content: '',
8
- ...overrides,
9
- });
10
-
11
- const statusEntry = (
12
- name: string,
13
- model: string,
14
- provider: AIProviderRegistryStatusEntry['status'] extends infer S
15
- ? S extends { provider: infer P }
16
- ? P
17
- : never
18
- : never,
19
- ): AIProviderRegistryStatusEntry => ({
20
- name,
21
- isDefault: false,
22
- status: { model, provider },
23
- });
24
-
25
- const suite = createLogicSuite('collectSessionModels');
26
-
27
- suite('collects unique models from assistant messages', () => {
28
- const models = collectSessionModels(
29
- [
30
- assistantMsg({ model: 'claude-sonnet-4-6' }),
31
- assistantMsg({ model: 'claude-haiku-4-5-20251001' }),
32
- assistantMsg({ model: 'claude-sonnet-4-6', provider: 'anthropic' }),
33
- assistantMsg({ model: 'gemini-2.5-pro', providerName: 'high', provider: 'gemini' }),
34
- ],
35
- [],
36
- );
37
- assert.is(models.length, 3);
38
- assert.is(models[0]?.model, 'claude-sonnet-4-6');
39
- assert.is(models[0]?.provider, 'anthropic');
40
- assert.is(models[2]?.model, 'gemini-2.5-pro');
41
- assert.is(models[2]?.provider, 'gemini');
42
- });
43
-
44
- suite('resolves vendor from providerStatuses when messages lack provider', () => {
45
- const models = collectSessionModels(
46
- [assistantMsg({ model: 'gemini-3.1-flash-lite', providerName: 'low' })],
47
- [statusEntry('low', 'gemini-3.1-flash-lite', 'gemini')],
48
- );
49
- assert.is(models.length, 1);
50
- assert.is(models[0]?.provider, 'gemini');
51
- });
52
-
53
- suite('does not persist registry tier names as provider', () => {
54
- const models = collectSessionModels(
55
- [assistantMsg({ model: 'gemini-3.1-flash-lite', providerName: 'low' })],
56
- [],
57
- );
58
- assert.is(models[0]?.provider, undefined);
59
- assert.is('providerName' in (models[0] ?? {}), false);
60
- });
61
-
62
- suite('includes models from sub-agent traces', () => {
63
- const models = collectSessionModels(
64
- [
65
- assistantMsg({
66
- toolCalls: [
67
- {
68
- id: 'tc1',
69
- name: 'spawn',
70
- args: {},
71
- subAgentTrace: [assistantMsg({ model: 'claude-haiku-4-5-20251001' })],
72
- },
73
- ],
74
- }),
75
- ],
76
- [],
77
- );
78
- assert.is(models.length, 1);
79
- assert.is(models[0]?.model, 'claude-haiku-4-5-20251001');
80
- });
81
-
82
- suite.run();