@adecore/agents-react 0.0.1 → 0.17.0-beta.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 (513) hide show
  1. package/LICENSE +110 -0
  2. package/README.md +53 -0
  3. package/dist/agents/AccountDot.d.ts +4 -0
  4. package/dist/agents/AccountDot.js +8 -0
  5. package/dist/agents/AgentIcon.d.ts +6 -0
  6. package/dist/agents/AgentIcon.js +20 -0
  7. package/dist/agents/ModelOptionControl.d.ts +15 -0
  8. package/dist/agents/ModelOptionControl.js +33 -0
  9. package/dist/agents/ProviderLogo.d.ts +8 -0
  10. package/dist/agents/ProviderLogo.js +8 -0
  11. package/dist/agents/account-limits.d.ts +7 -0
  12. package/dist/agents/account-limits.js +40 -0
  13. package/dist/agents/accounts.d.ts +22 -0
  14. package/dist/agents/accounts.js +113 -0
  15. package/dist/agents/model-name.d.ts +12 -0
  16. package/dist/agents/model-name.js +56 -0
  17. package/dist/agents/model-options.d.ts +5 -0
  18. package/dist/agents/model-options.js +15 -0
  19. package/dist/agents/provider-paths.d.ts +2 -0
  20. package/dist/agents/provider-paths.js +8 -0
  21. package/dist/agents/status-look.d.ts +13 -0
  22. package/dist/agents/status-look.js +24 -0
  23. package/dist/chat/account-choice.d.ts +11 -0
  24. package/dist/chat/account-choice.js +30 -0
  25. package/dist/chat/actions.d.ts +6 -0
  26. package/dist/chat/actions.js +68 -0
  27. package/dist/chat/activity.d.ts +5 -0
  28. package/dist/chat/activity.js +6 -0
  29. package/dist/chat/attachments.d.ts +23 -0
  30. package/dist/chat/attachments.js +86 -0
  31. package/dist/chat/bookmarks.d.ts +14 -0
  32. package/dist/chat/bookmarks.js +73 -0
  33. package/dist/chat/chat-client.d.ts +55 -0
  34. package/dist/chat/chat-client.js +279 -0
  35. package/dist/chat/chat-references.d.ts +5 -0
  36. package/dist/chat/chat-references.js +5 -0
  37. package/dist/chat/drafts.d.ts +29 -0
  38. package/dist/chat/drafts.js +116 -0
  39. package/dist/chat/forks.d.ts +2 -0
  40. package/dist/chat/forks.js +28 -0
  41. package/dist/chat/guards.d.ts +12 -0
  42. package/dist/chat/guards.js +69 -0
  43. package/dist/chat/logic/ansi.d.ts +13 -0
  44. package/dist/chat/logic/ansi.js +185 -0
  45. package/dist/chat/logic/answers.d.ts +1 -0
  46. package/dist/chat/logic/answers.js +8 -0
  47. package/dist/chat/logic/bookmarks.d.ts +5 -0
  48. package/dist/chat/logic/bookmarks.js +33 -0
  49. package/dist/chat/logic/context-usage.d.ts +12 -0
  50. package/dist/chat/logic/context-usage.js +21 -0
  51. package/dist/chat/logic/fork.d.ts +45 -0
  52. package/dist/chat/logic/fork.js +148 -0
  53. package/dist/chat/logic/handback.d.ts +2 -0
  54. package/dist/chat/logic/handback.js +8 -0
  55. package/dist/chat/logic/json.d.ts +1 -0
  56. package/dist/chat/logic/json.js +4 -0
  57. package/dist/chat/logic/limit.d.ts +7 -0
  58. package/dist/chat/logic/limit.js +34 -0
  59. package/dist/chat/logic/resume-compaction.d.ts +14 -0
  60. package/dist/chat/logic/resume-compaction.js +47 -0
  61. package/dist/chat/logic/scrubber.d.ts +50 -0
  62. package/dist/chat/logic/scrubber.js +220 -0
  63. package/dist/chat/logic/thread-cards.d.ts +3 -0
  64. package/dist/chat/logic/thread-cards.js +59 -0
  65. package/dist/chat/logic/timeline-copy.d.ts +4 -0
  66. package/dist/chat/logic/timeline-copy.js +59 -0
  67. package/dist/chat/logic/timeline-target.d.ts +12 -0
  68. package/dist/chat/logic/timeline-target.js +34 -0
  69. package/dist/chat/logic/timeline.d.ts +115 -0
  70. package/dist/chat/logic/timeline.js +343 -0
  71. package/dist/chat/logic/tool-catalog.d.ts +10 -0
  72. package/dist/chat/logic/tool-catalog.js +44 -0
  73. package/dist/chat/logic/tools.d.ts +15 -0
  74. package/dist/chat/logic/tools.js +82 -0
  75. package/dist/chat/logic/welcome.d.ts +2 -0
  76. package/dist/chat/logic/welcome.js +10 -0
  77. package/dist/chat/logic/workflow.d.ts +14 -0
  78. package/dist/chat/logic/workflow.js +47 -0
  79. package/dist/chat/mentions.d.ts +65 -0
  80. package/dist/chat/mentions.js +184 -0
  81. package/dist/chat/persisted-json.d.ts +5 -0
  82. package/dist/chat/persisted-json.js +58 -0
  83. package/dist/chat/preferences.d.ts +27 -0
  84. package/dist/chat/preferences.js +139 -0
  85. package/dist/chat/quote.d.ts +2 -0
  86. package/dist/chat/quote.js +25 -0
  87. package/dist/chat/recent-messages.d.ts +17 -0
  88. package/dist/chat/recent-messages.js +36 -0
  89. package/dist/chat/resume-compaction-dismissals.d.ts +3 -0
  90. package/dist/chat/resume-compaction-dismissals.js +14 -0
  91. package/dist/chat/runtime-modes.d.ts +4 -0
  92. package/dist/chat/runtime-modes.js +15 -0
  93. package/dist/chat/stash.d.ts +24 -0
  94. package/dist/chat/stash.js +91 -0
  95. package/dist/chat/subagent-conversation.d.ts +41 -0
  96. package/dist/chat/subagent-conversation.js +167 -0
  97. package/dist/chat/subagent-list.d.ts +19 -0
  98. package/dist/chat/subagent-list.js +106 -0
  99. package/dist/chat/subagent-support.d.ts +6 -0
  100. package/dist/chat/subagent-support.js +14 -0
  101. package/dist/chat/subagent-view.d.ts +35 -0
  102. package/dist/chat/subagent-view.js +96 -0
  103. package/dist/chat/thumbnails.d.ts +10 -0
  104. package/dist/chat/thumbnails.js +89 -0
  105. package/dist/chat/timeline-flash.d.ts +12 -0
  106. package/dist/chat/timeline-flash.js +16 -0
  107. package/dist/chat/timeline-scroll.d.ts +27 -0
  108. package/dist/chat/timeline-scroll.js +152 -0
  109. package/dist/chat/ui/AccountPill.d.ts +3 -0
  110. package/dist/chat/ui/AccountPill.js +23 -0
  111. package/dist/chat/ui/AnsiOutput.d.ts +5 -0
  112. package/dist/chat/ui/AnsiOutput.js +9 -0
  113. package/dist/chat/ui/BookmarkMarker.d.ts +8 -0
  114. package/dist/chat/ui/BookmarkMarker.js +57 -0
  115. package/dist/chat/ui/BookmarkSubmenu.d.ts +3 -0
  116. package/dist/chat/ui/BookmarkSubmenu.js +33 -0
  117. package/dist/chat/ui/ChatActivity.d.ts +7 -0
  118. package/dist/chat/ui/ChatActivity.js +84 -0
  119. package/dist/chat/ui/ChatReferenceChip.d.ts +4 -0
  120. package/dist/chat/ui/ChatReferenceChip.js +16 -0
  121. package/dist/chat/ui/CodeBlock.d.ts +4 -0
  122. package/dist/chat/ui/CodeBlock.js +132 -0
  123. package/dist/chat/ui/Composer.d.ts +15 -0
  124. package/dist/chat/ui/Composer.js +856 -0
  125. package/dist/chat/ui/ComposerInput.d.ts +29 -0
  126. package/dist/chat/ui/ComposerInput.js +127 -0
  127. package/dist/chat/ui/DiffPool.d.ts +5 -0
  128. package/dist/chat/ui/DiffPool.js +26 -0
  129. package/dist/chat/ui/EditDiff.d.ts +4 -0
  130. package/dist/chat/ui/EditDiff.js +21 -0
  131. package/dist/chat/ui/FadingWords.d.ts +3 -0
  132. package/dist/chat/ui/FadingWords.js +5 -0
  133. package/dist/chat/ui/ImageView.d.ts +6 -0
  134. package/dist/chat/ui/ImageView.js +81 -0
  135. package/dist/chat/ui/LimitState.d.ts +8 -0
  136. package/dist/chat/ui/LimitState.js +112 -0
  137. package/dist/chat/ui/Markdown.d.ts +18 -0
  138. package/dist/chat/ui/Markdown.js +159 -0
  139. package/dist/chat/ui/MessageActions.d.ts +9 -0
  140. package/dist/chat/ui/MessageActions.js +53 -0
  141. package/dist/chat/ui/Pickers.d.ts +21 -0
  142. package/dist/chat/ui/Pickers.js +111 -0
  143. package/dist/chat/ui/PromptComposer.d.ts +13 -0
  144. package/dist/chat/ui/PromptComposer.js +56 -0
  145. package/dist/chat/ui/QuoteButton.d.ts +7 -0
  146. package/dist/chat/ui/QuoteButton.js +74 -0
  147. package/dist/chat/ui/ResumeCompactionDock.d.ts +5 -0
  148. package/dist/chat/ui/ResumeCompactionDock.js +14 -0
  149. package/dist/chat/ui/RunSettings.d.ts +28 -0
  150. package/dist/chat/ui/RunSettings.js +153 -0
  151. package/dist/chat/ui/Scrubber.d.ts +17 -0
  152. package/dist/chat/ui/Scrubber.js +118 -0
  153. package/dist/chat/ui/SubagentControls.d.ts +11 -0
  154. package/dist/chat/ui/SubagentControls.js +36 -0
  155. package/dist/chat/ui/SubagentInfo.d.ts +4 -0
  156. package/dist/chat/ui/SubagentInfo.js +34 -0
  157. package/dist/chat/ui/SubagentStopButton.d.ts +7 -0
  158. package/dist/chat/ui/SubagentStopButton.js +34 -0
  159. package/dist/chat/ui/SubagentTimeline.d.ts +4 -0
  160. package/dist/chat/ui/SubagentTimeline.js +107 -0
  161. package/dist/chat/ui/Timeline.d.ts +6 -0
  162. package/dist/chat/ui/Timeline.js +424 -0
  163. package/dist/chat/ui/TimelineMenu.d.ts +12 -0
  164. package/dist/chat/ui/TimelineMenu.js +54 -0
  165. package/dist/chat/ui/UnifiedDiff.d.ts +11 -0
  166. package/dist/chat/ui/UnifiedDiff.js +66 -0
  167. package/dist/chat/ui/UploadThumb.d.ts +4 -0
  168. package/dist/chat/ui/UploadThumb.js +11 -0
  169. package/dist/chat/ui/Welcome.d.ts +4 -0
  170. package/dist/chat/ui/Welcome.js +16 -0
  171. package/dist/chat/ui/chips.d.ts +4 -0
  172. package/dist/chat/ui/chips.js +11 -0
  173. package/dist/chat/ui/code-lines.d.ts +18 -0
  174. package/dist/chat/ui/code-lines.js +46 -0
  175. package/dist/chat/ui/code-streaming.d.ts +1 -0
  176. package/dist/chat/ui/code-streaming.js +3 -0
  177. package/dist/chat/ui/code-theme.d.ts +3 -0
  178. package/dist/chat/ui/code-theme.js +12 -0
  179. package/dist/chat/ui/composer/chips.d.ts +6 -0
  180. package/dist/chat/ui/composer/chips.js +31 -0
  181. package/dist/chat/ui/composer/editor.d.ts +11 -0
  182. package/dist/chat/ui/composer/editor.js +98 -0
  183. package/dist/chat/ui/composer/insert.d.ts +2 -0
  184. package/dist/chat/ui/composer/insert.js +19 -0
  185. package/dist/chat/ui/composer/keys.d.ts +30 -0
  186. package/dist/chat/ui/composer/keys.js +100 -0
  187. package/dist/chat/ui/diff-theme.d.ts +2 -0
  188. package/dist/chat/ui/diff-theme.js +20 -0
  189. package/dist/chat/ui/file-links.d.ts +5 -0
  190. package/dist/chat/ui/file-links.js +20 -0
  191. package/dist/chat/ui/find-reveal.d.ts +7 -0
  192. package/dist/chat/ui/find-reveal.js +13 -0
  193. package/dist/chat/ui/full-diff.d.ts +6 -0
  194. package/dist/chat/ui/full-diff.js +19 -0
  195. package/dist/chat/ui/icons.d.ts +4 -0
  196. package/dist/chat/ui/icons.js +20 -0
  197. package/dist/chat/ui/markdown-blocks.d.ts +6 -0
  198. package/dist/chat/ui/markdown-blocks.js +103 -0
  199. package/dist/chat/ui/quote-selection.d.ts +3 -0
  200. package/dist/chat/ui/quote-selection.js +46 -0
  201. package/dist/chat/ui/rehype-chips.d.ts +23 -0
  202. package/dist/chat/ui/rehype-chips.js +32 -0
  203. package/dist/chat/ui/rehype-fade.d.ts +23 -0
  204. package/dist/chat/ui/rehype-fade.js +36 -0
  205. package/dist/chat/ui/remark-html-as-text.d.ts +8 -0
  206. package/dist/chat/ui/remark-html-as-text.js +26 -0
  207. package/dist/chat/ui/reply-context.d.ts +5 -0
  208. package/dist/chat/ui/reply-context.js +2 -0
  209. package/dist/chat/ui/reveal.d.ts +13 -0
  210. package/dist/chat/ui/reveal.js +114 -0
  211. package/dist/chat/ui/rows/ForksRow.d.ts +4 -0
  212. package/dist/chat/ui/rows/ForksRow.js +38 -0
  213. package/dist/chat/ui/rows/MessageRows.d.ts +40 -0
  214. package/dist/chat/ui/rows/MessageRows.js +204 -0
  215. package/dist/chat/ui/rows/Rows.d.ts +12 -0
  216. package/dist/chat/ui/rows/Rows.js +61 -0
  217. package/dist/chat/ui/rows/SubagentRow.d.ts +17 -0
  218. package/dist/chat/ui/rows/SubagentRow.js +101 -0
  219. package/dist/chat/ui/rows/TaskRow.d.ts +8 -0
  220. package/dist/chat/ui/rows/TaskRow.js +63 -0
  221. package/dist/chat/ui/rows/WorkRows.d.ts +47 -0
  222. package/dist/chat/ui/rows/WorkRows.js +168 -0
  223. package/dist/chat/ui/rows/WorkflowRow.d.ts +8 -0
  224. package/dist/chat/ui/rows/WorkflowRow.js +48 -0
  225. package/dist/chat/ui/rows/row-rhythm.d.ts +6 -0
  226. package/dist/chat/ui/rows/row-rhythm.js +34 -0
  227. package/dist/chat/ui/useToggleSet.d.ts +6 -0
  228. package/dist/chat/ui/useToggleSet.js +25 -0
  229. package/dist/host.d.ts +195 -0
  230. package/dist/host.js +55 -0
  231. package/dist/lazy.d.ts +6 -0
  232. package/dist/lazy.js +57 -0
  233. package/dist/locales/en/agent-chat.json +446 -0
  234. package/dist/locales/en/agent-prompts.json +42 -0
  235. package/dist/locales/en/agent-providers.json +131 -0
  236. package/dist/locales/en/agent-usage.json +131 -0
  237. package/dist/locales/nl/agent-chat.json +446 -0
  238. package/dist/locales/nl/agent-prompts.json +42 -0
  239. package/dist/locales/nl/agent-providers.json +131 -0
  240. package/dist/locales/nl/agent-usage.json +131 -0
  241. package/dist/locales.d.ts +5 -0
  242. package/dist/locales.js +20 -0
  243. package/dist/mounted-registry.d.ts +7 -0
  244. package/dist/mounted-registry.js +39 -0
  245. package/dist/port-transport.d.ts +6 -0
  246. package/dist/port-transport.js +129 -0
  247. package/dist/prompts/logic/focus.d.ts +2 -0
  248. package/dist/prompts/logic/focus.js +23 -0
  249. package/dist/prompts/logic/keys.d.ts +42 -0
  250. package/dist/prompts/logic/keys.js +94 -0
  251. package/dist/prompts/logic/prompts.d.ts +34 -0
  252. package/dist/prompts/logic/prompts.fixtures.d.ts +5 -0
  253. package/dist/prompts/logic/prompts.fixtures.js +79 -0
  254. package/dist/prompts/logic/prompts.js +32 -0
  255. package/dist/prompts/logic/subjects.d.ts +50 -0
  256. package/dist/prompts/logic/subjects.js +63 -0
  257. package/dist/prompts/logic/useFocusAfterAnswer.d.ts +6 -0
  258. package/dist/prompts/logic/useFocusAfterAnswer.js +28 -0
  259. package/dist/prompts/logic/usePromptSession.d.ts +24 -0
  260. package/dist/prompts/logic/usePromptSession.js +87 -0
  261. package/dist/prompts/ui/ApprovalBody.d.ts +13 -0
  262. package/dist/prompts/ui/ApprovalBody.js +34 -0
  263. package/dist/prompts/ui/PromptActions.d.ts +27 -0
  264. package/dist/prompts/ui/PromptActions.js +18 -0
  265. package/dist/prompts/ui/PromptCard.d.ts +16 -0
  266. package/dist/prompts/ui/PromptCard.js +50 -0
  267. package/dist/prompts/ui/PromptView.d.ts +2 -0
  268. package/dist/prompts/ui/PromptView.js +47 -0
  269. package/dist/prompts/ui/QuestionBody.d.ts +9 -0
  270. package/dist/prompts/ui/QuestionBody.js +87 -0
  271. package/dist/providers/AccountColors.d.ts +5 -0
  272. package/dist/providers/AccountColors.js +8 -0
  273. package/dist/providers/AccountDetail.d.ts +12 -0
  274. package/dist/providers/AccountDetail.js +69 -0
  275. package/dist/providers/AccountVariables.d.ts +8 -0
  276. package/dist/providers/AccountVariables.js +43 -0
  277. package/dist/providers/AddAccountForm.d.ts +10 -0
  278. package/dist/providers/AddAccountForm.js +54 -0
  279. package/dist/providers/CliDetail.d.ts +12 -0
  280. package/dist/providers/CliDetail.js +54 -0
  281. package/dist/providers/ProvidersPane.d.ts +9 -0
  282. package/dist/providers/ProvidersPane.js +86 -0
  283. package/dist/providers/account-actions.d.ts +7 -0
  284. package/dist/providers/account-actions.js +57 -0
  285. package/dist/providers/parts.d.ts +9 -0
  286. package/dist/providers/parts.js +18 -0
  287. package/dist/providers/provider-abilities.d.ts +2 -0
  288. package/dist/providers/provider-abilities.js +7 -0
  289. package/dist/providers/variables.d.ts +13 -0
  290. package/dist/providers/variables.js +63 -0
  291. package/dist/scope.d.ts +11 -0
  292. package/dist/scope.js +10 -0
  293. package/dist/settings/sections.d.ts +13 -0
  294. package/dist/settings/sections.js +32 -0
  295. package/dist/state/chats.d.ts +44 -0
  296. package/dist/state/chats.js +199 -0
  297. package/dist/state/provider-accounts.d.ts +17 -0
  298. package/dist/state/provider-accounts.js +67 -0
  299. package/dist/state/providers.d.ts +17 -0
  300. package/dist/state/providers.js +33 -0
  301. package/dist/state/usage.d.ts +44 -0
  302. package/dist/state/usage.js +121 -0
  303. package/dist/storage.d.ts +20 -0
  304. package/dist/storage.js +89 -0
  305. package/dist/theme.css +196 -0
  306. package/dist/transport.d.ts +24 -0
  307. package/dist/transport.js +18 -0
  308. package/dist/ui/PlainTextarea.d.ts +2 -0
  309. package/dist/ui/PlainTextarea.js +5 -0
  310. package/dist/usage/LimitsList.d.ts +18 -0
  311. package/dist/usage/LimitsList.js +73 -0
  312. package/dist/usage/UsageBreakdown.d.ts +9 -0
  313. package/dist/usage/UsageBreakdown.js +67 -0
  314. package/dist/usage/UsageChart.d.ts +10 -0
  315. package/dist/usage/UsageChart.js +47 -0
  316. package/dist/usage/UsageDialog.d.ts +6 -0
  317. package/dist/usage/UsageDialog.js +12 -0
  318. package/dist/usage/UsageLimits.d.ts +1 -0
  319. package/dist/usage/UsageLimits.js +57 -0
  320. package/dist/usage/UsageLimitsCard.d.ts +4 -0
  321. package/dist/usage/UsageLimitsCard.js +26 -0
  322. package/dist/usage/UsagePage.d.ts +7 -0
  323. package/dist/usage/UsagePage.js +154 -0
  324. package/dist/usage/UsagePane.d.ts +3 -0
  325. package/dist/usage/UsagePane.js +21 -0
  326. package/dist/usage/UsageSummary.d.ts +12 -0
  327. package/dist/usage/UsageSummary.js +15 -0
  328. package/dist/usage/UsageTiles.d.ts +7 -0
  329. package/dist/usage/UsageTiles.js +19 -0
  330. package/dist/usage/format.d.ts +13 -0
  331. package/dist/usage/format.js +55 -0
  332. package/dist/usage/limit-groups.d.ts +22 -0
  333. package/dist/usage/limit-groups.js +86 -0
  334. package/dist/usage/limits.d.ts +4 -0
  335. package/dist/usage/limits.js +58 -0
  336. package/dist/usage/money.d.ts +1 -0
  337. package/dist/usage/money.js +13 -0
  338. package/dist/usage/summary.d.ts +34 -0
  339. package/dist/usage/summary.js +94 -0
  340. package/package.json +895 -3
  341. package/src/agents/AccountDot.tsx +14 -0
  342. package/src/agents/AgentIcon.tsx +26 -0
  343. package/src/agents/ModelOptionControl.tsx +68 -0
  344. package/src/agents/ProviderLogo.tsx +20 -0
  345. package/src/agents/account-limits.ts +61 -0
  346. package/src/agents/accounts.ts +137 -0
  347. package/src/agents/model-name.ts +70 -0
  348. package/src/agents/model-options.ts +21 -0
  349. package/src/agents/provider-paths.ts +10 -0
  350. package/src/agents/status-look.ts +43 -0
  351. package/src/chat/account-choice.ts +44 -0
  352. package/src/chat/actions.ts +72 -0
  353. package/src/chat/activity.ts +8 -0
  354. package/src/chat/attachments.ts +118 -0
  355. package/src/chat/bookmarks.ts +87 -0
  356. package/src/chat/chat-client.ts +345 -0
  357. package/src/chat/chat-references.ts +11 -0
  358. package/src/chat/drafts.ts +165 -0
  359. package/src/chat/forks.ts +33 -0
  360. package/src/chat/guards.ts +84 -0
  361. package/src/chat/logic/ansi.ts +206 -0
  362. package/src/chat/logic/answers.ts +8 -0
  363. package/src/chat/logic/bookmarks.ts +42 -0
  364. package/src/chat/logic/context-usage.ts +36 -0
  365. package/src/chat/logic/fork.ts +200 -0
  366. package/src/chat/logic/handback.ts +10 -0
  367. package/src/chat/logic/json.ts +4 -0
  368. package/src/chat/logic/limit.ts +46 -0
  369. package/src/chat/logic/resume-compaction.ts +71 -0
  370. package/src/chat/logic/scrubber.ts +289 -0
  371. package/src/chat/logic/thread-cards.ts +64 -0
  372. package/src/chat/logic/timeline-copy.ts +65 -0
  373. package/src/chat/logic/timeline-target.ts +52 -0
  374. package/src/chat/logic/timeline.ts +419 -0
  375. package/src/chat/logic/tool-catalog.ts +62 -0
  376. package/src/chat/logic/tools.ts +102 -0
  377. package/src/chat/logic/welcome.ts +13 -0
  378. package/src/chat/logic/workflow.ts +66 -0
  379. package/src/chat/mentions.ts +232 -0
  380. package/src/chat/persisted-json.ts +67 -0
  381. package/src/chat/preferences.ts +179 -0
  382. package/src/chat/quote.ts +26 -0
  383. package/src/chat/recent-messages.ts +59 -0
  384. package/src/chat/resume-compaction-dismissals.ts +17 -0
  385. package/src/chat/runtime-modes.ts +18 -0
  386. package/src/chat/stash.ts +122 -0
  387. package/src/chat/subagent-conversation.ts +203 -0
  388. package/src/chat/subagent-list.ts +141 -0
  389. package/src/chat/subagent-support.ts +21 -0
  390. package/src/chat/subagent-view.ts +137 -0
  391. package/src/chat/thumbnails.ts +108 -0
  392. package/src/chat/timeline-flash.ts +33 -0
  393. package/src/chat/timeline-scroll.ts +199 -0
  394. package/src/chat/ui/AccountPill.tsx +31 -0
  395. package/src/chat/ui/AnsiOutput.tsx +18 -0
  396. package/src/chat/ui/BookmarkMarker.tsx +108 -0
  397. package/src/chat/ui/BookmarkSubmenu.tsx +53 -0
  398. package/src/chat/ui/ChatActivity.tsx +198 -0
  399. package/src/chat/ui/ChatReferenceChip.tsx +28 -0
  400. package/src/chat/ui/CodeBlock.tsx +175 -0
  401. package/src/chat/ui/Composer.tsx +1326 -0
  402. package/src/chat/ui/ComposerInput.tsx +187 -0
  403. package/src/chat/ui/DiffPool.tsx +36 -0
  404. package/src/chat/ui/EditDiff.tsx +30 -0
  405. package/src/chat/ui/FadingWords.tsx +18 -0
  406. package/src/chat/ui/ImageView.tsx +159 -0
  407. package/src/chat/ui/LimitState.tsx +165 -0
  408. package/src/chat/ui/Markdown.tsx +258 -0
  409. package/src/chat/ui/MessageActions.tsx +88 -0
  410. package/src/chat/ui/Pickers.tsx +272 -0
  411. package/src/chat/ui/PromptComposer.tsx +119 -0
  412. package/src/chat/ui/QuoteButton.tsx +103 -0
  413. package/src/chat/ui/ResumeCompactionDock.tsx +28 -0
  414. package/src/chat/ui/RunSettings.tsx +546 -0
  415. package/src/chat/ui/Scrubber.tsx +265 -0
  416. package/src/chat/ui/SubagentControls.tsx +66 -0
  417. package/src/chat/ui/SubagentInfo.tsx +67 -0
  418. package/src/chat/ui/SubagentStopButton.tsx +45 -0
  419. package/src/chat/ui/SubagentTimeline.tsx +170 -0
  420. package/src/chat/ui/Timeline.tsx +628 -0
  421. package/src/chat/ui/TimelineMenu.tsx +124 -0
  422. package/src/chat/ui/UnifiedDiff.tsx +99 -0
  423. package/src/chat/ui/UploadThumb.tsx +13 -0
  424. package/src/chat/ui/Welcome.tsx +22 -0
  425. package/src/chat/ui/chips.ts +15 -0
  426. package/src/chat/ui/code-lines.ts +60 -0
  427. package/src/chat/ui/code-streaming.ts +4 -0
  428. package/src/chat/ui/code-theme.ts +15 -0
  429. package/src/chat/ui/composer/chips.ts +43 -0
  430. package/src/chat/ui/composer/editor.ts +118 -0
  431. package/src/chat/ui/composer/insert.ts +22 -0
  432. package/src/chat/ui/composer/keys.ts +144 -0
  433. package/src/chat/ui/diff-theme.ts +23 -0
  434. package/src/chat/ui/file-links.ts +24 -0
  435. package/src/chat/ui/find-reveal.ts +23 -0
  436. package/src/chat/ui/full-diff.ts +25 -0
  437. package/src/chat/ui/icons.tsx +24 -0
  438. package/src/chat/ui/markdown-blocks.ts +123 -0
  439. package/src/chat/ui/quote-selection.ts +53 -0
  440. package/src/chat/ui/rehype-chips.ts +62 -0
  441. package/src/chat/ui/rehype-fade.ts +64 -0
  442. package/src/chat/ui/remark-html-as-text.ts +36 -0
  443. package/src/chat/ui/reply-context.ts +4 -0
  444. package/src/chat/ui/reveal.ts +135 -0
  445. package/src/chat/ui/rows/ForksRow.tsx +79 -0
  446. package/src/chat/ui/rows/MessageRows.tsx +415 -0
  447. package/src/chat/ui/rows/Rows.tsx +81 -0
  448. package/src/chat/ui/rows/SubagentRow.tsx +209 -0
  449. package/src/chat/ui/rows/TaskRow.tsx +91 -0
  450. package/src/chat/ui/rows/WorkRows.tsx +445 -0
  451. package/src/chat/ui/rows/WorkflowRow.tsx +120 -0
  452. package/src/chat/ui/rows/row-rhythm.ts +37 -0
  453. package/src/chat/ui/useToggleSet.ts +39 -0
  454. package/src/host.ts +324 -0
  455. package/src/lazy.tsx +64 -0
  456. package/src/locales/en/agent-chat.json +446 -0
  457. package/src/locales/en/agent-prompts.json +42 -0
  458. package/src/locales/en/agent-providers.json +131 -0
  459. package/src/locales/en/agent-usage.json +131 -0
  460. package/src/locales/nl/agent-chat.json +446 -0
  461. package/src/locales/nl/agent-prompts.json +42 -0
  462. package/src/locales/nl/agent-providers.json +131 -0
  463. package/src/locales/nl/agent-usage.json +131 -0
  464. package/src/locales.ts +33 -0
  465. package/src/mounted-registry.ts +45 -0
  466. package/src/port-transport.ts +146 -0
  467. package/src/prompts/logic/focus.ts +25 -0
  468. package/src/prompts/logic/keys.ts +116 -0
  469. package/src/prompts/logic/prompts.fixtures.ts +84 -0
  470. package/src/prompts/logic/prompts.ts +58 -0
  471. package/src/prompts/logic/subjects.ts +122 -0
  472. package/src/prompts/logic/useFocusAfterAnswer.ts +38 -0
  473. package/src/prompts/logic/usePromptSession.ts +114 -0
  474. package/src/prompts/ui/ApprovalBody.tsx +109 -0
  475. package/src/prompts/ui/PromptActions.tsx +109 -0
  476. package/src/prompts/ui/PromptCard.tsx +101 -0
  477. package/src/prompts/ui/PromptView.tsx +111 -0
  478. package/src/prompts/ui/QuestionBody.tsx +174 -0
  479. package/src/providers/AccountColors.tsx +8 -0
  480. package/src/providers/AccountDetail.tsx +209 -0
  481. package/src/providers/AccountVariables.tsx +126 -0
  482. package/src/providers/AddAccountForm.tsx +121 -0
  483. package/src/providers/CliDetail.tsx +157 -0
  484. package/src/providers/ProvidersPane.tsx +236 -0
  485. package/src/providers/account-actions.ts +67 -0
  486. package/src/providers/parts.tsx +25 -0
  487. package/src/providers/provider-abilities.ts +9 -0
  488. package/src/providers/variables.ts +87 -0
  489. package/src/scope.ts +30 -0
  490. package/src/settings/sections.ts +47 -0
  491. package/src/state/chats.ts +279 -0
  492. package/src/state/provider-accounts.ts +87 -0
  493. package/src/state/providers.ts +50 -0
  494. package/src/state/usage.ts +196 -0
  495. package/src/storage.ts +113 -0
  496. package/src/theme.css +196 -0
  497. package/src/transport.ts +50 -0
  498. package/src/ui/PlainTextarea.tsx +6 -0
  499. package/src/usage/LimitsList.tsx +232 -0
  500. package/src/usage/UsageBreakdown.tsx +210 -0
  501. package/src/usage/UsageChart.tsx +113 -0
  502. package/src/usage/UsageDialog.tsx +20 -0
  503. package/src/usage/UsageLimits.tsx +145 -0
  504. package/src/usage/UsageLimitsCard.tsx +36 -0
  505. package/src/usage/UsagePage.tsx +276 -0
  506. package/src/usage/UsagePane.tsx +52 -0
  507. package/src/usage/UsageSummary.tsx +50 -0
  508. package/src/usage/UsageTiles.tsx +34 -0
  509. package/src/usage/format.ts +71 -0
  510. package/src/usage/limit-groups.ts +119 -0
  511. package/src/usage/limits.ts +71 -0
  512. package/src/usage/money.ts +14 -0
  513. package/src/usage/summary.ts +137 -0
