@platless/axom-cli 0.3.13 → 0.6.36

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 (320) hide show
  1. package/README.md +18 -5
  2. package/dist/axom-patch/cli/README.md +119 -9
  3. package/dist/axom-patch/cli/account-limits/README.md +27 -0
  4. package/dist/axom-patch/cli/account-limits/antigravity-proxy.mjs +47 -0
  5. package/dist/axom-patch/cli/account-limits/antigravity-read.mjs +90 -0
  6. package/dist/axom-patch/cli/account-limits/antigravity.mjs +46 -0
  7. package/dist/axom-patch/cli/account-limits/generate.py +123 -0
  8. package/dist/axom-patch/cli/account-limits/kimi-proxy.mjs +81 -0
  9. package/dist/axom-patch/cli/account-limits/normalize.mjs +151 -0
  10. package/dist/axom-patch/cli/claude-account-limits.json +37 -0
  11. package/dist/axom-patch/cli/codex-account-limits.json +37 -0
  12. package/dist/axom-patch/cli/codex-terminal-output.json +16 -0
  13. package/dist/axom-patch/cli/commandcode-account-limits.json +30 -0
  14. package/dist/axom-patch/cli/kimi-quota.mjs +12 -0
  15. package/dist/axom-patch/cli/kimi-subagents.mjs +173 -0
  16. package/dist/axom-patch/cli/nightly-task.md +194 -0
  17. package/dist/axom-patch/cli/pi-subagent-subwork.json +23 -0
  18. package/dist/axom-patch/cli/probes/claude-late-async-terminal.mjs +5 -1
  19. package/dist/axom-patch/cli/probes/codex-terminal-output.mjs +33 -0
  20. package/dist/axom-patch/cli/probes/kimi-subagent-subwork.mjs +82 -0
  21. package/dist/axom-patch/cli/probes/pi-subagent-subwork.mjs +211 -0
  22. package/dist/axom-patch/cli/probes/zcode-subagent-subwork.mjs +257 -0
  23. package/dist/axom-patch/cli/qoder/dist/axom-acp-compat.mjs +1591 -0
  24. package/dist/axom-patch/cli/qoder/releases/1.1.75.json +360 -0
  25. package/dist/axom-patch/cli/qoder-background-terminal.md +13 -5
  26. package/dist/axom-patch/cli/qoder-foreground-output.md +14 -7
  27. package/dist/axom-patch/cli/qoder-native-acp.json +227 -0
  28. package/dist/axom-patch/cli/qoder-steering.md +20 -25
  29. package/dist/axom-patch/cli/qoder-subagent-subwork.md +17 -68
  30. package/dist/axom-patch/cli/runtime.json +39 -15
  31. package/dist/axom-patch/cli/zcode-account-limits.json +30 -0
  32. package/dist/axom-patch/cli/zcode-models.json +37 -0
  33. package/dist/axom-patch/cli/zcode-subagent-subwork.json +72 -0
  34. package/dist/cli.js +4 -9
  35. package/dist/commands/knowledge.js +47 -0
  36. package/dist/commands/update.js +65 -3
  37. package/dist/commands/workstation.js +73 -7
  38. package/dist/install/claude/commands.js +13 -13
  39. package/dist/keepers/acp/keeper.mjs +3083 -0
  40. package/dist/keepers/acp/manifest.json +9 -0
  41. package/dist/keepers/terminal/keeper.mjs +8469 -0
  42. package/dist/keepers/terminal/manifest.json +9 -0
  43. package/dist/keepers/terminal/node_modules/node-pty/lib/conpty_console_list_agent.js +25 -0
  44. package/dist/keepers/terminal/node_modules/node-pty/lib/eventEmitter2.js +47 -0
  45. package/dist/keepers/terminal/node_modules/node-pty/lib/index.js +52 -0
  46. package/dist/keepers/terminal/node_modules/node-pty/lib/interfaces.js +7 -0
  47. package/dist/keepers/terminal/node_modules/node-pty/lib/shared/conout.js +10 -0
  48. package/dist/keepers/terminal/node_modules/node-pty/lib/terminal.js +190 -0
  49. package/dist/keepers/terminal/node_modules/node-pty/lib/types.js +7 -0
  50. package/dist/keepers/terminal/node_modules/node-pty/lib/unixTerminal.js +351 -0
  51. package/dist/keepers/terminal/node_modules/node-pty/lib/utils.js +38 -0
  52. package/dist/keepers/terminal/node_modules/node-pty/lib/windowsConoutConnection.js +131 -0
  53. package/dist/keepers/terminal/node_modules/node-pty/lib/windowsPtyAgent.js +344 -0
  54. package/dist/keepers/terminal/node_modules/node-pty/lib/windowsTerminal.js +209 -0
  55. package/dist/keepers/terminal/node_modules/node-pty/lib/worker/conoutSocketWorker.js +22 -0
  56. package/dist/keepers/terminal/node_modules/node-pty/package.json +66 -0
  57. package/dist/keepers/terminal/node_modules/node-pty/prebuilds/darwin-arm64/pty.node +0 -0
  58. package/dist/keepers/terminal/node_modules/node-pty/prebuilds/darwin-arm64/spawn-helper +0 -0
  59. package/dist/keepers/terminal/node_modules/node-pty/prebuilds/darwin-x64/pty.node +0 -0
  60. package/dist/keepers/terminal/node_modules/node-pty/prebuilds/darwin-x64/spawn-helper +0 -0
  61. package/dist/keepers/terminal/node_modules/node-pty/prebuilds/linux-arm64/pty.node +0 -0
  62. package/dist/keepers/terminal/node_modules/node-pty/prebuilds/linux-x64/pty.node +0 -0
  63. package/dist/mcp.js +1 -1
  64. package/dist/native/darwin-arm64/axom-quic +0 -0
  65. package/dist/test/keeperKit.js +67 -0
  66. package/dist/workstation/acp/acpAgents.js +2192 -636
  67. package/dist/workstation/acp/acpClient.js +84 -495
  68. package/dist/workstation/acp/adoption.js +10 -396
  69. package/dist/workstation/acp/binaryInstall.js +100 -27
  70. package/dist/workstation/acp/builds/gc.js +245 -0
  71. package/dist/workstation/acp/builds/install/binary.js +28 -0
  72. package/dist/workstation/acp/builds/install/npm.js +324 -0
  73. package/dist/workstation/acp/builds/install/uv.js +254 -0
  74. package/dist/workstation/acp/builds/migrate.js +195 -0
  75. package/dist/workstation/acp/builds/model.js +128 -0
  76. package/dist/workstation/acp/builds/nodeGate.js +100 -0
  77. package/dist/workstation/acp/builds/store.js +352 -0
  78. package/dist/workstation/acp/commandTableProbe.js +68 -0
  79. package/dist/workstation/acp/configOptions.js +103 -1
  80. package/dist/workstation/acp/envSecrets.js +27 -0
  81. package/dist/workstation/acp/initializeResponse.js +290 -0
  82. package/dist/workstation/acp/installErrors.js +12 -2
  83. package/dist/workstation/acp/installProbe.js +1 -1
  84. package/dist/workstation/acp/managedPatches.js +239 -159
  85. package/dist/workstation/acp/managedRuntime.js +314 -0
  86. package/dist/workstation/acp/outbox.js +330 -0
  87. package/dist/workstation/acp/patchAssets.js +60 -3
  88. package/dist/workstation/acp/permission.js +42 -10
  89. package/dist/workstation/acp/processor/checkpoint.js +78 -0
  90. package/dist/workstation/acp/processor/commands.js +35 -0
  91. package/dist/workstation/acp/processor/control.js +282 -0
  92. package/dist/workstation/acp/processor/emit.js +26 -0
  93. package/dist/workstation/acp/processor/jsonrpc.js +172 -0
  94. package/dist/workstation/acp/processor/memoryLog.js +49 -0
  95. package/dist/workstation/acp/processor/origins.js +48 -0
  96. package/dist/workstation/acp/processor/output.js +67 -0
  97. package/dist/workstation/acp/processor/projection.js +42 -0
  98. package/dist/workstation/acp/processor/record.js +128 -0
  99. package/dist/workstation/acp/processor/runtime.js +105 -0
  100. package/dist/workstation/acp/processor/sdkSchemas.js +61 -0
  101. package/dist/workstation/acp/processor/session.js +496 -0
  102. package/dist/workstation/acp/processor/state.js +67 -0
  103. package/dist/workstation/acp/processor/step.js +442 -0
  104. package/dist/workstation/acp/processor/test/fixtureLog.js +58 -0
  105. package/dist/workstation/acp/processor/test/harness.js +120 -0
  106. package/dist/workstation/acp/processor/test/largeIdEcho.js +50 -0
  107. package/dist/workstation/acp/processor/test/rawAgent.js +70 -0
  108. package/dist/workstation/acp/processor/transport/generations.js +76 -0
  109. package/dist/workstation/acp/processor/transport/keeper.js +130 -0
  110. package/dist/workstation/acp/processor/transport/local.js +269 -0
  111. package/dist/workstation/acp/processor/transport/types.js +78 -0
  112. package/dist/workstation/acp/processor/updates.js +390 -0
  113. package/dist/workstation/acp/processorSession.js +1348 -0
  114. package/dist/workstation/acp/registry/catalog.js +189 -41
  115. package/dist/workstation/acp/registry/launch.js +76 -119
  116. package/dist/workstation/acp/sessionUpdateMapper.js +18 -7
  117. package/dist/workstation/acp/steering.js +58 -0
  118. package/dist/workstation/acp/switching.js +111 -0
  119. package/dist/workstation/acp/terminalLogin.js +16 -42
  120. package/dist/workstation/acp/terminalLoginSpec.js +41 -0
  121. package/dist/workstation/acp/test/adapterFixtures.js +256 -0
  122. package/dist/workstation/acp/test/daemonLife.js +106 -0
  123. package/dist/workstation/acp/test/fakeAgent.js +13 -0
  124. package/dist/workstation/acp/test/keeperWorld.js +295 -0
  125. package/dist/workstation/acp/test/memoryOutbox.js +55 -0
  126. package/dist/workstation/acp/test/raw/fixtures.js +196 -0
  127. package/dist/workstation/acp/test/raw/sanitize.js +344 -0
  128. package/dist/workstation/acp/test/raw/sources.js +135 -0
  129. package/dist/workstation/acp/test/realAdapter.js +331 -0
  130. package/dist/workstation/acp/test/scriptAgent.js +214 -0
  131. package/dist/workstation/acp/test/sessionFrames.js +42 -0
  132. package/dist/workstation/acp/timedCommand.js +4 -2
  133. package/dist/workstation/acp/uvxInstall.js +72 -122
  134. package/dist/workstation/agentFlow.js +103 -0
  135. package/dist/workstation/agentSessions.js +1289 -544
  136. package/dist/workstation/channels/agent.js +7 -5
  137. package/dist/workstation/channels/computer.js +69 -21
  138. package/dist/workstation/channels/computerExecs.js +200 -0
  139. package/dist/workstation/channels/management.js +172 -8
  140. package/dist/workstation/channels/url.js +1 -1
  141. package/dist/workstation/config.js +16 -4
  142. package/dist/workstation/connection/encryptedChannel.js +4 -1
  143. package/dist/workstation/control/bindingStore.js +200 -0
  144. package/dist/workstation/control/commands.js +124 -0
  145. package/dist/workstation/control/daemonIdentity.js +53 -0
  146. package/dist/workstation/control/enrollment.js +106 -0
  147. package/dist/workstation/control/inspect.js +71 -0
  148. package/dist/workstation/control/locks.js +63 -0
  149. package/dist/workstation/control/protocol.js +40 -0
  150. package/dist/workstation/control/restartGuard.js +46 -0
  151. package/dist/workstation/control/runtimeClient.js +97 -0
  152. package/dist/workstation/control/runtimeHost.js +148 -0
  153. package/dist/workstation/control/serverIdentity.js +35 -0
  154. package/dist/workstation/daemon.js +393 -12
  155. package/dist/workstation/daemonctl.js +103 -14
  156. package/dist/workstation/desktopControl/audio.js +99 -0
  157. package/dist/workstation/desktopControl/capabilities.js +92 -0
  158. package/dist/workstation/desktopControl/captureModes.js +9 -0
  159. package/dist/workstation/desktopControl/captureTargetLabel.js +9 -0
  160. package/dist/workstation/desktopControl/deviceSockets.js +246 -0
  161. package/dist/workstation/desktopControl/helperProcess.js +353 -0
  162. package/dist/workstation/desktopControl/lobby.js +129 -0
  163. package/dist/workstation/desktopControl/macProvider.js +155 -0
  164. package/dist/workstation/desktopControl/media.js +331 -0
  165. package/dist/workstation/desktopControl/nativeProvider.js +1018 -0
  166. package/dist/workstation/desktopControl/networkEvidence.js +43 -0
  167. package/dist/workstation/desktopControl/overview.js +58 -0
  168. package/dist/workstation/desktopControl/protocol.js +302 -0
  169. package/dist/workstation/desktopControl/quicMedia.js +240 -0
  170. package/dist/workstation/desktopControl/rateController.js +226 -0
  171. package/dist/workstation/desktopControl/supervisor.js +1681 -0
  172. package/dist/workstation/desktopControl/transport.js +27 -0
  173. package/dist/workstation/desktopControl/wallpaper.js +78 -0
  174. package/dist/workstation/exec/attach.js +126 -0
  175. package/dist/workstation/exec/command.js +40 -0
  176. package/dist/workstation/exec/durable.js +517 -0
  177. package/dist/workstation/exec/fallback.js +106 -0
  178. package/dist/workstation/exec/policy.js +837 -0
  179. package/dist/workstation/exec/shaping.js +211 -0
  180. package/dist/workstation/exec.js +3 -112
  181. package/dist/workstation/files/adapters/computerFileFrames.js +51 -0
  182. package/dist/workstation/files/adapters/toolRequests.js +121 -0
  183. package/dist/workstation/files/client.js +455 -0
  184. package/dist/workstation/files/credentials.js +142 -0
  185. package/dist/workstation/files/errors.js +28 -0
  186. package/dist/workstation/files/fence.js +350 -0
  187. package/dist/workstation/files/find/fileIndex.js +153 -0
  188. package/dist/workstation/files/find/fuzzy.js +155 -0
  189. package/dist/workstation/files/host.js +122 -0
  190. package/dist/workstation/files/hostProtocol.js +2 -0
  191. package/dist/workstation/files/media/cache.js +620 -0
  192. package/dist/workstation/files/media/encoding.js +308 -0
  193. package/dist/workstation/files/media/estimator.js +100 -0
  194. package/dist/workstation/files/media/media.js +325 -0
  195. package/dist/workstation/files/media/packedAudio.js +23 -0
  196. package/dist/workstation/files/media/scheduler.js +551 -0
  197. package/dist/workstation/files/media/types.js +5 -0
  198. package/dist/workstation/files/scope.js +45 -0
  199. package/dist/workstation/files/structured.js +284 -0
  200. package/dist/workstation/{fileOps.js → files/text.js} +152 -39
  201. package/dist/workstation/files/uploads.js +351 -0
  202. package/dist/workstation/journal.js +65 -236
  203. package/dist/workstation/keepers/acp/client.js +101 -0
  204. package/dist/workstation/keepers/acp/envelope.js +62 -0
  205. package/dist/workstation/keepers/acp/host.js +419 -0
  206. package/dist/workstation/keepers/acp/main.js +18 -0
  207. package/dist/workstation/keepers/acp/memoryStream.js +121 -0
  208. package/dist/workstation/keepers/acp/params.js +137 -0
  209. package/dist/workstation/keepers/acp/protocol.js +66 -0
  210. package/dist/workstation/keepers/acp/recover.js +120 -0
  211. package/dist/workstation/keepers/acp/session.js +584 -0
  212. package/dist/workstation/keepers/acp/stderr.js +154 -0
  213. package/dist/workstation/keepers/kit/activity.js +103 -0
  214. package/dist/workstation/keepers/kit/bootstrap.js +164 -0
  215. package/dist/workstation/keepers/kit/buildVersion.js +51 -0
  216. package/dist/workstation/keepers/kit/bundle.js +124 -0
  217. package/dist/workstation/keepers/kit/client.js +432 -0
  218. package/dist/workstation/keepers/kit/identity.js +21 -0
  219. package/dist/workstation/keepers/kit/ipc.js +112 -0
  220. package/dist/workstation/keepers/kit/launchConfig.js +8 -0
  221. package/dist/workstation/keepers/kit/launcher.js +289 -0
  222. package/dist/workstation/keepers/kit/lock.js +123 -0
  223. package/dist/workstation/keepers/kit/orphanSweep.js +86 -0
  224. package/dist/workstation/keepers/kit/paths.js +76 -0
  225. package/dist/workstation/keepers/kit/protocol.js +43 -0
  226. package/dist/workstation/keepers/kit/server.js +518 -0
  227. package/dist/workstation/keepers/kit/streamLog.js +697 -0
  228. package/dist/workstation/keepers/legacy.js +296 -0
  229. package/dist/workstation/keepers/policy.js +576 -0
  230. package/dist/workstation/keepers/stop.js +34 -0
  231. package/dist/workstation/keepers/terminal/client.js +213 -0
  232. package/dist/workstation/keepers/terminal/exec.js +981 -0
  233. package/dist/workstation/keepers/terminal/generations.js +129 -0
  234. package/dist/workstation/keepers/terminal/keeper.js +62 -0
  235. package/dist/workstation/keepers/terminal/main.js +43 -0
  236. package/dist/workstation/keepers/terminal/orphanSweep.js +3 -0
  237. package/dist/workstation/keepers/terminal/params.js +27 -0
  238. package/dist/workstation/keepers/terminal/protocol.js +56 -0
  239. package/dist/workstation/keepers/terminal/pty.js +330 -0
  240. package/dist/workstation/managedInstall.js +57 -0
  241. package/dist/workstation/protocol/accountLimits.js +47 -0
  242. package/dist/workstation/protocol/agent.js +50 -19
  243. package/dist/workstation/protocol/common.js +30 -0
  244. package/dist/workstation/protocol/computer.js +15 -4
  245. package/dist/workstation/protocol/management.js +141 -4
  246. package/dist/workstation/protocol/runtimeDiagnostics.js +15 -0
  247. package/dist/workstation/rd/artifact.js +119 -0
  248. package/dist/workstation/rd/control.js +119 -0
  249. package/dist/workstation/rd/display.js +29 -0
  250. package/dist/workstation/rd/provider.js +222 -0
  251. package/dist/workstation/rd/sessions.js +391 -0
  252. package/dist/workstation/rd/state.js +45 -0
  253. package/dist/workstation/rd/update.js +171 -0
  254. package/dist/workstation/rd/upkeep.js +38 -0
  255. package/dist/workstation/register.js +27 -37
  256. package/dist/workstation/service/installer.js +104 -13
  257. package/dist/workstation/service/launchd.js +1 -0
  258. package/dist/workstation/storageMetrics.js +118 -0
  259. package/dist/workstation/systemMetrics.js +154 -93
  260. package/dist/workstation/toolchain.js +94 -0
  261. package/dist/workstation/tools/browser.js +28 -4
  262. package/dist/workstation/tools/client.js +9 -1
  263. package/dist/workstation/tools/info.js +50 -0
  264. package/dist/workstation/tools/keeperProcess.js +15 -5
  265. package/dist/workstation/tools/router.js +55 -0
  266. package/dist/workstation/tools/terminalRouter.js +222 -0
  267. package/dist/workstation/tools/validation.js +37 -1
  268. package/dist/workstation/update/artifact.js +2 -1
  269. package/dist/workstation/update/coordinator.js +3 -0
  270. package/dist/workstation/update/globalInstall.js +116 -0
  271. package/dist/workstation/update/npmInstaller.js +49 -23
  272. package/dist/workstation/update/ptyPreflight.js +21 -0
  273. package/dist/workstation/update/rdRollback.js +145 -0
  274. package/dist/workstation/update/runUpdate.js +3 -2
  275. package/dist/workstation/update/taskRegistry.js +105 -11
  276. package/dist/workstation/update/watchdog.js +16790 -334
  277. package/package.json +11 -8
  278. package/skills/axom/SKILL.md +11 -11
  279. package/skills/axom-definition/SKILL.md +3 -3
  280. package/skills/axom-platform/SKILL.md +3 -3
  281. package/dist/axom-patch/cli/probes/qoder-steering.mjs +0 -89
  282. package/dist/axom-patch/cli/probes/qoder-subagent-origin.mjs +0 -49
  283. package/dist/axom-patch/cli/qoder-steering.json +0 -52
  284. package/dist/axom-patch/cli/qoder-subagent-origin.json +0 -73
  285. package/dist/commands/guide.js +0 -38
  286. package/dist/workstation/acp/acpSession.js +0 -1475
  287. package/dist/workstation/acp/contract.js +0 -499
  288. package/dist/workstation/acp/delegationMcp.js +0 -24
  289. package/dist/workstation/acp/draftInterceptor.js +0 -163
  290. package/dist/workstation/acp/qoderStartup.js +0 -45
  291. package/dist/workstation/acp/sessionScope.js +0 -19
  292. package/dist/workstation/filePaths.js +0 -174
  293. package/dist/workstation/fileWorker.js +0 -81
  294. package/dist/workstation/fileWorkerClient.js +0 -121
  295. package/dist/workstation/qoderAdapter/backgroundProcess.js +0 -106
  296. package/dist/workstation/qoderAdapter/backgroundTerminal.js +0 -262
  297. package/dist/workstation/qoderAdapter/completedContinuation.js +0 -139
  298. package/dist/workstation/qoderAdapter/contract.js +0 -78
  299. package/dist/workstation/qoderAdapter/foregroundOutput.js +0 -30
  300. package/dist/workstation/qoderAdapter/history.js +0 -87
  301. package/dist/workstation/qoderAdapter/identityIndex.js +0 -191
  302. package/dist/workstation/qoderAdapter/inputQueue.js +0 -42
  303. package/dist/workstation/qoderAdapter/main.js +0 -93
  304. package/dist/workstation/qoderAdapter/mapper.js +0 -117
  305. package/dist/workstation/qoderAdapter/observerPlugin.js +0 -245
  306. package/dist/workstation/qoderAdapter/observerReceiver.js +0 -187
  307. package/dist/workstation/qoderAdapter/permissions.js +0 -61
  308. package/dist/workstation/qoderAdapter/profile.js +0 -93
  309. package/dist/workstation/qoderAdapter/sdkDriver.js +0 -49
  310. package/dist/workstation/qoderAdapter/session.js +0 -319
  311. package/dist/workstation/qoderAdapter/subagentMeta.js +0 -28
  312. package/dist/workstation/qoderAdapter/subwork.js +0 -77
  313. package/dist/workstation/qoderAdapter/taskControl.js +0 -38
  314. package/dist/workstation/qoderAdapter/taskEvent.js +0 -26
  315. package/dist/workstation/qoderAdapter/taskLedger.js +0 -163
  316. package/dist/workstation/qoderAdapter/usage.js +0 -37
  317. package/dist/workstation/tools/core.js +0 -50
  318. package/dist/workstation/tools/files.js +0 -525
  319. package/dist/workstation/tools/terminal.js +0 -214
  320. /package/dist/workstation/{qoderAdapter/types.js → desktopControl/provider.js} +0 -0
