@hienlh/ppm 0.19.2 → 0.19.4

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 (622) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/CLAUDE.md +1 -0
  3. package/assets/skills/ppm/SKILL.md +1 -1
  4. package/assets/skills/ppm/references/cli-reference.md +4 -4
  5. package/assets/skills/ppm/references/http-api.md +3 -2
  6. package/dist/web/assets/accounts-settings-section-CCFQV-RB.js +1 -0
  7. package/dist/web/assets/ai-resource-editor-BTWds-aC.js +1 -0
  8. package/dist/web/assets/ai-settings-section-CmoBsSpY.js +1 -0
  9. package/dist/web/assets/ai-settings-section-DjbYj4wK.js +1 -0
  10. package/dist/web/assets/{angular-html-BlcrLjUx.js → angular-html-BuHNQIbQ.js} +1 -1
  11. package/dist/web/assets/{angular-ts-BUjtceAJ.js → angular-ts-DHRWoLc-.js} +1 -1
  12. package/dist/web/assets/api-client-BogqsFna.js +1 -0
  13. package/dist/web/assets/api-settings-CTMdz_VF.js +1 -0
  14. package/dist/web/assets/{api-themes-YsZgf6Xj.js → api-themes-Cknp3Q-P.js} +1 -1
  15. package/dist/web/assets/{apl-B3g0v1pk.js → apl-C1VSWVei.js} +1 -1
  16. package/dist/web/assets/appearance-settings-section-vLmakLtA.js +1 -0
  17. package/dist/web/assets/architecture-PBZL5I3N-Ca5IpdzP.js +1 -0
  18. package/dist/web/assets/arrow-left-CyFcsyzD.js +1 -0
  19. package/dist/web/assets/{astro-FRnyn7cH.js → astro-RX-rChsZ.js} +1 -1
  20. package/dist/web/assets/{audio-preview-DWTRm4HC.js → audio-preview-CCd1jdje.js} +1 -1
  21. package/dist/web/assets/{blade-NLdOqUKw.js → blade-CZ6bxMbK.js} +1 -1
  22. package/dist/web/assets/{pencil-BlIIg6RH.js → brain-KDa9jFRq.js} +1 -1
  23. package/dist/web/assets/{bsl-CenHZfaU.js → bsl-BhgCXLWk.js} +1 -1
  24. package/dist/web/assets/{button-Cq8yixjf.js → button-BZtEJcUv.js} +1 -1
  25. package/dist/web/assets/{cairo-B_WR0aiQ.js → cairo-BrwygPkn.js} +1 -1
  26. package/dist/web/assets/calendar-clock-C1asT-gk.js +1 -0
  27. package/dist/web/assets/chat-tab-CLRlS8Jc.js +15 -0
  28. package/dist/web/assets/check-DN75Ucvz.js +1 -0
  29. package/dist/web/assets/chevron-up-8PbsNiJp.js +1 -0
  30. package/dist/web/assets/{cobol-DOynhWYj.js → cobol-C5TGW1Ir.js} +1 -1
  31. package/dist/web/assets/code-editor-CCwVu5F5.js +7 -0
  32. package/dist/web/assets/{coffee-Bn6dbw4P.js → coffee-CFgKlmgI.js} +1 -1
  33. package/dist/web/assets/{conflict-editor-Cgb-z12D.js → conflict-editor-CvWYcbwD.js} +1 -1
  34. package/dist/web/assets/context-menu-C13b1uT3.js +1 -0
  35. package/dist/web/assets/{cpp-CoZR9t8r.js → cpp-BhG5q3-d.js} +1 -1
  36. package/dist/web/assets/{crystal-BSheONSg.js → crystal-DvuI7_tY.js} +1 -1
  37. package/dist/web/assets/{csv-preview-BZMHfFH3.js → csv-preview-DzuDuyE_.js} +1 -1
  38. package/dist/web/assets/{data-grid-overlay-editor-gwPoVSCU.js → data-grid-overlay-editor-DP1xkog4.js} +1 -1
  39. package/dist/web/assets/database-viewer-D4XGEDHm.js +1 -0
  40. package/dist/web/assets/diff-viewer-B5N1X61R.js +4 -0
  41. package/dist/web/assets/{docx-preview-DqVDOMmv.js → docx-preview-CWIDVNZn.js} +1 -1
  42. package/dist/web/assets/{edge-DTtoslQu.js → edge-BSIEsmlI.js} +1 -1
  43. package/dist/web/assets/{edit-diff-preview-CXSbSUBX.js → edit-diff-preview-DeHb85DN.js} +1 -1
  44. package/dist/web/assets/{elixir-CIBCZ3Zi.js → elixir-BF-lrGXM.js} +1 -1
  45. package/dist/web/assets/{elm-BLmb7K1H.js → elm-8mCsJOeN.js} +1 -1
  46. package/dist/web/assets/{erb-BBY8ApRd.js → erb-Dw5PUVKa.js} +1 -1
  47. package/dist/web/assets/{erlang-D5Zb5_P0.js → erlang-BvjBIA6O.js} +1 -1
  48. package/dist/web/assets/{esm-6F8v1jLL.js → esm-Bt9OwSnU.js} +1 -1
  49. package/dist/web/assets/explorer-body-BxLLjthF.js +2 -0
  50. package/dist/web/assets/explorer-window-content-CL1Cipj3.js +1 -0
  51. package/dist/web/assets/extension-manager-section-BpyGq15A.js +1 -0
  52. package/dist/web/assets/{extension-webview-BSh6KGd7.js → extension-webview-BM8FXhuN.js} +1 -1
  53. package/dist/web/assets/{plus-BR0qewJI.js → eye-gWwaF4Lg.js} +1 -1
  54. package/dist/web/assets/eye-off-GjXVuAOn.js +1 -0
  55. package/dist/web/assets/files-settings-section-B0_aR1nL.js +1 -0
  56. package/dist/web/assets/flask-conical-tp8gyZGh.js +1 -0
  57. package/dist/web/assets/{fortran-fixed-form-CBdwkEUJ.js → fortran-fixed-form-CRjUJe9K.js} +1 -1
  58. package/dist/web/assets/{fsharp-AT4XdVYH.js → fsharp-CjUIHIHp.js} +1 -1
  59. package/dist/web/assets/{gdresource-D852fWBR.js → gdresource-x_NUQFrZ.js} +1 -1
  60. package/dist/web/assets/general-settings-section-C1EJzjhU.js +1 -0
  61. package/dist/web/assets/{git-commit-DWfWAyxy.js → git-commit-BOpxSbns.js} +1 -1
  62. package/dist/web/assets/git-log-panel-D1yGTUE_.js +1 -0
  63. package/dist/web/assets/{git-rebase-C6opk8gr.js → git-rebase-BW4xlK7k.js} +1 -1
  64. package/dist/web/assets/gitGraph-HDMCJU4V-BloZfB0Q.js +1 -0
  65. package/dist/web/assets/{glide-data-grid-CICFjJon.js → glide-data-grid-Cu0STKML.js} +12 -12
  66. package/dist/web/assets/{glimmer-js-DLi5mWYO.js → glimmer-js-CT9jrmFj.js} +1 -1
  67. package/dist/web/assets/{glimmer-ts-D-scFX30.js → glimmer-ts-CjQT1lxK.js} +1 -1
  68. package/dist/web/assets/{glsl-dbQzF4iA.js → glsl-DsFJvtg5.js} +1 -1
  69. package/dist/web/assets/{graphql-D1m5Wen2.js → graphql-Dhlnmkn_.js} +1 -1
  70. package/dist/web/assets/group-chat-tab-BfcvkRX5.js +1 -0
  71. package/dist/web/assets/{hack-CqYmpO7u.js → hack-BKHbsJ7u.js} +1 -1
  72. package/dist/web/assets/{haml-BXzKWEag.js → haml-C1Ih25Y2.js} +1 -1
  73. package/dist/web/assets/{handlebars-DXnrtHtq.js → handlebars-CaFitvmC.js} +1 -1
  74. package/dist/web/assets/{html-DhgQJ-xL.js → html-BwpKIE6V.js} +1 -1
  75. package/dist/web/assets/{html-derivative-xZRft-YE.js → html-derivative-uuTEquZU.js} +1 -1
  76. package/dist/web/assets/{http-B53mSj14.js → http-Cv4Pg5cR.js} +1 -1
  77. package/dist/web/assets/{hurl-CsomVy04.js → hurl-DubkI6TG.js} +1 -1
  78. package/dist/web/assets/{hxml-LffjWwKA.js → hxml-B9OVZFAB.js} +1 -1
  79. package/dist/web/assets/{image-preview-CNA6XMNS.js → image-preview-DRbZ0FA2.js} +1 -1
  80. package/dist/web/assets/index-B1bzKRjJ.js +52 -0
  81. package/dist/web/assets/index-nP3uxXvR.css +2 -0
  82. package/dist/web/assets/info-3K5VOQVL-c0W8wp5o.js +1 -0
  83. package/dist/web/assets/{jinja-CCHURl7V.js → jinja-BHb1jyl6.js} +1 -1
  84. package/dist/web/assets/jira-watcher-section-DZh-Z2MK.js +1 -0
  85. package/dist/web/assets/{jison-7qBd8dnR.js → jison-CtpfXDzp.js} +1 -1
  86. package/dist/web/assets/{julia-BY9pnTcy.js → julia-TYjgpazU.js} +1 -1
  87. package/dist/web/assets/{just-D7wRSNRr.js → just-D27bzZuW.js} +1 -1
  88. package/dist/web/assets/key-round-CQpDrDxg.js +1 -0
  89. package/dist/web/assets/keybindings-store-CFwgcnpZ.js +1 -0
  90. package/dist/web/assets/keyboard-shortcuts-section-JBtcJGM3.js +1 -0
  91. package/dist/web/assets/{latex-RgsCae55.js → latex-D_MzopTn.js} +1 -1
  92. package/dist/web/assets/{liquid-CnDFzvOZ.js → liquid-BG_FG0Pe.js} +1 -1
  93. package/dist/web/assets/{lua-0lj5W8sm.js → lua-Bl29oC2L.js} +1 -1
  94. package/dist/web/assets/{markdown-renderer-dbLIvU2M.js → markdown-renderer-Bo8GgGXl.js} +3 -3
  95. package/dist/web/assets/markdown-renderer-DY60UZNW.js +1 -0
  96. package/dist/web/assets/{marko-CQgqAhbV.js → marko-D-HI7dbm.js} +1 -1
  97. package/dist/web/assets/{mdc-BkIcTiIy.js → mdc-y444JQTQ.js} +1 -1
  98. package/dist/web/assets/{media-url-DDciwvLU.js → media-url-VzecHk9g.js} +1 -1
  99. package/dist/web/assets/{mobile-bottom-sheet-pjcHKorA.js → mobile-bottom-sheet-BD5b7KJF.js} +1 -1
  100. package/dist/web/assets/mobile-explorer-sheet-C59rPw7Z.js +1 -0
  101. package/dist/web/assets/{nextflow-DvIW68DS.js → nextflow-C5sKcldC.js} +1 -1
  102. package/dist/web/assets/{nginx-O-hft0gG.js → nginx-bXnEC3wm.js} +1 -1
  103. package/dist/web/assets/{nim-BxrR4HwQ.js → nim-pevhf4WD.js} +1 -1
  104. package/dist/web/assets/notification-store-Bd6L0K_Q.js +1 -0
  105. package/dist/web/assets/notifications-settings-section-ARQlF-6E.js +1 -0
  106. package/dist/web/assets/{number-overlay-editor-BoLRYuZT.js → number-overlay-editor-B7WINIW4.js} +1 -1
  107. package/dist/web/assets/{overview-panel-l3g1gT5e.js → overview-panel-mhN0qw6Q.js} +1 -1
  108. package/dist/web/assets/packet-RMMSAZCW-D_BoHr_M.js +1 -0
  109. package/dist/web/assets/panel-store-BRrnDO_i.js +1 -0
  110. package/dist/web/assets/pdf-preview-CRShdbBS.js +1 -0
  111. package/dist/web/assets/{perl-Da-uNqmL.js → perl-B1KUtMnW.js} +1 -1
  112. package/dist/web/assets/{php-JMu45mEx.js → php-BGmVZBAJ.js} +1 -1
  113. package/dist/web/assets/pie-UPGHQEXC-BaLfuEf8.js +1 -0
  114. package/dist/web/assets/{postgres-viewer-DstZwUai.js → postgres-viewer-BNT-4yYl.js} +3 -3
  115. package/dist/web/assets/power-CriQkeK5.js +1 -0
  116. package/dist/web/assets/ppmbot-settings-section-Bq4YAcFW.js +1 -0
  117. package/dist/web/assets/{process-table-kmQ2-8qJ.js → process-table-CjiXW0rg.js} +1 -1
  118. package/dist/web/assets/{project-store-DTUCS65m.js → project-store-BuY7rxn_.js} +1 -1
  119. package/dist/web/assets/proxy-settings-section-wRbiyQiA.js +3 -0
  120. package/dist/web/assets/{pug-CLzyQhia.js → pug-BfN8CpEH.js} +1 -1
  121. package/dist/web/assets/{qml-BHXfGB1l.js → qml-D6fRdLZt.js} +1 -1
  122. package/dist/web/assets/query-audit-section-C5RmhRSu.js +1 -0
  123. package/dist/web/assets/radar-KQ55EAFF-DBIZEaIU.js +1 -0
  124. package/dist/web/assets/{razor-BqetJLN3.js → razor-fg-9AScR.js} +1 -1
  125. package/dist/web/assets/remote-desktop-mobile-sheet-BF-75_03.js +2 -0
  126. package/dist/web/assets/remote-desktop-mobile-view-C9KR9I9G.js +1 -0
  127. package/dist/web/assets/remote-desktop-readiness-gate-BnKJicB2.js +1 -0
  128. package/dist/web/assets/remote-desktop-window-content-awXsNw_R.js +1 -0
  129. package/dist/web/assets/{rst-iQdJZ0BP.js → rst-BZgx076b.js} +1 -1
  130. package/dist/web/assets/{ruby-CNwzZGdH.js → ruby-iUkWDcLo.js} +1 -1
  131. package/dist/web/assets/{run-in-terminal-CBXmVGWa.js → run-in-terminal-bKRwuO2z.js} +1 -1
  132. package/dist/web/assets/{sas-BpBIT2eR.js → sas-CLXO0lZ6.js} +1 -1
  133. package/dist/web/assets/schedules-settings-section-BOecKJzl.js +1 -0
  134. package/dist/web/assets/{scss-D1MotuEp.js → scss-DQqteGaG.js} +1 -1
  135. package/dist/web/assets/send-Bb0jtcR6.js +1 -0
  136. package/dist/web/assets/{send-to-chat-DJAdlvJ8.js → send-to-chat-S8LCNX7V.js} +1 -1
  137. package/dist/web/assets/separator-DqGg3f9c.js +1 -0
  138. package/dist/web/assets/settings-body-DGi5DXY6.js +2 -0
  139. package/dist/web/assets/settings-store-Brjd0QnO.js +2 -0
  140. package/dist/web/assets/settings-tab-GM9zTRul.js +1 -0
  141. package/dist/web/assets/settings-window-content-Dvhxo4_H.js +1 -0
  142. package/dist/web/assets/{shaderlab-BNqmcPQi.js → shaderlab-CeMTqagR.js} +1 -1
  143. package/dist/web/assets/{shellsession-BBLOVOO-.js → shellsession-BAkquZAV.js} +1 -1
  144. package/dist/web/assets/{soy-BpTGo9uy.js → soy-Bxm9Ez55.js} +1 -1
  145. package/dist/web/assets/{sparql-D7A3yKob.js → sparql-C1phwdLc.js} +1 -1
  146. package/dist/web/assets/sql-query-editor-BmkGFzcL.js +1 -0
  147. package/dist/web/assets/sqlite-viewer-DbziVQXQ.js +1 -0
  148. package/dist/web/assets/{stata-B7N56ptS.js → stata-DGNMQdEb.js} +1 -1
  149. package/dist/web/assets/{surrealql-CTmnD-lv.js → surrealql-ByVrV320.js} +1 -1
  150. package/dist/web/assets/{svelte-BppwY9TR.js → svelte-CNWjIMhl.js} +1 -1
  151. package/dist/web/assets/system-monitor-body-CbHW5aYm.js +2 -0
  152. package/dist/web/assets/system-monitor-tab-C7EZLMIo.js +1 -0
  153. package/dist/web/assets/{system-monitor-window-content-B9Y98UW6.js → system-monitor-window-content-C_w4w81e.js} +1 -1
  154. package/dist/web/assets/{tab-host-window-content-C1j8zr68.js → tab-host-window-content-BSP1SEXS.js} +1 -1
  155. package/dist/web/assets/{tab-store-DzYSsw5a.js → tab-store-PZsP5ZwG.js} +1 -1
  156. package/dist/web/assets/team-member-sheet-C9N0iA1-.js +2 -0
  157. package/dist/web/assets/team-member-window-content-CM2QFv-s.js +1 -0
  158. package/dist/web/assets/{templ-CvTMKKra.js → templ-Cf0gQL4I.js} +1 -1
  159. package/dist/web/assets/{terminal-tab-B9I_HlES.js → terminal-tab-jWYELm8y.js} +3 -3
  160. package/dist/web/assets/{tex--c4POEUE.js → tex-BTIqPw_R.js} +1 -1
  161. package/dist/web/assets/tool-cards-jefdOWVJ.js +9 -0
  162. package/dist/web/assets/treemap-KZPCXAKY-DFiG9UYW.js +1 -0
  163. package/dist/web/assets/{ts-tags--kx7D-Hw.js → ts-tags-B8dN_Yf6.js} +1 -1
  164. package/dist/web/assets/{twig-UG9uMHGd.js → twig-BgemonbJ.js} +1 -1
  165. package/dist/web/assets/use-accounts-data-MhMl0Y4Z.js +1 -0
  166. package/dist/web/assets/{use-blob-url-Bdxopwwc.js → use-blob-url-Gfi69PtP.js} +1 -1
  167. package/dist/web/assets/use-monaco-theme-BbAd7DXr.js +1 -0
  168. package/dist/web/assets/{use-open-team-member-D9EOB8-i.js → use-open-team-member-CruUTqzD.js} +1 -1
  169. package/dist/web/assets/use-remote-desktop-display-choice-DlfMIWq3.js +1 -0
  170. package/dist/web/assets/{use-websocket-xOOwBdwP.js → use-websocket-Bq8X_SUI.js} +1 -1
  171. package/dist/web/assets/{vendor-mermaid-0Nt-ew76.js → vendor-mermaid-BKWscKt0.js} +1 -1
  172. package/dist/web/assets/video-preview-BGh3B0Lu.js +1 -0
  173. package/dist/web/assets/{vue-jjTomlbt.js → vue-D3D4lbXz.js} +1 -1
  174. package/dist/web/assets/{vue-html-8KEW6-I9.js → vue-html-DYEQFAiK.js} +1 -1
  175. package/dist/web/assets/{vue-vine-C5tohCkU.js → vue-vine-D0OPNscN.js} +1 -1
  176. package/dist/web/assets/{window-pip-registry-FdZdGF7h.js → window-pip-registry-BEN0QNSW.js} +1 -1
  177. package/dist/web/assets/window-store-DGKf9rPG.js +1 -0
  178. package/dist/web/assets/ws-auth-Dawx17PM.js +1 -0
  179. package/dist/web/assets/{xml-ByFFosqL.js → xml-DHaWgpya.js} +1 -1
  180. package/dist/web/assets/{xsl-CZUwiLkj.js → xsl-DVRN8JsA.js} +1 -1
  181. package/dist/web/index.html +32 -55
  182. package/dist/web/sw.js +1 -1
  183. package/docs/codebase-summary.md +9 -5
  184. package/docs/lessons-learned.md +11 -0
  185. package/package.json +2 -2
  186. package/packages/vscode-compat/node_modules/.bin/tsc.bunx +0 -0
  187. package/packages/vscode-compat/node_modules/.bin/tsc.exe +0 -0
  188. package/packages/vscode-compat/node_modules/.bin/tsserver.bunx +0 -0
  189. package/packages/vscode-compat/node_modules/.bin/tsserver.exe +0 -0
  190. package/src/providers/codex-app-server/codex-event-mapper.ts +70 -5
  191. package/src/providers/codex-app-server/codex-history.ts +24 -0
  192. package/src/providers/codex-app-server/codex-jsonrpc-client.ts +39 -2
  193. package/src/providers/codex-app-server/codex-protocol.ts +24 -0
  194. package/src/providers/codex-app-server/codex-provider.ts +159 -17
  195. package/src/providers/codex-app-server/codex-rollout-items.ts +149 -0
  196. package/src/providers/codex-app-server/codex-skill-parser.ts +63 -0
  197. package/src/providers/codex-app-server/codex-usage-fetch.ts +22 -20
  198. package/src/providers/codex-app-server/codex-usage-source.ts +37 -0
  199. package/src/server/routes/chat.ts +67 -9
  200. package/src/server/routes/codex-accounts.ts +9 -5
  201. package/src/server/ws/chat.ts +46 -1
  202. package/src/services/claude-usage-source.ts +40 -0
  203. package/src/services/claude-usage.service.ts +77 -146
  204. package/src/services/codex-account-login.ts +123 -55
  205. package/src/services/codex-account.service.ts +27 -4
  206. package/src/services/db.service.ts +161 -20
  207. package/src/services/edge-forwarder.ts +191 -191
  208. package/src/services/fs-credential-path-guard.ts +35 -8
  209. package/src/services/fs-path-guard.service.ts +1 -0
  210. package/src/services/postgres.service.ts +144 -72
  211. package/src/services/provider-usage/index.ts +63 -0
  212. package/src/services/provider-usage/register-usage-sources.ts +18 -0
  213. package/src/services/provider-usage/usage-memory-cache.ts +71 -0
  214. package/src/services/provider-usage/usage-registry.ts +157 -0
  215. package/src/services/provider-usage/usage-scheduler.ts +117 -0
  216. package/src/services/provider-usage/usage-snapshot-store.ts +126 -0
  217. package/src/services/provider-usage/usage-source.ts +82 -0
  218. package/src/services/proxy-agent-anthropic-bridge.ts +93 -0
  219. package/src/services/proxy-agent-bridge.ts +100 -0
  220. package/src/services/proxy-agent-turn.ts +161 -0
  221. package/src/services/proxy-anthropic-format.ts +157 -0
  222. package/src/services/proxy-openai-format.ts +148 -0
  223. package/src/services/slash-discovery/builtin-commands.ts +13 -0
  224. package/src/services/slash-discovery/codex-skill-items.ts +57 -0
  225. package/src/services/slash-discovery/provider-skill-sigil.ts +19 -0
  226. package/src/services/slash-discovery/types.ts +19 -0
  227. package/src/services/windows-process-tree.ts +351 -351
  228. package/src/types/chat.ts +7 -1
  229. package/src/web/app.tsx +1 -7
  230. package/src/web/components/chat/chat-history-bar.tsx +10 -1
  231. package/src/web/components/chat/chat-tab.tsx +17 -1
  232. package/src/web/components/chat/message-input.tsx +11 -4
  233. package/src/web/components/chat/message-list.tsx +14 -0
  234. package/src/web/components/chat/slash-command-picker.tsx +22 -3
  235. package/src/web/components/chat/tool-cards.tsx +47 -4
  236. package/src/web/components/chat/usage-badge.tsx +59 -426
  237. package/src/web/components/floating-window/window-content-registry.ts +2 -0
  238. package/src/web/components/floating-window/window-store-types.ts +1 -1
  239. package/src/web/components/layout/command-palette.tsx +2 -2
  240. package/src/web/components/layout/mobile-drawer.tsx +5 -4
  241. package/src/web/components/layout/mobile-nav.tsx +5 -1
  242. package/src/web/components/layout/nav-section-rail.tsx +2 -1
  243. package/src/web/components/layout/project-bottom-sheet.tsx +3 -9
  244. package/src/web/components/layout/sidebar.tsx +0 -2
  245. package/src/web/components/layout/tab-pop-out-menu-item.tsx +3 -2
  246. package/src/web/components/settings/accounts/account-add-dialog.tsx +155 -0
  247. package/src/web/components/settings/accounts/account-backup-format.ts +19 -0
  248. package/src/web/components/settings/accounts/account-bucket-row.tsx +55 -0
  249. package/src/web/components/settings/accounts/account-card.tsx +151 -0
  250. package/src/web/components/settings/accounts/account-delete-confirm.tsx +62 -0
  251. package/src/web/components/settings/accounts/account-export-dialog.tsx +154 -0
  252. package/src/web/components/settings/accounts/account-export-simulation.tsx +196 -0
  253. package/src/web/components/settings/accounts/account-import-dialog.tsx +72 -0
  254. package/src/web/components/settings/accounts/account-profile-panel.tsx +52 -0
  255. package/src/web/components/{chat → settings/accounts}/account-rotation-settings.tsx +1 -1
  256. package/src/web/components/settings/accounts/account-token-test-dialog.tsx +147 -0
  257. package/src/web/components/settings/accounts/account-usage-format.ts +92 -0
  258. package/src/web/components/{chat/usage-pattern-chart.tsx → settings/accounts/account-usage-pattern-chart.tsx} +1 -1
  259. package/src/web/components/settings/accounts/accounts-pane-header.tsx +113 -0
  260. package/src/web/components/settings/accounts/accounts-settings-section.tsx +80 -0
  261. package/src/web/components/settings/accounts/claude-accounts-section.tsx +169 -0
  262. package/src/web/components/settings/accounts/codex-accounts-section.tsx +144 -0
  263. package/src/web/components/settings/accounts/codex-add-account-dialog.tsx +130 -0
  264. package/src/web/components/settings/accounts/codex-backup-dialog.tsx +101 -0
  265. package/src/web/components/settings/accounts/codex-rotation-dialog.tsx +65 -0
  266. package/src/web/components/settings/accounts/use-accounts-data.ts +81 -0
  267. package/src/web/components/settings/accounts/use-codex-accounts.ts +151 -0
  268. package/src/web/components/settings/ai-settings-section.tsx +14 -4
  269. package/src/web/components/settings/appearance-settings-section.tsx +125 -0
  270. package/src/web/components/settings/general-settings-section.tsx +87 -0
  271. package/src/web/components/settings/jira-watcher-section.tsx +34 -0
  272. package/src/web/components/settings/notifications-settings-section.tsx +37 -0
  273. package/src/web/components/settings/open-settings.ts +42 -0
  274. package/src/web/components/settings/resolve-open-settings-action.ts +45 -0
  275. package/src/web/components/settings/settings-body.tsx +110 -0
  276. package/src/web/components/settings/settings-categories.ts +102 -0
  277. package/src/web/components/settings/settings-category-list.tsx +56 -0
  278. package/src/web/components/settings/settings-category-rail.tsx +62 -0
  279. package/src/web/components/settings/settings-section-content.tsx +43 -0
  280. package/src/web/components/settings/settings-tab.tsx +15 -329
  281. package/src/web/components/settings/settings-window-content.tsx +24 -0
  282. package/src/web/hooks/use-chat.ts +38 -1
  283. package/src/web/hooks/use-global-keybindings.ts +3 -4
  284. package/src/web/hooks/use-url-sync.ts +30 -0
  285. package/src/web/hooks/use-usage.ts +23 -11
  286. package/src/web/lib/sidebar-tabs/tab-registry.ts +9 -9
  287. package/src/web/lib/slash-items-cache.ts +27 -7
  288. package/src/web/stores/panel-utils.ts +11 -0
  289. package/src/web/stores/settings-store.ts +4 -2
  290. package/src/web/stores/window-panel-actions.ts +1 -3
  291. package/src/web/stores/window-panel-persistence.ts +9 -4
  292. package/dist/web/assets/adaptive-context-menu-BbuRVTLn.js +0 -1
  293. package/dist/web/assets/ai-resource-editor-BCCOUzox.js +0 -1
  294. package/dist/web/assets/ai-settings-section-BEM1z7kp.js +0 -1
  295. package/dist/web/assets/api-client-D1CePgQ4.js +0 -1
  296. package/dist/web/assets/api-settings-CNtKkHqW.js +0 -1
  297. package/dist/web/assets/api-settings-Cubuqjsu.js +0 -1
  298. package/dist/web/assets/architecture-PBZL5I3N-6konx-J1.js +0 -1
  299. package/dist/web/assets/chat-tab-CmXGsJGc.js +0 -15
  300. package/dist/web/assets/clipboard-B-gwJ8DF.js +0 -1
  301. package/dist/web/assets/code-editor-DtHXzNZ1.js +0 -7
  302. package/dist/web/assets/context-menu-DfWZAxQy.js +0 -1
  303. package/dist/web/assets/database-viewer-GVaQVMdb.js +0 -1
  304. package/dist/web/assets/dialog-BSfuiTkJ.js +0 -1
  305. package/dist/web/assets/diff-viewer-BG5_fH1E.js +0 -4
  306. package/dist/web/assets/explorer-body-BPFnjxYj.js +0 -2
  307. package/dist/web/assets/explorer-window-content-Da1T9uM4.js +0 -1
  308. package/dist/web/assets/extension-store-Bhu--fPX.js +0 -1
  309. package/dist/web/assets/external-link-B6Hl0U4X.js +0 -1
  310. package/dist/web/assets/eye-off-CUdzgmX8.js +0 -1
  311. package/dist/web/assets/file-store-6HW2Lfl4.js +0 -1
  312. package/dist/web/assets/git-log-panel-DNC9fV0M.js +0 -1
  313. package/dist/web/assets/gitGraph-HDMCJU4V-ikUwsekd.js +0 -1
  314. package/dist/web/assets/group-chat-tab-DbUkcOaJ.js +0 -1
  315. package/dist/web/assets/index-DTqbMiVc.js +0 -52
  316. package/dist/web/assets/index-SFFv2-VM.css +0 -2
  317. package/dist/web/assets/info-3K5VOQVL-BAgmboBp.js +0 -1
  318. package/dist/web/assets/input-D0ol5jW0.js +0 -1
  319. package/dist/web/assets/keybindings-store-BGkB72gM.js +0 -1
  320. package/dist/web/assets/keybindings-store-Bq6VWl5X.js +0 -1
  321. package/dist/web/assets/lock-CNxUGpTk.js +0 -1
  322. package/dist/web/assets/markdown-renderer-DEAVpo0C.js +0 -1
  323. package/dist/web/assets/mobile-explorer-sheet-CD_kl-jA.js +0 -1
  324. package/dist/web/assets/monitor-smartphone-DLZ6Q05s.js +0 -1
  325. package/dist/web/assets/notification-store-Dgbsw9p9.js +0 -1
  326. package/dist/web/assets/packet-RMMSAZCW-RVlbbDRS.js +0 -1
  327. package/dist/web/assets/panel-store-UOV8wrfR.js +0 -1
  328. package/dist/web/assets/pdf-preview-B2CJ_EKv.js +0 -1
  329. package/dist/web/assets/pie-UPGHQEXC-ZWb4izpk.js +0 -1
  330. package/dist/web/assets/play-DpEEBJTT.js +0 -1
  331. package/dist/web/assets/radar-KQ55EAFF-bsP5x1Bx.js +0 -1
  332. package/dist/web/assets/refresh-cw-BQU5g2CY.js +0 -1
  333. package/dist/web/assets/remote-desktop-mobile-sheet-KKTRNjOL.js +0 -2
  334. package/dist/web/assets/remote-desktop-mobile-view-C1ST4-Fm.js +0 -1
  335. package/dist/web/assets/remote-desktop-readiness-gate-BH3BE5wA.js +0 -1
  336. package/dist/web/assets/remote-desktop-window-content-BaZJQt_N.js +0 -1
  337. package/dist/web/assets/rotate-ccw-CpjRo90J.js +0 -1
  338. package/dist/web/assets/scroll-area-lc0ZHORr.js +0 -1
  339. package/dist/web/assets/send-C_sLFt56.js +0 -1
  340. package/dist/web/assets/settings-store-zbPkG7o-.js +0 -2
  341. package/dist/web/assets/settings-tab-Cn-AqUP_.js +0 -1
  342. package/dist/web/assets/settings-tab-xyRpt2gq.js +0 -3
  343. package/dist/web/assets/shallow-CmPMAPte.js +0 -1
  344. package/dist/web/assets/sql-query-editor-B86NiZHI.js +0 -1
  345. package/dist/web/assets/sqlite-viewer-DB775F6C.js +0 -1
  346. package/dist/web/assets/switch-BmjKoIpR.js +0 -1
  347. package/dist/web/assets/system-monitor-body-BDxHQvy9.js +0 -2
  348. package/dist/web/assets/system-monitor-tab-BoTHLx6B.js +0 -1
  349. package/dist/web/assets/team-member-sheet-Cxz6h5Ne.js +0 -2
  350. package/dist/web/assets/team-member-window-content-CUSuRHCB.js +0 -1
  351. package/dist/web/assets/tool-cards-D3KSzd2K.js +0 -8
  352. package/dist/web/assets/trash-2-By4VMbC4.js +0 -1
  353. package/dist/web/assets/treemap-KZPCXAKY-Dh1oxNdQ.js +0 -1
  354. package/dist/web/assets/triangle-alert-CUTURFbG.js +0 -1
  355. package/dist/web/assets/upload-BNlX8D07.js +0 -1
  356. package/dist/web/assets/use-monaco-theme-CVCye2kq.js +0 -1
  357. package/dist/web/assets/use-remote-desktop-display-choice-neUqyE3p.js +0 -1
  358. package/dist/web/assets/video-preview-DvpgBDhM.js +0 -1
  359. package/dist/web/assets/window-store-D0x_fxr0.js +0 -1
  360. package/src/web/components/chat/account-dialogs.tsx +0 -384
  361. package/src/web/components/settings/accounts-settings-section.tsx +0 -1090
  362. package/src/web/components/settings/codex-accounts-section.tsx +0 -198
  363. package/src/web/components/settings/telegram-settings-section.tsx +0 -113
  364. /package/dist/web/assets/{abap-C5DFF0s3.js → abap-h3QyH8MW.js} +0 -0
  365. /package/dist/web/assets/{actionscript-3-B0qyRNbc.js → actionscript-3-mkxkq-FN.js} +0 -0
  366. /package/dist/web/assets/{ada-bAJzplug.js → ada-DhISK_EH.js} +0 -0
  367. /package/dist/web/assets/{andromeeda-DUCpISy6.js → andromeeda-DnlWWesX.js} +0 -0
  368. /package/dist/web/assets/{apache-BhAr84Z_.js → apache-P5AK3sfA.js} +0 -0
  369. /package/dist/web/assets/{apex-CRv8YnNq.js → apex-CJUPehg2.js} +0 -0
  370. /package/dist/web/assets/{api-client-BzWM7XPR.js → api-client-oY1tC3tM.js} +0 -0
  371. /package/dist/web/assets/{applescript-CD-Cw8bt.js → applescript-Dx6GGbay.js} +0 -0
  372. /package/dist/web/assets/{apply-theme-DJ794zSQ.js → apply-theme-DxTXOAcV.js} +0 -0
  373. /package/dist/web/assets/{ara-BUzeQYoG.js → ara-95Kx3qKb.js} +0 -0
  374. /package/dist/web/assets/{asciidoc-CT2hTg-v.js → asciidoc-Dp0RbIej.js} +0 -0
  375. /package/dist/web/assets/{asm-CAu35JeY.js → asm-C82coUjU.js} +0 -0
  376. /package/dist/web/assets/{aurora-x-BeFJoIA1.js → aurora-x-BFc3zeop.js} +0 -0
  377. /package/dist/web/assets/{awk-Vau-DrGN.js → awk-ChjC0icr.js} +0 -0
  378. /package/dist/web/assets/{ayu-dark-CLe-OTsQ.js → ayu-dark-DYfUf_PB.js} +0 -0
  379. /package/dist/web/assets/{ayu-light-CJbQfu4M.js → ayu-light-Bh6O0plO.js} +0 -0
  380. /package/dist/web/assets/{ayu-mirage-GWaKwdrx.js → ayu-mirage-DlNhHTGq.js} +0 -0
  381. /package/dist/web/assets/{ballerina-DWu0USI5.js → ballerina-Ceoll3Sk.js} +0 -0
  382. /package/dist/web/assets/{bat-_07Huw7E.js → bat-C7QS6Lfq.js} +0 -0
  383. /package/dist/web/assets/{beancount-EcvogrcF.js → beancount-5NMEAZgs.js} +0 -0
  384. /package/dist/web/assets/{berry-BBB87SY7.js → berry-BBL83Vbk.js} +0 -0
  385. /package/dist/web/assets/{bibtex-DbueS87A.js → bibtex-L7rPkPSG.js} +0 -0
  386. /package/dist/web/assets/{bicep-tcmXnUo4.js → bicep-BOwHIINh.js} +0 -0
  387. /package/dist/web/assets/{bird2-B2qwilIR.js → bird2-CWLsMom2.js} +0 -0
  388. /package/dist/web/assets/{c-Z58oivSt.js → c-CD4Ku5Cr.js} +0 -0
  389. /package/dist/web/assets/{c3-U40cGT9p.js → c3-CpMl-w3G.js} +0 -0
  390. /package/dist/web/assets/{cadence-BFcD-LyM.js → cadence-CvgF6ehN.js} +0 -0
  391. /package/dist/web/assets/{can-open-in-ppm-CdCK2c2a.js → can-open-in-ppm-j3jh7fUo.js} +0 -0
  392. /package/dist/web/assets/{catppuccin-frappe-RkC9AX6J.js → catppuccin-frappe-eo_dTm3F.js} +0 -0
  393. /package/dist/web/assets/{catppuccin-latte-Bsi4l-nO.js → catppuccin-latte-DoiMCU_7.js} +0 -0
  394. /package/dist/web/assets/{catppuccin-macchiato-B8JIdTW-.js → catppuccin-macchiato-UQxsK2Vh.js} +0 -0
  395. /package/dist/web/assets/{catppuccin-mocha-oH8wwrDj.js → catppuccin-mocha-J0i-8RNy.js} +0 -0
  396. /package/dist/web/assets/{clarity-C3WNeRCR.js → clarity-DXYM8-SA.js} +0 -0
  397. /package/dist/web/assets/{clojure-CFoAz7u9.js → clojure-DzamsL69.js} +0 -0
  398. /package/dist/web/assets/{cmake-Bcsqp9yR.js → cmake-CR6RP_T_.js} +0 -0
  399. /package/dist/web/assets/{codeowners-DC2Z7heP.js → codeowners-D1s6-IyY.js} +0 -0
  400. /package/dist/web/assets/{codeql-BfG68Al5.js → codeql-DAqNWMkS.js} +0 -0
  401. /package/dist/web/assets/{common-lisp-Z5P-lKQe.js → common-lisp-8e-KtitB.js} +0 -0
  402. /package/dist/web/assets/{coq-BJslqrnv.js → coq-UEq0WUcO.js} +0 -0
  403. /package/dist/web/assets/{csharp-DhhZTOTs.js → csharp-wxJ0TuCm.js} +0 -0
  404. /package/dist/web/assets/{css-DiFIpMfE.js → css-Cerub-yB.js} +0 -0
  405. /package/dist/web/assets/{csv-DViLbHxI.js → csv-Bh9_WD_h.js} +0 -0
  406. /package/dist/web/assets/{csv-parser-B2v8MoUy.js → csv-parser-BPIDt6ni.js} +0 -0
  407. /package/dist/web/assets/{cue-CoG3mg2I.js → cue-rzM1ztqU.js} +0 -0
  408. /package/dist/web/assets/{cypher-jTxC8NAB.js → cypher-DGaTQ8m0.js} +0 -0
  409. /package/dist/web/assets/{d-6hKIUAbU.js → d-DbkZGuhB.js} +0 -0
  410. /package/dist/web/assets/{dark-plus-D_-np2Sr.js → dark-plus-BO2bzZcA.js} +0 -0
  411. /package/dist/web/assets/{dart-CVpxLpVn.js → dart-BDhCoqVg.js} +0 -0
  412. /package/dist/web/assets/{data-grid-types-Dq_H1IPa.js → data-grid-types-CdAg-4W1.js} +0 -0
  413. /package/dist/web/assets/{dax-BPqJ8qIY.js → dax-Bh03stWh.js} +0 -0
  414. /package/dist/web/assets/{desktop-u_y5_Hr5.js → desktop-BOdr_fwq.js} +0 -0
  415. /package/dist/web/assets/{diff-Duj1XMrd.js → diff-Bb_gTsS2.js} +0 -0
  416. /package/dist/web/assets/{dist-CEZ4plgH.js → dist-BUMDu0av.js} +0 -0
  417. /package/dist/web/assets/{dist-aDSxTR_v.js → dist-DonmFWG8.js} +0 -0
  418. /package/dist/web/assets/{docker-8UGSCc7N.js → docker-BDsTF5ce.js} +0 -0
  419. /package/dist/web/assets/{dotenv-DVnj8kyR.js → dotenv-CbCWp9Ti.js} +0 -0
  420. /package/dist/web/assets/{dracula-D0cEmSg7.js → dracula-Cd1fxvoX.js} +0 -0
  421. /package/dist/web/assets/{dracula-soft-DfGS5Yfr.js → dracula-soft-B7eXkfXG.js} +0 -0
  422. /package/dist/web/assets/{dream-maker-qG8kMGTF.js → dream-maker-XlzuS21R.js} +0 -0
  423. /package/dist/web/assets/{emacs-lisp-iVqyMLyT.js → emacs-lisp-CXV1Ki_7.js} +0 -0
  424. /package/dist/web/assets/{everforest-dark-Cruygd5U.js → everforest-dark-DuqR60Dg.js} +0 -0
  425. /package/dist/web/assets/{everforest-light-BiJa8RHL.js → everforest-light-q4BPnenD.js} +0 -0
  426. /package/dist/web/assets/{fennel-H6XSEIoz.js → fennel-B-e0TX2Q.js} +0 -0
  427. /package/dist/web/assets/{fish-ho_7CxX6.js → fish-Bzb6-TtF.js} +0 -0
  428. /package/dist/web/assets/{fluent-1l5ssJTF.js → fluent-B-szH2-f.js} +0 -0
  429. /package/dist/web/assets/{folder-symlink-Dx1hOk8X.js → folder-symlink-fQRoG_rV.js} +0 -0
  430. /package/dist/web/assets/{format-bytes-gQw5wzqa.js → format-bytes-CKXk9k2d.js} +0 -0
  431. /package/dist/web/assets/{fortran-free-form-Bo1x233A.js → fortran-free-form-OS6SGYC0.js} +0 -0
  432. /package/dist/web/assets/{gdscript-DffFMp_k.js → gdscript-FYq4OP2d.js} +0 -0
  433. /package/dist/web/assets/{gdshader-CvnpAiAF.js → gdshader-BpG6P8rw.js} +0 -0
  434. /package/dist/web/assets/{genie-2eOvAEzW.js → genie-BS9RLAna.js} +0 -0
  435. /package/dist/web/assets/{gherkin-DL-5OUlV.js → gherkin-B_9FdtGs.js} +0 -0
  436. /package/dist/web/assets/{github-dark-DrTZprgG.js → github-dark-cfaIlnBV.js} +0 -0
  437. /package/dist/web/assets/{github-dark-default-_mDH8IMP.js → github-dark-default-B2Wv65He.js} +0 -0
  438. /package/dist/web/assets/{github-dark-dimmed-C48hJuLn.js → github-dark-dimmed-CtM9X76s.js} +0 -0
  439. /package/dist/web/assets/{github-dark-high-contrast-BQ-zGGNe.js → github-dark-high-contrast-DLHyk0tG.js} +0 -0
  440. /package/dist/web/assets/{github-light-BYc90Yx8.js → github-light-DWEI10iG.js} +0 -0
  441. /package/dist/web/assets/{github-light-default-Daecj4J5.js → github-light-default-DcJkK1YN.js} +0 -0
  442. /package/dist/web/assets/{github-light-high-contrast-DoafCbsU.js → github-light-high-contrast-Bcx9iKDt.js} +0 -0
  443. /package/dist/web/assets/{gleam-hUWWjmnp.js → gleam-DVpiM6b2.js} +0 -0
  444. /package/dist/web/assets/{gn-7PJ3R70o.js → gn-B80DNc0i.js} +0 -0
  445. /package/dist/web/assets/{gnuplot-V-Pxck7b.js → gnuplot-D9i3MaT-.js} +0 -0
  446. /package/dist/web/assets/{go-ARF6DprD.js → go-CwCBf_Pt.js} +0 -0
  447. /package/dist/web/assets/{groovy-CKGBIPOz.js → groovy-D7mEfwmW.js} +0 -0
  448. /package/dist/web/assets/{gruvbox-dark-hard-Cm9wqdI3.js → gruvbox-dark-hard-C4R4MOoH.js} +0 -0
  449. /package/dist/web/assets/{gruvbox-dark-medium-Cmsd6dG8.js → gruvbox-dark-medium-CZnFC0s8.js} +0 -0
  450. /package/dist/web/assets/{gruvbox-dark-soft-BBGlGGCz.js → gruvbox-dark-soft-DFcJlEBl.js} +0 -0
  451. /package/dist/web/assets/{gruvbox-light-hard-BCR7KmqG.js → gruvbox-light-hard-B6IF_1L-.js} +0 -0
  452. /package/dist/web/assets/{gruvbox-light-medium-CP32bga8.js → gruvbox-light-medium-C19AZJv2.js} +0 -0
  453. /package/dist/web/assets/{gruvbox-light-soft-qjxk8ps6.js → gruvbox-light-soft-BAnpDm9B.js} +0 -0
  454. /package/dist/web/assets/{haskell-C5DzwGRa.js → haskell-CsXSYXRG.js} +0 -0
  455. /package/dist/web/assets/{haxe-C8f_jCVv.js → haxe-B1uyiI9t.js} +0 -0
  456. /package/dist/web/assets/{hcl-DetJkf6E.js → hcl-DlYKn5q9.js} +0 -0
  457. /package/dist/web/assets/{hjson-DeZhwIdo.js → hjson-C_0VMGJJ.js} +0 -0
  458. /package/dist/web/assets/{hlsl-SMIDLRyX.js → hlsl-zt2PEzbg.js} +0 -0
  459. /package/dist/web/assets/{horizon-BMAVDxMx.js → horizon-6wQfyOK0.js} +0 -0
  460. /package/dist/web/assets/{horizon-bright-3BaV_VGD.js → horizon-bright-CqI2qTim.js} +0 -0
  461. /package/dist/web/assets/{houston-BWBbEPvb.js → houston-BljpHDoG.js} +0 -0
  462. /package/dist/web/assets/{hy-DcsmE-Nm.js → hy-XzuL55dw.js} +0 -0
  463. /package/dist/web/assets/{image-gallery-Bqb-wjNw.js → image-gallery-Cj8nd4xW.js} +0 -0
  464. /package/dist/web/assets/{imba-CnkgnYMf.js → imba-CqhhjBrP.js} +0 -0
  465. /package/dist/web/assets/{ini-BnvTdau7.js → ini-CiFDi6cY.js} +0 -0
  466. /package/dist/web/assets/{java-BLFACXat.js → java-OsQgTL5-.js} +0 -0
  467. /package/dist/web/assets/{javascript-BKcFWENk.js → javascript-qWAOScxl.js} +0 -0
  468. /package/dist/web/assets/{json-3AJtJADf.js → json-RiAKsaD1.js} +0 -0
  469. /package/dist/web/assets/{json5-DLyDVlwe.js → json5-BeCnS3H2.js} +0 -0
  470. /package/dist/web/assets/{jsonc-B5DgF1jW.js → jsonc-CbTW1Mka.js} +0 -0
  471. /package/dist/web/assets/{jsonl-CV_O71a2.js → jsonl-BLSi_669.js} +0 -0
  472. /package/dist/web/assets/{jsonnet-DZfPLgYz.js → jsonnet-C3xAggou.js} +0 -0
  473. /package/dist/web/assets/{jssm-Cr1zYOsY.js → jssm-CLdiagC9.js} +0 -0
  474. /package/dist/web/assets/{jsx-VN-JS2_q.js → jsx-lYE1lb50.js} +0 -0
  475. /package/dist/web/assets/{kanagawa-dragon-D9CcT3xg.js → kanagawa-dragon-Cd1vcrpD.js} +0 -0
  476. /package/dist/web/assets/{kanagawa-lotus-DLXsB8fr.js → kanagawa-lotus-Dw3QKtk9.js} +0 -0
  477. /package/dist/web/assets/{kanagawa-wave-weDs3Wjz.js → kanagawa-wave-DIq5Zv5G.js} +0 -0
  478. /package/dist/web/assets/{kdl-0XF56jnT.js → kdl-BfDV0825.js} +0 -0
  479. /package/dist/web/assets/{keyboard-DBBaoQSu.js → keyboard-DiOYrs6C.js} +0 -0
  480. /package/dist/web/assets/{kotlin-BPkH9vzk.js → kotlin-DONxR4wD.js} +0 -0
  481. /package/dist/web/assets/{kusto-DMVpADb9.js → kusto-BDP63Fh5.js} +0 -0
  482. /package/dist/web/assets/{laserwave-Cl6T2cGP.js → laserwave-BeBrWApL.js} +0 -0
  483. /package/dist/web/assets/{lean-BvwzQGl1.js → lean-DOZc0N1A.js} +0 -0
  484. /package/dist/web/assets/{less-DI_03xkC.js → less-C3H9NlDv.js} +0 -0
  485. /package/dist/web/assets/{lib-v9x_qXgI.js → lib-xrSJ4EZX.js} +0 -0
  486. /package/dist/web/assets/{libesm-dcbw4_18.js → libesm-DLBE59ul.js} +0 -0
  487. /package/dist/web/assets/{light-plus-hIiIsQN_.js → light-plus-C_kTL608.js} +0 -0
  488. /package/dist/web/assets/{llvm-B5hjjSWc.js → llvm-Dsp27U3P.js} +0 -0
  489. /package/dist/web/assets/{loader-circle-DouKgIXy.js → loader-circle-DPMrnLQ2.js} +0 -0
  490. /package/dist/web/assets/{log-D28raQiE.js → log-BywQaTCy.js} +0 -0
  491. /package/dist/web/assets/{logo-6Cdd4ASb.js → logo-BirJgciO.js} +0 -0
  492. /package/dist/web/assets/{luau-CK9nKpOX.js → luau-Btrz5kJB.js} +0 -0
  493. /package/dist/web/assets/{make-CVnxKCB6.js → make-Kl7nFQrS.js} +0 -0
  494. /package/dist/web/assets/{markdown-B9mMQzD5.js → markdown-qsC4P8S-.js} +0 -0
  495. /package/dist/web/assets/{material-theme-BZSs2o0j.js → material-theme-CC__ui3p.js} +0 -0
  496. /package/dist/web/assets/{material-theme-darker-BeAQBzQ_.js → material-theme-darker-D4FxG2FZ.js} +0 -0
  497. /package/dist/web/assets/{material-theme-lighter-BAe0IXcs.js → material-theme-lighter-zGqiWcxn.js} +0 -0
  498. /package/dist/web/assets/{material-theme-ocean-CiPIf0Np.js → material-theme-ocean-CZIdTTB1.js} +0 -0
  499. /package/dist/web/assets/{material-theme-palenight-DJBb4_XN.js → material-theme-palenight-CEIs0GcK.js} +0 -0
  500. /package/dist/web/assets/{matlab-CY0dbp_s.js → matlab-Cf0aUjN9.js} +0 -0
  501. /package/dist/web/assets/{mdx-DU9zbF7h.js → mdx-CIsXjH86.js} +0 -0
  502. /package/dist/web/assets/{mermaid-UmmjGwdh.js → mermaid-B06WO9Qc.js} +0 -0
  503. /package/dist/web/assets/{min-dark-C59GkzOm.js → min-dark-z8fyp0FT.js} +0 -0
  504. /package/dist/web/assets/{min-light-B9QglrdQ.js → min-light-Dr-qesbW.js} +0 -0
  505. /package/dist/web/assets/{mipsasm-Dc6Jsr_D.js → mipsasm-CWHzYLcE.js} +0 -0
  506. /package/dist/web/assets/{mojo-D5ij-nGW.js → mojo-D9zXrKmv.js} +0 -0
  507. /package/dist/web/assets/{monitor-CxPV_fQI.js → monitor-C835ucWH.js} +0 -0
  508. /package/dist/web/assets/{monokai-QAtE_yCP.js → monokai-4b-AKOpX.js} +0 -0
  509. /package/dist/web/assets/{moonbit-BMtIdiTL.js → moonbit-Dl4dhOZJ.js} +0 -0
  510. /package/dist/web/assets/{move-BB_jMHYZ.js → move-DTYKJmXy.js} +0 -0
  511. /package/dist/web/assets/{narrat-BvvoKGVn.js → narrat-DnvXOS-o.js} +0 -0
  512. /package/dist/web/assets/{nextflow-groovy-CqxtAsnF.js → nextflow-groovy-Di8QnEXg.js} +0 -0
  513. /package/dist/web/assets/{night-owl-CLoE9kjt.js → night-owl-Czh0yq-H.js} +0 -0
  514. /package/dist/web/assets/{night-owl-light-BwIFT5Xd.js → night-owl-light-D_OpZqOR.js} +0 -0
  515. /package/dist/web/assets/{nix-BKlnVT9r.js → nix-2MvcjpK3.js} +0 -0
  516. /package/dist/web/assets/{nord-B7R1KRAK.js → nord-CG_r4cyk.js} +0 -0
  517. /package/dist/web/assets/{nushell-CzUme-NJ.js → nushell-DINskMcU.js} +0 -0
  518. /package/dist/web/assets/{objective-c-D-UKKiOc.js → objective-c-Dv7BIicR.js} +0 -0
  519. /package/dist/web/assets/{objective-cpp-BucWn4Fx.js → objective-cpp-CpqsCxPb.js} +0 -0
  520. /package/dist/web/assets/{ocaml-EJCNXMGo.js → ocaml-DwTsYSmW.js} +0 -0
  521. /package/dist/web/assets/{odin-CEjnRU_3.js → odin-G-4O4k4t.js} +0 -0
  522. /package/dist/web/assets/{one-dark-pro-Bv469NX5.js → one-dark-pro-13Pv5SjI.js} +0 -0
  523. /package/dist/web/assets/{one-light-BlzO5RWI.js → one-light-Bi2e86mS.js} +0 -0
  524. /package/dist/web/assets/{openscad-Czl6alLw.js → openscad-D0WsOTij.js} +0 -0
  525. /package/dist/web/assets/{pascal-BI9fLvG4.js → pascal-DmDStuXr.js} +0 -0
  526. /package/dist/web/assets/{pkl-ChQLsyjU.js → pkl-Bl6Qjtuo.js} +0 -0
  527. /package/dist/web/assets/{plastic-DGL3mtdL.js → plastic-UeSVSjTI.js} +0 -0
  528. /package/dist/web/assets/{plsql-BJDjqcWp.js → plsql-DreFC689.js} +0 -0
  529. /package/dist/web/assets/{po-DK5O3OpF.js → po-ZIjccxsX.js} +0 -0
  530. /package/dist/web/assets/{poimandres-CnuqZT4T.js → poimandres-CvQHJPrI.js} +0 -0
  531. /package/dist/web/assets/{polar-DCsUxSsQ.js → polar-BrMB0Kgh.js} +0 -0
  532. /package/dist/web/assets/{portal-container-context-D-495m5C.js → portal-container-context-a399Hd9F.js} +0 -0
  533. /package/dist/web/assets/{postcss-8UjdFmis.js → postcss-pgUsil1T.js} +0 -0
  534. /package/dist/web/assets/{powerquery-CAp0CGQj.js → powerquery-CTsZkCg_.js} +0 -0
  535. /package/dist/web/assets/{powershell-CbBsKZJP.js → powershell-CcwVuBAp.js} +0 -0
  536. /package/dist/web/assets/{prisma-BnNpXCJN.js → prisma-spjnAI5W.js} +0 -0
  537. /package/dist/web/assets/{prolog-CC-l9E8Y.js → prolog-ChCkZpDO.js} +0 -0
  538. /package/dist/web/assets/{proto-CV_1k2rL.js → proto-wVGqK4gB.js} +0 -0
  539. /package/dist/web/assets/{puppet-2D8HU2bv.js → puppet-CED7iwPQ.js} +0 -0
  540. /package/dist/web/assets/{purescript-BEJAORgU.js → purescript-u9lGiQKV.js} +0 -0
  541. /package/dist/web/assets/{python-BBdbb7nn.js → python-DeqTx6ef.js} +0 -0
  542. /package/dist/web/assets/{qmldir-D5ZYI4Ie.js → qmldir-BH1uoPrX.js} +0 -0
  543. /package/dist/web/assets/{qss-BOJVGyXE.js → qss-CUVKA_13.js} +0 -0
  544. /package/dist/web/assets/{r-B5dlEtkj.js → r-BqdPzqYH.js} +0 -0
  545. /package/dist/web/assets/{racket-BUAEygJ6.js → racket-C2eTgPN3.js} +0 -0
  546. /package/dist/web/assets/{raku-BG0Ss0ID.js → raku-uAay1sWx.js} +0 -0
  547. /package/dist/web/assets/{react-B549sK_T.js → react-X4jUntEf.js} +0 -0
  548. /package/dist/web/assets/{red-C5yyQbtG.js → red-DTZP1ZfC.js} +0 -0
  549. /package/dist/web/assets/{reg-DHoBWtLf.js → reg-Cdk3YsxY.js} +0 -0
  550. /package/dist/web/assets/{regexp-DDtJrlKv.js → regexp-Bpa2aS7K.js} +0 -0
  551. /package/dist/web/assets/{rel-CR6WJDRD.js → rel-bNsb1-3L.js} +0 -0
  552. /package/dist/web/assets/{resolve-theme-4fexHWl6.js → resolve-theme-DQk_YcsT.js} +0 -0
  553. /package/dist/web/assets/{riscv-2V9tC-nx.js → riscv-DSaubByW.js} +0 -0
  554. /package/dist/web/assets/{ron-QLTmqGUw.js → ron-DbQzYM6i.js} +0 -0
  555. /package/dist/web/assets/{rose-pine-be0__X3A.js → rose-pine-C7RsprTu.js} +0 -0
  556. /package/dist/web/assets/{rose-pine-dawn-n_WQJwsW.js → rose-pine-dawn-VP976YzL.js} +0 -0
  557. /package/dist/web/assets/{rose-pine-moon-t2aLtzgB.js → rose-pine-moon-XHZNhOqd.js} +0 -0
  558. /package/dist/web/assets/{rosmsg-Ce1JD8vt.js → rosmsg-BVQlx0Lj.js} +0 -0
  559. /package/dist/web/assets/{rust-5fHH-ZoW.js → rust-VHaQ3rh3.js} +0 -0
  560. /package/dist/web/assets/{sass-DPmGFiOJ.js → sass-CO-kMkwD.js} +0 -0
  561. /package/dist/web/assets/{save-DYLiVzU8.js → save-CipGrG1k.js} +0 -0
  562. /package/dist/web/assets/{scala-BRMVFbZF.js → scala-cY0GXkiz.js} +0 -0
  563. /package/dist/web/assets/{scheme-BMSJWevF.js → scheme-D4LNZE0X.js} +0 -0
  564. /package/dist/web/assets/{sdbl-DTZ-pEHI.js → sdbl-Bh2Fd2cn.js} +0 -0
  565. /package/dist/web/assets/{shellscript-6-9yg5Cz.js → shellscript-KKclmCnt.js} +0 -0
  566. /package/dist/web/assets/{shield-check-CUI36aSc.js → shield-check-BsG3LV8i.js} +0 -0
  567. /package/dist/web/assets/{shield-off-GdkkVhb3.js → shield-off-7jKWpJBD.js} +0 -0
  568. /package/dist/web/assets/{slack-dark-DKJgiR7e.js → slack-dark-D6YQ-fqf.js} +0 -0
  569. /package/dist/web/assets/{slack-ochin-WjehVgyK.js → slack-ochin-CDf66CDj.js} +0 -0
  570. /package/dist/web/assets/{smalltalk-BChf4SN1.js → smalltalk-D2OH_QIl.js} +0 -0
  571. /package/dist/web/assets/{snazzy-light-C1WAwOx6.js → snazzy-light-CQeF8G1K.js} +0 -0
  572. /package/dist/web/assets/{solarized-dark-BxREZrGx.js → solarized-dark-LTPf1iLZ.js} +0 -0
  573. /package/dist/web/assets/{solarized-light-DG29B1pR.js → solarized-light-Ose1n88k.js} +0 -0
  574. /package/dist/web/assets/{solidity-bDbtGwdR.js → solidity-lFuOMHki.js} +0 -0
  575. /package/dist/web/assets/{splunk-Ceqx5njI.js → splunk-Bov-sB5S.js} +0 -0
  576. /package/dist/web/assets/{sql-OwMEKbz8.js → sql-BKyZ6wmk.js} +0 -0
  577. /package/dist/web/assets/{ssh-config-WAR3lu2k.js → ssh-config-CqJoMGMb.js} +0 -0
  578. /package/dist/web/assets/{stylus-Cjmoz3d8.js → stylus-Bf5pWRLI.js} +0 -0
  579. /package/dist/web/assets/{swift-S_ZSBG1q.js → swift-D5d1hn_u.js} +0 -0
  580. /package/dist/web/assets/{synthwave-84-DiUeWMSh.js → synthwave-84-B4c6YAgO.js} +0 -0
  581. /package/dist/web/assets/{system-verilog-RRjqPjUV.js → system-verilog-D7fXyDsI.js} +0 -0
  582. /package/dist/web/assets/{systemd-BD3SkXoU.js → systemd-oNagzlBK.js} +0 -0
  583. /package/dist/web/assets/{table-D5Q4ap4W.js → table-CRJsmfeg.js} +0 -0
  584. /package/dist/web/assets/{talonscript-DWefISOm.js → talonscript-C6Nqm9Dm.js} +0 -0
  585. /package/dist/web/assets/{tasl-DCjSS-09.js → tasl-CaWy-cBX.js} +0 -0
  586. /package/dist/web/assets/{tcl-DTYqT_66.js → tcl-C435P5Mq.js} +0 -0
  587. /package/dist/web/assets/{terraform-CkbP5QnR.js → terraform-CVDxqdPf.js} +0 -0
  588. /package/dist/web/assets/{text-wrap-DBvQD3kc.js → text-wrap-oiekm5SB.js} +0 -0
  589. /package/dist/web/assets/{tokyo-night-qVSqO9xy.js → tokyo-night-Uhs17tQ3.js} +0 -0
  590. /package/dist/web/assets/{toml-D9F5Osqs.js → toml-j6fPOJ6P.js} +0 -0
  591. /package/dist/web/assets/{tsv-C5tpHoxm.js → tsv-h78O6xrL.js} +0 -0
  592. /package/dist/web/assets/{tsx-CMWkGtGm.js → tsx-Bol-vCrQ.js} +0 -0
  593. /package/dist/web/assets/{turtle-Cn4z3xOi.js → turtle-Dw8eFf04.js} +0 -0
  594. /package/dist/web/assets/{typescript-DSyAEWsn.js → typescript-Dtz7DPci.js} +0 -0
  595. /package/dist/web/assets/{typespec-B93Qap7y.js → typespec-BIOQ7dMP.js} +0 -0
  596. /package/dist/web/assets/{typst-DtlOmFlX.js → typst-CoaGMjzu.js} +0 -0
  597. /package/dist/web/assets/{use-is-mobile-B9UZNwSm.js → use-is-mobile-CYW3FJFE.js} +0 -0
  598. /package/dist/web/assets/{use-visual-viewport-Sfb6CZ80.js → use-visual-viewport-rnvuooQZ.js} +0 -0
  599. /package/dist/web/assets/{utils-QHe_Q6UF.js → utils-C9OjCrou.js} +0 -0
  600. /package/dist/web/assets/{v-Dfzf-szS.js → v-BoqFdpJ7.js} +0 -0
  601. /package/dist/web/assets/{vala-CyiQZQ9f.js → vala-COBpNnCC.js} +0 -0
  602. /package/dist/web/assets/{vb-C7DGSldd.js → vb-DxI86znh.js} +0 -0
  603. /package/dist/web/assets/{vendor-xterm-BHKF_Nsf.js → vendor-xterm-BaMOj65g.js} +0 -0
  604. /package/dist/web/assets/{verilog-kF-hClY2.js → verilog-DcTAamuQ.js} +0 -0
  605. /package/dist/web/assets/{vesper-BmKcADuz.js → vesper-DO298JSv.js} +0 -0
  606. /package/dist/web/assets/{vhdl-CDnb16KJ.js → vhdl-Dq-bDVSN.js} +0 -0
  607. /package/dist/web/assets/{viml-B8cs6Ady.js → viml-zHTRWuQh.js} +0 -0
  608. /package/dist/web/assets/{vitesse-black-BR6o9Gto.js → vitesse-black-jlYMJCBf.js} +0 -0
  609. /package/dist/web/assets/{vitesse-dark-Dye14-KB.js → vitesse-dark-DS0sUlSZ.js} +0 -0
  610. /package/dist/web/assets/{vitesse-light-BLpswIxf.js → vitesse-light-HSeZ2YhG.js} +0 -0
  611. /package/dist/web/assets/{vyper-DU51Z97W.js → vyper-BQfKVO3M.js} +0 -0
  612. /package/dist/web/assets/{wasm-ChtccNfv.js → wasm-B1xp9bLp.js} +0 -0
  613. /package/dist/web/assets/{wasm-CVqlJAR2.js → wasm-B6mCuI8h.js} +0 -0
  614. /package/dist/web/assets/{wenyan-DhCa3hCn.js → wenyan-CfpS05sP.js} +0 -0
  615. /package/dist/web/assets/{wgsl-Bj1Ec4vv.js → wgsl-14GDsWRD.js} +0 -0
  616. /package/dist/web/assets/{wikitext-C90DHNa0.js → wikitext-Ccbq5EVX.js} +0 -0
  617. /package/dist/web/assets/{wit-DUgbOD7Z.js → wit-C19hb3R5.js} +0 -0
  618. /package/dist/web/assets/{wolfram-BgwUqD1I.js → wolfram-B-ZSwGwP.js} +0 -0
  619. /package/dist/web/assets/{x-DiOUR_mp.js → x-YDu4m2Iq.js} +0 -0
  620. /package/dist/web/assets/{yaml-DEQulj-8.js → yaml-mw_JUeio.js} +0 -0
  621. /package/dist/web/assets/{zenscript-C7Jp7TgT.js → zenscript-WbzDRjvF.js} +0 -0
  622. /package/dist/web/assets/{zig-9x4qXYLg.js → zig-F0vvImTJ.js} +0 -0
