@coool/opencode-adaptive 0.0.0-stage → 1.0.1

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 (334) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +99 -2
  3. package/dist/adapters/codex-capacity/index.d.ts +4 -0
  4. package/dist/adapters/codex-capacity/index.js +5 -0
  5. package/dist/adapters/codex-capacity/index.js.map +1 -0
  6. package/dist/adapters/codex-capacity/parse.d.ts +28 -0
  7. package/dist/adapters/codex-capacity/parse.js +146 -0
  8. package/dist/adapters/codex-capacity/parse.js.map +1 -0
  9. package/dist/adapters/codex-capacity/reading.d.ts +9 -0
  10. package/dist/adapters/codex-capacity/reading.js +45 -0
  11. package/dist/adapters/codex-capacity/reading.js.map +1 -0
  12. package/dist/adapters/codex-capacity/sensor.d.ts +23 -0
  13. package/dist/adapters/codex-capacity/sensor.js +30 -0
  14. package/dist/adapters/codex-capacity/sensor.js.map +1 -0
  15. package/dist/adapters/codex-capacity/types.d.ts +31 -0
  16. package/dist/adapters/codex-capacity/types.js +18 -0
  17. package/dist/adapters/codex-capacity/types.js.map +1 -0
  18. package/dist/adapters/opencode/escalation-tool.d.ts +51 -0
  19. package/dist/adapters/opencode/escalation-tool.js +61 -0
  20. package/dist/adapters/opencode/escalation-tool.js.map +1 -0
  21. package/dist/adapters/opencode/events.d.ts +23 -0
  22. package/dist/adapters/opencode/events.js +68 -0
  23. package/dist/adapters/opencode/events.js.map +1 -0
  24. package/dist/adapters/opencode/hooks.d.ts +34 -0
  25. package/dist/adapters/opencode/hooks.js +29 -0
  26. package/dist/adapters/opencode/hooks.js.map +1 -0
  27. package/dist/adapters/opencode/index.d.ts +16 -0
  28. package/dist/adapters/opencode/index.js +9 -0
  29. package/dist/adapters/opencode/index.js.map +1 -0
  30. package/dist/adapters/opencode/inventory.d.ts +30 -0
  31. package/dist/adapters/opencode/inventory.js +53 -0
  32. package/dist/adapters/opencode/inventory.js.map +1 -0
  33. package/dist/adapters/opencode/report.d.ts +91 -0
  34. package/dist/adapters/opencode/report.js +145 -0
  35. package/dist/adapters/opencode/report.js.map +1 -0
  36. package/dist/adapters/opencode/session-ops.d.ts +39 -0
  37. package/dist/adapters/opencode/session-ops.js +32 -0
  38. package/dist/adapters/opencode/session-ops.js.map +1 -0
  39. package/dist/adapters/opencode/storage.d.ts +15 -0
  40. package/dist/adapters/opencode/storage.js +8 -0
  41. package/dist/adapters/opencode/storage.js.map +1 -0
  42. package/dist/adapters/opencode/tier-tool.d.ts +37 -0
  43. package/dist/adapters/opencode/tier-tool.js +72 -0
  44. package/dist/adapters/opencode/tier-tool.js.map +1 -0
  45. package/dist/adapters/opencode/types.d.ts +101 -0
  46. package/dist/adapters/opencode/types.js +2 -0
  47. package/dist/adapters/opencode/types.js.map +1 -0
  48. package/dist/core/compatibility/gate.d.ts +7 -0
  49. package/dist/core/compatibility/gate.js +51 -0
  50. package/dist/core/compatibility/gate.js.map +1 -0
  51. package/dist/core/compatibility/index.d.ts +2 -0
  52. package/dist/core/compatibility/index.js +3 -0
  53. package/dist/core/compatibility/index.js.map +1 -0
  54. package/dist/core/compatibility/types.d.ts +53 -0
  55. package/dist/core/compatibility/types.js +27 -0
  56. package/dist/core/compatibility/types.js.map +1 -0
  57. package/dist/core/scheduler/admit.d.ts +6 -0
  58. package/dist/core/scheduler/admit.js +154 -0
  59. package/dist/core/scheduler/admit.js.map +1 -0
  60. package/dist/core/scheduler/escalate.d.ts +6 -0
  61. package/dist/core/scheduler/escalate.js +45 -0
  62. package/dist/core/scheduler/escalate.js.map +1 -0
  63. package/dist/core/scheduler/index.d.ts +4 -0
  64. package/dist/core/scheduler/index.js +5 -0
  65. package/dist/core/scheduler/index.js.map +1 -0
  66. package/dist/core/scheduler/mode.d.ts +8 -0
  67. package/dist/core/scheduler/mode.js +10 -0
  68. package/dist/core/scheduler/mode.js.map +1 -0
  69. package/dist/core/scheduler/types.d.ts +136 -0
  70. package/dist/core/scheduler/types.js +75 -0
  71. package/dist/core/scheduler/types.js.map +1 -0
  72. package/dist/core/tier-policy/index.d.ts +3 -0
  73. package/dist/core/tier-policy/index.js +4 -0
  74. package/dist/core/tier-policy/index.js.map +1 -0
  75. package/dist/core/tier-policy/resolve.d.ts +18 -0
  76. package/dist/core/tier-policy/resolve.js +52 -0
  77. package/dist/core/tier-policy/resolve.js.map +1 -0
  78. package/dist/core/tier-policy/tier-list.d.ts +10 -0
  79. package/dist/core/tier-policy/tier-list.js +23 -0
  80. package/dist/core/tier-policy/tier-list.js.map +1 -0
  81. package/dist/core/tier-policy/types.d.ts +86 -0
  82. package/dist/core/tier-policy/types.js +49 -0
  83. package/dist/core/tier-policy/types.js.map +1 -0
  84. package/dist/core/work-unit/index.d.ts +3 -0
  85. package/dist/core/work-unit/index.js +4 -0
  86. package/dist/core/work-unit/index.js.map +1 -0
  87. package/dist/core/work-unit/interruption.d.ts +7 -0
  88. package/dist/core/work-unit/interruption.js +12 -0
  89. package/dist/core/work-unit/interruption.js.map +1 -0
  90. package/dist/core/work-unit/tracker.d.ts +53 -0
  91. package/dist/core/work-unit/tracker.js +156 -0
  92. package/dist/core/work-unit/tracker.js.map +1 -0
  93. package/dist/core/work-unit/types.d.ts +151 -0
  94. package/dist/core/work-unit/types.js +23 -0
  95. package/dist/core/work-unit/types.js.map +1 -0
  96. package/dist/execution/continuation/execute.d.ts +18 -0
  97. package/dist/execution/continuation/execute.js +31 -0
  98. package/dist/execution/continuation/execute.js.map +1 -0
  99. package/dist/execution/continuation/index.d.ts +4 -0
  100. package/dist/execution/continuation/index.js +4 -0
  101. package/dist/execution/continuation/index.js.map +1 -0
  102. package/dist/execution/continuation/plan.d.ts +12 -0
  103. package/dist/execution/continuation/plan.js +18 -0
  104. package/dist/execution/continuation/plan.js.map +1 -0
  105. package/dist/execution/continuation/prompts.d.ts +7 -0
  106. package/dist/execution/continuation/prompts.js +8 -0
  107. package/dist/execution/continuation/prompts.js.map +1 -0
  108. package/dist/execution/continuation/types.d.ts +68 -0
  109. package/dist/execution/continuation/types.js +15 -0
  110. package/dist/execution/continuation/types.js.map +1 -0
  111. package/dist/execution/escalation/index.d.ts +1 -0
  112. package/dist/execution/escalation/index.js +2 -0
  113. package/dist/execution/escalation/index.js.map +1 -0
  114. package/dist/execution/escalation/types.d.ts +30 -0
  115. package/dist/execution/escalation/types.js +49 -0
  116. package/dist/execution/escalation/types.js.map +1 -0
  117. package/dist/execution/index.d.ts +4 -0
  118. package/dist/execution/index.js +5 -0
  119. package/dist/execution/index.js.map +1 -0
  120. package/dist/execution/parking/index.d.ts +2 -0
  121. package/dist/execution/parking/index.js +2 -0
  122. package/dist/execution/parking/index.js.map +1 -0
  123. package/dist/execution/parking/plan.d.ts +39 -0
  124. package/dist/execution/parking/plan.js +34 -0
  125. package/dist/execution/parking/plan.js.map +1 -0
  126. package/dist/execution/retry/decide.d.ts +29 -0
  127. package/dist/execution/retry/decide.js +75 -0
  128. package/dist/execution/retry/decide.js.map +1 -0
  129. package/dist/execution/retry/index.d.ts +2 -0
  130. package/dist/execution/retry/index.js +3 -0
  131. package/dist/execution/retry/index.js.map +1 -0
  132. package/dist/execution/retry/types.d.ts +64 -0
  133. package/dist/execution/retry/types.js +9 -0
  134. package/dist/execution/retry/types.js.map +1 -0
  135. package/dist/gui-experimental.cjs +1 -0
  136. package/dist/gui.d.ts +16 -0
  137. package/dist/gui.js +73 -0
  138. package/dist/gui.js.map +1 -0
  139. package/dist/health/capacity/index.d.ts +3 -0
  140. package/dist/health/capacity/index.js +4 -0
  141. package/dist/health/capacity/index.js.map +1 -0
  142. package/dist/health/capacity/merge.d.ts +57 -0
  143. package/dist/health/capacity/merge.js +154 -0
  144. package/dist/health/capacity/merge.js.map +1 -0
  145. package/dist/health/capacity/registry.d.ts +40 -0
  146. package/dist/health/capacity/registry.js +106 -0
  147. package/dist/health/capacity/registry.js.map +1 -0
  148. package/dist/health/capacity/types.d.ts +73 -0
  149. package/dist/health/capacity/types.js +6 -0
  150. package/dist/health/capacity/types.js.map +1 -0
  151. package/dist/health/circuit-breaker/breaker.d.ts +91 -0
  152. package/dist/health/circuit-breaker/breaker.js +150 -0
  153. package/dist/health/circuit-breaker/breaker.js.map +1 -0
  154. package/dist/health/circuit-breaker/index.d.ts +2 -0
  155. package/dist/health/circuit-breaker/index.js +3 -0
  156. package/dist/health/circuit-breaker/index.js.map +1 -0
  157. package/dist/health/circuit-breaker/types.d.ts +52 -0
  158. package/dist/health/circuit-breaker/types.js +13 -0
  159. package/dist/health/circuit-breaker/types.js.map +1 -0
  160. package/dist/health/classifier/classify.d.ts +8 -0
  161. package/dist/health/classifier/classify.js +245 -0
  162. package/dist/health/classifier/classify.js.map +1 -0
  163. package/dist/health/classifier/index.d.ts +2 -0
  164. package/dist/health/classifier/index.js +3 -0
  165. package/dist/health/classifier/index.js.map +1 -0
  166. package/dist/health/classifier/types.d.ts +71 -0
  167. package/dist/health/classifier/types.js +27 -0
  168. package/dist/health/classifier/types.js.map +1 -0
  169. package/dist/health/index.d.ts +4 -0
  170. package/dist/health/index.js +5 -0
  171. package/dist/health/index.js.map +1 -0
  172. package/dist/health/resource-health/index.d.ts +4 -0
  173. package/dist/health/resource-health/index.js +5 -0
  174. package/dist/health/resource-health/index.js.map +1 -0
  175. package/dist/health/resource-health/key.d.ts +18 -0
  176. package/dist/health/resource-health/key.js +54 -0
  177. package/dist/health/resource-health/key.js.map +1 -0
  178. package/dist/health/resource-health/registry.d.ts +46 -0
  179. package/dist/health/resource-health/registry.js +276 -0
  180. package/dist/health/resource-health/registry.js.map +1 -0
  181. package/dist/health/resource-health/transitions.d.ts +9 -0
  182. package/dist/health/resource-health/transitions.js +157 -0
  183. package/dist/health/resource-health/transitions.js.map +1 -0
  184. package/dist/health/resource-health/types.d.ts +122 -0
  185. package/dist/health/resource-health/types.js +44 -0
  186. package/dist/health/resource-health/types.js.map +1 -0
  187. package/dist/index.d.ts +11 -0
  188. package/dist/index.js +105 -0
  189. package/dist/index.js.map +1 -0
  190. package/dist/plugin-id.d.ts +2 -0
  191. package/dist/plugin-id.js +3 -0
  192. package/dist/plugin-id.js.map +1 -0
  193. package/dist/rpc/definition.d.ts +123 -0
  194. package/dist/rpc/definition.js +130 -0
  195. package/dist/rpc/definition.js.map +1 -0
  196. package/dist/rpc/handlers.d.ts +53 -0
  197. package/dist/rpc/handlers.js +125 -0
  198. package/dist/rpc/handlers.js.map +1 -0
  199. package/dist/rpc/status.d.ts +42 -0
  200. package/dist/rpc/status.js +45 -0
  201. package/dist/rpc/status.js.map +1 -0
  202. package/dist/rpc.d.ts +10 -0
  203. package/dist/rpc.js +8 -0
  204. package/dist/rpc.js.map +1 -0
  205. package/dist/state/capacity/index.d.ts +3 -0
  206. package/dist/state/capacity/index.js +4 -0
  207. package/dist/state/capacity/index.js.map +1 -0
  208. package/dist/state/capacity/parse.d.ts +2 -0
  209. package/dist/state/capacity/parse.js +122 -0
  210. package/dist/state/capacity/parse.js.map +1 -0
  211. package/dist/state/capacity/store.d.ts +18 -0
  212. package/dist/state/capacity/store.js +78 -0
  213. package/dist/state/capacity/store.js.map +1 -0
  214. package/dist/state/capacity/types.d.ts +37 -0
  215. package/dist/state/capacity/types.js +11 -0
  216. package/dist/state/capacity/types.js.map +1 -0
  217. package/dist/state/health/index.d.ts +4 -0
  218. package/dist/state/health/index.js +4 -0
  219. package/dist/state/health/index.js.map +1 -0
  220. package/dist/state/health/parse.d.ts +2 -0
  221. package/dist/state/health/parse.js +137 -0
  222. package/dist/state/health/parse.js.map +1 -0
  223. package/dist/state/health/store.d.ts +17 -0
  224. package/dist/state/health/store.js +69 -0
  225. package/dist/state/health/store.js.map +1 -0
  226. package/dist/state/health/types.d.ts +23 -0
  227. package/dist/state/health/types.js +5 -0
  228. package/dist/state/health/types.js.map +1 -0
  229. package/dist/state/parked-work/index.d.ts +4 -0
  230. package/dist/state/parked-work/index.js +4 -0
  231. package/dist/state/parked-work/index.js.map +1 -0
  232. package/dist/state/parked-work/parse.d.ts +6 -0
  233. package/dist/state/parked-work/parse.js +83 -0
  234. package/dist/state/parked-work/parse.js.map +1 -0
  235. package/dist/state/parked-work/store.d.ts +17 -0
  236. package/dist/state/parked-work/store.js +98 -0
  237. package/dist/state/parked-work/store.js.map +1 -0
  238. package/dist/state/parked-work/types.d.ts +41 -0
  239. package/dist/state/parked-work/types.js +7 -0
  240. package/dist/state/parked-work/types.js.map +1 -0
  241. package/dist/state/receipts/bound.d.ts +14 -0
  242. package/dist/state/receipts/bound.js +77 -0
  243. package/dist/state/receipts/bound.js.map +1 -0
  244. package/dist/state/receipts/index.d.ts +6 -0
  245. package/dist/state/receipts/index.js +5 -0
  246. package/dist/state/receipts/index.js.map +1 -0
  247. package/dist/state/receipts/parse.d.ts +6 -0
  248. package/dist/state/receipts/parse.js +226 -0
  249. package/dist/state/receipts/parse.js.map +1 -0
  250. package/dist/state/receipts/store.d.ts +34 -0
  251. package/dist/state/receipts/store.js +148 -0
  252. package/dist/state/receipts/store.js.map +1 -0
  253. package/dist/state/receipts/types.d.ts +61 -0
  254. package/dist/state/receipts/types.js +12 -0
  255. package/dist/state/receipts/types.js.map +1 -0
  256. package/dist/state/session-modes/index.d.ts +4 -0
  257. package/dist/state/session-modes/index.js +4 -0
  258. package/dist/state/session-modes/index.js.map +1 -0
  259. package/dist/state/session-modes/parse.d.ts +8 -0
  260. package/dist/state/session-modes/parse.js +62 -0
  261. package/dist/state/session-modes/parse.js.map +1 -0
  262. package/dist/state/session-modes/store.d.ts +20 -0
  263. package/dist/state/session-modes/store.js +87 -0
  264. package/dist/state/session-modes/store.js.map +1 -0
  265. package/dist/state/session-modes/types.d.ts +41 -0
  266. package/dist/state/session-modes/types.js +7 -0
  267. package/dist/state/session-modes/types.js.map +1 -0
  268. package/dist/state/settings/index.d.ts +4 -0
  269. package/dist/state/settings/index.js +4 -0
  270. package/dist/state/settings/index.js.map +1 -0
  271. package/dist/state/settings/parse.d.ts +33 -0
  272. package/dist/state/settings/parse.js +261 -0
  273. package/dist/state/settings/parse.js.map +1 -0
  274. package/dist/state/settings/store.d.ts +21 -0
  275. package/dist/state/settings/store.js +65 -0
  276. package/dist/state/settings/store.js.map +1 -0
  277. package/dist/state/settings/types.d.ts +62 -0
  278. package/dist/state/settings/types.js +12 -0
  279. package/dist/state/settings/types.js.map +1 -0
  280. package/dist/state/tier-requests/index.d.ts +4 -0
  281. package/dist/state/tier-requests/index.js +4 -0
  282. package/dist/state/tier-requests/index.js.map +1 -0
  283. package/dist/state/tier-requests/parse.d.ts +3 -0
  284. package/dist/state/tier-requests/parse.js +53 -0
  285. package/dist/state/tier-requests/parse.js.map +1 -0
  286. package/dist/state/tier-requests/store.d.ts +27 -0
  287. package/dist/state/tier-requests/store.js +96 -0
  288. package/dist/state/tier-requests/store.js.map +1 -0
  289. package/dist/state/tier-requests/types.d.ts +30 -0
  290. package/dist/state/tier-requests/types.js +7 -0
  291. package/dist/state/tier-requests/types.js.map +1 -0
  292. package/dist/supervisor/index.d.ts +4 -0
  293. package/dist/supervisor/index.js +4 -0
  294. package/dist/supervisor/index.js.map +1 -0
  295. package/dist/supervisor/log.d.ts +10 -0
  296. package/dist/supervisor/log.js +14 -0
  297. package/dist/supervisor/log.js.map +1 -0
  298. package/dist/supervisor/supervisor.d.ts +86 -0
  299. package/dist/supervisor/supervisor.js +1629 -0
  300. package/dist/supervisor/supervisor.js.map +1 -0
  301. package/dist/supervisor/types.d.ts +49 -0
  302. package/dist/supervisor/types.js +3 -0
  303. package/dist/supervisor/types.js.map +1 -0
  304. package/dist/supervisor/view.d.ts +3 -0
  305. package/dist/supervisor/view.js +4 -0
  306. package/dist/supervisor/view.js.map +1 -0
  307. package/dist/tui.d.ts +15 -0
  308. package/dist/tui.js +256 -0
  309. package/dist/tui.js.map +1 -0
  310. package/dist/ui/gui/api.d.ts +17 -0
  311. package/dist/ui/gui/api.js +21 -0
  312. package/dist/ui/gui/api.js.map +1 -0
  313. package/dist/ui/gui/view.d.ts +26 -0
  314. package/dist/ui/gui/view.js +49 -0
  315. package/dist/ui/gui/view.js.map +1 -0
  316. package/dist/ui/tui/controller.d.ts +97 -0
  317. package/dist/ui/tui/controller.js +415 -0
  318. package/dist/ui/tui/controller.js.map +1 -0
  319. package/dist/ui/tui/menu.d.ts +55 -0
  320. package/dist/ui/tui/menu.js +176 -0
  321. package/dist/ui/tui/menu.js.map +1 -0
  322. package/dist/ui/tui/options.d.ts +19 -0
  323. package/dist/ui/tui/options.js +17 -0
  324. package/dist/ui/tui/options.js.map +1 -0
  325. package/dist/ui/tui/receipts.d.ts +21 -0
  326. package/dist/ui/tui/receipts.js +112 -0
  327. package/dist/ui/tui/receipts.js.map +1 -0
  328. package/dist/ui/tui/status.d.ts +22 -0
  329. package/dist/ui/tui/status.js +92 -0
  330. package/dist/ui/tui/status.js.map +1 -0
  331. package/dist/ui/tui/tiers.d.ts +51 -0
  332. package/dist/ui/tui/tiers.js +108 -0
  333. package/dist/ui/tui/tiers.js.map +1 -0
  334. package/package.json +85 -4
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 @coool
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,100 @@
1
- # Temporary Holding Version
1
+ # @coool/opencode-adaptive
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Automatic model fallback and routing for [OpenCode](https://opencode.ai).
4
+
5
+ You list your models in order of preference. Adaptive runs each task on the first one that works. When a model hits a quota, rate limit or outage, work moves to the next one. When the preferred model is back, it is used again.
6
+
7
+ It is set up entirely from the `/adaptive` menu in OpenCode. There are no config files to edit and no model IDs to type.
8
+
9
+ ## What you can do with it
10
+
11
+ **Use subscriptions first, pay-as-you-go second.** Put your subscription models at the top of the list and an API provider such as OpenRouter at the bottom. You get the most out of what you already pay for. The API only takes over when the subscriptions are out of quota, and the subscriptions are used again as soon as they reset.
12
+
13
+ **Use cheap models for small work.** Keep separate lists for quick, standard and advanced work. Typo fixes don't need your strongest model.
14
+
15
+ **Leave long runs unattended.** If a model fails mid-task, work continues on the next ready model in the tier, and the preferred model is used again at the next task — finished tool calls are not repeated, even across an OpenCode restart. A `/goal` run is a series of tasks, so it carries on by itself.
16
+
17
+ ## Features
18
+
19
+ - **Menu setup.** Models, order and mode are set in `/adaptive` with model search. No config files.
20
+ - **Tiers.** Quick, standard and advanced work each get their own list, and a failure stays inside the tier the work needs. The agent can ask for a tier with the `adaptive_tier` tool. Routing follows fixed rules, not a prompt the model may or may not obey.
21
+ - **Quota awareness.** It reads usage from provider response headers. When a subscription is nearly used up (about 10% left), new tasks go to the next ready model in the tier, and it is used again after the reset time when the provider reports one.
22
+ - **Recovery that survives a restart.** If work is interrupted and cannot continue right away because no model in its tier is ready, it is saved and resumes when one returns — even after an OpenCode restart, without repeating finished tool calls.
23
+ - **Dry run.** In `observe` mode it records what it would have done, with the reason, before it changes anything. A session can have its own mode.
24
+ - **Escalation on evidence only.** An outage never sends work to a more expensive tier. Moving up needs a context overflow, a policy rejection, or an explicit request from the agent.
25
+
26
+ ## Install
27
+
28
+ ```sh
29
+ opencode plugin add @coool/opencode-adaptive
30
+ ```
31
+
32
+ Needs OpenCode 2.2.1 or newer.
33
+
34
+ ## Set up
35
+
36
+ 1. Start OpenCode and run `/adaptive`.
37
+ 2. Open **Models**, pick a tier, and choose **Add model…**. Search for a model and select it. Repeat, best first. If the tier a task needs is empty, OpenCode's own model runs it.
38
+ 3. Open **Mode** and choose `active`.
39
+
40
+ New installs start in `observe` mode: Adaptive records what it would do but changes nothing. Look at **Receipts** to check its decisions, then switch to `active`.
41
+
42
+ | Mode | What happens |
43
+ | --------- | ---------------------------------------------------------------------- |
44
+ | `off` | Nothing. |
45
+ | `observe` | Decisions are recorded, not applied. This is the default. |
46
+ | `active` | Decisions are applied. A task changes model only when its model fails. |
47
+
48
+ Any session can override the global mode: open `/adaptive` inside the session and choose **This session…**.
49
+
50
+ To reorder or remove a model, select it in the tier list. **Status** shows each model's state, including subscription usage (`used 72%, resets 18:42`) when the provider reports it.
51
+
52
+ ## Tiers
53
+
54
+ A tier is an ordered list of models. The order is your policy, and Adaptive never changes it.
55
+
56
+ - **Quick** for small, mechanical work.
57
+ - **Standard** for everything else. This is the default.
58
+ - **Advanced** for hard work.
59
+
60
+ A model can be in more than one tier.
61
+
62
+ The tier for a task is the first of these that applies:
63
+
64
+ 1. A pinned model (`/adaptive` → **Pin**).
65
+ 2. A tier request for the session: the agent can declare one with the `adaptive_tier` tool, or you set it inside a session with `/adaptive` → **Tier**.
66
+ 3. A per-agent tier from the `agentTiers` setting (RPC only).
67
+ 4. `standard`.
68
+
69
+ Background requests such as titles and summaries are observed but not routed separately in v1 — they keep OpenCode's own model.
70
+
71
+ No language model or keyword matching is involved. A model is skipped if it is unknown or can't fit the context, and the receipt says why. Adaptive only uses models you added.
72
+
73
+ ## When a model fails
74
+
75
+ - **Rate limit, quota, timeout, outage:** if the error is clear-cut and another model in the same tier is ready, the task switches to it and carries on from the current session. Your prompt is not resent and finished tool calls are not repeated. If the error is unclear or no other model is ready, OpenCode's normal retry continues.
76
+ - **A model fails repeatedly:** it is paused for a cooldown, then tried once. If that works, it is back.
77
+ - **Every model in the tier is blocked as a task starts:** OpenCode's own model runs it, and the decision is recorded as a receipt with the reason. A task that fails to recover is saved and resumes when a model returns.
78
+ - **You cancel:** nothing is retried or switched.
79
+ - **Auth error or unknown model:** the model is marked unavailable and skipped.
80
+
81
+ An outage never moves work to a stronger tier. Work only moves up on real evidence: a context overflow, a content-policy rejection, or the agent calling `adaptive_escalate` with a reason and evidence. This happens at most twice per task.
82
+
83
+ ## Limits
84
+
85
+ - No borrowing between tiers. If a tier is exhausted, the task runs on OpenCode's own model rather than another tier's models. To let a model serve two tiers, add it to both.
86
+ - A task that moved up a tier stays there. The next task is routed again from scratch.
87
+ - Usage figures come from response headers and are best effort. Providers that use WebSockets don't expose them.
88
+ - The browser/desktop settings page is experimental. No released OpenCode loads it yet, so use `/adaptive`.
89
+
90
+ ## Troubleshooting
91
+
92
+ - **Nothing changes.** You are in `observe` mode. Switch to `active` in `/adaptive` → **Mode**.
93
+ - **A model is never picked.** Its provider must be connected in OpenCode.
94
+ - **Receipts show `park` on a new task.** Every model in its tier was blocked when the task started, so it ran on OpenCode's own model. The receipt says why; check `/adaptive` → **Status** for cooldowns.
95
+ - **Why did it pick that model?** `/adaptive` → **Receipts**.
96
+ - **"Plugin failed" toast.** The plugin didn't load. Update it with `opencode plugin update`, or remove and add it again.
97
+
98
+ ## Privacy
99
+
100
+ Nothing leaves your machine. No telemetry, no network calls of its own, no secrets in settings, receipts or logs. Receipts keep the newest 200 entries and are never added to prompts.
@@ -0,0 +1,4 @@
1
+ export * from "./types.js";
2
+ export * from "./parse.js";
3
+ export * from "./reading.js";
4
+ export * from "./sensor.js";
@@ -0,0 +1,5 @@
1
+ export * from "./types.js";
2
+ export * from "./parse.js";
3
+ export * from "./reading.js";
4
+ export * from "./sensor.js";
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/adapters/codex-capacity/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAA;AAC1B,cAAc,YAAY,CAAA;AAC1B,cAAc,cAAc,CAAA;AAC5B,cAAc,aAAa,CAAA","sourcesContent":["export * from \"./types.js\"\nexport * from \"./parse.js\"\nexport * from \"./reading.js\"\nexport * from \"./sensor.js\"\n"]}
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Untrusted-header validation. Header values arrive from provider responses
3
+ * (or relays); every field is parsed defensively and a window is admitted only
4
+ * when its usage percentage is trustworthy. A usage above 100% voids the
5
+ * snapshot rather than leaving the other window to speak for it. Reset fields that fail validation
6
+ * are dropped field-wise — they cannot invalidate a valid percentage.
7
+ */
8
+ import type { CodexUsage } from "./types.js";
9
+ /**
10
+ * LiteLLM relays Codex headers under a provider prefix, e.g.
11
+ * `llm_provider-x-codex-primary-used-percent`. Matching on the substring keeps
12
+ * the parser independent of which relay prefix is used.
13
+ */
14
+ export declare const CODEX_HEADER_MARK = "x-codex-";
15
+ /**
16
+ * Allowlist predicate for the `http.response` capture option: matches plain
17
+ * and LiteLLM-prefixed Codex telemetry header names (case already lowered by
18
+ * the caller). Only quota telemetry passes — no other response header is ever
19
+ * captured.
20
+ */
21
+ export declare function isCodexHeaderName(lowercaseName: string): boolean;
22
+ /**
23
+ * Parse captured response headers into a validated usage snapshot. Header
24
+ * lookup is case-insensitive and accepts LiteLLM-prefixed names; fields
25
+ * accumulate per window in any order. Returns `null` when no window carries a
26
+ * trustworthy usage percentage.
27
+ */
28
+ export declare function parseCodexUsage(captured: Readonly<Record<string, string>>): CodexUsage | null;
@@ -0,0 +1,146 @@
1
+ import { CODEX_WINDOW_KINDS } from "./types.js";
2
+ /**
3
+ * LiteLLM relays Codex headers under a provider prefix, e.g.
4
+ * `llm_provider-x-codex-primary-used-percent`. Matching on the substring keeps
5
+ * the parser independent of which relay prefix is used.
6
+ */
7
+ export const CODEX_HEADER_MARK = "x-codex-";
8
+ /**
9
+ * Allowlist predicate for the `http.response` capture option: matches plain
10
+ * and LiteLLM-prefixed Codex telemetry header names (case already lowered by
11
+ * the caller). Only quota telemetry passes — no other response header is ever
12
+ * captured.
13
+ */
14
+ export function isCodexHeaderName(lowercaseName) {
15
+ return lowercaseName.includes(CODEX_HEADER_MARK);
16
+ }
17
+ function finiteAtLeast(value, min) {
18
+ // An empty (or whitespace-only) value must not fall through Number("") === 0.
19
+ const trimmed = value.trim();
20
+ if (trimmed === "") {
21
+ return undefined;
22
+ }
23
+ const parsed = Number(trimmed);
24
+ if (!Number.isFinite(parsed) || parsed < min) {
25
+ return undefined;
26
+ }
27
+ return parsed;
28
+ }
29
+ function emptyAccumulator() {
30
+ return {
31
+ usedPercent: undefined,
32
+ overshoot: undefined,
33
+ resetAfterSeconds: undefined,
34
+ resetAtSeconds: undefined,
35
+ };
36
+ }
37
+ function takenValue(accumulator, suffix) {
38
+ switch (suffix) {
39
+ case "used-percent":
40
+ return accumulator.usedPercent;
41
+ case "reset-after-seconds":
42
+ return accumulator.resetAfterSeconds;
43
+ case "reset-at":
44
+ return accumulator.resetAtSeconds;
45
+ default:
46
+ return undefined;
47
+ }
48
+ }
49
+ function applyField(accumulator, suffix, value) {
50
+ // First valid value per field wins; invalid or duplicate values are ignored.
51
+ if (takenValue(accumulator, suffix) !== undefined) {
52
+ return;
53
+ }
54
+ switch (suffix) {
55
+ case "used-percent": {
56
+ // The percentage is the window's existence test: out of range or
57
+ // unparseable values never admit a window.
58
+ const usedPercent = finiteAtLeast(value, 0);
59
+ if (usedPercent === undefined) {
60
+ return;
61
+ }
62
+ if (usedPercent > 100) {
63
+ accumulator.overshoot = true;
64
+ return;
65
+ }
66
+ accumulator.usedPercent = usedPercent;
67
+ return;
68
+ }
69
+ case "reset-after-seconds": {
70
+ const resetAfterSeconds = finiteAtLeast(value, 0);
71
+ if (resetAfterSeconds !== undefined) {
72
+ accumulator.resetAfterSeconds = resetAfterSeconds;
73
+ }
74
+ return;
75
+ }
76
+ case "reset-at": {
77
+ // Documented wire quirk: reset-at can be an empty string while
78
+ // reset-after-seconds carries the real value. Empty and unparseable
79
+ // values are dropped, never guessed at.
80
+ const resetAtSeconds = finiteAtLeast(value, 0);
81
+ if (resetAtSeconds !== undefined) {
82
+ accumulator.resetAtSeconds = resetAtSeconds;
83
+ }
84
+ return;
85
+ }
86
+ default:
87
+ // window-minutes and unknown suffixes carry no reading information.
88
+ return;
89
+ }
90
+ }
91
+ function windowOf(accumulator) {
92
+ const usedPercent = accumulator.usedPercent;
93
+ if (usedPercent === undefined) {
94
+ return undefined;
95
+ }
96
+ const { resetAfterSeconds, resetAtSeconds } = accumulator;
97
+ return {
98
+ usedPercent,
99
+ ...(resetAfterSeconds !== undefined ? { resetAfterSeconds } : {}),
100
+ ...(resetAtSeconds !== undefined ? { resetAtSeconds } : {}),
101
+ };
102
+ }
103
+ /**
104
+ * Parse captured response headers into a validated usage snapshot. Header
105
+ * lookup is case-insensitive and accepts LiteLLM-prefixed names; fields
106
+ * accumulate per window in any order. Returns `null` when no window carries a
107
+ * trustworthy usage percentage.
108
+ */
109
+ export function parseCodexUsage(captured) {
110
+ const accumulators = {
111
+ primary: undefined,
112
+ secondary: undefined,
113
+ };
114
+ for (const [rawName, value] of Object.entries(captured)) {
115
+ const mark = rawName.toLowerCase().indexOf(CODEX_HEADER_MARK);
116
+ if (mark < 0) {
117
+ continue;
118
+ }
119
+ const rest = rawName.slice(mark + CODEX_HEADER_MARK.length).toLowerCase();
120
+ const separator = rest.indexOf("-");
121
+ const kind = rest.slice(0, separator);
122
+ if (!CODEX_WINDOW_KINDS.includes(kind)) {
123
+ continue;
124
+ }
125
+ const key = kind;
126
+ const accumulator = accumulators[key] ?? emptyAccumulator();
127
+ applyField(accumulator, rest.slice(separator + 1), value);
128
+ accumulators[key] = accumulator;
129
+ }
130
+ // An overshooting window is exhausted, yet its percentage is not
131
+ // trustworthy: dropping just that window would let the other one present
132
+ // the subscription as usable, so the whole snapshot is void.
133
+ if (accumulators.primary?.overshoot === true || accumulators.secondary?.overshoot === true) {
134
+ return null;
135
+ }
136
+ const primary = windowOf(accumulators.primary ?? emptyAccumulator());
137
+ const secondary = windowOf(accumulators.secondary ?? emptyAccumulator());
138
+ if (primary === undefined && secondary === undefined) {
139
+ return null;
140
+ }
141
+ return {
142
+ ...(primary !== undefined ? { primary } : {}),
143
+ ...(secondary !== undefined ? { secondary } : {}),
144
+ };
145
+ }
146
+ //# sourceMappingURL=parse.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parse.js","sourceRoot":"","sources":["../../../src/adapters/codex-capacity/parse.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Untrusted-header validation. Header values arrive from provider responses\n * (or relays); every field is parsed defensively and a window is admitted only\n * when its usage percentage is trustworthy. A usage above 100% voids the\n * snapshot rather than leaving the other window to speak for it. Reset fields that fail validation\n * are dropped field-wise — they cannot invalidate a valid percentage.\n */\nimport type { CodexUsage, CodexWindow, CodexWindowKind } from \"./types.js\"\nimport { CODEX_WINDOW_KINDS } from \"./types.js\"\n\n/**\n * LiteLLM relays Codex headers under a provider prefix, e.g.\n * `llm_provider-x-codex-primary-used-percent`. Matching on the substring keeps\n * the parser independent of which relay prefix is used.\n */\nexport const CODEX_HEADER_MARK = \"x-codex-\"\n\n/**\n * Allowlist predicate for the `http.response` capture option: matches plain\n * and LiteLLM-prefixed Codex telemetry header names (case already lowered by\n * the caller). Only quota telemetry passes — no other response header is ever\n * captured.\n */\nexport function isCodexHeaderName(lowercaseName: string): boolean {\n return lowercaseName.includes(CODEX_HEADER_MARK)\n}\n\ninterface WindowAccumulator {\n usedPercent?: number | undefined\n /** A usage above 100% was reported: the window is exhausted, not absent. */\n overshoot?: boolean | undefined\n resetAfterSeconds?: number | undefined\n resetAtSeconds?: number | undefined\n}\n\nfunction finiteAtLeast(value: string, min: number): number | undefined {\n // An empty (or whitespace-only) value must not fall through Number(\"\") === 0.\n const trimmed = value.trim()\n if (trimmed === \"\") {\n return undefined\n }\n const parsed = Number(trimmed)\n if (!Number.isFinite(parsed) || parsed < min) {\n return undefined\n }\n return parsed\n}\n\nfunction emptyAccumulator(): WindowAccumulator {\n return {\n usedPercent: undefined,\n overshoot: undefined,\n resetAfterSeconds: undefined,\n resetAtSeconds: undefined,\n }\n}\n\nfunction takenValue(accumulator: WindowAccumulator, suffix: string): number | undefined {\n switch (suffix) {\n case \"used-percent\":\n return accumulator.usedPercent\n case \"reset-after-seconds\":\n return accumulator.resetAfterSeconds\n case \"reset-at\":\n return accumulator.resetAtSeconds\n default:\n return undefined\n }\n}\n\nfunction applyField(accumulator: WindowAccumulator, suffix: string, value: string): void {\n // First valid value per field wins; invalid or duplicate values are ignored.\n if (takenValue(accumulator, suffix) !== undefined) {\n return\n }\n switch (suffix) {\n case \"used-percent\": {\n // The percentage is the window's existence test: out of range or\n // unparseable values never admit a window.\n const usedPercent = finiteAtLeast(value, 0)\n if (usedPercent === undefined) {\n return\n }\n if (usedPercent > 100) {\n accumulator.overshoot = true\n return\n }\n accumulator.usedPercent = usedPercent\n return\n }\n case \"reset-after-seconds\": {\n const resetAfterSeconds = finiteAtLeast(value, 0)\n if (resetAfterSeconds !== undefined) {\n accumulator.resetAfterSeconds = resetAfterSeconds\n }\n return\n }\n case \"reset-at\": {\n // Documented wire quirk: reset-at can be an empty string while\n // reset-after-seconds carries the real value. Empty and unparseable\n // values are dropped, never guessed at.\n const resetAtSeconds = finiteAtLeast(value, 0)\n if (resetAtSeconds !== undefined) {\n accumulator.resetAtSeconds = resetAtSeconds\n }\n return\n }\n default:\n // window-minutes and unknown suffixes carry no reading information.\n return\n }\n}\n\nfunction windowOf(accumulator: WindowAccumulator): CodexWindow | undefined {\n const usedPercent = accumulator.usedPercent\n if (usedPercent === undefined) {\n return undefined\n }\n const { resetAfterSeconds, resetAtSeconds } = accumulator\n return {\n usedPercent,\n ...(resetAfterSeconds !== undefined ? { resetAfterSeconds } : {}),\n ...(resetAtSeconds !== undefined ? { resetAtSeconds } : {}),\n }\n}\n\n/**\n * Parse captured response headers into a validated usage snapshot. Header\n * lookup is case-insensitive and accepts LiteLLM-prefixed names; fields\n * accumulate per window in any order. Returns `null` when no window carries a\n * trustworthy usage percentage.\n */\nexport function parseCodexUsage(captured: Readonly<Record<string, string>>): CodexUsage | null {\n const accumulators: Record<CodexWindowKind, WindowAccumulator | undefined> = {\n primary: undefined,\n secondary: undefined,\n }\n for (const [rawName, value] of Object.entries(captured)) {\n const mark = rawName.toLowerCase().indexOf(CODEX_HEADER_MARK)\n if (mark < 0) {\n continue\n }\n const rest = rawName.slice(mark + CODEX_HEADER_MARK.length).toLowerCase()\n const separator = rest.indexOf(\"-\")\n const kind = rest.slice(0, separator)\n if (!(CODEX_WINDOW_KINDS as readonly string[]).includes(kind)) {\n continue\n }\n const key = kind as CodexWindowKind\n const accumulator = accumulators[key] ?? emptyAccumulator()\n applyField(accumulator, rest.slice(separator + 1), value)\n accumulators[key] = accumulator\n }\n // An overshooting window is exhausted, yet its percentage is not\n // trustworthy: dropping just that window would let the other one present\n // the subscription as usable, so the whole snapshot is void.\n if (accumulators.primary?.overshoot === true || accumulators.secondary?.overshoot === true) {\n return null\n }\n const primary = windowOf(accumulators.primary ?? emptyAccumulator())\n const secondary = windowOf(accumulators.secondary ?? emptyAccumulator())\n if (primary === undefined && secondary === undefined) {\n return null\n }\n return {\n ...(primary !== undefined ? { primary } : {}),\n ...(secondary !== undefined ? { secondary } : {}),\n }\n}\n"]}
@@ -0,0 +1,9 @@
1
+ import type { CapacityReading } from "../../health/capacity/types.js";
2
+ import type { CodexUsage, CodexWindow } from "./types.js";
3
+ export declare const CODEX_EVIDENCE = "x-codex headers";
4
+ /**
5
+ * The window whose exhaustion binds first: higher usage wins, ties → primary.
6
+ * Returns `undefined` only for a snapshot with no window at all.
7
+ */
8
+ export declare function bindingWindow(usage: CodexUsage): CodexWindow | undefined;
9
+ export declare function readingFromUsage(usage: CodexUsage, observedAt: number): CapacityReading;
@@ -0,0 +1,45 @@
1
+ export const CODEX_EVIDENCE = "x-codex headers";
2
+ function hasReset(window) {
3
+ return window.resetAfterSeconds !== undefined || window.resetAtSeconds !== undefined;
4
+ }
5
+ /**
6
+ * The window whose exhaustion binds first: higher usage wins, ties → primary.
7
+ * Returns `undefined` only for a snapshot with no window at all.
8
+ */
9
+ export function bindingWindow(usage) {
10
+ const primary = usage.primary;
11
+ const secondary = usage.secondary;
12
+ if (primary === undefined) {
13
+ return secondary;
14
+ }
15
+ if (secondary === undefined) {
16
+ return primary;
17
+ }
18
+ return secondary.usedPercent > primary.usedPercent ? secondary : primary;
19
+ }
20
+ export function readingFromUsage(usage, observedAt) {
21
+ const window = bindingWindow(usage);
22
+ if (window === undefined) {
23
+ // No measured field — the capacity merge layer rejects this as noise.
24
+ return { confidence: "low", observedAt, evidence: CODEX_EVIDENCE };
25
+ }
26
+ const usedRatio = window.usedPercent / 100;
27
+ const remainingRatio = 1 - usedRatio;
28
+ let resetAt;
29
+ if (window.resetAfterSeconds !== undefined) {
30
+ resetAt = observedAt + window.resetAfterSeconds * 1000;
31
+ }
32
+ else if (window.resetAtSeconds !== undefined) {
33
+ resetAt = window.resetAtSeconds * 1000;
34
+ }
35
+ const confidence = hasReset(window) ? "high" : "medium";
36
+ return {
37
+ usedRatio,
38
+ remainingRatio,
39
+ ...(resetAt !== undefined ? { resetAt } : {}),
40
+ confidence,
41
+ observedAt,
42
+ evidence: CODEX_EVIDENCE,
43
+ };
44
+ }
45
+ //# sourceMappingURL=reading.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reading.js","sourceRoot":"","sources":["../../../src/adapters/codex-capacity/reading.ts"],"names":[],"mappings":"AAiBA,MAAM,CAAC,MAAM,cAAc,GAAG,iBAAiB,CAAA;AAE/C,SAAS,QAAQ,CAAC,MAAmB;IACnC,OAAO,MAAM,CAAC,iBAAiB,KAAK,SAAS,IAAI,MAAM,CAAC,cAAc,KAAK,SAAS,CAAA;AACtF,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,aAAa,CAAC,KAAiB;IAC7C,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,CAAA;IAC7B,MAAM,SAAS,GAAG,KAAK,CAAC,SAAS,CAAA;IACjC,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;QAC1B,OAAO,SAAS,CAAA;IAClB,CAAC;IACD,IAAI,SAAS,KAAK,SAAS,EAAE,CAAC;QAC5B,OAAO,OAAO,CAAA;IAChB,CAAC;IACD,OAAO,SAAS,CAAC,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO,CAAA;AAC1E,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,KAAiB,EAAE,UAAkB;IACpE,MAAM,MAAM,GAAG,aAAa,CAAC,KAAK,CAAC,CAAA;IACnC,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;QACzB,sEAAsE;QACtE,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,cAAc,EAAE,CAAA;IACpE,CAAC;IACD,MAAM,SAAS,GAAG,MAAM,CAAC,WAAW,GAAG,GAAG,CAAA;IAC1C,MAAM,cAAc,GAAG,CAAC,GAAG,SAAS,CAAA;IACpC,IAAI,OAA2B,CAAA;IAC/B,IAAI,MAAM,CAAC,iBAAiB,KAAK,SAAS,EAAE,CAAC;QAC3C,OAAO,GAAG,UAAU,GAAG,MAAM,CAAC,iBAAiB,GAAG,IAAI,CAAA;IACxD,CAAC;SAAM,IAAI,MAAM,CAAC,cAAc,KAAK,SAAS,EAAE,CAAC;QAC/C,OAAO,GAAG,MAAM,CAAC,cAAc,GAAG,IAAI,CAAA;IACxC,CAAC;IACD,MAAM,UAAU,GAAe,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAA;IACnE,OAAO;QACL,SAAS;QACT,cAAc;QACd,GAAG,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC7C,UAAU;QACV,UAAU;QACV,QAAQ,EAAE,cAAc;KACzB,CAAA;AACH,CAAC","sourcesContent":["/**\n * Codex usage → `CapacityReading` conversion. The binding constraint is the\n * most-consumed window (ties read the primary window, deterministic). Ratios\n * are exact percent/100 fractions; the reset timestamp is anchored to when the\n * headers were observed, preferring the relative `reset-after-seconds` over\n * the absolute `reset-at` epoch-seconds field.\n *\n * Confidence grades telemetry completeness: a binding window with BOTH a\n * usage percentage and a validated reset field is `high` (self-consistent,\n * provider-generated, actionable for draining); a percentage alone is\n * `medium`. An usage snapshot with no window at all yields a no-measurement\n * reading that the capacity merge layer discards as pure noise.\n */\nimport type { Confidence } from \"../../health/capacity/types.js\"\nimport type { CapacityReading } from \"../../health/capacity/types.js\"\nimport type { CodexUsage, CodexWindow } from \"./types.js\"\n\nexport const CODEX_EVIDENCE = \"x-codex headers\"\n\nfunction hasReset(window: CodexWindow): boolean {\n return window.resetAfterSeconds !== undefined || window.resetAtSeconds !== undefined\n}\n\n/**\n * The window whose exhaustion binds first: higher usage wins, ties → primary.\n * Returns `undefined` only for a snapshot with no window at all.\n */\nexport function bindingWindow(usage: CodexUsage): CodexWindow | undefined {\n const primary = usage.primary\n const secondary = usage.secondary\n if (primary === undefined) {\n return secondary\n }\n if (secondary === undefined) {\n return primary\n }\n return secondary.usedPercent > primary.usedPercent ? secondary : primary\n}\n\nexport function readingFromUsage(usage: CodexUsage, observedAt: number): CapacityReading {\n const window = bindingWindow(usage)\n if (window === undefined) {\n // No measured field — the capacity merge layer rejects this as noise.\n return { confidence: \"low\", observedAt, evidence: CODEX_EVIDENCE }\n }\n const usedRatio = window.usedPercent / 100\n const remainingRatio = 1 - usedRatio\n let resetAt: number | undefined\n if (window.resetAfterSeconds !== undefined) {\n resetAt = observedAt + window.resetAfterSeconds * 1000\n } else if (window.resetAtSeconds !== undefined) {\n resetAt = window.resetAtSeconds * 1000\n }\n const confidence: Confidence = hasReset(window) ? \"high\" : \"medium\"\n return {\n usedRatio,\n remainingRatio,\n ...(resetAt !== undefined ? { resetAt } : {}),\n confidence,\n observedAt,\n evidence: CODEX_EVIDENCE,\n }\n}\n"]}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * The passive-header `CapacitySensor` (ADP-020). Holds the latest validated
3
+ * header observation; `read()` converts it to a reading. No polling, no
4
+ * endpoint calls, no credential handling — WebSocket-backed providers bypass
5
+ * the HTTP hook entirely (verified limitation), so this sensor reports
6
+ * `null` (no estimate) whenever no trustworthy headers have been observed.
7
+ * An unparseable observation CLEARS the stored usage: present-but-invalid
8
+ * telemetry is distrust, and a stale quota estimate must never linger.
9
+ */
10
+ import type { ResourceKey } from "../../health/resource-health/types.js";
11
+ import type { CapacityReading, CapacitySensor } from "../../health/capacity/types.js";
12
+ export declare class CodexCapacitySensor implements CapacitySensor {
13
+ #private;
14
+ readonly id = "codex-headers";
15
+ readonly resource: ResourceKey;
16
+ constructor(resource: ResourceKey);
17
+ /**
18
+ * Record one captured-header observation. Unparseable telemetry clears the
19
+ * previous estimate instead of keeping it (present-but-invalid = distrust).
20
+ */
21
+ observe(captured: Readonly<Record<string, string>>, observedAt: number): void;
22
+ read(): Promise<CapacityReading | null>;
23
+ }
@@ -0,0 +1,30 @@
1
+ import { parseCodexUsage } from "./parse.js";
2
+ import { readingFromUsage } from "./reading.js";
3
+ import { CODEX_SENSOR_ID } from "./types.js";
4
+ export class CodexCapacitySensor {
5
+ id = CODEX_SENSOR_ID;
6
+ resource;
7
+ #usage = null;
8
+ #observedAt = 0;
9
+ constructor(resource) {
10
+ this.resource = resource;
11
+ }
12
+ /**
13
+ * Record one captured-header observation. Unparseable telemetry clears the
14
+ * previous estimate instead of keeping it (present-but-invalid = distrust).
15
+ */
16
+ observe(captured, observedAt) {
17
+ this.#usage = parseCodexUsage(captured);
18
+ if (this.#usage !== null) {
19
+ this.#observedAt = observedAt;
20
+ }
21
+ }
22
+ read() {
23
+ const usage = this.#usage;
24
+ if (usage === null) {
25
+ return Promise.resolve(null);
26
+ }
27
+ return Promise.resolve(readingFromUsage(usage, this.#observedAt));
28
+ }
29
+ }
30
+ //# sourceMappingURL=sensor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sensor.js","sourceRoot":"","sources":["../../../src/adapters/codex-capacity/sensor.ts"],"names":[],"mappings":"AAWA,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAC5C,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAA;AAE/C,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAE5C,MAAM,OAAO,mBAAmB;IACrB,EAAE,GAAG,eAAe,CAAA;IACpB,QAAQ,CAAa;IAC9B,MAAM,GAAsB,IAAI,CAAA;IAChC,WAAW,GAAG,CAAC,CAAA;IAEf,YAAY,QAAqB;QAC/B,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAA;IAC1B,CAAC;IAED;;;OAGG;IACH,OAAO,CAAC,QAA0C,EAAE,UAAkB;QACpE,IAAI,CAAC,MAAM,GAAG,eAAe,CAAC,QAAQ,CAAC,CAAA;QACvC,IAAI,IAAI,CAAC,MAAM,KAAK,IAAI,EAAE,CAAC;YACzB,IAAI,CAAC,WAAW,GAAG,UAAU,CAAA;QAC/B,CAAC;IACH,CAAC;IAED,IAAI;QACF,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAA;QACzB,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;YACnB,OAAO,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,CAAA;QAC9B,CAAC;QACD,OAAO,OAAO,CAAC,OAAO,CAAC,gBAAgB,CAAC,KAAK,EAAE,IAAI,CAAC,WAAW,CAAC,CAAC,CAAA;IACnE,CAAC;CACF","sourcesContent":["/**\n * The passive-header `CapacitySensor` (ADP-020). Holds the latest validated\n * header observation; `read()` converts it to a reading. No polling, no\n * endpoint calls, no credential handling — WebSocket-backed providers bypass\n * the HTTP hook entirely (verified limitation), so this sensor reports\n * `null` (no estimate) whenever no trustworthy headers have been observed.\n * An unparseable observation CLEARS the stored usage: present-but-invalid\n * telemetry is distrust, and a stale quota estimate must never linger.\n */\nimport type { ResourceKey } from \"../../health/resource-health/types.js\"\nimport type { CapacityReading, CapacitySensor } from \"../../health/capacity/types.js\"\nimport { parseCodexUsage } from \"./parse.js\"\nimport { readingFromUsage } from \"./reading.js\"\nimport type { CodexUsage } from \"./types.js\"\nimport { CODEX_SENSOR_ID } from \"./types.js\"\n\nexport class CodexCapacitySensor implements CapacitySensor {\n readonly id = CODEX_SENSOR_ID\n readonly resource: ResourceKey\n #usage: CodexUsage | null = null\n #observedAt = 0\n\n constructor(resource: ResourceKey) {\n this.resource = resource\n }\n\n /**\n * Record one captured-header observation. Unparseable telemetry clears the\n * previous estimate instead of keeping it (present-but-invalid = distrust).\n */\n observe(captured: Readonly<Record<string, string>>, observedAt: number): void {\n this.#usage = parseCodexUsage(captured)\n if (this.#usage !== null) {\n this.#observedAt = observedAt\n }\n }\n\n read(): Promise<CapacityReading | null> {\n const usage = this.#usage\n if (usage === null) {\n return Promise.resolve(null)\n }\n return Promise.resolve(readingFromUsage(usage, this.#observedAt))\n }\n}\n"]}
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Passive Codex/ChatGPT capacity telemetry (ADP-020).
3
+ *
4
+ * The only consumed evidence is the `x-codex-*` response-header family
5
+ * (including LiteLLM's `llm_provider-x-codex-*` relay form), captured from the
6
+ * `http.response` hook by the wiring-supplied allowlist. Verified wire facts:
7
+ * `x-codex-primary-used-percent` (5h window), `x-codex-secondary-used-percent`
8
+ * (weekly window), `x-codex-{primary,secondary}-window-minutes`,
9
+ * `x-codex-{primary,secondary}-reset-after-seconds`, and
10
+ * `x-codex-{primary,secondary}-reset-at` — where `reset-at` is documented to
11
+ * sometimes arrive as an EMPTY STRING while `reset-after-seconds` is present.
12
+ * Everything is validated; unparseable telemetry produces no estimate rather
13
+ * than a guess.
14
+ */
15
+ export declare const CODEX_SENSOR_ID = "codex-headers";
16
+ /** The telemetry windows Codex headers report. Primary is the 5h window. */
17
+ export declare const CODEX_WINDOW_KINDS: readonly ["primary", "secondary"];
18
+ export type CodexWindowKind = (typeof CODEX_WINDOW_KINDS)[number];
19
+ export interface CodexWindow {
20
+ /** Usage percentage in [0, 100] — the window exists only if this is valid. */
21
+ readonly usedPercent: number;
22
+ /** Validated finite non-negative seconds until reset, when reported. */
23
+ readonly resetAfterSeconds?: number | undefined;
24
+ /** Validated finite non-negative epoch-seconds reset timestamp, when reported. */
25
+ readonly resetAtSeconds?: number | undefined;
26
+ }
27
+ /** One validated header observation. `null` windows are simply absent. */
28
+ export interface CodexUsage {
29
+ readonly primary?: CodexWindow | undefined;
30
+ readonly secondary?: CodexWindow | undefined;
31
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Passive Codex/ChatGPT capacity telemetry (ADP-020).
3
+ *
4
+ * The only consumed evidence is the `x-codex-*` response-header family
5
+ * (including LiteLLM's `llm_provider-x-codex-*` relay form), captured from the
6
+ * `http.response` hook by the wiring-supplied allowlist. Verified wire facts:
7
+ * `x-codex-primary-used-percent` (5h window), `x-codex-secondary-used-percent`
8
+ * (weekly window), `x-codex-{primary,secondary}-window-minutes`,
9
+ * `x-codex-{primary,secondary}-reset-after-seconds`, and
10
+ * `x-codex-{primary,secondary}-reset-at` — where `reset-at` is documented to
11
+ * sometimes arrive as an EMPTY STRING while `reset-after-seconds` is present.
12
+ * Everything is validated; unparseable telemetry produces no estimate rather
13
+ * than a guess.
14
+ */
15
+ export const CODEX_SENSOR_ID = "codex-headers";
16
+ /** The telemetry windows Codex headers report. Primary is the 5h window. */
17
+ export const CODEX_WINDOW_KINDS = ["primary", "secondary"];
18
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/adapters/codex-capacity/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,MAAM,CAAC,MAAM,eAAe,GAAG,eAAe,CAAA;AAE9C,4EAA4E;AAC5E,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,SAAS,EAAE,WAAW,CAAsC,CAAA","sourcesContent":["/**\n * Passive Codex/ChatGPT capacity telemetry (ADP-020).\n *\n * The only consumed evidence is the `x-codex-*` response-header family\n * (including LiteLLM's `llm_provider-x-codex-*` relay form), captured from the\n * `http.response` hook by the wiring-supplied allowlist. Verified wire facts:\n * `x-codex-primary-used-percent` (5h window), `x-codex-secondary-used-percent`\n * (weekly window), `x-codex-{primary,secondary}-window-minutes`,\n * `x-codex-{primary,secondary}-reset-after-seconds`, and\n * `x-codex-{primary,secondary}-reset-at` — where `reset-at` is documented to\n * sometimes arrive as an EMPTY STRING while `reset-after-seconds` is present.\n * Everything is validated; unparseable telemetry produces no estimate rather\n * than a guess.\n */\n\nexport const CODEX_SENSOR_ID = \"codex-headers\"\n\n/** The telemetry windows Codex headers report. Primary is the 5h window. */\nexport const CODEX_WINDOW_KINDS = [\"primary\", \"secondary\"] as const satisfies readonly string[]\n\nexport type CodexWindowKind = (typeof CODEX_WINDOW_KINDS)[number]\n\nexport interface CodexWindow {\n /** Usage percentage in [0, 100] — the window exists only if this is valid. */\n readonly usedPercent: number\n /** Validated finite non-negative seconds until reset, when reported. */\n readonly resetAfterSeconds?: number | undefined\n /** Validated finite non-negative epoch-seconds reset timestamp, when reported. */\n readonly resetAtSeconds?: number | undefined\n}\n\n/** One validated header observation. `null` windows are simply absent. */\nexport interface CodexUsage {\n readonly primary?: CodexWindow | undefined\n readonly secondary?: CodexWindow | undefined\n}\n"]}
@@ -0,0 +1,51 @@
1
+ /**
2
+ * The typed escalation tool (T-18, ADP-015): the smallest Adaptive-owned
3
+ * mechanism for an agent to request stronger capability. The input schema is
4
+ * a plain JSON Schema — the host builds the model-facing tool definition by
5
+ * converting the schema, and a Standard Schema implementation is silently
6
+ * dropped ("Schema vendor does not support JSON Schema conversion", observed
7
+ * on 2.2.5). The tool also opts out of the host's code-mode bundling: with
8
+ * code mode enabled the host routes codemode-bundled tools through the
9
+ * `execute` tool only, and a direct call fails with "No tool named ... is
10
+ * currently available" (observed on 2.2.5). `execute` still re-validates
11
+ * defensively: model-produced JSON is untrusted. The tool is deliberately
12
+ * not a planning or task-management API.
13
+ */
14
+ import { ESCALATION_TOOL_NAME } from "../../execution/escalation/index.js";
15
+ export { ESCALATION_TOOL_NAME };
16
+ /** Records the request against the session's current work unit. */
17
+ export type EscalationRecorder = (sessionID: string, reason: string, evidence: string) => boolean;
18
+ /** The tool's input schema in JSON Schema form (host-convertible). */
19
+ declare const escalationInputSchema: {
20
+ type: string;
21
+ properties: {
22
+ reason: {
23
+ type: string;
24
+ minLength: number;
25
+ description: string;
26
+ };
27
+ evidence: {
28
+ type: string;
29
+ minLength: number;
30
+ description: string;
31
+ };
32
+ };
33
+ required: string[];
34
+ additionalProperties: boolean;
35
+ };
36
+ /** Keeps the tool directly callable when the host's code mode is enabled. */
37
+ declare const escalationToolOptions: {
38
+ readonly codemode: false;
39
+ };
40
+ /** Build the plugin tool registration for the escalation request surface. */
41
+ export declare function escalationTool(record: EscalationRecorder): {
42
+ name: typeof ESCALATION_TOOL_NAME;
43
+ description: string;
44
+ input: typeof escalationInputSchema;
45
+ options: typeof escalationToolOptions;
46
+ execute: (input: unknown, context: {
47
+ sessionID: string;
48
+ }) => Promise<{
49
+ content: string;
50
+ }>;
51
+ };
@@ -0,0 +1,61 @@
1
+ /**
2
+ * The typed escalation tool (T-18, ADP-015): the smallest Adaptive-owned
3
+ * mechanism for an agent to request stronger capability. The input schema is
4
+ * a plain JSON Schema — the host builds the model-facing tool definition by
5
+ * converting the schema, and a Standard Schema implementation is silently
6
+ * dropped ("Schema vendor does not support JSON Schema conversion", observed
7
+ * on 2.2.5). The tool also opts out of the host's code-mode bundling: with
8
+ * code mode enabled the host routes codemode-bundled tools through the
9
+ * `execute` tool only, and a direct call fails with "No tool named ... is
10
+ * currently available" (observed on 2.2.5). `execute` still re-validates
11
+ * defensively: model-produced JSON is untrusted. The tool is deliberately
12
+ * not a planning or task-management API.
13
+ */
14
+ import { ESCALATION_TOOL_NAME, parseEscalationRequest } from "../../execution/escalation/index.js";
15
+ export { ESCALATION_TOOL_NAME };
16
+ const TOOL_DESCRIPTION = "Request stronger model capability for the current work unit. Provide a short reason and " +
17
+ "the concrete evidence (what failed or was blocked). Adaptive records the request as " +
18
+ "structured evidence and weighs it when a failure on this unit is already classified as a " +
19
+ "capability gap (for example context overflow or a content-policy rejection), within a " +
20
+ "finite budget; recording alone does not force a tier escalation. This is not a planning " +
21
+ "or task-management tool.";
22
+ /** The tool's input schema in JSON Schema form (host-convertible). */
23
+ const escalationInputSchema = {
24
+ type: "object",
25
+ properties: {
26
+ reason: {
27
+ type: "string",
28
+ minLength: 1,
29
+ description: "Short reason why the current work unit needs stronger capability.",
30
+ },
31
+ evidence: {
32
+ type: "string",
33
+ minLength: 1,
34
+ description: "Concrete evidence of the capability gap: what failed or was blocked.",
35
+ },
36
+ },
37
+ required: ["reason", "evidence"],
38
+ additionalProperties: false,
39
+ };
40
+ /** Keeps the tool directly callable when the host's code mode is enabled. */
41
+ const escalationToolOptions = { codemode: false };
42
+ /** Build the plugin tool registration for the escalation request surface. */
43
+ export function escalationTool(record) {
44
+ return {
45
+ name: ESCALATION_TOOL_NAME,
46
+ description: TOOL_DESCRIPTION,
47
+ input: escalationInputSchema,
48
+ options: escalationToolOptions,
49
+ execute: (input, context) => {
50
+ const request = parseEscalationRequest(input);
51
+ if (request === undefined) {
52
+ return Promise.resolve({ content: "reason and evidence are required non-empty strings" });
53
+ }
54
+ const recorded = record(context.sessionID, request.reason, request.evidence);
55
+ return Promise.resolve(recorded
56
+ ? { content: "escalation request recorded for the current work unit" }
57
+ : { content: "no active work unit; the request was not recorded" });
58
+ },
59
+ };
60
+ }
61
+ //# sourceMappingURL=escalation-tool.js.map