@maudecode/talaria-web 1.15.0 → 1.17.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (386) hide show
  1. package/_release.json +4 -4
  2. package/dist/api/automation-raw.js +6 -3
  3. package/dist/api/automation-raw.js.map +1 -1
  4. package/dist/api/automation-router.d.ts +158 -16
  5. package/dist/api/automation-router.js +12 -6
  6. package/dist/api/automation-router.js.map +1 -1
  7. package/dist/api/chat-router.d.ts +193 -6
  8. package/dist/api/chat-router.js +39 -16
  9. package/dist/api/chat-router.js.map +1 -1
  10. package/dist/api/git-router.d.ts +12 -1
  11. package/dist/api/git-router.js +60 -38
  12. package/dist/api/git-router.js.map +1 -1
  13. package/dist/api/raw-routes.d.ts +6 -0
  14. package/dist/api/raw-routes.js +35 -58
  15. package/dist/api/raw-routes.js.map +1 -1
  16. package/dist/api/router.d.ts +4 -2
  17. package/dist/api/router.js +8 -8
  18. package/dist/api/router.js.map +1 -1
  19. package/dist/api/sessions-router.d.ts +974 -42
  20. package/dist/api/sessions-router.js +87 -25
  21. package/dist/api/sessions-router.js.map +1 -1
  22. package/dist/api/settings-router.d.ts +254 -11
  23. package/dist/api/settings-router.js +68 -18
  24. package/dist/api/settings-router.js.map +1 -1
  25. package/dist/api/sse-routes.d.ts +11 -3
  26. package/dist/api/sse-routes.js +145 -65
  27. package/dist/api/sse-routes.js.map +1 -1
  28. package/dist/api/tools-raw.js +6 -2
  29. package/dist/api/tools-raw.js.map +1 -1
  30. package/dist/api/tools-router.d.ts +86 -9
  31. package/dist/api/tools-router.js +19 -15
  32. package/dist/api/tools-router.js.map +1 -1
  33. package/dist/app.d.ts +0 -8397
  34. package/dist/app.js +7 -2
  35. package/dist/app.js.map +1 -1
  36. package/dist/auth/gate.js +11 -4
  37. package/dist/auth/gate.js.map +1 -1
  38. package/dist/auth/oidc.d.ts +6 -2
  39. package/dist/auth/oidc.js +16 -11
  40. package/dist/auth/oidc.js.map +1 -1
  41. package/dist/auth/store.d.ts +14 -2
  42. package/dist/auth/store.js +26 -1
  43. package/dist/auth/store.js.map +1 -1
  44. package/dist/cli/dotenv.d.ts +1 -1
  45. package/dist/cli/dotenv.js +36 -3
  46. package/dist/cli/dotenv.js.map +1 -1
  47. package/dist/config/agent-config.d.ts +2 -0
  48. package/dist/config/agent-config.js +9 -0
  49. package/dist/config/agent-config.js.map +1 -1
  50. package/dist/http/addresses.d.ts +6 -0
  51. package/dist/http/addresses.js +26 -0
  52. package/dist/http/addresses.js.map +1 -1
  53. package/dist/http/context.d.ts +6 -3
  54. package/dist/http/context.js +10 -4
  55. package/dist/http/context.js.map +1 -1
  56. package/dist/links.d.ts +26 -0
  57. package/dist/links.js +60 -0
  58. package/dist/links.js.map +1 -0
  59. package/dist/onboarding.js +30 -7
  60. package/dist/onboarding.js.map +1 -1
  61. package/dist/profiles/profiles.js +1 -1
  62. package/dist/profiles/profiles.js.map +1 -1
  63. package/dist/providers/catalog.d.ts +167 -5
  64. package/dist/providers/catalog.js +662 -135
  65. package/dist/providers/catalog.js.map +1 -1
  66. package/dist/providers/tables.d.ts +3 -1
  67. package/dist/providers/tables.js +9 -4
  68. package/dist/providers/tables.js.map +1 -1
  69. package/dist/redact.d.ts +1 -0
  70. package/dist/redact.js +518 -15
  71. package/dist/redact.js.map +1 -1
  72. package/dist/runtime.js +79 -37
  73. package/dist/runtime.js.map +1 -1
  74. package/dist/sessions/anchor.d.ts +24 -3
  75. package/dist/sessions/anchor.js +94 -44
  76. package/dist/sessions/anchor.js.map +1 -1
  77. package/dist/sessions/background-tasks.d.ts +4 -1
  78. package/dist/sessions/background-tasks.js +7 -4
  79. package/dist/sessions/background-tasks.js.map +1 -1
  80. package/dist/sessions/claude-code.d.ts +19 -0
  81. package/dist/sessions/claude-code.js +250 -0
  82. package/dist/sessions/claude-code.js.map +1 -0
  83. package/dist/sessions/completions.d.ts +13 -1
  84. package/dist/sessions/completions.js +64 -12
  85. package/dist/sessions/completions.js.map +1 -1
  86. package/dist/sessions/compress.d.ts +14 -1
  87. package/dist/sessions/compress.js +52 -11
  88. package/dist/sessions/compress.js.map +1 -1
  89. package/dist/sessions/journal.d.ts +8 -0
  90. package/dist/sessions/journal.js +44 -10
  91. package/dist/sessions/journal.js.map +1 -1
  92. package/dist/sessions/list.d.ts +13 -5
  93. package/dist/sessions/list.js +56 -26
  94. package/dist/sessions/list.js.map +1 -1
  95. package/dist/sessions/merge.d.ts +122 -9
  96. package/dist/sessions/merge.js +544 -33
  97. package/dist/sessions/merge.js.map +1 -1
  98. package/dist/sessions/pending.d.ts +5 -0
  99. package/dist/sessions/pending.js +30 -10
  100. package/dist/sessions/pending.js.map +1 -1
  101. package/dist/sessions/service.d.ts +100 -4
  102. package/dist/sessions/service.js +567 -102
  103. package/dist/sessions/service.js.map +1 -1
  104. package/dist/sessions/session.d.ts +16 -1
  105. package/dist/sessions/session.js +29 -2
  106. package/dist/sessions/session.js.map +1 -1
  107. package/dist/sessions/shares.js +11 -2
  108. package/dist/sessions/shares.js.map +1 -1
  109. package/dist/sessions/state-db.d.ts +26 -4
  110. package/dist/sessions/state-db.js +126 -8
  111. package/dist/sessions/state-db.js.map +1 -1
  112. package/dist/sessions/store.js +6 -1
  113. package/dist/sessions/store.js.map +1 -1
  114. package/dist/sessions/streams.d.ts +10 -0
  115. package/dist/sessions/streams.js +14 -3
  116. package/dist/sessions/streams.js.map +1 -1
  117. package/dist/sessions/todo.d.ts +6 -1
  118. package/dist/sessions/todo.js +11 -4
  119. package/dist/sessions/todo.js.map +1 -1
  120. package/dist/sessions/tool-display.d.ts +12 -1
  121. package/dist/sessions/tool-display.js +76 -1
  122. package/dist/sessions/tool-display.js.map +1 -1
  123. package/dist/sessions/turn.d.ts +49 -10
  124. package/dist/sessions/turn.js +459 -185
  125. package/dist/sessions/turn.js.map +1 -1
  126. package/dist/settings.d.ts +20 -2
  127. package/dist/settings.js +49 -2
  128. package/dist/settings.js.map +1 -1
  129. package/dist/sidecar/fake.d.ts +2 -2
  130. package/dist/sidecar/fake.js +7 -2
  131. package/dist/sidecar/fake.js.map +1 -1
  132. package/dist/spa.js +2 -1
  133. package/dist/spa.js.map +1 -1
  134. package/dist/test/harness.js +2 -0
  135. package/dist/test/harness.js.map +1 -1
  136. package/dist/tools/commands.d.ts +15 -0
  137. package/dist/tools/commands.js +52 -0
  138. package/dist/tools/commands.js.map +1 -0
  139. package/dist/tools/crons.d.ts +4 -1
  140. package/dist/tools/crons.js +58 -11
  141. package/dist/tools/crons.js.map +1 -1
  142. package/dist/tools/insights.d.ts +8 -2
  143. package/dist/tools/insights.js +18 -2
  144. package/dist/tools/insights.js.map +1 -1
  145. package/dist/tools/kanban.d.ts +41 -1
  146. package/dist/tools/kanban.js +113 -2
  147. package/dist/tools/kanban.js.map +1 -1
  148. package/dist/tools/mcp.d.ts +2 -1
  149. package/dist/tools/mcp.js +16 -6
  150. package/dist/tools/mcp.js.map +1 -1
  151. package/dist/tools/terminal.d.ts +1 -0
  152. package/dist/tools/terminal.js +1 -1
  153. package/dist/tools/terminal.js.map +1 -1
  154. package/dist/tools/update-notifications.d.ts +1 -1
  155. package/dist/tools/update-notifications.js +1 -1
  156. package/dist/tools/update-notifications.js.map +1 -1
  157. package/dist/tools/updates.d.ts +15 -3
  158. package/dist/tools/updates.js +207 -41
  159. package/dist/tools/updates.js.map +1 -1
  160. package/dist/workspace/fs.d.ts +7 -3
  161. package/dist/workspace/fs.js +5 -1
  162. package/dist/workspace/fs.js.map +1 -1
  163. package/dist/workspace/git.d.ts +57 -10
  164. package/dist/workspace/git.js +302 -195
  165. package/dist/workspace/git.js.map +1 -1
  166. package/dist/workspace/media-refs.d.ts +13 -0
  167. package/dist/workspace/media-refs.js +165 -0
  168. package/dist/workspace/media-refs.js.map +1 -0
  169. package/dist/workspace/media.d.ts +19 -0
  170. package/dist/workspace/media.js +63 -2
  171. package/dist/workspace/media.js.map +1 -1
  172. package/dist/workspace/preview.d.ts +28 -0
  173. package/dist/workspace/preview.js +110 -0
  174. package/dist/workspace/preview.js.map +1 -0
  175. package/dist/workspace/rollback.d.ts +5 -4
  176. package/dist/workspace/rollback.js +12 -9
  177. package/dist/workspace/rollback.js.map +1 -1
  178. package/dist/workspace/upload.d.ts +2 -0
  179. package/dist/workspace/upload.js +4 -1
  180. package/dist/workspace/upload.js.map +1 -1
  181. package/dist/workspace/workspaces.d.ts +7 -1
  182. package/dist/workspace/workspaces.js +30 -21
  183. package/dist/workspace/workspaces.js.map +1 -1
  184. package/dist/workspace/worktrees.d.ts +2 -4
  185. package/dist/workspace/worktrees.js +28 -33
  186. package/dist/workspace/worktrees.js.map +1 -1
  187. package/package.json +4 -2
  188. package/sidecar/scripts/replay_sidecar.py +4 -2
  189. package/sidecar/talaria_sidecar/__init__.py +1 -1
  190. package/sidecar/talaria_sidecar/__main__.py +7 -1
  191. package/sidecar/talaria_sidecar/home.py +98 -2
  192. package/sidecar/talaria_sidecar/methods/__init__.py +2 -2
  193. package/sidecar/talaria_sidecar/methods/aux.py +19 -3
  194. package/sidecar/talaria_sidecar/methods/chat.py +551 -61
  195. package/sidecar/talaria_sidecar/methods/commands.py +61 -33
  196. package/sidecar/talaria_sidecar/methods/config.py +14 -10
  197. package/sidecar/talaria_sidecar/methods/cron.py +10 -6
  198. package/sidecar/talaria_sidecar/methods/kanban.py +115 -15
  199. package/sidecar/talaria_sidecar/methods/oauth.py +439 -0
  200. package/sidecar/talaria_sidecar/methods/process.py +60 -1
  201. package/sidecar/talaria_sidecar/methods/profiles.py +12 -5
  202. package/sidecar/talaria_sidecar/methods/providers.py +161 -1
  203. package/sidecar/talaria_sidecar/methods/runtime.py +5 -0
  204. package/sidecar/talaria_sidecar/methods/text.py +18 -21
  205. package/sidecar/talaria_sidecar/methods/usage.py +264 -5
  206. package/static/dist/FILES.txt +126 -121
  207. package/static/dist/assets/AppShell-fb2td9zi.js +1 -0
  208. package/static/dist/assets/ChatPage-cj96kiy9.js +25 -0
  209. package/static/dist/assets/Dialog-kxcg7ard.js +1 -0
  210. package/static/dist/assets/ExtensionRoute-jf7h0754.js +1 -0
  211. package/static/dist/assets/HubRoute-gmanpd5y.js +2 -0
  212. package/static/dist/assets/{Markdown-dy8t4dy7.js → Markdown-mdwlvj3w.js} +125 -125
  213. package/static/dist/assets/RightPanel-ftvrjlcp.js +1 -0
  214. package/static/dist/assets/Select-bilaypej.js +1 -0
  215. package/static/dist/assets/SessionListPanel-og8n36qs.js +1 -0
  216. package/static/dist/assets/SettingsLayout-jonjefmj.js +1 -0
  217. package/static/dist/assets/States-nmejrwrm.js +1 -0
  218. package/static/dist/assets/Toaster-nnl8zkt9.js +1 -0
  219. package/static/dist/assets/_app-gdidzd94.js +1 -0
  220. package/static/dist/assets/_app.ext._extensionId-opyh9y0v.js +1 -0
  221. package/static/dist/assets/_app.index-hbarzbjx.js +1 -0
  222. package/static/dist/assets/_app.insights-luywlarz.js +1 -0
  223. package/static/dist/assets/_app.kanban-kincnbr8.js +1 -0
  224. package/static/dist/assets/_app.logs-oi2gr3gs.js +1 -0
  225. package/static/dist/assets/_app.memory-b6jfivt6.js +1 -0
  226. package/static/dist/assets/_app.profiles-lpvixmqp.js +1 -0
  227. package/static/dist/assets/{_app.session._sessionId-gyoh5ylx.js → _app.session._sessionId-bfd4h3id.js} +1 -1
  228. package/static/dist/assets/_app.settings-vowx3iq0.js +1 -0
  229. package/static/dist/assets/_app.settings._section-cnntg1tu.js +3 -0
  230. package/static/dist/assets/_app.skills-fb57lpau.js +1 -0
  231. package/static/dist/assets/_app.tasks-eszpx1q7.js +2 -0
  232. package/static/dist/assets/_app.todos-nr05ac6w.js +1 -0
  233. package/static/dist/assets/_app.workspaces-d2luomu6.js +1 -0
  234. package/static/dist/assets/{abnfDiagram-VCTEODGH-cjqletra.js → abnfDiagram-VCTEODGH-b2x1lqot.js} +1 -1
  235. package/static/dist/assets/architecture-7GRP2DOG-hd0o7bde.js +1 -0
  236. package/static/dist/assets/architectureDiagram-5GKGNRK7-h38o5l7b.js +36 -0
  237. package/static/dist/assets/{back-e4m6nsnm.js → back-bemaer09.js} +1 -1
  238. package/static/dist/assets/{blockDiagram-I7D4REHJ-b195z021.js → blockDiagram-I7D4REHJ-bqquxv2u.js} +1 -1
  239. package/static/dist/assets/{c4Diagram-7LVT6UL2-olu212k1.js → c4Diagram-7LVT6UL2-f3fb8w6z.js} +1 -1
  240. package/static/dist/assets/cancel-drd0g6d9.js +1 -0
  241. package/static/dist/assets/channel-cpdgf867.js +1 -0
  242. package/static/dist/assets/{chunk-4HAMMTFA-b8o1nufs.js → chunk-4HAMMTFA-bzfyyzp3.js} +1 -1
  243. package/static/dist/assets/{chunk-75Z2AOVW-l1seddto.js → chunk-75Z2AOVW-ly99rfx5.js} +1 -1
  244. package/static/dist/assets/{chunk-DU6HZSFF-lo5zg15v.js → chunk-DU6HZSFF-bg6ecfqf.js} +3 -3
  245. package/static/dist/assets/{chunk-F27PBJKO-dsdzrp16.js → chunk-F27PBJKO-e8k7mmqw.js} +1 -1
  246. package/static/dist/assets/{chunk-GMAD6QVW-f3fxqq8b.js → chunk-GMAD6QVW-cmalnoaj.js} +1 -1
  247. package/static/dist/assets/{chunk-GVQU2GXP-h8egxtf6.js → chunk-GVQU2GXP-gmif5o01.js} +1 -1
  248. package/static/dist/assets/{chunk-IMKFNOWR-iolwd8s0.js → chunk-IMKFNOWR-f1nwwh6u.js} +1 -1
  249. package/static/dist/assets/{chunk-L3NEJ4N5-b88bbtwi.js → chunk-L3NEJ4N5-oktfh5v7.js} +1 -1
  250. package/static/dist/assets/{chunk-OSK3NFVY-e3e7nr6s.js → chunk-OSK3NFVY-b1sdx96z.js} +1 -1
  251. package/static/dist/assets/{chunk-P2QGCYS3-mdc46rt1.js → chunk-P2QGCYS3-fhu4nzxt.js} +1 -1
  252. package/static/dist/assets/{chunk-POPQ4Y6H-kqlllrrm.js → chunk-POPQ4Y6H-em31rnef.js} +1 -1
  253. package/static/dist/assets/{chunk-PWAF6VOD-he585k2x.js → chunk-PWAF6VOD-bxhhyftw.js} +1 -1
  254. package/static/dist/assets/{chunk-SHT3W25Y-gh94y9a4.js → chunk-SHT3W25Y-fmrvwcav.js} +3 -3
  255. package/static/dist/assets/{chunk-SVP7TREG-euxp9y7e.js → chunk-SVP7TREG-lymukl2j.js} +1 -1
  256. package/static/dist/assets/{chunk-TICWLB2K-c8cxes1q.js → chunk-TICWLB2K-lmza6k23.js} +1 -1
  257. package/static/dist/assets/classDiagram-ZZMXUADV-ko09sguk.js +1 -0
  258. package/static/dist/assets/classDiagram-v2-VYDZK3BY-ko09sguk.js +1 -0
  259. package/static/dist/assets/{copied-kq0u3ht7.js → copied-ima3pysb.js} +1 -1
  260. package/static/dist/assets/copy-fw5b1pc6.js +1 -0
  261. package/static/dist/assets/{cynefin-OW5HDTMX-lxd44ux4.js → cynefin-OW5HDTMX-czlhbyhs.js} +1 -1
  262. package/static/dist/assets/{cynefinDiagram-5FMLGOSQ-njpiwat8.js → cynefinDiagram-5FMLGOSQ-gq7mvswb.js} +1 -1
  263. package/static/dist/assets/{dagre-GXQ25YYZ-nvgy6yju.js → dagre-GXQ25YYZ-cqdbf7dr.js} +4 -4
  264. package/static/dist/assets/{diagram-S7CK7UJ4-g98nyv72.js → diagram-S7CK7UJ4-o6arxa58.js} +3 -3
  265. package/static/dist/assets/{diagram-UQ7AKVKN-ovgm6lvv.js → diagram-UQ7AKVKN-nhnd68tz.js} +1 -1
  266. package/static/dist/assets/{diagram-VSXAHHWV-pb2bn2e4.js → diagram-VSXAHHWV-dk2sclzn.js} +1 -1
  267. package/static/dist/assets/{diagram-VX7I27RA-6j4mvfqo.js → diagram-VX7I27RA-n3k5nyx3.js} +1 -1
  268. package/static/dist/assets/{diagram-Z3DM3KII-dqmb982r.js → diagram-Z3DM3KII-m6q9cwdh.js} +1 -1
  269. package/static/dist/assets/{ebnfDiagram-PWID7BFC-ozbpzb6d.js → ebnfDiagram-PWID7BFC-lsa5641p.js} +1 -1
  270. package/static/dist/assets/endpoints-d33siokx.js +64 -0
  271. package/static/dist/assets/{erDiagram-RLTQ6QDP-hl7m8vua.js → erDiagram-RLTQ6QDP-fo2ckbe6.js} +1 -1
  272. package/static/dist/assets/eventmodeling-NTZA5JFV-emctlo64.js +1 -0
  273. package/static/dist/assets/flowDiagram-HODETNUW-bfi3moqa.js +1 -0
  274. package/static/dist/assets/{ganttDiagram-EL5Y4UJY-ompixqmh.js → ganttDiagram-EL5Y4UJY-nzqz6x9g.js} +1 -1
  275. package/static/dist/assets/{gitGraph-4MIJSDKK-b6vxwp9q.js → gitGraph-4MIJSDKK-gukvcxe6.js} +1 -1
  276. package/static/dist/assets/{gitGraphDiagram-WWUBYQGX-k98f57on.js → gitGraphDiagram-WWUBYQGX-lqhdp8f0.js} +1 -1
  277. package/static/dist/assets/{highlighted-body-KPVGNVTW-cd3ff6ge.js → highlighted-body-KPVGNVTW-igw6cp39.js} +1 -1
  278. package/static/dist/assets/index-cxq58xb1.js +11 -0
  279. package/static/dist/assets/index-hgtx08n5.css +2 -0
  280. package/static/dist/assets/{info-A6RAGUB7-hjcwjami.js → info-A6RAGUB7-gvu7a93l.js} +1 -1
  281. package/static/dist/assets/infoDiagram-27XIBGKW-m8t8g6w1.js +2 -0
  282. package/static/dist/assets/{ishikawaDiagram-5VMMS53U-bxak4mwu.js → ishikawaDiagram-5VMMS53U-l07vkbgj.js} +1 -1
  283. package/static/dist/assets/{journeyDiagram-3NMN7TZE-khmihclr.js → journeyDiagram-3NMN7TZE-hybkgj8l.js} +1 -1
  284. package/static/dist/assets/{kanban-definition-UXKFOSKX-h1r32wdx.js → kanban-definition-UXKFOSKX-by13uvtn.js} +1 -1
  285. package/static/dist/assets/{lazyRouteComponent-b2eg0fws.js → lazyRouteComponent-af0lvgjx.js} +1 -1
  286. package/static/dist/assets/{line-friyjof3.js → line-osxq6fq7.js} +1 -1
  287. package/static/dist/assets/link-jyxicdhg.js +1 -0
  288. package/static/dist/assets/login-ec21t39x.js +1 -0
  289. package/static/dist/assets/mermaid-HWGCJPDP-gfl8z3ep.js +1 -0
  290. package/static/dist/assets/{mermaid-parser.core-f1gvpwrt.js → mermaid-parser.core-i91oxmou.js} +3 -3
  291. package/static/dist/assets/{mindmap-definition-YA3MSWOX-mt2oxwue.js → mindmap-definition-YA3MSWOX-m82mx8ww.js} +1 -1
  292. package/static/dist/assets/onboarding-btnltvyz.js +1 -0
  293. package/static/dist/assets/onboarding_model_label-dp7xffoh.js +1 -0
  294. package/static/dist/assets/{packet-AYTQ26CC-jik4ixlf.js → packet-AYTQ26CC-nbtgge2o.js} +1 -1
  295. package/static/dist/assets/{pegDiagram-XKGWAZYB-erxlv6cc.js → pegDiagram-XKGWAZYB-f1zxzga1.js} +1 -1
  296. package/static/dist/assets/{pie-WAS4IAKB-mnkoldfl.js → pie-WAS4IAKB-of8vuhxs.js} +1 -1
  297. package/static/dist/assets/{pieDiagram-E7YTZNPT-i7lwrada.js → pieDiagram-E7YTZNPT-o7y8ftio.js} +3 -3
  298. package/static/dist/assets/{quadrantDiagram-AXDQQJYC-fr8ejdoy.js → quadrantDiagram-AXDQQJYC-iqiwia1t.js} +1 -1
  299. package/static/dist/assets/{radar-RG4KPBEZ-otarjte2.js → radar-RG4KPBEZ-delnsy2v.js} +1 -1
  300. package/static/dist/assets/{railroad-74A4TZTK-lw362oto.js → railroad-74A4TZTK-nspnyq9u.js} +1 -1
  301. package/static/dist/assets/railroad-abnf-HS5TGJTU-nst1qxyn.js +1 -0
  302. package/static/dist/assets/railroad-ebnf-LZEXJU2U-ci37x8qy.js +1 -0
  303. package/static/dist/assets/railroad-peg-WCYAUIDC-aimkk07z.js +1 -0
  304. package/static/dist/assets/{railroadDiagram-O6MQD6OU-esyn75pw.js → railroadDiagram-O6MQD6OU-z41tqx5z.js} +1 -1
  305. package/static/dist/assets/refresh-b4uams1i.js +1 -0
  306. package/static/dist/assets/refresh-cw-nt1dtsx4.js +1 -0
  307. package/static/dist/assets/{requirementDiagram-BXWQKSXE-cyhi5u29.js → requirementDiagram-BXWQKSXE-bdvif194.js} +1 -1
  308. package/static/dist/assets/{sankeyDiagram-P5KCCOFB-jljpbo0h.js → sankeyDiagram-P5KCCOFB-h73e0rol.js} +1 -1
  309. package/static/dist/assets/{saved-do49si3v.js → saved-kg52csy7.js} +1 -1
  310. package/static/dist/assets/{sequenceDiagram-WJ2MYXX4-fz0dhlu7.js → sequenceDiagram-WJ2MYXX4-iv95mb1z.js} +1 -1
  311. package/static/dist/assets/{settings_section_extensions_title-b7lojn0o.js → settings_section_extensions_title-ej0iapxe.js} +1 -1
  312. package/static/dist/assets/share._token-f97e94r0.js +2 -0
  313. package/static/dist/assets/sliders-horizontal-fj18it47.js +1 -0
  314. package/static/dist/assets/sse-fwpqtzbg.js +1 -0
  315. package/static/dist/assets/{stateDiagram-D77RDMKH-kdnithdx.js → stateDiagram-D77RDMKH-k417ixmf.js} +1 -1
  316. package/static/dist/assets/stateDiagram-v2-MP3YSRHH-hwn4apct.js +1 -0
  317. package/static/dist/assets/{swimlanes-42K2YHIH-esd6gmz3.js → swimlanes-42K2YHIH-esola70w.js} +1 -1
  318. package/static/dist/assets/swimlanesDiagram-VR7AAH4N-d1h97oyg.js +8 -0
  319. package/static/dist/assets/{timeline-definition-24CTP7MA-g0qygccv.js → timeline-definition-24CTP7MA-getrz2y8.js} +1 -1
  320. package/static/dist/assets/todoStore-eginf9h4.js +1 -0
  321. package/static/dist/assets/{treeView-Q6P3EWNA-ndchzkuu.js → treeView-Q6P3EWNA-hc7ap1q7.js} +1 -1
  322. package/static/dist/assets/{treemap-WGGIJYW6-hwjxj0zz.js → treemap-WGGIJYW6-b7k1syy5.js} +1 -1
  323. package/static/dist/assets/tts-lxdxnmjd.js +1 -0
  324. package/static/dist/assets/{untitled-mbr6tuhu.js → untitled-ovpfthg4.js} +1 -1
  325. package/static/dist/assets/useForm-bcblv1rx.js +1 -0
  326. package/static/dist/assets/useLocale-fi5jb8ma.js +1 -0
  327. package/static/dist/assets/{useParams-lo9u33d1.js → useParams-lhg566ew.js} +1 -1
  328. package/static/dist/assets/{useSearch-lq1mr090.js → useSearch-lbrh6ejn.js} +1 -1
  329. package/static/dist/assets/{vennDiagram-4TSXK5OY-gubovd1r.js → vennDiagram-4TSXK5OY-n6pcy5qb.js} +1 -1
  330. package/static/dist/assets/{wardley-WFR3VGLG-ir3om65j.js → wardley-WFR3VGLG-lpa18vz1.js} +1 -1
  331. package/static/dist/assets/{wardleyDiagram-VM6X3IG4-ce0or1ie.js → wardleyDiagram-VM6X3IG4-kpclwwg7.js} +2 -2
  332. package/static/dist/assets/{xychartDiagram-S5SC5T6Z-dexylcjg.js → xychartDiagram-S5SC5T6Z-paa2n8y0.js} +1 -1
  333. package/static/dist/index.html +1 -1
  334. package/static/dist/sw.js +2 -2
  335. package/static/dist/assets/AppShell-orat1s43.js +0 -1
  336. package/static/dist/assets/ChatPage-ojhka3hy.js +0 -22
  337. package/static/dist/assets/HubRoute-gxgw1t33.js +0 -2
  338. package/static/dist/assets/RightPanel-c66ani1s.js +0 -1
  339. package/static/dist/assets/Select-fswf66up.js +0 -1
  340. package/static/dist/assets/SessionListPanel-goh5mtt8.js +0 -1
  341. package/static/dist/assets/SettingsLayout-n5d4wq4m.js +0 -1
  342. package/static/dist/assets/States-kxyu50at.js +0 -1
  343. package/static/dist/assets/Toaster-6zmbcqgg.js +0 -1
  344. package/static/dist/assets/_app-fowyruiu.js +0 -1
  345. package/static/dist/assets/_app.ext._extensionId-nvjyrwhy.js +0 -1
  346. package/static/dist/assets/_app.index-c9zv4y7w.js +0 -1
  347. package/static/dist/assets/_app.insights-jf12nofo.js +0 -1
  348. package/static/dist/assets/_app.kanban-lri5a4yu.js +0 -1
  349. package/static/dist/assets/_app.logs-md1p0zqo.js +0 -1
  350. package/static/dist/assets/_app.memory-bzvf2e28.js +0 -1
  351. package/static/dist/assets/_app.profiles-jpg7zmbn.js +0 -1
  352. package/static/dist/assets/_app.settings-mpuwlx4j.js +0 -1
  353. package/static/dist/assets/_app.settings._section-knz8rwoj.js +0 -1
  354. package/static/dist/assets/_app.skills-fwu1dkir.js +0 -1
  355. package/static/dist/assets/_app.tasks-h9gphtl3.js +0 -2
  356. package/static/dist/assets/_app.todos-hd1dnvrh.js +0 -1
  357. package/static/dist/assets/_app.workspaces-l8toyn5f.js +0 -1
  358. package/static/dist/assets/architecture-7GRP2DOG-cavnusaw.js +0 -1
  359. package/static/dist/assets/architectureDiagram-5GKGNRK7-k5a7v36n.js +0 -36
  360. package/static/dist/assets/channel-gozy3dp3.js +0 -1
  361. package/static/dist/assets/classDiagram-ZZMXUADV-n0vs2f2q.js +0 -1
  362. package/static/dist/assets/classDiagram-v2-VYDZK3BY-n0vs2f2q.js +0 -1
  363. package/static/dist/assets/endpoints-k8agnro8.js +0 -9
  364. package/static/dist/assets/eventmodeling-NTZA5JFV-j1y2ekec.js +0 -1
  365. package/static/dist/assets/flowDiagram-HODETNUW-g2cvcbm5.js +0 -1
  366. package/static/dist/assets/index-nfy0ezs6.css +0 -2
  367. package/static/dist/assets/index-nqed3oum.js +0 -11
  368. package/static/dist/assets/infoDiagram-27XIBGKW-fm06rj36.js +0 -2
  369. package/static/dist/assets/link-m90ahx3n.js +0 -1
  370. package/static/dist/assets/login-bw7m2fzo.js +0 -1
  371. package/static/dist/assets/matchContext-f8oy3lo7.js +0 -1
  372. package/static/dist/assets/mermaid-HWGCJPDP-njwcl62n.js +0 -1
  373. package/static/dist/assets/onboarding-ret37j1e.js +0 -1
  374. package/static/dist/assets/railroad-abnf-HS5TGJTU-m741owar.js +0 -1
  375. package/static/dist/assets/railroad-ebnf-LZEXJU2U-noa4fbia.js +0 -1
  376. package/static/dist/assets/railroad-peg-WCYAUIDC-o4bpw4nb.js +0 -1
  377. package/static/dist/assets/refresh-cw-k66keho6.js +0 -1
  378. package/static/dist/assets/search-j5ki6a9o.js +0 -1
  379. package/static/dist/assets/share._token-kg8w647b.js +0 -2
  380. package/static/dist/assets/sliders-horizontal-c4wya4dy.js +0 -1
  381. package/static/dist/assets/stateDiagram-v2-MP3YSRHH-k2wg2645.js +0 -1
  382. package/static/dist/assets/swimlanesDiagram-VR7AAH4N-n5kvjtgr.js +0 -8
  383. package/static/dist/assets/tabBuild-bjmyvmn5.js +0 -56
  384. package/static/dist/assets/todoStore-kjk5l0sy.js +0 -1
  385. package/static/dist/assets/useForm-ocxu460f.js +0 -1
  386. package/static/dist/assets/useLocale-fzy5jhg6.js +0 -1
