open-multi-agent-kit 0.98.5 → 1.0.0

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 (385) hide show
  1. package/CHANGELOG.md +48 -0
  2. package/README.md +4 -3
  3. package/dist/bun/cli.d.ts.map +1 -1
  4. package/dist/bun/cli.js +1 -0
  5. package/dist/bun/cli.js.map +1 -1
  6. package/dist/bun/register-bundled-coding-agent.d.ts +10 -0
  7. package/dist/bun/register-bundled-coding-agent.d.ts.map +1 -0
  8. package/dist/bun/register-bundled-coding-agent.js +12 -0
  9. package/dist/bun/register-bundled-coding-agent.js.map +1 -0
  10. package/dist/cli/args.d.ts +1 -1
  11. package/dist/cli/args.d.ts.map +1 -1
  12. package/dist/cli/args.js +1 -1
  13. package/dist/cli/args.js.map +1 -1
  14. package/dist/cli/help.d.ts.map +1 -1
  15. package/dist/cli/help.js +2 -1
  16. package/dist/cli/help.js.map +1 -1
  17. package/dist/cli.d.ts.map +1 -1
  18. package/dist/cli.js +14 -2
  19. package/dist/cli.js.map +1 -1
  20. package/dist/commands/neo-cli.d.ts +9 -0
  21. package/dist/commands/neo-cli.d.ts.map +1 -0
  22. package/dist/commands/neo-cli.js +61 -0
  23. package/dist/commands/neo-cli.js.map +1 -0
  24. package/dist/commands/verified-run-cli.d.ts.map +1 -1
  25. package/dist/commands/verified-run-cli.js +2 -2
  26. package/dist/commands/verified-run-cli.js.map +1 -1
  27. package/dist/core/agent-session.d.ts +29 -2
  28. package/dist/core/agent-session.d.ts.map +1 -1
  29. package/dist/core/agent-session.js +126 -32
  30. package/dist/core/agent-session.js.map +1 -1
  31. package/dist/core/bundled-skills.d.ts +4 -0
  32. package/dist/core/bundled-skills.d.ts.map +1 -0
  33. package/dist/core/bundled-skills.js +32 -0
  34. package/dist/core/bundled-skills.js.map +1 -0
  35. package/dist/core/cli-diagnostics.d.ts +6 -0
  36. package/dist/core/cli-diagnostics.d.ts.map +1 -0
  37. package/dist/core/cli-diagnostics.js +20 -0
  38. package/dist/core/cli-diagnostics.js.map +1 -0
  39. package/dist/core/context-budget-headroom.d.ts +12 -0
  40. package/dist/core/context-budget-headroom.d.ts.map +1 -1
  41. package/dist/core/context-budget-headroom.js +35 -0
  42. package/dist/core/context-budget-headroom.js.map +1 -1
  43. package/dist/core/context-budget-v2-input-validation.d.ts +4 -0
  44. package/dist/core/context-budget-v2-input-validation.d.ts.map +1 -0
  45. package/dist/core/context-budget-v2-input-validation.js +79 -0
  46. package/dist/core/context-budget-v2-input-validation.js.map +1 -0
  47. package/dist/core/context-budget-v2-observability.d.ts +15 -0
  48. package/dist/core/context-budget-v2-observability.d.ts.map +1 -0
  49. package/dist/core/context-budget-v2-observability.js +35 -0
  50. package/dist/core/context-budget-v2-observability.js.map +1 -0
  51. package/dist/core/context-budget-v2-planned-items.d.ts +5 -0
  52. package/dist/core/context-budget-v2-planned-items.d.ts.map +1 -0
  53. package/dist/core/context-budget-v2-planned-items.js +40 -0
  54. package/dist/core/context-budget-v2-planned-items.js.map +1 -0
  55. package/dist/core/context-budget-v2-planner.d.ts.map +1 -1
  56. package/dist/core/context-budget-v2-planner.js +57 -38
  57. package/dist/core/context-budget-v2-planner.js.map +1 -1
  58. package/dist/core/context-budget-v2-selection.d.ts +19 -3
  59. package/dist/core/context-budget-v2-selection.d.ts.map +1 -1
  60. package/dist/core/context-budget-v2-selection.js +35 -57
  61. package/dist/core/context-budget-v2-selection.js.map +1 -1
  62. package/dist/core/context-budget-v2-tiers.d.ts.map +1 -1
  63. package/dist/core/context-budget-v2-tiers.js +9 -42
  64. package/dist/core/context-budget-v2-tiers.js.map +1 -1
  65. package/dist/core/context-budget-v2-types.d.ts +8 -1
  66. package/dist/core/context-budget-v2-types.d.ts.map +1 -1
  67. package/dist/core/context-budget-v2-types.js.map +1 -1
  68. package/dist/core/devin-harness-dispatch.d.ts +12 -0
  69. package/dist/core/devin-harness-dispatch.d.ts.map +1 -0
  70. package/dist/core/devin-harness-dispatch.js +12 -0
  71. package/dist/core/devin-harness-dispatch.js.map +1 -0
  72. package/dist/core/devin-harness.d.ts +53 -0
  73. package/dist/core/devin-harness.d.ts.map +1 -0
  74. package/dist/core/devin-harness.js +112 -0
  75. package/dist/core/devin-harness.js.map +1 -0
  76. package/dist/core/domain-dispatch.d.ts +4 -1
  77. package/dist/core/domain-dispatch.d.ts.map +1 -1
  78. package/dist/core/domain-dispatch.js +5 -0
  79. package/dist/core/domain-dispatch.js.map +1 -1
  80. package/dist/core/domain-loadouts-provider-harness.d.ts +12 -0
  81. package/dist/core/domain-loadouts-provider-harness.d.ts.map +1 -0
  82. package/dist/core/domain-loadouts-provider-harness.js +122 -0
  83. package/dist/core/domain-loadouts-provider-harness.js.map +1 -0
  84. package/dist/core/domain-loadouts.d.ts +2 -33
  85. package/dist/core/domain-loadouts.d.ts.map +1 -1
  86. package/dist/core/domain-loadouts.js +3 -56
  87. package/dist/core/domain-loadouts.js.map +1 -1
  88. package/dist/core/domain-profile.d.ts +40 -0
  89. package/dist/core/domain-profile.d.ts.map +1 -0
  90. package/dist/core/domain-profile.js +7 -0
  91. package/dist/core/domain-profile.js.map +1 -0
  92. package/dist/core/extensions/bundled-virtual-modules.d.ts +15 -0
  93. package/dist/core/extensions/bundled-virtual-modules.d.ts.map +1 -0
  94. package/dist/core/extensions/bundled-virtual-modules.js +63 -0
  95. package/dist/core/extensions/bundled-virtual-modules.js.map +1 -0
  96. package/dist/core/extensions/loader.d.ts.map +1 -1
  97. package/dist/core/extensions/loader.js +2 -42
  98. package/dist/core/extensions/loader.js.map +1 -1
  99. package/dist/core/extensions/runner.d.ts +1 -0
  100. package/dist/core/extensions/runner.d.ts.map +1 -1
  101. package/dist/core/extensions/runner.js +6 -0
  102. package/dist/core/extensions/runner.js.map +1 -1
  103. package/dist/core/extensions/types.d.ts +10 -0
  104. package/dist/core/extensions/types.d.ts.map +1 -1
  105. package/dist/core/extensions/types.js.map +1 -1
  106. package/dist/core/grok-harness-dispatch.d.ts +6 -20
  107. package/dist/core/grok-harness-dispatch.d.ts.map +1 -1
  108. package/dist/core/grok-harness-dispatch.js +6 -55
  109. package/dist/core/grok-harness-dispatch.js.map +1 -1
  110. package/dist/core/grok-harness.d.ts +7 -9
  111. package/dist/core/grok-harness.d.ts.map +1 -1
  112. package/dist/core/grok-harness.js +9 -23
  113. package/dist/core/grok-harness.js.map +1 -1
  114. package/dist/core/harness-skills.d.ts +20 -0
  115. package/dist/core/harness-skills.d.ts.map +1 -0
  116. package/dist/core/harness-skills.js +36 -0
  117. package/dist/core/harness-skills.js.map +1 -0
  118. package/dist/core/index.d.ts +2 -1
  119. package/dist/core/index.d.ts.map +1 -1
  120. package/dist/core/index.js +2 -1
  121. package/dist/core/index.js.map +1 -1
  122. package/dist/core/loadout-runtime-state.d.ts +25 -0
  123. package/dist/core/loadout-runtime-state.d.ts.map +1 -0
  124. package/dist/core/loadout-runtime-state.js +39 -0
  125. package/dist/core/loadout-runtime-state.js.map +1 -0
  126. package/dist/core/loadout-runtime.d.ts +4 -20
  127. package/dist/core/loadout-runtime.d.ts.map +1 -1
  128. package/dist/core/loadout-runtime.js +6 -13
  129. package/dist/core/loadout-runtime.js.map +1 -1
  130. package/dist/core/mcp/client.d.ts +27 -6
  131. package/dist/core/mcp/client.d.ts.map +1 -1
  132. package/dist/core/mcp/client.js +80 -20
  133. package/dist/core/mcp/client.js.map +1 -1
  134. package/dist/core/mcp/manager.d.ts +10 -1
  135. package/dist/core/mcp/manager.d.ts.map +1 -1
  136. package/dist/core/mcp/manager.js +68 -9
  137. package/dist/core/mcp/manager.js.map +1 -1
  138. package/dist/core/mcp/protocol.d.ts +9 -3
  139. package/dist/core/mcp/protocol.d.ts.map +1 -1
  140. package/dist/core/mcp/protocol.js +61 -15
  141. package/dist/core/mcp/protocol.js.map +1 -1
  142. package/dist/core/mcp/stdio-transport.d.ts +12 -1
  143. package/dist/core/mcp/stdio-transport.d.ts.map +1 -1
  144. package/dist/core/mcp/stdio-transport.js +30 -2
  145. package/dist/core/mcp/stdio-transport.js.map +1 -1
  146. package/dist/core/mcp-descriptor-injection.d.ts +26 -0
  147. package/dist/core/mcp-descriptor-injection.d.ts.map +1 -0
  148. package/dist/core/mcp-descriptor-injection.js +25 -0
  149. package/dist/core/mcp-descriptor-injection.js.map +1 -0
  150. package/dist/core/mcp-public-presets.d.ts +1 -3
  151. package/dist/core/mcp-public-presets.d.ts.map +1 -1
  152. package/dist/core/mcp-public-presets.js +3 -15
  153. package/dist/core/mcp-public-presets.js.map +1 -1
  154. package/dist/core/model-registry.d.ts.map +1 -1
  155. package/dist/core/model-registry.js +24 -3
  156. package/dist/core/model-registry.js.map +1 -1
  157. package/dist/core/model-resolver.d.ts +1 -41
  158. package/dist/core/model-resolver.d.ts.map +1 -1
  159. package/dist/core/model-resolver.js +2 -49
  160. package/dist/core/model-resolver.js.map +1 -1
  161. package/dist/core/neo/catalog.d.ts +20 -0
  162. package/dist/core/neo/catalog.d.ts.map +1 -0
  163. package/dist/core/neo/catalog.js +50 -0
  164. package/dist/core/neo/catalog.js.map +1 -0
  165. package/dist/core/neo/setup.d.ts +3 -0
  166. package/dist/core/neo/setup.d.ts.map +1 -0
  167. package/dist/core/neo/setup.js +47 -0
  168. package/dist/core/neo/setup.js.map +1 -0
  169. package/dist/core/provider-default-models.d.ts +43 -0
  170. package/dist/core/provider-default-models.d.ts.map +1 -0
  171. package/dist/core/provider-default-models.js +51 -0
  172. package/dist/core/provider-default-models.js.map +1 -0
  173. package/dist/core/provider-display-names.d.ts.map +1 -1
  174. package/dist/core/provider-display-names.js +2 -0
  175. package/dist/core/provider-display-names.js.map +1 -1
  176. package/dist/core/provider-error-classification.d.ts +57 -0
  177. package/dist/core/provider-error-classification.d.ts.map +1 -0
  178. package/dist/core/provider-error-classification.js +103 -0
  179. package/dist/core/provider-error-classification.js.map +1 -0
  180. package/dist/core/provider-harness-dispatch.d.ts +63 -0
  181. package/dist/core/provider-harness-dispatch.d.ts.map +1 -0
  182. package/dist/core/provider-harness-dispatch.js +60 -0
  183. package/dist/core/provider-harness-dispatch.js.map +1 -0
  184. package/dist/core/provider-resilience.d.ts +10 -0
  185. package/dist/core/provider-resilience.d.ts.map +1 -1
  186. package/dist/core/provider-resilience.js +36 -3
  187. package/dist/core/provider-resilience.js.map +1 -1
  188. package/dist/core/provider-usage-commandcode.d.ts +9 -0
  189. package/dist/core/provider-usage-commandcode.d.ts.map +1 -0
  190. package/dist/core/provider-usage-commandcode.js +198 -0
  191. package/dist/core/provider-usage-commandcode.js.map +1 -0
  192. package/dist/core/provider-usage-devin.d.ts +18 -0
  193. package/dist/core/provider-usage-devin.d.ts.map +1 -0
  194. package/dist/core/provider-usage-devin.js +71 -0
  195. package/dist/core/provider-usage-devin.js.map +1 -0
  196. package/dist/core/provider-usage-text.d.ts +5 -0
  197. package/dist/core/provider-usage-text.d.ts.map +1 -0
  198. package/dist/core/provider-usage-text.js +15 -0
  199. package/dist/core/provider-usage-text.js.map +1 -0
  200. package/dist/core/provider-usage-types.d.ts +2 -1
  201. package/dist/core/provider-usage-types.d.ts.map +1 -1
  202. package/dist/core/provider-usage-types.js.map +1 -1
  203. package/dist/core/provider-usage.d.ts +1 -2
  204. package/dist/core/provider-usage.d.ts.map +1 -1
  205. package/dist/core/provider-usage.js +18 -7
  206. package/dist/core/provider-usage.js.map +1 -1
  207. package/dist/core/resource-admission.d.ts +36 -0
  208. package/dist/core/resource-admission.d.ts.map +1 -1
  209. package/dist/core/resource-admission.js +59 -0
  210. package/dist/core/resource-admission.js.map +1 -1
  211. package/dist/core/resource-loader.d.ts.map +1 -1
  212. package/dist/core/resource-loader.js +2 -2
  213. package/dist/core/resource-loader.js.map +1 -1
  214. package/dist/core/run-execution-api.d.ts +1 -1
  215. package/dist/core/run-execution-api.d.ts.map +1 -1
  216. package/dist/core/run-execution-api.js.map +1 -1
  217. package/dist/core/run-usage-ledger.d.ts +47 -0
  218. package/dist/core/run-usage-ledger.d.ts.map +1 -0
  219. package/dist/core/run-usage-ledger.js +162 -0
  220. package/dist/core/run-usage-ledger.js.map +1 -0
  221. package/dist/core/run-usage-operation.d.ts +8 -0
  222. package/dist/core/run-usage-operation.d.ts.map +1 -0
  223. package/dist/core/run-usage-operation.js +12 -0
  224. package/dist/core/run-usage-operation.js.map +1 -0
  225. package/dist/core/sdk.d.ts.map +1 -1
  226. package/dist/core/sdk.js +20 -16
  227. package/dist/core/sdk.js.map +1 -1
  228. package/dist/core/session-failure-cause.d.ts.map +1 -1
  229. package/dist/core/session-failure-cause.js +14 -5
  230. package/dist/core/session-failure-cause.js.map +1 -1
  231. package/dist/core/session-prompt-lifecycle.d.ts +6 -0
  232. package/dist/core/session-prompt-lifecycle.d.ts.map +1 -1
  233. package/dist/core/session-prompt-lifecycle.js +47 -2
  234. package/dist/core/session-prompt-lifecycle.js.map +1 -1
  235. package/dist/core/session-termination.d.ts.map +1 -1
  236. package/dist/core/session-termination.js +4 -2
  237. package/dist/core/session-termination.js.map +1 -1
  238. package/dist/core/subagent-lane-authority.d.ts +28 -0
  239. package/dist/core/subagent-lane-authority.d.ts.map +1 -0
  240. package/dist/core/subagent-lane-authority.js +119 -0
  241. package/dist/core/subagent-lane-authority.js.map +1 -0
  242. package/dist/core/subagent-lane-contract.d.ts +114 -0
  243. package/dist/core/subagent-lane-contract.d.ts.map +1 -0
  244. package/dist/core/subagent-lane-contract.js +18 -0
  245. package/dist/core/subagent-lane-contract.js.map +1 -0
  246. package/dist/core/subagent-lane-launcher.d.ts +3 -6
  247. package/dist/core/subagent-lane-launcher.d.ts.map +1 -1
  248. package/dist/core/subagent-lane-launcher.js +53 -12
  249. package/dist/core/subagent-lane-launcher.js.map +1 -1
  250. package/dist/core/subagent-orchestration.d.ts +4 -17
  251. package/dist/core/subagent-orchestration.d.ts.map +1 -1
  252. package/dist/core/subagent-orchestration.js.map +1 -1
  253. package/dist/core/verified-run/broker.d.ts.map +1 -1
  254. package/dist/core/verified-run/broker.js +4 -1
  255. package/dist/core/verified-run/broker.js.map +1 -1
  256. package/dist/core/verified-run/dag-phase.d.ts +1 -1
  257. package/dist/core/verified-run/dag-phase.d.ts.map +1 -1
  258. package/dist/core/verified-run/dag-phase.js +46 -8
  259. package/dist/core/verified-run/dag-phase.js.map +1 -1
  260. package/dist/core/verified-run/dag-projection.d.ts.map +1 -1
  261. package/dist/core/verified-run/dag-projection.js +25 -12
  262. package/dist/core/verified-run/dag-projection.js.map +1 -1
  263. package/dist/core/verified-run/dag-types.d.ts +11 -0
  264. package/dist/core/verified-run/dag-types.d.ts.map +1 -1
  265. package/dist/core/verified-run/dag-types.js.map +1 -1
  266. package/dist/core/verified-run/event-parser.d.ts.map +1 -1
  267. package/dist/core/verified-run/event-parser.js +1 -0
  268. package/dist/core/verified-run/event-parser.js.map +1 -1
  269. package/dist/core/verified-run/owned-execution.d.ts +1 -0
  270. package/dist/core/verified-run/owned-execution.d.ts.map +1 -1
  271. package/dist/core/verified-run/owned-execution.js +7 -1
  272. package/dist/core/verified-run/owned-execution.js.map +1 -1
  273. package/dist/core/verified-run/process-projection.d.ts +13 -0
  274. package/dist/core/verified-run/process-projection.d.ts.map +1 -0
  275. package/dist/core/verified-run/process-projection.js +82 -0
  276. package/dist/core/verified-run/process-projection.js.map +1 -0
  277. package/dist/core/verified-run/projection.d.ts.map +1 -1
  278. package/dist/core/verified-run/projection.js +11 -55
  279. package/dist/core/verified-run/projection.js.map +1 -1
  280. package/dist/core/verified-run/run-types.d.ts +1 -0
  281. package/dist/core/verified-run/run-types.d.ts.map +1 -1
  282. package/dist/core/verified-run/run-types.js.map +1 -1
  283. package/dist/core/verified-run/task-execution.d.ts +9 -0
  284. package/dist/core/verified-run/task-execution.d.ts.map +1 -0
  285. package/dist/core/verified-run/task-execution.js +29 -0
  286. package/dist/core/verified-run/task-execution.js.map +1 -0
  287. package/dist/core/verified-run/writer-projection.d.ts.map +1 -1
  288. package/dist/core/verified-run/writer-projection.js +6 -5
  289. package/dist/core/verified-run/writer-projection.js.map +1 -1
  290. package/dist/core/workload-permit-pool.d.ts +1 -1
  291. package/dist/core/workload-permit-pool.d.ts.map +1 -1
  292. package/dist/core/workload-permit-pool.js +3 -0
  293. package/dist/core/workload-permit-pool.js.map +1 -1
  294. package/dist/guardrails/strict-evidence-approval-adapter.d.ts +34 -0
  295. package/dist/guardrails/strict-evidence-approval-adapter.d.ts.map +1 -0
  296. package/dist/guardrails/strict-evidence-approval-adapter.js +70 -0
  297. package/dist/guardrails/strict-evidence-approval-adapter.js.map +1 -0
  298. package/dist/index.d.ts +3 -0
  299. package/dist/index.d.ts.map +1 -1
  300. package/dist/index.js +1 -0
  301. package/dist/index.js.map +1 -1
  302. package/dist/main.d.ts.map +1 -1
  303. package/dist/main.js +11 -18
  304. package/dist/main.js.map +1 -1
  305. package/dist/modes/acp/acp-agent.d.ts +24 -0
  306. package/dist/modes/acp/acp-agent.d.ts.map +1 -0
  307. package/dist/modes/acp/acp-agent.js +133 -0
  308. package/dist/modes/acp/acp-agent.js.map +1 -0
  309. package/dist/modes/acp/acp-mode.d.ts +4 -0
  310. package/dist/modes/acp/acp-mode.d.ts.map +1 -0
  311. package/dist/modes/acp/acp-mode.js +34 -0
  312. package/dist/modes/acp/acp-mode.js.map +1 -0
  313. package/dist/modes/acp/acp-session.d.ts +4 -0
  314. package/dist/modes/acp/acp-session.d.ts.map +1 -0
  315. package/dist/modes/acp/acp-session.js +76 -0
  316. package/dist/modes/acp/acp-session.js.map +1 -0
  317. package/dist/modes/acp/acp-transport.d.ts +5 -0
  318. package/dist/modes/acp/acp-transport.d.ts.map +1 -0
  319. package/dist/modes/acp/acp-transport.js +90 -0
  320. package/dist/modes/acp/acp-transport.js.map +1 -0
  321. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  322. package/dist/modes/interactive/interactive-mode.js +3 -5
  323. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  324. package/dist/modes/interactive/resource-description.d.ts +3 -0
  325. package/dist/modes/interactive/resource-description.d.ts.map +1 -0
  326. package/dist/modes/interactive/resource-description.js +15 -0
  327. package/dist/modes/interactive/resource-description.js.map +1 -0
  328. package/docs/audit-revalidation-2026-09-17.md +77 -0
  329. package/docs/devin-harness.md +150 -0
  330. package/docs/docs.json +4 -0
  331. package/docs/ecraf-normalization.md +83 -0
  332. package/docs/environment-variables.md +2 -1
  333. package/docs/index.md +1 -0
  334. package/docs/loadout-domains/README.md +2 -1
  335. package/docs/loadout-domains/devin-harness.md +72 -0
  336. package/docs/metrics.md +57 -16
  337. package/docs/model-catalog-refresh.md +51 -1
  338. package/docs/models.md +1 -1
  339. package/docs/neo.md +134 -0
  340. package/docs/providers.md +143 -1
  341. package/docs/release-audit-0.98.5.md +17 -0
  342. package/docs/release-audit-0.99.0.md +68 -0
  343. package/docs/run-protocol.md +4 -3
  344. package/docs/run-usage-ledger.md +56 -0
  345. package/docs/runtime-algorithms.md +48 -1
  346. package/docs/sdk.md +9 -5
  347. package/docs/settings.md +1 -1
  348. package/docs/skills.md +1 -1
  349. package/docs/startup-resource-labels-testing.md +47 -0
  350. package/docs/tb21-audit.md +18 -4
  351. package/docs/usage.md +7 -1
  352. package/docs/verified-run-remaining-design.md +881 -0
  353. package/docs/verified-run-testing.md +91 -0
  354. package/docs/verified-run.md +30 -9
  355. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  356. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  357. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  358. package/examples/extensions/gondolin/package-lock.json +2 -2
  359. package/examples/extensions/gondolin/package.json +1 -1
  360. package/examples/extensions/sandbox/package-lock.json +2 -2
  361. package/examples/extensions/sandbox/package.json +1 -1
  362. package/examples/extensions/subagent/adaptive-agent-runtime.ts +14 -1
  363. package/examples/extensions/subagent/graph-result.ts +48 -0
  364. package/examples/extensions/subagent/index.ts +246 -111
  365. package/examples/extensions/subagent/managed-process-tree.ts +42 -0
  366. package/examples/extensions/subagent/managed-process.test.ts +24 -0
  367. package/examples/extensions/subagent/managed-process.ts +93 -112
  368. package/examples/extensions/subagent/subagent-runtime-types.ts +17 -1
  369. package/examples/extensions/subagent/subagent-stream.ts +161 -0
  370. package/examples/extensions/terminal-browser/README.md +54 -0
  371. package/examples/extensions/terminal-browser/bridge-protocol.ts +29 -0
  372. package/examples/extensions/terminal-browser/bridge.ts +219 -0
  373. package/examples/extensions/terminal-browser/browser-surface.ts +288 -0
  374. package/examples/extensions/terminal-browser/index.ts +215 -0
  375. package/examples/extensions/terminal-browser/placeholders.ts +49 -0
  376. package/examples/extensions/with-deps/package-lock.json +2 -2
  377. package/examples/extensions/with-deps/package.json +1 -1
  378. package/npm-shrinkwrap.json +18 -18
  379. package/package.json +8 -7
  380. package/resources/neo/skills/omk-browser/SKILL.md +32 -0
  381. package/resources/neo/skills/omk-code-review/SKILL.md +24 -0
  382. package/resources/neo/skills/omk-computeruse/SKILL.md +34 -0
  383. package/resources/neo/skills/omk-mcp-setup/SKILL.md +48 -0
  384. package/resources/neo/skills/omk-research/SKILL.md +24 -0
  385. package/resources/neo/skills/omk-site/SKILL.md +28 -0