@@ -0,0 +1,67 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState } from 'react';
3
+ import clsx from 'clsx';
4
+ import { CircleHelp, Folder, Info } from 'lucide-react';
5
+ import { useTranslation } from 'react-i18next';
6
+ import { EmptyState, Icon, SectionLabel, Segmented, Tooltip } from '@adecore/ui';
7
+ import { useChatScope } from '../scope.js';
8
+ import { chatHost } from '../host.js';
9
+ import { ProviderLogo } from '../agents/ProviderLogo.js';
10
+ import { USAGE_PROVIDERS, totalTokensOf } from '@adecore/agent-contracts';
11
+ import { modelNameFromSlug } from '../agents/model-name.js';
12
+ import { formatCount, formatTokens, PROVIDER_COLORS, PROVIDER_LABELS, shortPath, slotLabel } from './format.js';
13
+ import { useMoney } from './money.js';
14
+ import { deriveDays } from './summary.js';
15
+ /* The row of every table, the same height and the same hover as a commit row in the git panel. */
16
+ const ROW = 'flex h-8 items-center gap-2 rounded-md px-2 text-xs hover:bg-surface-hover';
17
+ const HEAD = 'flex h-6 items-center gap-2 px-2 text-xs text-text-faint';
18
+ /* A row is padded so its hover has room around the text, and the table pulls that padding back off
19
+ again. The first column then starts on the same line as every other section of the page. */
20
+ const TABLE = '-mx-2 flex flex-col';
21
+ const TABS = ['models', 'projects', 'day'];
22
+ /* A share of the biggest row, never under one pixel. A row that did something has to be visible. */
23
+ function barWidth(value, top) {
24
+ return value <= 0 || top <= 0 ? 0 : Math.max(1, Math.round((value / top) * 100));
25
+ }
26
+ function ProviderMark({ provider }) {
27
+ return (_jsx(Tooltip, { label: PROVIDER_LABELS[provider], children: _jsx("span", { style: { color: PROVIDER_COLORS[provider] }, children: _jsx(ProviderLogo, { provider: provider }) }) }));
28
+ }
29
+ function ModelRows({ models, metric, total }) {
30
+ const { t } = useTranslation('agent-usage');
31
+ const money = useMoney();
32
+ /* On the tokens metric the biggest row is the one that moved the most, not the dearest. */
33
+ const sorted = [...models].sort((a, b) => metric === 'tokens' ? totalTokensOf(b.totals) - totalTokensOf(a.totals) : (b.costUsd ?? -1) - (a.costUsd ?? -1));
34
+ return (_jsxs("div", { className: TABLE, children: [_jsxs("div", { className: HEAD, children: [_jsx("span", { className: "grow", children: t('breakdown.models.model') }), _jsx("span", { className: "w-16 text-right", children: t('breakdown.models.calls') }), _jsx("span", { className: "w-16 text-right", children: t('breakdown.models.input') }), _jsx("span", { className: "w-16 text-right", children: t('breakdown.models.output') }), _jsx("span", { className: "w-24 text-right", children: t('breakdown.models.cache') }), _jsx("span", { className: "w-20 text-right", children: t('breakdown.models.cost') }), _jsx("span", { className: "w-12 text-right", children: t('breakdown.models.share') })] }), sorted.map((model) => (_jsxs("div", { className: ROW, children: [_jsx(ProviderMark, { provider: model.provider }), _jsx("span", { className: "truncate", children: modelNameFromSlug(model.model) }), model.priceBasis === 'family' && model.pricedAs !== null && (_jsx(Tooltip, { label: t('breakdown.models.pricedAs', { model: modelNameFromSlug(model.pricedAs) }), children: _jsx("span", { className: "text-text-faint", children: _jsx(Icon, { icon: Info, size: 12 }) }) })), _jsx("span", { className: "ml-auto w-16 text-right tabular-nums text-text-muted", children: formatCount(model.totals.calls) }), _jsx("span", { className: "w-16 text-right tabular-nums text-text-muted", children: formatTokens(model.totals.input) }), _jsx("span", { className: "w-16 text-right tabular-nums text-text-muted", children: formatTokens(model.totals.output) }), _jsxs("span", { className: "w-24 text-right tabular-nums text-text-muted", children: [formatTokens(model.totals.cacheRead), " / ", formatTokens(model.totals.cacheWrite)] }), _jsx("span", { className: "w-20 text-right tabular-nums", children: model.costUsd === null ? (_jsx(Tooltip, { label: t('breakdown.models.noPrice'), children: _jsxs("span", { className: "inline-flex items-center gap-1 text-text-faint", children: [_jsx(Icon, { icon: CircleHelp, size: 12 }), "?"] }) })) : (money(model.costUsd)) }), _jsx("span", { className: "w-12 text-right tabular-nums text-text-muted", children: model.costUsd === null || total <= 0 ? '' : `${Math.round((model.costUsd / total) * 100)}%` })] }, `${model.provider} ${model.model}`)))] }));
35
+ }
36
+ /* One project's row, under the name and the mark its host gives it, or its folder's own under a folder. */
37
+ function ProjectRow({ project, top, money }) {
38
+ // The usage on screen is one host's, so the look of a project is that host's too.
39
+ const look = chatHost().useProjectLook(useChatScope().id, project.projectId);
40
+ return (_jsxs("div", { className: `${ROW} h-auto items-start py-1.5`, children: [_jsx("span", { className: "mt-px flex", children: look?.mark ?? (_jsx("span", { className: "text-text-faint", children: _jsx(Icon, { icon: Folder, size: 16 }) })) }), _jsxs("span", { className: "flex min-w-0 grow flex-col", children: [_jsx("span", { className: "truncate", children: look?.name ?? project.name }), _jsx("span", { className: "truncate text-text-faint", children: shortPath(project.folder) })] }), _jsx("span", { className: "mt-1 flex h-[9px] w-32 shrink-0 overflow-hidden rounded-full bg-surface-sunken", children: USAGE_PROVIDERS.map((provider) => {
41
+ const share = project.byProvider[provider];
42
+ const width = barWidth(share?.costUsd ?? 0, top);
43
+ return width === 0 ? null : _jsx("span", { style: { width: `${width}%`, background: PROVIDER_COLORS[provider] } }, provider);
44
+ }) }), _jsx("span", { className: "w-20 text-right tabular-nums", children: money(project.costUsd) }), _jsx("span", { className: "w-16 text-right tabular-nums text-text-muted", children: formatTokens(totalTokensOf(project.totals)) })] }));
45
+ }
46
+ function ProjectRows({ projects }) {
47
+ const money = useMoney();
48
+ const top = Math.max(...projects.map((project) => project.costUsd), 0);
49
+ return (_jsx("div", { className: clsx(TABLE, 'gap-1'), children: projects.map((project) => (_jsx(ProjectRow, { project: project, top: top, money: money }, project.folder))) }));
50
+ }
51
+ /* The chart written out per calendar day, a column per provider that did anything in the period. */
52
+ function DayRows({ summary, providers }) {
53
+ const { t } = useTranslation('agent-usage');
54
+ const money = useMoney();
55
+ const rows = deriveDays(summary);
56
+ if (rows.length === 0) {
57
+ return _jsx(EmptyState, { children: t('breakdown.days.empty') });
58
+ }
59
+ return (_jsxs("div", { className: TABLE, children: [_jsxs("div", { className: HEAD, children: [_jsx("span", { className: "grow", children: t('breakdown.days.day') }), providers.map((provider) => (_jsx("span", { className: "w-20 text-right", children: PROVIDER_LABELS[provider] }, provider))), _jsx("span", { className: "w-20 text-right", children: t('breakdown.days.total') }), _jsx("span", { className: "w-16 text-right", children: t('breakdown.days.tokens') })] }), rows.map((row) => (_jsxs("div", { className: ROW, children: [_jsx("span", { className: "grow truncate", children: slotLabel(row.slot) }), providers.map((provider) => (_jsx("span", { className: "w-20 text-right tabular-nums text-text-muted", children: row.costByProvider[provider] === undefined ? '' : money(row.costByProvider[provider]) }, provider))), _jsx("span", { className: "w-20 text-right tabular-nums", children: money(row.costUsd) }), _jsx("span", { className: "w-16 text-right tabular-nums text-text-muted", children: formatTokens(row.tokens) })] }, row.slot)))] }));
60
+ }
61
+ /* The same period cut three ways: which model, which checkout, and which day it went on. */
62
+ export function UsageBreakdown({ summary, metric, providers }) {
63
+ const { t } = useTranslation('agent-usage');
64
+ const [tab, setTab] = useState('models');
65
+ const total = summary.models.reduce((sum, model) => sum + (model.costUsd ?? 0), 0);
66
+ return (_jsxs("section", { className: "flex flex-col gap-3", children: [_jsxs("div", { className: "flex items-center gap-3", children: [_jsx(SectionLabel, { render: _jsx("h2", {}), children: t('breakdown.title') }), _jsx("div", { className: "ml-auto", children: _jsx(Segmented, { value: tab, options: TABS.map((id) => ({ id, label: t(`breakdown.tabs.${id}`) })), onValueChange: (id) => setTab(id), label: t('breakdown.title') }) })] }), tab === 'models' && _jsx(ModelRows, { models: summary.models, metric: metric, total: total }), tab === 'projects' && _jsx(ProjectRows, { projects: summary.projects }), tab === 'day' && _jsx(DayRows, { summary: summary, providers: providers })] }));
67
+ }
@@ -0,0 +1,10 @@
1
+ import type { UsageProvider } from '@adecore/agent-contracts';
2
+ import { type ChartSlot } from './summary.js';
3
+ interface UsageChartProps {
4
+ slots: readonly ChartSlot[];
5
+ providers: readonly UsageProvider[];
6
+ format(value: number): string;
7
+ labelEvery: number;
8
+ }
9
+ export declare function UsageChart({ slots, providers, format, labelEvery }: UsageChartProps): import("react").JSX.Element;
10
+ export {};
@@ -0,0 +1,47 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState } from 'react';
3
+ import { useTranslation } from 'react-i18next';
4
+ import { Surface, useMeasuredWidth } from '@adecore/ui';
5
+ import { PROVIDER_COLORS, PROVIDER_LABELS, slotAxisLabel, slotLabel } from './format.js';
6
+ import { niceScale } from './summary.js';
7
+ const HEIGHT = 224;
8
+ const TOOLTIP_WIDTH = 168;
9
+ /* A bar that is dimmed still has to read as its own color, so the rest keeps well over half of it. */
10
+ const DIMMED = 0.55;
11
+ /*
12
+ * One stacked bar per slot, a segment per provider. Stacked rather than layered areas, since the total
13
+ * of a day is the height of its bar, and the smaller provider never disappears under the larger one.
14
+ */
15
+ export function UsageChart({ slots, providers, format, labelEvery }) {
16
+ const { t } = useTranslation('agent-usage');
17
+ const [measure, width] = useMeasuredWidth();
18
+ const [hovered, setHovered] = useState(null);
19
+ const scale = niceScale(Math.max(...slots.map((slot) => slot.total), 0));
20
+ const band = slots.length === 0 ? 0 : width / slots.length;
21
+ const barWidth = Math.max(1, Math.round(band * 0.62));
22
+ const ticks = Array.from({ length: Math.round(scale.max / scale.step) + 1 }, (_, index) => index * scale.step);
23
+ const active = hovered === null ? null : slots[hovered];
24
+ const onPointerMove = (e) => {
25
+ const bounds = e.currentTarget.getBoundingClientRect();
26
+ const index = band === 0 ? -1 : Math.floor((e.clientX - bounds.left) / band);
27
+ setHovered(index >= 0 && index < slots.length ? index : null);
28
+ };
29
+ /* Near the right edge the card would run off the column, so it hangs from the other side. */
30
+ const tooltipLeft = hovered === null ? 0 : Math.round(Math.min(Math.max(0, hovered * band + band / 2 - TOOLTIP_WIDTH / 2), Math.max(0, width - TOOLTIP_WIDTH)));
31
+ return (_jsxs("div", { className: "flex flex-col", children: [_jsxs("div", { ref: measure, className: "relative", style: { height: HEIGHT }, onPointerMove: onPointerMove, onPointerLeave: () => setHovered(null), children: [_jsxs("svg", { width: width, height: HEIGHT, role: "presentation", children: [ticks.map((tick) => {
32
+ const y = Math.round(HEIGHT - (tick / scale.max) * HEIGHT) + 0.5;
33
+ return _jsx("line", { x1: 0, x2: width, y1: y, y2: y, stroke: "var(--border)", strokeWidth: 1 }, tick);
34
+ }), slots.map((slot, index) => {
35
+ const left = Math.round(index * band + (band - barWidth) / 2);
36
+ let bottom = HEIGHT;
37
+ return (_jsx("g", { opacity: hovered === null || hovered === index ? 1 : DIMMED, children: providers.map((provider) => {
38
+ const value = slot.byProvider[provider] ?? 0;
39
+ if (value <= 0) {
40
+ return null;
41
+ }
42
+ const height = Math.max(1, Math.round((value / scale.max) * HEIGHT));
43
+ bottom -= height;
44
+ return _jsx("rect", { x: left, y: bottom, width: barWidth, height: height, fill: PROVIDER_COLORS[provider] }, provider);
45
+ }) }, slot.slot));
46
+ }), hovered !== null && (_jsx("line", { x1: Math.round(hovered * band + band / 2) + 0.5, x2: Math.round(hovered * band + band / 2) + 0.5, y1: 0, y2: HEIGHT, stroke: "var(--border-strong)", strokeWidth: 1 }))] }), active && (_jsxs(Surface, { className: "pointer-events-none absolute top-2 rounded-lg p-2 text-xs", style: { left: tooltipLeft, width: TOOLTIP_WIDTH }, children: [_jsx("p", { className: "mb-1 font-medium", children: slotLabel(active.slot) }), providers.map((provider) => (_jsxs("p", { className: "flex items-center gap-1.5 text-text-muted", children: [_jsx("span", { className: "size-2 shrink-0 rounded-full", style: { background: PROVIDER_COLORS[provider] } }), PROVIDER_LABELS[provider], _jsx("span", { className: "ml-auto tabular-nums text-text", children: format(active.byProvider[provider] ?? 0) })] }, provider))), _jsxs("p", { className: "mt-1 flex items-center gap-1.5 border-t border-border pt-1 font-medium", children: [t('chart.total'), _jsx("span", { className: "ml-auto tabular-nums", children: format(active.total) })] })] }))] }), _jsx("div", { className: "relative mt-2 h-4", children: slots.map((slot, index) => index % labelEvery === 0 ? (_jsx("span", { className: "absolute -translate-x-1/2 text-xs whitespace-nowrap text-text-faint", style: { left: Math.round(index * band + band / 2) }, children: slotAxisLabel(slot.slot) }, slot.slot)) : null) })] }));
47
+ }
@@ -0,0 +1,6 @@
1
+ import type { ReactNode } from 'react';
2
+ export declare function UsageDialog({ open, onOpenChange, children }: {
3
+ open: boolean;
4
+ onOpenChange(open: boolean): void;
5
+ children: ReactNode;
6
+ }): import("react").JSX.Element;
@@ -0,0 +1,12 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import i18next from 'i18next';
3
+ import { Dialog } from '@adecore/ui';
4
+ /*
5
+ * The frame of the usage page: larger than the settings, since the chart and the breakdown need the
6
+ * width. What goes in it (`UsagePage`, in an `ErrorBoundary` of the app's) mounts only while it is
7
+ * open, so nothing is scanned behind a closed dialog. It is a dialog and not a view, since none of it
8
+ * belongs to one project.
9
+ */
10
+ export function UsageDialog({ open, onOpenChange, children }) {
11
+ return (_jsx(Dialog.Root, { open: open, onOpenChange: onOpenChange, children: _jsxs(Dialog.Popup, { className: "flex h-[820px] w-[1080px] flex-col", children: [children, _jsx(Dialog.Description, { className: "sr-only", children: i18next.t('agent-usage:dialog.description') })] }) }));
12
+ }
@@ -0,0 +1 @@
1
+ export declare function UsageLimits(): import("react").JSX.Element | null;
@@ -0,0 +1,57 @@
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState } from 'react';
3
+ import { useTranslation } from 'react-i18next';
4
+ import { LogIn } from 'lucide-react';
5
+ import { AccountDot } from '../agents/AccountDot.js';
6
+ import { Button, Icon, SectionLabel, Segmented, Tooltip, useNow } from '@adecore/ui';
7
+ import { chatHost } from '../host.js';
8
+ import { PROVIDER_COLORS, PROVIDER_LABELS } from './format.js';
9
+ import { accountNote, checkedLabel, hasSeveralAccounts, isSignedOut } from './limit-groups.js';
10
+ import { WindowBar } from './LimitsList.js';
11
+ import { useLimitGroups, useUsageLimits } from './limits.js';
12
+ import { useProviderAccountsStore } from '../state/provider-accounts.js';
13
+ import { useChatScope } from '../scope.js';
14
+ import { ProviderLogo } from '../agents/ProviderLogo.js';
15
+ const MINUTE_MS = 60_000;
16
+ function LoginButton({ account, login }) {
17
+ const { t } = useTranslation('agent-usage');
18
+ const button = (_jsxs(Button, { variant: "secondary", size: "sm", className: login.blocked === null ? 'self-start' : undefined, disabled: login.blocked !== null, onClick: () => login.open(account), children: [_jsx(Icon, { icon: LogIn, size: 14 }), t('limits.logIn')] }));
19
+ return login.blocked === null ? (button) : (_jsx(Tooltip, { label: login.blocked, children: _jsx("span", { className: "inline-flex self-start", children: button }) }));
20
+ }
21
+ /* One account: where its numbers come from, then a bar per window, or why there are none. */
22
+ function LimitCard({ account, named, now, login }) {
23
+ const { t } = useTranslation('agent-usage');
24
+ const signedOut = isSignedOut(account);
25
+ const note = signedOut ? null : accountNote(account);
26
+ const plan = signedOut ? null : account.entry?.plan;
27
+ const checked = signedOut || account.entry === null ? null : checkedLabel(account.entry, now);
28
+ return (_jsxs("div", { className: "flex min-w-0 flex-col gap-3.5 rounded-xl border border-border bg-surface bg-clip-padding px-4 py-3.5", children: [_jsxs("p", { className: "flex min-w-0 items-center gap-2 text-xs font-medium", children: [_jsx("span", { className: "shrink-0", style: { color: PROVIDER_COLORS[account.kind] }, children: _jsx(ProviderLogo, { provider: account.kind }) }), _jsx("span", { className: "shrink-0", children: PROVIDER_LABELS[account.kind] }), named && (_jsxs(_Fragment, { children: [_jsx("span", { className: "text-text-faint", children: "\u00B7" }), _jsx(AccountDot, { color: account.color }), _jsx("span", { className: "truncate", children: account.name })] })), plan && _jsxs("span", { className: "shrink-0 font-normal text-text-muted", children: ["\u00B7 ", plan] }), checked !== null && _jsx("span", { className: "ml-auto shrink-0 pl-2 font-normal text-text-faint", children: checked })] }), signedOut ? (_jsxs(_Fragment, { children: [_jsx("p", { className: "text-xs text-text-faint", children: t('limits.signedOutDetail') }), login !== null && _jsx(LoginButton, { account: account, login: login })] })) : note !== null ? (_jsx("p", { className: "text-xs text-text-faint", children: note })) : (_jsx("div", { className: "flex flex-col gap-4", children: account.entry?.windows.map((window) => (_jsx(WindowBar, { window: window, now: now, compact: false }, window.id))) }))] }));
29
+ }
30
+ /*
31
+ * What is left of each plan, asked of the CLIs themselves, a card per account. Nothing here reads a
32
+ * credential: every CLI holds its own login and answers the question when the daemon starts one and asks.
33
+ */
34
+ export function UsageLimits() {
35
+ const { t } = useTranslation('agent-usage');
36
+ const limits = useUsageLimits();
37
+ const groups = useLimitGroups(limits);
38
+ const now = useNow(MINUTE_MS);
39
+ const scopeId = useChatScope().id;
40
+ const loginCommands = useProviderAccountsStore((s) => s.byScope[scopeId]?.accounts?.loginCommands);
41
+ const { openLogin } = chatHost();
42
+ const blocked = chatHost().useLoginBlocked(scopeId);
43
+ const [filter, setFilter] = useState('all');
44
+ if (limits === null) {
45
+ return null;
46
+ }
47
+ const filterable = groups.length > 1 && hasSeveralAccounts(groups);
48
+ const current = filterable && groups.some((group) => group.kind === filter) ? filter : 'all';
49
+ const shown = current === 'all' ? groups : groups.filter((group) => group.kind === current);
50
+ const loginFor = (kind) => openLogin === null || loginCommands?.[kind] === undefined
51
+ ? null
52
+ : { blocked, open: (account) => void openLogin(scopeId, kind, account.id, account.name) };
53
+ return (_jsxs("section", { className: "flex flex-col gap-3", children: [_jsxs("div", { className: "flex min-h-8 items-center gap-3", children: [_jsx(SectionLabel, { render: _jsx("h2", {}), className: "grow", children: t('limits.title') }), filterable && (_jsx(Segmented, { value: current, options: [
54
+ { id: 'all', label: t('dialog.accounts.all') },
55
+ ...groups.map((group) => ({ id: group.kind, label: PROVIDER_LABELS[group.kind] }))
56
+ ], onValueChange: setFilter, label: t('limits.filter') }))] }), _jsx("p", { className: "text-xs text-text-muted", children: t('limits.explain') }), _jsx("div", { className: "grid grid-cols-2 gap-3", children: shown.flatMap((group) => group.accounts.map((account) => (_jsx(LimitCard, { account: account, named: group.accounts.length > 1, now: now, login: loginFor(group.kind) }, account.id)))) })] }));
57
+ }
@@ -0,0 +1,4 @@
1
+ import { type ReactElement } from 'react';
2
+ export declare function UsageLimitsCard({ children }: {
3
+ children: ReactElement<Record<string, unknown>>;
4
+ }): import("react").JSX.Element;
@@ -0,0 +1,26 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import i18next from 'i18next';
3
+ import { useState } from 'react';
4
+ import { hasSeveralAccounts } from './limit-groups.js';
5
+ import { AccountLimitsList, LimitsList } from './LimitsList.js';
6
+ import { useLimitGroups, useUsageLimits } from './limits.js';
7
+ import { useNow, PreviewCard } from '@adecore/ui';
8
+ const MINUTE_MS = 60_000;
9
+ /* Asking the CLIs starts a process each, so the question waits until the card is actually opened. */
10
+ function CardBody() {
11
+ const limits = useUsageLimits();
12
+ const groups = useLimitGroups(limits);
13
+ const now = useNow(MINUTE_MS);
14
+ if (limits === null) {
15
+ return _jsx("p", { className: "text-xs text-text-faint", children: i18next.t('agent-usage:limits.loading') });
16
+ }
17
+ return hasSeveralAccounts(groups) ? _jsx(AccountLimitsList, { groups: groups, now: now }) : _jsx(LimitsList, { limits: limits, now: now, compact: true });
18
+ }
19
+ /*
20
+ * What is left of each plan, without opening the usage dialog. A hover card rather than a tooltip:
21
+ * the bars take a moment to read, and it stays open while the pointer travels to it.
22
+ */
23
+ export function UsageLimitsCard({ children }) {
24
+ const [opened, setOpened] = useState(false);
25
+ return (_jsxs(PreviewCard.Root, { onOpenChange: (open) => open && setOpened(true), children: [_jsx(PreviewCard.Trigger, { render: children, delay: 500 }), _jsx(PreviewCard.Popup, { side: "top", align: "end", className: "w-64 p-3", children: opened && _jsx(CardBody, {}) })] }));
26
+ }
@@ -0,0 +1,7 @@
1
+ import { type ReactNode } from 'react';
2
+ interface UsagePageProps {
3
+ pickers?: ReactNode;
4
+ notice?(stale: boolean): ReactNode;
5
+ }
6
+ export declare function UsagePage({ pickers, notice }: UsagePageProps): import("react").JSX.Element;
7
+ export {};
@@ -0,0 +1,154 @@
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useCallback, useEffect, useMemo, useState, useSyncExternalStore } from 'react';
3
+ import { useTranslation } from 'react-i18next';
4
+ import { ChartNoAxesColumn, RefreshCw } from 'lucide-react';
5
+ import { USAGE_PROVIDERS } from '@adecore/agent-contracts';
6
+ import { Button, CloseButton, Dialog, EmptyState, FormError, IconButton, Segmented, Select, Skeleton } from '@adecore/ui';
7
+ import { AccountDot } from '../agents/AccountDot.js';
8
+ import { chatHost } from '../host.js';
9
+ import { useChatScope } from '../scope.js';
10
+ import { useProvidersStore } from '../state/providers.js';
11
+ import { askedKey, summaryPayload, USAGE_PERIODS, useUsage, useUsageStore } from '../state/usage.js';
12
+ import { formatClock, formatCount, formatDate, formatTokens, PROVIDER_LABELS } from './format.js';
13
+ import { useMoney } from './money.js';
14
+ import { deriveUsage, labelEveryFor } from './summary.js';
15
+ import { UsageBreakdown } from './UsageBreakdown.js';
16
+ import { UsageChart } from './UsageChart.js';
17
+ import { UsageLimits } from './UsageLimits.js';
18
+ import { UsageSummary } from './UsageSummary.js';
19
+ import { UsageTiles } from './UsageTiles.js';
20
+ const METRICS = ['cost', 'tokens'];
21
+ /* The picker's value for every account, which no account id can be: an id starts with a letter. */
22
+ const ALL_ACCOUNTS = '*';
23
+ /* Over the whole popup rather than the body under the header, so an empty state stands in the middle of the dialog. */
24
+ const DIALOG_CENTER = 'pointer-events-none absolute inset-0';
25
+ /* Whether the scope's host answers right now, redrawn as its link comes and goes. */
26
+ function useAnswering(transport) {
27
+ return useSyncExternalStore(useCallback((onChange) => transport.subscribeStatus(onChange), [transport]), () => transport.status === 'open');
28
+ }
29
+ /*
30
+ * Which agent CLIs the host has, under an empty usage page: no usage is what a host without any
31
+ * agent looks like too, and the way to fix that is the agent settings.
32
+ */
33
+ function HostAgents() {
34
+ const { t } = useTranslation('agent-usage');
35
+ const { id } = useChatScope();
36
+ const row = useProvidersStore((s) => s.byScope[id]);
37
+ const installed = useMemo(() => (row?.providers ?? []).filter((provider) => provider.installed), [row]);
38
+ if (!row?.loaded) {
39
+ return null;
40
+ }
41
+ return (_jsxs("div", { className: "flex flex-col items-center gap-2", children: [_jsx("p", { className: "text-xs text-text-muted", children: installed.length === 0 ? t('agents.none') : t('agents.installed', { list: installed.map((provider) => provider.name).join(', ') }) }), _jsx(Button, { size: "sm", variant: "secondary", onClick: () => chatHost().openSettings('agents'), children: t('agents.settings') })] }));
42
+ }
43
+ /*
44
+ * Asks the host on screen for the period that is up, keeps the answer under the key of the question
45
+ * it answers, and tells the host to keep scanning while the page is here. A late answer to the period
46
+ * before this one never lands, because the key it carries is no longer the one being shown.
47
+ */
48
+ function useSummary(scopeId, transport, period, account) {
49
+ useEffect(() => {
50
+ const subscribe = () => void transport.request('usage.subscribe', {}).catch(() => undefined);
51
+ if (transport.status === 'open') {
52
+ subscribe();
53
+ }
54
+ // A host knows its followers per link, so every connection has to be told again, on a reconnect and on a move to another host.
55
+ const off = transport.subscribeStatus((status) => {
56
+ if (status === 'open') {
57
+ subscribe();
58
+ }
59
+ });
60
+ return () => {
61
+ off();
62
+ void transport.request('usage.unsubscribe', {}).catch(() => undefined);
63
+ };
64
+ }, [transport]);
65
+ const load = useCallback(() => {
66
+ const payload = summaryPayload(period, account);
67
+ const asked = askedKey(payload);
68
+ useUsageStore.getState().setLoading(scopeId, true);
69
+ transport
70
+ .request('usage.summary', payload)
71
+ .then((summary) => useUsageStore.getState().receive(scopeId, asked, summary))
72
+ .catch(() => useUsageStore.getState().fail(scopeId));
73
+ }, [transport, scopeId, period, account]);
74
+ useEffect(() => {
75
+ if (transport.status === 'open') {
76
+ load();
77
+ }
78
+ // A scan that found something is the sign to ask again; nothing else changes the numbers.
79
+ const offChanged = transport.on('usage.changed', load);
80
+ // Until the other host answers there is nothing to ask, and what is on screen is the host that left.
81
+ const offStatus = transport.subscribeStatus((status) => {
82
+ if (status === 'open') {
83
+ load();
84
+ }
85
+ });
86
+ return () => {
87
+ offChanged();
88
+ offStatus();
89
+ };
90
+ }, [transport, load]);
91
+ return load;
92
+ }
93
+ function Provenance() {
94
+ const { t } = useTranslation('agent-usage');
95
+ const summary = useUsage((s) => s.summary);
96
+ const currency = useUsage((s) => s.currency);
97
+ const failed = useUsage((s) => s.failed);
98
+ if (failed) {
99
+ return _jsx(FormError, { className: "mt-auto text-center", children: t('provenance.failed') });
100
+ }
101
+ if (summary === null) {
102
+ return null;
103
+ }
104
+ const prices = summary.pricing.fetchedAt === null ? t('provenance.bundledPrices') : t('provenance.fetchedPrices', { date: formatDate(summary.pricing.fetchedAt) });
105
+ // The rate is named only when it is being used, so a page in dollars says nothing about euros.
106
+ const rate = currency === 'USD' || summary.rate === null ? null : t('provenance.rate', { currency: summary.rate.currency, date: summary.rate.date });
107
+ return (_jsxs("p", { className: "mt-auto text-center text-xs text-text-muted", children: [t('provenance.scanned', { count: summary.scan.files, time: formatClock(summary.scan.at), files: formatCount(summary.scan.files) }), " \u00B7 ", prices, rate !== null && ` · ${rate}`] }));
108
+ }
109
+ function LoadingBody() {
110
+ return (_jsxs("div", { className: "flex flex-col gap-6", children: [_jsxs("div", { className: "grid gap-6 lg:grid-cols-[18rem_1fr]", children: [_jsxs("div", { className: "flex flex-col gap-3", children: [_jsx(Skeleton, { className: "h-10 w-40" }), _jsx(Skeleton, { className: "w-56" }), _jsx(Skeleton, { className: "w-48" })] }), _jsx(Skeleton, { className: "h-56 w-full" })] }), _jsx("div", { className: "grid grid-cols-2 gap-2 md:grid-cols-6", children: [0, 1, 2, 3, 4, 5].map((tile) => (_jsx(Skeleton, { className: "h-16 w-full rounded-xl" }, tile))) })] }));
111
+ }
112
+ /*
113
+ * Every account of the host, once a CLI has more than one: picking one narrows the page to what that
114
+ * account spent. The chart keeps its split per CLI, since an account's color is a dot and not a series.
115
+ */
116
+ function AccountPicker({ accounts, value, onChange }) {
117
+ const { t } = useTranslation('agent-usage');
118
+ if (!USAGE_PROVIDERS.some((kind) => accounts.filter((account) => account.kind === kind).length > 1)) {
119
+ return null;
120
+ }
121
+ return (_jsx(Select, { value: value ?? ALL_ACCOUNTS, items: [
122
+ { value: ALL_ACCOUNTS, label: t('dialog.accounts.all') },
123
+ ...accounts.map((account) => ({
124
+ value: account.id,
125
+ label: account.label,
126
+ icon: _jsx(AccountDot, { color: account.color }),
127
+ description: account.label === PROVIDER_LABELS[account.kind] ? undefined : PROVIDER_LABELS[account.kind]
128
+ }))
129
+ ], onValueChange: (id) => onChange(id === ALL_ACCOUNTS ? null : id), label: t('dialog.accounts.label'), align: "end" }));
130
+ }
131
+ /*
132
+ * What the CLIs of one host cost and how much of their plans is left: the host of the scope this is
133
+ * rendered in. It is the body of a dialog (`UsageDialog`); the page remounts per host, so nothing of
134
+ * one outlives a switch to another.
135
+ */
136
+ export function UsagePage({ pickers, notice }) {
137
+ const { t } = useTranslation('agent-usage');
138
+ const scope = useChatScope();
139
+ const period = useUsage((s) => s.period);
140
+ const metric = useUsage((s) => s.metric);
141
+ const summary = useUsage((s) => s.summary);
142
+ const asked = useUsage((s) => s.asked);
143
+ const loading = useUsage((s) => s.loading);
144
+ // Per host, since account ids are a host's own.
145
+ const [account, setAccount] = useState(null);
146
+ const reload = useSummary(scope.id, scope.transport, period, account);
147
+ const money = useMoney();
148
+ const answering = useAnswering(scope.transport);
149
+ const shown = summary !== null && asked === askedKey(summaryPayload(period, account)) ? summary : null;
150
+ const derived = shown === null ? null : deriveUsage(shown, metric);
151
+ const value = metric === 'cost' ? money : formatTokens;
152
+ const noRoots = shown !== null && shown.roots.every((root) => root.status === 'missing');
153
+ return (_jsxs("div", { className: "flex min-h-0 grow flex-col", children: [_jsxs("header", { className: "flex shrink-0 flex-wrap items-center gap-3 border-b border-border py-3 pr-3 pl-6 max-[960px]:pl-4", children: [_jsx(Dialog.Title, { children: t('dialog.title') }), _jsxs("div", { className: "ml-auto flex flex-wrap items-center gap-2", children: [pickers, _jsx(AccountPicker, { accounts: summary?.accounts ?? [], value: account, onChange: setAccount }), _jsx(Segmented, { value: period, options: USAGE_PERIODS.map((entry) => ({ id: entry.id, label: t(`dialog.periods.${entry.id}`) })), onValueChange: (id) => useUsageStore.getState().setPeriod(id), label: t('dialog.period') }), _jsx(Segmented, { value: metric, options: METRICS.map((id) => ({ id, label: t(`dialog.metrics.${id}`) })), onValueChange: (id) => useUsageStore.getState().setMetric(id), label: t('dialog.metric') }), _jsx(IconButton, { icon: RefreshCw, label: t('dialog.rescan'), busy: loading, onClick: reload, disabled: loading || !answering }), _jsx(CloseButton, { label: t('dialog.close'), dialog: true })] })] }), _jsxs("div", { className: "flex min-h-0 grow flex-col gap-6 overflow-y-auto px-6 pt-4 pb-6 max-[960px]:px-4", children: [notice?.(shown !== null), noRoots && (_jsx(EmptyState, { className: DIALOG_CENTER, icon: ChartNoAxesColumn, action: _jsx(HostAgents, {}), children: t('empty.noTranscripts') })), shown === null && answering && _jsx(LoadingBody, {}), shown === null && !answering && (_jsx(EmptyState, { className: DIALOG_CENTER, icon: ChartNoAxesColumn, action: _jsx(HostAgents, {}), children: t('empty.noUsage') })), shown !== null && derived !== null && !noRoots && (_jsxs(_Fragment, { children: [_jsxs("div", { className: "grid gap-6 lg:grid-cols-[18rem_1fr]", children: [_jsx(UsageSummary, { metric: metric, costUsd: derived.costUsd, totals: derived.totals, sessions: shown.sessions, providers: derived.providers }), _jsx(UsageChart, { slots: derived.slots, providers: derived.active, format: value, labelEvery: labelEveryFor(derived.slots.length) })] }), _jsx(UsageTiles, { totals: derived.totals, cacheSavingsUsd: derived.cacheSavingsUsd }), _jsx(UsageBreakdown, { summary: shown, metric: metric, providers: derived.active }), _jsx(UsageLimits, {})] })), _jsx(Provenance, {})] })] }));
154
+ }
@@ -0,0 +1,3 @@
1
+ export declare function UsagePane({ onOpenPage }: {
2
+ onOpenPage(): void;
3
+ }): import("react").JSX.Element;
@@ -0,0 +1,21 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { ChartNoAxesColumn } from 'lucide-react';
3
+ import { useTranslation } from 'react-i18next';
4
+ import { SettingsRow, SettingsSection } from '@adecore/ui/settings';
5
+ import { Segmented, Button, Icon } from '@adecore/ui';
6
+ import { useUsage, useUsageStore } from '../state/usage.js';
7
+ const CURRENCIES = ['USD', 'EUR'];
8
+ /*
9
+ * Every price a model has is in dollars, so the only choice here is what to read them in. The usage
10
+ * page is a dialog of its own, so `onOpenPage` is where the app lets settings step aside for it.
11
+ */
12
+ export function UsagePane({ onOpenPage }) {
13
+ const { t } = useTranslation('agent-usage');
14
+ const currency = useUsage((s) => s.currency);
15
+ const rate = useUsage((s) => s.summary?.rate ?? null);
16
+ return (_jsxs(SettingsSection, { children: [_jsx(SettingsRow, { searchId: "usage.currency", label: t('settings.currency.label'), description: currency === 'USD'
17
+ ? t('settings.currency.dollars')
18
+ : rate === null
19
+ ? t('settings.currency.noRate')
20
+ : t('settings.currency.converted', { date: rate.date }), control: _jsx(Segmented, { value: currency, options: CURRENCIES.map((id) => ({ id, label: t(`settings.currency.options.${id}`) })), onValueChange: (id) => useUsageStore.getState().setCurrency(id), label: t('settings.currency.label') }) }), _jsx(SettingsRow, { searchId: "usage.page", label: t('settings.page.label'), description: t('settings.page.description'), control: _jsxs(Button, { variant: "secondary", onClick: onOpenPage, children: [_jsx(Icon, { icon: ChartNoAxesColumn, size: 14 }), " ", t('settings.page.open')] }) })] }));
21
+ }
@@ -0,0 +1,12 @@
1
+ import type { UsageTotals } from '@adecore/agent-contracts';
2
+ import type { UsageMetric } from '../state/usage.js';
3
+ import type { ProviderTotal } from './summary.js';
4
+ interface UsageSummaryProps {
5
+ metric: UsageMetric;
6
+ costUsd: number;
7
+ totals: UsageTotals;
8
+ sessions: number;
9
+ providers: readonly ProviderTotal[];
10
+ }
11
+ export declare function UsageSummary({ metric, costUsd, totals, sessions, providers }: UsageSummaryProps): import("react").JSX.Element;
12
+ export {};
@@ -0,0 +1,15 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useTranslation } from 'react-i18next';
3
+ import { ProviderLogo } from '../agents/ProviderLogo.js';
4
+ import { totalTokensOf } from '@adecore/agent-contracts';
5
+ import { formatCount, formatTokens, PROVIDER_COLORS, PROVIDER_LABELS } from './format.js';
6
+ import { useMoney } from './money.js';
7
+ /*
8
+ * The number the page is about, with a row per provider under it. Those rows double as the chart's
9
+ * legend, which is why the mark is the same color the bar segment is drawn in.
10
+ */
11
+ export function UsageSummary({ metric, costUsd, totals, sessions, providers }) {
12
+ const { t } = useTranslation('agent-usage');
13
+ const money = useMoney();
14
+ return (_jsxs("div", { className: "flex flex-col gap-4", children: [_jsxs("div", { children: [_jsx("p", { className: "text-4xl font-semibold tabular-nums", children: metric === 'cost' ? money(costUsd) : formatTokens(totalTokensOf(totals)) }), _jsxs("p", { className: "mt-1 text-xs text-text-muted", children: [t('summary.sessions', { count: sessions, sessions: formatCount(sessions) }), " \u00B7", ' ', t('summary.calls', { count: totals.calls, calls: formatCount(totals.calls) }), " \u00B7 ", t('summary.apiRates')] })] }), _jsx("div", { className: "flex flex-col gap-2", children: providers.map((provider) => (_jsxs("div", { className: "flex items-center gap-2 text-xs", children: [_jsx("span", { style: { color: PROVIDER_COLORS[provider.provider] }, children: _jsx(ProviderLogo, { provider: provider.provider }) }), _jsx("span", { className: "text-text", children: PROVIDER_LABELS[provider.provider] }), _jsxs("span", { className: "ml-auto tabular-nums text-text-muted", children: [money(provider.costUsd), " \u00B7 ", formatTokens(totalTokensOf(provider.totals))] })] }, provider.provider))) })] }));
15
+ }
@@ -0,0 +1,7 @@
1
+ import type { UsageTotals } from '@adecore/agent-contracts';
2
+ interface UsageTilesProps {
3
+ totals: UsageTotals;
4
+ cacheSavingsUsd: number;
5
+ }
6
+ export declare function UsageTiles({ totals, cacheSavingsUsd }: UsageTilesProps): import("react").JSX.Element;
7
+ export {};
@@ -0,0 +1,19 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useTranslation } from 'react-i18next';
3
+ import { totalTokensOf } from '@adecore/agent-contracts';
4
+ import { formatTokens } from './format.js';
5
+ import { useMoney } from './money.js';
6
+ /* What the period moved, split by the kinds of token that are priced differently. */
7
+ export function UsageTiles({ totals, cacheSavingsUsd }) {
8
+ const { t } = useTranslation('agent-usage');
9
+ const money = useMoney();
10
+ const tiles = [
11
+ { id: 'processed', value: formatTokens(totalTokensOf(totals)) },
12
+ { id: 'uncachedInput', value: formatTokens(totals.input) },
13
+ { id: 'cachedInput', value: formatTokens(totals.cacheRead) },
14
+ { id: 'cacheWrites', value: formatTokens(totals.cacheWrite) },
15
+ { id: 'output', value: formatTokens(totals.output) },
16
+ { id: 'cacheSavings', value: money(cacheSavingsUsd) }
17
+ ];
18
+ return (_jsx("div", { className: "grid grid-cols-2 gap-2 md:grid-cols-6", children: tiles.map((tile) => (_jsxs("div", { className: "rounded-xl border border-border bg-surface px-4 py-3", children: [_jsx("p", { className: "text-xs text-text-muted", children: t(`tiles.${tile.id}`) }), _jsx("p", { className: "mt-0.5 text-base font-medium tabular-nums", children: tile.value })] }, tile.id))) }));
19
+ }
@@ -0,0 +1,13 @@
1
+ import type { UsageProvider, UsageRate } from '@adecore/agent-contracts';
2
+ import { formatTokens } from '@adecore/ui/format';
3
+ export { formatClock } from '@adecore/ui/format';
4
+ export type UsageCurrency = 'USD' | 'EUR';
5
+ export declare function formatCount(value: number): string;
6
+ export { formatTokens };
7
+ export declare function moneyFormat(currency: UsageCurrency, rate: UsageRate | null): (usd: number) => string;
8
+ export declare const PROVIDER_LABELS: Record<UsageProvider, string>;
9
+ export declare const PROVIDER_COLORS: Record<UsageProvider, string>;
10
+ export declare function slotLabel(slot: string): string;
11
+ export declare function slotAxisLabel(slot: string): string;
12
+ export declare function formatDate(at: number): string;
13
+ export declare function shortPath(path: string): string;
@@ -0,0 +1,55 @@
1
+ import { formatDay, formatHour, formatWeekdayDay, formatMoney, formatNumber, formatTokens } from '@adecore/ui/format';
2
+ export { formatClock } from '@adecore/ui/format';
3
+ /* Every number on this page is read in the region of the machine looking at it, as a bank statement
4
+ would be: `$5,480.96` in the US, `$ 5.480,96` in the Netherlands. The labels stay English. */
5
+ export function formatCount(value) {
6
+ return formatNumber(value);
7
+ }
8
+ export { formatTokens };
9
+ /*
10
+ * What a call cost, in the currency the page is set to. Prices are in dollars, so anything else is
11
+ * that amount at the day's reference rate; without a rate the page stays in dollars rather than
12
+ * showing a euro sign in front of a dollar amount.
13
+ */
14
+ export function moneyFormat(currency, rate) {
15
+ const converts = currency !== 'USD' && rate !== null && rate.currency === currency;
16
+ const factor = converts ? rate.rate : 1;
17
+ const code = converts ? currency : 'USD';
18
+ return (usd) => {
19
+ const value = usd * factor;
20
+ // Under a cent the two decimals of a currency read as zero, which is the one thing it is not.
21
+ return formatMoney(value, code, value !== 0 && Math.abs(value) < 0.01 ? 4 : undefined);
22
+ };
23
+ }
24
+ export const PROVIDER_LABELS = { claude: 'Claude Code', codex: 'Codex' };
25
+ export const PROVIDER_COLORS = { claude: 'var(--chart-claude)', codex: 'var(--chart-codex)' };
26
+ /* A slot is `2026-09-10` or `2026-09-10T14`, already in the viewer's own zone, so it is read as
27
+ plain wall clock and never handed to a parser that would shift it back. */
28
+ function dateOfSlot(slot) {
29
+ return new Date(Number(slot.slice(0, 4)), Number(slot.slice(5, 7)) - 1, Number(slot.slice(8, 10)));
30
+ }
31
+ function hourOfSlot(slot) {
32
+ return slot.length > 10 ? Number(slot.slice(11, 13)) : null;
33
+ }
34
+ function atHour(slot, hour) {
35
+ const day = dateOfSlot(slot);
36
+ day.setHours(hour);
37
+ return day;
38
+ }
39
+ export function slotLabel(slot) {
40
+ const hour = hourOfSlot(slot);
41
+ return hour === null ? formatWeekdayDay(dateOfSlot(slot)) : formatHour(atHour(slot, hour));
42
+ }
43
+ export function slotAxisLabel(slot) {
44
+ const hour = hourOfSlot(slot);
45
+ return hour === null ? formatDay(dateOfSlot(slot)) : formatHour(atHour(slot, hour));
46
+ }
47
+ export function formatDate(at) {
48
+ return formatDay(at);
49
+ }
50
+ /* A path under a row that already carries the name: enough of the tail to tell two checkouts of the
51
+ same repository apart, without the home directory nobody needs to read again. */
52
+ export function shortPath(path) {
53
+ const parts = path.split('/').filter(Boolean);
54
+ return parts.length <= 3 ? path : `…/${parts.slice(-3).join('/')}`;
55
+ }
@@ -0,0 +1,22 @@
1
+ import { type ProviderAccounts, type ProviderAccountStatus, type UsageLimitsProvider, type UsageLimitsSnapshot, type UsageProvider, type UsageWindow } from '@adecore/agent-contracts';
2
+ export interface LimitAccount {
3
+ id: string;
4
+ kind: UsageProvider;
5
+ name: string;
6
+ color: string | undefined;
7
+ entry: UsageLimitsProvider | null;
8
+ status: ProviderAccountStatus | null;
9
+ }
10
+ export interface LimitGroup {
11
+ kind: UsageProvider;
12
+ accounts: LimitAccount[];
13
+ }
14
+ export declare function limitGroups(snapshot: UsageLimitsSnapshot, accounts: ProviderAccounts | null): LimitGroup[];
15
+ export declare function hasSeveralAccounts(groups: readonly LimitGroup[]): boolean;
16
+ export declare function isSignedOut(account: LimitAccount): boolean;
17
+ export declare function nextReset(windows: readonly UsageWindow[], now: number): (UsageWindow & {
18
+ resetsAt: number;
19
+ }) | null;
20
+ export declare function checkedLabel(entry: UsageLimitsProvider, now: number): string | null;
21
+ export declare function explain(provider: UsageLimitsProvider): string | null;
22
+ export declare function accountNote(account: LimitAccount): string | null;