@@ -11,6 +11,9 @@ from __future__ import annotations
11
11
 
12
12
  import dataclasses
13
13
  import logging
14
+ import sys
15
+ import threading
16
+ from pathlib import Path
14
17
  from typing import Any
15
18
 
16
19
  from ..errors import InvalidParams, RpcError
@@ -61,19 +64,176 @@ def auth_status(provider_id: str | None) -> dict:
61
64
  return _plain(status) if isinstance(status, dict) else {"logged_in": bool(status)}
62
65
 
63
66
 
67
+ def _module_dir(module: Any) -> Path | None:
68
+ try:
69
+ return Path(module.__file__).resolve().parent
70
+ except Exception: # noqa: BLE001 - namespace/builtin modules have no file
71
+ return None
72
+
73
+
74
+ # The Agent's module-name prefix for provider plugins it imports from a ``$HERMES_HOME`` (``providers._import_plugin_dir``).
75
+ _USER_PLUGIN_MODULE = "_hermes_user_provider_"
76
+ _LOAD_LOCK = threading.Lock()
77
+ _LOAD_ATTEMPTED: set[Path] = set()
78
+
79
+
80
+ def _user_plugin_profiles() -> dict[int, tuple[Any, Path]]:
81
+ """Each registered profile a ``$HERMES_HOME`` provider plugin module holds, with that module's directory."""
82
+ from providers import list_providers
83
+ from providers.base import ProviderProfile
84
+
85
+ registered = {id(p) for p in list_providers()}
86
+ out: dict[int, tuple[Any, Path]] = {}
87
+ for name, module in list(sys.modules.items()):
88
+ directory = _module_dir(module) if module is not None and name.startswith(_USER_PLUGIN_MODULE) else None
89
+ if directory is None:
90
+ continue
91
+ for value in list(vars(module).values()):
92
+ if isinstance(value, ProviderProfile) and id(value) in registered:
93
+ out.setdefault(id(value), (value, directory))
94
+ return out
95
+
96
+
97
+ def _load_plugin(root: Path) -> None:
98
+ """Load the scoped profile's installed plugin that the process-wide discovery (run under the launch profile) skipped.
99
+
100
+ Uses the Agent's own loader, as ``hermes plugins dev`` does, once per directory. A plugin never displaces a provider
101
+ that is already registered, so one profile's plugin cannot change what another profile's provider id runs.
102
+ """
103
+ import providers
104
+
105
+ with _LOAD_LOCK:
106
+ if root in _LOAD_ATTEMPTED:
107
+ return
108
+ _LOAD_ATTEMPTED.add(root)
109
+ before = {p.name: p for p in providers.list_providers()}
110
+ try:
111
+ providers._import_plugin_dir(root, "user")
112
+ except Exception: # noqa: BLE001 - a broken plugin reads as not loaded
113
+ log.debug("loading provider plugin %s failed", root, exc_info=True)
114
+ return
115
+ after = {p.name: p for p in providers.list_providers()}
116
+ for name, previous in before.items():
117
+ if after.get(name) is not previous:
118
+ providers.register_provider(previous)
119
+
120
+
121
+ def installed_plugin_profiles() -> list[tuple[str, Any]]:
122
+ """``(manifest name, profile | None)`` for each enabled model-provider plugin installed in the scoped home.
123
+
124
+ Bundled providers are built-ins, not plugins. The Agent keeps one provider registry per process, so a profile
125
+ belongs to this home only when the plugin module that registered it lives in one of this home's plugin
126
+ directories; a plugin the launch discovery skipped is loaded here. ``None`` means the plugin could not be loaded:
127
+ it failed to import, its provider id is taken by another plugin, or another profile's plugin has the same directory
128
+ name. ponytail: ownership is read from the plugin module's globals, so a plugin that registers an unbound inline
129
+ profile also reads as not loaded; track registrations in the Agent if one ever does.
130
+ """
131
+ try:
132
+ from hermes_cli.plugins_discovery import _get_disabled_plugins, _get_enabled_plugins, collect_directory_manifests, gate_manifest
133
+ except Exception as exc: # noqa: BLE001
134
+ raise RpcError(f"plugin providers unavailable: {exc}", condition="plugins_unavailable") from exc
135
+ disabled, enabled = _get_disabled_plugins(), _get_enabled_plugins()
136
+ roots = {
137
+ Path(m.path).resolve(): str(m.name)
138
+ for m in collect_directory_manifests()
139
+ if m.kind == "model-provider" and m.source == "user" and m.path and gate_manifest(m, disabled, enabled).enabled
140
+ }
141
+ if not roots:
142
+ return []
143
+
144
+ def owned() -> dict[Path, list]:
145
+ found: dict[Path, list] = {root: [] for root in roots}
146
+ for profile, directory in _user_plugin_profiles().values():
147
+ root = next((r for r in roots if directory == r or r in directory.parents), None)
148
+ if root is not None:
149
+ found[root].append(profile)
150
+ return found
151
+
152
+ found = owned()
153
+ missing = [root for root, profiles in found.items() if not profiles]
154
+ if missing:
155
+ for root in missing:
156
+ _load_plugin(root)
157
+ found = owned()
158
+ return [(name, p) for root, name in roots.items() for p in (found[root] or [None])]
159
+
160
+
161
+ def _other_profile_plugin(provider_id: str) -> bool:
162
+ """Whether ``provider_id`` is a plugin provider that another profile installed (the registry is process-wide)."""
163
+ from providers import get_provider_profile
164
+
165
+ profile = get_provider_profile(provider_id)
166
+ if profile is None or id(profile) not in _user_plugin_profiles():
167
+ return False
168
+ return not any(p is profile for _name, p in installed_plugin_profiles())
169
+
170
+
171
+ def _setup_state(profile: Any) -> str:
172
+ """The Agent's own setup verdict for a plugin provider, without spawning its CLI or reading its credentials."""
173
+ try:
174
+ status = auth_status(profile.name)
175
+ except RpcError:
176
+ return "unavailable"
177
+ if status.get("error"):
178
+ return "unavailable"
179
+ if profile.auth_type == "external_process":
180
+ return "ready" if status.get("configured") else "missing_cli"
181
+ return "ready" if status.get("logged_in") or status.get("configured") else "needs_setup"
182
+
183
+
184
+ def plugin_providers() -> list[dict]:
185
+ """Sanitized rows for this profile's enabled model-provider plugins: identity and setup state, no paths or secrets."""
186
+ rows = []
187
+ for manifest_name, profile in installed_plugin_profiles():
188
+ if profile is None:
189
+ rows.append({"name": manifest_name, "display_name": manifest_name, "auth_type": "", "setup": "not_loaded"})
190
+ continue
191
+ name = str(profile.name).strip().lower()
192
+ rows.append({"name": name, "display_name": str(profile.display_name or name).strip(), "auth_type": str(profile.auth_type), "setup": _setup_state(profile)})
193
+ return rows
194
+
195
+
196
+ def _plugin_catalog(provider_id: str) -> list[str]:
197
+ """Discovery for an installed plugin the Agent's catalog has no fetcher for: the profile's own listing, then its fallback.
198
+
199
+ API-key plugins already get both from ``provider_model_ids`` with their key, so only keyless auth types list here.
200
+ """
201
+ for _name, profile in installed_plugin_profiles():
202
+ if profile is None or str(profile.name).strip().lower() != provider_id.strip().lower():
203
+ continue
204
+ try:
205
+ live = profile.fetch_models(timeout=8.0) if profile.supports_model_listing and profile.auth_type != "api_key" else None
206
+ except Exception: # noqa: BLE001 - a broken plugin answers with its fallback catalog
207
+ log.debug("fetch_models(%r) failed", provider_id, exc_info=True)
208
+ live = None
209
+ return [str(m) for m in (live or profile.fallback_models or ()) if m]
210
+ return []
211
+
212
+
64
213
  def model_ids(provider_id: str, *, force_refresh: bool) -> list[str]:
65
214
  try:
66
215
  from hermes_cli.models import provider_model_ids
67
216
  except Exception as exc: # noqa: BLE001
68
217
  raise RpcError(f"model catalog unavailable: {exc}", condition="providers_unavailable") from exc
218
+ try:
219
+ if _other_profile_plugin(provider_id):
220
+ return []
221
+ except Exception: # noqa: BLE001 - no readable provider registry means no plugin to keep apart
222
+ log.debug("plugin ownership check for %r failed", provider_id, exc_info=True)
69
223
  try:
70
224
  ids = provider_model_ids(provider_id, force_refresh=force_refresh)
71
225
  except TypeError:
72
226
  ids = provider_model_ids(provider_id)
73
227
  except Exception: # noqa: BLE001
74
228
  log.debug("provider_model_ids(%r) failed", provider_id, exc_info=True)
229
+ ids = []
230
+ ids = [str(m) for m in (ids or []) if m]
231
+ if ids:
232
+ return ids
233
+ try:
234
+ return _plugin_catalog(provider_id)
235
+ except RpcError:
75
236
  return []
76
- return [str(m) for m in (ids or []) if m]
77
237
 
78
238
 
79
239
  def resolve_runtime(*, requested: str | None, api_key: str | None, base_url: str | None, target_model: str | None) -> dict:
@@ -7,6 +7,7 @@ import re
7
7
 
8
8
  from .. import SIDECAR_RPC_VERSION
9
9
  from ..errors import InvalidParams, RpcError
10
+ from ..home import edit_launch_env
10
11
  from ..rpc import CallContext
11
12
 
12
13
 
@@ -53,6 +54,7 @@ def register(registry) -> None:
53
54
  os.environ.pop(name, None)
54
55
  for name, value in to_set.items():
55
56
  os.environ[name] = value
57
+ edit_launch_env(to_set, to_unset)
56
58
  # Cached agents bound the credentials they were built with; none of them may outlive a credential change.
57
59
  from .chat import evict_all_agents
58
60
 
@@ -64,5 +66,8 @@ def register(registry) -> None:
64
66
  code = params.get("exit_code", 0)
65
67
  if not isinstance(code, int):
66
68
  raise InvalidParams("exit_code must be an integer")
69
+ from .chat import drain_agents
70
+
71
+ drain_agents()
67
72
  ctx.server.request_shutdown(exit_code=code)
68
73
  return {"ok": True}
@@ -1,34 +1,22 @@
1
1
  """``text.*``: pure helpers from the Agent that the server must match exactly
2
- (redaction patterns, image input routing, portal tags)."""
2
+ (image input routing, portal tags)."""
3
3
 
4
4
  from __future__ import annotations
5
5
 
6
6
  import logging
7
7
 
8
- from ..errors import InvalidParams
9
8
  from ..home import profile_home_param, scoped_home
10
9
  from ..rpc import CallContext
11
10
 
12
11
  log = logging.getLogger("talaria_sidecar.text")
13
12
 
14
13
 
15
- def redact(text: str, *, force: bool = True) -> str:
16
- try:
17
- from agent.redact import redact_sensitive_text
18
- except Exception: # noqa: BLE001
19
- return text
20
- try:
21
- return redact_sensitive_text(text, force=force)
22
- except TypeError:
23
- return redact_sensitive_text(text)
24
-
25
-
26
14
  def image_mode(*, provider: str, model: str, cfg: dict | None, requested_provider: str) -> dict:
27
15
  """``native`` or ``text`` for current-turn image uploads, as the Agent decides it."""
28
16
  try:
29
17
  from agent.image_routing import decide_image_input_mode
30
18
  except Exception: # noqa: BLE001
31
- return {"mode": "text", "reason": "image routing unavailable"}
19
+ return {"mode": "text", "reason": "image routing unavailable", "supports_vision": None}
32
20
  try:
33
21
  decision = decide_image_input_mode(provider, model, cfg, requested_provider=requested_provider)
34
22
  except TypeError:
@@ -46,6 +34,18 @@ def image_mode(*, provider: str, model: str, cfg: dict | None, requested_provide
46
34
  return {"mode": str(decision or "text"), "reason": "", "supports_vision": supports_vision}
47
35
 
48
36
 
37
+ def _profile_config() -> dict | None:
38
+ """The scoped profile's ``config.yaml``; None when the Agent cannot load it."""
39
+ try:
40
+ from hermes_cli.config import load_config
41
+
42
+ cfg = load_config()
43
+ except Exception: # noqa: BLE001
44
+ log.debug("image mode config load failed", exc_info=True)
45
+ return None
46
+ return cfg if isinstance(cfg, dict) else None
47
+
48
+
49
49
  def portal_tags() -> dict:
50
50
  try:
51
51
  from agent.portal_tags import conversation_tag, hermes_client_tag, nous_portal_tags
@@ -66,17 +66,14 @@ def portal_tags() -> dict:
66
66
 
67
67
 
68
68
  def register(registry) -> None:
69
- @registry.method("text.redact")
70
- def redact_(ctx: CallContext, params: dict) -> dict:
71
- text = params.get("text")
72
- if not isinstance(text, str):
73
- raise InvalidParams("text must be a string")
74
- return {"text": redact(text, force=bool(params.get("force", True)))}
75
-
76
69
  @registry.method("text.image_mode")
77
70
  def image_mode_(ctx: CallContext, params: dict) -> dict:
78
71
  cfg = params.get("cfg") if isinstance(params.get("cfg"), dict) else None
79
72
  with scoped_home(profile_home_param(params)):
73
+ # TAL-545: without an explicit cfg the profile's config.yaml decides, so its vision overrides
74
+ # (``agent.image_input_mode``, ``supports_vision``) apply as they do for the Agent's own turn.
75
+ if cfg is None:
76
+ cfg = _profile_config()
80
77
  return image_mode(provider=str(params.get("provider") or ""), model=str(params.get("model") or ""), cfg=cfg, requested_provider=str(params.get("requested_provider") or ""))
