actoviq-agent-sdk 0.4.21 → 0.4.22

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 (278) hide show
  1. package/README-zh.md +50 -113
  2. package/README.md +51 -164
  3. package/assets/hadamard-icon.ico +0 -0
  4. package/assets/hadamard-icon.png +0 -0
  5. package/bin/hadamard-gui.js +31 -12
  6. package/dist/src/codeIntel/codeIntelligenceService.d.ts +16 -0
  7. package/dist/src/codeIntel/codeIntelligenceService.d.ts.map +1 -1
  8. package/dist/src/codeIntel/codeIntelligenceService.js +87 -2
  9. package/dist/src/codeIntel/codeIntelligenceService.js.map +1 -1
  10. package/dist/src/codeIntel/codeIntelligenceTools.d.ts.map +1 -1
  11. package/dist/src/codeIntel/codeIntelligenceTools.js +6 -0
  12. package/dist/src/codeIntel/codeIntelligenceTools.js.map +1 -1
  13. package/dist/src/codeIntel/index.d.ts +1 -0
  14. package/dist/src/codeIntel/index.d.ts.map +1 -1
  15. package/dist/src/codeIntel/index.js +1 -0
  16. package/dist/src/codeIntel/index.js.map +1 -1
  17. package/dist/src/codeIntel/languageServerPresets.d.ts +13 -0
  18. package/dist/src/codeIntel/languageServerPresets.d.ts.map +1 -0
  19. package/dist/src/codeIntel/languageServerPresets.js +51 -0
  20. package/dist/src/codeIntel/languageServerPresets.js.map +1 -0
  21. package/dist/src/codeIntel/languageServerRegistry.d.ts +1 -0
  22. package/dist/src/codeIntel/languageServerRegistry.d.ts.map +1 -1
  23. package/dist/src/codeIntel/languageServerRegistry.js +2 -2
  24. package/dist/src/codeIntel/languageServerRegistry.js.map +1 -1
  25. package/dist/src/config/resolveRuntimeConfig.d.ts.map +1 -1
  26. package/dist/src/config/resolveRuntimeConfig.js +5 -0
  27. package/dist/src/config/resolveRuntimeConfig.js.map +1 -1
  28. package/dist/src/extensions/builtInExtensions.d.ts +61 -0
  29. package/dist/src/extensions/builtInExtensions.d.ts.map +1 -0
  30. package/dist/src/extensions/builtInExtensions.js +218 -0
  31. package/dist/src/extensions/builtInExtensions.js.map +1 -0
  32. package/dist/src/extensions/filterOutputPolicy.d.ts +21 -0
  33. package/dist/src/extensions/filterOutputPolicy.d.ts.map +1 -0
  34. package/dist/src/extensions/filterOutputPolicy.js +121 -0
  35. package/dist/src/extensions/filterOutputPolicy.js.map +1 -0
  36. package/dist/src/extensions/securityPolicy.d.ts +27 -0
  37. package/dist/src/extensions/securityPolicy.d.ts.map +1 -0
  38. package/dist/src/extensions/securityPolicy.js +238 -0
  39. package/dist/src/extensions/securityPolicy.js.map +1 -0
  40. package/dist/src/extensions/sessionCostTracker.d.ts +63 -0
  41. package/dist/src/extensions/sessionCostTracker.d.ts.map +1 -0
  42. package/dist/src/extensions/sessionCostTracker.js +213 -0
  43. package/dist/src/extensions/sessionCostTracker.js.map +1 -0
  44. package/dist/src/extensions/taskNotifications.d.ts +31 -0
  45. package/dist/src/extensions/taskNotifications.d.ts.map +1 -0
  46. package/dist/src/extensions/taskNotifications.js +50 -0
  47. package/dist/src/extensions/taskNotifications.js.map +1 -0
  48. package/dist/src/extensions/usageBar.d.ts +35 -0
  49. package/dist/src/extensions/usageBar.d.ts.map +1 -0
  50. package/dist/src/extensions/usageBar.js +67 -0
  51. package/dist/src/extensions/usageBar.js.map +1 -0
  52. package/dist/src/gui/contextRailStore.d.ts +13 -0
  53. package/dist/src/gui/contextRailStore.d.ts.map +1 -1
  54. package/dist/src/gui/contextRailStore.js +16 -0
  55. package/dist/src/gui/contextRailStore.js.map +1 -1
  56. package/dist/src/gui/electronMain.js +45 -5
  57. package/dist/src/gui/electronMain.js.map +1 -1
  58. package/dist/src/gui/guiDesignClientScript.d.ts.map +1 -1
  59. package/dist/src/gui/guiDesignClientScript.js +31 -0
  60. package/dist/src/gui/guiDesignClientScript.js.map +1 -1
  61. package/dist/src/gui/guiUsageRoutingAssets.d.ts +7 -0
  62. package/dist/src/gui/guiUsageRoutingAssets.d.ts.map +1 -0
  63. package/dist/src/gui/guiUsageRoutingAssets.js +509 -0
  64. package/dist/src/gui/guiUsageRoutingAssets.js.map +1 -0
  65. package/dist/src/gui/guiUsageRoutingHttpController.d.ts +11 -0
  66. package/dist/src/gui/guiUsageRoutingHttpController.d.ts.map +1 -0
  67. package/dist/src/gui/guiUsageRoutingHttpController.js +125 -0
  68. package/dist/src/gui/guiUsageRoutingHttpController.js.map +1 -0
  69. package/dist/src/gui/guiUsageRoutingRuntime.d.ts +36 -0
  70. package/dist/src/gui/guiUsageRoutingRuntime.d.ts.map +1 -0
  71. package/dist/src/gui/guiUsageRoutingRuntime.js +98 -0
  72. package/dist/src/gui/guiUsageRoutingRuntime.js.map +1 -0
  73. package/dist/src/gui/hadamardGui.d.ts +5 -0
  74. package/dist/src/gui/hadamardGui.d.ts.map +1 -1
  75. package/dist/src/gui/hadamardGui.js +159 -16
  76. package/dist/src/gui/hadamardGui.js.map +1 -1
  77. package/dist/src/gui/hadamardGuiAssets.d.ts.map +1 -1
  78. package/dist/src/gui/hadamardGuiAssets.js +297 -41
  79. package/dist/src/gui/hadamardGuiAssets.js.map +1 -1
  80. package/dist/src/index.d.ts +32 -2
  81. package/dist/src/index.d.ts.map +1 -1
  82. package/dist/src/index.js +16 -1
  83. package/dist/src/index.js.map +1 -1
  84. package/dist/src/keyway/electronSecretStore.d.ts +37 -0
  85. package/dist/src/keyway/electronSecretStore.d.ts.map +1 -0
  86. package/dist/src/keyway/electronSecretStore.js +105 -0
  87. package/dist/src/keyway/electronSecretStore.js.map +1 -0
  88. package/dist/src/keyway/embeddedKeyway.d.ts +34 -0
  89. package/dist/src/keyway/embeddedKeyway.d.ts.map +1 -0
  90. package/dist/src/keyway/embeddedKeyway.js +81 -0
  91. package/dist/src/keyway/embeddedKeyway.js.map +1 -0
  92. package/dist/src/keyway/keywayLoopbackGateway.d.ts +30 -0
  93. package/dist/src/keyway/keywayLoopbackGateway.d.ts.map +1 -0
  94. package/dist/src/keyway/keywayLoopbackGateway.js +71 -0
  95. package/dist/src/keyway/keywayLoopbackGateway.js.map +1 -0
  96. package/dist/src/keyway/keywayMigrationService.d.ts +44 -0
  97. package/dist/src/keyway/keywayMigrationService.d.ts.map +1 -0
  98. package/dist/src/keyway/keywayMigrationService.js +219 -0
  99. package/dist/src/keyway/keywayMigrationService.js.map +1 -0
  100. package/dist/src/keyway/keywayModelApi.d.ts +24 -0
  101. package/dist/src/keyway/keywayModelApi.d.ts.map +1 -0
  102. package/dist/src/keyway/keywayModelApi.js +106 -0
  103. package/dist/src/keyway/keywayModelApi.js.map +1 -0
  104. package/dist/src/keyway/keywayPorts.d.ts +189 -0
  105. package/dist/src/keyway/keywayPorts.d.ts.map +1 -0
  106. package/dist/src/keyway/keywayPorts.js +2 -0
  107. package/dist/src/keyway/keywayPorts.js.map +1 -0
  108. package/dist/src/keyway/keywayProviderExecutor.d.ts +30 -0
  109. package/dist/src/keyway/keywayProviderExecutor.d.ts.map +1 -0
  110. package/dist/src/keyway/keywayProviderExecutor.js +257 -0
  111. package/dist/src/keyway/keywayProviderExecutor.js.map +1 -0
  112. package/dist/src/keyway/routingComposition.d.ts +18 -0
  113. package/dist/src/keyway/routingComposition.d.ts.map +1 -0
  114. package/dist/src/keyway/routingComposition.js +19 -0
  115. package/dist/src/keyway/routingComposition.js.map +1 -0
  116. package/dist/src/keyway/usageRoutingAdminService.d.ts +86 -0
  117. package/dist/src/keyway/usageRoutingAdminService.d.ts.map +1 -0
  118. package/dist/src/keyway/usageRoutingAdminService.js +384 -0
  119. package/dist/src/keyway/usageRoutingAdminService.js.map +1 -0
  120. package/dist/src/nativeCli/crushManagedClient.d.ts +84 -0
  121. package/dist/src/nativeCli/crushManagedClient.d.ts.map +1 -0
  122. package/dist/src/nativeCli/crushManagedClient.js +978 -0
  123. package/dist/src/nativeCli/crushManagedClient.js.map +1 -0
  124. package/dist/src/nativeCli/crushNativeCliClient.d.ts +19 -0
  125. package/dist/src/nativeCli/crushNativeCliClient.d.ts.map +1 -0
  126. package/dist/src/nativeCli/crushNativeCliClient.js +243 -0
  127. package/dist/src/nativeCli/crushNativeCliClient.js.map +1 -0
  128. package/dist/src/nativeCli/keywayNativeCliAdapter.d.ts +31 -0
  129. package/dist/src/nativeCli/keywayNativeCliAdapter.d.ts.map +1 -0
  130. package/dist/src/nativeCli/keywayNativeCliAdapter.js +35 -0
  131. package/dist/src/nativeCli/keywayNativeCliAdapter.js.map +1 -0
  132. package/dist/src/nativeCli/nativeCliAuth.d.ts +24 -0
  133. package/dist/src/nativeCli/nativeCliAuth.d.ts.map +1 -0
  134. package/dist/src/nativeCli/nativeCliAuth.js +327 -0
  135. package/dist/src/nativeCli/nativeCliAuth.js.map +1 -0
  136. package/dist/src/nativeCli/nativeCliClient.d.ts +16 -0
  137. package/dist/src/nativeCli/nativeCliClient.d.ts.map +1 -0
  138. package/dist/src/nativeCli/nativeCliClient.js +575 -0
  139. package/dist/src/nativeCli/nativeCliClient.js.map +1 -0
  140. package/dist/src/nativeCli/nativeCliCodewhaleProtocol.d.ts +18 -0
  141. package/dist/src/nativeCli/nativeCliCodewhaleProtocol.d.ts.map +1 -0
  142. package/dist/src/nativeCli/nativeCliCodewhaleProtocol.js +376 -0
  143. package/dist/src/nativeCli/nativeCliCodewhaleProtocol.js.map +1 -0
  144. package/dist/src/nativeCli/nativeCliContracts.d.ts +22 -0
  145. package/dist/src/nativeCli/nativeCliContracts.d.ts.map +1 -0
  146. package/dist/src/nativeCli/nativeCliContracts.js +2 -0
  147. package/dist/src/nativeCli/nativeCliContracts.js.map +1 -0
  148. package/dist/src/nativeCli/nativeCliCursorProtocol.d.ts +7 -0
  149. package/dist/src/nativeCli/nativeCliCursorProtocol.d.ts.map +1 -0
  150. package/dist/src/nativeCli/nativeCliCursorProtocol.js +210 -0
  151. package/dist/src/nativeCli/nativeCliCursorProtocol.js.map +1 -0
  152. package/dist/src/nativeCli/nativeCliEnvironment.d.ts +3 -0
  153. package/dist/src/nativeCli/nativeCliEnvironment.d.ts.map +1 -0
  154. package/dist/src/nativeCli/nativeCliEnvironment.js +17 -0
  155. package/dist/src/nativeCli/nativeCliEnvironment.js.map +1 -0
  156. package/dist/src/nativeCli/nativeCliExecResolver.d.ts +26 -0
  157. package/dist/src/nativeCli/nativeCliExecResolver.d.ts.map +1 -0
  158. package/dist/src/nativeCli/nativeCliExecResolver.js +152 -0
  159. package/dist/src/nativeCli/nativeCliExecResolver.js.map +1 -0
  160. package/dist/src/nativeCli/nativeCliPiProtocol.d.ts +15 -0
  161. package/dist/src/nativeCli/nativeCliPiProtocol.d.ts.map +1 -0
  162. package/dist/src/nativeCli/nativeCliPiProtocol.js +329 -0
  163. package/dist/src/nativeCli/nativeCliPiProtocol.js.map +1 -0
  164. package/dist/src/nativeCli/nativeCliProcessTree.d.ts +3 -0
  165. package/dist/src/nativeCli/nativeCliProcessTree.d.ts.map +1 -0
  166. package/dist/src/nativeCli/nativeCliProcessTree.js +104 -0
  167. package/dist/src/nativeCli/nativeCliProcessTree.js.map +1 -0
  168. package/dist/src/nativeCli/reasonixAcpSession.d.ts +35 -0
  169. package/dist/src/nativeCli/reasonixAcpSession.d.ts.map +1 -0
  170. package/dist/src/nativeCli/reasonixAcpSession.js +472 -0
  171. package/dist/src/nativeCli/reasonixAcpSession.js.map +1 -0
  172. package/dist/src/nativeCli/reasonixManagedClient.d.ts +56 -0
  173. package/dist/src/nativeCli/reasonixManagedClient.d.ts.map +1 -0
  174. package/dist/src/nativeCli/reasonixManagedClient.js +293 -0
  175. package/dist/src/nativeCli/reasonixManagedClient.js.map +1 -0
  176. package/dist/src/nativeCli/reasonixNativeCliClient.d.ts +20 -0
  177. package/dist/src/nativeCli/reasonixNativeCliClient.d.ts.map +1 -0
  178. package/dist/src/nativeCli/reasonixNativeCliClient.js +317 -0
  179. package/dist/src/nativeCli/reasonixNativeCliClient.js.map +1 -0
  180. package/dist/src/parity/bridgeExecResolver.d.ts +2 -25
  181. package/dist/src/parity/bridgeExecResolver.d.ts.map +1 -1
  182. package/dist/src/parity/bridgeExecResolver.js +2 -151
  183. package/dist/src/parity/bridgeExecResolver.js.map +1 -1
  184. package/dist/src/parity/bridgeProcessTree.d.ts +2 -2
  185. package/dist/src/parity/bridgeProcessTree.d.ts.map +1 -1
  186. package/dist/src/parity/bridgeProcessTree.js +2 -82
  187. package/dist/src/parity/bridgeProcessTree.js.map +1 -1
  188. package/dist/src/parity/crushManagedClient.d.ts +2 -83
  189. package/dist/src/parity/crushManagedClient.d.ts.map +1 -1
  190. package/dist/src/parity/crushManagedClient.js +2 -977
  191. package/dist/src/parity/crushManagedClient.js.map +1 -1
  192. package/dist/src/parity/externalCliAuth.d.ts +2 -22
  193. package/dist/src/parity/externalCliAuth.d.ts.map +1 -1
  194. package/dist/src/parity/externalCliAuth.js +2 -295
  195. package/dist/src/parity/externalCliAuth.js.map +1 -1
  196. package/dist/src/parity/hadamardBridgeSdk.d.ts.map +1 -1
  197. package/dist/src/parity/hadamardBridgeSdk.js +14 -1
  198. package/dist/src/parity/hadamardBridgeSdk.js.map +1 -1
  199. package/dist/src/parity/nativeCliBridgeAdapter.d.ts +6 -0
  200. package/dist/src/parity/nativeCliBridgeAdapter.d.ts.map +1 -0
  201. package/dist/src/parity/nativeCliBridgeAdapter.js +38 -0
  202. package/dist/src/parity/nativeCliBridgeAdapter.js.map +1 -0
  203. package/dist/src/parity/reasonixAcpSession.d.ts +2 -34
  204. package/dist/src/parity/reasonixAcpSession.d.ts.map +1 -1
  205. package/dist/src/parity/reasonixAcpSession.js +2 -471
  206. package/dist/src/parity/reasonixAcpSession.js.map +1 -1
  207. package/dist/src/parity/reasonixManagedClient.d.ts +2 -55
  208. package/dist/src/parity/reasonixManagedClient.d.ts.map +1 -1
  209. package/dist/src/parity/reasonixManagedClient.js +2 -292
  210. package/dist/src/parity/reasonixManagedClient.js.map +1 -1
  211. package/dist/src/provider/openai-client.js +2 -2
  212. package/dist/src/provider/openai-client.js.map +1 -1
  213. package/dist/src/provider/openai-model-api.d.ts.map +1 -1
  214. package/dist/src/provider/openai-model-api.js +5 -1
  215. package/dist/src/provider/openai-model-api.js.map +1 -1
  216. package/dist/src/runtime/agentClient.d.ts +14 -1
  217. package/dist/src/runtime/agentClient.d.ts.map +1 -1
  218. package/dist/src/runtime/agentClient.js +60 -6
  219. package/dist/src/runtime/agentClient.js.map +1 -1
  220. package/dist/src/runtime/toolPolicyPipeline.d.ts +32 -1
  221. package/dist/src/runtime/toolPolicyPipeline.d.ts.map +1 -1
  222. package/dist/src/runtime/toolPolicyPipeline.js +43 -3
  223. package/dist/src/runtime/toolPolicyPipeline.js.map +1 -1
  224. package/dist/src/tui/hadamardTuiController.d.ts.map +1 -1
  225. package/dist/src/tui/hadamardTuiController.js +62 -8
  226. package/dist/src/tui/hadamardTuiController.js.map +1 -1
  227. package/dist/src/tui/tuiBasicCommandHandler.d.ts +4 -0
  228. package/dist/src/tui/tuiBasicCommandHandler.d.ts.map +1 -1
  229. package/dist/src/tui/tuiBasicCommandHandler.js +23 -2
  230. package/dist/src/tui/tuiBasicCommandHandler.js.map +1 -1
  231. package/dist/src/tui/tuiCatalogCommandHandler.d.ts +2 -0
  232. package/dist/src/tui/tuiCatalogCommandHandler.d.ts.map +1 -1
  233. package/dist/src/tui/tuiCatalogCommandHandler.js +10 -1
  234. package/dist/src/tui/tuiCatalogCommandHandler.js.map +1 -1
  235. package/dist/src/tui/tuiUsageRoutingRuntime.d.ts +22 -0
  236. package/dist/src/tui/tuiUsageRoutingRuntime.d.ts.map +1 -0
  237. package/dist/src/tui/tuiUsageRoutingRuntime.js +81 -0
  238. package/dist/src/tui/tuiUsageRoutingRuntime.js.map +1 -0
  239. package/dist/src/types.d.ts +16 -0
  240. package/dist/src/types.d.ts.map +1 -1
  241. package/dist/src/types.js.map +1 -1
  242. package/dist/src/ui/commandSurface.d.ts.map +1 -1
  243. package/dist/src/ui/commandSurface.js +45 -1
  244. package/dist/src/ui/commandSurface.js.map +1 -1
  245. package/dist/src/ui/extensionsCommandView.d.ts +18 -0
  246. package/dist/src/ui/extensionsCommandView.d.ts.map +1 -0
  247. package/dist/src/ui/extensionsCommandView.js +82 -0
  248. package/dist/src/ui/extensionsCommandView.js.map +1 -0
  249. package/dist/src/ui/lspCommandView.d.ts +17 -0
  250. package/dist/src/ui/lspCommandView.d.ts.map +1 -0
  251. package/dist/src/ui/lspCommandView.js +25 -0
  252. package/dist/src/ui/lspCommandView.js.map +1 -0
  253. package/dist/src/ui/usageRoutingCommand.d.ts +13 -0
  254. package/dist/src/ui/usageRoutingCommand.d.ts.map +1 -0
  255. package/dist/src/ui/usageRoutingCommand.js +224 -0
  256. package/dist/src/ui/usageRoutingCommand.js.map +1 -0
  257. package/dist/src/usage/budgetService.d.ts +28 -0
  258. package/dist/src/usage/budgetService.d.ts.map +1 -0
  259. package/dist/src/usage/budgetService.js +50 -0
  260. package/dist/src/usage/budgetService.js.map +1 -0
  261. package/dist/src/usage/contracts.d.ts +149 -0
  262. package/dist/src/usage/contracts.d.ts.map +1 -0
  263. package/dist/src/usage/contracts.js +2 -0
  264. package/dist/src/usage/contracts.js.map +1 -0
  265. package/dist/src/usage/index.d.ts +7 -0
  266. package/dist/src/usage/index.d.ts.map +1 -0
  267. package/dist/src/usage/index.js +4 -0
  268. package/dist/src/usage/index.js.map +1 -0
  269. package/dist/src/usage/usageLedger.d.ts +19 -0
  270. package/dist/src/usage/usageLedger.d.ts.map +1 -0
  271. package/dist/src/usage/usageLedger.js +389 -0
  272. package/dist/src/usage/usageLedger.js.map +1 -0
  273. package/dist/src/usage/usageQueryService.d.ts +12 -0
  274. package/dist/src/usage/usageQueryService.d.ts.map +1 -0
  275. package/dist/src/usage/usageQueryService.js +29 -0
  276. package/dist/src/usage/usageQueryService.js.map +1 -0
  277. package/package.json +14 -2
  278. package/scripts/prepare-gui-launcher.mjs +62 -0
