@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,145 @@
1
+ import { useState } from 'react';
2
+ import { useTranslation } from 'react-i18next';
3
+ import { LogIn } from 'lucide-react';
4
+ import type { UsageProvider } from '@adecore/agent-contracts';
5
+ import { AccountDot } from '../agents/AccountDot';
6
+ import { Button, Icon, SectionLabel, Segmented, Tooltip, useNow } from '@adecore/ui';
7
+ import { chatHost } from '../host';
8
+ import { PROVIDER_COLORS, PROVIDER_LABELS } from './format';
9
+ import { accountNote, checkedLabel, hasSeveralAccounts, isSignedOut, type LimitAccount } from './limit-groups';
10
+ import { WindowBar } from './LimitsList';
11
+ import { useLimitGroups, useUsageLimits } from './limits';
12
+ import { useProviderAccountsStore } from '../state/provider-accounts';
13
+ import { useChatScope } from '../scope';
14
+ import { ProviderLogo } from '../agents/ProviderLogo';
15
+
16
+ const MINUTE_MS = 60_000;
17
+
18
+ type Filter = UsageProvider | 'all';
19
+
20
+ interface Login {
21
+ /* Why a login cannot open a terminal right now, or null when it can. */
22
+ blocked: string | null;
23
+ open: (account: LimitAccount) => void;
24
+ }
25
+
26
+ function LoginButton({ account, login }: { account: LimitAccount; login: Login }) {
27
+ const { t } = useTranslation('agent-usage');
28
+ const button = (
29
+ <Button
30
+ variant="secondary"
31
+ size="sm"
32
+ className={login.blocked === null ? 'self-start' : undefined}
33
+ disabled={login.blocked !== null}
34
+ onClick={() => login.open(account)}
35
+ >
36
+ <Icon icon={LogIn} size={14} />
37
+ {t('limits.logIn')}
38
+ </Button>
39
+ );
40
+ return login.blocked === null ? (
41
+ button
42
+ ) : (
43
+ <Tooltip label={login.blocked}>
44
+ <span className="inline-flex self-start">{button}</span>
45
+ </Tooltip>
46
+ );
47
+ }
48
+
49
+ /* One account: where its numbers come from, then a bar per window, or why there are none. */
50
+ function LimitCard({ account, named, now, login }: { account: LimitAccount; named: boolean; now: number; login: Login | null }) {
51
+ const { t } = useTranslation('agent-usage');
52
+ const signedOut = isSignedOut(account);
53
+ const note = signedOut ? null : accountNote(account);
54
+ const plan = signedOut ? null : account.entry?.plan;
55
+ const checked = signedOut || account.entry === null ? null : checkedLabel(account.entry, now);
56
+ return (
57
+ <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">
58
+ <p className="flex min-w-0 items-center gap-2 text-xs font-medium">
59
+ <span className="shrink-0" style={{ color: PROVIDER_COLORS[account.kind] }}>
60
+ <ProviderLogo provider={account.kind} />
61
+ </span>
62
+ <span className="shrink-0">{PROVIDER_LABELS[account.kind]}</span>
63
+ {named && (
64
+ <>
65
+ <span className="text-text-faint">·</span>
66
+ <AccountDot color={account.color} />
67
+ <span className="truncate">{account.name}</span>
68
+ </>
69
+ )}
70
+ {plan && <span className="shrink-0 font-normal text-text-muted">· {plan}</span>}
71
+ {checked !== null && <span className="ml-auto shrink-0 pl-2 font-normal text-text-faint">{checked}</span>}
72
+ </p>
73
+ {signedOut ? (
74
+ <>
75
+ <p className="text-xs text-text-faint">{t('limits.signedOutDetail')}</p>
76
+ {login !== null && <LoginButton account={account} login={login} />}
77
+ </>
78
+ ) : note !== null ? (
79
+ <p className="text-xs text-text-faint">{note}</p>
80
+ ) : (
81
+ <div className="flex flex-col gap-4">
82
+ {account.entry?.windows.map((window) => (
83
+ <WindowBar key={window.id} window={window} now={now} compact={false} />
84
+ ))}
85
+ </div>
86
+ )}
87
+ </div>
88
+ );
89
+ }
90
+
91
+ /*
92
+ * What is left of each plan, asked of the CLIs themselves, a card per account. Nothing here reads a
93
+ * credential: every CLI holds its own login and answers the question when the daemon starts one and asks.
94
+ */
95
+ export function UsageLimits() {
96
+ const { t } = useTranslation('agent-usage');
97
+ const limits = useUsageLimits();
98
+ const groups = useLimitGroups(limits);
99
+ const now = useNow(MINUTE_MS);
100
+ const scopeId = useChatScope().id;
101
+ const loginCommands = useProviderAccountsStore((s) => s.byScope[scopeId]?.accounts?.loginCommands);
102
+ const { openLogin } = chatHost();
103
+ const blocked = chatHost().useLoginBlocked(scopeId);
104
+ const [filter, setFilter] = useState<Filter>('all');
105
+
106
+ if (limits === null) {
107
+ return null;
108
+ }
109
+ const filterable = groups.length > 1 && hasSeveralAccounts(groups);
110
+ const current = filterable && groups.some((group) => group.kind === filter) ? filter : 'all';
111
+ const shown = current === 'all' ? groups : groups.filter((group) => group.kind === current);
112
+ const loginFor = (kind: UsageProvider): Login | null =>
113
+ openLogin === null || loginCommands?.[kind] === undefined
114
+ ? null
115
+ : { blocked, open: (account) => void openLogin(scopeId, kind, account.id, account.name) };
116
+
117
+ return (
118
+ <section className="flex flex-col gap-3">
119
+ <div className="flex min-h-8 items-center gap-3">
120
+ <SectionLabel render={<h2 />} className="grow">
121
+ {t('limits.title')}
122
+ </SectionLabel>
123
+ {filterable && (
124
+ <Segmented<Filter>
125
+ value={current}
126
+ options={[
127
+ { id: 'all', label: t('dialog.accounts.all') },
128
+ ...groups.map((group) => ({ id: group.kind, label: PROVIDER_LABELS[group.kind] }))
129
+ ]}
130
+ onValueChange={setFilter}
131
+ label={t('limits.filter')}
132
+ />
133
+ )}
134
+ </div>
135
+ <p className="text-xs text-text-muted">{t('limits.explain')}</p>
136
+ <div className="grid grid-cols-2 gap-3">
137
+ {shown.flatMap((group) =>
138
+ group.accounts.map((account) => (
139
+ <LimitCard key={account.id} account={account} named={group.accounts.length > 1} now={now} login={loginFor(group.kind)} />
140
+ ))
141
+ )}
142
+ </div>
143
+ </section>
144
+ );
145
+ }
@@ -0,0 +1,36 @@
1
+ import i18next from 'i18next';
2
+ import { useState, type ReactElement } from 'react';
3
+ import { hasSeveralAccounts } from './limit-groups';
4
+ import { AccountLimitsList, LimitsList } from './LimitsList';
5
+ import { useLimitGroups, useUsageLimits } from './limits';
6
+ import { useNow, PreviewCard } from '@adecore/ui';
7
+
8
+ const MINUTE_MS = 60_000;
9
+
10
+ /* Asking the CLIs starts a process each, so the question waits until the card is actually opened. */
11
+ function CardBody() {
12
+ const limits = useUsageLimits();
13
+ const groups = useLimitGroups(limits);
14
+ const now = useNow(MINUTE_MS);
15
+
16
+ if (limits === null) {
17
+ return <p className="text-xs text-text-faint">{i18next.t('agent-usage:limits.loading')}</p>;
18
+ }
19
+ return hasSeveralAccounts(groups) ? <AccountLimitsList groups={groups} now={now} /> : <LimitsList limits={limits} now={now} compact />;
20
+ }
21
+
22
+ /*
23
+ * What is left of each plan, without opening the usage dialog. A hover card rather than a tooltip:
24
+ * the bars take a moment to read, and it stays open while the pointer travels to it.
25
+ */
26
+ export function UsageLimitsCard({ children }: { children: ReactElement<Record<string, unknown>> }) {
27
+ const [opened, setOpened] = useState(false);
28
+ return (
29
+ <PreviewCard.Root onOpenChange={(open) => open && setOpened(true)}>
30
+ <PreviewCard.Trigger render={children} delay={500} />
31
+ <PreviewCard.Popup side="top" align="end" className="w-64 p-3">
32
+ {opened && <CardBody />}
33
+ </PreviewCard.Popup>
34
+ </PreviewCard.Root>
35
+ );
36
+ }
@@ -0,0 +1,276 @@
1
+ import { useCallback, useEffect, useMemo, useState, useSyncExternalStore, type ReactNode } from 'react';
2
+ import { useTranslation } from 'react-i18next';
3
+ import { ChartNoAxesColumn, RefreshCw } from 'lucide-react';
4
+ import { USAGE_PROVIDERS, type UsageAccount } from '@adecore/agent-contracts';
5
+ import { Button, CloseButton, Dialog, EmptyState, FormError, IconButton, Segmented, Select, Skeleton } from '@adecore/ui';
6
+ import { AccountDot } from '../agents/AccountDot';
7
+ import { chatHost } from '../host';
8
+ import { useChatScope } from '../scope';
9
+ import { useProvidersStore } from '../state/providers';
10
+ import { askedKey, summaryPayload, USAGE_PERIODS, useUsage, useUsageStore, type UsageMetric, type UsagePeriod } from '../state/usage';
11
+ import type { ChatTransport } from '../transport';
12
+ import { formatClock, formatCount, formatDate, formatTokens, PROVIDER_LABELS } from './format';
13
+ import { useMoney } from './money';
14
+ import { deriveUsage, labelEveryFor } from './summary';
15
+ import { UsageBreakdown } from './UsageBreakdown';
16
+ import { UsageChart } from './UsageChart';
17
+ import { UsageLimits } from './UsageLimits';
18
+ import { UsageSummary } from './UsageSummary';
19
+ import { UsageTiles } from './UsageTiles';
20
+
21
+ const METRICS: readonly UsageMetric[] = ['cost', 'tokens'];
22
+
23
+ /* The picker's value for every account, which no account id can be: an id starts with a letter. */
24
+ const ALL_ACCOUNTS = '*';
25
+
26
+ /* Over the whole popup rather than the body under the header, so an empty state stands in the middle of the dialog. */
27
+ const DIALOG_CENTER = 'pointer-events-none absolute inset-0';
28
+
29
+ /* Whether the scope's host answers right now, redrawn as its link comes and goes. */
30
+ function useAnswering(transport: ChatTransport): boolean {
31
+ return useSyncExternalStore(
32
+ useCallback((onChange: () => void) => transport.subscribeStatus(onChange), [transport]),
33
+ () => transport.status === 'open'
34
+ );
35
+ }
36
+
37
+ /*
38
+ * Which agent CLIs the host has, under an empty usage page: no usage is what a host without any
39
+ * agent looks like too, and the way to fix that is the agent settings.
40
+ */
41
+ function HostAgents() {
42
+ const { t } = useTranslation('agent-usage');
43
+ const { id } = useChatScope();
44
+ const row = useProvidersStore((s) => s.byScope[id]);
45
+ const installed = useMemo(() => (row?.providers ?? []).filter((provider) => provider.installed), [row]);
46
+ if (!row?.loaded) {
47
+ return null;
48
+ }
49
+ return (
50
+ <div className="flex flex-col items-center gap-2">
51
+ <p className="text-xs text-text-muted">
52
+ {installed.length === 0 ? t('agents.none') : t('agents.installed', { list: installed.map((provider) => provider.name).join(', ') })}
53
+ </p>
54
+ <Button size="sm" variant="secondary" onClick={() => chatHost().openSettings('agents')}>
55
+ {t('agents.settings')}
56
+ </Button>
57
+ </div>
58
+ );
59
+ }
60
+
61
+ /*
62
+ * Asks the host on screen for the period that is up, keeps the answer under the key of the question
63
+ * it answers, and tells the host to keep scanning while the page is here. A late answer to the period
64
+ * before this one never lands, because the key it carries is no longer the one being shown.
65
+ */
66
+ function useSummary(scopeId: string, transport: ChatTransport, period: UsagePeriod, account: string | null): () => void {
67
+ useEffect(() => {
68
+ const subscribe = (): void => void transport.request('usage.subscribe', {}).catch(() => undefined);
69
+ if (transport.status === 'open') {
70
+ subscribe();
71
+ }
72
+ // 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.
73
+ const off = transport.subscribeStatus((status) => {
74
+ if (status === 'open') {
75
+ subscribe();
76
+ }
77
+ });
78
+ return () => {
79
+ off();
80
+ void transport.request('usage.unsubscribe', {}).catch(() => undefined);
81
+ };
82
+ }, [transport]);
83
+
84
+ const load = useCallback((): void => {
85
+ const payload = summaryPayload(period, account);
86
+ const asked = askedKey(payload);
87
+ useUsageStore.getState().setLoading(scopeId, true);
88
+ transport
89
+ .request('usage.summary', payload)
90
+ .then((summary) => useUsageStore.getState().receive(scopeId, asked, summary))
91
+ .catch(() => useUsageStore.getState().fail(scopeId));
92
+ }, [transport, scopeId, period, account]);
93
+
94
+ useEffect(() => {
95
+ if (transport.status === 'open') {
96
+ load();
97
+ }
98
+ // A scan that found something is the sign to ask again; nothing else changes the numbers.
99
+ const offChanged = transport.on('usage.changed', load);
100
+ // Until the other host answers there is nothing to ask, and what is on screen is the host that left.
101
+ const offStatus = transport.subscribeStatus((status) => {
102
+ if (status === 'open') {
103
+ load();
104
+ }
105
+ });
106
+ return () => {
107
+ offChanged();
108
+ offStatus();
109
+ };
110
+ }, [transport, load]);
111
+
112
+ return load;
113
+ }
114
+
115
+ function Provenance() {
116
+ const { t } = useTranslation('agent-usage');
117
+ const summary = useUsage((s) => s.summary);
118
+ const currency = useUsage((s) => s.currency);
119
+ const failed = useUsage((s) => s.failed);
120
+ if (failed) {
121
+ return <FormError className="mt-auto text-center">{t('provenance.failed')}</FormError>;
122
+ }
123
+ if (summary === null) {
124
+ return null;
125
+ }
126
+ const prices =
127
+ summary.pricing.fetchedAt === null ? t('provenance.bundledPrices') : t('provenance.fetchedPrices', { date: formatDate(summary.pricing.fetchedAt) });
128
+ // The rate is named only when it is being used, so a page in dollars says nothing about euros.
129
+ const rate = currency === 'USD' || summary.rate === null ? null : t('provenance.rate', { currency: summary.rate.currency, date: summary.rate.date });
130
+ return (
131
+ <p className="mt-auto text-center text-xs text-text-muted">
132
+ {t('provenance.scanned', { count: summary.scan.files, time: formatClock(summary.scan.at), files: formatCount(summary.scan.files) })} · {prices}
133
+ {rate !== null && ` · ${rate}`}
134
+ </p>
135
+ );
136
+ }
137
+
138
+ function LoadingBody() {
139
+ return (
140
+ <div className="flex flex-col gap-6">
141
+ <div className="grid gap-6 lg:grid-cols-[18rem_1fr]">
142
+ <div className="flex flex-col gap-3">
143
+ <Skeleton className="h-10 w-40" />
144
+ <Skeleton className="w-56" />
145
+ <Skeleton className="w-48" />
146
+ </div>
147
+ <Skeleton className="h-56 w-full" />
148
+ </div>
149
+ <div className="grid grid-cols-2 gap-2 md:grid-cols-6">
150
+ {[0, 1, 2, 3, 4, 5].map((tile) => (
151
+ <Skeleton key={tile} className="h-16 w-full rounded-xl" />
152
+ ))}
153
+ </div>
154
+ </div>
155
+ );
156
+ }
157
+
158
+ /*
159
+ * Every account of the host, once a CLI has more than one: picking one narrows the page to what that
160
+ * account spent. The chart keeps its split per CLI, since an account's color is a dot and not a series.
161
+ */
162
+ function AccountPicker({ accounts, value, onChange }: { accounts: readonly UsageAccount[]; value: string | null; onChange(account: string | null): void }) {
163
+ const { t } = useTranslation('agent-usage');
164
+ if (!USAGE_PROVIDERS.some((kind) => accounts.filter((account) => account.kind === kind).length > 1)) {
165
+ return null;
166
+ }
167
+ return (
168
+ <Select
169
+ value={value ?? ALL_ACCOUNTS}
170
+ items={[
171
+ { value: ALL_ACCOUNTS, label: t('dialog.accounts.all') },
172
+ ...accounts.map((account) => ({
173
+ value: account.id,
174
+ label: account.label,
175
+ icon: <AccountDot color={account.color} />,
176
+ description: account.label === PROVIDER_LABELS[account.kind] ? undefined : PROVIDER_LABELS[account.kind]
177
+ }))
178
+ ]}
179
+ onValueChange={(id) => onChange(id === ALL_ACCOUNTS ? null : id)}
180
+ label={t('dialog.accounts.label')}
181
+ align="end"
182
+ />
183
+ );
184
+ }
185
+
186
+ interface UsagePageProps {
187
+ /* Drawn first among the controls of the header, such as a picker of the host these numbers are of. */
188
+ pickers?: ReactNode;
189
+ /* A line over the numbers about the host itself, such as that it does not answer; `stale` is true while older numbers stand under it. */
190
+ notice?(stale: boolean): ReactNode;
191
+ }
192
+
193
+ /*
194
+ * What the CLIs of one host cost and how much of their plans is left: the host of the scope this is
195
+ * rendered in. It is the body of a dialog (`UsageDialog`); the page remounts per host, so nothing of
196
+ * one outlives a switch to another.
197
+ */
198
+ export function UsagePage({ pickers, notice }: UsagePageProps) {
199
+ const { t } = useTranslation('agent-usage');
200
+ const scope = useChatScope();
201
+ const period = useUsage((s) => s.period);
202
+ const metric = useUsage((s) => s.metric);
203
+ const summary = useUsage((s) => s.summary);
204
+ const asked = useUsage((s) => s.asked);
205
+ const loading = useUsage((s) => s.loading);
206
+ // Per host, since account ids are a host's own.
207
+ const [account, setAccount] = useState<string | null>(null);
208
+ const reload = useSummary(scope.id, scope.transport, period, account);
209
+ const money = useMoney();
210
+ const answering = useAnswering(scope.transport);
211
+
212
+ const shown = summary !== null && asked === askedKey(summaryPayload(period, account)) ? summary : null;
213
+ const derived = shown === null ? null : deriveUsage(shown, metric);
214
+ const value = metric === 'cost' ? money : formatTokens;
215
+ const noRoots = shown !== null && shown.roots.every((root) => root.status === 'missing');
216
+
217
+ return (
218
+ <div className="flex min-h-0 grow flex-col">
219
+ <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">
220
+ <Dialog.Title>{t('dialog.title')}</Dialog.Title>
221
+ <div className="ml-auto flex flex-wrap items-center gap-2">
222
+ {pickers}
223
+ <AccountPicker accounts={summary?.accounts ?? []} value={account} onChange={setAccount} />
224
+ <Segmented
225
+ value={period}
226
+ options={USAGE_PERIODS.map((entry) => ({ id: entry.id, label: t(`dialog.periods.${entry.id}`) }))}
227
+ onValueChange={(id) => useUsageStore.getState().setPeriod(id)}
228
+ label={t('dialog.period')}
229
+ />
230
+ <Segmented
231
+ value={metric}
232
+ options={METRICS.map((id) => ({ id, label: t(`dialog.metrics.${id}`) }))}
233
+ onValueChange={(id) => useUsageStore.getState().setMetric(id)}
234
+ label={t('dialog.metric')}
235
+ />
236
+ <IconButton icon={RefreshCw} label={t('dialog.rescan')} busy={loading} onClick={reload} disabled={loading || !answering} />
237
+ <CloseButton label={t('dialog.close')} dialog />
238
+ </div>
239
+ </header>
240
+ <div className="flex min-h-0 grow flex-col gap-6 overflow-y-auto px-6 pt-4 pb-6 max-[960px]:px-4">
241
+ {notice?.(shown !== null)}
242
+ {noRoots && (
243
+ <EmptyState className={DIALOG_CENTER} icon={ChartNoAxesColumn} action={<HostAgents />}>
244
+ {t('empty.noTranscripts')}
245
+ </EmptyState>
246
+ )}
247
+ {/* The skeleton is the wait for an answer; without a link there is no answer on the way. */}
248
+ {shown === null && answering && <LoadingBody />}
249
+ {shown === null && !answering && (
250
+ <EmptyState className={DIALOG_CENTER} icon={ChartNoAxesColumn} action={<HostAgents />}>
251
+ {t('empty.noUsage')}
252
+ </EmptyState>
253
+ )}
254
+ {shown !== null && derived !== null && !noRoots && (
255
+ <>
256
+ <div className="grid gap-6 lg:grid-cols-[18rem_1fr]">
257
+ <UsageSummary
258
+ metric={metric}
259
+ costUsd={derived.costUsd}
260
+ totals={derived.totals}
261
+ sessions={shown.sessions}
262
+ providers={derived.providers}
263
+ />
264
+ <UsageChart slots={derived.slots} providers={derived.active} format={value} labelEvery={labelEveryFor(derived.slots.length)} />
265
+ </div>
266
+ <UsageTiles totals={derived.totals} cacheSavingsUsd={derived.cacheSavingsUsd} />
267
+ <UsageBreakdown summary={shown} metric={metric} providers={derived.active} />
268
+ <UsageLimits />
269
+ </>
270
+ )}
271
+ {/* Last in a column that fills the dialog, so it sits at the bottom even under a short page. */}
272
+ <Provenance />
273
+ </div>
274
+ </div>
275
+ );
276
+ }
@@ -0,0 +1,52 @@
1
+ import { ChartNoAxesColumn } from 'lucide-react';
2
+ import { useTranslation } from 'react-i18next';
3
+ import { SettingsRow, SettingsSection } from '@adecore/ui/settings';
4
+ import { Segmented, Button, Icon } from '@adecore/ui';
5
+ import { useUsage, useUsageStore } from '../state/usage';
6
+ import type { UsageCurrency } from './format';
7
+
8
+ const CURRENCIES: readonly UsageCurrency[] = ['USD', 'EUR'];
9
+
10
+ /*
11
+ * Every price a model has is in dollars, so the only choice here is what to read them in. The usage
12
+ * page is a dialog of its own, so `onOpenPage` is where the app lets settings step aside for it.
13
+ */
14
+ export function UsagePane({ onOpenPage }: { onOpenPage(): void }) {
15
+ const { t } = useTranslation('agent-usage');
16
+ const currency = useUsage((s) => s.currency);
17
+ const rate = useUsage((s) => s.summary?.rate ?? null);
18
+
19
+ return (
20
+ <SettingsSection>
21
+ <SettingsRow
22
+ searchId="usage.currency"
23
+ label={t('settings.currency.label')}
24
+ description={
25
+ currency === 'USD'
26
+ ? t('settings.currency.dollars')
27
+ : rate === null
28
+ ? t('settings.currency.noRate')
29
+ : t('settings.currency.converted', { date: rate.date })
30
+ }
31
+ control={
32
+ <Segmented
33
+ value={currency}
34
+ options={CURRENCIES.map((id) => ({ id, label: t(`settings.currency.options.${id}`) }))}
35
+ onValueChange={(id) => useUsageStore.getState().setCurrency(id)}
36
+ label={t('settings.currency.label')}
37
+ />
38
+ }
39
+ />
40
+ <SettingsRow
41
+ searchId="usage.page"
42
+ label={t('settings.page.label')}
43
+ description={t('settings.page.description')}
44
+ control={
45
+ <Button variant="secondary" onClick={onOpenPage}>
46
+ <Icon icon={ChartNoAxesColumn} size={14} /> {t('settings.page.open')}
47
+ </Button>
48
+ }
49
+ />
50
+ </SettingsSection>
51
+ );
52
+ }
@@ -0,0 +1,50 @@
1
+ import { useTranslation } from 'react-i18next';
2
+ import type { UsageTotals } from '@adecore/agent-contracts';
3
+ import type { UsageMetric } from '../state/usage';
4
+ import { ProviderLogo } from '../agents/ProviderLogo';
5
+ import { totalTokensOf } from '@adecore/agent-contracts';
6
+ import { formatCount, formatTokens, PROVIDER_COLORS, PROVIDER_LABELS } from './format';
7
+ import { useMoney } from './money';
8
+ import type { ProviderTotal } from './summary';
9
+
10
+ interface UsageSummaryProps {
11
+ metric: UsageMetric;
12
+ costUsd: number;
13
+ totals: UsageTotals;
14
+ sessions: number;
15
+ providers: readonly ProviderTotal[];
16
+ }
17
+
18
+ /*
19
+ * The number the page is about, with a row per provider under it. Those rows double as the chart's
20
+ * legend, which is why the mark is the same color the bar segment is drawn in.
21
+ */
22
+ export function UsageSummary({ metric, costUsd, totals, sessions, providers }: UsageSummaryProps) {
23
+ const { t } = useTranslation('agent-usage');
24
+ const money = useMoney();
25
+ return (
26
+ <div className="flex flex-col gap-4">
27
+ <div>
28
+ <p className="text-4xl font-semibold tabular-nums">{metric === 'cost' ? money(costUsd) : formatTokens(totalTokensOf(totals))}</p>
29
+ {/* A subscription is not billed per call, so the label spells out that these are API rates. */}
30
+ <p className="mt-1 text-xs text-text-muted">
31
+ {t('summary.sessions', { count: sessions, sessions: formatCount(sessions) })} ·{' '}
32
+ {t('summary.calls', { count: totals.calls, calls: formatCount(totals.calls) })} · {t('summary.apiRates')}
33
+ </p>
34
+ </div>
35
+ <div className="flex flex-col gap-2">
36
+ {providers.map((provider) => (
37
+ <div key={provider.provider} className="flex items-center gap-2 text-xs">
38
+ <span style={{ color: PROVIDER_COLORS[provider.provider] }}>
39
+ <ProviderLogo provider={provider.provider} />
40
+ </span>
41
+ <span className="text-text">{PROVIDER_LABELS[provider.provider]}</span>
42
+ <span className="ml-auto tabular-nums text-text-muted">
43
+ {money(provider.costUsd)} · {formatTokens(totalTokensOf(provider.totals))}
44
+ </span>
45
+ </div>
46
+ ))}
47
+ </div>
48
+ </div>
49
+ );
50
+ }
@@ -0,0 +1,34 @@
1
+ import { useTranslation } from 'react-i18next';
2
+ import type { UsageTotals } from '@adecore/agent-contracts';
3
+ import { totalTokensOf } from '@adecore/agent-contracts';
4
+ import { formatTokens } from './format';
5
+ import { useMoney } from './money';
6
+
7
+ interface UsageTilesProps {
8
+ totals: UsageTotals;
9
+ cacheSavingsUsd: number;
10
+ }
11
+
12
+ /* What the period moved, split by the kinds of token that are priced differently. */
13
+ export function UsageTiles({ totals, cacheSavingsUsd }: UsageTilesProps) {
14
+ const { t } = useTranslation('agent-usage');
15
+ const money = useMoney();
16
+ const tiles: { id: string; value: string }[] = [
17
+ { id: 'processed', value: formatTokens(totalTokensOf(totals)) },
18
+ { id: 'uncachedInput', value: formatTokens(totals.input) },
19
+ { id: 'cachedInput', value: formatTokens(totals.cacheRead) },
20
+ { id: 'cacheWrites', value: formatTokens(totals.cacheWrite) },
21
+ { id: 'output', value: formatTokens(totals.output) },
22
+ { id: 'cacheSavings', value: money(cacheSavingsUsd) }
23
+ ];
24
+ return (
25
+ <div className="grid grid-cols-2 gap-2 md:grid-cols-6">
26
+ {tiles.map((tile) => (
27
+ <div key={tile.id} className="rounded-xl border border-border bg-surface px-4 py-3">
28
+ <p className="text-xs text-text-muted">{t(`tiles.${tile.id}`)}</p>
29
+ <p className="mt-0.5 text-base font-medium tabular-nums">{tile.value}</p>
30
+ </div>
31
+ ))}
32
+ </div>
33
+ );
34
+ }
@@ -0,0 +1,71 @@
1
+ import type { UsageProvider, UsageRate } from '@adecore/agent-contracts';
2
+ import { formatDay, formatHour, formatWeekdayDay, formatMoney, formatNumber, formatTokens } from '@adecore/ui/format';
3
+ export { formatClock } from '@adecore/ui/format';
4
+
5
+ /* Dollars are what a price table is in; euros are what the page can be set to. */
6
+ export type UsageCurrency = 'USD' | 'EUR';
7
+
8
+ /* Every number on this page is read in the region of the machine looking at it, as a bank statement
9
+ would be: `$5,480.96` in the US, `$ 5.480,96` in the Netherlands. The labels stay English. */
10
+ export function formatCount(value: number): string {
11
+ return formatNumber(value);
12
+ }
13
+
14
+ export { formatTokens };
15
+
16
+ /*
17
+ * What a call cost, in the currency the page is set to. Prices are in dollars, so anything else is
18
+ * that amount at the day's reference rate; without a rate the page stays in dollars rather than
19
+ * showing a euro sign in front of a dollar amount.
20
+ */
21
+ export function moneyFormat(currency: UsageCurrency, rate: UsageRate | null): (usd: number) => string {
22
+ const converts = currency !== 'USD' && rate !== null && rate.currency === currency;
23
+ const factor = converts ? rate.rate : 1;
24
+ const code = converts ? currency : 'USD';
25
+ return (usd) => {
26
+ const value = usd * factor;
27
+ // Under a cent the two decimals of a currency read as zero, which is the one thing it is not.
28
+ return formatMoney(value, code, value !== 0 && Math.abs(value) < 0.01 ? 4 : undefined);
29
+ };
30
+ }
31
+
32
+ export const PROVIDER_LABELS: Record<UsageProvider, string> = { claude: 'Claude Code', codex: 'Codex' };
33
+
34
+ export const PROVIDER_COLORS: Record<UsageProvider, string> = { claude: 'var(--chart-claude)', codex: 'var(--chart-codex)' };
35
+
36
+ /* A slot is `2026-09-10` or `2026-09-10T14`, already in the viewer's own zone, so it is read as
37
+ plain wall clock and never handed to a parser that would shift it back. */
38
+ function dateOfSlot(slot: string): Date {
39
+ return new Date(Number(slot.slice(0, 4)), Number(slot.slice(5, 7)) - 1, Number(slot.slice(8, 10)));
40
+ }
41
+
42
+ function hourOfSlot(slot: string): number | null {
43
+ return slot.length > 10 ? Number(slot.slice(11, 13)) : null;
44
+ }
45
+
46
+ function atHour(slot: string, hour: number): Date {
47
+ const day = dateOfSlot(slot);
48
+ day.setHours(hour);
49
+ return day;
50
+ }
51
+
52
+ export function slotLabel(slot: string): string {
53
+ const hour = hourOfSlot(slot);
54
+ return hour === null ? formatWeekdayDay(dateOfSlot(slot)) : formatHour(atHour(slot, hour));
55
+ }
56
+
57
+ export function slotAxisLabel(slot: string): string {
58
+ const hour = hourOfSlot(slot);
59
+ return hour === null ? formatDay(dateOfSlot(slot)) : formatHour(atHour(slot, hour));
60
+ }
61
+
62
+ export function formatDate(at: number): string {
63
+ return formatDay(at);
64
+ }
65
+
66
+ /* A path under a row that already carries the name: enough of the tail to tell two checkouts of the
67
+ same repository apart, without the home directory nobody needs to read again. */
68
+ export function shortPath(path: string): string {
69
+ const parts = path.split('/').filter(Boolean);
70
+ return parts.length <= 3 ? path : `…/${parts.slice(-3).join('/')}`;
71
+ }