81
78
 
82
79
  @registry.method("text.portal_tags")
@@ -1,9 +1,16 @@
1
- """``usage.*``: provider account usage through ``agent.account_usage``."""
1
+ """``usage.*``: provider account usage through ``agent.account_usage``, the credential pool's local per-entry
2
+ state, and the key-based balance endpoints (TAL-548). Pool keys never leave the sidecar."""
2
3
 
3
4
  from __future__ import annotations
4
5
 
5
6
  import dataclasses
7
+ import json
6
8
  import logging
9
+ import math
10
+ import time
11
+ import urllib.error
12
+ import urllib.request
13
+ from datetime import date, datetime, timezone
7
14
  from typing import Any
8
15
 
9
16
  from ..errors import InvalidParams, RpcError
@@ -22,18 +29,246 @@ def _plain(value: Any) -> Any:
22
29
  return [_plain(v) for v in value]
23
30
  if isinstance(value, (str, int, float, bool)) or value is None:
24
31
  return value
32
+ if isinstance(value, datetime) and value.tzinfo is not None:
33
+ return value.astimezone(timezone.utc).isoformat().replace("+00:00", "Z")
34
+ if isinstance(value, date):
35
+ return value.isoformat()
25
36
  if hasattr(value, "__dict__"):
26
37
  return {k: _plain(v) for k, v in vars(value).items() if not k.startswith("_")}
27
38
  return str(value)
28
39
 
29
40
 