@@ -0,0 +1,81 @@
1
+ /** Verified Kimi 2.1.1 ACP extension shim. The web usage route is invoked ONLY for an explicit read. */
2
+ import {spawn} from 'node:child_process';
3
+ import {createInterface} from 'node:readline';
4
+ import {readFile} from 'node:fs/promises';
5
+ import {createServer} from 'node:net';
6
+ import {homedir} from 'node:os';
7
+ import {join} from 'node:path';
8
+ import {READ, UPDATED, CAPABILITY, kimi} from './normalize.mjs';
9
+ import {KimiSubagents} from '../kimi-subagents.mjs';
10
+ import {quotaErrorDetail, quotaRpcError} from '../kimi-quota.mjs';
11
+ const [binary,...args]=process.argv.slice(2);
12
+ const child=spawn(binary,args,{stdio:['pipe','pipe','pipe'],env:process.env});
13
+ const write=value=>process.stdout.write(JSON.stringify(value)+'\n');
14
+ const subagents=new KimiSubagents(write);
15
+ const subagentPoll=setInterval(()=>{void subagents.poll().catch(()=>{});},250);
16
+ subagentPoll.unref();
17
+ let initializeId; let reading=false; let selectedModel; let pendingPrompt; let quotaDetail;
18
+ async function usage() {
19
+ if(reading) throw new Error('A usage refresh is already in progress'); reading=true;
20
+ let web;
21
+ try {
22
+ const port=await new Promise((resolve,reject)=>{const server=createServer();server.on('error',reject);server.listen(0,'127.0.0.1',()=>{const port=server.address().port;server.close(()=>resolve(port));});});
23
+ web=spawn(binary,['web','--port',String(port),'--no-open'],{stdio:'ignore',env:process.env});
24
+ let spawnError;web.on('error',error=>{spawnError=error;});
25
+ const controller=new AbortController();const timeout=setTimeout(()=>controller.abort(),20000);
26
+ try {
27
+ for(let attempt=0;attempt<60;attempt++) {
28
+ if(spawnError)throw spawnError;
29
+ if(web.exitCode!==null) throw new Error('The Kimi usage service exited');
30
+ try {
31
+ const token=(await readFile(join(process.env.KIMI_CODE_HOME??join(homedir(),'.kimi-code'),'server.token'),'utf8')).trim();
32
+ const request=async path=>{const response=await fetch(`http://127.0.0.1:${port}/api/v1/${path}`,{headers:{Authorization:`Bearer ${token}`},signal:controller.signal});if(!response.ok)throw new Error('Failed to fetch usage');return response.json();};
33
+ // Native catalog resolves credentials, including environment overrides, without exposing keys.
34
+ const config=(await request('config')).data;
35
+ const providers=(await request('providers')).data?.items??[];
36
+ const model=config?.models?.[selectedModel??config?.default_model];
37
+ const provider=providers.find(p=>p.id===(model?.providerId??model?.provider_id??model?.provider??config?.default_provider));
38
+ if(provider?.has_api_key===true || (provider && provider.id!=='managed:kimi-code'))return {schemaVersion:1,provider:'kimi',billingMode:'non_subscription',availability:'not_applicable',observedAt:new Date().toISOString(),plan:null,limits:[],balances:[],_meta:{_kimi:{credential:'api'}}};
39
+ if(!provider || provider.status!=='connected')throw new Error('Failed to fetch usage');
40
+ const response=await fetch(`http://127.0.0.1:${port}/api/v1/oauth/usage`,{headers:{Authorization:`Bearer ${token}`},signal:controller.signal});
41
+ if(!response.ok) throw new Error('Failed to fetch usage');
42
+ const body=await response.json();
43
+ if(body.data?.kind!=='ok') throw new Error(body.data?.message??'Failed to fetch usage');
44
+ return kimi(body.data.quota);
45
+ } catch(error) {
46
+ if(controller.signal.aborted || (error.message && !['fetch failed'].includes(error.message) && error.code!=='ENOENT')) throw error;
47
+ }
48
+ await new Promise(resolve=>setTimeout(resolve,150));
49
+ }
50
+ throw new Error('The Kimi usage service did not start');
51
+ } finally {clearTimeout(timeout);}
52
+ } finally { reading=false; if(web){web.kill('SIGTERM');setTimeout(()=>{if(web.exitCode===null)web.kill('SIGKILL');},2000).unref();} }
53
+ }
54
+ createInterface({input:child.stdout}).on('line',line=>{
55
+ let message;try{message=JSON.parse(line);}catch{process.stdout.write(line+'\n');return;}
56
+ if(subagents.fromAgent(message))return;
57
+ if(pendingPrompt?.id===message.id){
58
+ const prompt=pendingPrompt; pendingPrompt=undefined;
59
+ if(message.result?.stopReason==='end_turn' && quotaDetail){
60
+ write({jsonrpc:'2.0',id:prompt.id,error:quotaRpcError(quotaDetail)}); quotaDetail=undefined; return;
61
+ }
62
+ quotaDetail=undefined;
63
+ }
64
+ if(message.id===initializeId&&message.result){message.result.agentCapabilities??={};message.result.agentCapabilities._meta??={};message.result.agentCapabilities._meta['axom/accountRateLimits']={...CAPABILITY,provider:'kimi'};write(message);return;}
65
+ process.stdout.write(line+'\n');
66
+ });
67
+ createInterface({input:child.stderr}).on('line',line=>{const detail=quotaErrorDetail(line);if(detail)quotaDetail=detail;process.stderr.write(line+'\n');});
68
+ createInterface({input:process.stdin}).on('line',line=>{
69
+ let message;try{message=JSON.parse(line);}catch{child.stdin.write(line+'\n');return;}
70
+ subagents.fromClient(message);
71
+ if(message.method==='initialize')initializeId=message.id;
72
+ if(message.method==='session/prompt' && message.id!==undefined){pendingPrompt={id:message.id,sessionId:message.params?.sessionId};quotaDetail=undefined;}
73
+ if(message.method==='session/set_config_option' && message.params?.configId==='model')selectedModel=message.params.value;
74
+ if(message.method==='session/set_model')selectedModel=message.params.modelId;
75
+ if(message.method===READ&&message.id!==undefined){void usage().then(result=>{write({jsonrpc:'2.0',id:message.id,result});write({jsonrpc:'2.0',method:UPDATED,params:result});},()=>write({jsonrpc:'2.0',id:message.id,error:{code:-32603,message:'Failed to fetch usage'}}));return;}
76
+ child.stdin.write(line+'\n');
77
+ });
78
+ process.stdin.on('end',()=>child.stdin.end());
79
+ for(const signal of ['SIGTERM','SIGINT'])process.on(signal,()=>child.kill(signal));
80
+ child.on('error',()=>process.exitCode=1);
81
+ child.on('exit',code=>process.exit(code??1));
@@ -0,0 +1,151 @@
1
+ /** Account quota normalization. No I/O: passive events must never trigger a query. */
2
+ export const READ = '_axom/account/rateLimits/read';
3
+ export const UPDATED = '_axom/account/rateLimits/updated';
4
+ export const CAPABILITY = { version: 2, read: true, updated: true };
5
+ const percent = v => typeof v === 'number' && Number.isFinite(v) ? Math.max(0, Math.min(100, v)) : null;
6
+ const finite = v => { if (v == null || typeof v === 'boolean') return null; const n = typeof v === 'number' ? v : Number(v); return Number.isFinite(n) ? n : null; };
7
+ const time = (v, milliseconds = false) => {
8
+ if (v == null || v === 0) return null;
9
+ const d = new Date(typeof v === 'number' ? v * (milliseconds ? 1 : 1000) : v);
10
+ return Number.isFinite(d.getTime()) ? d.toISOString() : null;
11
+ };
12
+ const status = v => ({ allowed: 'allowed', allowed_warning: 'warning', rejected: 'blocked', normal: 'allowed' }[v] ?? 'unknown');
13
+ /** Canonical window key rides the snapshot; the UI owns the key → display-label translation.
14
+ * Windows without a canonical key keep their vendor content untouched. */
15
+ export const windowKey = (seconds, period) => period === 'month' ? 'monthly' : seconds === 18000 ? 'five_hour' : seconds === 604800 ? 'weekly' : null;
16
+ const base = (provider, mode, raw, observedAt) => ({ schemaVersion: 1, updateMode: 'snapshot', provider, billingMode: mode, availability: mode === 'subscription' ? 'available' : mode === 'unknown' ? 'unknown' : 'not_applicable', observedAt: observedAt ?? new Date().toISOString(), plan: null, limits: [], balances: [], _meta: { ['_' + provider]: structuredClone(raw) } });
17
+ export function codex(raw, authType = 'chatgpt', observedAt) {
18
+ const out = base('codex', ['chatgpt', 'chatgptAuthTokens'].includes(authType) ? 'subscription' : ['apiKey','apikey','amazonBedrock'].includes(authType) ? 'non_subscription' : 'unknown', raw, observedAt);
19
+ if (out.billingMode !== 'subscription') return out;
20
+ const buckets = Object.entries(raw.rateLimitsByLimitId ?? {});
21
+ if (!buckets.length && raw.rateLimits) buckets.push([raw.rateLimits.limitId ?? 'codex', raw.rateLimits]);
22
+ for (const [id, bucket] of buckets) {
23
+ if (!bucket) continue;
24
+ if (bucket.planType) out.plan = { id: bucket.planType, label: bucket.planType };
25
+ for (const name of ['primary', 'secondary']) {
26
+ const w = bucket[name]; if (!w) continue;
27
+ const seconds = w.windowDurationMins == null ? null : w.windowDurationMins * 60;
28
+ out.limits.push({ id: `${id}.${name}`, label: bucket.limitName ?? id, kind: 'subscription', windowSeconds: seconds, key: windowKey(seconds), usedPercent: percent(w.usedPercent), resetsAt: time(w.resetsAt), status: bucket.rateLimitReachedType ? 'blocked' : 'unknown', scope: { bucket: id } });
29
+ }
30
+ if (bucket.credits) out.balances.push({ id: `${id}.credits`, label: 'Credits', remaining: bucket.credits.balance ?? null, unit: 'credits', unlimited: bucket.credits.unlimited === true });
31
+ }
32
+ // Account IDs must not escape via provider extensions.
33
+ delete out._meta._codex.accountId;
34
+ return out;
35
+ }
36
+ /** App-server notifications describe changed buckets, not the complete account read. */
37
+ export function codexEvent(raw, authType, observedAt) {
38
+ return { ...codex(raw, authType, observedAt), updateMode: 'delta' };
39
+ }
40
+ export function claude(raw, observedAt) {
41
+ const mode = raw.billing_mode ?? (raw.subscription_type || raw.rate_limits_available === true ? 'subscription' : 'unknown');
42
+ const out = base('claude', mode, raw, observedAt);
43
+ out.plan = raw.subscription_type ? { id: raw.subscription_type, label: raw.subscription_type } : null;
44
+ if (mode === 'subscription' && !raw.rate_limits_available) out.availability = 'unavailable';
45
+ const limits = raw.rate_limits ?? {};
46
+ const rows = Array.isArray(limits.limits) ? limits.limits : [];
47
+ if (rows.length) {
48
+ rows.forEach((w, i) => {
49
+ const seconds = w.group === 'session' ? 18000 : w.group === 'weekly' ? 604800 : null;
50
+ out.limits.push({ id: `claude.${w.kind}.${i}`, label: w.scope?.model?.display_name ?? w.kind, kind: 'subscription', windowSeconds: seconds, key: windowKey(seconds), usedPercent: percent(w.percent), resetsAt: time(w.resets_at), status: status(w.severity), active: w.is_active ?? null, scope: w.scope ?? null });
51
+ });
52
+ } else for (const [id, seconds] of [['five_hour', 18000], ['seven_day', 604800], ['seven_day_opus',604800], ['seven_day_sonnet',604800]]) {
53
+ const w = limits[id]; if (!w) continue;
54
+ out.limits.push({ id: `claude.${id}`, label: id, kind: 'subscription', windowSeconds: seconds, key: windowKey(seconds), usedPercent: percent(w.utilization), resetsAt: time(w.resets_at), status: 'unknown', scope: id.endsWith('opus') || id.endsWith('sonnet') ? { model: id.split('_').at(-1) } : null });
55
+ }
56
+ const spend=limits.spend;
57
+ if(spend?.enabled && spend.used && spend.limit) out.limits.push({id:'claude.extra_spend',label:'Extra usage',kind:'spend',windowSeconds:null,usedPercent:percent(spend.percent),resetsAt:null,status:status(spend.severity),used:String(spend.used.amount_minor),limit:String(spend.limit.amount_minor),unit:'money',currency:spend.used.currency,exponent:spend.used.exponent});
58
+ if(spend?.balance?.amount_minor != null) out.balances.push({id:'claude.extra_balance',label:'Extra usage balance',remaining:String(spend.balance.amount_minor),unit:'money',currency:spend.balance.currency,exponent:spend.balance.exponent});
59
+ return out;
60
+ }
61
+ export function claudeEvent(raw, previous = null, observedAt) {
62
+ const out = previous ? structuredClone(previous) : base('claude',raw.rateLimitType?'subscription':'unknown',{},observedAt);
63
+ out.updateMode = 'delta';
64
+ out.balances = [];
65
+ out.observedAt = observedAt ?? new Date().toISOString();
66
+ // Other sessions may have refreshed the account since this process's cached read.
67
+ // Send only facts from this event; the server owns the merged account observation.
68
+ out.plan = null;
69
+ out._meta = { _claude: { rateLimitEvent: raw } };
70
+ const seconds = raw.rateLimitType === 'five_hour' ? 18000 : raw.rateLimitType?.startsWith('seven_day') ? 604800 : null;
71
+ if (seconds !== null) {
72
+ const id = `claude.${raw.rateLimitType}`;
73
+ const scoped = !['five_hour','seven_day'].includes(raw.rateLimitType);
74
+ const prior=out.limits.find(w => w.windowSeconds===seconds && JSON.stringify(w.scope??null)===JSON.stringify(scoped ? {model:raw.rateLimitType.split('_').at(-1)}:null));
75
+ const row = { id: prior?.id ?? id, label: raw.rateLimitType, kind: 'subscription', windowSeconds: seconds, key: windowKey(seconds), usedPercent: raw.utilization == null ? null : percent(raw.utilization * 100), resetsAt: time(raw.resetsAt), status: status(raw.status), scope: scoped ? { model: raw.rateLimitType.split('_').at(-1) } : null };
76
+ // Event utilization is a ratio, unlike get_usage's percentage. Match the existing window without duplicating it.
77
+ out.limits = [row];
78
+ } else out.limits = [];
79
+ return out;
80
+ }
81
+ export function kimi(raw, observedAt) {
82
+ const quota = raw.quota ?? raw;
83
+ const out = base('kimi','subscription',quota,observedAt);
84
+ for (const [id, seconds] of [['limit5h',18000],['limit7d',604800],['monthTotal',null]]) {
85
+ const w = quota.usages?.[id]; if (!w) continue;
86
+ out.limits.push({ id: `kimi.${id}`, label: id, kind: 'subscription', windowSeconds: seconds, period: id === 'monthTotal' ? 'month' : null, key: windowKey(seconds, id === 'monthTotal' ? 'month' : null), usedPercent: percent(w.usedRatio * 100), resetsAt: time(w.resetAt), status: 'unknown' });
87
+ }
88
+ const wallet = quota.extraUsage;
89
+ if (wallet) out.balances.push({ id:'kimi.extra',label:'Extra usage',remaining:String(wallet.balanceCents),unit:'money',currency:wallet.currency,exponent:2 });
90
+ return out;
91
+ }
92
+ /** GLM quota `percentage` is the USED share (measured: 5% for 58/1000); counters win when present.
93
+ * Counter-derived shares are rounded to 2 decimals — 100-94.2 keeps fp noise otherwise. */
94
+ const glmPercent = limit => {
95
+ const used = finite(limit.currentValue), remaining = finite(limit.remaining), total = finite(limit.usage);
96
+ const cap = remaining !== null && used !== null ? remaining + used : total;
97
+ const round2 = v => v == null ? null : Math.round(v * 100) / 100;
98
+ if (cap !== null && cap > 0 && remaining !== null && remaining >= 0 && remaining <= cap) return round2(100 - percent(remaining / cap * 100));
99
+ if (cap !== null && cap > 0 && used !== null && used >= 0 && used <= cap) return round2(percent(used / cap * 100));
100
+ return percent(limit.percentage);
101
+ };
102
+ export function zcode(raw, observedAt) {
103
+ // Raw: the GLM quota endpoint's data block — { level, limits: [{ type, number, usage,
104
+ // currentValue, remaining, percentage, nextResetTime(epoch ms), usageDetails }] }. Which windows
105
+ // exist is plan-dependent (measured pro plan: 5h prompts + a tool/MCP allowance; no weekly row —
106
+ // a `number: 7` window maps when a plan returns one, and nothing is invented when it doesn't).
107
+ const limits = Array.isArray(raw?.limits) ? raw.limits : [];
108
+ const level = typeof raw?.level === 'string' && raw.level ? raw.level : null;
109
+ const out = base('zcode', limits.length ? 'subscription' : 'unknown', raw, observedAt);
110
+ out.plan = level ? { id: level, label: level } : null;
111
+ let tools = 0;
112
+ limits.forEach((limit, i) => {
113
+ if (!limit || typeof limit !== 'object') return;
114
+ const resetsAt = time(limit.nextResetTime, true);
115
+ if (limit.type === 'TOKENS_LIMIT') {
116
+ // GLM window convention: number 5 → the 5-hour prompt window, 7 → weekly.
117
+ // Ids mirror the adapter's own /quota keys (token_5h / token_week).
118
+ const seconds = limit.number === 5 ? 18000 : limit.number === 7 ? 604800 : null;
119
+ const label = limit.number === 5 ? '5h' : limit.number === 7 ? 'Week' : `token ${limit.number ?? i}`;
120
+ const id = limit.number === 5 ? 'zcode.token_5h' : limit.number === 7 ? 'zcode.token_week' : `zcode.token_${limit.number ?? i}`;
121
+ out.limits.push({ id, label, kind: 'subscription', windowSeconds: seconds, key: windowKey(seconds), usedPercent: glmPercent(limit), resetsAt, status: 'unknown' });
122
+ } else if (limit.type === 'TIME_LIMIT' || limit.type === 'MCP_LIMIT') {
123
+ // Built-in tool/MCP allowance: absolute counters (unit 'calls'); per-model detail stays in _meta.
124
+ const used = finite(limit.currentValue), total = finite(limit.usage), remaining = finite(limit.remaining);
125
+ out.limits.push({ id: tools ? `zcode.mcp.${i}` : 'zcode.mcp', label: 'MCP', kind: 'subscription', windowSeconds: null, usedPercent: glmPercent(limit), ...(used !== null && total !== null ? { used: String(used), limit: String(total), unit: 'calls' } : {}), resetsAt, status: remaining === 0 ? 'blocked' : used !== null || limit.percentage != null ? 'allowed' : 'unknown' });
126
+ tools++;
127
+ }
128
+ });
129
+ return out;
130
+ }
131
+ export function commandcode(raw, observedAt) {
132
+ const subscription = raw.subscription?.data ?? raw.subscription;
133
+ const windows = raw.credits?.windowLimits ?? raw.windowLimits;
134
+ const subscribed = ['active','trialing','past_due'].includes(subscription?.status) || windows?.limited === true;
135
+ const mode = subscribed ? 'subscription' : subscription || windows ? 'non_subscription' : 'unknown';
136
+ const out = base('commandcode',mode,raw,observedAt);
137
+ out.plan = subscription?.planId ? { id:subscription.planId,label:subscription.planId } : null;
138
+ for (const [id, seconds] of [['fiveHour',18000],['weekly',604800]]) {
139
+ const w=windows?.[id]; if (!w) continue;
140
+ out.limits.push({ id:`commandcode.${id}`,label:id,kind:'subscription',windowSeconds:seconds,key:windowKey(seconds),usedPercent:w.cap > 0 ? percent(w.used / w.cap * 100) : null,used:String(w.used),limit:String(w.cap),unit:'credits',resetsAt:time(w.resetAt,true),status:w.exceeded === true ? 'blocked' : 'allowed' });
141
+ }
142
+ const credits=raw.credits?.credits ?? raw.credits;
143
+ if (subscribed && credits?.monthlyCredits != null) {
144
+ const cap=raw.monthly?.limit;
145
+ const known=typeof cap==='number' && Number.isFinite(cap) && cap>0;
146
+ const used=known ? Math.max(0,cap-credits.monthlyCredits) : null;
147
+ out.limits.push({id:'commandcode.monthly',label:'Monthly',kind:'subscription',windowSeconds:null,period:'month',key:windowKey(null,'month'),usedPercent:known?percent(used/cap*100):null,resetsAt:time(subscription?.currentPeriodEnd,true),status:'unknown',...(known?{used:String(used),limit:String(cap),unit:'credits'}:{})});
148
+ }
149
+ for (const [id,label] of [['monthlyCredits','Monthly credits'],['purchasedCredits','Purchased credits'],['freeCredits','Free credits']]) if (credits?.[id] != null) out.balances.push({ id:`commandcode.${id}`,label,remaining:String(credits[id]),unit:'credits' });
150
+ return out;
151
+ }
@@ -0,0 +1,37 @@
1
+ {
2
+ "id": "claude-account-limits",
3
+ "package": "@agentclientprotocol/claude-agent-acp",
4
+ "versions": [
5
+ "0.97.0"
6
+ ],
7
+ "edits": [
8
+ {
9
+ "file": "dist/acp-agent.js",
10
+ "before_sha256": "c569a914b2b9c75127193139a69bca3ca4ae6b2ebd6728de0330a16689fdc268",
11
+ "after_sha256": "7535a559b2f54913e76c9fd70799f470454a74efe4a5d92054edfe98c1242527",
12
+ "before": "authStatus: authStatusCapability(),",
13
+ "after": "authStatus: authStatusCapability(), \"axom/accountRateLimits\": {...axomQuota.CAPABILITY,provider:\"claude\",authBillingModes:{account:\"subscription\",api_key:\"non_subscription\",external:\"non_subscription\",gateway:\"non_subscription\",none:\"unknown\"}},"
14
+ },
15
+ {
16
+ "file": "dist/acp-agent.js",
17
+ "before_sha256": "7535a559b2f54913e76c9fd70799f470454a74efe4a5d92054edfe98c1242527",
18
+ "after_sha256": "703b56abeeafed87febf6f3464fceb0b23730f23caf02033618a27f48f8ce301",
19
+ "before": "case \"rate_limit_event\": {",
20
+ "after": "case \"rate_limit_event\": {\n this.axomAccountLimits = axomQuota.claudeEvent(message.rate_limit_info, this.axomAccountLimits);\n axomQuotaNotify(this.axomAccountLimits);"
21
+ },
22
+ {
23
+ "file": "dist/acp-agent.js",
24
+ "before_sha256": "703b56abeeafed87febf6f3464fceb0b23730f23caf02033618a27f48f8ce301",
25
+ "after_sha256": "82bcbad9894a665bcc7e31591d28abd8b6cd980a8f65ad87a131a36a1cc23462",
26
+ "before": ".onRequest(methods.agent.session.new,",
27
+ "after": ".onRequest(axomQuota.READ, {parse: value => value ?? {}}, async ctx => { const session=agent.sessions[ctx.params.sessionId] ?? Object.values(agent.sessions)[0]; const owned=!session?.query; const q=session?.query ?? query({prompt:\"/usage\",options:{cwd:ctx.params.cwd??process.cwd(),persistSession:false}}); let raw;try{const auth=fromAccountInfo(await q.accountInfo()); if([\"api_key\",\"external\",\"gateway\"].includes(auth?.kind)){raw={billing_mode:\"non_subscription\",rate_limits_available:false};}else{raw=await q.usage_EXPERIMENTAL_MAY_CHANGE_DO_NOT_RELY_ON_THIS_API_YET({skipBehaviors:true});raw.subscription_type??=auth?.account?.plan;raw.billing_mode=auth?.kind===\"account\"?\"subscription\":\"unknown\";}}finally{if(owned)q.close();} agent.axomAccountLimits=axomQuota.claude({subscription_type:raw.subscription_type,rate_limits_available:raw.rate_limits_available,rate_limits:raw.rate_limits,billing_mode:raw.billing_mode}); if(agent.axomAccountLimits.billingMode===\"unknown\" || agent.axomAccountLimits.availability===\"unavailable\") throw new Error(\"Failed to fetch usage\"); axomQuotaNotify(agent.axomAccountLimits); return agent.axomAccountLimits; })\n .onRequest(methods.agent.session.new,"
28
+ },
29
+ {
30
+ "file": "dist/acp-agent.js",
31
+ "before_sha256": "82bcbad9894a665bcc7e31591d28abd8b6cd980a8f65ad87a131a36a1cc23462",
32
+ "after_sha256": "efd325571a7c7cd0e9caaa7e143ec5ba32e2eec998ebb359dc4f5627c399de7f",
33
+ "before": "import { agent as acpAgent, CreateElicitationResponse, methods, ndJsonStream, RequestError, } from \"",
34
+ "after": "\nconst axomQuota = (() => {\n/** Account quota normalization. No I/O: passive events must never trigger a query. */\nconst READ = '_axom/account/rateLimits/read';\nconst UPDATED = '_axom/account/rateLimits/updated';\nconst CAPABILITY = { version: 2, read: true, updated: true };\nconst percent = v => typeof v === 'number' && Number.isFinite(v) ? Math.max(0, Math.min(100, v)) : null;\nconst finite = v => { if (v == null || typeof v === 'boolean') return null; const n = typeof v === 'number' ? v : Number(v); return Number.isFinite(n) ? n : null; };\nconst time = (v, milliseconds = false) => {\n if (v == null || v === 0) return null;\n const d = new Date(typeof v === 'number' ? v * (milliseconds ? 1 : 1000) : v);\n return Number.isFinite(d.getTime()) ? d.toISOString() : null;\n};\nconst status = v => ({ allowed: 'allowed', allowed_warning: 'warning', rejected: 'blocked', normal: 'allowed' }[v] ?? 'unknown');\n/** Canonical window key rides the snapshot; the UI owns the key → display-label translation.\n * Windows without a canonical key keep their vendor content untouched. */\nconst windowKey = (seconds, period) => period === 'month' ? 'monthly' : seconds === 18000 ? 'five_hour' : seconds === 604800 ? 'weekly' : null;\nconst base = (provider, mode, raw, observedAt) => ({ schemaVersion: 1, updateMode: 'snapshot', provider, billingMode: mode, availability: mode === 'subscription' ? 'available' : mode === 'unknown' ? 'unknown' : 'not_applicable', observedAt: observedAt ?? new Date().toISOString(), plan: null, limits: [], balances: [], _meta: { ['_' + provider]: structuredClone(raw) } });\nfunction codex(raw, authType = 'chatgpt', observedAt) {\n const out = base('codex', ['chatgpt', 'chatgptAuthTokens'].includes(authType) ? 'subscription' : ['apiKey','apikey','amazonBedrock'].includes(authType) ? 'non_subscription' : 'unknown', raw, observedAt);\n if (out.billingMode !== 'subscription') return out;\n const buckets = Object.entries(raw.rateLimitsByLimitId ?? {});\n if (!buckets.length && raw.rateLimits) buckets.push([raw.rateLimits.limitId ?? 'codex', raw.rateLimits]);\n for (const [id, bucket] of buckets) {\n if (!bucket) continue;\n if (bucket.planType) out.plan = { id: bucket.planType, label: bucket.planType };\n for (const name of ['primary', 'secondary']) {\n const w = bucket[name]; if (!w) continue;\n const seconds = w.windowDurationMins == null ? null : w.windowDurationMins * 60;\n out.limits.push({ id: `${id}.${name}`, label: bucket.limitName ?? id, kind: 'subscription', windowSeconds: seconds, key: windowKey(seconds), usedPercent: percent(w.usedPercent), resetsAt: time(w.resetsAt), status: bucket.rateLimitReachedType ? 'blocked' : 'unknown', scope: { bucket: id } });\n }\n if (bucket.credits) out.balances.push({ id: `${id}.credits`, label: 'Credits', remaining: bucket.credits.balance ?? null, unit: 'credits', unlimited: bucket.credits.unlimited === true });\n }\n // Account IDs must not escape via provider extensions.\n delete out._meta._codex.accountId;\n return out;\n}\n/** App-server notifications describe changed buckets, not the complete account read. */\nfunction codexEvent(raw, authType, observedAt) {\n return { ...codex(raw, authType, observedAt), updateMode: 'delta' };\n}\nfunction claude(raw, observedAt) {\n const mode = raw.billing_mode ?? (raw.subscription_type || raw.rate_limits_available === true ? 'subscription' : 'unknown');\n const out = base('claude', mode, raw, observedAt);\n out.plan = raw.subscription_type ? { id: raw.subscription_type, label: raw.subscription_type } : null;\n if (mode === 'subscription' && !raw.rate_limits_available) out.availability = 'unavailable';\n const limits = raw.rate_limits ?? {};\n const rows = Array.isArray(limits.limits) ? limits.limits : [];\n if (rows.length) {\n rows.forEach((w, i) => {\n const seconds = w.group === 'session' ? 18000 : w.group === 'weekly' ? 604800 : null;\n out.limits.push({ id: `claude.${w.kind}.${i}`, label: w.scope?.model?.display_name ?? w.kind, kind: 'subscription', windowSeconds: seconds, key: windowKey(seconds), usedPercent: percent(w.percent), resetsAt: time(w.resets_at), status: status(w.severity), active: w.is_active ?? null, scope: w.scope ?? null });\n });\n } else for (const [id, seconds] of [['five_hour', 18000], ['seven_day', 604800], ['seven_day_opus',604800], ['seven_day_sonnet',604800]]) {\n const w = limits[id]; if (!w) continue;\n out.limits.push({ id: `claude.${id}`, label: id, kind: 'subscription', windowSeconds: seconds, key: windowKey(seconds), usedPercent: percent(w.utilization), resetsAt: time(w.resets_at), status: 'unknown', scope: id.endsWith('opus') || id.endsWith('sonnet') ? { model: id.split('_').at(-1) } : null });\n }\n const spend=limits.spend;\n if(spend?.enabled && spend.used && spend.limit) out.limits.push({id:'claude.extra_spend',label:'Extra usage',kind:'spend',windowSeconds:null,usedPercent:percent(spend.percent),resetsAt:null,status:status(spend.severity),used:String(spend.used.amount_minor),limit:String(spend.limit.amount_minor),unit:'money',currency:spend.used.currency,exponent:spend.used.exponent});\n if(spend?.balance?.amount_minor != null) out.balances.push({id:'claude.extra_balance',label:'Extra usage balance',remaining:String(spend.balance.amount_minor),unit:'money',currency:spend.balance.currency,exponent:spend.balance.exponent});\n return out;\n}\nfunction claudeEvent(raw, previous = null, observedAt) {\n const out = previous ? structuredClone(previous) : base('claude',raw.rateLimitType?'subscription':'unknown',{},observedAt);\n out.updateMode = 'delta';\n out.balances = [];\n out.observedAt = observedAt ?? new Date().toISOString();\n // Other sessions may have refreshed the account since this process's cached read.\n // Send only facts from this event; the server owns the merged account observation.\n out.plan = null;\n out._meta = { _claude: { rateLimitEvent: raw } };\n const seconds = raw.rateLimitType === 'five_hour' ? 18000 : raw.rateLimitType?.startsWith('seven_day') ? 604800 : null;\n if (seconds !== null) {\n const id = `claude.${raw.rateLimitType}`;\n const scoped = !['five_hour','seven_day'].includes(raw.rateLimitType);\n const prior=out.limits.find(w => w.windowSeconds===seconds && JSON.stringify(w.scope??null)===JSON.stringify(scoped ? {model:raw.rateLimitType.split('_').at(-1)}:null));\n const row = { id: prior?.id ?? id, label: raw.rateLimitType, kind: 'subscription', windowSeconds: seconds, key: windowKey(seconds), usedPercent: raw.utilization == null ? null : percent(raw.utilization * 100), resetsAt: time(raw.resetsAt), status: status(raw.status), scope: scoped ? { model: raw.rateLimitType.split('_').at(-1) } : null };\n // Event utilization is a ratio, unlike get_usage's percentage. Match the existing window without duplicating it.\n out.limits = [row];\n } else out.limits = [];\n return out;\n}\nfunction kimi(raw, observedAt) {\n const quota = raw.quota ?? raw;\n const out = base('kimi','subscription',quota,observedAt);\n for (const [id, seconds] of [['limit5h',18000],['limit7d',604800],['monthTotal',null]]) {\n const w = quota.usages?.[id]; if (!w) continue;\n out.limits.push({ id: `kimi.${id}`, label: id, kind: 'subscription', windowSeconds: seconds, period: id === 'monthTotal' ? 'month' : null, key: windowKey(seconds, id === 'monthTotal' ? 'month' : null), usedPercent: percent(w.usedRatio * 100), resetsAt: time(w.resetAt), status: 'unknown' });\n }\n const wallet = quota.extraUsage;\n if (wallet) out.balances.push({ id:'kimi.extra',label:'Extra usage',remaining:String(wallet.balanceCents),unit:'money',currency:wallet.currency,exponent:2 });\n return out;\n}\n/** GLM quota `percentage` is the USED share (measured: 5% for 58/1000); counters win when present.\n * Counter-derived shares are rounded to 2 decimals — 100-94.2 keeps fp noise otherwise. */\nconst glmPercent = limit => {\n const used = finite(limit.currentValue), remaining = finite(limit.remaining), total = finite(limit.usage);\n const cap = remaining !== null && used !== null ? remaining + used : total;\n const round2 = v => v == null ? null : Math.round(v * 100) / 100;\n if (cap !== null && cap > 0 && remaining !== null && remaining >= 0 && remaining <= cap) return round2(100 - percent(remaining / cap * 100));\n if (cap !== null && cap > 0 && used !== null && used >= 0 && used <= cap) return round2(percent(used / cap * 100));\n return percent(limit.percentage);\n};\nfunction zcode(raw, observedAt) {\n // Raw: the GLM quota endpoint's data block — { level, limits: [{ type, number, usage,\n // currentValue, remaining, percentage, nextResetTime(epoch ms), usageDetails }] }. Which windows\n // exist is plan-dependent (measured pro plan: 5h prompts + a tool/MCP allowance; no weekly row —\n // a `number: 7` window maps when a plan returns one, and nothing is invented when it doesn't).\n const limits = Array.isArray(raw?.limits) ? raw.limits : [];\n const level = typeof raw?.level === 'string' && raw.level ? raw.level : null;\n const out = base('zcode', limits.length ? 'subscription' : 'unknown', raw, observedAt);\n out.plan = level ? { id: level, label: level } : null;\n let tools = 0;\n limits.forEach((limit, i) => {\n if (!limit || typeof limit !== 'object') return;\n const resetsAt = time(limit.nextResetTime, true);\n if (limit.type === 'TOKENS_LIMIT') {\n // GLM window convention: number 5 → the 5-hour prompt window, 7 → weekly.\n // Ids mirror the adapter's own /quota keys (token_5h / token_week).\n const seconds = limit.number === 5 ? 18000 : limit.number === 7 ? 604800 : null;\n const label = limit.number === 5 ? '5h' : limit.number === 7 ? 'Week' : `token ${limit.number ?? i}`;\n const id = limit.number === 5 ? 'zcode.token_5h' : limit.number === 7 ? 'zcode.token_week' : `zcode.token_${limit.number ?? i}`;\n out.limits.push({ id, label, kind: 'subscription', windowSeconds: seconds, key: windowKey(seconds), usedPercent: glmPercent(limit), resetsAt, status: 'unknown' });\n } else if (limit.type === 'TIME_LIMIT' || limit.type === 'MCP_LIMIT') {\n // Built-in tool/MCP allowance: absolute counters (unit 'calls'); per-model detail stays in _meta.\n const used = finite(limit.currentValue), total = finite(limit.usage), remaining = finite(limit.remaining);\n out.limits.push({ id: tools ? `zcode.mcp.${i}` : 'zcode.mcp', label: 'MCP', kind: 'subscription', windowSeconds: null, usedPercent: glmPercent(limit), ...(used !== null && total !== null ? { used: String(used), limit: String(total), unit: 'calls' } : {}), resetsAt, status: remaining === 0 ? 'blocked' : used !== null || limit.percentage != null ? 'allowed' : 'unknown' });\n tools++;\n }\n });\n return out;\n}\nfunction commandcode(raw, observedAt) {\n const subscription = raw.subscription?.data ?? raw.subscription;\n const windows = raw.credits?.windowLimits ?? raw.windowLimits;\n const subscribed = ['active','trialing','past_due'].includes(subscription?.status) || windows?.limited === true;\n const mode = subscribed ? 'subscription' : subscription || windows ? 'non_subscription' : 'unknown';\n const out = base('commandcode',mode,raw,observedAt);\n out.plan = subscription?.planId ? { id:subscription.planId,label:subscription.planId } : null;\n for (const [id, seconds] of [['fiveHour',18000],['weekly',604800]]) {\n const w=windows?.[id]; if (!w) continue;\n out.limits.push({ id:`commandcode.${id}`,label:id,kind:'subscription',windowSeconds:seconds,key:windowKey(seconds),usedPercent:w.cap > 0 ? percent(w.used / w.cap * 100) : null,used:String(w.used),limit:String(w.cap),unit:'credits',resetsAt:time(w.resetAt,true),status:w.exceeded === true ? 'blocked' : 'allowed' });\n }\n const credits=raw.credits?.credits ?? raw.credits;\n if (subscribed && credits?.monthlyCredits != null) {\n const cap=raw.monthly?.limit;\n const known=typeof cap==='number' && Number.isFinite(cap) && cap>0;\n const used=known ? Math.max(0,cap-credits.monthlyCredits) : null;\n out.limits.push({id:'commandcode.monthly',label:'Monthly',kind:'subscription',windowSeconds:null,period:'month',key:windowKey(null,'month'),usedPercent:known?percent(used/cap*100):null,resetsAt:time(subscription?.currentPeriodEnd,true),status:'unknown',...(known?{used:String(used),limit:String(cap),unit:'credits'}:{})});\n }\n for (const [id,label] of [['monthlyCredits','Monthly credits'],['purchasedCredits','Purchased credits'],['freeCredits','Free credits']]) if (credits?.[id] != null) out.balances.push({ id:`commandcode.${id}`,label,remaining:String(credits[id]),unit:'credits' });\n return out;\n}\n\nreturn {READ,UPDATED,CAPABILITY,codex,codexEvent,claude,claudeEvent,kimi,commandcode,zcode};\n})();\nconst axomQuotaNotify = snapshot => process.stdout.write(JSON.stringify({jsonrpc:\"2.0\",method:axomQuota.UPDATED,params:snapshot})+\"\\n\");\nimport { agent as acpAgent, CreateElicitationResponse, methods, ndJsonStream, RequestError, } from \""
35
+ }
36
+ ]
37
+ }
@@ -0,0 +1,37 @@
1
+ {
2
+ "id": "codex-account-limits",
3
+ "package": "@agentclientprotocol/codex-acp",
4
+ "versions": [
5
+ "2.3.0"
6
+ ],
7
+ "edits": [
8
+ {
9
+ "file": "dist/index.js",
10
+ "before_sha256": "d584cb7df74847661c3cf69d34de6ad3a24f6bc69787faeaff49a1fd9a3d8870",
11
+ "after_sha256": "ca35466484445d86e84d7b4d5ffd84789544a4f4a973d3082bbb55d6c43715a2",
12
+ "before": "[AUTH_STATUS_META_KEY]: authStatusCapability()\n }\n },\n authMethods:",
13
+ "after": "[AUTH_STATUS_META_KEY]: authStatusCapability(), \"axom/accountRateLimits\": {...axomQuota.CAPABILITY,provider:\"codex\",authBillingModes:{account:\"subscription\",api_key:\"non_subscription\",external:\"non_subscription\",gateway:\"non_subscription\",none:\"unknown\"}}\n }\n },\n authMethods:"
14
+ },
15
+ {
16
+ "file": "dist/index.js",
17
+ "before_sha256": "ca35466484445d86e84d7b4d5ffd84789544a4f4a973d3082bbb55d6c43715a2",
18
+ "after_sha256": "9fa475ad64993e9c55a8a9443185f90774bbc5bf19e1c85d1398aadd629edd88",
19
+ "before": "this.handleRateLimitsUpdated(notification.params);\n return null;",
20
+ "after": "this.handleRateLimitsUpdated(notification.params);\n axomQuotaNotify(axomQuota.codexEvent(notification.params, this.sessionState.account?.type ?? \"unknown\"));\n return null;"
21
+ },
22
+ {
23
+ "file": "dist/index.js",
24
+ "before_sha256": "9fa475ad64993e9c55a8a9443185f90774bbc5bf19e1c85d1398aadd629edd88",
25
+ "after_sha256": "43175904fd64f9b56ff6d53ee423c4512e05ededa48805c942ba0f8854427ebb",
26
+ "before": ".onRequest(\"authentication/status\", emptyExtensionParamsParser,",
27
+ "after": ".onRequest(axomQuota.READ, emptyExtensionParamsParser, async () => { const agent=getAgent(); const account=(await agent.runWithProcessCheck(()=>agent.codexAcpClient.getAccount())).account; if (!account) throw new Error(\"Not authenticated\"); if (account.type !== \"chatgpt\") return axomQuota.codex({},account.type); const raw=await agent.runWithProcessCheck(()=>agent.codexAcpClient.getRateLimits()); const snapshot=axomQuota.codex(raw,account.type); axomQuotaNotify(snapshot); return snapshot; }).onRequest(\"authentication/status\", emptyExtensionParamsParser,"
28
+ },
29
+ {
30
+ "file": "dist/index.js",
31
+ "before_sha256": "43175904fd64f9b56ff6d53ee423c4512e05ededa48805c942ba0f8854427ebb",
32
+ "after_sha256": "ee0276757b18277f5ee827fa08f12df52cb7266d33b5d82d90e538c47efced45",
33
+ "before": "#!/usr/bin/env node\nimport { createRequire as __createRequire } from 'module'; const require = __cre",
34
+ "after": "#!/usr/bin/env node\n\nconst axomQuota = (() => {\n/** Account quota normalization. No I/O: passive events must never trigger a query. */\nconst READ = '_axom/account/rateLimits/read';\nconst UPDATED = '_axom/account/rateLimits/updated';\nconst CAPABILITY = { version: 2, read: true, updated: true };\nconst percent = v => typeof v === 'number' && Number.isFinite(v) ? Math.max(0, Math.min(100, v)) : null;\nconst finite = v => { if (v == null || typeof v === 'boolean') return null; const n = typeof v === 'number' ? v : Number(v); return Number.isFinite(n) ? n : null; };\nconst time = (v, milliseconds = false) => {\n if (v == null || v === 0) return null;\n const d = new Date(typeof v === 'number' ? v * (milliseconds ? 1 : 1000) : v);\n return Number.isFinite(d.getTime()) ? d.toISOString() : null;\n};\nconst status = v => ({ allowed: 'allowed', allowed_warning: 'warning', rejected: 'blocked', normal: 'allowed' }[v] ?? 'unknown');\n/** Canonical window key rides the snapshot; the UI owns the key → display-label translation.\n * Windows without a canonical key keep their vendor content untouched. */\nconst windowKey = (seconds, period) => period === 'month' ? 'monthly' : seconds === 18000 ? 'five_hour' : seconds === 604800 ? 'weekly' : null;\nconst base = (provider, mode, raw, observedAt) => ({ schemaVersion: 1, updateMode: 'snapshot', provider, billingMode: mode, availability: mode === 'subscription' ? 'available' : mode === 'unknown' ? 'unknown' : 'not_applicable', observedAt: observedAt ?? new Date().toISOString(), plan: null, limits: [], balances: [], _meta: { ['_' + provider]: structuredClone(raw) } });\nfunction codex(raw, authType = 'chatgpt', observedAt) {\n const out = base('codex', ['chatgpt', 'chatgptAuthTokens'].includes(authType) ? 'subscription' : ['apiKey','apikey','amazonBedrock'].includes(authType) ? 'non_subscription' : 'unknown', raw, observedAt);\n if (out.billingMode !== 'subscription') return out;\n const buckets = Object.entries(raw.rateLimitsByLimitId ?? {});\n if (!buckets.length && raw.rateLimits) buckets.push([raw.rateLimits.limitId ?? 'codex', raw.rateLimits]);\n for (const [id, bucket] of buckets) {\n if (!bucket) continue;\n if (bucket.planType) out.plan = { id: bucket.planType, label: bucket.planType };\n for (const name of ['primary', 'secondary']) {\n const w = bucket[name]; if (!w) continue;\n const seconds = w.windowDurationMins == null ? null : w.windowDurationMins * 60;\n out.limits.push({ id: `${id}.${name}`, label: bucket.limitName ?? id, kind: 'subscription', windowSeconds: seconds, key: windowKey(seconds), usedPercent: percent(w.usedPercent), resetsAt: time(w.resetsAt), status: bucket.rateLimitReachedType ? 'blocked' : 'unknown', scope: { bucket: id } });\n }\n if (bucket.credits) out.balances.push({ id: `${id}.credits`, label: 'Credits', remaining: bucket.credits.balance ?? null, unit: 'credits', unlimited: bucket.credits.unlimited === true });\n }\n // Account IDs must not escape via provider extensions.\n delete out._meta._codex.accountId;\n return out;\n}\n/** App-server notifications describe changed buckets, not the complete account read. */\nfunction codexEvent(raw, authType, observedAt) {\n return { ...codex(raw, authType, observedAt), updateMode: 'delta' };\n}\nfunction claude(raw, observedAt) {\n const mode = raw.billing_mode ?? (raw.subscription_type || raw.rate_limits_available === true ? 'subscription' : 'unknown');\n const out = base('claude', mode, raw, observedAt);\n out.plan = raw.subscription_type ? { id: raw.subscription_type, label: raw.subscription_type } : null;\n if (mode === 'subscription' && !raw.rate_limits_available) out.availability = 'unavailable';\n const limits = raw.rate_limits ?? {};\n const rows = Array.isArray(limits.limits) ? limits.limits : [];\n if (rows.length) {\n rows.forEach((w, i) => {\n const seconds = w.group === 'session' ? 18000 : w.group === 'weekly' ? 604800 : null;\n out.limits.push({ id: `claude.${w.kind}.${i}`, label: w.scope?.model?.display_name ?? w.kind, kind: 'subscription', windowSeconds: seconds, key: windowKey(seconds), usedPercent: percent(w.percent), resetsAt: time(w.resets_at), status: status(w.severity), active: w.is_active ?? null, scope: w.scope ?? null });\n });\n } else for (const [id, seconds] of [['five_hour', 18000], ['seven_day', 604800], ['seven_day_opus',604800], ['seven_day_sonnet',604800]]) {\n const w = limits[id]; if (!w) continue;\n out.limits.push({ id: `claude.${id}`, label: id, kind: 'subscription', windowSeconds: seconds, key: windowKey(seconds), usedPercent: percent(w.utilization), resetsAt: time(w.resets_at), status: 'unknown', scope: id.endsWith('opus') || id.endsWith('sonnet') ? { model: id.split('_').at(-1) } : null });\n }\n const spend=limits.spend;\n if(spend?.enabled && spend.used && spend.limit) out.limits.push({id:'claude.extra_spend',label:'Extra usage',kind:'spend',windowSeconds:null,usedPercent:percent(spend.percent),resetsAt:null,status:status(spend.severity),used:String(spend.used.amount_minor),limit:String(spend.limit.amount_minor),unit:'money',currency:spend.used.currency,exponent:spend.used.exponent});\n if(spend?.balance?.amount_minor != null) out.balances.push({id:'claude.extra_balance',label:'Extra usage balance',remaining:String(spend.balance.amount_minor),unit:'money',currency:spend.balance.currency,exponent:spend.balance.exponent});\n return out;\n}\nfunction claudeEvent(raw, previous = null, observedAt) {\n const out = previous ? structuredClone(previous) : base('claude',raw.rateLimitType?'subscription':'unknown',{},observedAt);\n out.updateMode = 'delta';\n out.balances = [];\n out.observedAt = observedAt ?? new Date().toISOString();\n // Other sessions may have refreshed the account since this process's cached read.\n // Send only facts from this event; the server owns the merged account observation.\n out.plan = null;\n out._meta = { _claude: { rateLimitEvent: raw } };\n const seconds = raw.rateLimitType === 'five_hour' ? 18000 : raw.rateLimitType?.startsWith('seven_day') ? 604800 : null;\n if (seconds !== null) {\n const id = `claude.${raw.rateLimitType}`;\n const scoped = !['five_hour','seven_day'].includes(raw.rateLimitType);\n const prior=out.limits.find(w => w.windowSeconds===seconds && JSON.stringify(w.scope??null)===JSON.stringify(scoped ? {model:raw.rateLimitType.split('_').at(-1)}:null));\n const row = { id: prior?.id ?? id, label: raw.rateLimitType, kind: 'subscription', windowSeconds: seconds, key: windowKey(seconds), usedPercent: raw.utilization == null ? null : percent(raw.utilization * 100), resetsAt: time(raw.resetsAt), status: status(raw.status), scope: scoped ? { model: raw.rateLimitType.split('_').at(-1) } : null };\n // Event utilization is a ratio, unlike get_usage's percentage. Match the existing window without duplicating it.\n out.limits = [row];\n } else out.limits = [];\n return out;\n}\nfunction kimi(raw, observedAt) {\n const quota = raw.quota ?? raw;\n const out = base('kimi','subscription',quota,observedAt);\n for (const [id, seconds] of [['limit5h',18000],['limit7d',604800],['monthTotal',null]]) {\n const w = quota.usages?.[id]; if (!w) continue;\n out.limits.push({ id: `kimi.${id}`, label: id, kind: 'subscription', windowSeconds: seconds, period: id === 'monthTotal' ? 'month' : null, key: windowKey(seconds, id === 'monthTotal' ? 'month' : null), usedPercent: percent(w.usedRatio * 100), resetsAt: time(w.resetAt), status: 'unknown' });\n }\n const wallet = quota.extraUsage;\n if (wallet) out.balances.push({ id:'kimi.extra',label:'Extra usage',remaining:String(wallet.balanceCents),unit:'money',currency:wallet.currency,exponent:2 });\n return out;\n}\n/** GLM quota `percentage` is the USED share (measured: 5% for 58/1000); counters win when present.\n * Counter-derived shares are rounded to 2 decimals — 100-94.2 keeps fp noise otherwise. */\nconst glmPercent = limit => {\n const used = finite(limit.currentValue), remaining = finite(limit.remaining), total = finite(limit.usage);\n const cap = remaining !== null && used !== null ? remaining + used : total;\n const round2 = v => v == null ? null : Math.round(v * 100) / 100;\n if (cap !== null && cap > 0 && remaining !== null && remaining >= 0 && remaining <= cap) return round2(100 - percent(remaining / cap * 100));\n if (cap !== null && cap > 0 && used !== null && used >= 0 && used <= cap) return round2(percent(used / cap * 100));\n return percent(limit.percentage);\n};\nfunction zcode(raw, observedAt) {\n // Raw: the GLM quota endpoint's data block — { level, limits: [{ type, number, usage,\n // currentValue, remaining, percentage, nextResetTime(epoch ms), usageDetails }] }. Which windows\n // exist is plan-dependent (measured pro plan: 5h prompts + a tool/MCP allowance; no weekly row —\n // a `number: 7` window maps when a plan returns one, and nothing is invented when it doesn't).\n const limits = Array.isArray(raw?.limits) ? raw.limits : [];\n const level = typeof raw?.level === 'string' && raw.level ? raw.level : null;\n const out = base('zcode', limits.length ? 'subscription' : 'unknown', raw, observedAt);\n out.plan = level ? { id: level, label: level } : null;\n let tools = 0;\n limits.forEach((limit, i) => {\n if (!limit || typeof limit !== 'object') return;\n const resetsAt = time(limit.nextResetTime, true);\n if (limit.type === 'TOKENS_LIMIT') {\n // GLM window convention: number 5 → the 5-hour prompt window, 7 → weekly.\n // Ids mirror the adapter's own /quota keys (token_5h / token_week).\n const seconds = limit.number === 5 ? 18000 : limit.number === 7 ? 604800 : null;\n const label = limit.number === 5 ? '5h' : limit.number === 7 ? 'Week' : `token ${limit.number ?? i}`;\n const id = limit.number === 5 ? 'zcode.token_5h' : limit.number === 7 ? 'zcode.token_week' : `zcode.token_${limit.number ?? i}`;\n out.limits.push({ id, label, kind: 'subscription', windowSeconds: seconds, key: windowKey(seconds), usedPercent: glmPercent(limit), resetsAt, status: 'unknown' });\n } else if (limit.type === 'TIME_LIMIT' || limit.type === 'MCP_LIMIT') {\n // Built-in tool/MCP allowance: absolute counters (unit 'calls'); per-model detail stays in _meta.\n const used = finite(limit.currentValue), total = finite(limit.usage), remaining = finite(limit.remaining);\n out.limits.push({ id: tools ? `zcode.mcp.${i}` : 'zcode.mcp', label: 'MCP', kind: 'subscription', windowSeconds: null, usedPercent: glmPercent(limit), ...(used !== null && total !== null ? { used: String(used), limit: String(total), unit: 'calls' } : {}), resetsAt, status: remaining === 0 ? 'blocked' : used !== null || limit.percentage != null ? 'allowed' : 'unknown' });\n tools++;\n }\n });\n return out;\n}\nfunction commandcode(raw, observedAt) {\n const subscription = raw.subscription?.data ?? raw.subscription;\n const windows = raw.credits?.windowLimits ?? raw.windowLimits;\n const subscribed = ['active','trialing','past_due'].includes(subscription?.status) || windows?.limited === true;\n const mode = subscribed ? 'subscription' : subscription || windows ? 'non_subscription' : 'unknown';\n const out = base('commandcode',mode,raw,observedAt);\n out.plan = subscription?.planId ? { id:subscription.planId,label:subscription.planId } : null;\n for (const [id, seconds] of [['fiveHour',18000],['weekly',604800]]) {\n const w=windows?.[id]; if (!w) continue;\n out.limits.push({ id:`commandcode.${id}`,label:id,kind:'subscription',windowSeconds:seconds,key:windowKey(seconds),usedPercent:w.cap > 0 ? percent(w.used / w.cap * 100) : null,used:String(w.used),limit:String(w.cap),unit:'credits',resetsAt:time(w.resetAt,true),status:w.exceeded === true ? 'blocked' : 'allowed' });\n }\n const credits=raw.credits?.credits ?? raw.credits;\n if (subscribed && credits?.monthlyCredits != null) {\n const cap=raw.monthly?.limit;\n const known=typeof cap==='number' && Number.isFinite(cap) && cap>0;\n const used=known ? Math.max(0,cap-credits.monthlyCredits) : null;\n out.limits.push({id:'commandcode.monthly',label:'Monthly',kind:'subscription',windowSeconds:null,period:'month',key:windowKey(null,'month'),usedPercent:known?percent(used/cap*100):null,resetsAt:time(subscription?.currentPeriodEnd,true),status:'unknown',...(known?{used:String(used),limit:String(cap),unit:'credits'}:{})});\n }\n for (const [id,label] of [['monthlyCredits','Monthly credits'],['purchasedCredits','Purchased credits'],['freeCredits','Free credits']]) if (credits?.[id] != null) out.balances.push({ id:`commandcode.${id}`,label,remaining:String(credits[id]),unit:'credits' });\n return out;\n}\n\nreturn {READ,UPDATED,CAPABILITY,codex,codexEvent,claude,claudeEvent,kimi,commandcode,zcode};\n})();\nconst axomQuotaNotify = snapshot => process.stdout.write(JSON.stringify({jsonrpc:\"2.0\",method:axomQuota.UPDATED,params:snapshot})+\"\\n\");\nimport { createRequire as __createRequire } from 'module'; const require = __cre"
35
+ }
36
+ ]
37
+ }
@@ -0,0 +1,16 @@
1
+ {
2
+ "id": "codex-terminal-output",
3
+ "package": "@agentclientprotocol/codex-acp",
4
+ "versions": [
5
+ "2.3.0"
6
+ ],
7
+ "edits": [
8
+ {
9
+ "file": "dist/index.js",
10
+ "before_sha256": "d584cb7df74847661c3cf69d34de6ad3a24f6bc69787faeaff49a1fd9a3d8870",
11
+ "after_sha256": "504eba20f2ef3cc58bd1352c5684d65d9da8972287ec19a9726e8a923efae294",
12
+ "before": " terminalExit: { exitCode: item.exitCode }\n };\n}\nfunction singleAction(actions) {",
13
+ "after": " opaqueResult: { formatted_output: output2, exit_code: item.exitCode },\n terminalExit: { exitCode: item.exitCode }\n };\n}\nfunction singleAction(actions) {"
14
+ }
15
+ ]
16
+ }
@@ -0,0 +1,30 @@
1
+ {
2
+ "id": "commandcode-account-limits",
3
+ "package": "command-code",
4
+ "versions": [
5
+ "1.88.0"
6
+ ],
7
+ "edits": [
8
+ {
9
+ "file": "dist/cli.mjs",
10
+ "before_sha256": "600725f967cc98f6144066c24e850d934bd4a1f503c3bf82f2bd2584a8757dfd",
11
+ "after_sha256": "68526ea26e9f4452dd9e81eda974835beb1ca796be9c4175190288937179c342",
12
+ "before": "const m={[bn.INITIALIZE]:e=>handleInitialize(e),[bn.AUTHENTICATE]",
13
+ "after": "const m={[\"_axom/account/rateLimits/read\"]:async()=>{const raw=await fetchUsageData2(); if(raw.errors.length) throw new Error(\"Failed to fetch usage\"); const snapshot=axomQuota.commandcode({credits:raw.credits,subscription:raw.subscription,orgLimits:raw.whoami?.orgLimits??[],monthly:{limit:raw.subscription?.data?.status===\"active\"?(getPlanInfo({planId:raw.subscription?.data?.planId??\"\"})?.monthlyCredits??null):null}}); axomQuotaNotify(snapshot); return snapshot;},[bn.INITIALIZE]:e=>handleInitialize(e),[bn.AUTHENTICATE]"
14
+ },
15
+ {
16
+ "file": "dist/cli.mjs",
17
+ "before_sha256": "68526ea26e9f4452dd9e81eda974835beb1ca796be9c4175190288937179c342",
18
+ "after_sha256": "fd5d7f1c4a972f951ffcac2db5ab46360be00b7b73e889154f6a34745ee65307",
19
+ "before": "agentCapabilities:{loadSession:!0,promptCapabilities:",
20
+ "after": "agentCapabilities:{_meta:{\"axom/accountRateLimits\":{...axomQuota.CAPABILITY,provider:\"commandcode\"}},loadSession:!0,promptCapabilities:"
21
+ },
22
+ {
23
+ "file": "dist/cli.mjs",
24
+ "before_sha256": "fd5d7f1c4a972f951ffcac2db5ab46360be00b7b73e889154f6a34745ee65307",
25
+ "after_sha256": "e068c8efc99dfba125e0a288e4e172469e8dd97b5d8c9c9b3d5b8f5bf88e7cdc",
26
+ "before": "#!/usr/bin/env node\nimport*as e from\"path\";import n,{join as o,dirname as r,parse as s,resolve as a,",
27
+ "after": "#!/usr/bin/env node\n\nconst axomQuota = (() => {\n/** Account quota normalization. No I/O: passive events must never trigger a query. */\nconst READ = '_axom/account/rateLimits/read';\nconst UPDATED = '_axom/account/rateLimits/updated';\nconst CAPABILITY = { version: 2, read: true, updated: true };\nconst percent = v => typeof v === 'number' && Number.isFinite(v) ? Math.max(0, Math.min(100, v)) : null;\nconst finite = v => { if (v == null || typeof v === 'boolean') return null; const n = typeof v === 'number' ? v : Number(v); return Number.isFinite(n) ? n : null; };\nconst time = (v, milliseconds = false) => {\n if (v == null || v === 0) return null;\n const d = new Date(typeof v === 'number' ? v * (milliseconds ? 1 : 1000) : v);\n return Number.isFinite(d.getTime()) ? d.toISOString() : null;\n};\nconst status = v => ({ allowed: 'allowed', allowed_warning: 'warning', rejected: 'blocked', normal: 'allowed' }[v] ?? 'unknown');\n/** Canonical window key rides the snapshot; the UI owns the key → display-label translation.\n * Windows without a canonical key keep their vendor content untouched. */\nconst windowKey = (seconds, period) => period === 'month' ? 'monthly' : seconds === 18000 ? 'five_hour' : seconds === 604800 ? 'weekly' : null;\nconst base = (provider, mode, raw, observedAt) => ({ schemaVersion: 1, updateMode: 'snapshot', provider, billingMode: mode, availability: mode === 'subscription' ? 'available' : mode === 'unknown' ? 'unknown' : 'not_applicable', observedAt: observedAt ?? new Date().toISOString(), plan: null, limits: [], balances: [], _meta: { ['_' + provider]: structuredClone(raw) } });\nfunction codex(raw, authType = 'chatgpt', observedAt) {\n const out = base('codex', ['chatgpt', 'chatgptAuthTokens'].includes(authType) ? 'subscription' : ['apiKey','apikey','amazonBedrock'].includes(authType) ? 'non_subscription' : 'unknown', raw, observedAt);\n if (out.billingMode !== 'subscription') return out;\n const buckets = Object.entries(raw.rateLimitsByLimitId ?? {});\n if (!buckets.length && raw.rateLimits) buckets.push([raw.rateLimits.limitId ?? 'codex', raw.rateLimits]);\n for (const [id, bucket] of buckets) {\n if (!bucket) continue;\n if (bucket.planType) out.plan = { id: bucket.planType, label: bucket.planType };\n for (const name of ['primary', 'secondary']) {\n const w = bucket[name]; if (!w) continue;\n const seconds = w.windowDurationMins == null ? null : w.windowDurationMins * 60;\n out.limits.push({ id: `${id}.${name}`, label: bucket.limitName ?? id, kind: 'subscription', windowSeconds: seconds, key: windowKey(seconds), usedPercent: percent(w.usedPercent), resetsAt: time(w.resetsAt), status: bucket.rateLimitReachedType ? 'blocked' : 'unknown', scope: { bucket: id } });\n }\n if (bucket.credits) out.balances.push({ id: `${id}.credits`, label: 'Credits', remaining: bucket.credits.balance ?? null, unit: 'credits', unlimited: bucket.credits.unlimited === true });\n }\n // Account IDs must not escape via provider extensions.\n delete out._meta._codex.accountId;\n return out;\n}\n/** App-server notifications describe changed buckets, not the complete account read. */\nfunction codexEvent(raw, authType, observedAt) {\n return { ...codex(raw, authType, observedAt), updateMode: 'delta' };\n}\nfunction claude(raw, observedAt) {\n const mode = raw.billing_mode ?? (raw.subscription_type || raw.rate_limits_available === true ? 'subscription' : 'unknown');\n const out = base('claude', mode, raw, observedAt);\n out.plan = raw.subscription_type ? { id: raw.subscription_type, label: raw.subscription_type } : null;\n if (mode === 'subscription' && !raw.rate_limits_available) out.availability = 'unavailable';\n const limits = raw.rate_limits ?? {};\n const rows = Array.isArray(limits.limits) ? limits.limits : [];\n if (rows.length) {\n rows.forEach((w, i) => {\n const seconds = w.group === 'session' ? 18000 : w.group === 'weekly' ? 604800 : null;\n out.limits.push({ id: `claude.${w.kind}.${i}`, label: w.scope?.model?.display_name ?? w.kind, kind: 'subscription', windowSeconds: seconds, key: windowKey(seconds), usedPercent: percent(w.percent), resetsAt: time(w.resets_at), status: status(w.severity), active: w.is_active ?? null, scope: w.scope ?? null });\n });\n } else for (const [id, seconds] of [['five_hour', 18000], ['seven_day', 604800], ['seven_day_opus',604800], ['seven_day_sonnet',604800]]) {\n const w = limits[id]; if (!w) continue;\n out.limits.push({ id: `claude.${id}`, label: id, kind: 'subscription', windowSeconds: seconds, key: windowKey(seconds), usedPercent: percent(w.utilization), resetsAt: time(w.resets_at), status: 'unknown', scope: id.endsWith('opus') || id.endsWith('sonnet') ? { model: id.split('_').at(-1) } : null });\n }\n const spend=limits.spend;\n if(spend?.enabled && spend.used && spend.limit) out.limits.push({id:'claude.extra_spend',label:'Extra usage',kind:'spend',windowSeconds:null,usedPercent:percent(spend.percent),resetsAt:null,status:status(spend.severity),used:String(spend.used.amount_minor),limit:String(spend.limit.amount_minor),unit:'money',currency:spend.used.currency,exponent:spend.used.exponent});\n if(spend?.balance?.amount_minor != null) out.balances.push({id:'claude.extra_balance',label:'Extra usage balance',remaining:String(spend.balance.amount_minor),unit:'money',currency:spend.balance.currency,exponent:spend.balance.exponent});\n return out;\n}\nfunction claudeEvent(raw, previous = null, observedAt) {\n const out = previous ? structuredClone(previous) : base('claude',raw.rateLimitType?'subscription':'unknown',{},observedAt);\n out.updateMode = 'delta';\n out.balances = [];\n out.observedAt = observedAt ?? new Date().toISOString();\n // Other sessions may have refreshed the account since this process's cached read.\n // Send only facts from this event; the server owns the merged account observation.\n out.plan = null;\n out._meta = { _claude: { rateLimitEvent: raw } };\n const seconds = raw.rateLimitType === 'five_hour' ? 18000 : raw.rateLimitType?.startsWith('seven_day') ? 604800 : null;\n if (seconds !== null) {\n const id = `claude.${raw.rateLimitType}`;\n const scoped = !['five_hour','seven_day'].includes(raw.rateLimitType);\n const prior=out.limits.find(w => w.windowSeconds===seconds && JSON.stringify(w.scope??null)===JSON.stringify(scoped ? {model:raw.rateLimitType.split('_').at(-1)}:null));\n const row = { id: prior?.id ?? id, label: raw.rateLimitType, kind: 'subscription', windowSeconds: seconds, key: windowKey(seconds), usedPercent: raw.utilization == null ? null : percent(raw.utilization * 100), resetsAt: time(raw.resetsAt), status: status(raw.status), scope: scoped ? { model: raw.rateLimitType.split('_').at(-1) } : null };\n // Event utilization is a ratio, unlike get_usage's percentage. Match the existing window without duplicating it.\n out.limits = [row];\n } else out.limits = [];\n return out;\n}\nfunction kimi(raw, observedAt) {\n const quota = raw.quota ?? raw;\n const out = base('kimi','subscription',quota,observedAt);\n for (const [id, seconds] of [['limit5h',18000],['limit7d',604800],['monthTotal',null]]) {\n const w = quota.usages?.[id]; if (!w) continue;\n out.limits.push({ id: `kimi.${id}`, label: id, kind: 'subscription', windowSeconds: seconds, period: id === 'monthTotal' ? 'month' : null, key: windowKey(seconds, id === 'monthTotal' ? 'month' : null), usedPercent: percent(w.usedRatio * 100), resetsAt: time(w.resetAt), status: 'unknown' });\n }\n const wallet = quota.extraUsage;\n if (wallet) out.balances.push({ id:'kimi.extra',label:'Extra usage',remaining:String(wallet.balanceCents),unit:'money',currency:wallet.currency,exponent:2 });\n return out;\n}\n/** GLM quota `percentage` is the USED share (measured: 5% for 58/1000); counters win when present.\n * Counter-derived shares are rounded to 2 decimals — 100-94.2 keeps fp noise otherwise. */\nconst glmPercent = limit => {\n const used = finite(limit.currentValue), remaining = finite(limit.remaining), total = finite(limit.usage);\n const cap = remaining !== null && used !== null ? remaining + used : total;\n const round2 = v => v == null ? null : Math.round(v * 100) / 100;\n if (cap !== null && cap > 0 && remaining !== null && remaining >= 0 && remaining <= cap) return round2(100 - percent(remaining / cap * 100));\n if (cap !== null && cap > 0 && used !== null && used >= 0 && used <= cap) return round2(percent(used / cap * 100));\n return percent(limit.percentage);\n};\nfunction zcode(raw, observedAt) {\n // Raw: the GLM quota endpoint's data block — { level, limits: [{ type, number, usage,\n // currentValue, remaining, percentage, nextResetTime(epoch ms), usageDetails }] }. Which windows\n // exist is plan-dependent (measured pro plan: 5h prompts + a tool/MCP allowance; no weekly row —\n // a `number: 7` window maps when a plan returns one, and nothing is invented when it doesn't).\n const limits = Array.isArray(raw?.limits) ? raw.limits : [];\n const level = typeof raw?.level === 'string' && raw.level ? raw.level : null;\n const out = base('zcode', limits.length ? 'subscription' : 'unknown', raw, observedAt);\n out.plan = level ? { id: level, label: level } : null;\n let tools = 0;\n limits.forEach((limit, i) => {\n if (!limit || typeof limit !== 'object') return;\n const resetsAt = time(limit.nextResetTime, true);\n if (limit.type === 'TOKENS_LIMIT') {\n // GLM window convention: number 5 → the 5-hour prompt window, 7 → weekly.\n // Ids mirror the adapter's own /quota keys (token_5h / token_week).\n const seconds = limit.number === 5 ? 18000 : limit.number === 7 ? 604800 : null;\n const label = limit.number === 5 ? '5h' : limit.number === 7 ? 'Week' : `token ${limit.number ?? i}`;\n const id = limit.number === 5 ? 'zcode.token_5h' : limit.number === 7 ? 'zcode.token_week' : `zcode.token_${limit.number ?? i}`;\n out.limits.push({ id, label, kind: 'subscription', windowSeconds: seconds, key: windowKey(seconds), usedPercent: glmPercent(limit), resetsAt, status: 'unknown' });\n } else if (limit.type === 'TIME_LIMIT' || limit.type === 'MCP_LIMIT') {\n // Built-in tool/MCP allowance: absolute counters (unit 'calls'); per-model detail stays in _meta.\n const used = finite(limit.currentValue), total = finite(limit.usage), remaining = finite(limit.remaining);\n out.limits.push({ id: tools ? `zcode.mcp.${i}` : 'zcode.mcp', label: 'MCP', kind: 'subscription', windowSeconds: null, usedPercent: glmPercent(limit), ...(used !== null && total !== null ? { used: String(used), limit: String(total), unit: 'calls' } : {}), resetsAt, status: remaining === 0 ? 'blocked' : used !== null || limit.percentage != null ? 'allowed' : 'unknown' });\n tools++;\n }\n });\n return out;\n}\nfunction commandcode(raw, observedAt) {\n const subscription = raw.subscription?.data ?? raw.subscription;\n const windows = raw.credits?.windowLimits ?? raw.windowLimits;\n const subscribed = ['active','trialing','past_due'].includes(subscription?.status) || windows?.limited === true;\n const mode = subscribed ? 'subscription' : subscription || windows ? 'non_subscription' : 'unknown';\n const out = base('commandcode',mode,raw,observedAt);\n out.plan = subscription?.planId ? { id:subscription.planId,label:subscription.planId } : null;\n for (const [id, seconds] of [['fiveHour',18000],['weekly',604800]]) {\n const w=windows?.[id]; if (!w) continue;\n out.limits.push({ id:`commandcode.${id}`,label:id,kind:'subscription',windowSeconds:seconds,key:windowKey(seconds),usedPercent:w.cap > 0 ? percent(w.used / w.cap * 100) : null,used:String(w.used),limit:String(w.cap),unit:'credits',resetsAt:time(w.resetAt,true),status:w.exceeded === true ? 'blocked' : 'allowed' });\n }\n const credits=raw.credits?.credits ?? raw.credits;\n if (subscribed && credits?.monthlyCredits != null) {\n const cap=raw.monthly?.limit;\n const known=typeof cap==='number' && Number.isFinite(cap) && cap>0;\n const used=known ? Math.max(0,cap-credits.monthlyCredits) : null;\n out.limits.push({id:'commandcode.monthly',label:'Monthly',kind:'subscription',windowSeconds:null,period:'month',key:windowKey(null,'month'),usedPercent:known?percent(used/cap*100):null,resetsAt:time(subscription?.currentPeriodEnd,true),status:'unknown',...(known?{used:String(used),limit:String(cap),unit:'credits'}:{})});\n }\n for (const [id,label] of [['monthlyCredits','Monthly credits'],['purchasedCredits','Purchased credits'],['freeCredits','Free credits']]) if (credits?.[id] != null) out.balances.push({ id:`commandcode.${id}`,label,remaining:String(credits[id]),unit:'credits' });\n return out;\n}\n\nreturn {READ,UPDATED,CAPABILITY,codex,codexEvent,claude,claudeEvent,kimi,commandcode,zcode};\n})();\nconst axomQuotaNotify = snapshot => process.stdout.write(JSON.stringify({jsonrpc:\"2.0\",method:axomQuota.UPDATED,params:snapshot})+\"\\n\");\nimport*as e from\"path\";import n,{join as o,dirname as r,parse as s,resolve as a,"
28
+ }
29
+ ]
30
+ }
@@ -0,0 +1,12 @@
1
+ const quotaPattern = /(?:quota|rate[\s_-]*limit|credit(?:s)?[\s_-]*(?:exhausted|depleted|insufficient)|insufficient[\s_-]*(?:quota|credit|balance)|(?:quota|credit)[\s_-]*exhausted|429)/i;
2
+
3
+ export const quotaErrorDetail = value => {
4
+ const line = typeof value === 'string' ? value.replace(/\s+/g, ' ').trim() : '';
5
+ if (!line || !quotaPattern.test(line)) return null;
6
+ return line.length > 300 ? `${line.slice(0, 297)}...` : line;
7
+ };
8
+
9
+ export const quotaRpcError = detail => ({
10
+ code: -32000,
11
+ message: `Kimi quota is exhausted or rate limited.${detail ? ` Provider detail: ${detail}` : ''}`,
12
+ });