@adecore/agents 0.0.1 → 0.17.0-beta.2

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 (348) hide show
  1. package/LICENSE +110 -0
  2. package/README.md +48 -0
  3. package/dist/async.d.ts +2 -0
  4. package/dist/async.js +31 -0
  5. package/dist/chat/attachment-store.d.ts +10 -0
  6. package/dist/chat/attachment-store.js +93 -0
  7. package/dist/chat/backend.d.ts +214 -0
  8. package/dist/chat/backend.js +1 -0
  9. package/dist/chat/background-work.d.ts +8 -0
  10. package/dist/chat/background-work.js +33 -0
  11. package/dist/chat/bookmark-store.d.ts +25 -0
  12. package/dist/chat/bookmark-store.js +141 -0
  13. package/dist/chat/chat-core.d.ts +311 -0
  14. package/dist/chat/chat-core.js +1025 -0
  15. package/dist/chat/chat-log.d.ts +33 -0
  16. package/dist/chat/chat-log.js +163 -0
  17. package/dist/chat/chat-process.d.ts +46 -0
  18. package/dist/chat/chat-process.js +190 -0
  19. package/dist/chat/chat-session.d.ts +209 -0
  20. package/dist/chat/chat-session.js +1511 -0
  21. package/dist/chat/chat-store.d.ts +37 -0
  22. package/dist/chat/chat-store.js +202 -0
  23. package/dist/chat/chat-title.d.ts +10 -0
  24. package/dist/chat/chat-title.js +74 -0
  25. package/dist/chat/claude-backend.d.ts +35 -0
  26. package/dist/chat/claude-backend.js +214 -0
  27. package/dist/chat/claude-protocol.d.ts +41 -0
  28. package/dist/chat/claude-protocol.js +642 -0
  29. package/dist/chat/claude-title.d.ts +11 -0
  30. package/dist/chat/claude-title.js +97 -0
  31. package/dist/chat/claude-transcript.d.ts +28 -0
  32. package/dist/chat/claude-transcript.js +211 -0
  33. package/dist/chat/codex-backend.d.ts +51 -0
  34. package/dist/chat/codex-backend.js +364 -0
  35. package/dist/chat/codex-protocol.d.ts +56 -0
  36. package/dist/chat/codex-protocol.js +767 -0
  37. package/dist/chat/codex-thread.d.ts +32 -0
  38. package/dist/chat/codex-thread.js +131 -0
  39. package/dist/chat/codex-transport.d.ts +42 -0
  40. package/dist/chat/codex-transport.js +188 -0
  41. package/dist/chat/composer-preferences.d.ts +15 -0
  42. package/dist/chat/composer-preferences.js +46 -0
  43. package/dist/chat/context-breakdown.d.ts +2 -0
  44. package/dist/chat/context-breakdown.js +74 -0
  45. package/dist/chat/delta-coalescer.d.ts +12 -0
  46. package/dist/chat/delta-coalescer.js +55 -0
  47. package/dist/chat/errors.d.ts +4 -0
  48. package/dist/chat/errors.js +4 -0
  49. package/dist/chat/fake-claude.d.ts +3 -0
  50. package/dist/chat/fake-claude.js +982 -0
  51. package/dist/chat/fake-cli.d.ts +25 -0
  52. package/dist/chat/fake-cli.js +157 -0
  53. package/dist/chat/fake-codex.d.ts +10 -0
  54. package/dist/chat/fake-codex.js +502 -0
  55. package/dist/chat/fixtures/claude-2.1.285-auto-fallback.json +24 -0
  56. package/dist/chat/fixtures/claude-2.1.285-native-children.json +6600 -0
  57. package/dist/chat/fixtures/claude-2.1.285-resume-direct-compact.json +202 -0
  58. package/dist/chat/fixtures/claude-2.1.285-resume-task-notification.json +57 -0
  59. package/dist/chat/fixtures/claude-projects/-work-demo/5f1c2a9e-0b7d-4c1e-9a53-3e2f8d6b7a10/subagents/agent-a4c2e8f10b3d5a7e9.jsonl +10 -0
  60. package/dist/chat/fixtures/claude-projects/-work-demo/5f1c2a9e-0b7d-4c1e-9a53-3e2f8d6b7a10/subagents/agent-a4c2e8f10b3d5a7e9.meta.json +1 -0
  61. package/dist/chat/fixtures/claude-projects/-work-demo/5f1c2a9e-0b7d-4c1e-9a53-3e2f8d6b7a10/subagents/agent-a9b8c7d6e5f4a3b21.jsonl +2 -0
  62. package/dist/chat/fixtures/claude-projects/-work-demo/5f1c2a9e-0b7d-4c1e-9a53-3e2f8d6b7a10/subagents/agent-a9b8c7d6e5f4a3b21.meta.json +1 -0
  63. package/dist/chat/fixtures/codex-0.159.2-native-approval.json +119 -0
  64. package/dist/chat/fixtures/codex-0.159.2-native-file-approval.json +136 -0
  65. package/dist/chat/fixtures/codex-0.159.2-native-interacted.json +220 -0
  66. package/dist/chat/fixtures/codex-0.159.2-usage-windows.json +74 -0
  67. package/dist/chat/input.d.ts +19 -0
  68. package/dist/chat/input.js +71 -0
  69. package/dist/chat/limit-resume.d.ts +11 -0
  70. package/dist/chat/limit-resume.js +68 -0
  71. package/dist/chat/projector.d.ts +60 -0
  72. package/dist/chat/projector.js +860 -0
  73. package/dist/chat/request-summary.d.ts +3 -0
  74. package/dist/chat/request-summary.js +143 -0
  75. package/dist/chat/subagent-projection.d.ts +8 -0
  76. package/dist/chat/subagent-projection.js +40 -0
  77. package/dist/chat/subagent-reader.d.ts +66 -0
  78. package/dist/chat/subagent-reader.js +396 -0
  79. package/dist/chat/subagent-settlement.d.ts +6 -0
  80. package/dist/chat/subagent-settlement.js +103 -0
  81. package/dist/chat/thread.d.ts +30 -0
  82. package/dist/chat/thread.js +147 -0
  83. package/dist/chat/wake-chat.d.ts +20 -0
  84. package/dist/chat/wake-chat.js +27 -0
  85. package/dist/client-sinks.d.ts +9 -0
  86. package/dist/client-sinks.js +34 -0
  87. package/dist/coded-error.d.ts +5 -0
  88. package/dist/coded-error.js +17 -0
  89. package/dist/context/argv.d.ts +11 -0
  90. package/dist/context/argv.js +58 -0
  91. package/dist/context/refusal.d.ts +9 -0
  92. package/dist/context/refusal.js +38 -0
  93. package/dist/context/verb.d.ts +85 -0
  94. package/dist/context/verb.js +233 -0
  95. package/dist/error-text.d.ts +5 -0
  96. package/dist/error-text.js +24 -0
  97. package/dist/events.d.ts +12 -0
  98. package/dist/events.js +1 -0
  99. package/dist/fs.d.ts +9 -0
  100. package/dist/fs.js +107 -0
  101. package/dist/host/agent-host.d.ts +28 -0
  102. package/dist/host/agent-host.js +105 -0
  103. package/dist/host/environment.d.ts +7 -0
  104. package/dist/host/environment.js +22 -0
  105. package/dist/host/handlers.d.ts +30 -0
  106. package/dist/host/handlers.js +111 -0
  107. package/dist/host/memory-port.d.ts +2 -0
  108. package/dist/host/memory-port.js +25 -0
  109. package/dist/host/wiring.d.ts +40 -0
  110. package/dist/host/wiring.js +120 -0
  111. package/dist/lineage.d.ts +44 -0
  112. package/dist/lineage.js +134 -0
  113. package/dist/messages/deliver-message.d.ts +21 -0
  114. package/dist/messages/deliver-message.js +42 -0
  115. package/dist/messages/deliver-notice.d.ts +25 -0
  116. package/dist/messages/deliver-notice.js +75 -0
  117. package/dist/messages/notice-notes.d.ts +15 -0
  118. package/dist/messages/notice-notes.js +26 -0
  119. package/dist/messages/notice-store.d.ts +30 -0
  120. package/dist/messages/notice-store.js +120 -0
  121. package/dist/modes.d.ts +11 -0
  122. package/dist/modes.js +28 -0
  123. package/dist/outbox/manual-clock.d.ts +10 -0
  124. package/dist/outbox/manual-clock.js +25 -0
  125. package/dist/outbox/outbox-worker.d.ts +44 -0
  126. package/dist/outbox/outbox-worker.js +173 -0
  127. package/dist/outbox/outbox.d.ts +37 -0
  128. package/dist/outbox/outbox.js +76 -0
  129. package/dist/providers/accounts/accounts.d.ts +20 -0
  130. package/dist/providers/accounts/accounts.js +158 -0
  131. package/dist/providers/accounts/launch.d.ts +17 -0
  132. package/dist/providers/accounts/launch.js +30 -0
  133. package/dist/providers/accounts/service.d.ts +87 -0
  134. package/dist/providers/accounts/service.js +538 -0
  135. package/dist/providers/accounts/shadow-home.d.ts +8 -0
  136. package/dist/providers/accounts/shadow-home.js +70 -0
  137. package/dist/providers/accounts/status.d.ts +12 -0
  138. package/dist/providers/accounts/status.js +85 -0
  139. package/dist/providers/accounts/store.d.ts +14 -0
  140. package/dist/providers/accounts/store.js +112 -0
  141. package/dist/providers/accounts/test-accounts.d.ts +12 -0
  142. package/dist/providers/accounts/test-accounts.js +38 -0
  143. package/dist/providers/accounts/variables.d.ts +27 -0
  144. package/dist/providers/accounts/variables.js +94 -0
  145. package/dist/providers/catalog.d.ts +17 -0
  146. package/dist/providers/catalog.js +76 -0
  147. package/dist/providers/claude-models.json +114 -0
  148. package/dist/providers/claude-provider.d.ts +4 -0
  149. package/dist/providers/claude-provider.js +29 -0
  150. package/dist/providers/claude.d.ts +15 -0
  151. package/dist/providers/claude.js +105 -0
  152. package/dist/providers/codex-models.json +135 -0
  153. package/dist/providers/codex-provider.d.ts +4 -0
  154. package/dist/providers/codex-provider.js +27 -0
  155. package/dist/providers/codex-rules.d.ts +9 -0
  156. package/dist/providers/codex-rules.js +35 -0
  157. package/dist/providers/codex.d.ts +14 -0
  158. package/dist/providers/codex.js +52 -0
  159. package/dist/providers/detect.d.ts +6 -0
  160. package/dist/providers/detect.js +30 -0
  161. package/dist/providers/provider.d.ts +25 -0
  162. package/dist/providers/provider.js +1 -0
  163. package/dist/providers/registry.d.ts +28 -0
  164. package/dist/providers/registry.js +118 -0
  165. package/dist/record-directory.d.ts +27 -0
  166. package/dist/record-directory.js +114 -0
  167. package/dist/run-process.d.ts +13 -0
  168. package/dist/run-process.js +42 -0
  169. package/dist/serializer.d.ts +9 -0
  170. package/dist/serializer.js +34 -0
  171. package/dist/skills.d.ts +21 -0
  172. package/dist/skills.js +174 -0
  173. package/dist/tasks/background-limit.d.ts +16 -0
  174. package/dist/tasks/background-limit.js +55 -0
  175. package/dist/tasks/end-children.d.ts +34 -0
  176. package/dist/tasks/end-children.js +53 -0
  177. package/dist/tasks/give-task.d.ts +14 -0
  178. package/dist/tasks/give-task.js +33 -0
  179. package/dist/tasks/task-coordinator.d.ts +52 -0
  180. package/dist/tasks/task-coordinator.js +323 -0
  181. package/dist/tasks/task-store.d.ts +49 -0
  182. package/dist/tasks/task-store.js +197 -0
  183. package/dist/tasks/task-work.d.ts +47 -0
  184. package/dist/tasks/task-work.js +28 -0
  185. package/dist/tasks/waiting-child.d.ts +51 -0
  186. package/dist/tasks/waiting-child.js +143 -0
  187. package/dist/tasks/wake-parent.d.ts +19 -0
  188. package/dist/tasks/wake-parent.js +109 -0
  189. package/dist/tasks/wiring.d.ts +51 -0
  190. package/dist/tasks/wiring.js +151 -0
  191. package/dist/title-file.d.ts +2 -0
  192. package/dist/title-file.js +55 -0
  193. package/dist/usage/accounts.d.ts +15 -0
  194. package/dist/usage/accounts.js +64 -0
  195. package/dist/usage/aggregate.d.ts +12 -0
  196. package/dist/usage/aggregate.js +154 -0
  197. package/dist/usage/exchange.d.ts +16 -0
  198. package/dist/usage/exchange.js +84 -0
  199. package/dist/usage/index-file.d.ts +15 -0
  200. package/dist/usage/index-file.js +123 -0
  201. package/dist/usage/limits/monitor.d.ts +50 -0
  202. package/dist/usage/limits/monitor.js +179 -0
  203. package/dist/usage/limits/normalize.d.ts +21 -0
  204. package/dist/usage/limits/normalize.js +160 -0
  205. package/dist/usage/limits/probe.d.ts +9 -0
  206. package/dist/usage/limits/probe.js +113 -0
  207. package/dist/usage/prices-snapshot.json +1071 -0
  208. package/dist/usage/pricing.d.ts +34 -0
  209. package/dist/usage/pricing.js +186 -0
  210. package/dist/usage/projects.d.ts +17 -0
  211. package/dist/usage/projects.js +49 -0
  212. package/dist/usage/readers/claude.d.ts +3 -0
  213. package/dist/usage/readers/claude.js +59 -0
  214. package/dist/usage/readers/codex.d.ts +23 -0
  215. package/dist/usage/readers/codex.js +155 -0
  216. package/dist/usage/record.d.ts +15 -0
  217. package/dist/usage/record.js +39 -0
  218. package/dist/usage/roots.d.ts +12 -0
  219. package/dist/usage/roots.js +29 -0
  220. package/dist/usage/scanner.d.ts +30 -0
  221. package/dist/usage/scanner.js +234 -0
  222. package/dist/usage/usage-service.d.ts +35 -0
  223. package/dist/usage/usage-service.js +130 -0
  224. package/dist/watch-seam.d.ts +29 -0
  225. package/dist/watch-seam.js +78 -0
  226. package/dist/watch-test-helpers.d.ts +28 -0
  227. package/dist/watch-test-helpers.js +86 -0
  228. package/package.json +42 -3
  229. package/src/async.ts +40 -0
  230. package/src/chat/attachment-store.ts +108 -0
  231. package/src/chat/backend.ts +199 -0
  232. package/src/chat/background-work.ts +42 -0
  233. package/src/chat/bookmark-store.ts +166 -0
  234. package/src/chat/chat-core.ts +1228 -0
  235. package/src/chat/chat-log.ts +187 -0
  236. package/src/chat/chat-process.ts +237 -0
  237. package/src/chat/chat-session.ts +1723 -0
  238. package/src/chat/chat-store.ts +261 -0
  239. package/src/chat/chat-title.ts +90 -0
  240. package/src/chat/claude-backend.ts +235 -0
  241. package/src/chat/claude-protocol.ts +684 -0
  242. package/src/chat/claude-title.ts +106 -0
  243. package/src/chat/claude-transcript.ts +233 -0
  244. package/src/chat/codex-backend.ts +397 -0
  245. package/src/chat/codex-protocol.ts +872 -0
  246. package/src/chat/codex-thread.ts +162 -0
  247. package/src/chat/codex-transport.ts +224 -0
  248. package/src/chat/composer-preferences.ts +67 -0
  249. package/src/chat/context-breakdown.ts +80 -0
  250. package/src/chat/delta-coalescer.ts +62 -0
  251. package/src/chat/errors.ts +25 -0
  252. package/src/chat/fake-claude.ts +1006 -0
  253. package/src/chat/fake-cli.ts +199 -0
  254. package/src/chat/fake-codex.ts +534 -0
  255. package/src/chat/fixtures/claude-2.1.285-auto-fallback.json +24 -0
  256. package/src/chat/fixtures/claude-2.1.285-native-children.json +6600 -0
  257. package/src/chat/fixtures/claude-2.1.285-resume-direct-compact.json +202 -0
  258. package/src/chat/fixtures/claude-2.1.285-resume-task-notification.json +57 -0
  259. package/src/chat/fixtures/claude-projects/-work-demo/5f1c2a9e-0b7d-4c1e-9a53-3e2f8d6b7a10/subagents/agent-a4c2e8f10b3d5a7e9.jsonl +10 -0
  260. package/src/chat/fixtures/claude-projects/-work-demo/5f1c2a9e-0b7d-4c1e-9a53-3e2f8d6b7a10/subagents/agent-a4c2e8f10b3d5a7e9.meta.json +1 -0
  261. package/src/chat/fixtures/claude-projects/-work-demo/5f1c2a9e-0b7d-4c1e-9a53-3e2f8d6b7a10/subagents/agent-a9b8c7d6e5f4a3b21.jsonl +2 -0
  262. package/src/chat/fixtures/claude-projects/-work-demo/5f1c2a9e-0b7d-4c1e-9a53-3e2f8d6b7a10/subagents/agent-a9b8c7d6e5f4a3b21.meta.json +1 -0
  263. package/src/chat/fixtures/codex-0.159.2-native-approval.json +119 -0
  264. package/src/chat/fixtures/codex-0.159.2-native-file-approval.json +136 -0
  265. package/src/chat/fixtures/codex-0.159.2-native-interacted.json +220 -0
  266. package/src/chat/fixtures/codex-0.159.2-usage-windows.json +74 -0
  267. package/src/chat/input.ts +89 -0
  268. package/src/chat/limit-resume.ts +74 -0
  269. package/src/chat/projector.ts +969 -0
  270. package/src/chat/request-summary.ts +168 -0
  271. package/src/chat/subagent-projection.ts +45 -0
  272. package/src/chat/subagent-reader.ts +469 -0
  273. package/src/chat/subagent-settlement.ts +111 -0
  274. package/src/chat/thread.ts +167 -0
  275. package/src/chat/wake-chat.ts +45 -0
  276. package/src/client-sinks.ts +39 -0
  277. package/src/coded-error.ts +18 -0
  278. package/src/context/argv.ts +61 -0
  279. package/src/context/refusal.ts +50 -0
  280. package/src/context/verb.ts +392 -0
  281. package/src/error-text.ts +28 -0
  282. package/src/events.ts +9 -0
  283. package/src/fs.ts +118 -0
  284. package/src/host/agent-host.ts +124 -0
  285. package/src/host/environment.ts +31 -0
  286. package/src/host/handlers.ts +142 -0
  287. package/src/host/memory-port.ts +27 -0
  288. package/src/host/wiring.ts +164 -0
  289. package/src/lineage.ts +159 -0
  290. package/src/messages/deliver-message.ts +61 -0
  291. package/src/messages/deliver-notice.ts +120 -0
  292. package/src/messages/notice-notes.ts +34 -0
  293. package/src/messages/notice-store.ts +142 -0
  294. package/src/modes.ts +38 -0
  295. package/src/outbox/manual-clock.ts +31 -0
  296. package/src/outbox/outbox-worker.ts +214 -0
  297. package/src/outbox/outbox.ts +106 -0
  298. package/src/providers/accounts/accounts.ts +192 -0
  299. package/src/providers/accounts/launch.ts +55 -0
  300. package/src/providers/accounts/service.ts +632 -0
  301. package/src/providers/accounts/shadow-home.ts +85 -0
  302. package/src/providers/accounts/status.ts +106 -0
  303. package/src/providers/accounts/store.ts +158 -0
  304. package/src/providers/accounts/test-accounts.ts +48 -0
  305. package/src/providers/accounts/variables.ts +132 -0
  306. package/src/providers/catalog.ts +90 -0
  307. package/src/providers/claude-models.json +114 -0
  308. package/src/providers/claude-provider.ts +32 -0
  309. package/src/providers/claude.ts +123 -0
  310. package/src/providers/codex-models.json +135 -0
  311. package/src/providers/codex-provider.ts +30 -0
  312. package/src/providers/codex-rules.ts +54 -0
  313. package/src/providers/codex.ts +69 -0
  314. package/src/providers/detect.ts +40 -0
  315. package/src/providers/provider.ts +46 -0
  316. package/src/providers/registry.ts +150 -0
  317. package/src/record-directory.ts +136 -0
  318. package/src/run-process.ts +61 -0
  319. package/src/serializer.ts +41 -0
  320. package/src/skills.ts +199 -0
  321. package/src/tasks/background-limit.ts +72 -0
  322. package/src/tasks/end-children.ts +100 -0
  323. package/src/tasks/give-task.ts +52 -0
  324. package/src/tasks/task-coordinator.ts +384 -0
  325. package/src/tasks/task-store.ts +236 -0
  326. package/src/tasks/task-work.ts +62 -0
  327. package/src/tasks/waiting-child.ts +203 -0
  328. package/src/tasks/wake-parent.ts +142 -0
  329. package/src/tasks/wiring.ts +231 -0
  330. package/src/title-file.ts +57 -0
  331. package/src/usage/accounts.ts +86 -0
  332. package/src/usage/aggregate.ts +220 -0
  333. package/src/usage/exchange.ts +93 -0
  334. package/src/usage/index-file.ts +178 -0
  335. package/src/usage/limits/monitor.ts +239 -0
  336. package/src/usage/limits/normalize.ts +188 -0
  337. package/src/usage/limits/probe.ts +120 -0
  338. package/src/usage/prices-snapshot.json +1071 -0
  339. package/src/usage/pricing.ts +236 -0
  340. package/src/usage/projects.ts +65 -0
  341. package/src/usage/readers/claude.ts +60 -0
  342. package/src/usage/readers/codex.ts +196 -0
  343. package/src/usage/record.ts +60 -0
  344. package/src/usage/roots.ts +39 -0
  345. package/src/usage/scanner.ts +265 -0
  346. package/src/usage/usage-service.ts +161 -0
  347. package/src/watch-seam.ts +111 -0
  348. package/src/watch-test-helpers.ts +102 -0