30
- def account(provider: str, *, base_url: str | None, api_key: str | None) -> dict | None:
41
+ def _iso(epoch: float | None) -> str | None:
42
+ return None if epoch is None else datetime.fromtimestamp(epoch, tz=timezone.utc).isoformat().replace("+00:00", "Z")
43
+
44
+
45
+ # Python `_is_ambient_gh_cli_entry`: a seeded `gh` CLI or GitHub env token is not an account the user added.
46
+ _AMBIENT_GH_SOURCES = frozenset({"gh_cli", "gh auth token", "env:github_token", "env:gh_token"})
47
+
48
+
49
+ def _is_ambient(entry) -> bool:
50
+ return any(str((entry.get(k) if isinstance(entry, dict) else getattr(entry, k, "")) or "").strip().lower() in _AMBIENT_GH_SOURCES for k in ("source", "label", "key_source"))
51
+
52
+
53
+ def pool_providers() -> list[str]:
54
+ """Providers whose persisted credential pool holds an account the user added; a read of ``auth.json`` with no seeding."""
55
+ try:
56
+ from hermes_cli.auth import read_credential_pool
57
+ except Exception as exc: # noqa: BLE001
58
+ raise RpcError(f"credential pool unavailable: {exc}", condition="usage_unavailable") from exc
59
+ try:
60
+ pools = read_credential_pool()
61
+ except Exception: # noqa: BLE001
62
+ log.debug("read_credential_pool() failed", exc_info=True)
63
+ return []
64
+ return sorted(str(pid) for pid, entries in (pools or {}).items()
65
+ if isinstance(entries, list) and any(isinstance(e, dict) and str(e.get("id") or "").strip() and not _is_ambient(e) for e in entries))
66
+
67
+
68
+ def _pool(provider: str) -> tuple[Any, list]:
69
+ """The Agent's credential pool for the scoped profile and its non-ambient entries; ``(None, [])`` when unreadable."""
70
+ try:
71
+ from agent.credential_pool import load_pool
72
+ except Exception as exc: # noqa: BLE001
73
+ raise RpcError(f"credential pool unavailable: {exc}", condition="usage_unavailable") from exc
74
+ try:
75
+ pool = load_pool(provider)
76
+ entries = list(pool.entries()) if pool is not None else []
77
+ except Exception: # noqa: BLE001
78
+ log.debug("load_pool(%r) failed", provider, exc_info=True)
79
+ return None, []
80
+ return pool, [e for e in entries if not _is_ambient(e)]
81
+
82
+
83
+ def _entry(provider: str, credential_id: str):
84
+ return next((e for e in _pool(provider)[1] if str(getattr(e, "id", "") or "").strip() == credential_id), None)
85
+
86
+
87
+ def _entry_key(entry) -> str:
88
+ """Python `_get_provider_api_key(provider, credential_id)`: the entry's runtime key."""
89
+ for name in ("runtime_api_key", "agent_key", "access_token"):
90
+ try:
91
+ value = str(getattr(entry, name, "") or "").strip()
92
+ except Exception: # noqa: BLE001 - a runtime key property that cannot resolve
93
+ value = ""
94
+ if value:
95
+ return value
96
+ return ""
97
+
98
+
99
+ def _label(entry, index: int) -> str:
100
+ """Python `_safe_entry_label`."""
101
+ label = " ".join(str(getattr(entry, "label", "") or getattr(entry, "source", "") or f"Credential {index}").split())
102
+ return label[:61].rstrip() + "..." if len(label) > 64 else label
103
+
104
+
105
+ def pool(provider: str, *, api_key: str | None = None) -> list[dict]:
106
+ """Python `_local_pool_snapshot` rows: one per pool entry with an id, its local status, whether it holds the
107
+ server-resolved ``api_key``, and no secrets."""
108
+ agent_pool, entries = _pool(provider)
109
+ configured = str(api_key or "").strip()
110
+ try:
111
+ from agent.credential_pool import _exhausted_until
112
+ except Exception: # noqa: BLE001 - an Agent without it cannot date an exhausted mark, so the entry reads as available
113
+ def _exhausted_until(entry, *, sole_credential=False):
114
+ return None
115
+ sole_fn = getattr(agent_pool, "_is_sole_credential", None)
116
+ sole = bool(sole_fn()) if callable(sole_fn) else len(entries) == 1
117
+ rows = []
118
+ for index, entry in enumerate(entries, start=1):
119
+ credential_id = str(getattr(entry, "id", "") or "").strip()
120
+ if not credential_id:
121
+ continue
122
+ row = {"credential_id": credential_id, "label": _label(entry, index), "status": "available", "unavailable_reason": None, "retry_after": None,
123
+ "matches_api_key": bool(configured) and _entry_key(entry) == configured}
124
+ status = str(getattr(entry, "last_status", "") or "").strip().lower()
125
+ until = _exhausted_until(entry, sole_credential=sole) if status == "exhausted" else None
126
+ if status == "dead":
127
+ row.update(status="dead", unavailable_reason="Credential permanently revoked or invalid.")
128
+ elif until is not None and time.time() < until:
129
+ retry_after = _iso(until)
130
+ code = getattr(entry, "last_error_code", None)
131
+ reason = "Credential pool marked this credential exhausted" + (f" after provider status {code}" if code else "") + (f"; retry after {retry_after}" if retry_after else "")
132
+ row.update(status="exhausted", unavailable_reason=reason + ".", retry_after=retry_after)
133
+ rows.append(row)
134
+ return rows
135
+
136
+
137
+ _BALANCE_URLS = {
138
+ "openrouter": "https://openrouter.ai/api/v1/key",
139
+ "deepseek": "https://api.deepseek.com/user/balance",
140
+ "opencode-go": "https://opencode.ai/zen/go/v1/usage",
141
+ }
142
+ _BALANCE_TIMEOUT_SECONDS = 15.0
143
+ _BALANCE_MAX_BYTES = 256 * 1024
144
+
145
+
146
+ def _number(value: Any) -> int | float | None:
147
+ """Python `_quota_number`, finite only: a NaN or infinity is not JSON."""
148
+ if isinstance(value, bool) or value is None:
149
+ return None
150
+ if isinstance(value, int):
151
+ return value
152
+ try:
153
+ number = float(value if isinstance(value, float) else str(value).strip())
154
+ except (TypeError, ValueError):
155
+ return None
156
+ if not math.isfinite(number):
157
+ return None
158
+ return int(number) if number.is_integer() and not isinstance(value, float) else number
159
+
160
+
161
+ def _openrouter(payload: Any) -> dict | None:
162
+ data = payload.get("data") if isinstance(payload, dict) and isinstance(payload.get("data"), dict) else payload
163
+ data = data if isinstance(data, dict) else {}
164
+ return {"quota": {k: _number(data.get(k)) for k in ("limit_remaining", "usage", "limit")}, "label": str(data.get("label") or "").strip() or None}
165
+
166
+
167
+ def _deepseek(payload: Any) -> dict | None:
168
+ """Python `_sanitize_deepseek_balances`: CNY/USD rows with a total, plus the account's `is_available`."""
169
+ if not isinstance(payload, dict) or not isinstance(payload.get("is_available"), bool):
170
+ return None
171
+ balances = []
172
+ for row in payload.get("balance_infos") or []:
173
+ if not isinstance(row, dict):
174
+ continue
175
+ currency = str(row.get("currency") or "").strip().upper()
176
+ total = _number(row.get("total_balance"))
177
+ if currency in {"CNY", "USD"} and total is not None:
178
+ balances.append({"currency": currency, "total": total, "granted": _number(row.get("granted_balance")), "topped_up": _number(row.get("topped_up_balance"))})
179
+ return {"balances": balances, "is_available": payload["is_available"]} if balances else None
180
+
181
+
182
+ def _opencode_go(payload: Any) -> dict | None:
183
+ """Python `_sanitize_opencode_go_account_limits`: all three windows, each well-formed, or nothing."""
184
+ usage = payload.get("usage") if isinstance(payload, dict) else None
185
+ if not isinstance(usage, dict):
186
+ return None
187
+ windows = []
188
+ for key in ("rolling", "weekly", "monthly"):
189
+ window = usage.get(key)
190
+ if not isinstance(window, dict) or window.get("status") not in {"ok", "rate-limited"}:
191
+ return None
192
+ used = _number(window.get("percent"))
193
+ try:
194
+ reset_at = datetime.fromisoformat(str(window.get("resetsAt") or "").strip().replace("Z", "+00:00"))
195
+ except ValueError:
196
+ return None
197
+ if used is None or not 0 <= used <= 100:
198
+ return None
199
+ reset_at = reset_at if reset_at.tzinfo else reset_at.replace(tzinfo=timezone.utc)
200
+ windows.append({"key": key, "used_percent": used, "reset_at": _iso(reset_at.timestamp()), "rate_limited": window["status"] == "rate-limited"})
201
+ return {"windows": windows}
202
+
203
+
204
+ _BALANCE_PARSERS = {"openrouter": _openrouter, "deepseek": _deepseek, "opencode-go": _opencode_go}
205
+
206
+
207
+ def balance(provider: str, *, credential_id: str | None, api_key: str | None) -> dict:
208
+ """One key-based balance read. A pool credential's key resolves here; otherwise the server passes the configured key."""
209
+ result = {"status": "unavailable", "http_status": None, "quota": None, "label": None, "is_available": None, "balances": [], "windows": []}
210
+ key = str(api_key or "").strip()
211
+ if credential_id:
212
+ entry = _entry(provider, credential_id)
213
+ key = _entry_key(entry) if entry is not None else ""
214
+ if not key:
215
+ return {**result, "status": "no_key"}
216
+ request = urllib.request.Request(_BALANCE_URLS[provider], headers={"Authorization": f"Bearer {key}", "Accept": "application/json", "User-Agent": "Talaria-Web/1.0"})
217
+ try:
218
+ with urllib.request.urlopen(request, timeout=_BALANCE_TIMEOUT_SECONDS) as response:
219
+ raw = response.read(_BALANCE_MAX_BYTES + 1)
220
+ if len(raw) > _BALANCE_MAX_BYTES:
221
+ raise ValueError("oversized balance response")
222
+ parsed = _BALANCE_PARSERS[provider](json.loads(raw.decode("utf-8")))
223
+ except urllib.error.HTTPError as exc:
224
+ return {**result, "status": "http_error", "http_status": exc.code}
225
+ except (OSError, ValueError): # URLError and timeouts are OSErrors; malformed JSON or UTF-8 are ValueErrors
226
+ log.debug("%s balance read failed", provider, exc_info=True)
227
+ return result
228
+ return {**result, **parsed, "status": "ok"} if parsed is not None else result
229
+
230
+
231
+ _ANTHROPIC_WINDOWS = (("five_hour", "Current session"), ("seven_day", "Current week"), ("seven_day_opus", "Opus week"), ("seven_day_sonnet", "Sonnet week"))
232
+
233
+
234
+ def _anthropic_usage(token: str):
235
+ """The Agent's Anthropic account-usage read for one given token. The Agent's fetcher reads only the ambient token, so a
236
+ pool account would otherwise report the active account's limits."""
237
+ # ponytail: mirrors agent.account_usage._fetch_anthropic_account_usage on the Agent's own helpers; drop it once that
238
+ # fetcher honours its api_key argument.
239
+ from agent import account_usage as agent_usage
240
+
241
+ if not agent_usage._is_oauth_token(token):
242
+ return agent_usage._snapshot("anthropic", "oauth_usage_api", [], [], unavailable_reason="Anthropic account limits are only available for OAuth-backed Claude accounts.")
243
+ headers = {"Authorization": f"Bearer {token}", "Accept": "application/json", "Content-Type": "application/json",
244
+ "anthropic-beta": "oauth-2025-04-20", "User-Agent": "claude-code/2.1.0"}
245
+ payload = agent_usage._get_json("https://api.anthropic.com/api/oauth/usage", headers, timeout=15.0)
246
+ windows = agent_usage._usage_windows(payload, _ANTHROPIC_WINDOWS, "utilization", "resets_at", fraction=True)
247
+ extra = payload.get("extra_usage") or {}
248
+ used, limit = extra.get("used_credits"), extra.get("monthly_limit")
249
+ details = [f"Extra usage: {used:.2f} / {limit:.2f} {extra.get('currency') or 'USD'}"] if extra.get("is_enabled") and agent_usage._is_num(used) and agent_usage._is_num(limit) else []
250
+ return agent_usage._snapshot("anthropic", "oauth_usage_api", windows, details)
251
+
252
+
253
+ def account(provider: str, *, base_url: str | None, api_key: str | None, credential_id: str | None = None) -> dict | None:
254
+ if credential_id:
255
+ # One pool account's usage, read with that account's own key.
256
+ entry = _entry(provider, credential_id)
257
+ if entry is None:
258
+ return None
259
+ api_key = _entry_key(entry)
260
+ if not api_key:
261
+ return {"provider": provider, "available": False, "unavailable_reason": "No runtime token available.", "windows": [], "details": []}
262
+ base_url = str(getattr(entry, "runtime_base_url", None) or getattr(entry, "base_url", None) or "").strip() or base_url
31
263
  try:
32
264
  from agent.account_usage import fetch_account_usage
33
265
  except Exception as exc: # noqa: BLE001
34
266
  raise RpcError(f"account usage unavailable: {exc}", condition="usage_unavailable") from exc
35
267
  try:
36
- snapshot = fetch_account_usage(provider, base_url=base_url, api_key=api_key)
268
+ if credential_id and provider == "anthropic":
269
+ snapshot = _anthropic_usage(api_key)
270
+ else:
271
+ snapshot = fetch_account_usage(provider, base_url=base_url, api_key=api_key)
37
272
  except Exception as exc: # noqa: BLE001
38
273
  log.debug("fetch_account_usage(%r) failed", provider, exc_info=True)
39
274
  return {"provider": provider, "available": False, "unavailable_reason": f"{type(exc).__name__}: {exc}", "windows": [], "details": []}
@@ -45,7 +280,8 @@ def account(provider: str, *, base_url: str | None, api_key: str | None) -> dict
45
280
  row.setdefault("provider", provider)
46
281
  row["windows"] = [w for w in (row.get("windows") or []) if isinstance(w, dict)]
47
282
  row["details"] = list(row.get("details") or [])
48
- row["available"] = bool(row.get("available"))
283
+ # The Agent's `available` is a property, which `_plain` (asdict/vars) never sees; read it off the snapshot.
284
+ row["available"] = bool(getattr(snapshot, "available", row.get("available")))
49
285
  return row
50
286
 
51
287
 
@@ -55,5 +291,28 @@ def register(registry) -> None:
55
291
  provider = str(params.get("provider") or "").strip()
56
292
  if not provider:
57
293
  raise InvalidParams("provider is required")
294
+ credential_id = str(params.get("credential_id") or "").strip() or None
295
+ with scoped_home(profile_home_param(params)):
296
+ return {"snapshot": account(provider, base_url=params.get("base_url"), api_key=params.get("api_key"), credential_id=credential_id)}
297
+
298
+ @registry.method("usage.pool")
299
+ def pool_(ctx: CallContext, params: dict) -> dict:
300
+ provider = str(params.get("provider") or "").strip()
301
+ if not provider:
302
+ raise InvalidParams("provider is required")
303
+ with scoped_home(profile_home_param(params)):
304
+ return {"entries": pool(provider, api_key=params.get("api_key"))}
305
+
306
+ @registry.method("usage.pool_providers")
307
+ def pool_providers_(ctx: CallContext, params: dict) -> dict:
308
+ with scoped_home(profile_home_param(params)):
309
+ return {"providers": pool_providers()}
310
+
311
+ @registry.method("usage.balance")
312
+ def balance_(ctx: CallContext, params: dict) -> dict:
313
+ provider = str(params.get("provider") or "").strip()
314
+ if provider not in _BALANCE_URLS:
315
+ raise InvalidParams(f"provider must be one of {sorted(_BALANCE_URLS)}")
316
+ credential_id = str(params.get("credential_id") or "").strip() or None
58
317
  with scoped_home(profile_home_param(params)):
59
- return {"snapshot": account(provider, base_url=params.get("base_url"), api_key=params.get("api_key"))}
318
+ return balance(provider, credential_id=credential_id, api_key=params.get("api_key"))