@@ -0,0 +1,157 @@
1
+ import type { UsageInfo } from "../../types/chat.ts";
2
+ import {
3
+ AMBIENT_ACCOUNT_KEY,
4
+ FETCH_TIMEOUT_MS,
5
+ type ProviderUsageSource,
6
+ } from "./usage-source.ts";
7
+ import {
8
+ getCachedUsageEntry,
9
+ isRecentlyFailed,
10
+ setCachedFailure,
11
+ setCachedUsage,
12
+ invalidateCachedUsage,
13
+ } from "./usage-memory-cache.ts";
14
+ import { readStoredUsage, writeStoredUsage } from "./usage-snapshot-store.ts";
15
+
16
+ /**
17
+ * Registry of provider usage sources, and the read/refresh path over them.
18
+ *
19
+ * The rule the whole layer exists to enforce: **reads never wait on a provider.**
20
+ * `getUsage` is synchronous and answers from memory, falling back to the stored
21
+ * snapshot. Anything that talks to a provider happens on the background sweep or
22
+ * behind an explicit refresh, both of which are bounded by a timeout.
23
+ */
24
+
25
+ const sources = new Map<string, ProviderUsageSource>();
26
+
27
+ export function registerUsageSource(source: ProviderUsageSource): void {
28
+ sources.set(source.providerId, source);
29
+ }
30
+
31
+ export function getUsageSource(providerId: string): ProviderUsageSource | undefined {
32
+ return sources.get(providerId);
33
+ }
34
+
35
+ export function listUsageSources(): ProviderUsageSource[] {
36
+ return [...sources.values()];
37
+ }
38
+
39
+ /** Test seam: drop every registered source. */
40
+ export function _clearUsageSources(): void {
41
+ sources.clear();
42
+ }
43
+
44
+ /**
45
+ * Latest known usage for one account, without ever touching the provider.
46
+ *
47
+ * Memory first, then the stored snapshot — which is what makes a percentage
48
+ * appear immediately after a restart, before the first sweep lands. Returns an
49
+ * empty object when nothing has ever been recorded; the caller cannot tell that
50
+ * apart from "no limits reported", and does not need to.
51
+ */
52
+ export function getUsage(providerId: string, accountId = AMBIENT_ACCOUNT_KEY): UsageInfo {
53
+ const cached = getCachedUsageEntry(providerId, accountId);
54
+ if (cached) return cached;
55
+ const stored = readStoredUsage(providerId, accountId);
56
+ if (stored) {
57
+ // Promote into memory so the next read skips the query, but keep the
58
+ // provider's own freshness clock: this value is as old as it ever was.
59
+ setCachedUsage(providerId, accountId, stored);
60
+ return stored;
61
+ }
62
+ return {};
63
+ }
64
+
65
+ /** Reject if `promise` has not settled within `ms`, naming what timed out. */
66
+ function withTimeout<T>(promise: Promise<T>, ms: number, what: string): Promise<T> {
67
+ return new Promise<T>((resolve, reject) => {
68
+ const timer = setTimeout(() => reject(new Error(`${what} timed out after ${ms}ms`)), ms);
69
+ promise.then(
70
+ (v) => { clearTimeout(timer); resolve(v); },
71
+ (e) => { clearTimeout(timer); reject(e); },
72
+ );
73
+ });
74
+ }
75
+
76
+ /**
77
+ * Concurrent refreshes of the same account share one in-flight fetch.
78
+ *
79
+ * Each attempt carries a token so its cleanup can tell "I am still the current
80
+ * attempt" from "I was abandoned and something newer took my place".
81
+ */
82
+ const inflight = new Map<string, { token: object; promise: Promise<UsageInfo> }>();
83
+
84
+ /**
85
+ * Fetch one account live, then cache and store it.
86
+ *
87
+ * Failures are swallowed into the last known value rather than propagated: the
88
+ * callers are a background sweep and a toolbar refresh, and neither has anything
89
+ * useful to do with the error beyond showing what it already had. The failure is
90
+ * still remembered so it does not turn into a retry per request.
91
+ */
92
+ export async function refreshUsage(
93
+ providerId: string,
94
+ accountId = AMBIENT_ACCOUNT_KEY,
95
+ ): Promise<UsageInfo> {
96
+ const source = sources.get(providerId);
97
+ if (!source) return {};
98
+
99
+ const key = `${providerId}\0${accountId}`;
100
+ const existing = inflight.get(key);
101
+ if (existing) return existing.promise;
102
+
103
+ // Identity for this attempt. Comparing tokens rather than the promise avoids
104
+ // the closure having to reference the very promise it is producing.
105
+ const token = {};
106
+ const run = (async () => {
107
+ try {
108
+ const usage = await withTimeout(
109
+ source.fetch(accountId),
110
+ source.fetchTimeoutMs ?? FETCH_TIMEOUT_MS,
111
+ `${providerId} usage fetch`,
112
+ );
113
+ setCachedUsage(providerId, accountId, usage);
114
+ writeStoredUsage(providerId, accountId, usage);
115
+ return usage;
116
+ } catch (e) {
117
+ setCachedFailure(providerId, accountId);
118
+ console.error(`[usage] ${providerId}/${accountId || "ambient"}:`, (e as Error).message);
119
+ return readStoredUsage(providerId, accountId) ?? {};
120
+ } finally {
121
+ // Only clear if this is still the current attempt. An abandoned fetch
122
+ // that finishes late must not evict the entry a newer one just installed,
123
+ // or that newer caller would be silently un-deduped.
124
+ if (inflight.get(key)?.token === token) inflight.delete(key);
125
+ }
126
+ })();
127
+
128
+ inflight.set(key, { token, promise: run });
129
+ return run;
130
+ }
131
+
132
+ /** Test seam: forget every in-flight fetch so the next call starts a fresh one. */
133
+ export function _clearInflightUsage(): void {
134
+ inflight.clear();
135
+ }
136
+
137
+ /**
138
+ * Read, fetching only if nothing usable is known yet.
139
+ *
140
+ * Used by the request path so a cold start still produces a number, while a
141
+ * remembered failure short-circuits to the stored value instead of re-attempting
142
+ * a fetch on every request.
143
+ */
144
+ export async function getOrFetchUsage(
145
+ providerId: string,
146
+ accountId = AMBIENT_ACCOUNT_KEY,
147
+ ): Promise<UsageInfo> {
148
+ const known = getUsage(providerId, accountId);
149
+ if (Object.keys(known).length > 0) return known;
150
+ if (isRecentlyFailed(providerId, accountId)) return known;
151
+ return refreshUsage(providerId, accountId);
152
+ }
153
+
154
+ /** Force the next read of this provider (or one account) to go back to the source. */
155
+ export function invalidateUsage(providerId: string, accountId?: string): void {
156
+ invalidateCachedUsage(providerId, accountId);
157
+ }
@@ -0,0 +1,117 @@
1
+ import {
2
+ ACCOUNT_STAGGER_MS,
3
+ AMBIENT_ACCOUNT_KEY,
4
+ DEFAULT_POLL_INTERVAL_MS,
5
+ type ProviderUsageSource,
6
+ } from "./usage-source.ts";
7
+ import { listUsageSources, refreshUsage } from "./usage-registry.ts";
8
+
9
+ /**
10
+ * The one background loop that keeps every provider's quota warm.
11
+ *
12
+ * Sweeping in the background is what makes reads instant: by the time the
13
+ * toolbar asks, the value is already in memory. Both providers run on the same
14
+ * cadence and through the same code, so a new provider gets this by registering
15
+ * a source — there is no second timer to write.
16
+ *
17
+ * Survives `bun --hot` reloads through globalThis: module state resets on
18
+ * reload, so a module-level timer would leak a new interval per reload while
19
+ * the old one kept firing.
20
+ */
21
+
22
+ const HOT_KEY = "__PPM_PROVIDER_USAGE_POLL__" as const;
23
+
24
+ interface HotState {
25
+ timers: Map<string, ReturnType<typeof setTimeout>>;
26
+ inflight: Map<string, Promise<void>>;
27
+ }
28
+
29
+ const hot: HotState = ((globalThis as Record<string, unknown>)[HOT_KEY] ??= {
30
+ timers: new Map(),
31
+ inflight: new Map(),
32
+ }) as HotState;
33
+
34
+ /**
35
+ * One pass over a source's accounts.
36
+ *
37
+ * Accounts are walked in series with a gap between them: a sweep is maintenance,
38
+ * not something worth spending a burst of subprocesses or API calls on. Each
39
+ * account is independent — `refreshUsage` never rejects, so a failing account
40
+ * does not cut the sweep short for the ones after it.
41
+ */
42
+ async function sweepSource(source: ProviderUsageSource): Promise<void> {
43
+ const accountIds = source.listAccountIds();
44
+ // No account store configured: the provider reads one ambient login instead.
45
+ const targets = accountIds.length > 0 ? accountIds : [AMBIENT_ACCOUNT_KEY];
46
+
47
+ for (let i = 0; i < targets.length; i++) {
48
+ const accountId = targets[i]!;
49
+ try {
50
+ if (await source.shouldSkip?.(accountId)) continue;
51
+ } catch {
52
+ continue; // A source that cannot decide is treated as "skip", not "fetch".
53
+ }
54
+ await refreshUsage(source.providerId, accountId);
55
+ if (i < targets.length - 1) await new Promise((r) => setTimeout(r, ACCOUNT_STAGGER_MS));
56
+ }
57
+ }
58
+
59
+ /** Sweep one source now; concurrent callers share the in-flight pass. */
60
+ export function sweepUsageSource(source: ProviderUsageSource): Promise<void> {
61
+ const existing = hot.inflight.get(source.providerId);
62
+ if (existing) return existing;
63
+
64
+ const pass = sweepSource(source)
65
+ .catch((e) => console.error(`[usage] sweep ${source.providerId}:`, (e as Error).message))
66
+ .finally(() => {
67
+ // Only clear if still the current pass, so a stale finally cannot drop a newer one.
68
+ if (hot.inflight.get(source.providerId) === pass) hot.inflight.delete(source.providerId);
69
+ });
70
+
71
+ hot.inflight.set(source.providerId, pass);
72
+ return pass;
73
+ }
74
+
75
+ /**
76
+ * Start the background loop for every registered source.
77
+ *
78
+ * Each source is swept once immediately — a fresh server should not show blank
79
+ * percentages for a whole interval — and then rescheduled after each pass
80
+ * completes rather than on a fixed interval, so a slow sweep cannot overlap the
81
+ * next one.
82
+ */
83
+ export function startProviderUsagePolling(): void {
84
+ stopProviderUsagePolling();
85
+ for (const source of listUsageSources()) {
86
+ const interval = source.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
87
+ const scheduleNext = () => {
88
+ hot.timers.set(source.providerId, setTimeout(async () => {
89
+ await sweepUsageSource(source);
90
+ scheduleNext();
91
+ }, interval));
92
+ };
93
+ sweepUsageSource(source).then(scheduleNext, scheduleNext);
94
+ }
95
+ }
96
+
97
+ export function stopProviderUsagePolling(): void {
98
+ for (const timer of hot.timers.values()) clearTimeout(timer);
99
+ hot.timers.clear();
100
+ }
101
+
102
+ /**
103
+ * Abandon any in-flight sweep so the next one starts fresh.
104
+ *
105
+ * Needed when a sweep is known to be stuck: without it the hung pass is handed
106
+ * to every later caller, and a provider that stops answering blocks its own
107
+ * refresh indefinitely. The guard in {@link sweepUsageSource} means the
108
+ * abandoned pass cannot evict its replacement when it eventually settles.
109
+ */
110
+ export function _clearInflightSweeps(): void {
111
+ hot.inflight.clear();
112
+ }
113
+
114
+ /** Test seam: true while any source has a scheduled pass. */
115
+ export function _isUsagePollingActive(): boolean {
116
+ return hot.timers.size > 0;
117
+ }
@@ -0,0 +1,126 @@
1
+ import type { UsageInfo, LimitBucket } from "../../types/chat.ts";
2
+ import {
3
+ insertLimitSnapshot,
4
+ getLatestLimitSnapshot,
5
+ getLatestSnapshotForAccount,
6
+ touchSnapshotTimestamp,
7
+ cleanupOldLimitSnapshots,
8
+ type LimitSnapshotRow,
9
+ } from "../db.service.ts";
10
+ import { AMBIENT_ACCOUNT_KEY } from "./usage-source.ts";
11
+
12
+ /**
13
+ * The durable half of the usage layer: turning a provider's `UsageInfo` into a
14
+ * snapshot row and back, for any provider.
15
+ *
16
+ * Persistence is what lets the toolbar show a number the moment the server comes
17
+ * back up, before the first background sweep has finished. Without it a restart
18
+ * blanks every percentage for as long as the first fetch takes.
19
+ *
20
+ * A row is only written when something actually moved. Otherwise the newest row
21
+ * has its timestamp touched, so "last fetched" stays honest without growing a
22
+ * row per account per sweep forever.
23
+ */
24
+
25
+ /** Recompute a bucket's countdown against the current clock. */
26
+ function toBucket(util: number, resetsAt: string, windowHours: number): LimitBucket {
27
+ const diff = resetsAt ? new Date(resetsAt).getTime() - Date.now() : 0;
28
+ const totalMins = diff > 0 ? Math.ceil(diff / 60_000) : 0;
29
+ return {
30
+ utilization: util,
31
+ resetsAt,
32
+ resetsInMinutes: windowHours <= 5 ? totalMins : null,
33
+ resetsInHours: windowHours > 5 ? Math.round((totalMins / 60) * 100) / 100 : null,
34
+ windowHours,
35
+ };
36
+ }
37
+
38
+ /** Snapshot row → UsageInfo, with the flat mirror fields the chat toolbar reads. */
39
+ export function snapshotToUsage(row: LimitSnapshotRow): UsageInfo & { lastFetchedAt?: string } {
40
+ // SQLite's datetime('now') is UTC but carries no Z, which JS would read as local time.
41
+ const iso = row.recorded_at.endsWith("Z")
42
+ ? row.recorded_at
43
+ : row.recorded_at.replace(" ", "T") + "Z";
44
+ const usage: UsageInfo & { lastFetchedAt?: string } = { lastFetchedAt: iso };
45
+ if (row.five_hour_util != null) {
46
+ usage.session = toBucket(row.five_hour_util, row.five_hour_resets_at ?? "", 5);
47
+ usage.fiveHour = row.five_hour_util;
48
+ if (row.five_hour_resets_at) usage.fiveHourResetsAt = row.five_hour_resets_at;
49
+ }
50
+ if (row.weekly_util != null) {
51
+ usage.weekly = toBucket(row.weekly_util, row.weekly_resets_at ?? "", 168);
52
+ usage.sevenDay = row.weekly_util;
53
+ if (row.weekly_resets_at) usage.sevenDayResetsAt = row.weekly_resets_at;
54
+ }
55
+ if (row.weekly_opus_util != null) {
56
+ usage.weeklyOpus = toBucket(row.weekly_opus_util, row.weekly_opus_resets_at ?? "", 168);
57
+ }
58
+ if (row.weekly_sonnet_util != null) {
59
+ usage.weeklySonnet = toBucket(row.weekly_sonnet_util, row.weekly_sonnet_resets_at ?? "", 168);
60
+ }
61
+ return usage;
62
+ }
63
+
64
+ /** Read the newest stored snapshot for one provider account, or undefined. */
65
+ export function readStoredUsage(
66
+ providerId: string,
67
+ accountId: string,
68
+ ): (UsageInfo & { lastFetchedAt?: string }) | undefined {
69
+ const row = accountId === AMBIENT_ACCOUNT_KEY
70
+ ? getLatestLimitSnapshot(providerId)
71
+ : getLatestSnapshotForAccount(accountId, providerId);
72
+ return row ? snapshotToUsage(row) : undefined;
73
+ }
74
+
75
+ /**
76
+ * True when any stored bucket differs from the freshly fetched one.
77
+ *
78
+ * Utilizations compare with a tolerance rather than exactly: they are floats
79
+ * that round-trip through SQLite, and treating a last-digit wobble as a change
80
+ * would write a row on every single sweep.
81
+ */
82
+ function hasChanged(usage: UsageInfo, last: LimitSnapshotRow | null): boolean {
83
+ if (!last) return true;
84
+ const moved = (fresh: number | null | undefined, stored: number | null) =>
85
+ fresh != null && (stored == null || Math.abs(fresh - stored) > 0.001);
86
+ if (moved(usage.session?.utilization, last.five_hour_util)) return true;
87
+ if (moved(usage.weekly?.utilization, last.weekly_util)) return true;
88
+ if (moved(usage.weeklyOpus?.utilization, last.weekly_opus_util)) return true;
89
+ if (moved(usage.weeklySonnet?.utilization, last.weekly_sonnet_util)) return true;
90
+ if (usage.session?.resetsAt && usage.session.resetsAt !== (last.five_hour_resets_at ?? "")) return true;
91
+ if (usage.weekly?.resetsAt && usage.weekly.resetsAt !== (last.weekly_resets_at ?? "")) return true;
92
+ return false;
93
+ }
94
+
95
+ /**
96
+ * Persist a freshly fetched value, writing a row only when a bucket moved.
97
+ *
98
+ * An account with nothing to report (every bucket absent) is not written at all:
99
+ * a row of nulls is indistinguishable from "never fetched" on read, so storing
100
+ * one buys nothing and only makes the history noisier.
101
+ */
102
+ export function writeStoredUsage(providerId: string, accountId: string, usage: UsageInfo): void {
103
+ const hasAnyBucket = usage.session != null || usage.weekly != null
104
+ || usage.weeklyOpus != null || usage.weeklySonnet != null;
105
+ if (!hasAnyBucket) return;
106
+
107
+ const id = accountId === AMBIENT_ACCOUNT_KEY ? null : accountId;
108
+ const last = id ? getLatestSnapshotForAccount(id, providerId) : getLatestLimitSnapshot(providerId);
109
+ if (!hasChanged(usage, last)) {
110
+ if (id) touchSnapshotTimestamp(id, providerId);
111
+ return;
112
+ }
113
+ insertLimitSnapshot({
114
+ provider: providerId,
115
+ account_id: id,
116
+ five_hour_util: usage.session?.utilization ?? null,
117
+ five_hour_resets_at: usage.session?.resetsAt ?? null,
118
+ weekly_util: usage.weekly?.utilization ?? null,
119
+ weekly_resets_at: usage.weekly?.resetsAt ?? null,
120
+ weekly_opus_util: usage.weeklyOpus?.utilization ?? null,
121
+ weekly_opus_resets_at: usage.weeklyOpus?.resetsAt ?? null,
122
+ weekly_sonnet_util: usage.weeklySonnet?.utilization ?? null,
123
+ weekly_sonnet_resets_at: usage.weeklySonnet?.resetsAt ?? null,
124
+ });
125
+ cleanupOldLimitSnapshots();
126
+ }
@@ -0,0 +1,82 @@
1
+ import type { UsageInfo } from "../../types/chat.ts";
2
+
3
+ /**
4
+ * What one provider must supply for its quota to be polled, cached, and stored
5
+ * by the shared usage layer.
6
+ *
7
+ * Everything scheduling-shaped — the background timer, the fetch timeout, the
8
+ * per-account cache, negative caching, staggering, in-flight de-duplication,
9
+ * writing to and reading back from the snapshot store — belongs to that layer,
10
+ * not here. A new provider implements the two required methods and inherits all
11
+ * of it; nothing in this interface knows about HTTP, JSON-RPC, or subprocesses.
12
+ */
13
+ export interface ProviderUsageSource {
14
+ /** Matches the AIProvider id, and is what a snapshot row is scoped by. */
15
+ readonly providerId: string;
16
+
17
+ /**
18
+ * Accounts whose quota should be kept warm.
19
+ *
20
+ * An empty array is meaningful: it means this provider has no account store
21
+ * configured and reads a single ambient login instead, which the layer keys
22
+ * under {@link AMBIENT_ACCOUNT_KEY}.
23
+ */
24
+ listAccountIds(): string[];
25
+
26
+ /**
27
+ * Read one account's quota from the provider, live.
28
+ *
29
+ * Throw or reject on failure; do NOT return an empty object to signal one.
30
+ * The layer distinguishes the two: a rejection is cached briefly so a broken
31
+ * account cannot be retried on every request, while a resolved empty object
32
+ * is stored as a genuine "this account has no limits to report".
33
+ *
34
+ * `accountId` is {@link AMBIENT_ACCOUNT_KEY} when the provider has no
35
+ * account store.
36
+ */
37
+ fetch(accountId: string): Promise<UsageInfo>;
38
+
39
+ /**
40
+ * Skip this account on a background sweep without treating it as a failure —
41
+ * an expired token, a cooldown after a rate-limit, a login that cannot
42
+ * refresh. The last stored value stays readable.
43
+ */
44
+ shouldSkip?(accountId: string): boolean | Promise<boolean>;
45
+
46
+ /** Poll period override. Defaults to {@link DEFAULT_POLL_INTERVAL_MS}. */
47
+ readonly pollIntervalMs?: number;
48
+
49
+ /** Per-fetch ceiling override. Defaults to {@link FETCH_TIMEOUT_MS}. */
50
+ readonly fetchTimeoutMs?: number;
51
+ }
52
+
53
+ /** Cache/store key for a provider that reads one ambient login, not an account list. */
54
+ export const AMBIENT_ACCOUNT_KEY = "";
55
+
56
+ /** Both providers refresh on the same cadence; a source may override it. */
57
+ export const DEFAULT_POLL_INTERVAL_MS = 300_000; // 5 min
58
+
59
+ /** Gap between accounts inside one sweep, so a sweep is not a burst. */
60
+ export const ACCOUNT_STAGGER_MS = 1_000;
61
+
62
+ /**
63
+ * Ceiling on a single `fetch`. Codex spawns an app-server to answer, and that
64
+ * subprocess has been observed to accept the spawn and then never reply — with
65
+ * no ceiling the sweep waits forever, the HTTP route behind it never answers,
66
+ * and the subprocess is never closed. Claude's HTTP call has its own timeout
67
+ * but is covered here too, so one hung account cannot stall the whole sweep.
68
+ */
69
+ export const FETCH_TIMEOUT_MS = 20_000;
70
+
71
+ /** How long a resolved value is served from memory before a re-read. */
72
+ export const USAGE_CACHE_TTL_MS = 300_000; // 5 min — matches the poll period
73
+
74
+ /**
75
+ * How long a FAILURE is remembered.
76
+ *
77
+ * Short, because the cause is usually transient (a login being refreshed, a
78
+ * subprocess losing a race). Non-zero, because without it every request that
79
+ * misses re-attempts a fetch: for codex that meant spawning another app-server
80
+ * per request, and the ones that hung were never reaped.
81
+ */
82
+ export const USAGE_FAILURE_TTL_MS = 30_000;
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Agent → Anthropic Messages, for `POST /proxy/<provider>/v1/messages`.
3
+ *
4
+ * The mirror of `proxy-agent-bridge.ts`: same agent turn (`proxy-agent-turn.ts`),
5
+ * same ephemeral-session and sandbox rules, rendered in Anthropic's dialect so a
6
+ * client already pointed at `ANTHROPIC_BASE_URL` can reach any PPM provider by
7
+ * setting that base to `…/proxy/<provider>`.
8
+ */
9
+ import {
10
+ startAgentTurn, usageOf, resolveProvider, proxyableProviderIds,
11
+ } from "./proxy-agent-turn.ts";
12
+ import {
13
+ buildPromptFromAnthropicMessages, messageResponse, anthropicError,
14
+ MessageStreamWriter, ANTHROPIC_SSE_HEADERS, type AnthropicMessagesBody,
15
+ } from "./proxy-anthropic-format.ts";
16
+
17
+ /** Open the turn described by an Anthropic-format body. */
18
+ function startTurn(providerId: string, body: AnthropicMessagesBody) {
19
+ const { prompt, systemPrompt } = buildPromptFromAnthropicMessages(body);
20
+ return startAgentTurn(providerId, { prompt, systemPrompt, model: body.model });
21
+ }
22
+
23
+ /** Non-streaming: drain the turn, return one `message`. */
24
+ async function runNonStreaming(providerId: string, body: AnthropicMessagesBody): Promise<Response> {
25
+ const { events, cleanup } = await startTurn(providerId, body);
26
+ try {
27
+ let text = "";
28
+ let usage: ReturnType<typeof usageOf>;
29
+ for await (const ev of events) {
30
+ if (ev.type === "text") text += ev.content;
31
+ else if (ev.type === "error") throw new Error(ev.message);
32
+ else if (ev.type === "done") usage = usageOf(ev);
33
+ }
34
+ return messageResponse(text, body.model || providerId, usage);
35
+ } finally {
36
+ await cleanup();
37
+ }
38
+ }
39
+
40
+ /** Streaming: map assistant text onto the Anthropic SSE event sequence. */
41
+ async function runStreaming(providerId: string, body: AnthropicMessagesBody): Promise<Response> {
42
+ // Started before the stream so a setup failure is still a JSON error the
43
+ // client can read, rather than an SSE stream that opens and immediately dies.
44
+ const { events, cleanup } = await startTurn(providerId, body);
45
+ const model = body.model || providerId;
46
+
47
+ const readable = new ReadableStream<Uint8Array>({
48
+ async start(controller) {
49
+ const stream = new MessageStreamWriter(controller, model);
50
+ stream.open();
51
+ let usage: ReturnType<typeof usageOf>;
52
+ try {
53
+ for await (const ev of events) {
54
+ if (ev.type === "text") stream.text(ev.content);
55
+ else if (ev.type === "error") throw new Error(ev.message);
56
+ else if (ev.type === "done") usage = usageOf(ev);
57
+ }
58
+ stream.close(usage);
59
+ } catch (e) {
60
+ // The stream already carries a 200, so the error has to ride inside it.
61
+ stream.text(`\n\nError: ${(e as Error).message}`);
62
+ stream.close(usage);
63
+ } finally {
64
+ await cleanup();
65
+ }
66
+ },
67
+ async cancel() {
68
+ await cleanup();
69
+ },
70
+ });
71
+
72
+ return new Response(readable, { headers: ANTHROPIC_SSE_HEADERS });
73
+ }
74
+
75
+ /**
76
+ * Entry point for `POST /proxy/<provider>/v1/messages`.
77
+ * Never throws: every failure becomes an Anthropic-shaped error response.
78
+ */
79
+ export async function forwardAgentMessages(
80
+ providerId: string,
81
+ body: AnthropicMessagesBody,
82
+ ): Promise<Response> {
83
+ if (!resolveProvider(providerId)) {
84
+ return anthropicError(404, `Unknown provider "${providerId}". Available: ${proxyableProviderIds().join(", ") || "none"}`);
85
+ }
86
+ try {
87
+ return body.stream
88
+ ? await runStreaming(providerId, body)
89
+ : await runNonStreaming(providerId, body);
90
+ } catch (e) {
91
+ return anthropicError(502, (e as Error).message);
92
+ }
93
+ }
@@ -0,0 +1,100 @@
1
+ /**
2
+ * Agent → OpenAI Chat Completions, for `POST /proxy/<provider>/v1/chat/completions`.
3
+ *
4
+ * An OpenAI client points its `baseURL` at `…/proxy/codex/v1`; the provider comes
5
+ * from the URL and the model from the request body. Session lifetime, sandboxing
6
+ * and timeouts live in `proxy-agent-turn.ts`, shared with the Anthropic-format
7
+ * endpoint so the two cannot drift apart.
8
+ *
9
+ * Unlike the Claude-only bridge (`proxy-openai-bridge.ts`, a single SDK query),
10
+ * this drives a real agent session: a turn may run the provider's own tools
11
+ * before answering. Tool traffic has no place in the OpenAI wire format, so only
12
+ * assistant text reaches the caller.
13
+ */
14
+ import {
15
+ startAgentTurn, usageOf, resolveProvider, proxyableProviderIds,
16
+ } from "./proxy-agent-turn.ts";
17
+ import {
18
+ buildPromptFromOpenAiMessages, completionResponse, openAiError,
19
+ ChunkWriter, SSE_HEADERS, type OpenAiChatBody,
20
+ } from "./proxy-openai-format.ts";
21
+
22
+ /** Open the turn described by an OpenAI-format body. */
23
+ function startTurn(providerId: string, body: OpenAiChatBody) {
24
+ const { prompt, systemPrompt } = buildPromptFromOpenAiMessages(body);
25
+ return startAgentTurn(providerId, { prompt, systemPrompt, model: body.model });
26
+ }
27
+
28
+ /** Non-streaming: drain the turn, return one `chat.completion`. */
29
+ async function runNonStreaming(providerId: string, body: OpenAiChatBody): Promise<Response> {
30
+ const { events, cleanup } = await startTurn(providerId, body);
31
+ try {
32
+ let content = "";
33
+ let usage: ReturnType<typeof usageOf>;
34
+ for await (const ev of events) {
35
+ if (ev.type === "text") content += ev.content;
36
+ else if (ev.type === "error") throw new Error(ev.message);
37
+ else if (ev.type === "done") usage = usageOf(ev);
38
+ }
39
+ return completionResponse(content, body.model || providerId, usage && {
40
+ promptTokens: usage.inputTokens, completionTokens: usage.outputTokens,
41
+ });
42
+ } finally {
43
+ await cleanup();
44
+ }
45
+ }
46
+
47
+ /** Streaming: map assistant text onto `chat.completion.chunk` frames. */
48
+ async function runStreaming(providerId: string, body: OpenAiChatBody): Promise<Response> {
49
+ // Started before the stream so a setup failure is still a JSON error the
50
+ // client can read, rather than an SSE stream that opens and immediately dies.
51
+ const { events, cleanup } = await startTurn(providerId, body);
52
+ const model = body.model || providerId;
53
+
54
+ const readable = new ReadableStream<Uint8Array>({
55
+ async start(controller) {
56
+ const chunks = new ChunkWriter(controller, model);
57
+ chunks.open();
58
+ try {
59
+ for await (const ev of events) {
60
+ if (ev.type === "text") chunks.text(ev.content);
61
+ else if (ev.type === "error") throw new Error(ev.message);
62
+ }
63
+ chunks.close();
64
+ } catch (e) {
65
+ // The stream already carries a 200, so the error has to ride inside it.
66
+ chunks.text(`\n\nError: ${(e as Error).message}`);
67
+ chunks.close();
68
+ } finally {
69
+ await cleanup();
70
+ }
71
+ },
72
+ async cancel() {
73
+ await cleanup();
74
+ },
75
+ });
76
+
77
+ return new Response(readable, { headers: SSE_HEADERS });
78
+ }
79
+
80
+ /**
81
+ * Entry point for `POST /proxy/<provider>/v1/chat/completions`.
82
+ * Never throws: every failure becomes an OpenAI-shaped error response.
83
+ */
84
+ export async function forwardAgentChatCompletions(
85
+ providerId: string,
86
+ body: OpenAiChatBody,
87
+ ): Promise<Response> {
88
+ if (!resolveProvider(providerId)) {
89
+ return openAiError(404, `Unknown provider "${providerId}". Available: ${proxyableProviderIds().join(", ") || "none"}`);
90
+ }
91
+ try {
92
+ return body.stream
93
+ ? await runStreaming(providerId, body)
94
+ : await runNonStreaming(providerId, body);
95
+ } catch (e) {
96
+ return openAiError(502, (e as Error).message);
97
+ }
98
+ }
99
+
100
+ export { listProviderModels } from "./proxy-agent-turn.ts";