@@ -0,0 +1,34 @@
1
+ import type { UsagePriceBasis, UsagePricing, UsageTotals } from '@adecore/agent-contracts';
2
+ export declare const LITELLM_URL = "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json";
3
+ export interface ModelPrice {
4
+ input: number;
5
+ output: number;
6
+ cacheRead: number;
7
+ cacheWrite: number;
8
+ cacheWrite1h: number;
9
+ }
10
+ export type PriceTable = Map<string, ModelPrice>;
11
+ export interface PriceLookup {
12
+ price: ModelPrice | null;
13
+ basis: UsagePriceBasis;
14
+ pricedAs: string | null;
15
+ }
16
+ export declare function parsePriceTable(document: unknown): PriceTable;
17
+ export declare function lookupPrice(table: PriceTable, model: string): PriceLookup;
18
+ export declare function costOf(totals: UsageTotals, price: ModelPrice): number;
19
+ export declare function cacheSavingsOf(totals: UsageTotals, price: ModelPrice): number;
20
+ export declare class PriceBook {
21
+ private readonly home;
22
+ private readonly allowFetch;
23
+ private table;
24
+ private source;
25
+ private fetchedAt;
26
+ private loadedFromDisk;
27
+ private inFlight;
28
+ constructor(home: string, allowFetch?: boolean);
29
+ get pricing(): UsagePricing;
30
+ look(model: string): PriceLookup;
31
+ ensure(): Promise<void>;
32
+ private get file();
33
+ private refresh;
34
+ }
@@ -0,0 +1,186 @@
1
+ import { mkdir, readFile } from 'node:fs/promises';
2
+ import { join } from 'node:path';
3
+ import { isNotFound, writeAtomic } from '../fs.js';
4
+ import { errorText } from '../error-text.js';
5
+ import bundled from './prices-snapshot.json' with { type: 'json' };
6
+ export const LITELLM_URL = 'https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json';
7
+ /* A table a day old still prices everything anyone ran today, and nobody has to be online for it. */
8
+ const TTL_MS = 24 * 60 * 60 * 1000;
9
+ const FETCH_TIMEOUT_MS = 10_000;
10
+ /* What Anthropic charges over the base input rate when the table itself says nothing. */
11
+ const CACHE_READ_SHARE = 0.1;
12
+ const CACHE_WRITE_SHARE = 1.25;
13
+ const CACHE_WRITE_1H_SHARE = 2;
14
+ /* A bare family name prices nothing: `opus` and `sonnet` are several models with several prices. */
15
+ const AMBIGUOUS = new Set(['opus', 'sonnet', 'haiku', 'fable', 'gpt', 'codex', 'synthetic', '<synthetic>']);
16
+ const PROVIDERS = new Set(['anthropic', 'openai']);
17
+ const MODES = new Set(['chat', 'responses']);
18
+ function rate(value) {
19
+ return typeof value === 'number' && Number.isFinite(value) && value >= 0 ? value : null;
20
+ }
21
+ /*
22
+ * The LiteLLM table as this app needs it: the Anthropic and OpenAI chat and responses models, keyed
23
+ * by the lowercase name and by the bare name behind a provider prefix. A model priced on one side
24
+ * only is dropped, because half a price reads as a cheap model rather than an unknown one.
25
+ */
26
+ export function parsePriceTable(document) {
27
+ const table = new Map();
28
+ if (typeof document !== 'object' || document === null) {
29
+ return table;
30
+ }
31
+ for (const [name, value] of Object.entries(document)) {
32
+ if (typeof value !== 'object' || value === null) {
33
+ continue;
34
+ }
35
+ if (typeof value.litellm_provider !== 'string' || !PROVIDERS.has(value.litellm_provider)) {
36
+ continue;
37
+ }
38
+ if (typeof value.mode !== 'string' || !MODES.has(value.mode)) {
39
+ continue;
40
+ }
41
+ const input = rate(value.input_cost_per_token);
42
+ const output = rate(value.output_cost_per_token);
43
+ if (input === null || output === null) {
44
+ continue;
45
+ }
46
+ const price = {
47
+ input,
48
+ output,
49
+ cacheRead: rate(value.cache_read_input_token_cost) ?? input * CACHE_READ_SHARE,
50
+ cacheWrite: rate(value.cache_creation_input_token_cost) ?? input * CACHE_WRITE_SHARE,
51
+ cacheWrite1h: input * CACHE_WRITE_1H_SHARE
52
+ };
53
+ const key = name.trim().toLowerCase();
54
+ table.set(key, price);
55
+ const bare = key.slice(key.lastIndexOf('/') + 1);
56
+ if (bare !== key && !table.has(bare)) {
57
+ table.set(bare, price);
58
+ }
59
+ }
60
+ return table;
61
+ }
62
+ /* `claude-opus-4-5-20251101` is the same model as `claude-opus-4-5`, dated. */
63
+ function withoutDate(key) {
64
+ const match = /^(.*)-\d{6,8}$/.exec(key);
65
+ return match === null ? null : match[1];
66
+ }
67
+ /*
68
+ * Exact first, then the same name without its date, then the longest family the table knows. A name
69
+ * nobody prices is left unpriced rather than guessed at: `$0.00` for a model that costs money is a
70
+ * worse answer than a question mark.
71
+ */
72
+ export function lookupPrice(table, model) {
73
+ const key = model.trim().toLowerCase().split('[')[0];
74
+ const bare = key.slice(key.lastIndexOf('/') + 1);
75
+ if (bare === '' || AMBIGUOUS.has(bare)) {
76
+ return { price: null, basis: 'unknown', pricedAs: null };
77
+ }
78
+ const exact = table.get(key) ?? table.get(bare);
79
+ if (exact !== undefined) {
80
+ return { price: exact, basis: 'exact', pricedAs: null };
81
+ }
82
+ const dated = withoutDate(bare);
83
+ const undated = dated === null ? undefined : table.get(dated);
84
+ if (undated !== undefined && dated !== null) {
85
+ return { price: undated, basis: 'exact', pricedAs: dated };
86
+ }
87
+ for (let family = dated ?? bare; family.includes('-');) {
88
+ family = family.slice(0, family.lastIndexOf('-'));
89
+ const found = AMBIGUOUS.has(family) ? undefined : table.get(family);
90
+ if (found !== undefined) {
91
+ return { price: found, basis: 'family', pricedAs: family };
92
+ }
93
+ }
94
+ return { price: null, basis: 'unknown', pricedAs: null };
95
+ }
96
+ /* Reasoning tokens are a part of the output and are never charged a second time. */
97
+ export function costOf(totals, price) {
98
+ return (totals.input * price.input +
99
+ totals.cacheRead * price.cacheRead +
100
+ Math.max(0, totals.cacheWrite - totals.cacheWrite1h) * price.cacheWrite +
101
+ totals.cacheWrite1h * price.cacheWrite1h +
102
+ totals.output * price.output);
103
+ }
104
+ /* What reading from the cache saved against sending the same tokens again. The premium a write
105
+ costs is not taken off: it was paid for the turn that wrote, not for the ones that read. */
106
+ export function cacheSavingsOf(totals, price) {
107
+ return totals.cacheRead * (price.input - price.cacheRead);
108
+ }
109
+ /*
110
+ * One price table for the whole host. It starts on the snapshot that ships with the app, so the
111
+ * first page ever opened already carries prices, then reads the newer copy on disk, then asks
112
+ * LiteLLM once a day. Every step is optional: with the fetch off and nothing on disk the bundled
113
+ * table is what prices everything.
114
+ */
115
+ export class PriceBook {
116
+ home;
117
+ allowFetch;
118
+ table;
119
+ source = 'snapshot';
120
+ fetchedAt = null;
121
+ loadedFromDisk = false;
122
+ inFlight = null;
123
+ constructor(home, allowFetch = true) {
124
+ this.home = home;
125
+ this.allowFetch = allowFetch;
126
+ this.table = parsePriceTable(bundled);
127
+ }
128
+ get pricing() {
129
+ return { source: this.table.size === 0 ? 'none' : this.source, fetchedAt: this.fetchedAt, models: this.table.size };
130
+ }
131
+ look(model) {
132
+ return lookupPrice(this.table, model);
133
+ }
134
+ /* Refreshes at most one table at a time; a second caller waits for the first one's answer. */
135
+ ensure() {
136
+ this.inFlight ??= this.refresh().finally(() => {
137
+ this.inFlight = null;
138
+ });
139
+ return this.inFlight;
140
+ }
141
+ get file() {
142
+ return join(this.home, 'usage', 'prices.json');
143
+ }
144
+ async refresh() {
145
+ if (!this.loadedFromDisk) {
146
+ this.loadedFromDisk = true;
147
+ try {
148
+ const snapshot = JSON.parse(await readFile(this.file, 'utf8'));
149
+ const table = parsePriceTable(snapshot?.document);
150
+ if (table.size > 0 && typeof snapshot.fetchedAt === 'number') {
151
+ this.table = table;
152
+ this.source = 'litellm';
153
+ this.fetchedAt = snapshot.fetchedAt;
154
+ }
155
+ }
156
+ catch (e) {
157
+ if (!isNotFound(e)) {
158
+ console.warn('The stored price table would not read; using the bundled one:', errorText(e));
159
+ }
160
+ }
161
+ }
162
+ if (!this.allowFetch || (this.fetchedAt !== null && Date.now() - this.fetchedAt < TTL_MS)) {
163
+ return;
164
+ }
165
+ try {
166
+ const response = await fetch(LITELLM_URL, { signal: AbortSignal.timeout(FETCH_TIMEOUT_MS) });
167
+ if (!response.ok) {
168
+ throw new Error(`LiteLLM answered ${response.status}`);
169
+ }
170
+ const document = await response.json();
171
+ const table = parsePriceTable(document);
172
+ if (table.size === 0) {
173
+ throw new Error('The LiteLLM table held no model this app prices');
174
+ }
175
+ this.table = table;
176
+ this.source = 'litellm';
177
+ this.fetchedAt = Date.now();
178
+ await mkdir(join(this.home, 'usage'), { recursive: true, mode: 0o700 });
179
+ await writeAtomic(this.file, JSON.stringify({ fetchedAt: this.fetchedAt, document }));
180
+ }
181
+ catch (e) {
182
+ // Whatever is loaded keeps pricing; a table that could not be refreshed is not a failure.
183
+ console.warn('Could not refresh the price table:', errorText(e));
184
+ }
185
+ }
186
+ }
@@ -0,0 +1,17 @@
1
+ export interface KnownProject {
2
+ projectId: string;
3
+ name: string;
4
+ folder: string;
5
+ }
6
+ export interface ResolvedProject {
7
+ folder: string;
8
+ name: string;
9
+ projectId: string | null;
10
+ }
11
+ export declare class ProjectResolver {
12
+ private readonly known;
13
+ private readonly roots;
14
+ constructor(known: readonly KnownProject[]);
15
+ resolve(cwd: string): Promise<ResolvedProject>;
16
+ private rootOf;
17
+ }
@@ -0,0 +1,49 @@
1
+ import { readFile, stat } from 'node:fs/promises';
2
+ import { basename, dirname, join } from 'node:path';
3
+ /*
4
+ * A checkout that is a worktree keeps its `.git` in a file pointing back into the repository it
5
+ * belongs to. The work done there is the same repository's work, so both fold to the one folder.
6
+ */
7
+ async function mainCheckoutOf(gitPath) {
8
+ const text = await readFile(gitPath, 'utf8');
9
+ const match = /^gitdir:\s*(.+)$/m.exec(text);
10
+ const gitDir = match?.[1]?.trim();
11
+ const at = gitDir?.indexOf('/.git/worktrees/') ?? -1;
12
+ return gitDir !== undefined && at > 0 ? gitDir.slice(0, at) : null;
13
+ }
14
+ /*
15
+ * Which folder a directory's work belongs to: the repository it sits in, or the directory itself
16
+ * when it is in none. Every answer is remembered, because one scan asks about the same handful of
17
+ * directories tens of thousands of times.
18
+ */
19
+ export class ProjectResolver {
20
+ known;
21
+ roots = new Map();
22
+ constructor(known) {
23
+ this.known = new Map(known.flatMap((project) => (project.folder === '' ? [] : [[project.folder, project]])));
24
+ }
25
+ async resolve(cwd) {
26
+ const folder = cwd === '' ? '' : await this.rootOf(cwd);
27
+ const project = this.known.get(folder);
28
+ return { folder, name: project?.name ?? (folder === '' ? 'Elsewhere' : basename(folder)), projectId: project?.projectId ?? null };
29
+ }
30
+ async rootOf(cwd) {
31
+ const cached = this.roots.get(cwd);
32
+ if (cached !== undefined) {
33
+ return cached;
34
+ }
35
+ let root = cwd;
36
+ for (let at = cwd; at !== dirname(at); at = dirname(at)) {
37
+ try {
38
+ const info = await stat(join(at, '.git'));
39
+ root = (info.isFile() ? await mainCheckoutOf(join(at, '.git')) : null) ?? at;
40
+ break;
41
+ }
42
+ catch {
43
+ // Not a checkout at this level; the one above it may be.
44
+ }
45
+ }
46
+ this.roots.set(cwd, root);
47
+ return root;
48
+ }
49
+ }
@@ -0,0 +1,3 @@
1
+ import { type UsageRecord } from '../record.ts';
2
+ export declare function claudeMightCarryUsage(line: string): boolean;
3
+ export declare function parseClaudeLine(line: string): UsageRecord | null;
@@ -0,0 +1,59 @@
1
+ import { asObject, asString, int } from '../record.js';
2
+ /* Only a line with this in it can carry counts, which keeps `JSON.parse` off about two thirds of them. */
3
+ export function claudeMightCarryUsage(line) {
4
+ return line.includes('"usage"');
5
+ }
6
+ /*
7
+ * One line of `~/.claude/projects/**\/*.jsonl`. An assistant line carries `message.usage` with the
8
+ * counts of the call that produced it; every other kind of line has nothing to price. The counts are
9
+ * Anthropic's, so `input_tokens` already excludes what was read from or written to the cache.
10
+ */
11
+ export function parseClaudeLine(line) {
12
+ let parsed;
13
+ try {
14
+ parsed = JSON.parse(line);
15
+ }
16
+ catch {
17
+ return null;
18
+ }
19
+ const record = asObject(parsed);
20
+ if (record === null || record.type !== 'assistant') {
21
+ return null;
22
+ }
23
+ const message = asObject(record.message);
24
+ const usage = message === null ? null : asObject(message.usage);
25
+ if (message === null || usage === null) {
26
+ return null;
27
+ }
28
+ const model = asString(message.model);
29
+ const timestampMs = Date.parse(asString(record.timestamp));
30
+ // `<synthetic>` is what Claude Code writes for a message it made up itself, an error or an
31
+ // interruption. It never reached the API, and its counts are zero, so it is not a call.
32
+ if (model === '' || model === '<synthetic>' || Number.isNaN(timestampMs)) {
33
+ return null;
34
+ }
35
+ const creation = asObject(usage.cache_creation);
36
+ const outputDetails = asObject(usage.output_tokens_details);
37
+ const messageId = asString(message.id);
38
+ const totals = {
39
+ calls: 1,
40
+ input: int(usage.input_tokens),
41
+ cacheRead: int(usage.cache_read_input_tokens),
42
+ cacheWrite: int(usage.cache_creation_input_tokens),
43
+ cacheWrite1h: creation === null ? 0 : int(creation.ephemeral_1h_input_tokens),
44
+ output: int(usage.output_tokens),
45
+ reasoning: outputDetails === null ? 0 : int(outputDetails.thinking_tokens)
46
+ };
47
+ if (totals.input + totals.cacheRead + totals.cacheWrite + totals.output === 0) {
48
+ return null;
49
+ }
50
+ return {
51
+ provider: 'claude',
52
+ timestampMs,
53
+ model,
54
+ sessionId: asString(record.sessionId),
55
+ cwd: asString(record.cwd),
56
+ totals,
57
+ dedupeKey: messageId === '' ? null : messageId
58
+ };
59
+ }
@@ -0,0 +1,23 @@
1
+ import { type UsageRecord } from '../record.ts';
2
+ export declare function codexMightCarryUsage(line: string): boolean;
3
+ interface CodexCounts {
4
+ input: number;
5
+ cached: number;
6
+ cacheWrite: number;
7
+ output: number;
8
+ reasoning: number;
9
+ }
10
+ export interface CodexParserState {
11
+ model: string | null;
12
+ sessionId: string;
13
+ cwd: string;
14
+ total: CodexCounts | null;
15
+ sawSessionMeta: boolean;
16
+ suppressing: boolean;
17
+ anchorMs: number;
18
+ lastSignature: string | null;
19
+ }
20
+ export declare function createCodexState(): CodexParserState;
21
+ export declare function cloneCodexState(state: CodexParserState): CodexParserState;
22
+ export declare function parseCodexLine(line: string, state: CodexParserState): UsageRecord | null;
23
+ export {};
@@ -0,0 +1,155 @@
1
+ import { asObject, asString, int } from '../record.js';
2
+ /* A `token_count` line carries the counts; the other two carry the model and the directory they belong to. */
3
+ export function codexMightCarryUsage(line) {
4
+ return line.includes('token_count') || line.includes('turn_context') || line.includes('session_meta');
5
+ }
6
+ /* A fork replays its ancestors' events into the new file the moment it is created, so anything this
7
+ close to the file's own start is a copy of work that was already counted somewhere else. */
8
+ const FORK_COPY_MAX_GAP_MS = 1_000;
9
+ export function createCodexState() {
10
+ return {
11
+ model: null,
12
+ sessionId: '',
13
+ cwd: '',
14
+ total: null,
15
+ sawSessionMeta: false,
16
+ suppressing: false,
17
+ anchorMs: 0,
18
+ lastSignature: null
19
+ };
20
+ }
21
+ export function cloneCodexState(state) {
22
+ return { ...state, total: state.total === null ? null : { ...state.total } };
23
+ }
24
+ function countsOf(usage) {
25
+ return {
26
+ input: int(usage.input_tokens),
27
+ cached: int(usage.cached_input_tokens),
28
+ cacheWrite: int(usage.cache_write_input_tokens),
29
+ output: int(usage.output_tokens),
30
+ reasoning: int(usage.reasoning_output_tokens)
31
+ };
32
+ }
33
+ function deltaOf(total, previous) {
34
+ if (previous === null) {
35
+ return total;
36
+ }
37
+ const delta = {
38
+ input: total.input - previous.input,
39
+ cached: total.cached - previous.cached,
40
+ cacheWrite: total.cacheWrite - previous.cacheWrite,
41
+ output: total.output - previous.output,
42
+ reasoning: total.reasoning - previous.reasoning
43
+ };
44
+ // A compaction or a resume restarts the count, so anything falling is not a delta at all.
45
+ return delta.input < 0 || delta.cached < 0 || delta.cacheWrite < 0 || delta.output < 0 || delta.reasoning < 0 ? null : delta;
46
+ }
47
+ /* A thread that was forked or spawned repeats its parent's events before its own work starts. */
48
+ function isForkOrSubagent(payload) {
49
+ if (typeof payload.forked_from_id === 'string' || typeof payload.parent_thread_id === 'string') {
50
+ return true;
51
+ }
52
+ const spawn = asObject(asObject(payload.source)?.subagent)?.thread_spawn;
53
+ return typeof asObject(spawn)?.parent_thread_id === 'string';
54
+ }
55
+ /*
56
+ * One line of `~/.codex/sessions/**\/*.jsonl`, against the state of the file it came from. Codex
57
+ * reports a cumulative total per turn, so the call is the difference with the line before it; when
58
+ * that difference falls the run started over and `last_token_usage` is the turn on its own. Codex
59
+ * counts cache reads and writes inside `input_tokens`, unlike Anthropic, so what is left after
60
+ * taking both out is the input that was actually paid for at the full rate.
61
+ */
62
+ export function parseCodexLine(line, state) {
63
+ let parsed;
64
+ try {
65
+ parsed = JSON.parse(line);
66
+ }
67
+ catch {
68
+ return null;
69
+ }
70
+ const record = asObject(parsed);
71
+ const payload = record === null ? null : asObject(record.payload);
72
+ if (record === null || payload === null) {
73
+ return null;
74
+ }
75
+ if (record.type === 'session_meta') {
76
+ // A fork carries its ancestors' metas as well; only the file's own says what the file is.
77
+ if (state.sawSessionMeta) {
78
+ return null;
79
+ }
80
+ state.sawSessionMeta = true;
81
+ state.sessionId = asString(payload.id) || asString(payload.session_id);
82
+ state.cwd = asString(payload.cwd);
83
+ if (isForkOrSubagent(payload)) {
84
+ state.suppressing = true;
85
+ state.anchorMs = Date.parse(asString(record.timestamp)) || 0;
86
+ }
87
+ return null;
88
+ }
89
+ if (record.type === 'turn_context') {
90
+ const model = asString(payload.model);
91
+ if (model !== '') {
92
+ state.model = model;
93
+ }
94
+ const cwd = asString(payload.cwd);
95
+ if (cwd !== '') {
96
+ state.cwd = cwd;
97
+ }
98
+ return null;
99
+ }
100
+ if (payload.type !== 'token_count') {
101
+ return null;
102
+ }
103
+ const info = asObject(payload.info);
104
+ const total = info === null ? null : asObject(info.total_token_usage);
105
+ if (info === null || total === null || state.model === null) {
106
+ return null;
107
+ }
108
+ const timestampMs = Date.parse(asString(record.timestamp));
109
+ if (Number.isNaN(timestampMs)) {
110
+ return null;
111
+ }
112
+ const signature = JSON.stringify(info.total_token_usage);
113
+ if (signature === state.lastSignature) {
114
+ return null;
115
+ }
116
+ state.lastSignature = signature;
117
+ const cumulative = countsOf(total);
118
+ const last = asObject(info.last_token_usage);
119
+ const counts = deltaOf(cumulative, state.total) ?? (last === null ? null : countsOf(last));
120
+ state.total = cumulative;
121
+ if (counts === null) {
122
+ return null;
123
+ }
124
+ if (state.suppressing) {
125
+ if (timestampMs - state.anchorMs <= FORK_COPY_MAX_GAP_MS) {
126
+ state.anchorMs = timestampMs;
127
+ return null;
128
+ }
129
+ // The first event that is not part of the replayed burst is this thread's own work.
130
+ state.suppressing = false;
131
+ }
132
+ const uncached = Math.max(0, counts.input - counts.cached - counts.cacheWrite);
133
+ const totals = {
134
+ calls: 1,
135
+ input: uncached,
136
+ cacheRead: counts.cached,
137
+ cacheWrite: counts.cacheWrite,
138
+ cacheWrite1h: 0,
139
+ output: counts.output,
140
+ reasoning: Math.min(counts.output, counts.reasoning)
141
+ };
142
+ if (uncached + counts.cached + counts.cacheWrite + counts.output === 0) {
143
+ return null;
144
+ }
145
+ return {
146
+ provider: 'codex',
147
+ timestampMs,
148
+ model: state.model,
149
+ sessionId: state.sessionId,
150
+ cwd: state.cwd,
151
+ totals,
152
+ // Fork copies are gone by here, so every event that is left is a call of its own.
153
+ dedupeKey: null
154
+ };
155
+ }
@@ -0,0 +1,15 @@
1
+ import type { UsageProvider, UsageTotals } from '@adecore/agent-contracts';
2
+ export interface UsageRecord {
3
+ provider: UsageProvider;
4
+ timestampMs: number;
5
+ model: string;
6
+ sessionId: string;
7
+ cwd: string;
8
+ account?: string;
9
+ totals: UsageTotals;
10
+ dedupeKey: string | null;
11
+ }
12
+ export declare function int(value: unknown): number;
13
+ export declare function asObject(value: unknown): Record<string, unknown> | null;
14
+ export declare function asString(value: unknown): string;
15
+ export declare function foldByKey(records: readonly UsageRecord[]): UsageRecord[];
@@ -0,0 +1,39 @@
1
+ /* A count on the wire is a whole non-negative number, whatever a transcript put in the field. */
2
+ export function int(value) {
3
+ return typeof value === 'number' && Number.isFinite(value) && value > 0 ? Math.trunc(value) : 0;
4
+ }
5
+ /* A field a transcript wrote as anything; a reader takes what it can use and nothing else. */
6
+ export function asObject(value) {
7
+ return typeof value === 'object' && value !== null ? value : null;
8
+ }
9
+ export function asString(value) {
10
+ return typeof value === 'string' ? value : '';
11
+ }
12
+ function maxTotals(into, from) {
13
+ into.calls = Math.max(into.calls, from.calls);
14
+ into.input = Math.max(into.input, from.input);
15
+ into.cacheRead = Math.max(into.cacheRead, from.cacheRead);
16
+ into.cacheWrite = Math.max(into.cacheWrite, from.cacheWrite);
17
+ into.cacheWrite1h = Math.max(into.cacheWrite1h, from.cacheWrite1h);
18
+ into.output = Math.max(into.output, from.output);
19
+ into.reasoning = Math.max(into.reasoning, from.reasoning);
20
+ }
21
+ // Claude repeats growing usage per message and across resumed transcripts; keep each field's maximum.
22
+ export function foldByKey(records) {
23
+ const folded = [];
24
+ const at = new Map();
25
+ for (const record of records) {
26
+ if (record.dedupeKey === null) {
27
+ folded.push(record);
28
+ continue;
29
+ }
30
+ const seen = at.get(record.dedupeKey);
31
+ if (seen === undefined) {
32
+ at.set(record.dedupeKey, folded.length);
33
+ folded.push({ ...record, totals: { ...record.totals } });
34
+ continue;
35
+ }
36
+ maxTotals(folded[seen].totals, record.totals);
37
+ }
38
+ return folded;
39
+ }
@@ -0,0 +1,12 @@
1
+ import type { UsageProvider } from '@adecore/agent-contracts';
2
+ export interface UsageRootPath {
3
+ provider: UsageProvider;
4
+ path: string;
5
+ accounts?: string[];
6
+ }
7
+ export declare function usageRoots(env?: Record<string, string | undefined>): UsageRootPath[];
8
+ export declare function accountRoots(folders: ReadonlyArray<{
9
+ id: string;
10
+ kind: UsageProvider;
11
+ folder: string;
12
+ }>): UsageRootPath[];
@@ -0,0 +1,29 @@
1
+ import { homedir } from 'node:os';
2
+ import { join } from 'node:path';
3
+ /* Where in its config folder each CLI leaves its transcripts. */
4
+ const TRANSCRIPTS = { claude: 'projects', codex: 'sessions' };
5
+ /* Where each CLI leaves its transcripts. The same environment variables the hooks installer reads,
6
+ so the scanner and the installer never disagree about where a CLI keeps its things. */
7
+ export function usageRoots(env = process.env) {
8
+ const home = env.HOME ?? homedir();
9
+ return [
10
+ { provider: 'claude', path: join(env.CLAUDE_CONFIG_DIR ?? join(home, '.claude'), TRANSCRIPTS.claude) },
11
+ { provider: 'codex', path: join(env.CODEX_HOME ?? join(home, '.codex'), TRANSCRIPTS.codex) }
12
+ ];
13
+ }
14
+ /* The transcripts of every account, once per folder: Codex accounts over one home share its sessions. */
15
+ export function accountRoots(folders) {
16
+ const roots = new Map();
17
+ for (const { id, kind, folder } of folders) {
18
+ const path = join(folder, TRANSCRIPTS[kind]);
19
+ const key = `${kind}\0${path}`;
20
+ const root = roots.get(key);
21
+ if (root === undefined) {
22
+ roots.set(key, { provider: kind, path, accounts: [id] });
23
+ }
24
+ else {
25
+ root.accounts.push(id);
26
+ }
27
+ }
28
+ return [...roots.values()];
29
+ }
@@ -0,0 +1,30 @@
1
+ import type { UsageRoot } from '@adecore/agent-contracts';
2
+ import { type UsageRecord } from './record.ts';
3
+ import { type UsageRootPath } from './roots.ts';
4
+ export interface ScanReport {
5
+ at: number;
6
+ files: number;
7
+ changedFiles: number;
8
+ durationMs: number;
9
+ roots: UsageRoot[];
10
+ }
11
+ export interface UsageScannerOptions {
12
+ sessionAccounts?: () => ReadonlyMap<string, string>;
13
+ }
14
+ type AccountOf = (record: UsageRecord) => string | undefined;
15
+ export declare function accountResolver(root: UsageRootPath, sessionAccounts: () => ReadonlyMap<string, string>): AccountOf;
16
+ export declare class UsageScanner {
17
+ private readonly home;
18
+ private readonly roots;
19
+ private readonly sessionAccounts;
20
+ private index;
21
+ private loaded;
22
+ private dirty;
23
+ constructor(home: string, roots?: UsageRootPath[] | (() => UsageRootPath[]), options?: UsageScannerOptions);
24
+ private get file();
25
+ load(): Promise<void>;
26
+ scan(): Promise<ScanReport>;
27
+ records(): UsageRecord[];
28
+ private readFileInto;
29
+ }
30
+ export {};