@@ -0,0 +1,509 @@
1
+ export const usageRoutingConfigurationHeaderHtml = `
2
+ <header class="region-header">
3
+ <div class="region-titles">
4
+ <h1>Configuration</h1>
5
+ <p>Configure models, providers, and local API or CLI runtimes</p>
6
+ </div>
7
+ <div class="region-actions" role="tablist" aria-label="Configuration sections">
8
+ <button type="button" id="configurationModelsTab" class="pill-btn active" role="tab" aria-selected="true">Models &amp; runtimes</button>
9
+ <button type="button" id="configurationUsageTab" class="pill-btn" role="tab" aria-selected="false">Usage &amp; Routing</button>
10
+ <button type="button" id="bridgeNewConfig" class="pill-btn primary">+ New config</button>
11
+ </div>
12
+ </header>
13
+ `;
14
+ export const usageRoutingConfigurationPageHtml = `
15
+ <div class="region-body bridge-region-body configuration-usage-page" id="configurationUsagePage" hidden>
16
+ <div id="configurationUsageSlot"></div>
17
+ </div>
18
+ `;
19
+ export const usageRoutingHtml = `
20
+ <section class="settings-panel" data-configuration-usage-panel>
21
+ <h1>Usage &amp; Routing</h1>
22
+ <p class="muted">One local control center for the token ledger, limits, managed API keys, Gateway Routes, and native CLI login status.</p>
23
+ <div class="usage-routing-tabs" role="tablist" aria-label="Usage and routing pages">
24
+ <button type="button" class="active" data-usage-routing-page-tab="overview">Overview</button>
25
+ <button type="button" data-usage-routing-page-tab="ledger">Ledger</button>
26
+ <button type="button" data-usage-routing-page-tab="limits">Limits</button>
27
+ <button type="button" data-usage-routing-page-tab="keys">API Keys</button>
28
+ <button type="button" data-usage-routing-page-tab="routes">Routes</button>
29
+ </div>
30
+ <div class="settings-group usage-filter-bar">
31
+ <label>Range<select id="usageRange"><option value="7">Last 7 days</option><option value="30" selected>Last 30 days</option><option value="0">All time</option></select></label>
32
+ <label>Provider<input id="usageProviderFilter" autocomplete="off" placeholder="All providers"></label>
33
+ <label>Model<input id="usageModelFilter" autocomplete="off" placeholder="All models"></label>
34
+ <label>Status<select id="usageStatusFilter"><option value="">All statuses</option><option value="succeeded">Succeeded</option><option value="failed">Failed</option><option value="denied">Denied</option><option value="cancelled">Cancelled</option></select></label>
35
+ <button type="button" id="usageRoutingRefresh" class="secondary-btn">Refresh</button>
36
+ </div>
37
+ <p id="usageRoutingStatus" class="muted" role="status">Open this tab to load local usage and routing data.</p>
38
+
39
+ <div class="usage-routing-page" data-usage-routing-page="overview">
40
+ <div class="usage-kpis">
41
+ <article><span>Requests</span><strong id="usageKpiRequests">—</strong></article>
42
+ <article><span>Total tokens</span><strong id="usageKpiTokens">—</strong></article>
43
+ <article><span>Cache read</span><strong id="usageKpiCache">—</strong></article>
44
+ <article><span>Estimated cost</span><strong id="usageKpiCost">—</strong></article>
45
+ </div>
46
+ <div class="settings-group">
47
+ <div class="settings-group-head"><div><h2>Token trend</h2><p class="muted">Daily totals under the active filter.</p></div></div>
48
+ <svg id="usageTrendChart" class="usage-trend-chart" viewBox="0 0 720 220" role="img" aria-label="No usage trend data"><desc>Daily token usage trend.</desc></svg>
49
+ <div id="usageTrendFallback" class="sr-only" aria-live="polite"></div>
50
+ </div>
51
+ <div class="settings-group"><h2>Provider breakdown</h2><div id="usageProviderBreakdown" class="settings-card-list compact"></div></div>
52
+ </div>
53
+
54
+ <div class="usage-routing-page" data-usage-routing-page="ledger" hidden>
55
+ <div class="settings-group usage-table-wrap">
56
+ <table class="usage-table"><thead><tr><th>Time</th><th>Source</th><th>Provider / route</th><th>Model</th><th>Input</th><th>Output</th><th>Cache</th><th>Cost</th><th>Status</th></tr></thead><tbody id="usageLedgerBody"></tbody></table>
57
+ <p id="usageLedgerEmpty" class="muted">No matching ledger entries.</p>
58
+ <div class="settings-action-row"><button type="button" id="usageLedgerPrev" class="secondary-btn">Previous</button><span id="usageLedgerPage" class="muted">Page 1</span><button type="button" id="usageLedgerNext" class="secondary-btn">Next</button></div>
59
+ </div>
60
+ </div>
61
+
62
+ <div class="usage-routing-page" data-usage-routing-page="limits" hidden>
63
+ <div class="settings-group"><h2>Usage limits</h2><div id="usageBudgetList" class="settings-card-list"></div></div>
64
+ <div class="settings-group"><h2>Add or update a limit</h2><div class="usage-admin-grid">
65
+ <label>ID<input id="usageBudgetId" autocomplete="off" placeholder="budget.monthly"></label>
66
+ <label>Scope<select id="usageBudgetScope"><option value="global">Global</option><option value="provider">Provider</option><option value="credential">Credential</option><option value="route">Route</option><option value="model">Model</option></select></label>
67
+ <label>Scope ID<input id="usageBudgetScopeId" autocomplete="off" placeholder="Not used for global"></label>
68
+ <label>Period<select id="usageBudgetPeriod"><option value="daily">Daily</option><option value="monthly" selected>Monthly</option><option value="lifetime">Lifetime</option></select></label>
69
+ <label>Metric<select id="usageBudgetMetric"><option value="totalTokens">Tokens</option><option value="requests">Requests</option><option value="costUsd">USD cost</option></select></label>
70
+ <label>Limit<input id="usageBudgetLimit" type="number" min="0" step="any" value="1000000"></label>
71
+ <label>Action<select id="usageBudgetAction"><option value="warn">Warn</option><option value="deny">Deny</option><option value="fallbackRoute">Fallback route</option></select></label>
72
+ <label>Fallback route ID<input id="usageBudgetFallback" autocomplete="off"></label>
73
+ </div><div class="settings-action-row"><button type="button" id="usageBudgetSave" class="primary">Save limit</button></div></div>
74
+ </div>
75
+
76
+ <div class="usage-routing-page" data-usage-routing-page="keys" hidden>
77
+ <div class="settings-group"><h2>Managed API credentials</h2><p class="muted">Secrets are write-only and encrypted by the desktop host. Native CLI OAuth/session data is never imported here.</p><div id="usageCredentialList" class="settings-card-list"></div></div>
78
+ <div class="settings-group"><h2>Add or rotate a credential</h2><div class="usage-admin-grid">
79
+ <label>ID<input id="usageCredentialId" autocomplete="off" placeholder="credential.provider.primary"></label>
80
+ <label>Provider ID<input id="usageCredentialProvider" autocomplete="off" placeholder="provider-id"></label>
81
+ <label>Label<input id="usageCredentialLabel" autocomplete="off" placeholder="Primary"></label>
82
+ <label>Priority<input id="usageCredentialPriority" type="number" min="0" value="0"></label>
83
+ <label>Weight<input id="usageCredentialWeight" type="number" min="1" value="1"></label>
84
+ <label>New secret<input id="usageCredentialSecret" type="password" autocomplete="new-password" placeholder="Leave blank to keep existing"></label>
85
+ </div><div class="settings-action-row"><button type="button" id="usageCredentialSave" class="primary">Save credential</button></div></div>
86
+ <div class="settings-group"><h2>Migration</h2><p class="muted">Preview first. Bridge API keys are copied into write-only storage only on Apply; native CLI OAuth/session secrets are never read. KeywayExportV1 files contain metadata and usage, not secrets.</p>
87
+ <div class="settings-action-row"><button type="button" id="usageBridgeMigrationPreview" class="secondary-btn">Preview bridge configs</button><button type="button" id="usageBridgeMigrationApply" class="secondary-btn">Apply ready configs</button></div>
88
+ <label class="inline-field">KeywayExportV1 file<input id="usagePortableMigrationFile" autocomplete="off" placeholder="C:\\path\\to\\keyway-export-v1.json"></label>
89
+ <div class="settings-action-row"><button type="button" id="usagePortableMigrationPreview" class="secondary-btn">Preview file</button><button type="button" id="usagePortableMigrationApply" class="secondary-btn">Import transactionally</button></div>
90
+ </div>
91
+ </div>
92
+
93
+ <div class="usage-routing-page" data-usage-routing-page="routes" hidden>
94
+ <div class="settings-group"><h2>Authenticated loopback gateway</h2><p id="usageGatewayStatus" class="muted">Stopped. Embedded in-process routing remains available.</p><div class="settings-action-row"><button type="button" id="usageGatewayStart" class="secondary-btn">Start</button><button type="button" id="usageGatewayStop" class="secondary-btn">Stop</button></div><p class="muted">Binds to 127.0.0.1 only. A client key is shown once after startup and is never returned by status.</p></div>
95
+ <div class="settings-group"><h2>Execution targets</h2><div id="usageTargetList" class="settings-card-list"></div></div>
96
+ <div class="settings-group"><h2>Add or update a target</h2><div class="usage-admin-grid">
97
+ <label>ID<input id="usageTargetId" autocomplete="off" placeholder="target.ark"></label>
98
+ <label>Kind<select id="usageTargetKind"><option value="managed-api">Managed API</option><option value="native-cli">Native CLI login</option></select></label>
99
+ <label>Provider / runtime<input id="usageTargetProvider" autocomplete="off" placeholder="ark or claude"></label>
100
+ <label>Protocol<select id="usageTargetProtocol"><option value="openai">OpenAI-compatible</option><option value="anthropic">Anthropic-compatible</option></select></label>
101
+ <label>Base URL / profile<input id="usageTargetLocation" autocomplete="off" placeholder="https://api.example.com/v1"></label>
102
+ </div><div class="settings-action-row"><button type="button" id="usageTargetSave" class="primary">Save target</button></div></div>
103
+ <div class="settings-group"><h2>Gateway Routes</h2><div id="usageRouteList" class="settings-card-list"></div></div>
104
+ <div class="settings-group"><h2>Add or update a route</h2><div class="usage-admin-grid">
105
+ <label>ID<input id="usageRouteId" autocomplete="off" placeholder="route.chat"></label>
106
+ <label>Alias<input id="usageRouteAlias" autocomplete="off" placeholder="chat-default"></label>
107
+ <label>Mode<select id="usageRouteMode"><option value="direct">Direct</option><option value="priority-failover">Priority failover</option></select></label>
108
+ <label class="usage-wide">Candidates<textarea id="usageRouteCandidates" rows="4" placeholder="target.ark | glm-5.2 | 0&#10;target.backup | glm-5.3 | 1"></textarea><small>One per line: target ID | upstream model | priority</small></label>
109
+ </div><div class="settings-action-row"><button type="button" id="usageRouteSave" class="primary">Save route</button></div></div>
110
+ </div>
111
+ </section>
112
+ `;
113
+ export const usageRoutingStyles = `
114
+ .usage-routing-tabs { display: flex; gap: 4px; margin: 18px 0 0; padding-bottom: 10px; overflow-x: auto; border-bottom: 1px solid var(--border); }
115
+ .configuration-usage-page { justify-content: center; }
116
+ .configuration-usage-page[hidden], #configurationModelsPage[hidden] { display: none !important; }
117
+ .configuration-usage-page .settings-panel { display: block !important; width: min(960px, 100%); max-width: 960px; }
118
+ .configuration-usage-page .settings-panel > h1 { margin-top: 2px; }
119
+ .usage-routing-tabs button { flex: 0 0 auto; border: 0; border-radius: 8px; min-height: 32px; padding: 0 12px; background: transparent; color: var(--text-2); cursor: pointer; }
120
+ .usage-routing-tabs button.active { background: color-mix(in srgb, var(--text-1) 10%, transparent); color: var(--text-1); font-weight: 600; }
121
+ .usage-routing-page[hidden] { display: none; }
122
+ .usage-filter-bar { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)) auto; align-items: end; gap: 10px; }
123
+ .usage-filter-bar label, .usage-admin-grid label { display: grid; gap: 5px; color: var(--text-2); font-size: 11.5px; }
124
+ .usage-filter-bar input, .usage-filter-bar select, .usage-admin-grid input, .usage-admin-grid select, .usage-admin-grid textarea {
125
+ width: 100%; min-width: 0; min-height: 34px; border: 1px solid var(--border-hover); border-radius: 8px; padding: 6px 9px; background: var(--bg-surface); color: var(--text-1); font: inherit;
126
+ }
127
+ .usage-kpis { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 10px; padding-top: 18px; }
128
+ .usage-kpis article { min-width: 0; padding: 14px; border: 1px solid var(--border); border-radius: 10px; background: var(--bg-surface); }
129
+ .usage-kpis span { display: block; color: var(--text-2); font-size: 11.5px; }
130
+ .usage-kpis strong { display: block; margin-top: 8px; overflow-wrap: anywhere; font-size: 20px; font-variant-numeric: tabular-nums; }
131
+ .usage-trend-chart { width: 100%; height: 220px; border: 1px solid var(--border); border-radius: 10px; background: var(--bg-surface); }
132
+ .usage-trend-chart .grid { stroke: var(--border); stroke-width: 1; }
133
+ .usage-trend-chart .area { fill: color-mix(in srgb, var(--brand) 14%, transparent); }
134
+ .usage-trend-chart .line { fill: none; stroke: var(--brand); stroke-width: 2.5; vector-effect: non-scaling-stroke; }
135
+ .usage-table-wrap { overflow-x: auto; }
136
+ .usage-table { width: 100%; min-width: 860px; border-collapse: collapse; font-size: 11.5px; }
137
+ .usage-table th, .usage-table td { padding: 9px 8px; border-bottom: 1px solid var(--border); text-align: left; white-space: nowrap; }
138
+ .usage-table th { color: var(--text-2); font-weight: 600; }
139
+ .usage-table td { font-variant-numeric: tabular-nums; }
140
+ .usage-admin-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; }
141
+ .usage-admin-grid .usage-wide { grid-column: 1 / -1; }
142
+ .usage-admin-grid textarea { resize: vertical; min-height: 88px; font-family: ui-monospace, SFMono-Regular, Consolas, monospace; }
143
+ .usage-admin-card { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 8px 14px; align-items: center; }
144
+ .usage-admin-card small { overflow-wrap: anywhere; }
145
+ .usage-admin-actions { display: flex; gap: 6px; flex-wrap: wrap; justify-content: flex-end; }
146
+ .usage-admin-actions button { min-height: 28px; padding: 0 8px; border: 1px solid var(--border); border-radius: 7px; background: var(--bg-surface); color: var(--text-1); cursor: pointer; }
147
+ .usage-admin-actions button.danger { color: var(--err); }
148
+ .sr-only { position: absolute !important; width: 1px !important; height: 1px !important; padding: 0 !important; margin: -1px !important; overflow: hidden !important; clip: rect(0,0,0,0) !important; white-space: nowrap !important; border: 0 !important; }
149
+ @media (max-width: 760px) {
150
+ .usage-filter-bar { grid-template-columns: repeat(2, minmax(0, 1fr)); }
151
+ .usage-kpis { grid-template-columns: repeat(2, minmax(0, 1fr)); }
152
+ .usage-admin-grid { grid-template-columns: 1fr; }
153
+ .usage-admin-grid .usage-wide { grid-column: auto; }
154
+ }
155
+ `;
156
+ export const usageRoutingClient = `
157
+ let usageRoutingActivePage = 'overview';
158
+ let usageLedgerOffset = 0;
159
+ const USAGE_LEDGER_PAGE_SIZE = 50;
160
+
161
+ function compactUsageNumber(value) {
162
+ const number = Number(value || 0);
163
+ return new Intl.NumberFormat(undefined, { notation: number >= 10000 ? 'compact' : 'standard', maximumFractionDigits: 1 }).format(number);
164
+ }
165
+
166
+ function usageFilterSearch(includePage) {
167
+ const search = new URLSearchParams();
168
+ const days = Number(el('usageRange')?.value || 0);
169
+ if (days > 0) search.set('from', new Date(Date.now() - days * 86400000).toISOString());
170
+ const provider = el('usageProviderFilter')?.value.trim();
171
+ const model = el('usageModelFilter')?.value.trim();
172
+ const status = el('usageStatusFilter')?.value;
173
+ if (provider) search.set('providerId', provider);
174
+ if (model) search.set('model', model);
175
+ if (status) search.set('status', status);
176
+ if (includePage) {
177
+ search.set('limit', String(USAGE_LEDGER_PAGE_SIZE));
178
+ search.set('offset', String(usageLedgerOffset));
179
+ }
180
+ return search.toString();
181
+ }
182
+
183
+ async function usageRoutingJson(path, options) {
184
+ const response = await api(path, options || {});
185
+ const payload = await response.json().catch(() => ({}));
186
+ if (!response.ok) throw new Error(payload.error || 'Usage & Routing is unavailable.');
187
+ return payload;
188
+ }
189
+
190
+ function renderUsageTrend(points) {
191
+ const svg = el('usageTrendChart');
192
+ const fallback = el('usageTrendFallback');
193
+ if (!svg) return;
194
+ const ns = 'http://www.w3.org/2000/svg';
195
+ const desc = document.createElementNS(ns, 'desc');
196
+ desc.textContent = 'Daily total token usage under the active filters.';
197
+ svg.replaceChildren(desc);
198
+ if (!Array.isArray(points) || points.length === 0) {
199
+ svg.setAttribute('aria-label', 'No token trend data for the active filter');
200
+ if (fallback) fallback.textContent = 'No token trend data for the active filter.';
201
+ return;
202
+ }
203
+ const width = 720;
204
+ const height = 220;
205
+ const pad = 28;
206
+ const max = Math.max(1, ...points.map(point => Number(point.tokens || 0)));
207
+ for (let index = 0; index < 5; index += 1) {
208
+ const y = pad + ((height - pad * 2) * index / 4);
209
+ const line = document.createElementNS(ns, 'line');
210
+ line.setAttribute('x1', String(pad)); line.setAttribute('x2', String(width - pad));
211
+ line.setAttribute('y1', String(y)); line.setAttribute('y2', String(y)); line.setAttribute('class', 'grid');
212
+ svg.appendChild(line);
213
+ }
214
+ const coordinates = points.map((point, index) => {
215
+ const x = points.length === 1 ? width / 2 : pad + ((width - pad * 2) * index / (points.length - 1));
216
+ const y = height - pad - ((height - pad * 2) * Number(point.tokens || 0) / max);
217
+ return [x, y];
218
+ });
219
+ const linePath = coordinates.map((point, index) => (index ? 'L' : 'M') + point[0] + ' ' + point[1]).join(' ');
220
+ const area = document.createElementNS(ns, 'path');
221
+ area.setAttribute('d', linePath + ' L ' + coordinates.at(-1)[0] + ' ' + (height - pad) + ' L ' + coordinates[0][0] + ' ' + (height - pad) + ' Z');
222
+ area.setAttribute('class', 'area');
223
+ const line = document.createElementNS(ns, 'path');
224
+ line.setAttribute('d', linePath); line.setAttribute('class', 'line');
225
+ svg.append(area, line);
226
+ const summary = points.map(point => point.date + ': ' + compactUsageNumber(point.tokens) + ' tokens').join('; ');
227
+ svg.setAttribute('aria-label', summary);
228
+ if (fallback) fallback.textContent = summary;
229
+ }
230
+
231
+ function renderUsageOverview(payload) {
232
+ const summary = payload.summary || {};
233
+ el('usageKpiRequests').textContent = compactUsageNumber(summary.requests);
234
+ el('usageKpiTokens').textContent = compactUsageNumber(summary.totalTokens);
235
+ el('usageKpiCache').textContent = compactUsageNumber(summary.cacheReadTokens);
236
+ el('usageKpiCost').textContent = '$' + Number(summary.costUsd || 0).toFixed(4);
237
+ renderUsageTrend(payload.trend || []);
238
+ const list = el('usageProviderBreakdown');
239
+ list.replaceChildren();
240
+ for (const item of payload.byProvider || []) {
241
+ const card = document.createElement('div');
242
+ card.className = 'settings-help-row';
243
+ const text = document.createElement('span');
244
+ const strong = document.createElement('strong');
245
+ strong.textContent = item.key;
246
+ const small = document.createElement('small');
247
+ small.textContent = compactUsageNumber(item.tokens) + ' tokens · ' + compactUsageNumber(item.requests) + ' requests · $' + Number(item.costUsd || 0).toFixed(4);
248
+ text.append(strong, small); card.append(text); list.append(card);
249
+ }
250
+ if (!list.childElementCount) list.textContent = 'No provider-attributed usage.';
251
+ }
252
+
253
+ function renderUsageLedger(payload) {
254
+ const body = el('usageLedgerBody');
255
+ body.replaceChildren();
256
+ for (const event of payload.events || []) {
257
+ const row = document.createElement('tr');
258
+ const values = [
259
+ new Date(event.timestamp).toLocaleString(),
260
+ event.source,
261
+ [event.providerId, event.routeAlias || event.routeId].filter(Boolean).join(' / ') || '—',
262
+ event.resolvedModel || event.requestedModel,
263
+ compactUsageNumber(event.usage?.inputTokens),
264
+ compactUsageNumber(event.usage?.outputTokens),
265
+ compactUsageNumber((event.usage?.cacheReadTokens || 0) + (event.usage?.cacheWriteTokens || 0)),
266
+ event.usage?.costUsd === undefined ? 'Unpriced' : '$' + Number(event.usage.costUsd).toFixed(4),
267
+ event.status,
268
+ ];
269
+ for (const value of values) { const cell = document.createElement('td'); cell.textContent = String(value ?? ''); row.append(cell); }
270
+ body.append(row);
271
+ }
272
+ el('usageLedgerEmpty').hidden = body.childElementCount > 0;
273
+ el('usageLedgerPage').textContent = 'Page ' + (Math.floor(usageLedgerOffset / USAGE_LEDGER_PAGE_SIZE) + 1);
274
+ el('usageLedgerPrev').disabled = usageLedgerOffset === 0;
275
+ el('usageLedgerNext').disabled = (payload.events || []).length < USAGE_LEDGER_PAGE_SIZE;
276
+ }
277
+
278
+ function usageAdminCard(title, detail, actions) {
279
+ const card = document.createElement('div');
280
+ card.className = 'settings-help-row usage-admin-card';
281
+ const text = document.createElement('span');
282
+ const strong = document.createElement('strong'); strong.textContent = title;
283
+ const small = document.createElement('small'); small.textContent = detail;
284
+ text.append(strong, small);
285
+ const actionBox = document.createElement('span'); actionBox.className = 'usage-admin-actions';
286
+ for (const action of actions) {
287
+ const button = document.createElement('button'); button.type = 'button'; button.textContent = action.label;
288
+ if (action.danger) button.classList.add('danger');
289
+ button.addEventListener('click', () => {
290
+ Promise.resolve().then(action.run).catch(error => { el('usageRoutingStatus').textContent = error.message || String(error); });
291
+ });
292
+ actionBox.append(button);
293
+ }
294
+ card.append(text, actionBox);
295
+ return card;
296
+ }
297
+
298
+ async function usageMutation(path, method, body) {
299
+ const status = el('usageRoutingStatus');
300
+ status.textContent = 'Saving...';
301
+ await usageRoutingJson(path, {
302
+ method,
303
+ headers: { 'content-type': 'application/json' },
304
+ ...(body ? { body: JSON.stringify(body) } : {}),
305
+ });
306
+ status.textContent = 'Saved locally.';
307
+ await refreshUsageRouting();
308
+ }
309
+
310
+ function renderUsageCatalog(catalog) {
311
+ const gateway = catalog.gateway || {};
312
+ const gatewayStatus = el('usageGatewayStatus');
313
+ if (gatewayStatus) gatewayStatus.textContent = gateway.running
314
+ ? 'Running at ' + String(gateway.url || 'unknown') + ' · ' + String(gateway.authentication || 'client-key')
315
+ : 'Stopped. Embedded in-process routing remains available.';
316
+ const budgets = el('usageBudgetList'); budgets.replaceChildren();
317
+ for (const budget of catalog.budgets || []) {
318
+ const scope = budget.scope?.kind === 'global' ? 'global' : budget.scope?.kind + ':' + budget.scope?.id;
319
+ budgets.append(usageAdminCard(budget.id, scope + ' · ' + budget.period + ' · ' + budget.metric + ' ' + budget.limit + ' · ' + budget.action + (budget.enabled ? '' : ' · disabled'), [
320
+ { label: 'Delete', danger: true, run: () => void usageMutation('/api/usage-routing/budgets/' + encodeURIComponent(budget.id), 'DELETE') },
321
+ ]));
322
+ }
323
+ if (!budgets.childElementCount) budgets.textContent = 'No usage limits configured.';
324
+
325
+ const credentials = el('usageCredentialList'); credentials.replaceChildren();
326
+ for (const credential of catalog.credentials || []) {
327
+ const health = credential.health?.state || (credential.secretConfigured ? 'configured' : 'missing');
328
+ credentials.append(usageAdminCard(credential.label || credential.id, credential.providerId + ' · priority ' + credential.priority + ' · ' + health, [
329
+ { label: 'Edit', run: () => { el('usageCredentialId').value = credential.id; el('usageCredentialProvider').value = credential.providerId; el('usageCredentialLabel').value = credential.label || ''; el('usageCredentialPriority').value = String(credential.priority); el('usageCredentialWeight').value = String(credential.weight); } },
330
+ { label: 'Test', run: async () => { const result = await usageRoutingJson('/api/usage-routing/credentials/' + encodeURIComponent(credential.id) + '/test', { method: 'POST' }); el('usageRoutingStatus').textContent = credential.id + ': ' + result.state; await refreshUsageRouting(); } },
331
+ { label: 'Delete', danger: true, run: () => void usageMutation('/api/usage-routing/credentials/' + encodeURIComponent(credential.id), 'DELETE') },
332
+ ]));
333
+ }
334
+ if (!credentials.childElementCount) credentials.textContent = 'No managed API credentials. Native CLI logins appear under targets.';
335
+
336
+ const targets = el('usageTargetList'); targets.replaceChildren();
337
+ for (const target of catalog.targets || []) {
338
+ const detail = target.kind === 'managed-api'
339
+ ? target.providerId + ' · ' + target.protocol + ' · ' + target.baseUrl
340
+ : target.runtime + ' · native login' + (target.profileName ? ' · ' + target.profileName : '');
341
+ targets.append(usageAdminCard(target.id, detail, [
342
+ { label: 'Edit', run: () => { el('usageTargetId').value = target.id; el('usageTargetKind').value = target.kind; el('usageTargetProvider').value = target.kind === 'managed-api' ? target.providerId : target.runtime; el('usageTargetProtocol').value = target.protocol || 'openai'; el('usageTargetLocation').value = target.kind === 'managed-api' ? target.baseUrl : (target.profileName || ''); } },
343
+ { label: 'Test', run: async () => { const result = await usageRoutingJson('/api/usage-routing/targets/' + encodeURIComponent(target.id) + '/test', { method: 'POST' }); el('usageRoutingStatus').textContent = target.id + ': ' + (result.state || 'configured'); } },
344
+ { label: 'Delete', danger: true, run: () => void usageMutation('/api/usage-routing/targets/' + encodeURIComponent(target.id), 'DELETE') },
345
+ ]));
346
+ }
347
+ if (!targets.childElementCount) targets.textContent = 'No execution targets configured.';
348
+
349
+ const routes = el('usageRouteList'); routes.replaceChildren();
350
+ for (const route of catalog.routes || []) {
351
+ const candidates = (route.candidates || []).map(candidate => candidate.targetId + ' → ' + candidate.upstreamModel + ' (p' + candidate.priority + ')').join(', ');
352
+ routes.append(usageAdminCard(route.alias, route.mode + ' · ' + candidates, [
353
+ { label: 'Use in Hadamard', run: async () => { const selected = await usageRoutingJson('/api/usage-routing/routes/' + encodeURIComponent(route.alias) + '/activate', { method: 'POST' }); el('usageRoutingStatus').textContent = 'Hadamard now uses ' + selected.routeAlias + ' (' + selected.model + ').'; } },
354
+ { label: 'Edit', run: () => { el('usageRouteId').value = route.id; el('usageRouteAlias').value = route.alias; el('usageRouteMode').value = route.mode; el('usageRouteCandidates').value = (route.candidates || []).map(candidate => candidate.targetId + ' | ' + candidate.upstreamModel + ' | ' + candidate.priority).join('\\n'); } },
355
+ { label: 'Delete', danger: true, run: () => void usageMutation('/api/usage-routing/routes/' + encodeURIComponent(route.id), 'DELETE') },
356
+ ]));
357
+ }
358
+ if (!routes.childElementCount) routes.textContent = 'No Gateway Routes configured.';
359
+ }
360
+
361
+ async function refreshUsageRouting() {
362
+ const status = el('usageRoutingStatus');
363
+ if (!status) return;
364
+ status.textContent = 'Loading local usage and routing data...';
365
+ try {
366
+ const query = usageFilterSearch(false);
367
+ const tasks = [usageRoutingJson('/api/usage-routing/catalog')];
368
+ if (usageRoutingActivePage === 'overview') tasks.push(usageRoutingJson('/api/usage-routing/overview?' + query));
369
+ if (usageRoutingActivePage === 'ledger') tasks.push(usageRoutingJson('/api/usage-routing/ledger?' + usageFilterSearch(true)));
370
+ const results = await Promise.all(tasks);
371
+ renderUsageCatalog(results[0]);
372
+ if (usageRoutingActivePage === 'overview') renderUsageOverview(results[1]);
373
+ if (usageRoutingActivePage === 'ledger') renderUsageLedger(results[1]);
374
+ status.textContent = 'Local data refreshed. Secrets and prompts are excluded from this view.';
375
+ } catch (error) {
376
+ status.textContent = error.message || String(error);
377
+ }
378
+ }
379
+
380
+ function showUsageRoutingPage(page) {
381
+ usageRoutingActivePage = ['overview', 'ledger', 'limits', 'keys', 'routes'].includes(page) ? page : 'overview';
382
+ document.querySelectorAll('[data-usage-routing-page-tab]').forEach(button => button.classList.toggle('active', button.dataset.usageRoutingPageTab === usageRoutingActivePage));
383
+ document.querySelectorAll('[data-usage-routing-page]').forEach(panel => { panel.hidden = panel.dataset.usageRoutingPage !== usageRoutingActivePage; });
384
+ void refreshUsageRouting();
385
+ }
386
+
387
+ function showConfigurationPage(page) {
388
+ const usage = page === 'usage';
389
+ el('configurationModelsPage').hidden = usage;
390
+ el('configurationUsagePage').hidden = !usage;
391
+ el('bridgeNewConfig').hidden = usage;
392
+ el('configurationModelsTab').classList.toggle('active', !usage);
393
+ el('configurationUsageTab').classList.toggle('active', usage);
394
+ el('configurationModelsTab').setAttribute('aria-selected', usage ? 'false' : 'true');
395
+ el('configurationUsageTab').setAttribute('aria-selected', usage ? 'true' : 'false');
396
+ if (usage) void refreshUsageRouting();
397
+ }
398
+
399
+ `;
400
+ export const usageRoutingBindings = `
401
+ const usageRoutingPanel = document.querySelector('[data-configuration-usage-panel]');
402
+ if (usageRoutingPanel && el('configurationUsageSlot')) el('configurationUsageSlot').appendChild(usageRoutingPanel);
403
+ el('configurationModelsTab')?.addEventListener('click', () => showConfigurationPage('models'));
404
+ el('configurationUsageTab')?.addEventListener('click', () => showConfigurationPage('usage'));
405
+ document.querySelectorAll('[data-usage-routing-page-tab]').forEach(button => button.addEventListener('click', () => showUsageRoutingPage(button.dataset.usageRoutingPageTab)));
406
+ el('usageRoutingRefresh')?.addEventListener('click', () => { usageLedgerOffset = 0; void refreshUsageRouting(); });
407
+ for (const id of ['usageRange', 'usageProviderFilter', 'usageModelFilter', 'usageStatusFilter']) {
408
+ el(id)?.addEventListener('change', () => { usageLedgerOffset = 0; void refreshUsageRouting(); });
409
+ }
410
+ el('usageLedgerPrev')?.addEventListener('click', () => { usageLedgerOffset = Math.max(0, usageLedgerOffset - USAGE_LEDGER_PAGE_SIZE); void refreshUsageRouting(); });
411
+ el('usageLedgerNext')?.addEventListener('click', () => { usageLedgerOffset += USAGE_LEDGER_PAGE_SIZE; void refreshUsageRouting(); });
412
+ el('usageBudgetSave')?.addEventListener('click', () => {
413
+ const scopeKind = el('usageBudgetScope').value;
414
+ void usageMutation('/api/usage-routing/budgets', 'PUT', {
415
+ id: el('usageBudgetId').value.trim(),
416
+ scopeKind,
417
+ ...(scopeKind === 'global' ? {} : { scopeId: el('usageBudgetScopeId').value.trim() }),
418
+ period: el('usageBudgetPeriod').value,
419
+ metric: el('usageBudgetMetric').value,
420
+ limit: Number(el('usageBudgetLimit').value),
421
+ action: el('usageBudgetAction').value,
422
+ fallbackRouteId: el('usageBudgetFallback').value.trim(),
423
+ enabled: true,
424
+ }).catch(error => { el('usageRoutingStatus').textContent = error.message || String(error); });
425
+ });
426
+ el('usageCredentialSave')?.addEventListener('click', () => {
427
+ const secretInput = el('usageCredentialSecret');
428
+ const secret = secretInput.value;
429
+ secretInput.value = '';
430
+ void usageMutation('/api/usage-routing/credentials', 'PUT', {
431
+ id: el('usageCredentialId').value.trim(),
432
+ providerId: el('usageCredentialProvider').value.trim(),
433
+ label: el('usageCredentialLabel').value.trim(),
434
+ priority: Number(el('usageCredentialPriority').value),
435
+ weight: Number(el('usageCredentialWeight').value),
436
+ ...(secret ? { secret } : {}),
437
+ enabled: true,
438
+ }).catch(error => { el('usageRoutingStatus').textContent = error.message || String(error); });
439
+ });
440
+ el('usageBridgeMigrationPreview')?.addEventListener('click', async () => {
441
+ try {
442
+ const result = await usageRoutingJson('/api/usage-routing/migration/bridge');
443
+ el('usageRoutingStatus').textContent = 'Bridge migration preview: ' + result.ready + ' ready, ' + result.blocked + ' blocked. Native OAuth/session secrets read: no.';
444
+ } catch (error) { el('usageRoutingStatus').textContent = error.message || String(error); }
445
+ });
446
+ el('usageBridgeMigrationApply')?.addEventListener('click', async () => {
447
+ try {
448
+ const result = await usageRoutingJson('/api/usage-routing/migration/bridge', { method: 'POST' });
449
+ await refreshUsageRouting();
450
+ el('usageRoutingStatus').textContent = 'Bridge migration: ' + result.imported + ' imported, ' + result.skipped + ' skipped. Legacy configs remain unchanged.';
451
+ } catch (error) { el('usageRoutingStatus').textContent = error.message || String(error); }
452
+ });
453
+ for (const action of ['Preview', 'Apply']) {
454
+ el('usagePortableMigration' + action)?.addEventListener('click', async () => {
455
+ const filePath = el('usagePortableMigrationFile').value.trim();
456
+ try {
457
+ const result = await usageRoutingJson('/api/usage-routing/migration/portable/' + (action === 'Preview' ? 'preview' : 'import'), {
458
+ method: 'POST',
459
+ headers: { 'content-type': 'application/json' },
460
+ body: JSON.stringify({ filePath }),
461
+ });
462
+ if (action === 'Apply') await refreshUsageRouting();
463
+ el('usageRoutingStatus').textContent = (action === 'Preview' ? 'Import preview: ' : 'Import complete: ') + JSON.stringify(result);
464
+ } catch (error) { el('usageRoutingStatus').textContent = error.message || String(error); }
465
+ });
466
+ }
467
+ el('usageGatewayStart')?.addEventListener('click', async () => {
468
+ try {
469
+ const result = await usageRoutingJson('/api/usage-routing/gateway/start', {
470
+ method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ port: 0 }),
471
+ });
472
+ await refreshUsageRouting();
473
+ el('usageRoutingStatus').textContent = result.clientKey
474
+ ? 'Gateway started at ' + result.url + '. Client key (shown once): ' + result.clientKey
475
+ : 'Gateway is already running at ' + result.url + '.';
476
+ } catch (error) { el('usageRoutingStatus').textContent = error.message || String(error); }
477
+ });
478
+ el('usageGatewayStop')?.addEventListener('click', async () => {
479
+ try {
480
+ await usageRoutingJson('/api/usage-routing/gateway/stop', { method: 'POST' });
481
+ await refreshUsageRouting();
482
+ el('usageRoutingStatus').textContent = 'Loopback gateway stopped.';
483
+ } catch (error) { el('usageRoutingStatus').textContent = error.message || String(error); }
484
+ });
485
+ el('usageTargetSave')?.addEventListener('click', () => {
486
+ const kind = el('usageTargetKind').value;
487
+ const provider = el('usageTargetProvider').value.trim();
488
+ const location = el('usageTargetLocation').value.trim();
489
+ const body = kind === 'managed-api'
490
+ ? { id: el('usageTargetId').value.trim(), kind, providerId: provider, protocol: el('usageTargetProtocol').value, baseUrl: location, enabled: true }
491
+ : { id: el('usageTargetId').value.trim(), kind, runtime: provider, ...(location ? { profileName: location } : {}), enabled: true };
492
+ void usageMutation('/api/usage-routing/targets', 'PUT', body).catch(error => { el('usageRoutingStatus').textContent = error.message || String(error); });
493
+ });
494
+ el('usageRouteSave')?.addEventListener('click', () => {
495
+ const routeId = el('usageRouteId').value.trim();
496
+ const candidates = el('usageRouteCandidates').value.split(/\\r?\\n/u).map(line => line.trim()).filter(Boolean).map((line, index) => {
497
+ const parts = line.split('|').map(part => part.trim());
498
+ return { id: routeId + '.candidate.' + (index + 1), targetId: parts[0], upstreamModel: parts[1], priority: Number(parts[2] || index), weight: 1, enabled: true };
499
+ });
500
+ void usageMutation('/api/usage-routing/routes', 'PUT', {
501
+ id: routeId,
502
+ alias: el('usageRouteAlias').value.trim(),
503
+ mode: el('usageRouteMode').value,
504
+ candidates,
505
+ enabled: true,
506
+ }).catch(error => { el('usageRoutingStatus').textContent = error.message || String(error); });
507
+ });
508
+ `;
509
+ //# sourceMappingURL=guiUsageRoutingAssets.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"guiUsageRoutingAssets.js","sourceRoot":"","sources":["../../../src/gui/guiUsageRoutingAssets.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,mCAAmC,GAAG;;;;;;;;;;;;CAYlD,CAAC;AAEF,MAAM,CAAC,MAAM,iCAAiC,GAAG;;;;CAIhD,CAAC;AAEF,MAAM,CAAC,MAAM,gBAAgB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA6F/B,CAAC;AAEF,MAAM,CAAC,MAAM,kBAAkB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA0CjC,CAAC;AAEF,MAAM,CAAC,MAAM,kBAAkB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAmPjC,CAAC;AAEF,MAAM,CAAC,MAAM,oBAAoB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA4GnC,CAAC"}
@@ -0,0 +1,11 @@
1
+ import { type GuiHttpRouter } from './guiHttpRouter.js';
2
+ import { type UsageRoutingAdminService } from '../keyway/usageRoutingAdminService.js';
3
+ export type GuiUsageRoutingPort = Pick<UsageRoutingAdminService, 'overview' | 'ledger' | 'catalog' | 'saveCredential' | 'deleteCredential' | 'testCredential' | 'saveTarget' | 'deleteTarget' | 'testTarget' | 'saveRoute' | 'deleteRoute' | 'saveBudget' | 'deleteBudget' | 'gatewayStatus' | 'startGateway' | 'stopGateway' | 'previewBridgeMigration' | 'importBridgeMigration' | 'previewPortableMigration' | 'importPortableMigration'>;
4
+ export interface GuiUsageRoutingSelectionPort {
5
+ activateRoute(reference: string): Promise<{
6
+ routeAlias: string;
7
+ model: string;
8
+ }>;
9
+ }
10
+ export declare function registerGuiUsageRoutingHttpController(router: GuiHttpRouter, port: GuiUsageRoutingPort, selection?: GuiUsageRoutingSelectionPort): void;
11
+ //# sourceMappingURL=guiUsageRoutingHttpController.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"guiUsageRoutingHttpController.d.ts","sourceRoot":"","sources":["../../../src/gui/guiUsageRoutingHttpController.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkB,KAAK,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACxE,OAAO,EAAyB,KAAK,wBAAwB,EAAE,MAAM,uCAAuC,CAAC;AAE7G,MAAM,MAAM,mBAAmB,GAAG,IAAI,CAAC,wBAAwB,EAC3D,UAAU,GAAG,QAAQ,GAAG,SAAS,GACjC,gBAAgB,GAAG,kBAAkB,GAAG,gBAAgB,GACxD,YAAY,GAAG,cAAc,GAAG,YAAY,GAC5C,WAAW,GAAG,aAAa,GAC3B,YAAY,GAAG,cAAc,GAC7B,eAAe,GAAG,cAAc,GAAG,aAAa,GAChD,wBAAwB,GAAG,uBAAuB,GAClD,0BAA0B,GAAG,yBAAyB,CAAC,CAAC;AAE5D,MAAM,WAAW,4BAA4B;IAC3C,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAClF;AAQD,wBAAgB,qCAAqC,CACnD,MAAM,EAAE,aAAa,EACrB,IAAI,EAAE,mBAAmB,EACzB,SAAS,CAAC,EAAE,4BAA4B,GACvC,IAAI,CAuEN"}