@@ -57,7 +57,7 @@ export function isContentSafetyStopMessage(text) {
57
57
  export function isQuotaExhaustionMessage(text) {
58
58
  if (!text)
59
59
  return false;
60
- return /usage limit|GoUsageLimitError|FreeUsageLimitError|available balance|insufficient_quota|out of budget|quota exceeded|billing/i.test(text);
60
+ return /usage limit|GoUsageLimitError|FreeUsageLimitError|available balance|insufficient_quota|insufficient balance|insufficient credits|out of budget|quota exceeded|quota\s+(?:has been\s+|was\s+|is\s+)?exhaust|billing/i.test(text);
61
61
  }
62
62
  /**
63
63
  * Anthropic rejecting the spoofed Claude Code client version because the target
@@ -89,6 +89,30 @@ export function isOrphanToolCallIdError(text) {
89
89
  return false;
90
90
  return /tool_call_id\s+is\s+not\s+found|tool_call_id\s+not\s+found|unknown\s+tool_call_id/i.test(text);
91
91
  }
92
+ /**
93
+ * Status-code-context match: a bare `500` inside a message body ("saved 500
94
+ * records") is not a status code, so numeric matches require an adjacent
95
+ * transport word — either a code-context word before the digits ("HTTP 429",
96
+ * "error 503", "status 401") or a status-context word after them ("503
97
+ * Upstream request failed", "429 Too Many Requests", "500 Internal Server
98
+ * Error"). Structured `status`/`errorCode` fields on the caller's side still
99
+ * outrank this text heuristic entirely.
100
+ */
101
+ export function hasStatusCodeLike(text, codes) {
102
+ const left = /(?:https?|status(?:\s*code)?|error(?:\s*code)?|code)\s*:?\s*(\d{3})\b/gi;
103
+ const right = /\b(\d{3})\s+(?:too many|upstream|bad gateway|service unavailable|internal server|gateway|error|status|request failed|timed? ?out|unauthori[sz]ed|forbidden)/gi;
104
+ for (const re of [left, right]) {
105
+ re.lastIndex = 0;
106
+ for (;;) {
107
+ const match = re.exec(text);
108
+ if (match === null)
109
+ break;
110
+ if (codes.includes(Number.parseInt(match[1], 10)))
111
+ return true;
112
+ }
113
+ }
114
+ return false;
115
+ }
92
116
  /**
93
117
  * Transient upstream availability failures: gateway 5xx passthroughs
94
118
  * ("503 Upstream request failed", "Endpoint is unavailable") and dropped
@@ -99,7 +123,7 @@ export function isOrphanToolCallIdError(text) {
99
123
  export function isUpstreamUnavailableMessage(text) {
100
124
  if (!text)
101
125
  return false;
102
- return /upstream(?: request)? failed|upstream connect|endpoint (?:is )?unavailable|stream ended without finish_reason|\b50[0234]\b/i.test(text);
126
+ return (/upstream(?: request)? failed|upstream connect|endpoint (?:is )?unavailable|stream ended without finish_reason/i.test(text) || hasStatusCodeLike(text, [500, 502, 503, 504]));
103
127
  }
104
128
  /**
105
129
  * Route families: the same underlying model served by several provider
@@ -110,6 +134,10 @@ export function isUpstreamUnavailableMessage(text) {
110
134
  */
111
135
  const MODEL_ROUTE_FAMILIES = [
112
136
  { pattern: /(?:^|[-_/])ox[-_ ]?alpha(?:[-_/\s(]|$)/i, family: "ox-alpha" },
137
+ // union-alpha is served by opencode-go/union-alpha (anthropic-messages) and
138
+ // openrouter/stealth/union-alpha (openai-completions); both map to one family
139
+ // so an upstream-unavailable 503/empty stream rotates to the live route.
140
+ { pattern: /(?:^|[-_/])union[-_ ]?alpha(?:[-_/\s(]|$)/i, family: "union-alpha" },
113
141
  ];
114
142
  /** Family key for a model route, or undefined when it has no known alias group. */
115
143
  export function modelRouteFamily(model) {
@@ -156,7 +184,12 @@ export function isTransientProviderErrorMessage(text) {
156
184
  // `at capacity|high demand`: xAI serves overload as HTTP 429 whose body carries
157
185
  // no status or limit token, so the message-level classifier misses it and the
158
186
  // turn never auto-retries (observed 2026-09-03, xai/grok-4.6).
159
- return /overloaded|at capacity|high demand|provider.?returned.?error|rate.?limit|too many requests|429|500|502|503|504|service.?unavailable|server.?error|internal.?error|network.?error|connection.?error|connection.?refused|connection.?lost|websocket.?closed|websocket.?error|other side closed|fetch failed|upstream.?connect|reset before headers|socket hang up|ended without|stream ended before message_stop|http2 request did not get a response|timed? out|timeout|\bterminated\b|retry delay|content\/safety stop|stop_reason\s*=\s*(refusal|sensitive)|safety stop|tool_call_id\s+is\s+not\s+found|tool_call_id\s+not\s+found|invalid_request_error|json error injected into sse stream|injected into sse/i.test(text);
187
+ if (/overloaded|at capacity|high demand|provider.?returned.?error|rate.?limit|too many requests|service.?unavailable|server.?error|internal.?error|network.?error|connection.?error|connection.?refused|connection.?lost|websocket.?closed|websocket.?error|other side closed|fetch failed|upstream.?connect|reset before headers|socket hang up|ended without|stream ended before message_stop|stream ended before terminal|http2 request did not get a response|timed? out|timeout|\bterminated\b|retry delay|content\/safety stop|stop_reason\s*=\s*(refusal|sensitive)|safety stop|tool_call_id\s+is\s+not\s+found|tool_call_id\s+not\s+found|invalid_request_error|json error injected into sse stream|injected into sse/i.test(text)) {
188
+ return true;
189
+ }
190
+ // Status-looking numbers only count with adjacent transport context — a
191
+ // bare `500` in a message body ("saved 500 records") is not a status code.
192
+ return hasStatusCodeLike(text, [429, 500, 502, 503, 504]);
160
193
  }
161
194
  export function resolveFailoverCandidates(settings) {
162
195
  const custom = settings?.failoverCandidates;
@@ -1 +1 @@
1
- {"version":3,"file":"provider-resilience.js","sourceRoot":"","sources":["../../src/core/provider-resilience.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAOH,oFAAoF;AACpF,MAAM,CAAC,MAAM,sBAAsB,GAAG,QAAQ,CAAC;AAE/C,8EAA8E;AAC9E,MAAM,CAAC,MAAM,2BAA2B,GAAG,oCAAoC,CAAC;AAEhF,wEAAwE;AACxE,MAAM,CAAC,MAAM,kCAAkC,GAAiC;IAC/E,EAAE,QAAQ,EAAE,aAAa,EAAE,EAAE,EAAE,IAAI,EAAE;IACrC,EAAE,QAAQ,EAAE,kBAAkB,EAAE,EAAE,EAAE,aAAa,EAAE;IACnD,EAAE,QAAQ,EAAE,KAAK,EAAE,EAAE,EAAE,UAAU,EAAE;IACnC,EAAE,QAAQ,EAAE,UAAU,EAAE,EAAE,EAAE,iBAAiB,EAAE;IAC/C,EAAE,QAAQ,EAAE,UAAU,EAAE,EAAE,EAAE,mBAAmB,EAAE;IACjD,EAAE,QAAQ,EAAE,kBAAkB,EAAE,EAAE,EAAE,iBAAiB,EAAE;IACvD,EAAE,QAAQ,EAAE,aAAa,EAAE,EAAE,EAAE,iBAAiB,EAAE;CACzC,CAAC;AAWX,MAAM,CAAC,MAAM,2BAA2B,GAIpC;IACH,uBAAuB,EAAE,IAAI;IAC7B,wBAAwB,EAAE,IAAI;IAC9B,kBAAkB,EAAE,kCAAkC;CACtD,CAAC;AAEF,MAAM,UAAU,mBAAmB,CAAC,OAA2B,EAAE,QAA6B,EAAW;IACxG,MAAM,EAAE,GAAG,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IAClC,IAAI,CAAC,EAAE;QAAE,OAAO,KAAK,CAAC;IACtB,IAAI,sBAAsB,CAAC,IAAI,CAAC,EAAE,CAAC;QAAE,OAAO,IAAI,CAAC;IACjD,MAAM,CAAC,GAAG,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC;IACzC,OAAO,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,IAAI,sBAAsB,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;AAAA,CAClE;AAED,0EAA0E;AAC1E,MAAM,UAAU,uBAAuB,CAAC,OAA2B,EAAE,QAA6B,EAAW;IAC5G,MAAM,EAAE,GAAG,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IAClC,IAAI,CAAC,EAAE;QAAE,OAAO,KAAK,CAAC;IACtB,IAAI,2BAA2B,CAAC,IAAI,CAAC,EAAE,CAAC;QAAE,OAAO,IAAI,CAAC;IACtD,MAAM,CAAC,GAAG,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC;IACzC,OAAO,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,IAAI,2BAA2B,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;AAAA,CACvE;AAED,MAAM,UAAU,0BAA0B,CAAC,IAAwB,EAAW;IAC7E,IAAI,CAAC,IAAI;QAAE,OAAO,KAAK,CAAC;IACxB,OAAO,iHAAiH,CAAC,IAAI,CAC5H,IAAI,CACJ,CAAC;AAAA,CACF;AAED;;;;;GAKG;AACH,MAAM,UAAU,wBAAwB,CAAC,IAAwB,EAAW;IAC3E,IAAI,CAAC,IAAI;QAAE,OAAO,KAAK,CAAC;IACxB,OAAO,8HAA8H,CAAC,IAAI,CACzI,IAAI,CACJ,CAAC;AAAA,CACF;AAED;;;;;;GAMG;AACH,MAAM,UAAU,gCAAgC,CAAC,IAAwB,EAAW;IACnF,IAAI,CAAC,IAAI;QAAE,OAAO,KAAK,CAAC;IACxB,OAAO,qGAAqG,CAAC,IAAI,CAChH,IAAI,CACJ,CAAC;AAAA,CACF;AAED;;;;;;GAMG;AACH,MAAM,UAAU,4CAA4C,CAAC,IAAwB,EAAW;IAC/F,IAAI,CAAC,IAAI;QAAE,OAAO,KAAK,CAAC;IACxB,OAAO,iEAAiE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAAA,CACpF;AAED,0FAA0F;AAC1F,MAAM,UAAU,uBAAuB,CAAC,IAAwB,EAAW;IAC1E,IAAI,CAAC,IAAI;QAAE,OAAO,KAAK,CAAC;IACxB,OAAO,oFAAoF,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAAA,CACvG;AAED;;;;;;GAMG;AACH,MAAM,UAAU,4BAA4B,CAAC,IAAwB,EAAW;IAC/E,IAAI,CAAC,IAAI;QAAE,OAAO,KAAK,CAAC;IACxB,OAAO,6HAA6H,CAAC,IAAI,CACxI,IAAI,CACJ,CAAC;AAAA,CACF;AAED;;;;;;GAMG;AACH,MAAM,oBAAoB,GAAqE;IAC9F,EAAE,OAAO,EAAE,yCAAyC,EAAE,MAAM,EAAE,UAAU,EAAE;CAC1E,CAAC;AAEF,mFAAmF;AACnF,MAAM,UAAU,gBAAgB,CAAC,KAIhC,EAAsB;IACtB,KAAK,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,oBAAoB,EAAE,CAAC;QACxD,IAAI,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;YAAE,OAAO,MAAM,CAAC;QAC1C,IAAI,KAAK,CAAC,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC;YAAE,OAAO,MAAM,CAAC;IAC3D,CAAC;IACD,OAAO,SAAS,CAAC;AAAA,CACjB;AAED;;;;GAIG;AACH,MAAM,UAAU,wBAAwB,CAGvC,MAA8F,EAC9F,SAAuB,EACjB;IACN,MAAM,MAAM,GAAG,gBAAgB,CAAC,MAAM,CAAC,CAAC;IACxC,IAAI,CAAC,MAAM;QAAE,OAAO,EAAE,CAAC;IACvB,MAAM,UAAU,GAAQ,EAAE,CAAC;IAC3B,KAAK,MAAM,KAAK,IAAI,SAAS,EAAE,CAAC;QAC/B,IAAI,KAAK,CAAC,QAAQ,KAAK,MAAM,CAAC,QAAQ,IAAI,KAAK,CAAC,EAAE,KAAK,MAAM,CAAC,EAAE;YAAE,SAAS;QAC3E,IAAI,gBAAgB,CAAC,KAAK,CAAC,KAAK,MAAM;YAAE,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAChE,CAAC;IACD,OAAO,UAAU,CAAC;AAAA,CAClB;AAED;;;GAGG;AACH,MAAM,UAAU,+BAA+B,CAAC,IAAwB,EAAW;IAClF,IAAI,CAAC,IAAI;QAAE,OAAO,KAAK,CAAC;IACxB,wEAAwE;IACxE,4EAA4E;IAC5E,eAAe;IACf,IAAI,gCAAgC,CAAC,IAAI,CAAC;QAAE,OAAO,KAAK,CAAC;IACzD,IAAI,4CAA4C,CAAC,IAAI,CAAC;QAAE,OAAO,KAAK,CAAC;IACrE,gFAAgF;IAChF,8EAA8E;IAC9E,+DAA+D;IAC/D,OAAO,krBAAkrB,CAAC,IAAI,CAC7rB,IAAI,CACJ,CAAC;AAAA,CACF;AAED,MAAM,UAAU,yBAAyB,CACxC,QAAgD,EACjB;IAC/B,MAAM,MAAM,GAAG,QAAQ,EAAE,kBAAkB,CAAC;IAC5C,IAAI,MAAM,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,MAAM,CAAC;IAC/C,OAAO,kCAAkC,CAAC;AAAA,CAC1C;AAED,0EAA0E;AAC1E,MAAM,UAAU,yBAAyB,CAAC,OAKzC,EAAW;IACX,IAAI,CAAC,OAAO,CAAC,wBAAwB,IAAI,OAAO,CAAC,WAAW;QAAE,OAAO,KAAK,CAAC;IAC3E,OAAO,CAAC,uBAAuB,CAAC,OAAO,CAAC,OAAO,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAC;AAAA,CACnE;AAED,0EAA0E;AAC1E,MAAM,UAAU,4BAA4B,CAAC,OAG5C,EAAW;IACX,OAAO,OAAO,CAAC,uBAAuB,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC;AAAA,CAC/D;AAED,MAAM,UAAU,yBAAyB,CAAC,QAAgD,EAIxF;IACD,OAAO;QACN,uBAAuB,EAAE,QAAQ,EAAE,uBAAuB,IAAI,2BAA2B,CAAC,uBAAuB;QACjH,wBAAwB,EACvB,QAAQ,EAAE,wBAAwB,IAAI,2BAA2B,CAAC,wBAAwB;QAC3F,kBAAkB,EAAE,yBAAyB,CAAC,QAAQ,CAAC;KACvD,CAAC;AAAA,CACF;AAED;;;GAGG;AACH,MAAM,UAAU,qBAAqB,CACpC,UAAwC,EACxC,OAAyE,EACzE,SAAoD,EACpB;IAChC,KAAK,MAAM,CAAC,IAAI,UAAU,EAAE,CAAC;QAC5B,IAAI,OAAO,IAAI,CAAC,CAAC,QAAQ,KAAK,OAAO,CAAC,QAAQ,IAAI,CAAC,CAAC,EAAE,KAAK,OAAO,CAAC,EAAE;YAAE,SAAS;QAChF,IAAI,mBAAmB,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,QAAQ,CAAC;YAAE,SAAS;QACpD,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC;YAAE,SAAS;QAC5B,OAAO,CAAC,CAAC;IACV,CAAC;IACD,OAAO,SAAS,CAAC;AAAA,CACjB;AAED,MAAM,UAAU,wBAAwB,CAAC,OAAe,EAAE,QAAgB,EAAU;IACnF,OAAO,CACN,+BAA+B,QAAQ,IAAI,OAAO,iDAAiD;QACnG,2EAA2E;QAC3E,0DAA0D,CAC1D,CAAC;AAAA,CACF","sourcesContent":["/**\n * Provider resilience — root-level recovery for sticky safety models and\n * sanitize-and-retry protocol failures (orphan tool_call_id, terminated streams).\n *\n * This is not a jailbreak layer. It keeps sessions alive when a provider\n * false-positives or returns a sticky transcript-shape error.\n */\n\nexport type FailoverCandidate = Readonly<{\n\treadonly provider: string;\n\treadonly id: string;\n}>;\n\n/** Models known to emit high false-positive content/safety stops on coding work. */\nexport const STICKY_SAFETY_MODEL_RE = /fable/i;\n\n/** Claude lines that must stay on-model when they refuse. No DeepSeek hop. */\nexport const NO_SAFETY_FAILOVER_MODEL_RE = /(?:claude-)?(?:fable|opus|sonnet)/i;\n\n/** Default failover chain when a sticky safety model refuses a turn. */\nexport const DEFAULT_SAFETY_FAILOVER_CANDIDATES: readonly FailoverCandidate[] = [\n\t{ provider: \"kimi-coding\", id: \"k3\" },\n\t{ provider: \"modelstudio-maas\", id: \"qwen3.8-max\" },\n\t{ provider: \"xai\", id: \"grok-4.5\" },\n\t{ provider: \"deepseek\", id: \"deepseek-v4-pro\" },\n\t{ provider: \"deepseek\", id: \"deepseek-v4-flash\" },\n\t{ provider: \"modelstudio-maas\", id: \"deepseek-v4-pro\" },\n\t{ provider: \"kimi-coding\", id: \"kimi-for-coding\" },\n] as const;\n\nexport type ProviderResilienceSettings = Readonly<{\n\t/** When true (default), refuse selecting sticky safety models (e.g. claude-fable-5). */\n\treadonly blockStickySafetyModels?: boolean;\n\t/** When true (default), auto-switch model on content/safety stop before retry. */\n\treadonly autoFailoverOnSafetyStop?: boolean;\n\t/** Ordered failover targets. Falls back to DEFAULT_SAFETY_FAILOVER_CANDIDATES. */\n\treadonly failoverCandidates?: readonly FailoverCandidate[];\n}>;\n\nexport const DEFAULT_PROVIDER_RESILIENCE: Required<\n\tPick<ProviderResilienceSettings, \"blockStickySafetyModels\" | \"autoFailoverOnSafetyStop\">\n> & {\n\treadonly failoverCandidates: readonly FailoverCandidate[];\n} = {\n\tblockStickySafetyModels: true,\n\tautoFailoverOnSafetyStop: true,\n\tfailoverCandidates: DEFAULT_SAFETY_FAILOVER_CANDIDATES,\n};\n\nexport function isStickySafetyModel(modelId: string | undefined, provider?: string | undefined): boolean {\n\tconst id = (modelId ?? \"\").trim();\n\tif (!id) return false;\n\tif (STICKY_SAFETY_MODEL_RE.test(id)) return true;\n\tconst p = (provider ?? \"\").toLowerCase();\n\treturn p.includes(\"anthropic\") && STICKY_SAFETY_MODEL_RE.test(id);\n}\n\n/** Fable / Opus / Sonnet stay on the requested id after a safety stop. */\nexport function isNoSafetyFailoverModel(modelId: string | undefined, provider?: string | undefined): boolean {\n\tconst id = (modelId ?? \"\").trim();\n\tif (!id) return false;\n\tif (NO_SAFETY_FAILOVER_MODEL_RE.test(id)) return true;\n\tconst p = (provider ?? \"\").toLowerCase();\n\treturn p.includes(\"anthropic\") && NO_SAFETY_FAILOVER_MODEL_RE.test(id);\n}\n\nexport function isContentSafetyStopMessage(text: string | undefined): boolean {\n\tif (!text) return false;\n\treturn /content\\/safety stop|stop_reason\\s*=\\s*(refusal|sensitive)|safety stop|provider\\.refusal|kind=provider_refusal/i.test(\n\t\ttext,\n\t);\n}\n\n/**\n * Billing/quota exhaustion (\"usage limit for this billing cycle\",\n * \"insufficient_quota\", zero balance, ...). Same-model retry is useless until\n * the cycle resets, but the turn can still be saved by failing over to another\n * candidate — so these classify as retryable-with-failover, not auth-fatal.\n */\nexport function isQuotaExhaustionMessage(text: string | undefined): boolean {\n\tif (!text) return false;\n\treturn /usage limit|GoUsageLimitError|FreeUsageLimitError|available balance|insufficient_quota|out of budget|quota exceeded|billing/i.test(\n\t\ttext,\n\t);\n}\n\n/**\n * Anthropic rejecting the spoofed Claude Code client version because the target\n * model is gated on a newer one (`claude_code_version_too_old`, HTTP 400).\n * Permanent for this build: every retry and every failover re-sends the same\n * user-agent, so the fix is bumping `CLAUDE_CODE_VERSION` in omk-ai (or\n * selecting an ungated model).\n */\nexport function isClaudeCodeVersionTooOldMessage(text: string | undefined): boolean {\n\tif (!text) return false;\n\treturn /claude_code_version_too_old|does not support this model;\\s*version\\s+[\\d.]+\\s+or newer is required/i.test(\n\t\ttext,\n\t);\n}\n\n/**\n * Codex HTTP 400 when a ChatGPT-account OAuth session asks for a model the\n * account catalog does not serve (`gpt-6-astra` during the 2026-09-04 rollout).\n * Permanent for this login: same-model retry, transcript sanitize, and /new\n * session all re-send the same slug and get the same 400. Switch model or use\n * an API-key route that has the model.\n */\nexport function isCodexChatgptAccountUnsupportedModelMessage(text: string | undefined): boolean {\n\tif (!text) return false;\n\treturn /model is not supported when using Codex with a ChatGPT account/i.test(text);\n}\n\n/** Orphan tool results / Kimi-K3 protocol shape errors that heal after sanitize+retry. */\nexport function isOrphanToolCallIdError(text: string | undefined): boolean {\n\tif (!text) return false;\n\treturn /tool_call_id\\s+is\\s+not\\s+found|tool_call_id\\s+not\\s+found|unknown\\s+tool_call_id/i.test(text);\n}\n\n/**\n * Transient upstream availability failures: gateway 5xx passthroughs\n * (\"503 Upstream request failed\", \"Endpoint is unavailable\") and dropped\n * streams that never carried a finish reason (\"Stream ended without\n * finish_reason\"). Transport problems, not transcript-shape problems — they\n * heal by backoff/retry or switching model, never by transcript sanitization.\n */\nexport function isUpstreamUnavailableMessage(text: string | undefined): boolean {\n\tif (!text) return false;\n\treturn /upstream(?: request)? failed|upstream connect|endpoint (?:is )?unavailable|stream ended without finish_reason|\\b50[0234]\\b/i.test(\n\t\ttext,\n\t);\n}\n\n/**\n * Route families: the same underlying model served by several provider\n * routes. Matched on the model id OR display name so alias routes like\n * `openrouter/stealth/ox-alpha`, `opencode-go/ox-alpha-free`, and\n * `opencode/x-preview-f-free` (\"Ox Alpha Free (Unlimited)\") rotate as one\n * ox-alpha family.\n */\nconst MODEL_ROUTE_FAMILIES: readonly { readonly pattern: RegExp; readonly family: string }[] = [\n\t{ pattern: /(?:^|[-_/])ox[-_ ]?alpha(?:[-_/\\s(]|$)/i, family: \"ox-alpha\" },\n];\n\n/** Family key for a model route, or undefined when it has no known alias group. */\nexport function modelRouteFamily(model: {\n\treadonly provider?: string | undefined;\n\treadonly id: string;\n\treadonly name?: string | undefined;\n}): string | undefined {\n\tfor (const { pattern, family } of MODEL_ROUTE_FAMILIES) {\n\t\tif (pattern.test(model.id)) return family;\n\t\tif (model.name && pattern.test(model.name)) return family;\n\t}\n\treturn undefined;\n}\n\n/**\n * Alternative provider routes serving the same model family as\n * {@link failed}, in registry order, excluding the failed route itself.\n * Callers filter by authentication and per-turn failure bookkeeping.\n */\nexport function sameModelRouteCandidates<\n\tT extends { readonly provider: string; readonly id: string; readonly name?: string | undefined },\n>(\n\tfailed: { readonly provider: string; readonly id: string; readonly name?: string | undefined },\n\tavailable: readonly T[],\n): T[] {\n\tconst family = modelRouteFamily(failed);\n\tif (!family) return [];\n\tconst candidates: T[] = [];\n\tfor (const model of available) {\n\t\tif (model.provider === failed.provider && model.id === failed.id) continue;\n\t\tif (modelRouteFamily(model) === family) candidates.push(model);\n\t}\n\treturn candidates;\n}\n\n/**\n * Errors the agent loop may auto-retry (after optional failover / message sanitize).\n * Kept in one place so agent-session and tests share the same contract.\n */\nexport function isTransientProviderErrorMessage(text: string | undefined): boolean {\n\tif (!text) return false;\n\t// Checked first: the payload carries `invalid_request_error`, which the\n\t// pattern below treats as transient, but a stale client version never heals\n\t// by retrying.\n\tif (isClaudeCodeVersionTooOldMessage(text)) return false;\n\tif (isCodexChatgptAccountUnsupportedModelMessage(text)) return false;\n\t// `at capacity|high demand`: xAI serves overload as HTTP 429 whose body carries\n\t// no status or limit token, so the message-level classifier misses it and the\n\t// turn never auto-retries (observed 2026-09-03, xai/grok-4.6).\n\treturn /overloaded|at capacity|high demand|provider.?returned.?error|rate.?limit|too many requests|429|500|502|503|504|service.?unavailable|server.?error|internal.?error|network.?error|connection.?error|connection.?refused|connection.?lost|websocket.?closed|websocket.?error|other side closed|fetch failed|upstream.?connect|reset before headers|socket hang up|ended without|stream ended before message_stop|http2 request did not get a response|timed? out|timeout|\\bterminated\\b|retry delay|content\\/safety stop|stop_reason\\s*=\\s*(refusal|sensitive)|safety stop|tool_call_id\\s+is\\s+not\\s+found|tool_call_id\\s+not\\s+found|invalid_request_error|json error injected into sse stream|injected into sse/i.test(\n\t\ttext,\n\t);\n}\n\nexport function resolveFailoverCandidates(\n\tsettings: ProviderResilienceSettings | undefined,\n): readonly FailoverCandidate[] {\n\tconst custom = settings?.failoverCandidates;\n\tif (custom && custom.length > 0) return custom;\n\treturn DEFAULT_SAFETY_FAILOVER_CANDIDATES;\n}\n\n/** Explicit `--model` / `--provider` pins stay on the requested model. */\nexport function shouldHonorSafetyFailover(options: {\n\treadonly autoFailoverOnSafetyStop: boolean;\n\treadonly modelPinned: boolean;\n\treadonly modelId?: string;\n\treadonly provider?: string;\n}): boolean {\n\tif (!options.autoFailoverOnSafetyStop || options.modelPinned) return false;\n\treturn !isNoSafetyFailoverModel(options.modelId, options.provider);\n}\n\n/** Sticky-model eject still applies unless the user pinned that model. */\nexport function shouldEjectStickySafetyModel(options: {\n\treadonly blockStickySafetyModels: boolean;\n\treadonly modelPinned: boolean;\n}): boolean {\n\treturn options.blockStickySafetyModels && !options.modelPinned;\n}\n\nexport function resolveProviderResilience(settings: ProviderResilienceSettings | undefined): {\n\treadonly blockStickySafetyModels: boolean;\n\treadonly autoFailoverOnSafetyStop: boolean;\n\treadonly failoverCandidates: readonly FailoverCandidate[];\n} {\n\treturn {\n\t\tblockStickySafetyModels: settings?.blockStickySafetyModels ?? DEFAULT_PROVIDER_RESILIENCE.blockStickySafetyModels,\n\t\tautoFailoverOnSafetyStop:\n\t\t\tsettings?.autoFailoverOnSafetyStop ?? DEFAULT_PROVIDER_RESILIENCE.autoFailoverOnSafetyStop,\n\t\tfailoverCandidates: resolveFailoverCandidates(settings),\n\t};\n}\n\n/**\n * Pick first failover candidate that is not the current model and passes `isAllowed`.\n * Pure — caller performs auth checks and setModel.\n */\nexport function pickFailoverCandidate(\n\tcandidates: readonly FailoverCandidate[],\n\tcurrent: { readonly provider?: string; readonly id?: string } | undefined,\n\tisAllowed: (candidate: FailoverCandidate) => boolean,\n): FailoverCandidate | undefined {\n\tfor (const c of candidates) {\n\t\tif (current && c.provider === current.provider && c.id === current.id) continue;\n\t\tif (isStickySafetyModel(c.id, c.provider)) continue;\n\t\tif (!isAllowed(c)) continue;\n\t\treturn c;\n\t}\n\treturn undefined;\n}\n\nexport function stickySafetyBlockMessage(modelId: string, provider: string): string {\n\treturn (\n\t\t`Blocked sticky safety model ${provider}/${modelId} (providerResilience.blockStickySafetyModels). ` +\n\t\t`Use kimi-coding/k3, modelstudio-maas/qwen3.8-max, grok-4.5, or deepseek; ` +\n\t\t`or set providerResilience.blockStickySafetyModels=false.`\n\t);\n}\n"]}
1
+ {"version":3,"file":"provider-resilience.js","sourceRoot":"","sources":["../../src/core/provider-resilience.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Provider resilience — root-level recovery for sticky safety models and\n * sanitize-and-retry protocol failures (orphan tool_call_id, terminated streams).\n *\n * This is not a jailbreak layer. It keeps sessions alive when a provider\n * false-positives or returns a sticky transcript-shape error.\n */\n\nexport type FailoverCandidate = Readonly<{\n\treadonly provider: string;\n\treadonly id: string;\n}>;\n\n/** Models known to emit high false-positive content/safety stops on coding work. */\nexport const STICKY_SAFETY_MODEL_RE = /fable/i;\n\n/** Claude lines that must stay on-model when they refuse. No DeepSeek hop. */\nexport const NO_SAFETY_FAILOVER_MODEL_RE = /(?:claude-)?(?:fable|opus|sonnet)/i;\n\n/** Default failover chain when a sticky safety model refuses a turn. */\nexport const DEFAULT_SAFETY_FAILOVER_CANDIDATES: readonly FailoverCandidate[] = [\n\t{ provider: \"kimi-coding\", id: \"k3\" },\n\t{ provider: \"modelstudio-maas\", id: \"qwen3.8-max\" },\n\t{ provider: \"xai\", id: \"grok-4.5\" },\n\t{ provider: \"deepseek\", id: \"deepseek-v4-pro\" },\n\t{ provider: \"deepseek\", id: \"deepseek-v4-flash\" },\n\t{ provider: \"modelstudio-maas\", id: \"deepseek-v4-pro\" },\n\t{ provider: \"kimi-coding\", id: \"kimi-for-coding\" },\n] as const;\n\nexport type ProviderResilienceSettings = Readonly<{\n\t/** When true (default), refuse selecting sticky safety models (e.g. claude-fable-5). */\n\treadonly blockStickySafetyModels?: boolean;\n\t/** When true (default), auto-switch model on content/safety stop before retry. */\n\treadonly autoFailoverOnSafetyStop?: boolean;\n\t/** Ordered failover targets. Falls back to DEFAULT_SAFETY_FAILOVER_CANDIDATES. */\n\treadonly failoverCandidates?: readonly FailoverCandidate[];\n}>;\n\nexport const DEFAULT_PROVIDER_RESILIENCE: Required<\n\tPick<ProviderResilienceSettings, \"blockStickySafetyModels\" | \"autoFailoverOnSafetyStop\">\n> & {\n\treadonly failoverCandidates: readonly FailoverCandidate[];\n} = {\n\tblockStickySafetyModels: true,\n\tautoFailoverOnSafetyStop: true,\n\tfailoverCandidates: DEFAULT_SAFETY_FAILOVER_CANDIDATES,\n};\n\nexport function isStickySafetyModel(modelId: string | undefined, provider?: string | undefined): boolean {\n\tconst id = (modelId ?? \"\").trim();\n\tif (!id) return false;\n\tif (STICKY_SAFETY_MODEL_RE.test(id)) return true;\n\tconst p = (provider ?? \"\").toLowerCase();\n\treturn p.includes(\"anthropic\") && STICKY_SAFETY_MODEL_RE.test(id);\n}\n\n/** Fable / Opus / Sonnet stay on the requested id after a safety stop. */\nexport function isNoSafetyFailoverModel(modelId: string | undefined, provider?: string | undefined): boolean {\n\tconst id = (modelId ?? \"\").trim();\n\tif (!id) return false;\n\tif (NO_SAFETY_FAILOVER_MODEL_RE.test(id)) return true;\n\tconst p = (provider ?? \"\").toLowerCase();\n\treturn p.includes(\"anthropic\") && NO_SAFETY_FAILOVER_MODEL_RE.test(id);\n}\n\nexport function isContentSafetyStopMessage(text: string | undefined): boolean {\n\tif (!text) return false;\n\treturn /content\\/safety stop|stop_reason\\s*=\\s*(refusal|sensitive)|safety stop|provider\\.refusal|kind=provider_refusal/i.test(\n\t\ttext,\n\t);\n}\n\n/**\n * Billing/quota exhaustion (\"usage limit for this billing cycle\",\n * \"insufficient_quota\", zero balance, ...). Same-model retry is useless until\n * the cycle resets, but the turn can still be saved by failing over to another\n * candidate — so these classify as retryable-with-failover, not auth-fatal.\n */\nexport function isQuotaExhaustionMessage(text: string | undefined): boolean {\n\tif (!text) return false;\n\treturn /usage limit|GoUsageLimitError|FreeUsageLimitError|available balance|insufficient_quota|insufficient balance|insufficient credits|out of budget|quota exceeded|quota\\s+(?:has been\\s+|was\\s+|is\\s+)?exhaust|billing/i.test(\n\t\ttext,\n\t);\n}\n\n/**\n * Anthropic rejecting the spoofed Claude Code client version because the target\n * model is gated on a newer one (`claude_code_version_too_old`, HTTP 400).\n * Permanent for this build: every retry and every failover re-sends the same\n * user-agent, so the fix is bumping `CLAUDE_CODE_VERSION` in omk-ai (or\n * selecting an ungated model).\n */\nexport function isClaudeCodeVersionTooOldMessage(text: string | undefined): boolean {\n\tif (!text) return false;\n\treturn /claude_code_version_too_old|does not support this model;\\s*version\\s+[\\d.]+\\s+or newer is required/i.test(\n\t\ttext,\n\t);\n}\n\n/**\n * Codex HTTP 400 when a ChatGPT-account OAuth session asks for a model the\n * account catalog does not serve (`gpt-6-astra` during the 2026-09-04 rollout).\n * Permanent for this login: same-model retry, transcript sanitize, and /new\n * session all re-send the same slug and get the same 400. Switch model or use\n * an API-key route that has the model.\n */\nexport function isCodexChatgptAccountUnsupportedModelMessage(text: string | undefined): boolean {\n\tif (!text) return false;\n\treturn /model is not supported when using Codex with a ChatGPT account/i.test(text);\n}\n\n/** Orphan tool results / Kimi-K3 protocol shape errors that heal after sanitize+retry. */\nexport function isOrphanToolCallIdError(text: string | undefined): boolean {\n\tif (!text) return false;\n\treturn /tool_call_id\\s+is\\s+not\\s+found|tool_call_id\\s+not\\s+found|unknown\\s+tool_call_id/i.test(text);\n}\n\n/**\n * Status-code-context match: a bare `500` inside a message body (\"saved 500\n * records\") is not a status code, so numeric matches require an adjacent\n * transport word — either a code-context word before the digits (\"HTTP 429\",\n * \"error 503\", \"status 401\") or a status-context word after them (\"503\n * Upstream request failed\", \"429 Too Many Requests\", \"500 Internal Server\n * Error\"). Structured `status`/`errorCode` fields on the caller's side still\n * outrank this text heuristic entirely.\n */\nexport function hasStatusCodeLike(text: string, codes: readonly number[]): boolean {\n\tconst left = /(?:https?|status(?:\\s*code)?|error(?:\\s*code)?|code)\\s*:?\\s*(\\d{3})\\b/gi;\n\tconst right =\n\t\t/\\b(\\d{3})\\s+(?:too many|upstream|bad gateway|service unavailable|internal server|gateway|error|status|request failed|timed? ?out|unauthori[sz]ed|forbidden)/gi;\n\tfor (const re of [left, right]) {\n\t\tre.lastIndex = 0;\n\t\tfor (;;) {\n\t\t\tconst match = re.exec(text);\n\t\t\tif (match === null) break;\n\t\t\tif (codes.includes(Number.parseInt(match[1], 10))) return true;\n\t\t}\n\t}\n\treturn false;\n}\n\n/**\n * Transient upstream availability failures: gateway 5xx passthroughs\n * (\"503 Upstream request failed\", \"Endpoint is unavailable\") and dropped\n * streams that never carried a finish reason (\"Stream ended without\n * finish_reason\"). Transport problems, not transcript-shape problems — they\n * heal by backoff/retry or switching model, never by transcript sanitization.\n */\nexport function isUpstreamUnavailableMessage(text: string | undefined): boolean {\n\tif (!text) return false;\n\treturn (\n\t\t/upstream(?: request)? failed|upstream connect|endpoint (?:is )?unavailable|stream ended without finish_reason/i.test(\n\t\t\ttext,\n\t\t) || hasStatusCodeLike(text, [500, 502, 503, 504])\n\t);\n}\n\n/**\n * Route families: the same underlying model served by several provider\n * routes. Matched on the model id OR display name so alias routes like\n * `openrouter/stealth/ox-alpha`, `opencode-go/ox-alpha-free`, and\n * `opencode/x-preview-f-free` (\"Ox Alpha Free (Unlimited)\") rotate as one\n * ox-alpha family.\n */\nconst MODEL_ROUTE_FAMILIES: readonly { readonly pattern: RegExp; readonly family: string }[] = [\n\t{ pattern: /(?:^|[-_/])ox[-_ ]?alpha(?:[-_/\\s(]|$)/i, family: \"ox-alpha\" },\n\t// union-alpha is served by opencode-go/union-alpha (anthropic-messages) and\n\t// openrouter/stealth/union-alpha (openai-completions); both map to one family\n\t// so an upstream-unavailable 503/empty stream rotates to the live route.\n\t{ pattern: /(?:^|[-_/])union[-_ ]?alpha(?:[-_/\\s(]|$)/i, family: \"union-alpha\" },\n];\n\n/** Family key for a model route, or undefined when it has no known alias group. */\nexport function modelRouteFamily(model: {\n\treadonly provider?: string | undefined;\n\treadonly id: string;\n\treadonly name?: string | undefined;\n}): string | undefined {\n\tfor (const { pattern, family } of MODEL_ROUTE_FAMILIES) {\n\t\tif (pattern.test(model.id)) return family;\n\t\tif (model.name && pattern.test(model.name)) return family;\n\t}\n\treturn undefined;\n}\n\n/**\n * Alternative provider routes serving the same model family as\n * {@link failed}, in registry order, excluding the failed route itself.\n * Callers filter by authentication and per-turn failure bookkeeping.\n */\nexport function sameModelRouteCandidates<\n\tT extends { readonly provider: string; readonly id: string; readonly name?: string | undefined },\n>(\n\tfailed: { readonly provider: string; readonly id: string; readonly name?: string | undefined },\n\tavailable: readonly T[],\n): T[] {\n\tconst family = modelRouteFamily(failed);\n\tif (!family) return [];\n\tconst candidates: T[] = [];\n\tfor (const model of available) {\n\t\tif (model.provider === failed.provider && model.id === failed.id) continue;\n\t\tif (modelRouteFamily(model) === family) candidates.push(model);\n\t}\n\treturn candidates;\n}\n\n/**\n * Errors the agent loop may auto-retry (after optional failover / message sanitize).\n * Kept in one place so agent-session and tests share the same contract.\n */\nexport function isTransientProviderErrorMessage(text: string | undefined): boolean {\n\tif (!text) return false;\n\t// Checked first: the payload carries `invalid_request_error`, which the\n\t// pattern below treats as transient, but a stale client version never heals\n\t// by retrying.\n\tif (isClaudeCodeVersionTooOldMessage(text)) return false;\n\tif (isCodexChatgptAccountUnsupportedModelMessage(text)) return false;\n\t// `at capacity|high demand`: xAI serves overload as HTTP 429 whose body carries\n\t// no status or limit token, so the message-level classifier misses it and the\n\t// turn never auto-retries (observed 2026-09-03, xai/grok-4.6).\n\tif (\n\t\t/overloaded|at capacity|high demand|provider.?returned.?error|rate.?limit|too many requests|service.?unavailable|server.?error|internal.?error|network.?error|connection.?error|connection.?refused|connection.?lost|websocket.?closed|websocket.?error|other side closed|fetch failed|upstream.?connect|reset before headers|socket hang up|ended without|stream ended before message_stop|stream ended before terminal|http2 request did not get a response|timed? out|timeout|\\bterminated\\b|retry delay|content\\/safety stop|stop_reason\\s*=\\s*(refusal|sensitive)|safety stop|tool_call_id\\s+is\\s+not\\s+found|tool_call_id\\s+not\\s+found|invalid_request_error|json error injected into sse stream|injected into sse/i.test(\n\t\t\ttext,\n\t\t)\n\t) {\n\t\treturn true;\n\t}\n\t// Status-looking numbers only count with adjacent transport context — a\n\t// bare `500` in a message body (\"saved 500 records\") is not a status code.\n\treturn hasStatusCodeLike(text, [429, 500, 502, 503, 504]);\n}\n\nexport function resolveFailoverCandidates(\n\tsettings: ProviderResilienceSettings | undefined,\n): readonly FailoverCandidate[] {\n\tconst custom = settings?.failoverCandidates;\n\tif (custom && custom.length > 0) return custom;\n\treturn DEFAULT_SAFETY_FAILOVER_CANDIDATES;\n}\n\n/** Explicit `--model` / `--provider` pins stay on the requested model. */\nexport function shouldHonorSafetyFailover(options: {\n\treadonly autoFailoverOnSafetyStop: boolean;\n\treadonly modelPinned: boolean;\n\treadonly modelId?: string;\n\treadonly provider?: string;\n}): boolean {\n\tif (!options.autoFailoverOnSafetyStop || options.modelPinned) return false;\n\treturn !isNoSafetyFailoverModel(options.modelId, options.provider);\n}\n\n/** Sticky-model eject still applies unless the user pinned that model. */\nexport function shouldEjectStickySafetyModel(options: {\n\treadonly blockStickySafetyModels: boolean;\n\treadonly modelPinned: boolean;\n}): boolean {\n\treturn options.blockStickySafetyModels && !options.modelPinned;\n}\n\nexport function resolveProviderResilience(settings: ProviderResilienceSettings | undefined): {\n\treadonly blockStickySafetyModels: boolean;\n\treadonly autoFailoverOnSafetyStop: boolean;\n\treadonly failoverCandidates: readonly FailoverCandidate[];\n} {\n\treturn {\n\t\tblockStickySafetyModels: settings?.blockStickySafetyModels ?? DEFAULT_PROVIDER_RESILIENCE.blockStickySafetyModels,\n\t\tautoFailoverOnSafetyStop:\n\t\t\tsettings?.autoFailoverOnSafetyStop ?? DEFAULT_PROVIDER_RESILIENCE.autoFailoverOnSafetyStop,\n\t\tfailoverCandidates: resolveFailoverCandidates(settings),\n\t};\n}\n\n/**\n * Pick first failover candidate that is not the current model and passes `isAllowed`.\n * Pure — caller performs auth checks and setModel.\n */\nexport function pickFailoverCandidate(\n\tcandidates: readonly FailoverCandidate[],\n\tcurrent: { readonly provider?: string; readonly id?: string } | undefined,\n\tisAllowed: (candidate: FailoverCandidate) => boolean,\n): FailoverCandidate | undefined {\n\tfor (const c of candidates) {\n\t\tif (current && c.provider === current.provider && c.id === current.id) continue;\n\t\tif (isStickySafetyModel(c.id, c.provider)) continue;\n\t\tif (!isAllowed(c)) continue;\n\t\treturn c;\n\t}\n\treturn undefined;\n}\n\nexport function stickySafetyBlockMessage(modelId: string, provider: string): string {\n\treturn (\n\t\t`Blocked sticky safety model ${provider}/${modelId} (providerResilience.blockStickySafetyModels). ` +\n\t\t`Use kimi-coding/k3, modelstudio-maas/qwen3.8-max, grok-4.5, or deepseek; ` +\n\t\t`or set providerResilience.blockStickySafetyModels=false.`\n\t);\n}\n"]}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Command Code Provider API usage: maps `/alpha` billing + rolling windows onto
3
+ * the status-rail meters. Auth is the stored Command Code API key from
4
+ * `models.json` / auth storage; this module never reads cookies or env itself.
5
+ */
6
+ import type { FetchLike, SubscriptionUsageSnapshot } from "./provider-usage-types.ts";
7
+ export declare function parseCommandCodeUsageSnapshot(value: unknown): Pick<SubscriptionUsageSnapshot, "windows" | "message">;
8
+ export declare function fetchCommandCodeUsage(label: string, apiKey: string, fetchImpl: FetchLike): Promise<SubscriptionUsageSnapshot>;
9
+ //# sourceMappingURL=provider-usage-commandcode.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provider-usage-commandcode.d.ts","sourceRoot":"","sources":["../../src/core/provider-usage-commandcode.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,KAAK,EAAE,SAAS,EAAE,yBAAyB,EAA2B,MAAM,2BAA2B,CAAC;AAgB/G,wBAAgB,6BAA6B,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI,CAAC,yBAAyB,EAAE,SAAS,GAAG,SAAS,CAAC,CAiBpH;AAED,wBAAsB,qBAAqB,CAC1C,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,SAAS,GAClB,OAAO,CAAC,yBAAyB,CAAC,CA6BpC","sourcesContent":["/**\n * Command Code Provider API usage: maps `/alpha` billing + rolling windows onto\n * the status-rail meters. Auth is the stored Command Code API key from\n * `models.json` / auth storage; this module never reads cookies or env itself.\n */\n\nimport { clampPercent, usageText } from \"./provider-usage-text.ts\";\nimport type { FetchLike, SubscriptionUsageSnapshot, SubscriptionUsageWindow } from \"./provider-usage-types.ts\";\n\nconst COMMAND_CODE_ORIGIN = \"https://api.commandcode.ai\";\nconst MAX_USAGE_RESPONSE_BYTES = 1024 * 1024;\nconst PLAN_NAMES: Readonly<Record<string, string>> = {\n\t\"individual-go\": \"Go\",\n\t\"individual-goat\": \"GOAT\",\n\t\"individual-pro\": \"Pro\",\n\t\"individual-max\": \"Max\",\n\t\"individual-ultra\": \"Ultra\",\n\tgo: \"Go\",\n\tgoat: \"GOAT\",\n\tpro: \"Pro\",\n\tmax: \"Max\",\n};\n\nexport function parseCommandCodeUsageSnapshot(value: unknown): Pick<SubscriptionUsageSnapshot, \"windows\" | \"message\"> {\n\tconst root = record(value);\n\tconst creditsNode = record(root?.credits);\n\tconst credits = record(creditsNode?.credits) ?? creditsNode;\n\tconst windowLimits = record(root?.windowLimits) ?? record(creditsNode?.windowLimits);\n\tconst summary = record(root?.summary);\n\tconst subscription = record(record(root?.subscription)?.data) ?? record(root?.subscription);\n\tconst windows: SubscriptionUsageWindow[] = [];\n\tconst fiveHour = creditWindow(\"5H\", record(windowLimits?.fiveHour));\n\tconst weekly = creditWindow(\"7D\", record(windowLimits?.weekly));\n\tif (fiveHour) windows.push(fiveHour);\n\tif (weekly) windows.push(weekly);\n\tconst monthly = monthlyWindow(credits, summary, subscription);\n\tif (monthly) windows.push(monthly);\n\tconst message = planMessage(subscription);\n\tif (windows.length === 0 && message === undefined) return { windows: [], message: \"usage unavailable\" };\n\treturn { windows, ...(message === undefined ? {} : { message }) };\n}\n\nexport async function fetchCommandCodeUsage(\n\tlabel: string,\n\tapiKey: string,\n\tfetchImpl: FetchLike,\n): Promise<SubscriptionUsageSnapshot> {\n\tconst unavailable = (): SubscriptionUsageSnapshot => ({ label, windows: [], message: \"usage unavailable\" });\n\tconst whoami = await fetchQuotaJson(fetchImpl, `${COMMAND_CODE_ORIGIN}/alpha/whoami`, apiKey);\n\tconst account = parseWhoami(whoami);\n\tif (!account) return unavailable();\n\n\tconst orgQuery = account.orgId ? `?orgId=${encodeURIComponent(account.orgId)}` : \"\";\n\tconst [creditsPayload, subscriptionPayload] = await Promise.all([\n\t\tfetchQuotaJson(fetchImpl, `${COMMAND_CODE_ORIGIN}/alpha/billing/credits${orgQuery}`, apiKey),\n\t\tfetchQuotaJson(fetchImpl, `${COMMAND_CODE_ORIGIN}/alpha/billing/subscriptions${orgQuery}`, apiKey),\n\t]);\n\tconst periodStart = periodQuery(record(record(subscriptionPayload)?.data)?.currentPeriodStart);\n\tconst summaryQuery = new URLSearchParams();\n\tif (account.orgId) summaryQuery.set(\"orgId\", account.orgId);\n\tif (periodStart) summaryQuery.set(\"since\", periodStart);\n\tconst summaryPath = summaryQuery.size > 0 ? `?${summaryQuery.toString()}` : \"\";\n\tconst summaryPayload = await fetchQuotaJson(\n\t\tfetchImpl,\n\t\t`${COMMAND_CODE_ORIGIN}/alpha/usage/summary${summaryPath}`,\n\t\tapiKey,\n\t);\n\tconst { windows, message } = parseCommandCodeUsageSnapshot({\n\t\tcredits: creditsPayload,\n\t\twindowLimits: record(creditsPayload)?.windowLimits,\n\t\tsummary: summaryPayload,\n\t\tsubscription: subscriptionPayload,\n\t});\n\tif (windows.length > 0) return { label, windows, ...(message === undefined ? {} : { message }) };\n\treturn { label, windows: [], message: message ?? \"usage unavailable\" };\n}\n\nasync function fetchQuotaJson(fetchImpl: FetchLike, url: string, apiKey: string): Promise<unknown> {\n\tconst response = await fetchImpl(url, {\n\t\theaders: {\n\t\t\tAuthorization: `Bearer ${apiKey}`,\n\t\t\tAccept: \"application/json\",\n\t\t\t\"User-Agent\": \"omk\",\n\t\t},\n\t\tsignal: AbortSignal.timeout(10_000),\n\t});\n\tif (!response.ok) return undefined;\n\tconst declaredBytes = Number(response.headers?.get(\"content-length\"));\n\tif (Number.isFinite(declaredBytes) && declaredBytes > MAX_USAGE_RESPONSE_BYTES) return undefined;\n\tif (!response.body || typeof response.body.getReader !== \"function\") {\n\t\treturn await response.json();\n\t}\n\tconst reader = response.body.getReader();\n\tconst chunks: Uint8Array[] = [];\n\tlet totalBytes = 0;\n\twhile (true) {\n\t\tconst { done, value } = await reader.read();\n\t\tif (done) break;\n\t\ttotalBytes += value.byteLength;\n\t\tif (totalBytes > MAX_USAGE_RESPONSE_BYTES) {\n\t\t\tawait reader.cancel();\n\t\t\treturn undefined;\n\t\t}\n\t\tchunks.push(value);\n\t}\n\tconst body = new Uint8Array(totalBytes);\n\tlet offset = 0;\n\tfor (const chunk of chunks) {\n\t\tbody.set(chunk, offset);\n\t\toffset += chunk.byteLength;\n\t}\n\ttry {\n\t\treturn JSON.parse(new TextDecoder().decode(body)) as unknown;\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\nfunction parseWhoami(value: unknown): { readonly orgId?: string } | undefined {\n\tconst payload = record(value);\n\tif (!payload) return undefined;\n\tconst org = record(payload.org);\n\tconst user = record(payload.user);\n\tconst login =\n\t\tstringValue(org?.login) ?? stringValue(user?.userName) ?? stringValue(user?.name) ?? stringValue(user?.keyName);\n\tif (!login) return undefined;\n\tconst orgId = stringValue(org?.id);\n\treturn orgId === undefined ? {} : { orgId };\n}\n\nfunction creditWindow(label: string, entry: Record<string, unknown> | undefined): SubscriptionUsageWindow | undefined {\n\tif (!entry) return undefined;\n\tconst used = finiteNumber(entry.used);\n\tconst cap = finiteNumber(entry.cap);\n\tif (used === undefined || cap === undefined || cap <= 0 || (used === 0 && cap === 0)) return undefined;\n\tconst resetsAt = epochSeconds(entry.resetAt);\n\treturn {\n\t\tlabel,\n\t\tusedPercent: clampPercent((used / cap) * 100),\n\t\t...(resetsAt === undefined ? {} : { resetsAt }),\n\t};\n}\n\nfunction monthlyWindow(\n\tcredits: Record<string, unknown> | undefined,\n\tsummary: Record<string, unknown> | undefined,\n\tsubscription: Record<string, unknown> | undefined,\n): SubscriptionUsageWindow | undefined {\n\tconst monthly = finiteNumber(credits?.monthlyCredits);\n\tconst purchased = finiteNumber(credits?.purchasedCredits);\n\tconst free = finiteNumber(credits?.freeCredits);\n\tif (monthly === undefined && purchased === undefined && free === undefined) return undefined;\n\tconst remaining = (monthly ?? 0) + (purchased ?? 0) + (free ?? 0);\n\tconst spent = finiteNumber(summary?.totalCost);\n\tif (spent === undefined) return undefined;\n\tconst pool = remaining + spent;\n\tif (pool <= 0) return undefined;\n\tconst resetsAt = epochSeconds(subscription?.currentPeriodEnd);\n\treturn {\n\t\tlabel: \"MO\",\n\t\tusedPercent: clampPercent((spent / pool) * 100),\n\t\t...(resetsAt === undefined ? {} : { resetsAt }),\n\t};\n}\n\nfunction planMessage(subscription: Record<string, unknown> | undefined): string | undefined {\n\tconst planId = stringValue(subscription?.planId);\n\tif (!planId) return undefined;\n\treturn usageText(PLAN_NAMES[planId] ?? planId.replace(/[_-]+/g, \" \"));\n}\n\nfunction periodQuery(value: unknown): string | undefined {\n\tif (typeof value === \"string\" && value.trim()) return value.trim();\n\tconst seconds = epochSeconds(value);\n\tif (seconds === undefined) return undefined;\n\treturn new Date(seconds * 1000).toISOString();\n}\n\nfunction epochSeconds(value: unknown): number | undefined {\n\tif (value === null) return undefined;\n\tconst numeric = finiteNumber(value);\n\tif (numeric !== undefined) return numeric > 1_000_000_000_000 ? numeric / 1000 : numeric;\n\tif (typeof value !== \"string\" || !value.trim()) return undefined;\n\tconst parsed = Date.parse(value);\n\treturn Number.isFinite(parsed) ? parsed / 1000 : undefined;\n}\n\nfunction finiteNumber(value: unknown): number | undefined {\n\tconst number =\n\t\ttypeof value === \"number\" ? value : typeof value === \"string\" && value.trim() ? Number(value) : Number.NaN;\n\treturn Number.isFinite(number) ? number : undefined;\n}\n\nfunction stringValue(value: unknown): string | undefined {\n\treturn typeof value === \"string\" && value.trim() ? value.trim() : undefined;\n}\n\nfunction record(value: unknown): Record<string, unknown> | undefined {\n\treturn typeof value === \"object\" && value !== null && !Array.isArray(value)\n\t\t? (value as Record<string, unknown>)\n\t\t: undefined;\n}\n"]}
@@ -0,0 +1,198 @@
1
+ /**
2
+ * Command Code Provider API usage: maps `/alpha` billing + rolling windows onto
3
+ * the status-rail meters. Auth is the stored Command Code API key from
4
+ * `models.json` / auth storage; this module never reads cookies or env itself.
5
+ */
6
+ import { clampPercent, usageText } from "./provider-usage-text.js";
7
+ const COMMAND_CODE_ORIGIN = "https://api.commandcode.ai";
8
+ const MAX_USAGE_RESPONSE_BYTES = 1024 * 1024;
9
+ const PLAN_NAMES = {
10
+ "individual-go": "Go",
11
+ "individual-goat": "GOAT",
12
+ "individual-pro": "Pro",
13
+ "individual-max": "Max",
14
+ "individual-ultra": "Ultra",
15
+ go: "Go",
16
+ goat: "GOAT",
17
+ pro: "Pro",
18
+ max: "Max",
19
+ };
20
+ export function parseCommandCodeUsageSnapshot(value) {
21
+ const root = record(value);
22
+ const creditsNode = record(root?.credits);
23
+ const credits = record(creditsNode?.credits) ?? creditsNode;
24
+ const windowLimits = record(root?.windowLimits) ?? record(creditsNode?.windowLimits);
25
+ const summary = record(root?.summary);
26
+ const subscription = record(record(root?.subscription)?.data) ?? record(root?.subscription);
27
+ const windows = [];
28
+ const fiveHour = creditWindow("5H", record(windowLimits?.fiveHour));
29
+ const weekly = creditWindow("7D", record(windowLimits?.weekly));
30
+ if (fiveHour)
31
+ windows.push(fiveHour);
32
+ if (weekly)
33
+ windows.push(weekly);
34
+ const monthly = monthlyWindow(credits, summary, subscription);
35
+ if (monthly)
36
+ windows.push(monthly);
37
+ const message = planMessage(subscription);
38
+ if (windows.length === 0 && message === undefined)
39
+ return { windows: [], message: "usage unavailable" };
40
+ return { windows, ...(message === undefined ? {} : { message }) };
41
+ }
42
+ export async function fetchCommandCodeUsage(label, apiKey, fetchImpl) {
43
+ const unavailable = () => ({ label, windows: [], message: "usage unavailable" });
44
+ const whoami = await fetchQuotaJson(fetchImpl, `${COMMAND_CODE_ORIGIN}/alpha/whoami`, apiKey);
45
+ const account = parseWhoami(whoami);
46
+ if (!account)
47
+ return unavailable();
48
+ const orgQuery = account.orgId ? `?orgId=${encodeURIComponent(account.orgId)}` : "";
49
+ const [creditsPayload, subscriptionPayload] = await Promise.all([
50
+ fetchQuotaJson(fetchImpl, `${COMMAND_CODE_ORIGIN}/alpha/billing/credits${orgQuery}`, apiKey),
51
+ fetchQuotaJson(fetchImpl, `${COMMAND_CODE_ORIGIN}/alpha/billing/subscriptions${orgQuery}`, apiKey),
52
+ ]);
53
+ const periodStart = periodQuery(record(record(subscriptionPayload)?.data)?.currentPeriodStart);
54
+ const summaryQuery = new URLSearchParams();
55
+ if (account.orgId)
56
+ summaryQuery.set("orgId", account.orgId);
57
+ if (periodStart)
58
+ summaryQuery.set("since", periodStart);
59
+ const summaryPath = summaryQuery.size > 0 ? `?${summaryQuery.toString()}` : "";
60
+ const summaryPayload = await fetchQuotaJson(fetchImpl, `${COMMAND_CODE_ORIGIN}/alpha/usage/summary${summaryPath}`, apiKey);
61
+ const { windows, message } = parseCommandCodeUsageSnapshot({
62
+ credits: creditsPayload,
63
+ windowLimits: record(creditsPayload)?.windowLimits,
64
+ summary: summaryPayload,
65
+ subscription: subscriptionPayload,
66
+ });
67
+ if (windows.length > 0)
68
+ return { label, windows, ...(message === undefined ? {} : { message }) };
69
+ return { label, windows: [], message: message ?? "usage unavailable" };
70
+ }
71
+ async function fetchQuotaJson(fetchImpl, url, apiKey) {
72
+ const response = await fetchImpl(url, {
73
+ headers: {
74
+ Authorization: `Bearer ${apiKey}`,
75
+ Accept: "application/json",
76
+ "User-Agent": "omk",
77
+ },
78
+ signal: AbortSignal.timeout(10_000),
79
+ });
80
+ if (!response.ok)
81
+ return undefined;
82
+ const declaredBytes = Number(response.headers?.get("content-length"));
83
+ if (Number.isFinite(declaredBytes) && declaredBytes > MAX_USAGE_RESPONSE_BYTES)
84
+ return undefined;
85
+ if (!response.body || typeof response.body.getReader !== "function") {
86
+ return await response.json();
87
+ }
88
+ const reader = response.body.getReader();
89
+ const chunks = [];
90
+ let totalBytes = 0;
91
+ while (true) {
92
+ const { done, value } = await reader.read();
93
+ if (done)
94
+ break;
95
+ totalBytes += value.byteLength;
96
+ if (totalBytes > MAX_USAGE_RESPONSE_BYTES) {
97
+ await reader.cancel();
98
+ return undefined;
99
+ }
100
+ chunks.push(value);
101
+ }
102
+ const body = new Uint8Array(totalBytes);
103
+ let offset = 0;
104
+ for (const chunk of chunks) {
105
+ body.set(chunk, offset);
106
+ offset += chunk.byteLength;
107
+ }
108
+ try {
109
+ return JSON.parse(new TextDecoder().decode(body));
110
+ }
111
+ catch {
112
+ return undefined;
113
+ }
114
+ }
115
+ function parseWhoami(value) {
116
+ const payload = record(value);
117
+ if (!payload)
118
+ return undefined;
119
+ const org = record(payload.org);
120
+ const user = record(payload.user);
121
+ const login = stringValue(org?.login) ?? stringValue(user?.userName) ?? stringValue(user?.name) ?? stringValue(user?.keyName);
122
+ if (!login)
123
+ return undefined;
124
+ const orgId = stringValue(org?.id);
125
+ return orgId === undefined ? {} : { orgId };
126
+ }
127
+ function creditWindow(label, entry) {
128
+ if (!entry)
129
+ return undefined;
130
+ const used = finiteNumber(entry.used);
131
+ const cap = finiteNumber(entry.cap);
132
+ if (used === undefined || cap === undefined || cap <= 0 || (used === 0 && cap === 0))
133
+ return undefined;
134
+ const resetsAt = epochSeconds(entry.resetAt);
135
+ return {
136
+ label,
137
+ usedPercent: clampPercent((used / cap) * 100),
138
+ ...(resetsAt === undefined ? {} : { resetsAt }),
139
+ };
140
+ }
141
+ function monthlyWindow(credits, summary, subscription) {
142
+ const monthly = finiteNumber(credits?.monthlyCredits);
143
+ const purchased = finiteNumber(credits?.purchasedCredits);
144
+ const free = finiteNumber(credits?.freeCredits);
145
+ if (monthly === undefined && purchased === undefined && free === undefined)
146
+ return undefined;
147
+ const remaining = (monthly ?? 0) + (purchased ?? 0) + (free ?? 0);
148
+ const spent = finiteNumber(summary?.totalCost);
149
+ if (spent === undefined)
150
+ return undefined;
151
+ const pool = remaining + spent;
152
+ if (pool <= 0)
153
+ return undefined;
154
+ const resetsAt = epochSeconds(subscription?.currentPeriodEnd);
155
+ return {
156
+ label: "MO",
157
+ usedPercent: clampPercent((spent / pool) * 100),
158
+ ...(resetsAt === undefined ? {} : { resetsAt }),
159
+ };
160
+ }
161
+ function planMessage(subscription) {
162
+ const planId = stringValue(subscription?.planId);
163
+ if (!planId)
164
+ return undefined;
165
+ return usageText(PLAN_NAMES[planId] ?? planId.replace(/[_-]+/g, " "));
166
+ }
167
+ function periodQuery(value) {
168
+ if (typeof value === "string" && value.trim())
169
+ return value.trim();
170
+ const seconds = epochSeconds(value);
171
+ if (seconds === undefined)
172
+ return undefined;
173
+ return new Date(seconds * 1000).toISOString();
174
+ }
175
+ function epochSeconds(value) {
176
+ if (value === null)
177
+ return undefined;
178
+ const numeric = finiteNumber(value);
179
+ if (numeric !== undefined)
180
+ return numeric > 1_000_000_000_000 ? numeric / 1000 : numeric;
181
+ if (typeof value !== "string" || !value.trim())
182
+ return undefined;
183
+ const parsed = Date.parse(value);
184
+ return Number.isFinite(parsed) ? parsed / 1000 : undefined;
185
+ }
186
+ function finiteNumber(value) {
187
+ const number = typeof value === "number" ? value : typeof value === "string" && value.trim() ? Number(value) : Number.NaN;
188
+ return Number.isFinite(number) ? number : undefined;
189
+ }
190
+ function stringValue(value) {
191
+ return typeof value === "string" && value.trim() ? value.trim() : undefined;
192
+ }
193
+ function record(value) {
194
+ return typeof value === "object" && value !== null && !Array.isArray(value)
195
+ ? value
196
+ : undefined;
197
+ }
198
+ //# sourceMappingURL=provider-usage-commandcode.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provider-usage-commandcode.js","sourceRoot":"","sources":["../../src/core/provider-usage-commandcode.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Command Code Provider API usage: maps `/alpha` billing + rolling windows onto\n * the status-rail meters. Auth is the stored Command Code API key from\n * `models.json` / auth storage; this module never reads cookies or env itself.\n */\n\nimport { clampPercent, usageText } from \"./provider-usage-text.ts\";\nimport type { FetchLike, SubscriptionUsageSnapshot, SubscriptionUsageWindow } from \"./provider-usage-types.ts\";\n\nconst COMMAND_CODE_ORIGIN = \"https://api.commandcode.ai\";\nconst MAX_USAGE_RESPONSE_BYTES = 1024 * 1024;\nconst PLAN_NAMES: Readonly<Record<string, string>> = {\n\t\"individual-go\": \"Go\",\n\t\"individual-goat\": \"GOAT\",\n\t\"individual-pro\": \"Pro\",\n\t\"individual-max\": \"Max\",\n\t\"individual-ultra\": \"Ultra\",\n\tgo: \"Go\",\n\tgoat: \"GOAT\",\n\tpro: \"Pro\",\n\tmax: \"Max\",\n};\n\nexport function parseCommandCodeUsageSnapshot(value: unknown): Pick<SubscriptionUsageSnapshot, \"windows\" | \"message\"> {\n\tconst root = record(value);\n\tconst creditsNode = record(root?.credits);\n\tconst credits = record(creditsNode?.credits) ?? creditsNode;\n\tconst windowLimits = record(root?.windowLimits) ?? record(creditsNode?.windowLimits);\n\tconst summary = record(root?.summary);\n\tconst subscription = record(record(root?.subscription)?.data) ?? record(root?.subscription);\n\tconst windows: SubscriptionUsageWindow[] = [];\n\tconst fiveHour = creditWindow(\"5H\", record(windowLimits?.fiveHour));\n\tconst weekly = creditWindow(\"7D\", record(windowLimits?.weekly));\n\tif (fiveHour) windows.push(fiveHour);\n\tif (weekly) windows.push(weekly);\n\tconst monthly = monthlyWindow(credits, summary, subscription);\n\tif (monthly) windows.push(monthly);\n\tconst message = planMessage(subscription);\n\tif (windows.length === 0 && message === undefined) return { windows: [], message: \"usage unavailable\" };\n\treturn { windows, ...(message === undefined ? {} : { message }) };\n}\n\nexport async function fetchCommandCodeUsage(\n\tlabel: string,\n\tapiKey: string,\n\tfetchImpl: FetchLike,\n): Promise<SubscriptionUsageSnapshot> {\n\tconst unavailable = (): SubscriptionUsageSnapshot => ({ label, windows: [], message: \"usage unavailable\" });\n\tconst whoami = await fetchQuotaJson(fetchImpl, `${COMMAND_CODE_ORIGIN}/alpha/whoami`, apiKey);\n\tconst account = parseWhoami(whoami);\n\tif (!account) return unavailable();\n\n\tconst orgQuery = account.orgId ? `?orgId=${encodeURIComponent(account.orgId)}` : \"\";\n\tconst [creditsPayload, subscriptionPayload] = await Promise.all([\n\t\tfetchQuotaJson(fetchImpl, `${COMMAND_CODE_ORIGIN}/alpha/billing/credits${orgQuery}`, apiKey),\n\t\tfetchQuotaJson(fetchImpl, `${COMMAND_CODE_ORIGIN}/alpha/billing/subscriptions${orgQuery}`, apiKey),\n\t]);\n\tconst periodStart = periodQuery(record(record(subscriptionPayload)?.data)?.currentPeriodStart);\n\tconst summaryQuery = new URLSearchParams();\n\tif (account.orgId) summaryQuery.set(\"orgId\", account.orgId);\n\tif (periodStart) summaryQuery.set(\"since\", periodStart);\n\tconst summaryPath = summaryQuery.size > 0 ? `?${summaryQuery.toString()}` : \"\";\n\tconst summaryPayload = await fetchQuotaJson(\n\t\tfetchImpl,\n\t\t`${COMMAND_CODE_ORIGIN}/alpha/usage/summary${summaryPath}`,\n\t\tapiKey,\n\t);\n\tconst { windows, message } = parseCommandCodeUsageSnapshot({\n\t\tcredits: creditsPayload,\n\t\twindowLimits: record(creditsPayload)?.windowLimits,\n\t\tsummary: summaryPayload,\n\t\tsubscription: subscriptionPayload,\n\t});\n\tif (windows.length > 0) return { label, windows, ...(message === undefined ? {} : { message }) };\n\treturn { label, windows: [], message: message ?? \"usage unavailable\" };\n}\n\nasync function fetchQuotaJson(fetchImpl: FetchLike, url: string, apiKey: string): Promise<unknown> {\n\tconst response = await fetchImpl(url, {\n\t\theaders: {\n\t\t\tAuthorization: `Bearer ${apiKey}`,\n\t\t\tAccept: \"application/json\",\n\t\t\t\"User-Agent\": \"omk\",\n\t\t},\n\t\tsignal: AbortSignal.timeout(10_000),\n\t});\n\tif (!response.ok) return undefined;\n\tconst declaredBytes = Number(response.headers?.get(\"content-length\"));\n\tif (Number.isFinite(declaredBytes) && declaredBytes > MAX_USAGE_RESPONSE_BYTES) return undefined;\n\tif (!response.body || typeof response.body.getReader !== \"function\") {\n\t\treturn await response.json();\n\t}\n\tconst reader = response.body.getReader();\n\tconst chunks: Uint8Array[] = [];\n\tlet totalBytes = 0;\n\twhile (true) {\n\t\tconst { done, value } = await reader.read();\n\t\tif (done) break;\n\t\ttotalBytes += value.byteLength;\n\t\tif (totalBytes > MAX_USAGE_RESPONSE_BYTES) {\n\t\t\tawait reader.cancel();\n\t\t\treturn undefined;\n\t\t}\n\t\tchunks.push(value);\n\t}\n\tconst body = new Uint8Array(totalBytes);\n\tlet offset = 0;\n\tfor (const chunk of chunks) {\n\t\tbody.set(chunk, offset);\n\t\toffset += chunk.byteLength;\n\t}\n\ttry {\n\t\treturn JSON.parse(new TextDecoder().decode(body)) as unknown;\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\nfunction parseWhoami(value: unknown): { readonly orgId?: string } | undefined {\n\tconst payload = record(value);\n\tif (!payload) return undefined;\n\tconst org = record(payload.org);\n\tconst user = record(payload.user);\n\tconst login =\n\t\tstringValue(org?.login) ?? stringValue(user?.userName) ?? stringValue(user?.name) ?? stringValue(user?.keyName);\n\tif (!login) return undefined;\n\tconst orgId = stringValue(org?.id);\n\treturn orgId === undefined ? {} : { orgId };\n}\n\nfunction creditWindow(label: string, entry: Record<string, unknown> | undefined): SubscriptionUsageWindow | undefined {\n\tif (!entry) return undefined;\n\tconst used = finiteNumber(entry.used);\n\tconst cap = finiteNumber(entry.cap);\n\tif (used === undefined || cap === undefined || cap <= 0 || (used === 0 && cap === 0)) return undefined;\n\tconst resetsAt = epochSeconds(entry.resetAt);\n\treturn {\n\t\tlabel,\n\t\tusedPercent: clampPercent((used / cap) * 100),\n\t\t...(resetsAt === undefined ? {} : { resetsAt }),\n\t};\n}\n\nfunction monthlyWindow(\n\tcredits: Record<string, unknown> | undefined,\n\tsummary: Record<string, unknown> | undefined,\n\tsubscription: Record<string, unknown> | undefined,\n): SubscriptionUsageWindow | undefined {\n\tconst monthly = finiteNumber(credits?.monthlyCredits);\n\tconst purchased = finiteNumber(credits?.purchasedCredits);\n\tconst free = finiteNumber(credits?.freeCredits);\n\tif (monthly === undefined && purchased === undefined && free === undefined) return undefined;\n\tconst remaining = (monthly ?? 0) + (purchased ?? 0) + (free ?? 0);\n\tconst spent = finiteNumber(summary?.totalCost);\n\tif (spent === undefined) return undefined;\n\tconst pool = remaining + spent;\n\tif (pool <= 0) return undefined;\n\tconst resetsAt = epochSeconds(subscription?.currentPeriodEnd);\n\treturn {\n\t\tlabel: \"MO\",\n\t\tusedPercent: clampPercent((spent / pool) * 100),\n\t\t...(resetsAt === undefined ? {} : { resetsAt }),\n\t};\n}\n\nfunction planMessage(subscription: Record<string, unknown> | undefined): string | undefined {\n\tconst planId = stringValue(subscription?.planId);\n\tif (!planId) return undefined;\n\treturn usageText(PLAN_NAMES[planId] ?? planId.replace(/[_-]+/g, \" \"));\n}\n\nfunction periodQuery(value: unknown): string | undefined {\n\tif (typeof value === \"string\" && value.trim()) return value.trim();\n\tconst seconds = epochSeconds(value);\n\tif (seconds === undefined) return undefined;\n\treturn new Date(seconds * 1000).toISOString();\n}\n\nfunction epochSeconds(value: unknown): number | undefined {\n\tif (value === null) return undefined;\n\tconst numeric = finiteNumber(value);\n\tif (numeric !== undefined) return numeric > 1_000_000_000_000 ? numeric / 1000 : numeric;\n\tif (typeof value !== \"string\" || !value.trim()) return undefined;\n\tconst parsed = Date.parse(value);\n\treturn Number.isFinite(parsed) ? parsed / 1000 : undefined;\n}\n\nfunction finiteNumber(value: unknown): number | undefined {\n\tconst number =\n\t\ttypeof value === \"number\" ? value : typeof value === \"string\" && value.trim() ? Number(value) : Number.NaN;\n\treturn Number.isFinite(number) ? number : undefined;\n}\n\nfunction stringValue(value: unknown): string | undefined {\n\treturn typeof value === \"string\" && value.trim() ? value.trim() : undefined;\n}\n\nfunction record(value: unknown): Record<string, unknown> | undefined {\n\treturn typeof value === \"object\" && value !== null && !Array.isArray(value)\n\t\t? (value as Record<string, unknown>)\n\t\t: undefined;\n}\n"]}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Devin CLI subscription usage: maps the account `GetUserStatus` payload onto
3
+ * the status-rail windows. The token is whatever `/login devin` stored or an
4
+ * already-owned `DEVIN_API_KEY`; this module never reads the environment itself.
5
+ */
6
+ import { type DevinUserStatus } from "omk-ai";
7
+ import type { FetchLike, SubscriptionUsageSnapshot } from "./provider-usage-types.ts";
8
+ /**
9
+ * Map a decoded `GetUserStatus` payload to rail windows. The server reports
10
+ * remaining percents; the rail renders used percents. Matches the CLI `/usage`
11
+ * surface: a percent window is shown when the plan does not hide it and the
12
+ * wire dated the reset, or when the plan is explicitly quota-billed. Credit-
13
+ * billed plans leave proto percents at 0; those are not exhausted windows.
14
+ * A dated reset without a percent still reads as exhausted.
15
+ */
16
+ export declare function parseDevinUsageSnapshot(status: DevinUserStatus): Pick<SubscriptionUsageSnapshot, "windows" | "message">;
17
+ export declare function fetchDevinUsage(label: string, apiKey: string, fetchImpl: FetchLike): Promise<SubscriptionUsageSnapshot>;
18
+ //# sourceMappingURL=provider-usage-devin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provider-usage-devin.d.ts","sourceRoot":"","sources":["../../src/core/provider-usage-devin.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,KAAK,eAAe,EAAsB,MAAM,QAAQ,CAAC;AAElE,OAAO,KAAK,EAAE,SAAS,EAAE,yBAAyB,EAA2B,MAAM,2BAA2B,CAAC;AAK/G;;;;;;;GAOG;AACH,wBAAgB,uBAAuB,CACtC,MAAM,EAAE,eAAe,GACrB,IAAI,CAAC,yBAAyB,EAAE,SAAS,GAAG,SAAS,CAAC,CA8BxD;AAoBD,wBAAsB,eAAe,CACpC,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,SAAS,GAClB,OAAO,CAAC,yBAAyB,CAAC,CAKpC","sourcesContent":["/**\n * Devin CLI subscription usage: maps the account `GetUserStatus` payload onto\n * the status-rail windows. The token is whatever `/login devin` stored or an\n * already-owned `DEVIN_API_KEY`; this module never reads the environment itself.\n */\n\nimport { type DevinUserStatus, getDevinUserStatus } from \"omk-ai\";\nimport { clampPercent, usageText } from \"./provider-usage-text.ts\";\nimport type { FetchLike, SubscriptionUsageSnapshot, SubscriptionUsageWindow } from \"./provider-usage-types.ts\";\n\n/** CLI `BillingStrategy.QUOTA` — undated remaining percents are still a quota window. */\nconst DEVIN_BILLING_QUOTA = 2;\n\n/**\n * Map a decoded `GetUserStatus` payload to rail windows. The server reports\n * remaining percents; the rail renders used percents. Matches the CLI `/usage`\n * surface: a percent window is shown when the plan does not hide it and the\n * wire dated the reset, or when the plan is explicitly quota-billed. Credit-\n * billed plans leave proto percents at 0; those are not exhausted windows.\n * A dated reset without a percent still reads as exhausted.\n */\nexport function parseDevinUsageSnapshot(\n\tstatus: DevinUserStatus,\n): Pick<SubscriptionUsageSnapshot, \"windows\" | \"message\"> {\n\tconst windows: SubscriptionUsageWindow[] = [];\n\tconst quotaWindow = (\n\t\tlabel: string,\n\t\thidden: boolean | undefined,\n\t\tremainingPercent: number | undefined,\n\t\tresetsAt: number | undefined,\n\t): SubscriptionUsageWindow | undefined => {\n\t\tif (hidden === true) return undefined;\n\t\tconst dated = resetsAt !== undefined && resetsAt > 0;\n\t\t// CLI /usage: dated reset, or an explicit quota plan. Credit-billed proto zeros are not windows.\n\t\tif (!dated && status.billingStrategy !== DEVIN_BILLING_QUOTA) return undefined;\n\t\tif (!dated && remainingPercent === undefined) return undefined;\n\t\treturn {\n\t\t\tlabel,\n\t\t\tusedPercent: clampPercent(100 - (remainingPercent ?? 0)),\n\t\t\t...(dated ? { resetsAt } : {}),\n\t\t};\n\t};\n\tconst daily = quotaWindow(\"1D\", status.hideDailyQuota, status.dailyQuotaRemainingPercent, status.dailyQuotaResetAt);\n\tconst weekly = quotaWindow(\n\t\t\"7D\",\n\t\tstatus.hideWeeklyQuota,\n\t\tstatus.weeklyQuotaRemainingPercent,\n\t\tstatus.weeklyQuotaResetAt,\n\t);\n\tif (daily) windows.push(daily);\n\tif (weekly) windows.push(weekly);\n\tconst message = devinUsageMessage(status);\n\treturn { windows, ...(message === undefined ? {} : { message }) };\n}\n\n/** Plan name plus credit balances for plans whose quota windows are not reported. */\nfunction devinUsageMessage(status: DevinUserStatus): string | undefined {\n\tconst parts: string[] = [];\n\tconst plan = usageText(status.planName);\n\tif (plan) parts.push(plan);\n\tconst balances: readonly (readonly [string, number | undefined])[] = [\n\t\t[\"prompt\", status.availablePromptCredits],\n\t\t[\"flow\", status.availableFlowCredits],\n\t\t[\"flex\", status.availableFlexCredits],\n\t];\n\tconst credits: string[] = [];\n\tfor (const [name, amount] of balances) {\n\t\tif (amount !== undefined) credits.push(`${amount} ${name}`);\n\t}\n\tif (credits.length > 0) parts.push(credits.join(\" · \"));\n\treturn parts.length > 0 ? parts.join(\" · \") : undefined;\n}\n\nexport async function fetchDevinUsage(\n\tlabel: string,\n\tapiKey: string,\n\tfetchImpl: FetchLike,\n): Promise<SubscriptionUsageSnapshot> {\n\tconst status = await getDevinUserStatus(apiKey, AbortSignal.timeout(10_000), fetchImpl);\n\tconst { windows, message } = parseDevinUsageSnapshot(status);\n\tif (windows.length > 0) return { label, windows, ...(message === undefined ? {} : { message }) };\n\treturn { label, windows: [], message: message ?? \"usage unavailable\" };\n}\n"]}
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Devin CLI subscription usage: maps the account `GetUserStatus` payload onto
3
+ * the status-rail windows. The token is whatever `/login devin` stored or an
4
+ * already-owned `DEVIN_API_KEY`; this module never reads the environment itself.
5
+ */
6
+ import { getDevinUserStatus } from "omk-ai";
7
+ import { clampPercent, usageText } from "./provider-usage-text.js";
8
+ /** CLI `BillingStrategy.QUOTA` — undated remaining percents are still a quota window. */
9
+ const DEVIN_BILLING_QUOTA = 2;
10
+ /**
11
+ * Map a decoded `GetUserStatus` payload to rail windows. The server reports
12
+ * remaining percents; the rail renders used percents. Matches the CLI `/usage`
13
+ * surface: a percent window is shown when the plan does not hide it and the
14
+ * wire dated the reset, or when the plan is explicitly quota-billed. Credit-
15
+ * billed plans leave proto percents at 0; those are not exhausted windows.
16
+ * A dated reset without a percent still reads as exhausted.
17
+ */
18
+ export function parseDevinUsageSnapshot(status) {
19
+ const windows = [];
20
+ const quotaWindow = (label, hidden, remainingPercent, resetsAt) => {
21
+ if (hidden === true)
22
+ return undefined;
23
+ const dated = resetsAt !== undefined && resetsAt > 0;
24
+ // CLI /usage: dated reset, or an explicit quota plan. Credit-billed proto zeros are not windows.
25
+ if (!dated && status.billingStrategy !== DEVIN_BILLING_QUOTA)
26
+ return undefined;
27
+ if (!dated && remainingPercent === undefined)
28
+ return undefined;
29
+ return {
30
+ label,
31
+ usedPercent: clampPercent(100 - (remainingPercent ?? 0)),
32
+ ...(dated ? { resetsAt } : {}),
33
+ };
34
+ };
35
+ const daily = quotaWindow("1D", status.hideDailyQuota, status.dailyQuotaRemainingPercent, status.dailyQuotaResetAt);
36
+ const weekly = quotaWindow("7D", status.hideWeeklyQuota, status.weeklyQuotaRemainingPercent, status.weeklyQuotaResetAt);
37
+ if (daily)
38
+ windows.push(daily);
39
+ if (weekly)
40
+ windows.push(weekly);
41
+ const message = devinUsageMessage(status);
42
+ return { windows, ...(message === undefined ? {} : { message }) };
43
+ }
44
+ /** Plan name plus credit balances for plans whose quota windows are not reported. */
45
+ function devinUsageMessage(status) {
46
+ const parts = [];
47
+ const plan = usageText(status.planName);
48
+ if (plan)
49
+ parts.push(plan);
50
+ const balances = [
51
+ ["prompt", status.availablePromptCredits],
52
+ ["flow", status.availableFlowCredits],
53
+ ["flex", status.availableFlexCredits],
54
+ ];
55
+ const credits = [];
56
+ for (const [name, amount] of balances) {
57
+ if (amount !== undefined)
58
+ credits.push(`${amount} ${name}`);
59
+ }
60
+ if (credits.length > 0)
61
+ parts.push(credits.join(" · "));
62
+ return parts.length > 0 ? parts.join(" · ") : undefined;
63
+ }
64
+ export async function fetchDevinUsage(label, apiKey, fetchImpl) {
65
+ const status = await getDevinUserStatus(apiKey, AbortSignal.timeout(10_000), fetchImpl);
66
+ const { windows, message } = parseDevinUsageSnapshot(status);
67
+ if (windows.length > 0)
68
+ return { label, windows, ...(message === undefined ? {} : { message }) };
69
+ return { label, windows: [], message: message ?? "usage unavailable" };
70
+ }
71
+ //# sourceMappingURL=provider-usage-devin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provider-usage-devin.js","sourceRoot":"","sources":["../../src/core/provider-usage-devin.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Devin CLI subscription usage: maps the account `GetUserStatus` payload onto\n * the status-rail windows. The token is whatever `/login devin` stored or an\n * already-owned `DEVIN_API_KEY`; this module never reads the environment itself.\n */\n\nimport { type DevinUserStatus, getDevinUserStatus } from \"omk-ai\";\nimport { clampPercent, usageText } from \"./provider-usage-text.ts\";\nimport type { FetchLike, SubscriptionUsageSnapshot, SubscriptionUsageWindow } from \"./provider-usage-types.ts\";\n\n/** CLI `BillingStrategy.QUOTA` — undated remaining percents are still a quota window. */\nconst DEVIN_BILLING_QUOTA = 2;\n\n/**\n * Map a decoded `GetUserStatus` payload to rail windows. The server reports\n * remaining percents; the rail renders used percents. Matches the CLI `/usage`\n * surface: a percent window is shown when the plan does not hide it and the\n * wire dated the reset, or when the plan is explicitly quota-billed. Credit-\n * billed plans leave proto percents at 0; those are not exhausted windows.\n * A dated reset without a percent still reads as exhausted.\n */\nexport function parseDevinUsageSnapshot(\n\tstatus: DevinUserStatus,\n): Pick<SubscriptionUsageSnapshot, \"windows\" | \"message\"> {\n\tconst windows: SubscriptionUsageWindow[] = [];\n\tconst quotaWindow = (\n\t\tlabel: string,\n\t\thidden: boolean | undefined,\n\t\tremainingPercent: number | undefined,\n\t\tresetsAt: number | undefined,\n\t): SubscriptionUsageWindow | undefined => {\n\t\tif (hidden === true) return undefined;\n\t\tconst dated = resetsAt !== undefined && resetsAt > 0;\n\t\t// CLI /usage: dated reset, or an explicit quota plan. Credit-billed proto zeros are not windows.\n\t\tif (!dated && status.billingStrategy !== DEVIN_BILLING_QUOTA) return undefined;\n\t\tif (!dated && remainingPercent === undefined) return undefined;\n\t\treturn {\n\t\t\tlabel,\n\t\t\tusedPercent: clampPercent(100 - (remainingPercent ?? 0)),\n\t\t\t...(dated ? { resetsAt } : {}),\n\t\t};\n\t};\n\tconst daily = quotaWindow(\"1D\", status.hideDailyQuota, status.dailyQuotaRemainingPercent, status.dailyQuotaResetAt);\n\tconst weekly = quotaWindow(\n\t\t\"7D\",\n\t\tstatus.hideWeeklyQuota,\n\t\tstatus.weeklyQuotaRemainingPercent,\n\t\tstatus.weeklyQuotaResetAt,\n\t);\n\tif (daily) windows.push(daily);\n\tif (weekly) windows.push(weekly);\n\tconst message = devinUsageMessage(status);\n\treturn { windows, ...(message === undefined ? {} : { message }) };\n}\n\n/** Plan name plus credit balances for plans whose quota windows are not reported. */\nfunction devinUsageMessage(status: DevinUserStatus): string | undefined {\n\tconst parts: string[] = [];\n\tconst plan = usageText(status.planName);\n\tif (plan) parts.push(plan);\n\tconst balances: readonly (readonly [string, number | undefined])[] = [\n\t\t[\"prompt\", status.availablePromptCredits],\n\t\t[\"flow\", status.availableFlowCredits],\n\t\t[\"flex\", status.availableFlexCredits],\n\t];\n\tconst credits: string[] = [];\n\tfor (const [name, amount] of balances) {\n\t\tif (amount !== undefined) credits.push(`${amount} ${name}`);\n\t}\n\tif (credits.length > 0) parts.push(credits.join(\" · \"));\n\treturn parts.length > 0 ? parts.join(\" · \") : undefined;\n}\n\nexport async function fetchDevinUsage(\n\tlabel: string,\n\tapiKey: string,\n\tfetchImpl: FetchLike,\n): Promise<SubscriptionUsageSnapshot> {\n\tconst status = await getDevinUserStatus(apiKey, AbortSignal.timeout(10_000), fetchImpl);\n\tconst { windows, message } = parseDevinUsageSnapshot(status);\n\tif (windows.length > 0) return { label, windows, ...(message === undefined ? {} : { message }) };\n\treturn { label, windows: [], message: message ?? \"usage unavailable\" };\n}\n"]}
@@ -0,0 +1,5 @@
1
+ /** Small value helpers shared by the subscription-usage parsers. */
2
+ /** Strip control/format characters and collapse whitespace in server-supplied text. */
3
+ export declare function usageText(value: string | undefined): string | undefined;
4
+ export declare function clampPercent(value: number): number;
5
+ //# sourceMappingURL=provider-usage-text.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provider-usage-text.d.ts","sourceRoot":"","sources":["../../src/core/provider-usage-text.ts"],"names":[],"mappings":"AAAA,oEAAoE;AAEpE,uFAAuF;AACvF,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAOvE;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAElD","sourcesContent":["/** Small value helpers shared by the subscription-usage parsers. */\n\n/** Strip control/format characters and collapse whitespace in server-supplied text. */\nexport function usageText(value: string | undefined): string | undefined {\n\tif (value === undefined) return undefined;\n\tconst safe = value\n\t\t.replace(/[\\u0000-\\u001f\\u007f-\\u009f\\u200b-\\u200f\\u202a-\\u202e\\u2060-\\u206f]/g, \"\")\n\t\t.replace(/\\s+/g, \" \")\n\t\t.trim();\n\treturn safe ? safe.slice(0, 40) : undefined;\n}\n\nexport function clampPercent(value: number): number {\n\treturn Number(Math.max(0, Math.min(100, value)).toFixed(2));\n}\n"]}
@@ -0,0 +1,15 @@
1
+ /** Small value helpers shared by the subscription-usage parsers. */
2
+ /** Strip control/format characters and collapse whitespace in server-supplied text. */
3
+ export function usageText(value) {
4
+ if (value === undefined)
5
+ return undefined;
6
+ const safe = value
7
+ .replace(/[\u0000-\u001f\u007f-\u009f\u200b-\u200f\u202a-\u202e\u2060-\u206f]/g, "")
8
+ .replace(/\s+/g, " ")
9
+ .trim();
10
+ return safe ? safe.slice(0, 40) : undefined;
11
+ }
12
+ export function clampPercent(value) {
13
+ return Number(Math.max(0, Math.min(100, value)).toFixed(2));
14
+ }
15
+ //# sourceMappingURL=provider-usage-text.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provider-usage-text.js","sourceRoot":"","sources":["../../src/core/provider-usage-text.ts"],"names":[],"mappings":"AAAA,oEAAoE;AAEpE,uFAAuF;AACvF,MAAM,UAAU,SAAS,CAAC,KAAyB,EAAsB;IACxE,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,SAAS,CAAC;IAC1C,MAAM,IAAI,GAAG,KAAK;SAChB,OAAO,CAAC,sEAAsE,EAAE,EAAE,CAAC;SACnF,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC;SACpB,IAAI,EAAE,CAAC;IACT,OAAO,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;AAAA,CAC5C;AAED,MAAM,UAAU,YAAY,CAAC,KAAa,EAAU;IACnD,OAAO,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC;AAAA,CAC5D","sourcesContent":["/** Small value helpers shared by the subscription-usage parsers. */\n\n/** Strip control/format characters and collapse whitespace in server-supplied text. */\nexport function usageText(value: string | undefined): string | undefined {\n\tif (value === undefined) return undefined;\n\tconst safe = value\n\t\t.replace(/[\\u0000-\\u001f\\u007f-\\u009f\\u200b-\\u200f\\u202a-\\u202e\\u2060-\\u206f]/g, \"\")\n\t\t.replace(/\\s+/g, \" \")\n\t\t.trim();\n\treturn safe ? safe.slice(0, 40) : undefined;\n}\n\nexport function clampPercent(value: number): number {\n\treturn Number(Math.max(0, Math.min(100, value)).toFixed(2));\n}\n"]}
@@ -27,7 +27,7 @@ export type PassiveUsageEntry = {
27
27
  readonly primary?: ObservedCodexWindow;
28
28
  readonly secondary?: ObservedCodexWindow;
29
29
  };
30
- export type UsageKind = "codex" | "claude" | "kimi" | "zai" | "grok" | "qwen-token-plan" | "unavailable";
30
+ export type UsageKind = "codex" | "claude" | "kimi" | "zai" | "grok" | "devin" | "commandcode" | "qwen-token-plan" | "unavailable";
31
31
  export type CredentialCandidate = {
32
32
  readonly provider: string;
33
33
  readonly oauthOnly: boolean;
@@ -39,4 +39,5 @@ export type SubscriptionUsageSource = {
39
39
  readonly ttlMs: number;
40
40
  readonly unavailableMessage?: string;
41
41
  };
42
+ export type FetchLike = (input: string | URL | Request, init?: RequestInit) => Promise<Response>;
42
43
  //# sourceMappingURL=provider-usage-types.d.ts.map