@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,237 +0,0 @@
1
- import type { AggregateUsage, ChatMessage } from '@genesislcap/foundation-ai';
2
- import { assert, createLogicSuite } from '@genesislcap/foundation-testing';
3
- import { sumUsage } from './sum-usage';
4
- import { usageRows } from './usage-rows';
5
-
6
- const msg = (over: Partial<ChatMessage>): ChatMessage =>
7
- ({ role: 'assistant', content: '', ...over }) as ChatMessage;
8
-
9
- const rolled = (over: Partial<AggregateUsage>): AggregateUsage => ({
10
- costUsd: 0,
11
- uncachedInputTokens: 0,
12
- cacheReadTokens: 0,
13
- cacheWriteTokens: 0,
14
- outputTokens: 0,
15
- ...over,
16
- });
17
-
18
- /** Every place spend can hide, in one transcript: a delegation and a compaction. */
19
- const richHistory = (): ChatMessage[] => [
20
- msg({
21
- role: 'compacted-summary',
22
- content: 'summary',
23
- compaction: {
24
- compactedCount: 4,
25
- rolledUpUsage: rolled({ costUsd: 0.05, uncachedInputTokens: 400, outputTokens: 60 }),
26
- // The summariser's model — NOT what the banked spend ran on.
27
- model: 'claude-haiku-4-5-20251001',
28
- },
29
- } as Partial<ChatMessage>),
30
- msg({ role: 'user', content: 'go' }),
31
- msg({
32
- model: 'claude-sonnet-5',
33
- agentName: 'boss',
34
- cost: 0.01,
35
- inputTokens: 1_000,
36
- cacheReadTokens: 900,
37
- outputTokens: 50,
38
- toolCalls: [
39
- {
40
- id: 'tc1',
41
- name: 'delegate',
42
- args: {},
43
- subAgentTrace: [
44
- msg({
45
- model: 'gemini-2.5-flash',
46
- agentName: 'worker',
47
- cost: 0.12,
48
- inputTokens: 500,
49
- outputTokens: 200,
50
- }),
51
- ],
52
- },
53
- ],
54
- }),
55
- ];
56
-
57
- const suite = createLogicSuite('usageRows');
58
-
59
- suite('reconciles exactly with sumUsage over the same input', () => {
60
- // THE guarantee. A per-call view that disagrees with the total is worse than none,
61
- // because both look right in isolation — which is how the consumer's own copy
62
- // under-reported for a month.
63
- const history = richHistory();
64
- const rows = usageRows(history);
65
- const total = sumUsage(history);
66
-
67
- const rowCost = rows.reduce((n, r) => n + (r.costUsd ?? 0) + (r.externalCostUsd ?? 0), 0);
68
- assert.ok(Math.abs(rowCost - total.costUsd) < 1e-12, `${rowCost} vs ${total.costUsd}`);
69
-
70
- const bucket = (
71
- k: 'uncachedInputTokens' | 'cacheReadTokens' | 'cacheWriteTokens' | 'outputTokens',
72
- ) => rows.reduce((n, r) => n + r[k], 0);
73
- assert.is(bucket('uncachedInputTokens'), total.uncachedInputTokens, 'uncached input');
74
- assert.is(bucket('cacheReadTokens'), total.cacheReadTokens, 'cache read');
75
- assert.is(bucket('cacheWriteTokens'), total.cacheWriteTokens, 'cache write');
76
- assert.is(bucket('outputTokens'), total.outputTokens, 'output');
77
- });
78
-
79
- suite('emits a compaction row so banked spend is not lost from a per-call view', () => {
80
- // The shortfall this function exists to close: the summary message carries no
81
- // `cost` and no token fields of its own, so a naive per-message walk drops the
82
- // spend of every turn the compaction deleted.
83
- const rows = usageRows(richHistory());
84
- const compactionRows = rows.filter((r) => r.source === 'compaction');
85
- assert.is(compactionRows.length, 1);
86
- assert.is(compactionRows[0].costUsd, 0.05);
87
- assert.is(compactionRows[0].uncachedInputTokens, 400);
88
- assert.is(
89
- compactionRows[0].model,
90
- undefined,
91
- 'no model — the banked spend may span several, and compaction.model is the summariser',
92
- );
93
- });
94
-
95
- suite('recurses into sub-agent traces, tagging depth and the spawning call', () => {
96
- const rows = usageRows(richHistory());
97
- const child = rows.find((r) => r.model === 'gemini-2.5-flash');
98
- assert.ok(child, 'the sub-agent turn produced a row');
99
- assert.is(child!.subAgentDepth, 1);
100
- assert.is(child!.subAgentOf, 'tc1');
101
- assert.is(child!.agentName, 'worker');
102
- const parent = rows.find((r) => r.model === 'claude-sonnet-5');
103
- assert.is(parent!.subAgentDepth, 0);
104
- assert.is(parent!.subAgentOf, undefined);
105
- });
106
-
107
- suite('prefers the provider the driver stamped over re-deriving it from the model', () => {
108
- // `ChatMessage.provider` comes from the resolved provider's own status, so it is
109
- // authoritative and knows vendors the model allowlists do not. Re-deriving from the
110
- // model id alone drops attribution for a server-proxied turn — exactly the rows a
111
- // usage ledger cares most about getting right. PR review.
112
- const rows = usageRows([
113
- msg({ model: 'gpt-5', provider: 'openai', cost: 1, inputTokens: 1 } as Partial<ChatMessage>),
114
- // Stamped provider disagrees with what the model id would derive: the stamp still wins.
115
- msg({
116
- model: 'claude-sonnet-5',
117
- provider: 'chrome',
118
- cost: 1,
119
- inputTokens: 1,
120
- } as Partial<ChatMessage>),
121
- ]);
122
- assert.equal(
123
- rows.map((r) => r.provider),
124
- ['openai', 'chrome'],
125
- );
126
- });
127
-
128
- suite('resolves the provider from the model, and leaves it unset when unknown', () => {
129
- const rows = usageRows([
130
- msg({ model: 'claude-sonnet-5', cost: 1, inputTokens: 1 }),
131
- msg({ model: 'gemini-2.5-flash', cost: 1, inputTokens: 1 }),
132
- msg({ model: 'gpt-5', cost: 1, inputTokens: 1 }),
133
- msg({ cost: 1, inputTokens: 1 }),
134
- ]);
135
- assert.equal(
136
- rows.map((r) => r.provider),
137
- ['anthropic', 'gemini', undefined, undefined],
138
- 'an unrecognised or absent model leaves provider unset rather than guessing',
139
- );
140
- });
141
-
142
- suite('splits the prompt into buckets the same way sumUsage does', () => {
143
- const [row] = usageRows([
144
- msg({
145
- model: 'claude-sonnet-5',
146
- inputTokens: 1_000,
147
- cacheReadTokens: 600,
148
- cacheWriteTokens: 300,
149
- }),
150
- ]);
151
- assert.is(row.uncachedInputTokens, 100, 'the remainder, not an addition');
152
- assert.is(row.cacheReadTokens, 600);
153
- assert.is(row.cacheWriteTokens, 300);
154
- });
155
-
156
- suite('clamps a prompt whose cache buckets exceed the total', () => {
157
- const [row] = usageRows([msg({ inputTokens: 100, cacheReadTokens: 900 })]);
158
- assert.is(row.uncachedInputTokens, 0, 'never negative');
159
- });
160
-
161
- suite('leaves costUsd undefined when nothing was reported, rather than zero', () => {
162
- // What the "carried usage but no cost" audit keys off. A zero here would read as a
163
- // free call and the alarm would never fire.
164
- const [row] = usageRows([msg({ model: 'claude-sonnet-5', inputTokens: 500, outputTokens: 10 })]);
165
- assert.is(row.costUsd, undefined);
166
- assert.is(row.source, 'request');
167
- });
168
-
169
- suite('records external (non-LLM) cost and counts it in the reconciliation', () => {
170
- const history = [msg({ externalCostUsd: 0.4 }), msg({ model: 'claude-sonnet-5', cost: 0.1 })];
171
- const rows = usageRows(history);
172
- assert.is(rows.find((r) => r.externalCostUsd != null)!.externalCostUsd, 0.4);
173
- const rowCost = rows.reduce((n, r) => n + (r.costUsd ?? 0) + (r.externalCostUsd ?? 0), 0);
174
- assert.ok(Math.abs(rowCost - sumUsage(history).costUsd) < 1e-12);
175
- });
176
-
177
- suite('produces no row for a message carrying no usage', () => {
178
- assert.equal(
179
- usageRows([
180
- msg({ role: 'user', content: 'hello' }),
181
- msg({ content: 'thinking out loud', category: 'reasoning' }),
182
- ]),
183
- [],
184
- );
185
- });
186
-
187
- suite('is empty for an empty transcript', () => {
188
- assert.equal(usageRows([]), []);
189
- });
190
-
191
- suite('reconciles on a nested delegation two levels deep', () => {
192
- const history = [
193
- msg({
194
- model: 'claude-sonnet-5',
195
- cost: 0.01,
196
- inputTokens: 100,
197
- toolCalls: [
198
- {
199
- id: 'a',
200
- name: 'delegate',
201
- args: {},
202
- subAgentTrace: [
203
- msg({
204
- model: 'claude-sonnet-5',
205
- cost: 0.02,
206
- inputTokens: 200,
207
- toolCalls: [
208
- {
209
- id: 'b',
210
- name: 'delegate',
211
- args: {},
212
- subAgentTrace: [msg({ model: 'gemini-2.5-flash', cost: 0.04, inputTokens: 400 })],
213
- },
214
- ],
215
- }),
216
- ],
217
- },
218
- ],
219
- }),
220
- ];
221
- const rows = usageRows(history);
222
- assert.equal(
223
- rows.map((r) => r.subAgentDepth),
224
- [0, 1, 2],
225
- );
226
- const rowCost = rows.reduce((n, r) => n + (r.costUsd ?? 0), 0);
227
- assert.ok(Math.abs(rowCost - sumUsage(history).costUsd) < 1e-12, 'still reconciles');
228
- });
229
-
230
- suite('does not mutate the transcript', () => {
231
- const history = richHistory();
232
- const before = JSON.stringify(history);
233
- usageRows(history);
234
- assert.is(JSON.stringify(history), before, 'a projection, not a transform');
235
- });
236
-
237
- suite.run();
@@ -1,187 +0,0 @@
1
- import type { AggregateUsage, AIProviderType, ChatMessage } from '@genesislcap/foundation-ai';
2
- import { vendorOfModel } from '@genesislcap/foundation-ai';
3
-
4
- /**
5
- * One billable unit of spend, projected out of a transcript.
6
- *
7
- * @remarks
8
- * Deliberately **not** a `ChatMessage`, and not an extension of one. A row is a
9
- * derived record for a ledger, and keeping the types distinct makes the one
10
- * dangerous mistake — feeding rows back into `sumUsage`, which does its own
11
- * recursion and would count everything twice — a compile error rather than a
12
- * plausible wrong number.
13
- *
14
- * @beta
15
- */
16
- export interface UsageRow {
17
- /**
18
- * Where the spend came from.
19
- *
20
- * - `'request'` — one model call, with a message behind it.
21
- * - `'compaction'` — spend **banked** by a compaction. The turns that incurred it
22
- * were summarised and deleted, so there is no single call to attribute it to;
23
- * the amount survives on the summary's `compaction.rolledUpUsage`. Emitted as
24
- * its own row because otherwise this money is simply missing from a per-call
25
- * view while remaining present in the total, and the two disagree silently.
26
- */
27
- source: 'request' | 'compaction';
28
- /**
29
- * Model that produced the call. `undefined` on a `'compaction'` row: the banked
30
- * spend covers however many turns were summarised, which may have run on
31
- * different models, so attributing it to one would invent a fact. (The summary's
32
- * `compaction.model` is the model that did the *summarising* — a different thing,
33
- * and not what this spend was.)
34
- */
35
- model?: string;
36
- /**
37
- * Vendor that served the call. Taken from the provider the driver stamped on the message,
38
- * which is authoritative and covers vendors the model allowlists do not — a server-proxied
39
- * `gpt-5` turn is `'openai'` here and unknown to `vendorOfModel`. Falls back to resolving
40
- * {@link UsageRow.model} through `vendorOfModel` only for messages persisted before that
41
- * field existed, and is left unset when neither knows rather than guessed at.
42
- */
43
- provider?: AIProviderType;
44
- /**
45
- * USD for this row, as reported by the provider and stamped by the transport.
46
- *
47
- * `undefined` means no cost was reported — **not** zero. Nothing here estimates
48
- * one from the token counts: an estimate is plausible, unremarkable and quietly
49
- * wrong, whereas a total that is visibly too low gets investigated. A row with
50
- * tokens but no `costUsd` is worth alarming on; that is the shape that check needs.
51
- */
52
- costUsd?: number;
53
- /** Non-LLM spend a host reported for this turn (an external service it called). */
54
- externalCostUsd?: number;
55
- /** Prompt tokens billed at the full uncached rate — the remainder after the cache buckets. */
56
- uncachedInputTokens: number;
57
- /** Prompt tokens served from the provider's cache. */
58
- cacheReadTokens: number;
59
- /** Prompt tokens written to the provider's cache. */
60
- cacheWriteTokens: number;
61
- /** Generated tokens, including reasoning tokens the provider bills as output. */
62
- outputTokens: number;
63
- /** Agent that produced the call, when known. */
64
- agentName?: string;
65
- /** Delegation depth: 0 for the top-level conversation, 1 for a sub-agent, 2 for its sub-agent, … */
66
- subAgentDepth: number;
67
- /** Id of the tool call that spawned this row's conversation — correlates a row back to its delegation. */
68
- subAgentOf?: string;
69
- }
70
-
71
- /** Whether a message carries anything a ledger would record. */
72
- function hasUsage(m: ChatMessage): boolean {
73
- return (
74
- m.cost != null ||
75
- m.externalCostUsd != null ||
76
- m.inputTokens != null ||
77
- m.outputTokens != null ||
78
- m.cacheReadTokens != null ||
79
- m.cacheWriteTokens != null
80
- );
81
- }
82
-
83
- /**
84
- * Project a transcript into one row per billable unit of spend.
85
- *
86
- * @remarks
87
- * Reads the messages and returns a table; it stores nothing, mutates nothing, and
88
- * adds nothing to history. Use it where an **aggregate is not enough** — writing
89
- * usage rows for per-project attribution, a cost dashboard, or auditing which turns
90
- * came back unpriced. For a single total, `sumUsage` is the answer and this is the
91
- * wrong tool.
92
- *
93
- * Walks the same shape `sumUsage` does, and is guaranteed to agree with it: summing
94
- * `costUsd` and `externalCostUsd` across these rows equals `sumUsage(...).costUsd`
95
- * over the same input, and the four token buckets reconcile likewise. That property
96
- * is the point of the function — a per-call view that quietly disagrees with the
97
- * total is worse than no per-call view, because both look right in isolation.
98
- *
99
- * Covers the two places spend hides:
100
- *
101
- * - **Sub-agent conversations.** A delegating tool call carries the child's entire
102
- * conversation on `toolCall.subAgentTrace`, so a walk of top-level messages alone
103
- * misses everything a delegating agent spent. A consumer that reimplemented this
104
- * and omitted the branch reported $0.106 on a run that cost $0.234.
105
- * - **Compactions.** A compaction deletes the messages it summarises, banking their
106
- * spend on the summary. Those turns produce a single `source: 'compaction'` row.
107
- *
108
- * Messages carrying no usage at all (a user turn, a narration) produce no row.
109
- *
110
- * **Do not pass the result to `sumUsage`** — the types prevent it, and the reason is
111
- * that both recurse, so a pre-flattened list would be counted twice. Both functions
112
- * take raw history.
113
- *
114
- * @beta
115
- */
116
- export function usageRows(messages: readonly ChatMessage[]): UsageRow[] {
117
- return collectRows(messages, 0, undefined);
118
- }
119
-
120
- /** Map an already-bucketed {@link AggregateUsage} onto a row's token fields. */
121
- function bucketsOf(
122
- usage: AggregateUsage,
123
- ): Pick<UsageRow, 'uncachedInputTokens' | 'cacheReadTokens' | 'cacheWriteTokens' | 'outputTokens'> {
124
- return {
125
- uncachedInputTokens: usage.uncachedInputTokens,
126
- cacheReadTokens: usage.cacheReadTokens,
127
- cacheWriteTokens: usage.cacheWriteTokens,
128
- outputTokens: usage.outputTokens,
129
- };
130
- }
131
-
132
- function collectRows(
133
- messages: readonly ChatMessage[],
134
- depth: number,
135
- subAgentOf: string | undefined,
136
- ): UsageRow[] {
137
- const rows: UsageRow[] = [];
138
- for (const m of messages) {
139
- if (hasUsage(m)) {
140
- const cacheReadTokens = m.cacheReadTokens ?? 0;
141
- const cacheWriteTokens = m.cacheWriteTokens ?? 0;
142
- rows.push({
143
- source: 'request',
144
- model: m.model,
145
- // The driver stamps `ChatMessage.provider` from the resolved provider's own
146
- // status, so it is AUTHORITATIVE and covers vendors the model allowlists do
147
- // not — a server-proxied `gpt-5` turn is `provider: 'openai'` on the message
148
- // and unknown to `vendorOfModel`. Prefer it; fall back to resolving the model
149
- // id only for messages persisted before the field existed. Still left
150
- // undefined when neither knows, rather than guessed at.
151
- provider: m.provider ?? (m.model ? vendorOfModel(m.model) : undefined),
152
- ...(m.cost != null && { costUsd: m.cost }),
153
- ...(m.externalCostUsd != null && { externalCostUsd: m.externalCostUsd }),
154
- // Uncached input is the REMAINDER, matching `sumUsage`: `inputTokens` is the
155
- // whole prompt and the cache fields break it down. Clamped for the same
156
- // reason — hand-edited history must not drive a total negative.
157
- uncachedInputTokens: Math.max(0, (m.inputTokens ?? 0) - cacheReadTokens - cacheWriteTokens),
158
- cacheReadTokens,
159
- cacheWriteTokens,
160
- outputTokens: m.outputTokens ?? 0,
161
- ...(m.agentName != null && { agentName: m.agentName }),
162
- subAgentDepth: depth,
163
- ...(subAgentOf != null && { subAgentOf }),
164
- });
165
- }
166
-
167
- // Spend the compaction banked on this summary's behalf. Its buckets are already
168
- // disjoint (it is an `AggregateUsage`), so they map across without the
169
- // subtraction a per-message record needs.
170
- const rolled = m.compaction?.rolledUpUsage;
171
- if (rolled) {
172
- rows.push({
173
- source: 'compaction',
174
- ...(rolled.costUsd != null && { costUsd: rolled.costUsd }),
175
- ...bucketsOf(rolled),
176
- ...(m.agentName != null && { agentName: m.agentName }),
177
- subAgentDepth: depth,
178
- ...(subAgentOf != null && { subAgentOf }),
179
- });
180
- }
181
-
182
- for (const tc of m.toolCalls ?? []) {
183
- if (tc.subAgentTrace) rows.push(...collectRows(tc.subAgentTrace, depth + 1, tc.id));
184
- }
185
- }
186
- return rows;
187
- }
@@ -1,24 +0,0 @@
1
- /**
2
- * Resolve `promise`, or **reject** if it doesn't settle within `ms`. Rejecting
3
- * (rather than resolving to a fallback) lets restore tell a genuine timeout apart
4
- * from a clean "nothing stored" result, so it can surface a notice for the former
5
- * and stay silent for the latter. Because the loser of the race is abandoned, a
6
- * late-resolving `load()` is discarded and can never clobber a now-live session;
7
- * a late *rejection* is swallowed so it doesn't surface as an unhandled rejection.
8
- *
9
- * @internal
10
- */
11
- export async function withTimeout<T>(promise: Promise<T>, ms: number): Promise<T> {
12
- let timer: ReturnType<typeof setTimeout> | undefined;
13
- promise.catch(() => {});
14
- try {
15
- return await Promise.race([
16
- promise,
17
- new Promise<T>((_resolve, reject) => {
18
- timer = setTimeout(() => reject(new Error(`withTimeout: exceeded ${ms}ms`)), ms);
19
- }),
20
- ]);
21
- } finally {
22
- clearTimeout(timer);
23
- }
24
- }
package/tsconfig.json DELETED
@@ -1,9 +0,0 @@
1
- {
2
- "extends": "../../../../tsconfig.json",
3
- "compilerOptions": {
4
- "declarationDir": "./dist/dts",
5
- "outDir": "./dist/esm",
6
- "rootDir": "./src"
7
- },
8
- "include": ["src/**/*"]
9
- }