@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,191 +0,0 @@
1
- import type { ChatMessage } from '@genesislcap/foundation-ai';
2
- import { assert, createLogicSuite } from '@genesislcap/foundation-testing';
3
- import { addUsage, emptyUsage, messageUsage, sumUsage, totalTokens } from './sum-usage';
4
-
5
- const suite = createLogicSuite('sumUsage');
6
-
7
- const msg = (over: Partial<ChatMessage> = {}): ChatMessage =>
8
- ({ role: 'assistant', content: '', ...over }) as ChatMessage;
9
-
10
- suite('returns zeroes when no message carries usage', () => {
11
- const usage = sumUsage([msg({ role: 'user', content: 'hi' })]);
12
- assert.equal(usage, emptyUsage());
13
- assert.is(totalTokens(usage), 0);
14
- });
15
-
16
- suite('splits the prompt into three disjoint input buckets', () => {
17
- // `inputTokens` is the WHOLE prompt; the cache fields break it down. So uncached is
18
- // the remainder, and the three must re-add to the prompt exactly — that identity is
19
- // what stops the buckets double-counting the prompt when summed.
20
- const usage = sumUsage([
21
- msg({ inputTokens: 1000, cacheReadTokens: 700, cacheWriteTokens: 200, outputTokens: 50 }),
22
- ]);
23
- assert.is(usage.uncachedInputTokens, 100);
24
- assert.is(usage.cacheReadTokens, 700);
25
- assert.is(usage.cacheWriteTokens, 200);
26
- assert.is(usage.outputTokens, 50);
27
- assert.is(
28
- usage.uncachedInputTokens + usage.cacheReadTokens + usage.cacheWriteTokens,
29
- 1000,
30
- 'input buckets re-add to the prompt total',
31
- );
32
- assert.is(totalTokens(usage), 1050);
33
- });
34
-
35
- suite('treats the whole prompt as uncached when no cache split is reported', () => {
36
- // Providers that report no cache breakdown (and messages persisted before the fields
37
- // existed) must still contribute their full prompt, not drop it.
38
- const usage = sumUsage([msg({ inputTokens: 400, outputTokens: 60 })]);
39
- assert.is(usage.uncachedInputTokens, 400);
40
- assert.is(usage.cacheReadTokens, 0);
41
- assert.is(usage.cacheWriteTokens, 0);
42
- assert.is(totalTokens(usage), 460);
43
- });
44
-
45
- suite('clamps uncached input at zero when buckets exceed the prompt total', () => {
46
- // Malformed or hand-edited history must not be able to drive a cost display negative.
47
- const usage = sumUsage([
48
- msg({ inputTokens: 100, cacheReadTokens: 90, cacheWriteTokens: 90, outputTokens: 0 }),
49
- ]);
50
- assert.is(usage.uncachedInputTokens, 0);
51
- });
52
-
53
- suite('sums LLM cost and widget-reported external cost', () => {
54
- const usage = sumUsage([msg({ cost: 0.25 }), msg({ externalCostUsd: 0.1, cost: 0.05 })]);
55
- assert.is(Math.round(usage.costUsd * 100) / 100, 0.4);
56
- });
57
-
58
- suite('recurses into sub-agent traces', () => {
59
- // A sub-agent's whole conversation is embedded in the parent's tool call, so this is
60
- // the only place its spend is visible to a host total.
61
- const usage = sumUsage([
62
- msg({
63
- toolCalls: [
64
- {
65
- id: 'tc-1',
66
- name: 'delegate',
67
- args: {},
68
- subAgentTrace: [
69
- msg({ cost: 0.2, inputTokens: 300, cacheReadTokens: 100, outputTokens: 40 }),
70
- ],
71
- },
72
- ],
73
- }),
74
- ]);
75
- assert.is(usage.costUsd, 0.2);
76
- assert.is(usage.uncachedInputTokens, 200);
77
- assert.is(usage.cacheReadTokens, 100);
78
- assert.is(usage.outputTokens, 40);
79
- });
80
-
81
- suite('counts usage banked onto a compaction summary', () => {
82
- // Compaction destroys the messages it summarises. Without the banked rollup, a total
83
- // re-derived from the transcript would fall by whatever those turns had spent.
84
- const before = sumUsage([
85
- msg({ cost: 0.5, inputTokens: 1000, cacheReadTokens: 400, outputTokens: 100 }),
86
- msg({ cost: 0.3, inputTokens: 500, outputTokens: 50 }),
87
- ]);
88
- const afterCompaction = sumUsage([
89
- msg({
90
- role: 'compacted-summary',
91
- content: 'summary of the above',
92
- compaction: {
93
- compactedCount: 2,
94
- createdAt: '2026-06-14T12:00:00.000Z',
95
- rolledUpUsage: before,
96
- },
97
- }),
98
- ]);
99
- assert.equal(afterCompaction, before, 'compaction preserves the running total');
100
- });
101
-
102
- suite('ignores tokensBefore, which is a context reading rather than spend', () => {
103
- // `tokensBefore` is one turn's context-window size. Counting it as cumulative spend
104
- // would silently inflate the session total on every compaction.
105
- const usage = sumUsage([
106
- msg({
107
- role: 'compacted-summary',
108
- content: 'summary',
109
- compaction: {
110
- compactedCount: 3,
111
- createdAt: '2026-06-14T12:00:00.000Z',
112
- tokensBefore: 90_000,
113
- },
114
- }),
115
- ]);
116
- assert.is(totalTokens(usage), 0);
117
- });
118
-
119
- suite('addUsage adds every bucket and mutates neither operand', () => {
120
- const a = { ...emptyUsage(), costUsd: 1.5, uncachedInputTokens: 100, outputTokens: 10 };
121
- const b = { ...emptyUsage(), costUsd: 0.25, cacheReadTokens: 900, outputTokens: 5 };
122
- const sum = addUsage(a, b);
123
-
124
- assert.is(sum.costUsd, 1.75);
125
- assert.is(sum.uncachedInputTokens, 100);
126
- assert.is(sum.cacheReadTokens, 900);
127
- assert.is(sum.outputTokens, 15);
128
- // Both operands are live state (a row's banked figure and the session total), so a
129
- // caller must be able to add without either being altered underneath it.
130
- assert.is(a.costUsd, 1.5);
131
- assert.is(b.cacheReadTokens, 900);
132
- });
133
-
134
- suite('addUsage with an empty operand is the identity', () => {
135
- const a = { ...emptyUsage(), costUsd: 3, cacheWriteTokens: 42 };
136
- assert.equal(addUsage(a, emptyUsage()), a);
137
- assert.equal(addUsage(emptyUsage(), a), a);
138
- });
139
-
140
- suite('messageUsage is undefined for a message reporting nothing', () => {
141
- // A user turn, or a display-only reasoning/narration split. Distinct from a zeroed
142
- // usage: "this call reported nothing" is not "this call cost nothing".
143
- assert.is(messageUsage(msg({ role: 'user', content: 'hi' })), undefined);
144
- assert.is(messageUsage(msg({ content: 'thinking…', category: 'reasoning' })), undefined);
145
- });
146
-
147
- suite('messageUsage agrees with sumUsage over the same single message', () => {
148
- // The two derivations are deliberately separate — `accumulate` mutates one
149
- // accumulator rather than allocating per message — so pin them together. A drift
150
- // here would put one figure on a turn entry and a different one in the session
151
- // total, both looking plausible.
152
- const cases: Partial<ChatMessage>[] = [
153
- { inputTokens: 1000, cacheReadTokens: 700, cacheWriteTokens: 200, outputTokens: 50, cost: 0.4 },
154
- { inputTokens: 400, outputTokens: 60 },
155
- { inputTokens: 100, cacheReadTokens: 90, cacheWriteTokens: 90 },
156
- { cost: 0.01, externalCostUsd: 0.02 },
157
- ];
158
- for (const over of cases) {
159
- const m = msg(over);
160
- assert.equal(
161
- messageUsage(m),
162
- sumUsage([m]),
163
- `messageUsage must match sumUsage for ${JSON.stringify(over)}`,
164
- );
165
- }
166
- });
167
-
168
- suite('messageUsage is shallow — it prices the one request, not the tree', () => {
169
- // Deliberately blind to sub-agent traces and banked compaction usage: it answers
170
- // "what did this call cost", which is what a turn snapshot needs. `sumUsage` is the
171
- // function that walks everything underneath.
172
- const m = msg({
173
- inputTokens: 100,
174
- outputTokens: 10,
175
- cost: 0.05,
176
- toolCalls: [
177
- {
178
- id: 't1',
179
- name: 'delegate',
180
- args: {},
181
- subAgentTrace: [msg({ inputTokens: 900, outputTokens: 90, cost: 0.5 })],
182
- },
183
- ],
184
- });
185
- const usage = messageUsage(m)!;
186
- assert.is(usage.costUsd, 0.05, 'the child conversation is not folded in');
187
- assert.is(usage.uncachedInputTokens, 100);
188
- assert.ok(sumUsage([m]).costUsd > usage.costUsd, 'sumUsage does fold it in');
189
- });
190
-
191
- suite.run();
@@ -1,208 +0,0 @@
1
- import type { AggregateUsage, ChatMessage } from '@genesislcap/foundation-ai';
2
-
3
- /**
4
- * Zeroed {@link AggregateUsage}. A factory, not a shared const, because callers
5
- * accumulate into the returned object.
6
- *
7
- * @beta
8
- */
9
- export function emptyUsage(): AggregateUsage {
10
- return {
11
- costUsd: 0,
12
- uncachedInputTokens: 0,
13
- cacheReadTokens: 0,
14
- cacheWriteTokens: 0,
15
- outputTokens: 0,
16
- };
17
- }
18
-
19
- /**
20
- * Total tokens processed — the four buckets added up. Safe to sum because the
21
- * buckets are disjoint (see {@link AggregateUsage}); the equivalent sum over
22
- * per-message fields would double-count the prompt.
23
- *
24
- * Bear in mind what this number is: because every turn resends the conversation,
25
- * a turn's prompt is counted again in the next turn's. It is billed throughput,
26
- * NOT the count of distinct tokens in the conversation, so it grows
27
- * super-linearly with turn count and is expected to dwarf the context size.
28
- *
29
- * @beta
30
- */
31
- export function totalTokens(usage: AggregateUsage): number {
32
- return (
33
- usage.uncachedInputTokens + usage.cacheReadTokens + usage.cacheWriteTokens + usage.outputTokens
34
- );
35
- }
36
-
37
- /**
38
- * `a + b`, per bucket, as a new object.
39
- *
40
- * Used to lay a row's banked (pre-transcript) spend over what the live transcript
41
- * proves. Pure rather than in-place because both operands are live state a caller
42
- * must not mutate.
43
- *
44
- * @beta
45
- */
46
- export function addUsage(a: AggregateUsage, b: AggregateUsage): AggregateUsage {
47
- return {
48
- costUsd: a.costUsd + b.costUsd,
49
- uncachedInputTokens: a.uncachedInputTokens + b.uncachedInputTokens,
50
- cacheReadTokens: a.cacheReadTokens + b.cacheReadTokens,
51
- cacheWriteTokens: a.cacheWriteTokens + b.cacheWriteTokens,
52
- outputTokens: a.outputTokens + b.outputTokens,
53
- };
54
- }
55
-
56
- /**
57
- * The four buckets plus USD for ONE message — the per-message fields projected into
58
- * the disjoint, addable shape, or `undefined` when the message reports no usage at
59
- * all (a user turn, a display-only reasoning/narration split, a provider that
60
- * reports none).
61
- *
62
- * Shallow by design: it does NOT recurse into `toolCalls[].subAgentTrace` and does
63
- * NOT count `compaction.rolledUpUsage`, so it answers "what did this one request
64
- * cost" rather than "what did this message and everything under it cost". Use
65
- * {@link sumUsage} for the latter — passing a whole transcript through this one
66
- * message at a time would silently drop both.
67
- *
68
- * `costUsd` folds in `externalCostUsd` (non-LLM spend a widget reported), matching
69
- * `sumUsage`. It is `0` when the provider priced nothing, which is NOT a claim that
70
- * the call was free — see {@link AggregateUsage}; `UsageRow.costUsd` is the
71
- * shape that keeps unpriced distinguishable, and is the right tool for a ledger.
72
- *
73
- * @beta
74
- */
75
- export function messageUsage(m: ChatMessage): AggregateUsage | undefined {
76
- if (
77
- m.cost == null &&
78
- m.externalCostUsd == null &&
79
- m.inputTokens == null &&
80
- m.outputTokens == null &&
81
- m.cacheReadTokens == null &&
82
- m.cacheWriteTokens == null
83
- ) {
84
- return undefined;
85
- }
86
- const cacheReadTokens = m.cacheReadTokens ?? 0;
87
- const cacheWriteTokens = m.cacheWriteTokens ?? 0;
88
- return {
89
- costUsd: (m.cost ?? 0) + (m.externalCostUsd ?? 0),
90
- // The uncached REMAINDER, clamped — the same arithmetic `accumulate` below applies,
91
- // for the same reasons. The two are deliberately separate implementations (that walk
92
- // mutates one accumulator rather than allocating per message) and must agree;
93
- // `sum-usage.test.ts` pins them together.
94
- uncachedInputTokens: Math.max(0, (m.inputTokens ?? 0) - cacheReadTokens - cacheWriteTokens),
95
- cacheReadTokens,
96
- cacheWriteTokens,
97
- outputTokens: m.outputTokens ?? 0,
98
- };
99
- }
100
-
101
- /**
102
- * Sum cost and per-bucket token usage across a message list.
103
- *
104
- * One walk produces everything a usage display needs, replacing separate cost and
105
- * token passes over the same (potentially deep) transcript. Recurses into each
106
- * tool call's `subAgentTrace`, so LLM work a sub-agent did — possibly on another
107
- * provider at other rates — lands in the total; the driver embeds a whole
108
- * sub-agent conversation in the parent's tool result, so this is the only place
109
- * that spend is visible.
110
- *
111
- * Splitting the prompt into buckets is the point: `cost` already has the cache
112
- * discount applied, but a single token total cannot show that a session was cheap
113
- * because most of its prompt was a cache hit. The three input buckets are derived
114
- * from the per-message fields, where `inputTokens` is the whole prompt and the
115
- * cache fields break it down — so uncached input is the remainder, never an
116
- * addition.
117
- *
118
- * Also counts `compaction.rolledUpUsage`: compaction destroys the messages it
119
- * summarises, and without their banked usage every total re-derived from the
120
- * transcript would drop by whatever those turns had already spent.
121
- *
122
- * Returns a zeroed total when no message carries usage — providers that report
123
- * none (e.g. Chrome built-in) are skipped silently rather than guessed at.
124
- *
125
- * @remarks
126
- * This sums the WHOLE array, which is the right answer only when the array is the
127
- * whole of the work being billed. A driver whose conversation **continues across
128
- * rounds** opens its history with turns an earlier round already paid for, so
129
- * summing it again bills them twice — and the session total then grows
130
- * quadratically in the number of rounds while every individual figure in it stays
131
- * correct. Nothing about the result looks wrong, which is why it is called out
132
- * here rather than left to the caller to notice.
133
- *
134
- * Do NOT solve this with a "sum from this message onwards" marker. Compaction
135
- * *replaces* the messages it summarises — and compaction is one of this function's
136
- * own call sites — so the marker message can be absent from history by the time
137
- * the sum runs, leaving the caller to either fail or silently sum everything,
138
- * which is the double-count the marker was meant to prevent.
139
- *
140
- * Instead hold a **banked baseline**: spend that predates the current transcript,
141
- * carried as a value, with `usage = banked + live` composed via {@link addUsage}.
142
- * `resolveBankedUsage` in `utils/cost-session-history.ts` is the worked form, and
143
- * the shape of its signature is the load-bearing part. It separates three cases:
144
- *
145
- * 1. no prior record — a genuine first round, so nothing is banked;
146
- * 2. prior record, and the live transcript already accounts for it (a restored
147
- * session) — carry the prior record's own banked figure forward;
148
- * 3. prior record, and the transcript starts empty — bank the prior record's
149
- * whole total, because nothing in the transcript can re-derive it.
150
- *
151
- * Case 1 and case 3 take the same arithmetic and mean opposite things, so the
152
- * prior record must be a **required** input with no zero-defaulting path: a
153
- * caller who carries a transcript and forgets its baseline reports the entire
154
- * prior conversation as this round's spend. Distinguishing 2 from 3 is a separate
155
- * decision, and getting it wrong is destructive in the other direction — it
156
- * rewrites a total down to the current page-load and drops earlier spend.
157
- *
158
- * @beta
159
- */
160
- export function sumUsage(messages: readonly ChatMessage[]): AggregateUsage {
161
- const total = emptyUsage();
162
- accumulate(messages, total);
163
- return total;
164
- }
165
-
166
- /** Add `from` into `into`, in place. */
167
- function addInto(into: AggregateUsage, from: AggregateUsage): void {
168
- into.costUsd += from.costUsd;
169
- into.uncachedInputTokens += from.uncachedInputTokens;
170
- into.cacheReadTokens += from.cacheReadTokens;
171
- into.cacheWriteTokens += from.cacheWriteTokens;
172
- into.outputTokens += from.outputTokens;
173
- }
174
-
175
- /**
176
- * Walk `messages`, accumulating into `into`. Mutates a single accumulator rather
177
- * than merging returned objects — this runs on every transcript change, so the
178
- * per-message allocation is worth avoiding.
179
- */
180
- function accumulate(messages: readonly ChatMessage[], into: AggregateUsage): void {
181
- for (const m of messages) {
182
- // Provider/LLM cost plus any non-LLM cost a widget reported for its own
183
- // external service calls. Both are real spend on this session.
184
- if (m.cost != null) into.costUsd += m.cost;
185
- if (m.externalCostUsd != null) into.costUsd += m.externalCostUsd;
186
-
187
- const cacheRead = m.cacheReadTokens ?? 0;
188
- const cacheWrite = m.cacheWriteTokens ?? 0;
189
- into.cacheReadTokens += cacheRead;
190
- into.cacheWriteTokens += cacheWrite;
191
- // The uncached remainder of the prompt. Clamped at zero so malformed or
192
- // hand-edited history whose buckets exceed the prompt total can't drive the
193
- // session total negative — a wrong-but-plausible number is worse than a
194
- // conservative one here, since this feeds a cost display.
195
- into.uncachedInputTokens += Math.max(0, (m.inputTokens ?? 0) - cacheRead - cacheWrite);
196
- into.outputTokens += m.outputTokens ?? 0;
197
-
198
- // Spend banked by a compaction on this summary's behalf. NOTE: deliberately
199
- // not `compaction.tokensBefore` — that is a one-turn context-window reading,
200
- // not cumulative spend, and counting it here would inflate the total.
201
- const rolled = m.compaction?.rolledUpUsage;
202
- if (rolled) addInto(into, rolled);
203
-
204
- for (const tc of m.toolCalls ?? []) {
205
- if (tc.subAgentTrace) accumulate(tc.subAgentTrace, into);
206
- }
207
- }
208
- }
@@ -1,181 +0,0 @@
1
- import type { ChatToolDefinition, ChatToolHandlers } from '@genesislcap/foundation-ai';
2
-
3
- /**
4
- * Symbol used to attach ToolFold metadata to a facade handler function.
5
- * The ChatDriver inspects this to detect fold facades at runtime.
6
- *
7
- * @internal
8
- */
9
- export const TOOL_FOLD_SYMBOL = Symbol('toolFold');
10
-
11
- /**
12
- * Internal metadata for a tool fold. Attached to the facade handler via TOOL_FOLD_SYMBOL.
13
- *
14
- * @internal
15
- */
16
- export interface ToolFold {
17
- /** Facade tool name. */
18
- name: string;
19
- /** Description shown to the model. */
20
- description: string;
21
- /** Optional guidance injected in the fold-open response. */
22
- usageNotes?: string;
23
- /** Tool definitions inside this fold. */
24
- tools: ChatToolDefinition[];
25
- /** Handlers for the inner tools (keyed by tool name). */
26
- handlers: ChatToolHandlers;
27
- /**
28
- * When true (default), all other tools are removed when this fold opens —
29
- * the model can only see this fold's tools until it closes.
30
- * When false, sibling tools remain alongside the inner tools.
31
- */
32
- exclusive: boolean;
33
- }
34
-
35
- /**
36
- * The value returned by `createToolFold`.
37
- *
38
- * @beta
39
- */
40
- export interface ToolFoldResult {
41
- /**
42
- * The facade tool definition. Add this to your agent's `toolDefinitions`.
43
- * It is a regular `ChatToolDefinition` with an empty parameter schema.
44
- */
45
- definition: ChatToolDefinition;
46
- /**
47
- * The facade handler — a single-entry map keyed by the fold name.
48
- * Spread this into your agent's `toolHandlers`.
49
- * Inner tool handlers are NOT included here — they are encapsulated inside
50
- * the fold metadata and installed by the driver only when the fold opens.
51
- */
52
- handler: ChatToolHandlers;
53
- }
54
-
55
- /**
56
- * Creates a tool fold — a facade that hides a group of related tools behind a single
57
- * named entry, revealing them progressively when the model invokes the facade.
58
- *
59
- * The `tools` and `handlers` parameters accept the exact same types as `AgentConfig.toolDefinitions`
60
- * and `AgentConfig.toolHandlers`, so existing tool creation functions can be passed in unchanged.
61
- *
62
- * @example
63
- * ```typescript
64
- * const tradingTools = createToolFold({
65
- * name: 'trading_tools',
66
- * tools: createTradesToolDefinitions(),
67
- * handlers: createTradesToolHandlers(connect),
68
- * });
69
- *
70
- * const agent: AgentConfig = {
71
- * name: 'Trades',
72
- * systemPrompt: '...',
73
- * toolDefinitions: [getCurrentDateDef, tradingTools.definition],
74
- * toolHandlers: { get_current_date: handler, ...tradingTools.handler },
75
- * };
76
- * ```
77
- *
78
- * @example Nesting folds
79
- * ```typescript
80
- * const insertFold = createToolFold({
81
- * name: 'trade_insertion',
82
- * tools: [validateDef, insertDef],
83
- * handlers: { validate_trade: vHandler, insert_trade: iHandler },
84
- * });
85
- *
86
- * const tradingFold = createToolFold({
87
- * name: 'trading_tools',
88
- * tools: [searchDef, insertFold.definition],
89
- * handlers: { search_trades: sHandler, ...insertFold.handler },
90
- * });
91
- * ```
92
- *
93
- * @beta
94
- */
95
- export function createToolFold(config: {
96
- /** Facade tool name — used as the tool name the model calls to open this fold. */
97
- name: string;
98
- /**
99
- * Description shown to the model for the facade tool.
100
- * If omitted, auto-generated from the inner tool names:
101
- * "Contains: tool_a, tool_b. Invoke to access these tools."
102
- */
103
- description?: string;
104
- /**
105
- * Optional usage guidance injected into the fold-open response message.
106
- * Not included in the facade description — only visible once the fold is open.
107
- */
108
- usageNotes?: string;
109
- /** Inner tool definitions. Same type as `AgentConfig.toolDefinitions`. */
110
- tools: ChatToolDefinition[];
111
- /** Inner tool handlers. Same type as `AgentConfig.toolHandlers`. */
112
- handlers: ChatToolHandlers;
113
- /**
114
- * When true (default), all other tools are removed when this fold opens.
115
- * When false, sibling tools remain available alongside the inner tools.
116
- */
117
- exclusive?: boolean;
118
- }): ToolFoldResult {
119
- const description =
120
- config.description ??
121
- `Contains: ${config.tools.map((t) => t.name).join(', ')}. Invoke to access these tools.`;
122
-
123
- const fold: ToolFold = {
124
- name: config.name,
125
- description,
126
- usageNotes: config.usageNotes,
127
- tools: config.tools,
128
- handlers: config.handlers,
129
- exclusive: config.exclusive ?? true,
130
- };
131
-
132
- const definition: ChatToolDefinition = {
133
- name: config.name,
134
- description,
135
- parameters: { type: 'object', properties: {} },
136
- };
137
-
138
- // The facade handler body is never actually called — the driver intercepts it
139
- // via TOOL_FOLD_SYMBOL before dispatch. The body is a no-op fallback.
140
- const facadeHandler = async (): Promise<string> => `Fold: ${config.name}`;
141
- (facadeHandler as any)[TOOL_FOLD_SYMBOL] = fold;
142
-
143
- return {
144
- definition,
145
- handler: {
146
- [config.name]: facadeHandler,
147
- },
148
- };
149
- }
150
-
151
- /**
152
- * A tool entry in the expanded debug tree.
153
- * Folds are represented as nodes with a `tools` array; regular tools have none.
154
- *
155
- * @beta
156
- */
157
- export interface ToolTreeNode extends ChatToolDefinition {
158
- /** Present when this tool is a fold — contains its inner tools recursively expanded. */
159
- tools?: ToolTreeNode[];
160
- }
161
-
162
- /**
163
- * Expands a flat list of tool definitions into a nested tree by resolving fold
164
- * metadata from the corresponding handlers. Use this for debug output so the
165
- * full tool hierarchy is visible rather than just the top-level facade names.
166
- *
167
- * @beta
168
- */
169
- export function expandToolTree(
170
- definitions: ChatToolDefinition[],
171
- handlers: ChatToolHandlers,
172
- ): ToolTreeNode[] {
173
- return definitions.map((def) => {
174
- const handler = handlers[def.name];
175
- const fold: ToolFold | undefined = handler ? (handler as any)[TOOL_FOLD_SYMBOL] : undefined;
176
- if (fold) {
177
- return { ...def, tools: expandToolTree(fold.tools, fold.handlers) };
178
- }
179
- return { ...def };
180
- });
181
- }