@slkiser/opencode-quota 5.0.3 → 6.0.0-beta.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 (794) hide show
  1. package/README.md +30 -44
  2. package/dist/agent-tools.d.ts +24 -0
  3. package/dist/agent-tools.d.ts.map +1 -0
  4. package/dist/agent-tools.js +578 -0
  5. package/dist/agent-tools.js.map +1 -0
  6. package/dist/bin/opencode-quota.d.ts.map +1 -1
  7. package/dist/bin/opencode-quota.js +1 -32
  8. package/dist/bin/opencode-quota.js.map +1 -1
  9. package/dist/core/errors.d.ts +12 -0
  10. package/dist/core/errors.d.ts.map +1 -0
  11. package/dist/core/errors.js +141 -0
  12. package/dist/core/errors.js.map +1 -0
  13. package/dist/core/pace.d.ts +13 -0
  14. package/dist/core/pace.d.ts.map +1 -0
  15. package/dist/core/pace.js +52 -0
  16. package/dist/core/pace.js.map +1 -0
  17. package/dist/core/persist.d.ts +44 -0
  18. package/dist/core/persist.d.ts.map +1 -0
  19. package/dist/core/persist.js +210 -0
  20. package/dist/core/persist.js.map +1 -0
  21. package/dist/core/provider-source.d.ts +40 -0
  22. package/dist/core/provider-source.d.ts.map +1 -0
  23. package/dist/core/provider-source.js +210 -0
  24. package/dist/core/provider-source.js.map +1 -0
  25. package/dist/core/redact.d.ts +17 -0
  26. package/dist/core/redact.d.ts.map +1 -0
  27. package/dist/core/redact.js +217 -0
  28. package/dist/core/redact.js.map +1 -0
  29. package/dist/core/rows.d.ts +20 -0
  30. package/dist/core/rows.d.ts.map +1 -0
  31. package/dist/core/rows.js +142 -0
  32. package/dist/core/rows.js.map +1 -0
  33. package/dist/core/selection.d.ts +15 -0
  34. package/dist/core/selection.d.ts.map +1 -0
  35. package/dist/core/selection.js +43 -0
  36. package/dist/core/selection.js.map +1 -0
  37. package/dist/core/snapshot-publisher.d.ts +15 -0
  38. package/dist/core/snapshot-publisher.d.ts.map +1 -0
  39. package/dist/core/snapshot-publisher.js +57 -0
  40. package/dist/core/snapshot-publisher.js.map +1 -0
  41. package/dist/core/store.d.ts +93 -0
  42. package/dist/core/store.d.ts.map +1 -0
  43. package/dist/core/store.js +563 -0
  44. package/dist/core/store.js.map +1 -0
  45. package/dist/core/types.d.ts +186 -0
  46. package/dist/core/types.d.ts.map +1 -0
  47. package/dist/core/types.js +2 -0
  48. package/dist/core/types.js.map +1 -0
  49. package/dist/core/usage-ranges.d.ts +46 -0
  50. package/dist/core/usage-ranges.d.ts.map +1 -0
  51. package/dist/core/usage-ranges.js +72 -0
  52. package/dist/core/usage-ranges.js.map +1 -0
  53. package/dist/core/usage.d.ts +100 -0
  54. package/dist/core/usage.d.ts.map +1 -0
  55. package/dist/core/usage.js +373 -0
  56. package/dist/core/usage.js.map +1 -0
  57. package/dist/core/validate.d.ts +8 -0
  58. package/dist/core/validate.d.ts.map +1 -0
  59. package/dist/core/validate.js +167 -0
  60. package/dist/core/validate.js.map +1 -0
  61. package/dist/core/windows.d.ts +24 -0
  62. package/dist/core/windows.d.ts.map +1 -0
  63. package/dist/core/windows.js +43 -0
  64. package/dist/core/windows.js.map +1 -0
  65. package/dist/index.d.ts +2 -1
  66. package/dist/index.d.ts.map +1 -1
  67. package/dist/index.js.map +1 -1
  68. package/dist/lib/accounting-format.d.ts +2 -52
  69. package/dist/lib/accounting-format.d.ts.map +1 -1
  70. package/dist/lib/accounting-format.js +6 -255
  71. package/dist/lib/accounting-format.js.map +1 -1
  72. package/dist/lib/alibaba-auth.d.ts +14 -21
  73. package/dist/lib/alibaba-auth.d.ts.map +1 -1
  74. package/dist/lib/alibaba-auth.js +36 -149
  75. package/dist/lib/alibaba-auth.js.map +1 -1
  76. package/dist/lib/alibaba-coding-plan-local-quota.d.ts +1 -6
  77. package/dist/lib/alibaba-coding-plan-local-quota.d.ts.map +1 -1
  78. package/dist/lib/alibaba-coding-plan-local-quota.js +1 -1
  79. package/dist/lib/alibaba-coding-plan-local-quota.js.map +1 -1
  80. package/dist/lib/alibaba-token-plan.d.ts +18 -27
  81. package/dist/lib/alibaba-token-plan.d.ts.map +1 -1
  82. package/dist/lib/alibaba-token-plan.js +121 -285
  83. package/dist/lib/alibaba-token-plan.js.map +1 -1
  84. package/dist/lib/anthropic-auth.d.ts +3 -5
  85. package/dist/lib/anthropic-auth.d.ts.map +1 -1
  86. package/dist/lib/anthropic-auth.js +6 -9
  87. package/dist/lib/anthropic-auth.js.map +1 -1
  88. package/dist/lib/anthropic.d.ts +12 -37
  89. package/dist/lib/anthropic.d.ts.map +1 -1
  90. package/dist/lib/anthropic.js +348 -698
  91. package/dist/lib/anthropic.js.map +1 -1
  92. package/dist/lib/api-key-resolver.d.ts +165 -192
  93. package/dist/lib/api-key-resolver.d.ts.map +1 -1
  94. package/dist/lib/api-key-resolver.js +208 -234
  95. package/dist/lib/api-key-resolver.js.map +1 -1
  96. package/dist/lib/atomic-json.d.ts +4 -1
  97. package/dist/lib/atomic-json.d.ts.map +1 -1
  98. package/dist/lib/atomic-json.js +2 -3
  99. package/dist/lib/atomic-json.js.map +1 -1
  100. package/dist/lib/calendar-date.d.ts +15 -0
  101. package/dist/lib/calendar-date.d.ts.map +1 -0
  102. package/dist/lib/calendar-date.js +21 -0
  103. package/dist/lib/calendar-date.js.map +1 -0
  104. package/dist/lib/chutes.d.ts +0 -1
  105. package/dist/lib/chutes.d.ts.map +1 -1
  106. package/dist/lib/chutes.js +2 -4
  107. package/dist/lib/chutes.js.map +1 -1
  108. package/dist/lib/cli-reports.d.ts +15 -11
  109. package/dist/lib/cli-reports.d.ts.map +1 -1
  110. package/dist/lib/cli-reports.js +34 -107
  111. package/dist/lib/cli-reports.js.map +1 -1
  112. package/dist/lib/cli-show.d.ts +9 -22
  113. package/dist/lib/cli-show.d.ts.map +1 -1
  114. package/dist/lib/cli-show.js +104 -75
  115. package/dist/lib/cli-show.js.map +1 -1
  116. package/dist/lib/config-file-utils.d.ts +3 -15
  117. package/dist/lib/config-file-utils.d.ts.map +1 -1
  118. package/dist/lib/config-file-utils.js +4 -63
  119. package/dist/lib/config-file-utils.js.map +1 -1
  120. package/dist/lib/config-write-target.d.ts +4 -7
  121. package/dist/lib/config-write-target.d.ts.map +1 -1
  122. package/dist/lib/config-write-target.js +4 -18
  123. package/dist/lib/config-write-target.js.map +1 -1
  124. package/dist/lib/copilot.d.ts +29 -20
  125. package/dist/lib/copilot.d.ts.map +1 -1
  126. package/dist/lib/copilot.js +392 -649
  127. package/dist/lib/copilot.js.map +1 -1
  128. package/dist/lib/cursor-detection.d.ts +3 -3
  129. package/dist/lib/cursor-detection.d.ts.map +1 -1
  130. package/dist/lib/cursor-detection.js +44 -88
  131. package/dist/lib/cursor-detection.js.map +1 -1
  132. package/dist/lib/cursor-pricing.d.ts +71 -8
  133. package/dist/lib/cursor-pricing.d.ts.map +1 -1
  134. package/dist/lib/cursor-pricing.js +33 -125
  135. package/dist/lib/cursor-pricing.js.map +1 -1
  136. package/dist/lib/cursor-usage.d.ts +6 -4
  137. package/dist/lib/cursor-usage.d.ts.map +1 -1
  138. package/dist/lib/cursor-usage.js +27 -40
  139. package/dist/lib/cursor-usage.js.map +1 -1
  140. package/dist/lib/deepseek.d.ts +9 -17
  141. package/dist/lib/deepseek.d.ts.map +1 -1
  142. package/dist/lib/deepseek.js +24 -54
  143. package/dist/lib/deepseek.js.map +1 -1
  144. package/dist/lib/display-sanitize.d.ts +0 -8
  145. package/dist/lib/display-sanitize.d.ts.map +1 -1
  146. package/dist/lib/display-sanitize.js +0 -139
  147. package/dist/lib/display-sanitize.js.map +1 -1
  148. package/dist/lib/entries.d.ts +13 -239
  149. package/dist/lib/entries.d.ts.map +1 -1
  150. package/dist/lib/entries.js +1 -46
  151. package/dist/lib/entries.js.map +1 -1
  152. package/dist/lib/file-lock.d.ts +23 -0
  153. package/dist/lib/file-lock.d.ts.map +1 -0
  154. package/dist/lib/file-lock.js +122 -0
  155. package/dist/lib/file-lock.js.map +1 -0
  156. package/dist/lib/format-utils.d.ts +2 -82
  157. package/dist/lib/format-utils.d.ts.map +1 -1
  158. package/dist/lib/format-utils.js +2 -191
  159. package/dist/lib/format-utils.js.map +1 -1
  160. package/dist/lib/glm-coding-plan.d.ts +6 -5
  161. package/dist/lib/glm-coding-plan.d.ts.map +1 -1
  162. package/dist/lib/glm-coding-plan.js +19 -1
  163. package/dist/lib/glm-coding-plan.js.map +1 -1
  164. package/dist/lib/google-agy.d.ts +8 -40
  165. package/dist/lib/google-agy.d.ts.map +1 -1
  166. package/dist/lib/google-agy.js +88 -519
  167. package/dist/lib/google-agy.js.map +1 -1
  168. package/dist/lib/google-companion-credentials.d.ts +9 -40
  169. package/dist/lib/google-companion-credentials.d.ts.map +1 -1
  170. package/dist/lib/google-companion-credentials.js +167 -224
  171. package/dist/lib/google-companion-credentials.js.map +1 -1
  172. package/dist/lib/google-gemini-cli.d.ts +5 -38
  173. package/dist/lib/google-gemini-cli.d.ts.map +1 -1
  174. package/dist/lib/google-gemini-cli.js +71 -442
  175. package/dist/lib/google-gemini-cli.js.map +1 -1
  176. package/dist/lib/google-oauth-quota.d.ts +76 -0
  177. package/dist/lib/google-oauth-quota.d.ts.map +1 -0
  178. package/dist/lib/google-oauth-quota.js +297 -0
  179. package/dist/lib/google-oauth-quota.js.map +1 -0
  180. package/dist/lib/google-token-cache.d.ts +3 -5
  181. package/dist/lib/google-token-cache.d.ts.map +1 -1
  182. package/dist/lib/google-token-cache.js +2 -14
  183. package/dist/lib/google-token-cache.js.map +1 -1
  184. package/dist/lib/home-dir-display.d.ts.map +1 -1
  185. package/dist/lib/home-dir-display.js +21 -39
  186. package/dist/lib/home-dir-display.js.map +1 -1
  187. package/dist/lib/http.d.ts +4 -1
  188. package/dist/lib/http.d.ts.map +1 -1
  189. package/dist/lib/http.js +2 -1
  190. package/dist/lib/http.js.map +1 -1
  191. package/dist/lib/json-guards.d.ts +11 -0
  192. package/dist/lib/json-guards.d.ts.map +1 -0
  193. package/dist/lib/json-guards.js +25 -0
  194. package/dist/lib/json-guards.js.map +1 -0
  195. package/dist/lib/kilo.d.ts +0 -2
  196. package/dist/lib/kilo.d.ts.map +1 -1
  197. package/dist/lib/kilo.js +33 -116
  198. package/dist/lib/kilo.js.map +1 -1
  199. package/dist/lib/kimi-endpoints.d.ts +2 -2
  200. package/dist/lib/kimi-endpoints.d.ts.map +1 -1
  201. package/dist/lib/kimi-endpoints.js +1 -1
  202. package/dist/lib/kimi-endpoints.js.map +1 -1
  203. package/dist/lib/kimi.d.ts.map +1 -1
  204. package/dist/lib/kimi.js +40 -86
  205. package/dist/lib/kimi.js.map +1 -1
  206. package/dist/lib/maintainer-announcements.d.ts +4 -17
  207. package/dist/lib/maintainer-announcements.d.ts.map +1 -1
  208. package/dist/lib/maintainer-announcements.js +7 -44
  209. package/dist/lib/maintainer-announcements.js.map +1 -1
  210. package/dist/lib/maintainer-home-notice.d.ts +19 -0
  211. package/dist/lib/maintainer-home-notice.d.ts.map +1 -0
  212. package/dist/lib/maintainer-home-notice.js +92 -0
  213. package/dist/lib/maintainer-home-notice.js.map +1 -0
  214. package/dist/lib/mimo-config.d.ts +6 -9
  215. package/dist/lib/mimo-config.d.ts.map +1 -1
  216. package/dist/lib/mimo-config.js +43 -74
  217. package/dist/lib/mimo-config.js.map +1 -1
  218. package/dist/lib/mimo.d.ts +2 -5
  219. package/dist/lib/mimo.d.ts.map +1 -1
  220. package/dist/lib/mimo.js +57 -109
  221. package/dist/lib/mimo.js.map +1 -1
  222. package/dist/lib/minimax-endpoints.d.ts +2 -2
  223. package/dist/lib/minimax-endpoints.d.ts.map +1 -1
  224. package/dist/lib/minimax-endpoints.js +1 -1
  225. package/dist/lib/minimax-endpoints.js.map +1 -1
  226. package/dist/lib/modelsdev-pricing.d.ts +22 -16
  227. package/dist/lib/modelsdev-pricing.d.ts.map +1 -1
  228. package/dist/lib/modelsdev-pricing.js +279 -560
  229. package/dist/lib/modelsdev-pricing.js.map +1 -1
  230. package/dist/lib/nanogpt.d.ts +6 -6
  231. package/dist/lib/nanogpt.d.ts.map +1 -1
  232. package/dist/lib/nanogpt.js +19 -72
  233. package/dist/lib/nanogpt.js.map +1 -1
  234. package/dist/lib/ollama-cloud.d.ts +3 -6
  235. package/dist/lib/ollama-cloud.d.ts.map +1 -1
  236. package/dist/lib/ollama-cloud.js +26 -37
  237. package/dist/lib/ollama-cloud.js.map +1 -1
  238. package/dist/lib/openai.d.ts +18 -29
  239. package/dist/lib/openai.d.ts.map +1 -1
  240. package/dist/lib/openai.js +108 -182
  241. package/dist/lib/openai.js.map +1 -1
  242. package/dist/lib/opencode-auth-sqlite.d.ts.map +1 -1
  243. package/dist/lib/opencode-auth-sqlite.js +22 -23
  244. package/dist/lib/opencode-auth-sqlite.js.map +1 -1
  245. package/dist/lib/opencode-auth.d.ts +17 -11
  246. package/dist/lib/opencode-auth.d.ts.map +1 -1
  247. package/dist/lib/opencode-auth.js +39 -46
  248. package/dist/lib/opencode-auth.js.map +1 -1
  249. package/dist/lib/opencode-config-editor.d.ts +3 -34
  250. package/dist/lib/opencode-config-editor.d.ts.map +1 -1
  251. package/dist/lib/opencode-config-editor.js +23 -142
  252. package/dist/lib/opencode-config-editor.js.map +1 -1
  253. package/dist/lib/opencode-config-providers.d.ts +4 -3
  254. package/dist/lib/opencode-config-providers.d.ts.map +1 -1
  255. package/dist/lib/opencode-config-providers.js +7 -11
  256. package/dist/lib/opencode-config-providers.js.map +1 -1
  257. package/dist/lib/opencode-config-read.d.ts +2 -1
  258. package/dist/lib/opencode-config-read.d.ts.map +1 -1
  259. package/dist/lib/opencode-config-read.js +3 -2
  260. package/dist/lib/opencode-config-read.js.map +1 -1
  261. package/dist/lib/opencode-console-auth.d.ts +2 -3
  262. package/dist/lib/opencode-console-auth.d.ts.map +1 -1
  263. package/dist/lib/opencode-console-auth.js +2 -2
  264. package/dist/lib/opencode-console-auth.js.map +1 -1
  265. package/dist/lib/opencode-go.d.ts.map +1 -1
  266. package/dist/lib/opencode-go.js +51 -79
  267. package/dist/lib/opencode-go.js.map +1 -1
  268. package/dist/lib/opencode-storage.d.ts +18 -3
  269. package/dist/lib/opencode-storage.d.ts.map +1 -1
  270. package/dist/lib/opencode-storage.js +134 -2
  271. package/dist/lib/opencode-storage.js.map +1 -1
  272. package/dist/lib/opencode-zen.d.ts +15 -12
  273. package/dist/lib/opencode-zen.d.ts.map +1 -1
  274. package/dist/lib/opencode-zen.js +139 -147
  275. package/dist/lib/opencode-zen.js.map +1 -1
  276. package/dist/lib/openrouter.d.ts.map +1 -1
  277. package/dist/lib/openrouter.js +1 -13
  278. package/dist/lib/openrouter.js.map +1 -1
  279. package/dist/lib/pricing-key.d.ts +48 -0
  280. package/dist/lib/pricing-key.d.ts.map +1 -0
  281. package/dist/lib/pricing-key.js +371 -0
  282. package/dist/lib/pricing-key.js.map +1 -0
  283. package/dist/lib/provider-metadata.d.ts +16 -20
  284. package/dist/lib/provider-metadata.d.ts.map +1 -1
  285. package/dist/lib/provider-metadata.js +69 -69
  286. package/dist/lib/provider-metadata.js.map +1 -1
  287. package/dist/lib/provider-model-matching.d.ts +2 -1
  288. package/dist/lib/provider-model-matching.d.ts.map +1 -1
  289. package/dist/lib/provider-registration.d.ts +1 -177
  290. package/dist/lib/provider-registration.d.ts.map +1 -1
  291. package/dist/lib/provider-registration.js +1 -163
  292. package/dist/lib/provider-registration.js.map +1 -1
  293. package/dist/lib/quota-dialog-commands.d.ts +87 -53
  294. package/dist/lib/quota-dialog-commands.d.ts.map +1 -1
  295. package/dist/lib/quota-dialog-commands.js +259 -519
  296. package/dist/lib/quota-dialog-commands.js.map +1 -1
  297. package/dist/lib/quota-export-types.d.ts +29 -23
  298. package/dist/lib/quota-export-types.d.ts.map +1 -1
  299. package/dist/lib/quota-export.d.ts +16 -26
  300. package/dist/lib/quota-export.d.ts.map +1 -1
  301. package/dist/lib/quota-export.js +105 -147
  302. package/dist/lib/quota-export.js.map +1 -1
  303. package/dist/lib/quota-providers-local.d.ts +12 -8
  304. package/dist/lib/quota-providers-local.d.ts.map +1 -1
  305. package/dist/lib/quota-providers-local.js +150 -223
  306. package/dist/lib/quota-providers-local.js.map +1 -1
  307. package/dist/lib/quota-providers-remote.d.ts +3 -8
  308. package/dist/lib/quota-providers-remote.d.ts.map +1 -1
  309. package/dist/lib/quota-providers-remote.js +309 -449
  310. package/dist/lib/quota-providers-remote.js.map +1 -1
  311. package/dist/lib/quota-providers.d.ts +57 -15
  312. package/dist/lib/quota-providers.d.ts.map +1 -1
  313. package/dist/lib/quota-providers.js +157 -64
  314. package/dist/lib/quota-providers.js.map +1 -1
  315. package/dist/lib/quota-report-message.d.ts +23 -10
  316. package/dist/lib/quota-report-message.d.ts.map +1 -1
  317. package/dist/lib/quota-report-message.js +37 -13
  318. package/dist/lib/quota-report-message.js.map +1 -1
  319. package/dist/lib/quota-retry-wait.d.ts +14 -6
  320. package/dist/lib/quota-retry-wait.d.ts.map +1 -1
  321. package/dist/lib/quota-retry-wait.js +37 -39
  322. package/dist/lib/quota-retry-wait.js.map +1 -1
  323. package/dist/lib/quota-status.d.ts +50 -33
  324. package/dist/lib/quota-status.d.ts.map +1 -1
  325. package/dist/lib/quota-status.js +1113 -773
  326. package/dist/lib/quota-status.js.map +1 -1
  327. package/dist/lib/quota-telemetry.d.ts +7 -14
  328. package/dist/lib/quota-telemetry.d.ts.map +1 -1
  329. package/dist/lib/quota-telemetry.js +46 -97
  330. package/dist/lib/quota-telemetry.js.map +1 -1
  331. package/dist/lib/report-document.d.ts +5 -65
  332. package/dist/lib/report-document.d.ts.map +1 -1
  333. package/dist/lib/report-document.js +14 -167
  334. package/dist/lib/report-document.js.map +1 -1
  335. package/dist/lib/resolved-auth-identity.d.ts +1 -2
  336. package/dist/lib/resolved-auth-identity.d.ts.map +1 -1
  337. package/dist/lib/resolved-auth-identity.js +0 -4
  338. package/dist/lib/resolved-auth-identity.js.map +1 -1
  339. package/dist/lib/session-tokens.d.ts +19 -19
  340. package/dist/lib/session-tokens.d.ts.map +1 -1
  341. package/dist/lib/session-tokens.js +58 -44
  342. package/dist/lib/session-tokens.js.map +1 -1
  343. package/dist/lib/synthetic.d.ts +0 -1
  344. package/dist/lib/synthetic.d.ts.map +1 -1
  345. package/dist/lib/synthetic.js +43 -94
  346. package/dist/lib/synthetic.js.map +1 -1
  347. package/dist/lib/token-buckets.d.ts +0 -1
  348. package/dist/lib/token-buckets.d.ts.map +1 -1
  349. package/dist/lib/token-buckets.js +0 -3
  350. package/dist/lib/token-buckets.js.map +1 -1
  351. package/dist/lib/token-cost.d.ts +2 -1
  352. package/dist/lib/token-cost.d.ts.map +1 -1
  353. package/dist/lib/types.d.ts +25 -333
  354. package/dist/lib/types.d.ts.map +1 -1
  355. package/dist/lib/types.js +2 -70
  356. package/dist/lib/types.js.map +1 -1
  357. package/dist/lib/xai.d.ts +6 -6
  358. package/dist/lib/xai.d.ts.map +1 -1
  359. package/dist/lib/xai.js +51 -90
  360. package/dist/lib/xai.js.map +1 -1
  361. package/dist/plugin.d.ts +6 -3
  362. package/dist/plugin.d.ts.map +1 -1
  363. package/dist/plugin.js +315 -90
  364. package/dist/plugin.js.map +1 -1
  365. package/dist/providers/alibaba-coding-plan.d.ts +4 -0
  366. package/dist/providers/alibaba-coding-plan.d.ts.map +1 -1
  367. package/dist/providers/alibaba-coding-plan.js +47 -77
  368. package/dist/providers/alibaba-coding-plan.js.map +1 -1
  369. package/dist/providers/alibaba-token-plan.d.ts.map +1 -1
  370. package/dist/providers/alibaba-token-plan.js +34 -46
  371. package/dist/providers/alibaba-token-plan.js.map +1 -1
  372. package/dist/providers/anthropic.d.ts +2 -4
  373. package/dist/providers/anthropic.d.ts.map +1 -1
  374. package/dist/providers/anthropic.js +80 -162
  375. package/dist/providers/anthropic.js.map +1 -1
  376. package/dist/providers/cache-policies.d.ts +0 -2
  377. package/dist/providers/cache-policies.d.ts.map +1 -1
  378. package/dist/providers/cache-policies.js +38 -88
  379. package/dist/providers/cache-policies.js.map +1 -1
  380. package/dist/providers/chutes.d.ts +0 -3
  381. package/dist/providers/chutes.d.ts.map +1 -1
  382. package/dist/providers/chutes.js +16 -28
  383. package/dist/providers/chutes.js.map +1 -1
  384. package/dist/providers/contract.d.ts +128 -0
  385. package/dist/providers/contract.d.ts.map +1 -0
  386. package/dist/providers/contract.js +101 -0
  387. package/dist/providers/contract.js.map +1 -0
  388. package/dist/providers/copilot.d.ts.map +1 -1
  389. package/dist/providers/copilot.js +171 -175
  390. package/dist/providers/copilot.js.map +1 -1
  391. package/dist/providers/cursor.d.ts.map +1 -1
  392. package/dist/providers/cursor.js +56 -109
  393. package/dist/providers/cursor.js.map +1 -1
  394. package/dist/providers/deepseek.d.ts +1 -6
  395. package/dist/providers/deepseek.d.ts.map +1 -1
  396. package/dist/providers/deepseek.js +46 -81
  397. package/dist/providers/deepseek.js.map +1 -1
  398. package/dist/providers/glm-coding-plan.d.ts +8 -0
  399. package/dist/providers/glm-coding-plan.d.ts.map +1 -0
  400. package/dist/providers/glm-coding-plan.js +106 -0
  401. package/dist/providers/glm-coding-plan.js.map +1 -0
  402. package/dist/providers/google-agy.d.ts +0 -1
  403. package/dist/providers/google-agy.d.ts.map +1 -1
  404. package/dist/providers/google-agy.js +41 -192
  405. package/dist/providers/google-agy.js.map +1 -1
  406. package/dist/providers/google-gemini-cli.d.ts.map +1 -1
  407. package/dist/providers/google-gemini-cli.js +51 -151
  408. package/dist/providers/google-gemini-cli.js.map +1 -1
  409. package/dist/providers/google-oauth-provider.d.ts +39 -0
  410. package/dist/providers/google-oauth-provider.d.ts.map +1 -0
  411. package/dist/providers/google-oauth-provider.js +161 -0
  412. package/dist/providers/google-oauth-provider.js.map +1 -0
  413. package/dist/providers/kilo.d.ts +0 -3
  414. package/dist/providers/kilo.d.ts.map +1 -1
  415. package/dist/providers/kilo.js +69 -123
  416. package/dist/providers/kilo.js.map +1 -1
  417. package/dist/providers/kimi-code.d.ts +5 -0
  418. package/dist/providers/kimi-code.d.ts.map +1 -1
  419. package/dist/providers/kimi-code.js +91 -136
  420. package/dist/providers/kimi-code.js.map +1 -1
  421. package/dist/providers/mimo.d.ts.map +1 -1
  422. package/dist/providers/mimo.js +55 -94
  423. package/dist/providers/mimo.js.map +1 -1
  424. package/dist/providers/minimax-coding-plan.d.ts +2 -23
  425. package/dist/providers/minimax-coding-plan.d.ts.map +1 -1
  426. package/dist/providers/minimax-coding-plan.js +161 -277
  427. package/dist/providers/minimax-coding-plan.js.map +1 -1
  428. package/dist/providers/nanogpt.d.ts +0 -3
  429. package/dist/providers/nanogpt.d.ts.map +1 -1
  430. package/dist/providers/nanogpt.js +61 -95
  431. package/dist/providers/nanogpt.js.map +1 -1
  432. package/dist/providers/ollama-cloud.d.ts +0 -6
  433. package/dist/providers/ollama-cloud.d.ts.map +1 -1
  434. package/dist/providers/ollama-cloud.js +86 -112
  435. package/dist/providers/ollama-cloud.js.map +1 -1
  436. package/dist/providers/openai.d.ts.map +1 -1
  437. package/dist/providers/openai.js +144 -166
  438. package/dist/providers/openai.js.map +1 -1
  439. package/dist/providers/opencode-go.d.ts.map +1 -1
  440. package/dist/providers/opencode-go.js +157 -199
  441. package/dist/providers/opencode-go.js.map +1 -1
  442. package/dist/providers/opencode-zen.d.ts.map +1 -1
  443. package/dist/providers/opencode-zen.js +100 -150
  444. package/dist/providers/opencode-zen.js.map +1 -1
  445. package/dist/providers/openrouter.d.ts +0 -3
  446. package/dist/providers/openrouter.d.ts.map +1 -1
  447. package/dist/providers/openrouter.js +26 -13
  448. package/dist/providers/openrouter.js.map +1 -1
  449. package/dist/providers/quota-providers.d.ts +7 -3
  450. package/dist/providers/quota-providers.d.ts.map +1 -1
  451. package/dist/providers/quota-providers.js +111 -217
  452. package/dist/providers/quota-providers.js.map +1 -1
  453. package/dist/providers/registry.d.ts.map +1 -1
  454. package/dist/providers/registry.js +1 -2
  455. package/dist/providers/registry.js.map +1 -1
  456. package/dist/providers/result-helpers.d.ts +10 -37
  457. package/dist/providers/result-helpers.d.ts.map +1 -1
  458. package/dist/providers/result-helpers.js +23 -56
  459. package/dist/providers/result-helpers.js.map +1 -1
  460. package/dist/providers/synthetic.d.ts +0 -3
  461. package/dist/providers/synthetic.d.ts.map +1 -1
  462. package/dist/providers/synthetic.js +16 -58
  463. package/dist/providers/synthetic.js.map +1 -1
  464. package/dist/providers/xai.d.ts +2 -1
  465. package/dist/providers/xai.d.ts.map +1 -1
  466. package/dist/providers/xai.js +49 -71
  467. package/dist/providers/xai.js.map +1 -1
  468. package/dist/rpc.d.ts +110 -38
  469. package/dist/rpc.d.ts.map +1 -1
  470. package/dist/rpc.js +125 -37
  471. package/dist/rpc.js.map +1 -1
  472. package/dist/settings/convert.d.ts +60 -0
  473. package/dist/settings/convert.d.ts.map +1 -0
  474. package/dist/settings/convert.js +473 -0
  475. package/dist/settings/convert.js.map +1 -0
  476. package/dist/settings/file.d.ts +89 -0
  477. package/dist/settings/file.d.ts.map +1 -0
  478. package/dist/settings/file.js +452 -0
  479. package/dist/settings/file.js.map +1 -0
  480. package/dist/settings/index.d.ts +10 -0
  481. package/dist/settings/index.d.ts.map +1 -0
  482. package/dist/settings/index.js +3 -0
  483. package/dist/settings/index.js.map +1 -0
  484. package/dist/settings/schema.d.ts +607 -0
  485. package/dist/settings/schema.d.ts.map +1 -0
  486. package/dist/settings/schema.js +479 -0
  487. package/dist/settings/schema.js.map +1 -0
  488. package/dist/tui/client.d.ts +86 -0
  489. package/dist/tui/client.d.ts.map +1 -0
  490. package/dist/tui/client.js +229 -0
  491. package/dist/tui/client.js.map +1 -0
  492. package/dist/tui/display.d.ts +127 -0
  493. package/dist/tui/display.d.ts.map +1 -0
  494. package/dist/tui/display.js +474 -0
  495. package/dist/tui/display.js.map +1 -0
  496. package/dist/tui/index.d.ts +21 -0
  497. package/dist/tui/index.d.ts.map +1 -0
  498. package/dist/tui/index.js +305 -0
  499. package/dist/tui/server-settings.d.ts +43 -0
  500. package/dist/tui/server-settings.d.ts.map +1 -0
  501. package/dist/tui/server-settings.js +233 -0
  502. package/dist/tui/server-settings.js.map +1 -0
  503. package/dist/tui/sidebar.d.ts +41 -0
  504. package/dist/tui/sidebar.d.ts.map +1 -0
  505. package/dist/tui/sidebar.js +148 -0
  506. package/dist/tui/status-line.d.ts +14 -0
  507. package/dist/tui/status-line.d.ts.map +1 -0
  508. package/dist/tui/status-line.js +225 -0
  509. package/dist/tui/theme.d.ts +41 -0
  510. package/dist/tui/theme.d.ts.map +1 -0
  511. package/dist/tui/theme.js +52 -0
  512. package/dist/tui/theme.js.map +1 -0
  513. package/dist/tui/toast-colors.d.ts +37 -0
  514. package/dist/tui/toast-colors.d.ts.map +1 -0
  515. package/dist/tui/toast-colors.js +77 -0
  516. package/dist/tui/toasts.d.ts +59 -0
  517. package/dist/tui/toasts.d.ts.map +1 -0
  518. package/dist/tui/toasts.js +148 -0
  519. package/dist/tui/toasts.js.map +1 -0
  520. package/dist/tui/window/drag.d.ts +26 -0
  521. package/dist/tui/window/drag.d.ts.map +1 -0
  522. package/dist/tui/window/drag.js +80 -0
  523. package/dist/tui/window/drag.js.map +1 -0
  524. package/dist/tui/window/limits.d.ts +15 -0
  525. package/dist/tui/window/limits.d.ts.map +1 -0
  526. package/dist/tui/window/limits.js +227 -0
  527. package/dist/tui/window/limits.js.map +1 -0
  528. package/dist/tui/window/rail.d.ts +21 -0
  529. package/dist/tui/window/rail.d.ts.map +1 -0
  530. package/dist/tui/window/rail.js +81 -0
  531. package/dist/tui/window/settings.d.ts +164 -0
  532. package/dist/tui/window/settings.d.ts.map +1 -0
  533. package/dist/tui/window/settings.js +1679 -0
  534. package/dist/tui/window/shell.d.ts +108 -0
  535. package/dist/tui/window/shell.d.ts.map +1 -0
  536. package/dist/tui/window/shell.js +1003 -0
  537. package/dist/tui/window/usage.d.ts +75 -0
  538. package/dist/tui/window/usage.d.ts.map +1 -0
  539. package/dist/tui/window/usage.js +928 -0
  540. package/dist/tui/window/views.d.ts +61 -0
  541. package/dist/tui/window/views.d.ts.map +1 -0
  542. package/dist/tui/window/views.js +242 -0
  543. package/dist/tui.d.ts +1 -5
  544. package/dist/tui.js +1 -894
  545. package/dist/view/columns.d.ts +15 -0
  546. package/dist/view/columns.d.ts.map +1 -0
  547. package/dist/view/columns.js +48 -0
  548. package/dist/view/columns.js.map +1 -0
  549. package/dist/view/format.d.ts +82 -0
  550. package/dist/view/format.d.ts.map +1 -0
  551. package/dist/view/format.js +201 -0
  552. package/dist/view/format.js.map +1 -0
  553. package/dist/view/quick-look.d.ts +28 -0
  554. package/dist/view/quick-look.d.ts.map +1 -0
  555. package/dist/view/quick-look.js +150 -0
  556. package/dist/view/quick-look.js.map +1 -0
  557. package/dist/view/select.d.ts +67 -0
  558. package/dist/view/select.d.ts.map +1 -0
  559. package/dist/view/select.js +87 -0
  560. package/dist/view/select.js.map +1 -0
  561. package/dist/view/text.d.ts +153 -0
  562. package/dist/view/text.d.ts.map +1 -0
  563. package/dist/view/text.js +642 -0
  564. package/dist/view/text.js.map +1 -0
  565. package/dist/view/usage.d.ts +86 -0
  566. package/dist/view/usage.d.ts.map +1 -0
  567. package/dist/view/usage.js +724 -0
  568. package/dist/view/usage.js.map +1 -0
  569. package/package.json +4 -6
  570. package/dist/lib/chutes-config.d.ts +0 -15
  571. package/dist/lib/chutes-config.d.ts.map +0 -1
  572. package/dist/lib/chutes-config.js +0 -28
  573. package/dist/lib/chutes-config.js.map +0 -1
  574. package/dist/lib/cli-status.d.ts +0 -8
  575. package/dist/lib/cli-status.d.ts.map +0 -1
  576. package/dist/lib/cli-status.js +0 -98
  577. package/dist/lib/cli-status.js.map +0 -1
  578. package/dist/lib/command-parsing.d.ts +0 -43
  579. package/dist/lib/command-parsing.d.ts.map +0 -1
  580. package/dist/lib/command-parsing.js +0 -103
  581. package/dist/lib/command-parsing.js.map +0 -1
  582. package/dist/lib/config.d.ts +0 -63
  583. package/dist/lib/config.d.ts.map +0 -1
  584. package/dist/lib/config.js +0 -1092
  585. package/dist/lib/config.js.map +0 -1
  586. package/dist/lib/deepseek-auth.d.ts +0 -15
  587. package/dist/lib/deepseek-auth.d.ts.map +0 -1
  588. package/dist/lib/deepseek-auth.js +0 -28
  589. package/dist/lib/deepseek-auth.js.map +0 -1
  590. package/dist/lib/format.d.ts +0 -25
  591. package/dist/lib/format.d.ts.map +0 -1
  592. package/dist/lib/format.js +0 -300
  593. package/dist/lib/format.js.map +0 -1
  594. package/dist/lib/google-agy-companion.d.ts +0 -8
  595. package/dist/lib/google-agy-companion.d.ts.map +0 -1
  596. package/dist/lib/google-agy-companion.js +0 -64
  597. package/dist/lib/google-agy-companion.js.map +0 -1
  598. package/dist/lib/google-gemini-cli-companion.d.ts +0 -8
  599. package/dist/lib/google-gemini-cli-companion.d.ts.map +0 -1
  600. package/dist/lib/google-gemini-cli-companion.js +0 -64
  601. package/dist/lib/google-gemini-cli-companion.js.map +0 -1
  602. package/dist/lib/grouped-entry-normalization.d.ts +0 -12
  603. package/dist/lib/grouped-entry-normalization.d.ts.map +0 -1
  604. package/dist/lib/grouped-entry-normalization.js +0 -124
  605. package/dist/lib/grouped-entry-normalization.js.map +0 -1
  606. package/dist/lib/grouped-header-format.d.ts +0 -5
  607. package/dist/lib/grouped-header-format.d.ts.map +0 -1
  608. package/dist/lib/grouped-header-format.js +0 -16
  609. package/dist/lib/grouped-header-format.js.map +0 -1
  610. package/dist/lib/init-installer.d.ts +0 -123
  611. package/dist/lib/init-installer.d.ts.map +0 -1
  612. package/dist/lib/init-installer.js +0 -1111
  613. package/dist/lib/init-installer.js.map +0 -1
  614. package/dist/lib/jsonc.d.ts +0 -17
  615. package/dist/lib/jsonc.d.ts.map +0 -1
  616. package/dist/lib/jsonc.js +0 -23
  617. package/dist/lib/jsonc.js.map +0 -1
  618. package/dist/lib/kilo-config.d.ts +0 -15
  619. package/dist/lib/kilo-config.d.ts.map +0 -1
  620. package/dist/lib/kilo-config.js +0 -27
  621. package/dist/lib/kilo-config.js.map +0 -1
  622. package/dist/lib/kimi-auth.d.ts +0 -49
  623. package/dist/lib/kimi-auth.d.ts.map +0 -1
  624. package/dist/lib/kimi-auth.js +0 -82
  625. package/dist/lib/kimi-auth.js.map +0 -1
  626. package/dist/lib/markdown-table.d.ts +0 -51
  627. package/dist/lib/markdown-table.d.ts.map +0 -1
  628. package/dist/lib/markdown-table.js +0 -179
  629. package/dist/lib/markdown-table.js.map +0 -1
  630. package/dist/lib/minimax-auth.d.ts +0 -62
  631. package/dist/lib/minimax-auth.d.ts.map +0 -1
  632. package/dist/lib/minimax-auth.js +0 -206
  633. package/dist/lib/minimax-auth.js.map +0 -1
  634. package/dist/lib/nanogpt-config.d.ts +0 -15
  635. package/dist/lib/nanogpt-config.d.ts.map +0 -1
  636. package/dist/lib/nanogpt-config.js +0 -31
  637. package/dist/lib/nanogpt-config.js.map +0 -1
  638. package/dist/lib/ollama-cloud-config.d.ts +0 -15
  639. package/dist/lib/ollama-cloud-config.d.ts.map +0 -1
  640. package/dist/lib/ollama-cloud-config.js +0 -27
  641. package/dist/lib/ollama-cloud-config.js.map +0 -1
  642. package/dist/lib/opencode-go-auth.d.ts +0 -16
  643. package/dist/lib/opencode-go-auth.d.ts.map +0 -1
  644. package/dist/lib/opencode-go-auth.js +0 -42
  645. package/dist/lib/opencode-go-auth.js.map +0 -1
  646. package/dist/lib/opencode-version.d.ts +0 -6
  647. package/dist/lib/opencode-version.d.ts.map +0 -1
  648. package/dist/lib/opencode-version.js +0 -26
  649. package/dist/lib/opencode-version.js.map +0 -1
  650. package/dist/lib/provider-add-command.d.ts +0 -35
  651. package/dist/lib/provider-add-command.d.ts.map +0 -1
  652. package/dist/lib/provider-add-command.js +0 -204
  653. package/dist/lib/provider-add-command.js.map +0 -1
  654. package/dist/lib/provider-add-json-v1-questionnaire.d.ts +0 -32
  655. package/dist/lib/provider-add-json-v1-questionnaire.d.ts.map +0 -1
  656. package/dist/lib/provider-add-json-v1-questionnaire.js +0 -336
  657. package/dist/lib/provider-add-json-v1-questionnaire.js.map +0 -1
  658. package/dist/lib/provider-add.d.ts +0 -21
  659. package/dist/lib/provider-add.d.ts.map +0 -1
  660. package/dist/lib/provider-add.js +0 -219
  661. package/dist/lib/provider-add.js.map +0 -1
  662. package/dist/lib/quota-accounting-projection.d.ts +0 -9
  663. package/dist/lib/quota-accounting-projection.d.ts.map +0 -1
  664. package/dist/lib/quota-accounting-projection.js +0 -254
  665. package/dist/lib/quota-accounting-projection.js.map +0 -1
  666. package/dist/lib/quota-columns.d.ts +0 -38
  667. package/dist/lib/quota-columns.d.ts.map +0 -1
  668. package/dist/lib/quota-columns.js +0 -129
  669. package/dist/lib/quota-columns.js.map +0 -1
  670. package/dist/lib/quota-command-format.d.ts +0 -34
  671. package/dist/lib/quota-command-format.d.ts.map +0 -1
  672. package/dist/lib/quota-command-format.js +0 -240
  673. package/dist/lib/quota-command-format.js.map +0 -1
  674. package/dist/lib/quota-dialog-command-specs.d.ts +0 -63
  675. package/dist/lib/quota-dialog-command-specs.d.ts.map +0 -1
  676. package/dist/lib/quota-dialog-command-specs.js +0 -145
  677. package/dist/lib/quota-dialog-command-specs.js.map +0 -1
  678. package/dist/lib/quota-entry-display.d.ts +0 -7
  679. package/dist/lib/quota-entry-display.d.ts.map +0 -1
  680. package/dist/lib/quota-entry-display.js +0 -59
  681. package/dist/lib/quota-entry-display.js.map +0 -1
  682. package/dist/lib/quota-exhaustion-projection.d.ts +0 -11
  683. package/dist/lib/quota-exhaustion-projection.d.ts.map +0 -1
  684. package/dist/lib/quota-exhaustion-projection.js +0 -105
  685. package/dist/lib/quota-exhaustion-projection.js.map +0 -1
  686. package/dist/lib/quota-format-style.d.ts +0 -21
  687. package/dist/lib/quota-format-style.d.ts.map +0 -1
  688. package/dist/lib/quota-format-style.js +0 -38
  689. package/dist/lib/quota-format-style.js.map +0 -1
  690. package/dist/lib/quota-render-data.d.ts +0 -102
  691. package/dist/lib/quota-render-data.d.ts.map +0 -1
  692. package/dist/lib/quota-render-data.js +0 -419
  693. package/dist/lib/quota-render-data.js.map +0 -1
  694. package/dist/lib/quota-reset-notifications.d.ts +0 -20
  695. package/dist/lib/quota-reset-notifications.d.ts.map +0 -1
  696. package/dist/lib/quota-reset-notifications.js +0 -331
  697. package/dist/lib/quota-reset-notifications.js.map +0 -1
  698. package/dist/lib/quota-runtime-context.d.ts +0 -54
  699. package/dist/lib/quota-runtime-context.d.ts.map +0 -1
  700. package/dist/lib/quota-runtime-context.js +0 -110
  701. package/dist/lib/quota-runtime-context.js.map +0 -1
  702. package/dist/lib/quota-state-codec.d.ts +0 -16
  703. package/dist/lib/quota-state-codec.d.ts.map +0 -1
  704. package/dist/lib/quota-state-codec.js +0 -412
  705. package/dist/lib/quota-state-codec.js.map +0 -1
  706. package/dist/lib/quota-state.d.ts +0 -28
  707. package/dist/lib/quota-state.d.ts.map +0 -1
  708. package/dist/lib/quota-state.js +0 -473
  709. package/dist/lib/quota-state.js.map +0 -1
  710. package/dist/lib/quota-stats-format.d.ts +0 -29
  711. package/dist/lib/quota-stats-format.d.ts.map +0 -1
  712. package/dist/lib/quota-stats-format.js +0 -459
  713. package/dist/lib/quota-stats-format.js.map +0 -1
  714. package/dist/lib/quota-stats.d.ts +0 -126
  715. package/dist/lib/quota-stats.d.ts.map +0 -1
  716. package/dist/lib/quota-stats.js +0 -695
  717. package/dist/lib/quota-stats.js.map +0 -1
  718. package/dist/lib/quota-surface-data.d.ts +0 -21
  719. package/dist/lib/quota-surface-data.d.ts.map +0 -1
  720. package/dist/lib/quota-surface-data.js +0 -362
  721. package/dist/lib/quota-surface-data.js.map +0 -1
  722. package/dist/lib/scoped-update-migration.d.ts +0 -103
  723. package/dist/lib/scoped-update-migration.d.ts.map +0 -1
  724. package/dist/lib/scoped-update-migration.js +0 -344
  725. package/dist/lib/scoped-update-migration.js.map +0 -1
  726. package/dist/lib/scoped-update.d.ts +0 -77
  727. package/dist/lib/scoped-update.d.ts.map +0 -1
  728. package/dist/lib/scoped-update.js +0 -590
  729. package/dist/lib/scoped-update.js.map +0 -1
  730. package/dist/lib/session-tokens-format.d.ts +0 -36
  731. package/dist/lib/session-tokens-format.d.ts.map +0 -1
  732. package/dist/lib/session-tokens-format.js +0 -111
  733. package/dist/lib/session-tokens-format.js.map +0 -1
  734. package/dist/lib/synthetic-config.d.ts +0 -15
  735. package/dist/lib/synthetic-config.d.ts.map +0 -1
  736. package/dist/lib/synthetic-config.js +0 -28
  737. package/dist/lib/synthetic-config.js.map +0 -1
  738. package/dist/lib/toast-format-grouped.d.ts +0 -25
  739. package/dist/lib/toast-format-grouped.d.ts.map +0 -1
  740. package/dist/lib/toast-format-grouped.js +0 -296
  741. package/dist/lib/toast-format-grouped.js.map +0 -1
  742. package/dist/lib/tui-compact-format.d.ts +0 -10
  743. package/dist/lib/tui-compact-format.d.ts.map +0 -1
  744. package/dist/lib/tui-compact-format.js +0 -460
  745. package/dist/lib/tui-compact-format.js.map +0 -1
  746. package/dist/lib/tui-panel-state.d.ts +0 -15
  747. package/dist/lib/tui-panel-state.d.ts.map +0 -1
  748. package/dist/lib/tui-panel-state.js +0 -2
  749. package/dist/lib/tui-panel-state.js.map +0 -1
  750. package/dist/lib/tui-prompt-bar-format.d.ts +0 -25
  751. package/dist/lib/tui-prompt-bar-format.d.ts.map +0 -1
  752. package/dist/lib/tui-prompt-bar-format.js +0 -206
  753. package/dist/lib/tui-prompt-bar-format.js.map +0 -1
  754. package/dist/lib/tui-sidebar-format.d.ts +0 -13
  755. package/dist/lib/tui-sidebar-format.d.ts.map +0 -1
  756. package/dist/lib/tui-sidebar-format.js +0 -34
  757. package/dist/lib/tui-sidebar-format.js.map +0 -1
  758. package/dist/lib/zai-auth.d.ts +0 -15
  759. package/dist/lib/zai-auth.d.ts.map +0 -1
  760. package/dist/lib/zai-auth.js +0 -37
  761. package/dist/lib/zai-auth.js.map +0 -1
  762. package/dist/lib/zai.d.ts +0 -5
  763. package/dist/lib/zai.d.ts.map +0 -1
  764. package/dist/lib/zai.js +0 -14
  765. package/dist/lib/zai.js.map +0 -1
  766. package/dist/lib/zhipu-auth.d.ts +0 -15
  767. package/dist/lib/zhipu-auth.d.ts.map +0 -1
  768. package/dist/lib/zhipu-auth.js +0 -42
  769. package/dist/lib/zhipu-auth.js.map +0 -1
  770. package/dist/lib/zhipu.d.ts +0 -5
  771. package/dist/lib/zhipu.d.ts.map +0 -1
  772. package/dist/lib/zhipu.js +0 -13
  773. package/dist/lib/zhipu.js.map +0 -1
  774. package/dist/providers/glm-coding-plan-provider.d.ts +0 -56
  775. package/dist/providers/glm-coding-plan-provider.d.ts.map +0 -1
  776. package/dist/providers/glm-coding-plan-provider.js +0 -115
  777. package/dist/providers/glm-coding-plan-provider.js.map +0 -1
  778. package/dist/providers/google-account-format.d.ts +0 -7
  779. package/dist/providers/google-account-format.d.ts.map +0 -1
  780. package/dist/providers/google-account-format.js +0 -52
  781. package/dist/providers/google-account-format.js.map +0 -1
  782. package/dist/providers/zai.d.ts +0 -2
  783. package/dist/providers/zai.d.ts.map +0 -1
  784. package/dist/providers/zai.js +0 -24
  785. package/dist/providers/zai.js.map +0 -1
  786. package/dist/providers/zhipu.d.ts +0 -2
  787. package/dist/providers/zhipu.d.ts.map +0 -1
  788. package/dist/providers/zhipu.js +0 -21
  789. package/dist/providers/zhipu.js.map +0 -1
  790. package/dist/tui-v2.d.ts +0 -5
  791. package/dist/tui-v2.d.ts.map +0 -1
  792. package/dist/tui-v2.jsx +0 -624
  793. package/dist/tui-v2.jsx.map +0 -1
  794. package/dist/tui.d.ts.map +0 -1
@@ -1,21 +1,130 @@
1
- import { stat } from "fs/promises";
2
- import { homedir } from "os";
3
- import { getProviders } from "../providers/registry.js";
4
- import { interpretAccountingRow } from "./accounting-format.js";
5
- import { QUOTA_TOAST_SETTING_SOURCE_KEYS, } from "./config.js";
6
- import { sanitizeQuotaProviderResult, sanitizeSingleLineDisplayText } from "./display-sanitize.js";
7
- import { replaceHomeDirWithTilde, replaceHomeDirWithTildeInDocument } from "./home-dir-display.js";
8
- import { getPricingRefreshPolicy, getPricingSnapshotHealth, getPricingSnapshotMeta, getPricingSnapshotSource, getProviderModelCount, getRuntimePricingRefreshStatePath, getRuntimePricingSnapshotPath, listProviders, readPricingRefreshState, hasProvider as snapshotHasProvider, } from "./modelsdev-pricing.js";
9
- import { getCredentialSourceDiagnostics } from "./opencode-auth.js";
1
+ /**
2
+ * The Quota Status report (`/quota_status`, the `quota_status` tool, `opencode-quota status`):
3
+ * the OpenCode version and computer, where the settings come from and which differ from the
4
+ * defaults, what each known provider returned, logins, local files and prices. Every provider
5
+ * section is built the same way from the store's snapshot (state, rows, error, debug details).
6
+ *
7
+ * Users paste this report into public issues, so it is built from a closed vocabulary. Every
8
+ * value it prints is a member of a set this plugin defines (provider ids, row keys, windows,
9
+ * error codes, states, setting keys, the provider's own env variables, folder and file names)
10
+ * or a number, count or time this file formats in its range. Everything else is counted or
11
+ * stands in as a placeholder: "custom 1", "account 2", "(part 1)", "(custom path)", "(out of
12
+ * range)", "3 other details not shown". A debug detail shows only when its key is in its
13
+ * provider's allow list and its value passes that key's rule.
14
+ */
15
+ import { stat } from "node:fs/promises";
16
+ import { homedir } from "node:os";
17
+ import { classifyError, describeError, errorKind, httpStatus, isQuotaErrorCode, LOCAL_TOOLS, } from "../core/errors.js";
18
+ import { isQuotaWindow } from "../core/windows.js";
19
+ import { isV5SettingKey } from "../settings/convert.js";
20
+ import { defaultSettings, diffSettings, isSettingsGroup, PROVIDER_IDS as SETTINGS_PROVIDER_IDS, SETTINGS_SCHEMA, THEME_COLOR_NAMES, } from "../settings/schema.js";
21
+ import { formatShare, formatTokens } from "../view/format.js";
22
+ import { COUNT_LABELS } from "./accounting-format.js";
23
+ import { ERROR_MESSAGES as ALIBABA_TOKEN_PLAN_ERRORS } from "./alibaba-token-plan.js";
24
+ import { API_KEY_LOGIN_VENDORS, API_KEY_VENDORS } from "./api-key-resolver.js";
25
+ import { COPILOT_AUTH_KEYS } from "./copilot.js";
26
+ import { CURSOR_CANONICAL_PLUGIN_PACKAGE } from "./cursor-detection.js";
27
+ import { sanitizeSingleLineDisplayText } from "./display-sanitize.js";
28
+ import { AGY_AUTH_KEYS } from "./google-agy.js";
29
+ import { GEMINI_CLI_AUTH_KEYS } from "./google-gemini-cli.js";
30
+ import { replaceHomeDirWithTildeInDocument } from "./home-dir-display.js";
31
+ import { getPricingRefreshPolicy, getPricingSnapshotHealth, getPricingSnapshotMeta, getPricingSnapshotSource, getRuntimePricingRefreshStatePath, getRuntimePricingSnapshotPath, isBundledPricingModelId, isBundledPricingProviderId, listProviders, readPricingRefreshState, } from "./modelsdev-pricing.js";
32
+ import { OPENAI_AUTH_SOURCE_KEYS } from "./openai.js";
10
33
  import { getOpenCodeDbPath } from "./opencode-db-path.js";
11
34
  import { getOpencodeRuntimeDirs } from "./opencode-runtime-paths.js";
12
35
  import { getOpenCodeDbStats } from "./opencode-storage.js";
13
- import { getQuotaProviderDisplayLabel } from "./provider-metadata.js";
14
- import { isMaintainedQuotaProviderTuning } from "./quota-providers.js";
15
- import { aggregateUsage } from "./quota-stats.js";
16
- import { commandHeading, renderPlainTextReport, } from "./report-document.js";
17
- import { totalTokenBuckets } from "./token-buckets.js";
36
+ import { MEMBER_BUDGET_NOTE } from "./opencode-zen.js";
37
+ import { getQuotaProviderDisplayLabel, PROVIDER_ROW_KEYS, QUOTA_PROVIDER_CATALOG, QUOTA_PROVIDER_RUNTIME_IDS, } from "./provider-metadata.js";
38
+ import { isMaintainedQuotaProviderTuning, } from "./quota-providers.js";
39
+ import { renderPlainTextReport, } from "./report-document.js";
18
40
  import { getPackageVersion } from "./version.js";
41
+ /** Rows listed per provider section; the rest are counted. */
42
+ const ROW_LIST_LIMIT = 8;
43
+ /** Models without a price listed in the pricing section; the rest are counted. */
44
+ const UNPRICED_LIST_LIMIT = 5;
45
+ /** Shown for a path outside the known folders, or with a part this plugin did not name. */
46
+ const CUSTOM_PATH = "(custom path)";
47
+ /** The quota providers' ids. */
48
+ const PROVIDER_IDS = new Set(Object.keys(QUOTA_PROVIDER_CATALOG));
49
+ /** The provider ids OpenCode uses for them ("github-copilot", "codex", …), and theirs. */
50
+ const OPENCODE_PROVIDER_IDS = new Set([
51
+ ...PROVIDER_IDS,
52
+ ...Object.values(QUOTA_PROVIDER_RUNTIME_IDS).flat(),
53
+ ]);
54
+ const CURRENCIES = new Set(Intl.supportedValuesOf("currency"));
55
+ const CREDENTIAL_SOURCE_KINDS = {
56
+ "opencode-integration-api": true,
57
+ sqlite: true,
58
+ };
59
+ const CREDENTIAL_FAILURE_CATEGORIES = new Set(["refresh_failed", "resolve_empty", "active_failed"]);
60
+ /** Folder and file names this plugin and OpenCode use; a shown path is made only of these. */
61
+ export const KNOWN_PATH_PARTS = new Set([
62
+ ".config",
63
+ ".local",
64
+ "share",
65
+ "state",
66
+ ".cache",
67
+ "opencode",
68
+ "opencode-quota",
69
+ "quota-providers",
70
+ ".claude",
71
+ "local",
72
+ "bin",
73
+ ".cursor",
74
+ "cursor",
75
+ "Library",
76
+ "Application Support",
77
+ "AppData",
78
+ "Roaming",
79
+ "Local",
80
+ ]);
81
+ /** File names this plugin and OpenCode read or write. */
82
+ export const KNOWN_FILE_NAMES = new Set([
83
+ "settings.jsonc",
84
+ "opencode.db",
85
+ "opencode.json",
86
+ "opencode.jsonc",
87
+ "auth.json",
88
+ "cli-config.json",
89
+ ".credentials.json",
90
+ "copilot-quota-token.json",
91
+ "mimo.json",
92
+ "alibaba-coding-plan-local-quota.json",
93
+ "modelsdev-pricing.runtime.min.json",
94
+ "modelsdev-pricing.refresh-state.json",
95
+ "snapshot-v6.json",
96
+ "quota-export.json",
97
+ "google-access-tokens.json",
98
+ "maintainer-announcements-home.json",
99
+ "quota-toast.json",
100
+ "quota-toast.jsonc",
101
+ "claude",
102
+ ]);
103
+ /** Paths outside the home dir this plugin looks at. */
104
+ export const KNOWN_ABSOLUTE_PATHS = new Set([
105
+ "/opt/homebrew/bin/claude",
106
+ "/usr/local/bin/claude",
107
+ ]);
108
+ /** Where an API key came from, besides an env variable or a path. */
109
+ const KEY_FILE_SOURCES = new Set([
110
+ "opencode.json",
111
+ "opencode.jsonc",
112
+ "opencode.db",
113
+ ]);
114
+ /** The providers that read a key or a cookie, by id, and the env variables they read it from. */
115
+ const KEY_VENDORS = {
116
+ ...API_KEY_VENDORS,
117
+ ...API_KEY_LOGIN_VENDORS,
118
+ openrouter: { envVars: ["OPENROUTER_API_KEY"] },
119
+ cursor: { envVars: ["CURSOR_API_KEY"] },
120
+ xiaomi: { envVars: ["MIMO_USAGE_COOKIE"] },
121
+ };
122
+ const ORIGINS = {
123
+ slash_command: "/quota_status",
124
+ tool: "quota_status tool",
125
+ tui: "TUI Plugin tab",
126
+ cli: "opencode-quota status (CLI)",
127
+ };
19
128
  async function pathExists(path) {
20
129
  try {
21
130
  await stat(path);
@@ -26,839 +135,1070 @@ async function pathExists(path) {
26
135
  }
27
136
  }
28
137
  function fmtInt(n) {
29
- return Math.trunc(n).toLocaleString("en-US");
30
- }
31
- const STATUS_SAMPLE_LIMIT = 5;
32
- const STATUS_LIVE_ENTRY_LIMIT = 2;
33
- const STATUS_LIVE_ERROR_LIMIT = 2;
34
- const STATUS_LIVE_ROW_MAX_LENGTH = 120;
35
- const OPENCODE_GO_STATUS_DETAIL_KEYS = new Set([
36
- "auth_state",
37
- "auth_source",
38
- "auth_checked_paths",
39
- "credential_database_paths",
40
- "auth_error",
41
- "selected_windows",
42
- "rolling_usage",
43
- "weekly_usage",
44
- "monthly_usage",
45
- "live_fetch_error",
46
- "opencode_go_state",
47
- "go_source",
48
- "console_error",
49
- "console_auth_state",
50
- "console_server",
51
- ]);
52
- function joinOrNone(values) {
53
- return values.length > 0 ? values.join(" | ") : "(none)";
138
+ return Number.isFinite(n) ? Math.trunc(n).toLocaleString("en-US") : "(none)";
54
139
  }
55
- function formatSettingSources(sources) {
56
- if (!sources)
140
+ /** The times this report prints: from 2000 to 2100. */
141
+ const FIRST_DATE_MS = Date.UTC(2000, 0, 1);
142
+ const LAST_DATE_MS = Date.UTC(2100, 0, 1);
143
+ /** An ISO time from 2000 to 2100, "(none)" for no time, and "(invalid date)" for any other. */
144
+ function isoText(ms) {
145
+ if (typeof ms !== "number" || ms === 0)
57
146
  return "(none)";
58
- const parts = QUOTA_TOAST_SETTING_SOURCE_KEYS.filter((key) => typeof sources[key] === "string" && sources[key].length > 0).map((key) => `${key}<=${sources[key]}`);
59
- return parts.length > 0 ? parts.join(" | ") : "(none)";
60
- }
61
- function getConfigPrecedenceLabel(configSource) {
62
- switch (configSource) {
63
- case "files":
64
- return "global defaults -> workspace overrides";
65
- case "sdk":
66
- return "sdk fallback (no file-backed config)";
67
- case "defaults":
68
- return "built-in defaults only";
69
- default:
70
- return configSource;
71
- }
72
- }
73
- function createKvSection(id, title, rows) {
74
- return {
75
- id,
76
- title,
77
- blocks: [{ kind: "kv", rows }],
78
- };
147
+ return ms >= FIRST_DATE_MS && ms < LAST_DATE_MS ? new Date(ms).toISOString() : "(invalid date)";
79
148
  }
80
- function createLinesSection(id, title, lines) {
81
- return {
82
- id,
83
- title,
84
- blocks: [{ kind: "lines", lines }],
85
- };
149
+ /** An ISO date text ("2026-10-11T00:00:00Z") as `isoText` shows it; other text is no date. */
150
+ function dateText(text) {
151
+ return /^\d{4}-\d{2}-\d{2}/u.test(text) ? isoText(Date.parse(text)) : "(invalid date)";
86
152
  }
87
- function normalizeLiveProbeText(value) {
88
- return sanitizeSingleLineDisplayText(value).replace(/:+$/u, "").toLowerCase();
153
+ /** A whole number from `min` to `max`, else "(out of range)": a long number is no count. */
154
+ function inRange(value, min, max) {
155
+ return Number.isInteger(value) && value >= min && value <= max ? String(value) : "(out of range)";
89
156
  }
90
- function isRedundantLiveProbeDescriptor(providerId, value) {
91
- if (!value)
92
- return true;
93
- const normalized = normalizeLiveProbeText(value);
94
- if (!normalized)
95
- return true;
96
- return (normalized === normalizeLiveProbeText(providerId) ||
97
- normalized === normalizeLiveProbeText(getQuotaProviderDisplayLabel(providerId)));
157
+ const DAY_MS = 24 * 60 * 60 * 1000;
158
+ /** The most days `providers.pricing.autoRefreshDays` shows: a longer wait is no setting. */
159
+ const MAX_REFRESH_DAYS = 9999;
160
+ /** A time span in ms up to `MAX_REFRESH_DAYS` days, else "(out of range)". */
161
+ function msText(ms) {
162
+ return Number.isFinite(ms) && ms >= 0 && ms <= MAX_REFRESH_DAYS * DAY_MS
163
+ ? fmtInt(ms)
164
+ : "(out of range)";
98
165
  }
99
- function findProviderLiveProbe(providerId, probes) {
100
- return probes?.find((probe) => probe.providerId === providerId);
166
+ /** "2.1.3": the first three numbers of a version, each up to 999; undefined for no version. */
167
+ function versionText(value) {
168
+ const match = /^(\d+)\.(\d+)\.(\d+)/u.exec(value);
169
+ if (!match)
170
+ return undefined;
171
+ const parts = match.slice(1, 4).map(Number);
172
+ return parts.every((part) => part <= 999) ? parts.join(".") : "(out of range)";
101
173
  }
102
- function getProviderStatusDetails(providerId, probes) {
103
- const probe = findProviderLiveProbe(providerId, probes);
104
- return probe ? sanitizeQuotaProviderResult(probe.result).statusDetails : undefined;
174
+ function joinOrNone(values) {
175
+ return values.length > 0 ? values.join(" | ") : "(none)";
105
176
  }
106
- function appendProviderStatusDetailRows(rows, providerId, probes, keys) {
107
- for (const detail of getProviderStatusDetails(providerId, probes) ?? []) {
108
- if (!keys || keys.has(detail.key)) {
109
- rows.push({ key: detail.key, value: detail.value });
110
- }
111
- }
177
+ function kvSection(id, rows) {
178
+ return { id, title: `${id}:`, blocks: [{ kind: "kv", rows }] };
112
179
  }
113
- function findProviderStatusDetail(providerId, key, probes) {
114
- return getProviderStatusDetails(providerId, probes)?.find((detail) => detail.key === key)?.value;
180
+ /** "1 other detail not shown", "3 other details not shown". */
181
+ function countText(count, one, many) {
182
+ return `${count} ${count === 1 ? one : many}`;
115
183
  }
116
- function getLiveProbeState(probe) {
117
- if (!probe)
118
- return "unavailable";
119
- if (probe.result.entries.length > 0 && probe.result.errors.length > 0)
120
- return "partial";
121
- if (probe.result.entries.length > 0)
122
- return "success";
123
- if (probe.result.errors.length > 0)
124
- return "error";
125
- return "no_data";
126
- }
127
- function appendProviderCompactLiveProbeRows(rows, providerId, probes, availability, homeDir) {
128
- const provider = availability.find((item) => item.id === providerId);
129
- if (!provider?.enabled || !provider.available)
130
- return;
131
- appendCompactLiveProbeRows(rows, providerId, findProviderLiveProbe(providerId, probes), homeDir);
132
- }
133
- function createCompactLiveProbeOnlySection(params) {
134
- const provider = params.availability.find((item) => item.id === params.providerId);
135
- if (!provider?.enabled || !provider.available)
136
- return null;
137
- const probe = findProviderLiveProbe(params.providerId, params.probes);
138
- if (!probe) {
139
- return null;
140
- }
141
- const rows = [];
142
- appendCompactLiveProbeRows(rows, params.providerId, probe, params.homeDir);
143
- return createKvSection(params.id, params.title, rows);
184
+ /** Hands out 1, 2, 3, … per distinct value, in the order values are first seen. */
185
+ function numbering() {
186
+ const seen = [];
187
+ return (value) => {
188
+ if (!seen.includes(value))
189
+ seen.push(value);
190
+ return seen.indexOf(value) + 1;
191
+ };
144
192
  }
145
- function createProviderStatusSection(params) {
146
- const rows = [];
147
- if (params.includeDetails !== false) {
148
- appendProviderStatusDetailRows(rows, params.providerId, params.probes, params.detailKeys);
149
- }
150
- appendProviderCompactLiveProbeRows(rows, params.providerId, params.probes, params.availability, params.homeDir);
151
- return createKvSection(params.id, params.title, rows);
193
+ /** The 1-based place of each custom definition id, as the settings section lists them. */
194
+ function customNumbers(params) {
195
+ return new Map((params.quotaProviders ?? []).map((definition, index) => [definition.id, index + 1]));
152
196
  }
153
- function getCompactLiveProbeDescriptor(providerId, entry, semanticLabel) {
154
- if (entry.semantic) {
155
- return sanitizeSingleLineDisplayText(semanticLabel);
156
- }
157
- const candidates = [entry.label, entry.name, entry.group];
158
- for (const candidate of candidates) {
159
- if (typeof candidate !== "string")
160
- continue;
161
- const cleaned = sanitizeSingleLineDisplayText(candidate);
162
- if (!cleaned || isRedundantLiveProbeDescriptor(providerId, cleaned)) {
197
+ /** The path's parts after the home dir ("~" or `homeDir`), or undefined outside it. */
198
+ function partsUnderHome(path, homeDir) {
199
+ for (const home of ["~", homeDir]) {
200
+ if (!home)
163
201
  continue;
202
+ if (path === home)
203
+ return [];
204
+ const separator = path.charAt(home.length);
205
+ if (path.startsWith(home) && (separator === "/" || separator === "\\")) {
206
+ return path.slice(home.length + 1).split(/[/\\]/u);
164
207
  }
165
- return cleaned;
166
208
  }
167
209
  return undefined;
168
210
  }
169
211
  /**
170
- * One live probe row, cut to its length limit. The home dir becomes "~" before the cut, so a
171
- * cut inside a home path cannot leave part of the user name (e.g. "/Users/ali").
212
+ * A path made only of known parts: under the home dir, every folder and file name one this
213
+ * plugin or OpenCode uses ("~/.config/opencode/opencode-quota/settings.jsonc"), or a known
214
+ * absolute path. Any other path, such as one from an env variable or the settings, shows as
215
+ * "(custom path)", with its file name when that is a known one.
172
216
  */
173
- function shortenLiveRowText(text, homeDir) {
174
- return replaceHomeDirWithTilde(sanitizeSingleLineDisplayText(text), homeDir).slice(0, STATUS_LIVE_ROW_MAX_LENGTH);
217
+ function pathText(raw, homeDir) {
218
+ const path = sanitizeSingleLineDisplayText(raw);
219
+ if (KNOWN_ABSOLUTE_PATHS.has(path))
220
+ return path;
221
+ const parts = partsUnderHome(path, sanitizeSingleLineDisplayText(homeDir));
222
+ const known = (part) => KNOWN_PATH_PARTS.has(part) || KNOWN_FILE_NAMES.has(part);
223
+ if (parts?.every(known))
224
+ return ["~", ...parts].join("/");
225
+ const fileName = path.split(/[/\\]/u).at(-1) ?? "";
226
+ return KNOWN_FILE_NAMES.has(fileName) ? `${CUSTOM_PATH}/${fileName}` : CUSTOM_PATH;
175
227
  }
176
- function formatCompactLiveProbeEntry(providerId, entry, homeDir) {
177
- const interpretation = interpretAccountingRow(entry, { booleanWording: "semantic" });
178
- const parts = [];
179
- const descriptor = getCompactLiveProbeDescriptor(providerId, entry, interpretation.label);
180
- if (descriptor) {
181
- parts.push(descriptor);
182
- }
183
- if (interpretation.display.kind === "value") {
184
- const value = interpretation.display.entryKind === "value"
185
- ? sanitizeSingleLineDisplayText(interpretation.display.text)
186
- : interpretation.display.text;
187
- parts.push(`value=${value}`);
188
- }
189
- else {
190
- if (entry.right) {
191
- parts.push(sanitizeSingleLineDisplayText(entry.right));
192
- }
193
- const percentRemaining = Number.isFinite(interpretation.display.percentRemaining)
194
- ? Math.max(0, Math.min(100, Math.round(interpretation.display.percentRemaining)))
195
- : 0;
196
- parts.push(`percent_remaining=${percentRemaining}`);
228
+ /** A path with the "(experimental.quotaToast)" note v5 conversion adds to a source. */
229
+ function sourceFileText(source, homeDir) {
230
+ const note = " (experimental.quotaToast)";
231
+ return source.endsWith(note)
232
+ ? `${pathText(source.slice(0, -note.length), homeDir)}${note}`
233
+ : pathText(source, homeDir);
234
+ }
235
+ /**
236
+ * The session's model as "openai/gpt-5": ids OpenCode or the shipped price list knows, else a
237
+ * placeholder. A downloaded price list is a file on this machine, so its ids are not trusted.
238
+ */
239
+ function currentModelText(params) {
240
+ const { currentModel, currentProviderID } = params;
241
+ if (currentModel) {
242
+ const provider = currentProviderID === undefined
243
+ ? "(unknown provider)"
244
+ : OPENCODE_PROVIDER_IDS.has(currentProviderID) ||
245
+ isBundledPricingProviderId(currentProviderID)
246
+ ? currentProviderID
247
+ : "(other provider)";
248
+ const model = isBundledPricingModelId(currentModel)
249
+ ? currentModel
250
+ : "(model not in the shipped price list)";
251
+ return `${provider}/${model}`;
197
252
  }
198
- if (entry.resetTimeIso) {
199
- parts.push(`reset_at=${sanitizeSingleLineDisplayText(entry.resetTimeIso)}`);
253
+ if (params.sessionModelLookup === "not_found")
254
+ return "(the session has no model yet)";
255
+ if (params.sessionModelLookup === "no_session")
256
+ return "(no session)";
257
+ return "(unknown)";
258
+ }
259
+ /** Known provider ids joined, and how many others there were. */
260
+ function providerIdsText(ids) {
261
+ const known = ids.filter((id) => SETTINGS_PROVIDER_IDS.includes(id));
262
+ const others = ids.length - known.length;
263
+ const text = joinOrNone(known);
264
+ return others > 0 ? `${text} (+${countText(others, "unknown id", "unknown ids")})` : text;
265
+ }
266
+ /**
267
+ * A custom provider definition as its number, mode and model count: "custom 1 (remote-api,
268
+ * 3 models)". Its id, provider id, URL, key, env variable and model ids are never shown. A
269
+ * definition that tunes a maintained provider names that provider. A switched-off definition
270
+ * says so and where to switch it on.
271
+ */
272
+ function customProviderText(definition, number) {
273
+ if (definition.enabled === false) {
274
+ return `custom ${number} off (switch on in /quota_settings → Providers)`;
200
275
  }
201
- return shortenLiveRowText(parts.join(" "), homeDir);
202
- }
203
- function formatCompactLiveProbeError(providerId, error, homeDir) {
204
- const label = isRedundantLiveProbeDescriptor(providerId, error.label)
205
- ? ""
206
- : sanitizeSingleLineDisplayText(error.label);
207
- const message = sanitizeSingleLineDisplayText(error.message);
208
- return shortenLiveRowText(label ? `${label}: ${message}` : message, homeDir);
209
- }
210
- function getQuotaProviderCredentialCategory(source) {
211
- switch (source) {
212
- case "explicit_env":
213
- return "environment";
214
- case "global_opencode_json":
215
- case "global_opencode_jsonc":
216
- return "trusted_global_config";
217
- case "opencode_db":
218
- return "opencode_db";
219
- default:
220
- return "none";
276
+ const mode = definition.mode === "remote-api" ? "remote-api" : "local-estimate";
277
+ const models = definition.modelIds
278
+ ? countText(definition.modelIds.length, "model", "models")
279
+ : "all models";
280
+ const tunes = isMaintainedQuotaProviderTuning(definition) && PROVIDER_IDS.has(definition.id)
281
+ ? `, tunes ${definition.id}`
282
+ : "";
283
+ return `custom ${number} (${mode}, ${models}${tunes})`;
284
+ }
285
+ function settingsFileText(params, homeDir) {
286
+ const { exists } = params.settings;
287
+ const path = pathText(params.settings.path, homeDir);
288
+ if (exists)
289
+ return `${path} (exists)`;
290
+ if (params.conversion)
291
+ return `${path} (missing; the v5 settings are converted in memory)`;
292
+ return `${path} (missing; using the defaults)`;
293
+ }
294
+ /** Our words for a settings issue, chosen by its message; never the value it names. */
295
+ const ISSUE_CODES = [
296
+ [/^expected one JSON object/u, "not one JSON object; using the defaults"],
297
+ [/^expected /u, "invalid value"],
298
+ [/^unknown provider id/u, "unknown provider id"],
299
+ [/^duplicate key/u, "duplicate key"],
300
+ [/^cannot read it/u, "cannot read the file; using the defaults"],
301
+ [/^file-only/u, "file-only setting"],
302
+ ];
303
+ function issueCodeText(message) {
304
+ const line = /^syntax error on line (\d+)/u.exec(message)?.[1];
305
+ if (line !== undefined)
306
+ return `syntax error on line ${inRange(Number(line), 1, 9999)}`;
307
+ return codeOf(ISSUE_CODES, message) ?? "invalid";
308
+ }
309
+ /** The code of the first pattern the text matches. */
310
+ function codeOf(codes, text) {
311
+ return codes.find(([pattern]) => pattern.test(text))?.[1];
312
+ }
313
+ /** Our code for each v5 conversion note, chosen by its words; never the paths it names. */
314
+ const CONVERSION_NOTE_CODES = [
315
+ [/ does not parse as a JSON object, so it was not converted$/u, "file_not_a_json_object"],
316
+ [/ was not converted: v5 used .+ when both exist$/su, "jsonc_used_over_json"],
317
+ [/^experimental\.quotaToast in .+ v5 ignored it while /su, "quota_toast_block_ignored"],
318
+ [/ does not parse, so its experimental\.quotaToast was not converted$/u, "config_not_parsed"],
319
+ [/^the old settings in .+ were not converted: .+ has some too$/su, "older_folder_not_converted"],
320
+ ];
321
+ /** A settings key from the schema, a group, a file-level key, or the custom providers. */
322
+ function issueKeyText(key) {
323
+ if (Object.hasOwn(SETTINGS_SCHEMA, key) || isSettingsGroup(key))
324
+ return key;
325
+ if (key === "$file" || key === "$patch" || key === "$conversion")
326
+ return key;
327
+ if (key.startsWith("providers.custom[") || key.startsWith("providers.custom.")) {
328
+ return "providers.custom";
221
329
  }
330
+ return undefined;
222
331
  }
223
- function createQuotaProvidersSection(params) {
224
- if (params.definitions.length === 0)
225
- return null;
226
- const diagnostics = findProviderLiveProbe("quota-providers", params.probes)?.result.diagnostics ?? [];
227
- const diagnosticsBySource = new Map(diagnostics.map((diagnostic) => [diagnostic.sourceId, diagnostic]));
332
+ /** Each issue as our key and words; unknown keys are counted. */
333
+ function settingsIssueRows(issues) {
228
334
  const rows = [];
229
- for (const definition of params.definitions) {
230
- const diagnostic = diagnosticsBySource.get(definition.id);
231
- const coverage = definition.modelIds ? definition.modelIds.join(",") : "all_models";
232
- if (definition.mode === "local-estimate" && isMaintainedQuotaProviderTuning(definition)) {
233
- const probe = findProviderLiveProbe(definition.id, params.probes);
234
- const statePath = findProviderStatusDetail(definition.id, "local_state_path", params.probes);
235
- const stateExists = findProviderStatusDetail(definition.id, "local_state_exists", params.probes);
236
- const stateHealth = findProviderStatusDetail(definition.id, "local_state_health", params.probes);
237
- const stateVersion = findProviderStatusDetail(definition.id, "local_state_version", params.probes);
238
- const stateLastUpdate = findProviderStatusDetail(definition.id, "local_state_last_update", params.probes);
239
- rows.push({
240
- key: `provider_${definition.id}`,
241
- value: [
242
- `provider_id=${definition.providerId}`,
243
- "mode=local-estimate",
244
- `coverage=${coverage}`,
245
- `outcome=${getLiveProbeState(probe)}`,
246
- `limits=${definition.windows.map((window) => `${window.id}:${window.requestLimit}`).join(",")}`,
247
- `state_path=${statePath ?? "(none)"}`,
248
- `state_exists=${stateExists ?? "false"}`,
249
- `state_health=${stateHealth ?? "missing"}`,
250
- `state_version=${stateVersion ?? "(none)"}`,
251
- `state_last_update=${stateLastUpdate ?? "(none)"}`,
252
- ]
253
- .map((part) => sanitizeSingleLineDisplayText(part))
254
- .join(" "),
255
- });
256
- continue;
257
- }
258
- if (definition.mode === "local-estimate") {
259
- const stateHealth = diagnostic?.stateHealth ?? "missing";
260
- rows.push({
261
- key: `provider_${definition.id}`,
262
- value: [
263
- `provider_id=${definition.providerId}`,
264
- "mode=local-estimate",
265
- `coverage=${coverage}`,
266
- `outcome=${diagnostic?.outcome ?? "unavailable"}`,
267
- `state_path=${diagnostic?.statePath ?? "(none)"}`,
268
- `state_exists=${diagnostic?.statePath && stateHealth !== "missing" ? "true" : "false"}`,
269
- `state_health=${stateHealth}`,
270
- `state_version=${diagnostic?.stateVersion ?? "(none)"}`,
271
- `state_last_update=${diagnostic?.stateLastUpdatedAt == null ? "(none)" : new Date(diagnostic.stateLastUpdatedAt).toISOString()}`,
272
- ]
273
- .map((part) => sanitizeSingleLineDisplayText(part))
274
- .join(" "),
275
- });
335
+ let unknownKeys = 0;
336
+ for (const issue of issues) {
337
+ const key = issueKeyText(issue.key);
338
+ if (key === undefined || issue.message.startsWith("unknown setting")) {
339
+ unknownKeys += 1;
276
340
  continue;
277
341
  }
278
- const checkedPaths = diagnostic
279
- ? [...new Set([...diagnostic.checkedPaths, ...diagnostic.credentialDatabasePaths])]
280
- : [];
281
- rows.push({
282
- key: `provider_${definition.id}`,
283
- value: [
284
- `provider_id=${definition.providerId}`,
285
- "mode=remote-api",
286
- `format=${definition.format}`,
287
- `coverage=${coverage}`,
288
- `outcome=${diagnostic?.outcome ?? "unavailable"}`,
289
- `credential_category=${getQuotaProviderCredentialCategory(diagnostic?.credentialSource ?? null)}`,
290
- `env_name=${definition.apiKeyEnv ?? "(none)"}`,
291
- `checked_paths=${checkedPaths.length > 0 ? checkedPaths.join(" | ") : "(none)"}`,
292
- ]
293
- .map((part) => sanitizeSingleLineDisplayText(part))
294
- .join(" "),
295
- });
296
- }
297
- return createKvSection("quota_providers", "quota_providers:", rows);
298
- }
299
- function appendCompactLiveProbeRows(rows, providerId, probe, homeDir) {
300
- if (!probe)
301
- return;
302
- const result = sanitizeQuotaProviderResult(probe.result);
303
- const entryCount = Math.min(result.entries.length, STATUS_LIVE_ENTRY_LIMIT);
304
- const errorCount = Math.min(result.errors.length, STATUS_LIVE_ERROR_LIMIT);
305
- const state = result.entries.length > 0 && result.errors.length > 0
306
- ? "partial"
307
- : result.entries.length > 0
308
- ? "success"
309
- : result.errors.length > 0
310
- ? "error"
311
- : "no_data";
312
- rows.push({ key: "live_probe", value: state });
313
- for (let index = 0; index < entryCount; index += 1) {
314
- rows.push({
315
- key: `live_entry_${index + 1}`,
316
- value: formatCompactLiveProbeEntry(providerId, result.entries[index], homeDir),
317
- });
342
+ rows.push({ key: "settings_issue", value: `${key}: ${issueCodeText(issue.message)}` });
318
343
  }
319
- for (let index = 0; index < errorCount; index += 1) {
344
+ if (unknownKeys > 0) {
320
345
  rows.push({
321
- key: `live_error_${index + 1}`,
322
- value: formatCompactLiveProbeError(providerId, result.errors[index], homeDir),
323
- });
324
- }
325
- const suppressedCount = Math.max(0, result.entries.length - entryCount) +
326
- Math.max(0, result.errors.length - errorCount);
327
- if (suppressedCount > 0) {
328
- rows.push({
329
- key: "live_more",
330
- value: `+${suppressedCount} additional rows suppressed`,
346
+ key: "settings_issue",
347
+ value: countText(unknownKeys, "unknown key", "unknown keys"),
331
348
  });
332
349
  }
350
+ return rows;
333
351
  }
334
- function computePricingCoverageFromAgg(agg) {
335
- const byProvider = new Map();
336
- let pricedKeysSeen = 0;
337
- let mappedMissingKeysSeen = 0;
338
- let unpricedKeysSeen = 0;
339
- // Priced keys seen in history
340
- for (const row of agg.byModel) {
341
- const p = row.key.provider;
342
- const existing = byProvider.get(p) ?? {
343
- pricedKeysSeen: 0,
344
- mappedMissingKeysSeen: 0,
345
- unpricedKeysSeen: 0,
346
- };
347
- existing.pricedKeysSeen += 1;
348
- byProvider.set(p, existing);
349
- pricedKeysSeen += 1;
350
- }
351
- // Keys that mapped to an official provider/model but were missing pricing
352
- for (const row of agg.unknown) {
353
- const p = row.key.mappedProvider;
354
- if (!p || !row.key.mappedModel)
355
- continue;
356
- const existing = byProvider.get(p) ?? {
357
- pricedKeysSeen: 0,
358
- mappedMissingKeysSeen: 0,
359
- unpricedKeysSeen: 0,
360
- };
361
- existing.mappedMissingKeysSeen += 1;
362
- byProvider.set(p, existing);
363
- mappedMissingKeysSeen += 1;
364
- }
365
- // Mapped keys that we explicitly consider unpriced
366
- for (const row of agg.unpriced) {
367
- const p = row.key.mappedProvider;
368
- const existing = byProvider.get(p) ?? {
369
- pricedKeysSeen: 0,
370
- mappedMissingKeysSeen: 0,
371
- unpricedKeysSeen: 0,
372
- };
373
- existing.unpricedKeysSeen += 1;
374
- byProvider.set(p, existing);
375
- unpricedKeysSeen += 1;
376
- }
377
- return { byProvider, totals: { pricedKeysSeen, mappedMissingKeysSeen, unpricedKeysSeen } };
378
- }
379
- function supportedProviderPricingRow(params) {
380
- const id = params.id;
381
- if (id === "synthetic") {
382
- return {
383
- id,
384
- pricing: "no",
385
- notes: "subscription request quota (not token-priced)",
386
- };
387
- }
388
- if (id === "alibaba-coding-plan") {
389
- return {
390
- id,
391
- pricing: "no",
392
- notes: "local request-count estimate (tiered rolling windows, no token pricing API)",
393
- };
394
- }
395
- if (id === "alibaba-token-plan") {
396
- return {
397
- id,
398
- pricing: "no",
399
- notes: "official Alibaba Cloud CLI Personal Token Plan quota (not token-priced)",
400
- };
401
- }
402
- if (id === "cursor") {
403
- return {
404
- id,
405
- pricing: "partial",
406
- notes: "API-pool models map to official pricing; Auto/Composer use bundled static Cursor rates",
407
- };
408
- }
409
- if (id === "nanogpt") {
410
- return {
411
- id,
412
- pricing: "no",
413
- notes: "subscription request quota + account balance (not token-priced)",
414
- };
415
- }
416
- if (id === "deepseek") {
417
- return {
418
- id,
419
- pricing: "no",
420
- notes: "account balance only (not token-priced)",
421
- };
422
- }
423
- if (id === "opencode-go") {
424
- return {
425
- id,
426
- pricing: "no",
427
- notes: "subscription percentage quota from the OpenCode Go usage API (not token-priced)",
428
- };
429
- }
430
- if (id === "xiaomi") {
431
- return {
432
- id,
433
- pricing: "no",
434
- notes: "dashboard monthly token quota and balances; per-key costs unsupported",
435
- };
436
- }
437
- if (id === "kimi-code-plan-global" ||
438
- id === "kimi-code-plan-cn" ||
439
- id === "kimi-for-coding" ||
440
- id === "kimi-code" ||
441
- id === "kimi") {
442
- return {
443
- id,
444
- pricing: "no",
445
- notes: "request quota via Kimi Code API (not token-priced)",
446
- };
447
- }
448
- // Providers that correspond directly to models.dev providers.
449
- if (params.snapshotProviders.includes(id)) {
450
- return { id, pricing: "yes", notes: "models.dev snapshot provider" };
451
- }
452
- // Connector to snapshot provider; treat as priced if snapshot has OpenAI pricing.
453
- // Copilot is an OpenCode provider but token costs still map into official model pricing.
454
- if (id === "copilot") {
455
- return snapshotHasProvider("openai")
456
- ? { id, pricing: "yes", notes: "connector (priced via models.dev openai)" }
457
- : { id, pricing: "partial", notes: "connector (pricing snapshot missing openai)" };
458
- }
459
- if (id === "google-gemini-cli") {
460
- return snapshotHasProvider("google")
461
- ? { id, pricing: "yes", notes: "connector (priced via models.dev google)" }
462
- : { id, pricing: "partial", notes: "connector (pricing snapshot missing google)" };
352
+ /** v5 keys joined, and how many unknown keys there were: "showOnIdle, debug (+1 unknown key)". */
353
+ function v5KeysText(keys) {
354
+ // "quotaProviders[0].url" is a problem inside the v5 key "quotaProviders".
355
+ const bases = keys.map((key) => key.replace(/\[.*$/su, ""));
356
+ const known = [...new Set(bases.filter(isV5SettingKey))];
357
+ const others = bases.filter((key) => !isV5SettingKey(key)).length;
358
+ const text = known.join(", ");
359
+ if (others === 0)
360
+ return text;
361
+ const count = countText(others, "unknown key", "unknown keys");
362
+ return text ? `${text} (+${count})` : count;
363
+ }
364
+ /** Where the v5 settings went: written to settings.jsonc, or not. Only v5 keys are named. */
365
+ function conversionRows(conversion, homeDir) {
366
+ const { converted, dropped, invalid } = conversion;
367
+ const sources = conversion.sources.map((source) => sourceFileText(source, homeDir));
368
+ const counts = `${converted.length} converted, ${dropped.length} dropped, ${invalid.length} invalid`;
369
+ const outcome = conversion.written
370
+ ? "written to settings.jsonc"
371
+ : conversion.notWrittenReason !== undefined
372
+ ? "not written"
373
+ : "converted in memory";
374
+ const rows = [
375
+ { key: "conversion", value: `from ${joinOrNone(sources)} (${counts}); ${outcome}` },
376
+ ];
377
+ const lists = [
378
+ ["conversion_converted", converted],
379
+ ["conversion_dropped", dropped],
380
+ ["conversion_invalid", invalid],
381
+ ];
382
+ for (const [key, items] of lists) {
383
+ if (items.length > 0)
384
+ rows.push({ key, value: v5KeysText(items.map((item) => item.key)) });
463
385
  }
464
- // Connector providers: pricing exists when model IDs can be mapped into snapshot pricing keys.
465
- // Use local history as the source of truth.
466
- const hasAnyUsage = params.agg.bySourceProvider.some((p) => p.providerID === id);
467
- const hasAnyUnknown = params.agg.unknown.some((u) => u.key.sourceProviderID === id);
468
- // Note: agg.byModel is already mapped to official pricing keys, not source provider IDs.
469
- // So for connector providers we infer pricing availability based on whether we saw usage at all
470
- // and whether it was mappable.
471
- if (!hasAnyUsage && !hasAnyUnknown) {
472
- return { id, pricing: "no", notes: "no local usage observed" };
386
+ if (conversion.notes.length > 0) {
387
+ const codes = conversion.notes.map((note) => codeOf(CONVERSION_NOTE_CODES, note) ?? "(other)");
388
+ rows.push({ key: "conversion_notes", value: codes.join(", ") });
473
389
  }
474
- if (hasAnyUnknown) {
475
- return {
476
- id,
477
- pricing: "partial",
478
- notes: "some models not in snapshot (see unpriced_models / unknown_pricing)",
479
- };
390
+ return rows;
391
+ }
392
+ /** Settings with rows of their own (the off list, custom definitions) or no setting (version). */
393
+ const OWN_ROW_KEYS = new Set(["version", "providers.off", "providers.custom"]);
394
+ /** The separators the Settings tab steps through. */
395
+ const SEPARATORS = new Set([" · ", " | ", " / ", " "]);
396
+ /**
397
+ * A changed setting's value in this report's words: a switch, choice, number or color as it is,
398
+ * a list or map as its count, a path as a known path; never a name or text the user typed.
399
+ */
400
+ function settingValueText(rule, value, homeDir) {
401
+ if (value === null)
402
+ return "null";
403
+ switch (rule.kind) {
404
+ case "boolean":
405
+ return value === true ? "true" : "false";
406
+ case "choice":
407
+ return rule.values.includes(String(value)) ? String(value) : "(other)";
408
+ case "number": {
409
+ if (rule.word !== undefined && value === rule.word)
410
+ return rule.word;
411
+ const rounded = typeof value === "number" ? Number(value.toFixed(2)) : Number.NaN;
412
+ return Number.isFinite(rounded) && Math.abs(rounded) < 1e6
413
+ ? String(rounded)
414
+ : "(out of range)";
415
+ }
416
+ case "path":
417
+ return typeof value === "string" ? pathText(value, homeDir) : CUSTOM_PATH;
418
+ case "color":
419
+ if (typeof value === "string" && THEME_COLOR_NAMES.includes(value))
420
+ return value;
421
+ return typeof value === "string" && /^#[0-9a-f]{6}$/iu.test(value)
422
+ ? "(custom color)"
423
+ : "(color)";
424
+ case "separator":
425
+ return typeof value === "string" && SEPARATORS.has(value)
426
+ ? JSON.stringify(value)
427
+ : "(custom separator)";
428
+ default: {
429
+ const count = Array.isArray(value)
430
+ ? value.length
431
+ : typeof value === "object"
432
+ ? Object.keys(value ?? {}).length
433
+ : 1;
434
+ return countText(count, "entry", "entries");
435
+ }
480
436
  }
481
- return {
482
- id,
483
- pricing: "yes",
484
- notes: "model IDs map into snapshot pricing",
485
- };
486
437
  }
487
- export async function buildQuotaStatusReport(params) {
488
- return renderPlainTextReport(await buildQuotaStatusReportDocument(params));
438
+ /** Every setting that differs from its default, by its settings.jsonc key, under a count. */
439
+ function changedSettingRows(settings, homeDir) {
440
+ const changes = diffSettings(defaultSettings(), settings).filter((change) => !OWN_ROW_KEYS.has(change.key));
441
+ const rules = SETTINGS_SCHEMA;
442
+ return [
443
+ {
444
+ key: "changed_from_defaults",
445
+ value: changes.length > 0 ? countText(changes.length, "setting", "settings") : "(none)",
446
+ },
447
+ ...changes.flatMap((change) => {
448
+ const rule = rules[change.key];
449
+ if (!rule)
450
+ return [];
451
+ return [{ key: change.key, value: settingValueText(rule, change.after, homeDir), indent: 1 }];
452
+ }),
453
+ ];
489
454
  }
490
- export async function buildQuotaStatusReportDocument(params) {
491
- const homeDir = params.homeDir ?? homedir();
492
- const version = await getPackageVersion();
493
- const v = version ?? "unknown";
494
- const modelDisplay = params.currentModel
495
- ? params.currentModel
496
- : params.sessionModelLookup === "not_found"
497
- ? "(error: session.get returned no modelID)"
498
- : params.sessionModelLookup === "no_session"
499
- ? "(no session available)"
500
- : "(unknown)";
501
- const sections = [];
502
- // === toast diagnostics ===
503
- const toastLines = [
504
- `- configSource: ${params.configSource}`,
505
- `- configPaths: ${joinOrNone(params.configPaths)}`,
506
- `- precedence: ${getConfigPrecedenceLabel(params.configSource)}`,
507
- `- global_config_paths: ${joinOrNone(params.globalConfigPaths ?? [])}`,
508
- `- workspace_config_paths: ${joinOrNone(params.workspaceConfigPaths ?? [])}`,
509
- `- setting_sources: ${formatSettingSources(params.settingSources)}`,
510
- `- enabledProviders: ${params.enabledProviders === "auto" ? "(auto)" : params.enabledProviders.length ? params.enabledProviders.join(",") : "(none)"}`,
511
- `- onlyCurrentModel: ${params.onlyCurrentModel ? "true" : "false"}`,
512
- `- currentModel: ${modelDisplay}`,
455
+ /** OpenCode release channels this report names; a build's own channel name is not shown. */
456
+ const OPENCODE_CHANNELS = new Set([
457
+ "latest",
458
+ "beta",
459
+ "next",
460
+ "nightly",
461
+ "preview",
462
+ "canary",
463
+ "dev",
464
+ "local",
465
+ "snapshot",
466
+ ]);
467
+ /**
468
+ * When and where the report was made, and what it ran on: OpenCode's version and release
469
+ * channel, the system and CPU, and the JavaScript runtime.
470
+ */
471
+ function environmentSection(params, nowMs) {
472
+ const opencode = params.opencode
473
+ ? `${versionText(params.opencode.version) ?? "(unknown version)"} (${OPENCODE_CHANNELS.has(params.opencode.channel) ? params.opencode.channel : "other channel"})`
474
+ : "(unknown)";
475
+ const bun = process.versions.bun;
476
+ const runtime = bun
477
+ ? `bun ${versionText(bun) ?? "(unknown version)"}`
478
+ : `node ${versionText(process.versions.node) ?? "(unknown version)"}`;
479
+ return kvSection("environment", [
480
+ { key: "generated_at", value: isoText(nowMs) },
481
+ { key: "origin", value: ORIGINS[params.origin] },
482
+ { key: "opencode", value: opencode },
483
+ { key: "os", value: `${process.platform} ${process.arch}` },
484
+ { key: "runtime", value: runtime },
485
+ ]);
486
+ }
487
+ /** The settings file, what is wrong in it, the v5 conversion, and what quota is set to do. */
488
+ function settingsSection(params, homeDir) {
489
+ const rows = [
490
+ { key: "settings_file", value: settingsFileText(params, homeDir) },
491
+ ...settingsIssueRows(params.settings.issues),
513
492
  ];
514
- if (params.configIssues?.length) {
515
- toastLines.push("- config_errors:");
516
- for (const issue of params.configIssues) {
517
- toastLines.push(` - ${sanitizeSingleLineDisplayText(issue.path)} ${sanitizeSingleLineDisplayText(issue.key)}: ${sanitizeSingleLineDisplayText(issue.message)}`);
518
- }
519
- }
520
- if (params.runtimeRoots) {
521
- toastLines.push(`- workspace_root: ${params.runtimeRoots.workspaceRoot}`);
522
- toastLines.push(`- config_root: ${params.runtimeRoots.configRoot}`);
493
+ if (params.conversion)
494
+ rows.push(...conversionRows(params.conversion, homeDir));
495
+ if (params.settings.notRead.length > 0) {
496
+ const paths = params.settings.notRead.map((path) => sourceFileText(path, homeDir));
497
+ rows.push({ key: "not_read", value: joinOrNone(paths) });
523
498
  }
524
- toastLines.push("- providers:");
525
- for (const p of params.providerAvailability) {
526
- const bits = [];
527
- bits.push(p.enabled ? "enabled" : "disabled");
528
- bits.push(p.available ? "available" : "unavailable");
529
- if (p.matchesCurrentModel !== undefined) {
530
- bits.push(`matchesCurrentModel=${p.matchesCurrentModel ? "yes" : "no"}`);
499
+ rows.push({ key: "providers.off", value: providerIdsText(params.offProviders) }, ...changedSettingRows(params.settings.settings, homeDir), { key: "current_model", value: currentModelText(params) }, { key: "workspace_root", value: pathText(params.workspaceRoot, homeDir) });
500
+ (params.quotaProviders ?? []).forEach((definition, index) => {
501
+ rows.push({ key: "providers.custom", value: customProviderText(definition, index + 1) });
502
+ });
503
+ return kvSection("settings", rows);
504
+ }
505
+ function providerIdText(id) {
506
+ return PROVIDER_IDS.has(id) ? id : "(other provider)";
507
+ }
508
+ function providerListSection(availability) {
509
+ return kvSection("providers", availability.map((provider) => {
510
+ const bits = [
511
+ provider.enabled ? "enabled" : "disabled",
512
+ provider.available ? "available" : "unavailable",
513
+ ];
514
+ if (provider.matchesCurrentModel !== undefined) {
515
+ bits.push(`matchesCurrentModel=${provider.matchesCurrentModel ? "yes" : "no"}`);
531
516
  }
532
- toastLines.push(` - ${p.id}: ${bits.join(" ")}`);
517
+ return { key: providerIdText(provider.id), value: bits.join(" ") };
518
+ }));
519
+ }
520
+ function noticesSection(announcements) {
521
+ const { summary } = announcements;
522
+ return kvSection("notices", [
523
+ { key: "source", value: summary.source === "bundled_only" ? "bundled_only" : "(other)" },
524
+ { key: "network", value: summary.network ? "true" : "false" },
525
+ { key: "active", value: fmtInt(summary.activeCount) },
526
+ { key: "future", value: fmtInt(summary.futureCount) },
527
+ { key: "expired", value: fmtInt(summary.expiredCount) },
528
+ ]);
529
+ }
530
+ async function pathsSection(homeDir) {
531
+ const dirs = getOpencodeRuntimeDirs();
532
+ const dbPath = getOpenCodeDbPath();
533
+ const shownDb = pathText(dbPath, homeDir);
534
+ let db = `path=${shownDb} present=false`;
535
+ if (await pathExists(dbPath)) {
536
+ const stats = await getOpenCodeDbStats();
537
+ db = `path=${shownDb} present=true sessions=${fmtInt(stats.sessionCount)} messages=${fmtInt(stats.messageCount)} assistant_messages=${fmtInt(stats.assistantMessageCount)}`;
533
538
  }
534
- sections.push(createLinesSection("toast", "toast:", toastLines));
535
- const quotaProvidersSection = createQuotaProvidersSection({
536
- definitions: params.quotaProviders ?? [],
537
- probes: params.providerLiveProbes,
539
+ const dir = (path) => pathText(path, homeDir);
540
+ return kvSection("paths", [
541
+ {
542
+ key: "opencode_dirs",
543
+ value: `data=${dir(dirs.dataDir)} config=${dir(dirs.configDir)} cache=${dir(dirs.cacheDir)} state=${dir(dirs.stateDir)}`,
544
+ },
545
+ { key: "opencode.db", value: `${db} (session and token history)` },
546
+ ]);
547
+ }
548
+ /**
549
+ * The login source and its failures: each failed login's integration id (when OpenCode or this
550
+ * plugin knows it), failure kind and time, never its label or the error's own words.
551
+ */
552
+ function credentialSourceSection(diagnostics) {
553
+ const failures = diagnostics.failures.map((failure) => {
554
+ const integration = OPENCODE_PROVIDER_IDS.has(failure.integrationId)
555
+ ? failure.integrationId
556
+ : "(other integration)";
557
+ const category = CREDENTIAL_FAILURE_CATEGORIES.has(failure.category)
558
+ ? failure.category
559
+ : "(other failure)";
560
+ return `${integration}: ${category} at ${isoText(failure.at)}`;
538
561
  });
539
- if (quotaProvidersSection) {
540
- sections.push(quotaProvidersSection);
562
+ const listError = diagnostics.lastListError;
563
+ const listErrorText = listError
564
+ ? `${errorKind(classifyError(listError.detail))} at ${isoText(listError.at)}`
565
+ : "(none)";
566
+ const source = diagnostics.state === "bound"
567
+ ? Object.hasOwn(CREDENTIAL_SOURCE_KINDS, diagnostics.kind)
568
+ ? diagnostics.kind
569
+ : "(other source)"
570
+ : "unbound";
571
+ return kvSection("credential_source", [
572
+ { key: "source", value: source },
573
+ { key: "list_error", value: listErrorText },
574
+ { key: "failures", value: joinOrNone(failures) },
575
+ ]);
576
+ }
577
+ const COMPACT = new Intl.NumberFormat("en-US", {
578
+ notation: "compact",
579
+ maximumSignificantDigits: 3,
580
+ });
581
+ /** A decimal in three significant digits ("4.5", "1.23K", "123T"): no long number shows. */
582
+ function numberText(decimal) {
583
+ const value = Number(decimal);
584
+ if (!Number.isFinite(value))
585
+ return "(not a number)";
586
+ return Math.abs(value) < 1e15 ? COMPACT.format(value) : "(out of range)";
587
+ }
588
+ /** "4.5 USD", "12 request"; a unit this plugin does not name shows as a placeholder. */
589
+ function amountText(amount) {
590
+ const { unit } = amount;
591
+ let unitText = "(custom unit)";
592
+ if (unit.kind === "currency")
593
+ unitText = CURRENCIES.has(unit.code) ? unit.code : "(currency)";
594
+ else if (unit.kind === "count") {
595
+ unitText = Object.hasOwn(COUNT_LABELS, unit.unit) ? unit.unit : "(unit)";
541
596
  }
542
- if (params.maintainerAnnouncements) {
543
- const announcements = params.maintainerAnnouncements;
544
- const summary = announcements.summary;
545
- sections.push(createLinesSection("maintainer_announcements", "maintainer_announcements:", [
546
- `- enabled: ${announcements.config.enabled ? "true" : "false"}`,
547
- `- home: ${announcements.config.home ? "true" : "false"}`,
548
- `- home_frequency: ${announcements.config.homeFrequency}`,
549
- `- source: ${summary.source}`,
550
- `- network: ${summary.network ? "true" : "false"}`,
551
- `- active: ${summary.activeCount}`,
552
- `- future: ${summary.futureCount}`,
553
- `- expired: ${summary.expiredCount}`,
554
- ]));
597
+ return `${numberText(amount.decimal)} ${unitText}`;
598
+ }
599
+ /**
600
+ * A row's facts in one line, for example "window=five_hour, 43% left, 57/100 request,
601
+ * resets 2026-…". Its label and text are the provider's words: a text value is only noted.
602
+ */
603
+ function rowFacts(row) {
604
+ const parts = [];
605
+ if (row.window !== undefined) {
606
+ parts.push(isQuotaWindow(row.window) ? `window=${row.window}` : "window=(other)");
607
+ }
608
+ if (typeof row.percentRemaining === "number") {
609
+ const percent = inRange(Math.round(row.percentRemaining), 0, 100);
610
+ parts.push(percent === "(out of range)" ? "percent (out of range)" : `${percent}% left`);
555
611
  }
556
- // === paths ===
557
- const pathsRows = [];
558
- const runtime = getOpencodeRuntimeDirs();
559
- pathsRows.push({
560
- key: "opencode_dirs",
561
- value: `data=${runtime.dataDir} config=${runtime.configDir} cache=${runtime.cacheDir} state=${runtime.stateDir}`,
612
+ if (row.used && row.limit)
613
+ parts.push(`${numberText(row.used.decimal)}/${amountText(row.limit)}`);
614
+ if (row.amount)
615
+ parts.push(amountText(row.amount));
616
+ if (row.flag !== undefined)
617
+ parts.push(row.flag ? "yes" : "no");
618
+ if (row.text !== undefined)
619
+ parts.push("text not shown");
620
+ if (row.resetsAt !== undefined)
621
+ parts.push(`resets ${dateText(row.resetsAt)}`);
622
+ return parts.join(", ");
623
+ }
624
+ /**
625
+ * A row id from the ledger: the provider id and its row key, each `*` part as "(part 1)" and a
626
+ * custom definition's id as "(custom 1)", numbered like the settings section.
627
+ */
628
+ function rowIdText(row, parts, customs) {
629
+ const [providerId = "", ...keyParts] = row.id.split(".");
630
+ if (!PROVIDER_IDS.has(providerId))
631
+ return "(other row)";
632
+ const patterns = PROVIDER_ROW_KEYS[providerId].map((key) => key.split("."));
633
+ const pattern = patterns.find((candidate) => candidate.length === keyParts.length &&
634
+ candidate.every((part, index) => part === "*" || part === keyParts[index]));
635
+ if (!pattern)
636
+ return `${providerId}.(other row)`;
637
+ const shown = pattern.map((part, index) => {
638
+ if (part !== "*")
639
+ return part;
640
+ const value = keyParts[index] ?? "";
641
+ const custom = providerId === "quota-providers" && index === 1 ? customs.get(value) : undefined;
642
+ return custom !== undefined ? `(custom ${custom})` : `(part ${parts(value)})`;
562
643
  });
563
- const dbPath = getOpenCodeDbPath();
564
- pathsRows.push({
565
- key: "opencode.db",
566
- value: `path=${dbPath} present=${(await pathExists(dbPath)) ? "true" : "false"} (session and token history)`,
644
+ return [providerId, ...shown].join(".");
645
+ }
646
+ /**
647
+ * The error in our own words, with its code and HTTP status: "Provider error 503
648
+ * (provider_error, http 503)". A local tool is named only when it is one this plugin knows.
649
+ */
650
+ function errorText(error, providerId, nowMs) {
651
+ if (!isQuotaErrorCode(error.code))
652
+ return "(unknown error code)";
653
+ const tool = error.tool !== undefined && LOCAL_TOOLS.includes(error.tool) ? error.tool : undefined;
654
+ // A wait shows in minutes or hours: up to a week.
655
+ const retryAtMs = typeof error.retryAtMs === "number" && error.retryAtMs <= nowMs + 7 * 24 * 60 * 60 * 1000
656
+ ? error.retryAtMs
657
+ : undefined;
658
+ const known = {
659
+ code: error.code,
660
+ status: httpStatus(error.status),
661
+ retryAtMs,
662
+ tool,
663
+ missing: error.missing === true,
664
+ };
665
+ const label = PROVIDER_IDS.has(providerId)
666
+ ? getQuotaProviderDisplayLabel(providerId)
667
+ : "the provider";
668
+ return `${describeError(known, label, nowMs)} (${errorKind(known)})`;
669
+ }
670
+ /** "ok", or the error in our words; "partial" when rows came with the error. */
671
+ function providerState(provider, nowMs) {
672
+ if (provider.checkedAtMs === undefined)
673
+ return provider.refreshing ? "checking" : "not checked";
674
+ const error = provider.error;
675
+ if (!error)
676
+ return "ok";
677
+ const words = errorText(error, provider.providerId, nowMs);
678
+ return provider.rows.length > 0 ? `partial: ${words}` : `error: ${words}`;
679
+ }
680
+ /** Shown as they are for any allowed key. */
681
+ const NO_VALUES = new Set(["(none)", "(unknown)", "unknown"]);
682
+ function oneOf(...values) {
683
+ const allowed = new Set(values);
684
+ return (value) => (allowed.has(value) ? value : undefined);
685
+ }
686
+ /** "a,b" when every item is one of `values`. */
687
+ function listOf(values) {
688
+ const allowed = new Set(values);
689
+ return (value) => (value.split(",").every((item) => allowed.has(item)) ? value : undefined);
690
+ }
691
+ /** The same rule for each named key. */
692
+ function each(rule, ...keys) {
693
+ return Object.fromEntries(keys.map((key) => [key, rule]));
694
+ }
695
+ const bool = oneOf("true", "false");
696
+ /** An error or message key: that it is set, never its words. */
697
+ const present = () => "(text not shown)";
698
+ /** An error's text as our code and HTTP status ("provider_error, http 503"), never its words. */
699
+ const errorKindOf = (value) => errorKind(classifyError(value));
700
+ /** A failed login's kind as rows.ts wrote it ("provider_error, http 503"), checked again. */
701
+ const writtenErrorKind = (value) => {
702
+ const match = /^([a-z_]+)(?:, http [1-5]\d{2})?$/u.exec(value);
703
+ return match && isQuotaErrorCode(match[1]) ? value : undefined;
704
+ };
705
+ /** Why a row was dropped (`validateRows`, `addRow`), never the row's own id. */
706
+ const DROPPED_ROW_REASONS = new Set([
707
+ "not an object",
708
+ "wrong provider",
709
+ "no value",
710
+ "repeated row",
711
+ ...[
712
+ "id",
713
+ "label",
714
+ "type",
715
+ "method",
716
+ "authority",
717
+ "prominence",
718
+ "account",
719
+ "window",
720
+ "percentRemaining",
721
+ "used",
722
+ "limit",
723
+ "remaining",
724
+ "amount",
725
+ "flag",
726
+ "text",
727
+ "resetsAt",
728
+ "windowStartedAt",
729
+ ].map((field) => `invalid ${field}`),
730
+ ]);
731
+ const droppedRow = (value) => {
732
+ const reason = value.slice(value.lastIndexOf(": ") + 2);
733
+ return DROPPED_ROW_REASONS.has(reason) ? reason : undefined;
734
+ };
735
+ /** A name the user chose (an organization, an enterprise): that it is set, never the name. */
736
+ const setOrNot = (value) => (value.trim() ? "set" : "not set");
737
+ /** A whole number from `min` to `max`, written with or without commas. */
738
+ function whole(min, max) {
739
+ return (value) => {
740
+ const plain = value.replaceAll(",", "");
741
+ return /^\d+$/u.test(plain) ? inRange(Number(plain), min, max) : undefined;
742
+ };
743
+ }
744
+ /** Accounts, models and state versions: a few at most. */
745
+ const count = whole(0, 999);
746
+ const time = dateText;
747
+ /** A month ("2026-10") from 2000 to 2099. */
748
+ const month = (value) => {
749
+ if (!/^\d{4}-\d{2}$/u.test(value))
750
+ return undefined;
751
+ return dateText(`${value}-01`) === "(invalid date)" ? "(invalid date)" : value;
752
+ };
753
+ const version = versionText;
754
+ const status = (value) => {
755
+ const shown = httpStatus(Number(value));
756
+ return shown === undefined ? undefined : String(shown);
757
+ };
758
+ /** A file path, or `env:NAME` for a variable its provider reads (`env:(other)` for any other). */
759
+ function pathOrEnv(value, { homeDir, envNames }) {
760
+ if (!value.startsWith("env:"))
761
+ return pathText(value, homeDir);
762
+ return envNames.includes(value.slice(4)) ? value : "env:(other)";
763
+ }
764
+ /** Paths joined by " | ". */
765
+ const pathList = (value, context) => value
766
+ .split(" | ")
767
+ .map((path) => pathOrEnv(path, context))
768
+ .join(" | ");
769
+ /** Where an API key came from: `env:NAME`, a config file name, or a path. */
770
+ const keySource = (value, context) => KEY_FILE_SOURCES.has(value) ? value : pathOrEnv(value, context);
771
+ /** The Claude CLI's arguments this plugin passes. */
772
+ const CLAUDE_ARGS = new Set(["--version", "auth", "status", "--json"]);
773
+ /** Claude CLI command lines joined by " | ": the program as a path, then known arguments. */
774
+ const claudeCommands = (value, { homeDir }) => value
775
+ .split(" | ")
776
+ .map((command) => {
777
+ const words = command.match(/"(?:[^"\\]|\\.)*"|[^\s"]+/gu) ?? [];
778
+ const [program = "", ...args] = words.map((word) => {
779
+ if (!word.startsWith('"'))
780
+ return word;
781
+ try {
782
+ return String(JSON.parse(word));
783
+ }
784
+ catch {
785
+ return "";
786
+ }
567
787
  });
568
- appendProviderStatusDetailRows(pathsRows, "alibaba-coding-plan", params.providerLiveProbes, new Set([
569
- "alibaba auth configured",
570
- "alibaba_api_key_source",
571
- "alibaba_api_key_checked_paths",
572
- "alibaba_api_key_credential_database_paths",
573
- "alibaba_coding_plan",
574
- "alibaba_auth_error",
575
- ]));
576
- sections.push(createKvSection("paths", "paths:", pathsRows));
577
- // === credential_source ===
578
- const credentialSource = getCredentialSourceDiagnostics();
579
- sections.push(createKvSection("credential_source", "credential_source:", [
580
- {
581
- key: "source",
582
- value: credentialSource.state === "bound" ? credentialSource.kind : "unbound",
583
- },
584
- { key: "list_error", value: credentialSource.lastListError?.detail ?? "(none)" },
585
- {
586
- key: "failures",
587
- value: joinOrNone(credentialSource.failures.map((failure) => [failure.integrationId, failure.label, failure.category, failure.detail]
588
- .map((part) => sanitizeSingleLineDisplayText(part))
589
- .join(":"))),
590
- },
591
- ]));
592
- for (const [id, providerId] of [
593
- ["openai", "openai"],
594
- ["anthropic", "anthropic"],
595
- ]) {
596
- sections.push(createProviderStatusSection({
597
- id,
598
- title: `${id}:`,
599
- providerId,
600
- probes: params.providerLiveProbes,
601
- availability: params.providerAvailability,
602
- homeDir,
603
- }));
788
+ const programText = program === "claude" ? "claude" : pathText(program, homeDir);
789
+ return [programText, ...args.map((arg) => (CLAUDE_ARGS.has(arg) ? arg : "(arg)"))].join(" ");
790
+ })
791
+ .join(" | ");
792
+ /**
793
+ * A value of `name=value` fields after an optional head word ("yes found=openai,codex"). Every
794
+ * part must pass its rule; a field rule may read the rest of a value (a path list).
795
+ */
796
+ function fields(rules, head) {
797
+ const pattern = new RegExp(`(?:^| )(${Object.keys(rules).join("|")})=`, "gu");
798
+ return (value, context) => {
799
+ const matches = [...value.matchAll(pattern)];
800
+ const headText = value.slice(0, matches[0]?.index ?? value.length);
801
+ const shown = [];
802
+ if (headText) {
803
+ const text = head?.(headText, context);
804
+ if (text === undefined)
805
+ return undefined;
806
+ shown.push(text);
807
+ }
808
+ for (const [index, match] of matches.entries()) {
809
+ const name = match[1] ?? "";
810
+ const start = match.index + match[0].length;
811
+ const end = matches[index + 1]?.index ?? value.length;
812
+ const text = ruleValue(rules[name], value.slice(start, end), context);
813
+ if (text === undefined)
814
+ return undefined;
815
+ shown.push(`${name}=${text}`);
816
+ }
817
+ return shown.length > 0 ? shown.join(" ") : undefined;
818
+ };
819
+ }
820
+ function ruleValue(rule, value, context) {
821
+ if (!rule)
822
+ return undefined;
823
+ return NO_VALUES.has(value) ? value : rule(value, context);
824
+ }
825
+ /**
826
+ * How a provider that reads an API key (one with env variables) looked for it, as the key
827
+ * resolver writes it, with or without a stored login's state.
828
+ */
829
+ const API_KEY_DETAILS = {
830
+ api_key_configured: oneOf("true", "false", "unreadable"),
831
+ api_key_source: keySource,
832
+ ...each(pathList, "api_key_checked_paths", "api_key_credential_database_paths"),
833
+ auth_state: oneOf("none", "configured", "invalid"),
834
+ auth_error: present,
835
+ };
836
+ /** Synthetic and Chutes write their key lookup as one value. */
837
+ const KEY_SUMMARY = fields({ configured: bool, source: keySource, checked: pathList });
838
+ const GOOGLE_DETAILS = {
839
+ auth_state: oneOf("missing", "present", "invalid"),
840
+ auth_source: oneOf(...AGY_AUTH_KEYS, ...GEMINI_CLI_AUTH_KEYS),
841
+ ...each(count, "account_count", "valid_account_count"),
842
+ companion_package_state: oneOf("present", "missing", "invalid"),
843
+ companion_package_path: pathList,
844
+ auth_error: present,
845
+ };
846
+ const STATE_HEALTH = ["missing", "healthy", "malformed", "version_mismatch"];
847
+ const CONSOLE_AUTH_STATES = ["none", "configured", "expired", "invalid"];
848
+ /**
849
+ * The debug details each provider's section may show besides its setup and API-key details,
850
+ * by key; every other one is counted.
851
+ */
852
+ const DEBUG_DETAILS = {
853
+ anthropic: {
854
+ ...each(bool, "cli_installed", "quota_supported"),
855
+ cli_version: version,
856
+ binary_path: (value, { homeDir }) => value === "claude (PATH)" ? value : pathText(value, homeDir),
857
+ auth_status: oneOf("authenticated", "unauthenticated", "unknown"),
858
+ quota_source: oneOf("claude-auth-status-json", "claude-credentials-oauth-api", "opencode-auth-oauth-api"),
859
+ oauth_credential_source: oneOf("opencode-auth", "claude-credentials"),
860
+ checked_commands: claudeCommands,
861
+ },
862
+ copilot: {
863
+ pat_state: oneOf("absent", "invalid", "valid"),
864
+ ...each(pathList, "pat_path", "pat_checked_paths"),
865
+ pat_token_kind: oneOf("github_pat", "ghp", "ghu", "ghs", "other"),
866
+ pat_tier: oneOf("free", "student", "pro", "pro+", "max", "business", "enterprise"),
867
+ ...each(setOrNot, "pat_organization", "pat_enterprise"),
868
+ billing_model: oneOf("ai_credits", "legacy_premium_requests"),
869
+ deployment: oneOf("github.com", "ghe.com", "invalid", "none"),
870
+ api_host: (value) => (value === "api.github.com" ? value : "github enterprise (custom host)"),
871
+ ...each(oneOf("pat", "oauth", "none"), "enterprise_host_source", "effective_source"),
872
+ billing_mode: oneOf("user_quota", "organization_usage", "enterprise_usage", "none"),
873
+ billing_scope: oneOf("user", "organization", "enterprise", "none"),
874
+ quota_api: oneOf("github_ai_credit_api", "github_legacy_premium_request_api", "copilot_internal_user", "none"),
875
+ budget_api: oneOf("organization_budgets", "enterprise_budgets", "not_available"),
876
+ billing_api_access_likely: bool,
877
+ remaining_totals_state: oneOf("available", "value_only_without_denominator", "not_available_from_org_usage", "not_available_from_enterprise_usage", "reported_by_copilot_internal_user", "unavailable"),
878
+ billing_period: month,
879
+ oauth_configured: fields({ key: oneOf(...COPILOT_AUTH_KEYS), refresh: bool, access: bool, enterprise_host: bool }, bool),
880
+ oauth_accounting_state: oneOf("available_via_copilot_internal_user", "invalid_enterprise_host", "not_configured"),
881
+ override: oneOf("pat_overrides_oauth", "none"),
882
+ },
883
+ openai: {
884
+ auth_configured: bool,
885
+ auth_source: oneOf(...OPENAI_AUTH_SOURCE_KEYS, ...OPENCODE_PROVIDER_IDS),
886
+ auth_method: oneOf("chatgpt-token-sharing"),
887
+ token_status: oneOf("api key", "failed", "expired", "valid"),
888
+ token_expires_at: time,
889
+ },
890
+ kilo: { accounting_mode: oneOf("kilo_pass", "gateway_balance"), reset_at: time },
891
+ cursor: {
892
+ plan: oneOf("Pro", "Pro Plus", "Ultra", "none"),
893
+ billing_cycle_start_day: (value, context) => value === "(calendar month)" ? value : whole(1, 31)(value, context),
894
+ auth_state: oneOf("missing", "present", "invalid"),
895
+ auth_error: present,
896
+ auth_selected_path: keySource,
897
+ ...each(pathList, "auth_present_paths", "auth_candidate_paths"),
898
+ ...each(pathList, "config_matches", "config_checked_paths"),
899
+ ...each(bool, "plugin_enabled", "provider_configured"),
900
+ canonical_plugin_package: oneOf(CURSOR_CANONICAL_PLUGIN_PACKAGE),
901
+ cycle_source: oneOf("configured_day", "calendar_month"),
902
+ cycle_reset_at: time,
903
+ unknown_cursor_models: count,
904
+ },
905
+ "alibaba-coding-plan": {
906
+ ...each(bool, "alibaba auth configured", "local_state_exists"),
907
+ alibaba_api_key_source: keySource,
908
+ ...each(pathList, "alibaba_api_key_checked_paths", "local_state_path"),
909
+ alibaba_api_key_credential_database_paths: pathList,
910
+ alibaba_coding_plan: oneOf("lite", "pro", "invalid"),
911
+ local_state_health: oneOf(...STATE_HEALTH),
912
+ local_state_version: count,
913
+ local_state_last_update: time,
914
+ },
915
+ "alibaba-token-plan": {
916
+ cli_available: bool,
917
+ // The provider writes the platform this report runs on.
918
+ platform: oneOf(process.platform),
919
+ windows: listOf(["5h", "weekly"]),
920
+ failure_kind: oneOf(...Object.keys(ALIBABA_TOKEN_PLAN_ERRORS)),
921
+ },
922
+ synthetic: { api_key_diagnostics: KEY_SUMMARY },
923
+ chutes: { api_key_diagnostics: KEY_SUMMARY },
924
+ "google-gemini-cli": GOOGLE_DETAILS,
925
+ "google-agy": GOOGLE_DETAILS,
926
+ nanogpt: {
927
+ ...each(bool, "subscription_active", "enforce_daily_limit"),
928
+ subscription_state: oneOf("active", "grace", "inactive"),
929
+ ...each(time, "billing_period_end", "grace_until"),
930
+ ...each(errorKindOf, "live_error_usage", "live_error_balance"),
931
+ },
932
+ "minimax-coding-plan": { api_endpoint: oneOf("international", "china") },
933
+ "minimax-china-coding-plan": { api_endpoint: oneOf("international", "china") },
934
+ "kimi-code-plan-global": { api_endpoint: oneOf("global", "cn") },
935
+ "kimi-code-plan-cn": { api_endpoint: oneOf("global", "cn") },
936
+ xiaomi: {
937
+ config_state: oneOf("none", "configured", "invalid"),
938
+ config_source: keySource,
939
+ config_checked_paths: pathList,
940
+ },
941
+ "opencode-go": {
942
+ auth_source: keySource,
943
+ ...each(pathList, "auth_checked_paths", "credential_database_paths"),
944
+ console_auth_state: oneOf(...CONSOLE_AUTH_STATES),
945
+ console_error: present,
946
+ go_source: oneOf("console", "legacy_key"),
947
+ opencode_go_state: oneOf("not_subscribed"),
948
+ selected_windows: listOf(["rolling", "weekly", "monthly"]),
949
+ },
950
+ "ollama-cloud": {
951
+ ...each(time, "included_period_from", "included_period_until", "session_resets_at", "weekly_resets_at"),
952
+ },
953
+ opencode: {
954
+ console_auth_state: oneOf(...CONSOLE_AUTH_STATES),
955
+ budget_source: oneOf("member_budget", "credit_limit"),
956
+ member_budget_note: oneOf(MEMBER_BUDGET_NOTE),
957
+ auto_reload: bool,
958
+ },
959
+ };
960
+ /**
961
+ * Every section may show whether OpenCode lists the provider ("yes found=openai"), the kind of
962
+ * an error a check noted, and why a row was dropped.
963
+ */
964
+ const SETUP_DETAILS = {
965
+ opencode_provider: fields({ found: listOf(OPENCODE_PROVIDER_IDS), looked_for: listOf(OPENCODE_PROVIDER_IDS) }, oneOf("yes", "no", "unreadable")),
966
+ error_kind: writtenErrorKind,
967
+ live_fetch_error: errorKindOf,
968
+ dropped_row: droppedRow,
969
+ };
970
+ /** A numbered live error ("live_error_1"), shown as its kind like `live_fetch_error`. */
971
+ const LIVE_ERROR_KEY = /^live_error_\d{1,2}$/u;
972
+ /** A custom definition's check: outcome, login source, files read, local counter state. */
973
+ const CUSTOM_CHECK = fields({
974
+ outcome: oneOf("missing_credential", "success", "http_error", "redirect_error", "timeout", "body_too_large", "invalid_content_type", "invalid_json", "invalid_response", "network_error", "local_state_error"),
975
+ http_status: status,
976
+ credential_source: oneOf("explicit_env", "global_opencode_json", "global_opencode_jsonc", "opencode_db"),
977
+ checked_paths: pathList,
978
+ state_health: oneOf(...STATE_HEALTH),
979
+ state_version: count,
980
+ state_last_update: time,
981
+ });
982
+ /** "custom 2 check" for the debug key of the second custom definition's check. */
983
+ function customCheckKey(key, customs) {
984
+ const id = /^custom_(.+)_check$/su.exec(key)?.[1];
985
+ const number = id === undefined ? undefined : customs.get(id);
986
+ return number === undefined ? undefined : `custom ${number} check`;
987
+ }
988
+ /** The debug details the provider's allow list has, and a count of the others. */
989
+ function debugRows(provider, customs, homeDir) {
990
+ const id = provider.providerId;
991
+ const envNames = Object.hasOwn(KEY_VENDORS, id) ? (KEY_VENDORS[id]?.envVars ?? []) : [];
992
+ const rules = {
993
+ ...SETUP_DETAILS,
994
+ ...(envNames.length > 0 ? API_KEY_DETAILS : {}),
995
+ ...(PROVIDER_IDS.has(id) ? DEBUG_DETAILS[id] : {}),
996
+ };
997
+ const context = { homeDir, envNames };
998
+ const rows = [];
999
+ let hidden = 0;
1000
+ for (const item of provider.debug) {
1001
+ // A failed login's own words: its `error_kind` detail, made with it, shows its kind.
1002
+ if (item.key === "error")
1003
+ continue;
1004
+ const customKey = customCheckKey(item.key, customs);
1005
+ const rule = customKey
1006
+ ? CUSTOM_CHECK
1007
+ : Object.hasOwn(rules, item.key)
1008
+ ? rules[item.key]
1009
+ : LIVE_ERROR_KEY.test(item.key)
1010
+ ? errorKindOf
1011
+ : undefined;
1012
+ const value = ruleValue(rule, item.value, context);
1013
+ if (value === undefined)
1014
+ hidden += 1;
1015
+ else
1016
+ rows.push({ key: customKey ?? item.key, value });
604
1017
  }
605
- // === cursor ===
606
- const cursorRows = [];
607
- appendProviderStatusDetailRows(cursorRows, "cursor", params.providerLiveProbes);
608
- appendProviderStatusDetailRows(cursorRows, "alibaba-coding-plan", params.providerLiveProbes, new Set(["alibaba coding plan local quota", "alibaba coding plan error"]));
609
- appendProviderCompactLiveProbeRows(cursorRows, "cursor", params.providerLiveProbes, params.providerAvailability, homeDir);
610
- sections.push(createKvSection("cursor", "cursor:", cursorRows));
611
- const alibabaCodingPlanLiveProbeSection = createCompactLiveProbeOnlySection({
612
- id: "alibaba_coding_plan",
613
- title: "alibaba_coding_plan:",
614
- providerId: "alibaba-coding-plan",
615
- probes: params.providerLiveProbes,
616
- availability: params.providerAvailability,
617
- homeDir,
618
- });
619
- if (alibabaCodingPlanLiveProbeSection) {
620
- sections.push(alibabaCodingPlanLiveProbeSection);
1018
+ if (hidden > 0) {
1019
+ rows.push({ key: countText(hidden, "other detail not shown", "other details not shown") });
621
1020
  }
622
- const alibabaTokenPlanLiveProbeSection = createCompactLiveProbeOnlySection({
623
- id: "alibaba_token_plan",
624
- title: "alibaba_token_plan:",
625
- providerId: "alibaba-token-plan",
626
- probes: params.providerLiveProbes,
627
- availability: params.providerAvailability,
628
- homeDir,
629
- });
630
- if (alibabaTokenPlanLiveProbeSection) {
631
- sections.push(alibabaTokenPlanLiveProbeSection);
1021
+ return rows;
1022
+ }
1023
+ /**
1024
+ * "disabled" (turned off in the settings, never fetched) and "unavailable" (only local setup
1025
+ * details) win over the state of a check. With "auto" no provider is turned off; with a list,
1026
+ * `availability.enabled` says whether the provider is in it.
1027
+ */
1028
+ function sectionState(availability, provider, enabledProviders, nowMs) {
1029
+ if (enabledProviders !== "auto" && !availability.enabled)
1030
+ return "disabled";
1031
+ if (!availability.available)
1032
+ return "unavailable";
1033
+ return provider ? providerState(provider, nowMs) : "not checked";
1034
+ }
1035
+ /**
1036
+ * One provider's section, the same for every provider: state, rows, debug details. Rows show
1037
+ * their ledger id, an account number and their facts; debug details show when their provider
1038
+ * allows them, and the rest are counted.
1039
+ */
1040
+ function providerSection(availability, provider, params, homeDir) {
1041
+ const nowMs = params.generatedAtMs ?? Date.now();
1042
+ const state = sectionState(availability, provider, params.enabledProviders, nowMs);
1043
+ const title = providerIdText(availability.id);
1044
+ const rows = [{ key: "state", value: state }];
1045
+ if (!provider)
1046
+ return kvSection(title, rows);
1047
+ if (provider.checkedAtMs !== undefined) {
1048
+ const lastRows = provider.okAtMs !== undefined && provider.okAtMs !== provider.checkedAtMs
1049
+ ? ` (rows from ${isoText(provider.okAtMs)})`
1050
+ : "";
1051
+ rows.push({ key: "checked_at", value: `${isoText(provider.checkedAtMs)}${lastRows}` });
632
1052
  }
633
- for (const section of [
634
- { id: "minimax", title: "minimax:", providerId: "minimax-coding-plan" },
635
- {
636
- id: "minimax_china",
637
- title: "minimax_china:",
638
- providerId: "minimax-china-coding-plan",
639
- },
640
- { id: "kimi", title: "kimi:", providerId: "kimi-code-plan-global" },
641
- { id: "kimi_cn", title: "kimi_cn:", providerId: "kimi-code-plan-cn" },
642
- {
643
- id: "opencode_go",
644
- title: "opencode_go:",
645
- providerId: "opencode-go",
646
- detailKeys: OPENCODE_GO_STATUS_DETAIL_KEYS,
647
- },
648
- { id: "opencode_zen", title: "opencode_zen:", providerId: "opencode" },
649
- { id: "xiaomi", title: "xiaomi:", providerId: "xiaomi" },
650
- { id: "zai", title: "zai:", providerId: "zai" },
651
- { id: "zhipu", title: "zhipu:", providerId: "zhipu" },
652
- { id: "synthetic", title: "synthetic:", providerId: "synthetic" },
653
- { id: "chutes", title: "chutes:", providerId: "chutes" },
654
- { id: "deepseek", title: "deepseek:", providerId: "deepseek" },
655
- { id: "xai", title: "xai:", providerId: "xai", includeDetails: false },
656
- { id: "nanogpt", title: "nanogpt:", providerId: "nanogpt" },
657
- { id: "openrouter", title: "openrouter:", providerId: "openrouter" },
658
- {
659
- id: "copilot_quota_auth",
660
- title: "copilot_quota_auth:",
661
- providerId: "copilot",
662
- },
663
- ]) {
664
- sections.push(createProviderStatusSection({
665
- ...section,
666
- probes: params.providerLiveProbes,
667
- availability: params.providerAvailability,
668
- homeDir,
669
- }));
1053
+ rows.push({ key: "rows", value: String(provider.rows.length) });
1054
+ // An account is a login's label (often an email address): it shows as its number.
1055
+ const account = numbering();
1056
+ const part = numbering();
1057
+ const customs = customNumbers(params);
1058
+ for (const row of provider.rows.slice(0, ROW_LIST_LIMIT)) {
1059
+ const accountText = row.account ? ` (account ${account(row.account)})` : "";
1060
+ const id = rowIdText(row, part, customs);
1061
+ rows.push({ key: `${id}${accountText}`, value: rowFacts(row), indent: 1 });
670
1062
  }
671
- if (findProviderLiveProbe("kilo", params.providerLiveProbes)) {
672
- sections.push(createProviderStatusSection({
673
- id: "kilo",
674
- title: "kilo:",
675
- providerId: "kilo",
676
- probes: params.providerLiveProbes,
677
- availability: params.providerAvailability,
678
- homeDir,
679
- }));
1063
+ if (provider.rows.length > ROW_LIST_LIMIT) {
1064
+ rows.push({ key: `… (${provider.rows.length - ROW_LIST_LIMIT} more)`, indent: 1 });
680
1065
  }
681
- for (const [id, providerId] of [
682
- ["google_gemini_cli", "google-gemini-cli"],
683
- ["google_agy", "google-agy"],
684
- ]) {
685
- sections.push(createProviderStatusSection({
686
- id,
687
- title: `${id}:`,
688
- providerId,
689
- probes: params.providerLiveProbes,
690
- availability: params.providerAvailability,
691
- homeDir,
692
- }));
1066
+ rows.push(...debugRows(provider, customs, homeDir));
1067
+ return kvSection(title, rows);
1068
+ }
1069
+ /** "openai/gpt-5" when OpenCode or the shipped price list knows both ids, else undefined. */
1070
+ function knownModelText(providerId, modelId) {
1071
+ const knownProvider = OPENCODE_PROVIDER_IDS.has(providerId) || isBundledPricingProviderId(providerId);
1072
+ return knownProvider && isBundledPricingModelId(modelId) ? `${providerId}/${modelId}` : undefined;
1073
+ }
1074
+ /**
1075
+ * The models of the last 30 days without a price: their tokens add no cost. A model shows when
1076
+ * the shipped price list has its id; the others are counted.
1077
+ */
1078
+ function unpricedRows(usage) {
1079
+ if (!usage)
1080
+ return [{ key: "unpriced_30d", value: "(the usage history could not be read)" }];
1081
+ const { totals } = usage;
1082
+ if (totals.tokens.processed === 0)
1083
+ return [{ key: "unpriced_30d", value: "(no usage)" }];
1084
+ const summary = `${formatShare(totals.unpricedShare)} of ${formatTokens(totals.tokens.processed)} tokens`;
1085
+ const unpriced = usage.models.filter((model) => !model.priced);
1086
+ const rows = [{ key: "unpriced_30d", value: summary }];
1087
+ const listed = unpriced
1088
+ .map((model) => ({ model, name: knownModelText(model.providerID, model.modelID) }))
1089
+ .filter((item) => item.name !== undefined)
1090
+ .slice(0, UNPRICED_LIST_LIMIT);
1091
+ for (const { model, name } of listed) {
1092
+ rows.push({
1093
+ key: name ?? "",
1094
+ value: `${formatTokens(model.unpricedTokens)} tokens`,
1095
+ indent: 1,
1096
+ });
693
1097
  }
694
- // === session token errors ===
695
- if (params.sessionTokenError) {
696
- const sessionTokenErrorRows = [
697
- { key: "session_id", value: params.sessionTokenError.sessionID },
698
- { key: "error", value: params.sessionTokenError.error },
699
- ];
700
- if (params.sessionTokenError.checkedPath) {
701
- sessionTokenErrorRows.push({
702
- key: "checked_path",
703
- value: params.sessionTokenError.checkedPath,
704
- });
705
- }
706
- sections.push(createKvSection("session_tokens_error", "session_tokens_error:", sessionTokenErrorRows));
1098
+ const others = unpriced.length - listed.length;
1099
+ if (others > 0) {
1100
+ const text = countText(others, "more unpriced model", "more unpriced models");
1101
+ rows.push({ key: `… (${text})`, indent: 1 });
707
1102
  }
708
- // === storage scan ===
709
- const dbStats = await getOpenCodeDbStats();
710
- sections.push(createKvSection("storage", "storage:", [
711
- { key: "sessions_in_db", value: fmtInt(dbStats.sessionCount) },
712
- { key: "messages_in_db", value: fmtInt(dbStats.messageCount) },
713
- { key: "assistant_messages_in_db", value: fmtInt(dbStats.assistantMessageCount) },
714
- ]));
715
- // === pricing snapshot ===
716
- const agg = await aggregateUsage({});
1103
+ return rows;
1104
+ }
1105
+ const PRICING_UNITS = "USD per 1M tokens";
1106
+ const PRICING_SOURCE_NAMES = {
1107
+ bundled: "bundled",
1108
+ runtime: "downloaded",
1109
+ };
1110
+ const PRICING_SELECTIONS = new Set(["auto", "bundled", "runtime"]);
1111
+ /** The active price list as fixed words: where it came from, its date and its units. */
1112
+ export function publicPricingMeta() {
717
1113
  const meta = getPricingSnapshotMeta();
718
- const providers = listProviders();
719
- const coverage = computePricingCoverageFromAgg(agg);
720
- const refreshPolicy = getPricingRefreshPolicy();
721
- const autoRefreshDays = Math.round(refreshPolicy.maxAgeMs / (24 * 60 * 60 * 1000));
722
- const health = getPricingSnapshotHealth({
723
- maxAgeMs: refreshPolicy.maxAgeMs,
724
- });
725
- const snapshotSource = getPricingSnapshotSource();
726
- const runtimeSnapshotPath = getRuntimePricingSnapshotPath();
727
- const refreshStatePath = getRuntimePricingRefreshStatePath();
728
- const pricingRefreshState = await readPricingRefreshState();
729
- const pricingRows = [
1114
+ return {
1115
+ source: PRICING_SOURCE_NAMES[getPricingSnapshotSource()] ?? "unknown",
1116
+ generatedAt: isoText(meta.generatedAt),
1117
+ units: meta.units === PRICING_UNITS ? PRICING_UNITS : "(other units)",
1118
+ };
1119
+ }
1120
+ async function pricingSection(params, homeDir) {
1121
+ const meta = publicPricingMeta();
1122
+ const activeSource = getPricingSnapshotSource();
1123
+ const { maxAgeMs } = getPricingRefreshPolicy();
1124
+ const health = getPricingSnapshotHealth({ maxAgeMs });
1125
+ const refreshState = await readPricingRefreshState();
1126
+ const configured = PRICING_SELECTIONS.has(params.pricingSnapshotSource)
1127
+ ? params.pricingSnapshotSource
1128
+ : "(other)";
1129
+ const rows = [
730
1130
  {
731
1131
  key: "pricing",
732
- value: `source=${meta.source} active_source=${snapshotSource} generated_at=${new Date(meta.generatedAt).toISOString()} units=${meta.units}`,
733
- },
734
- {
735
- key: "selection",
736
- value: `configured=${params.pricingSnapshotSource} active=${snapshotSource}`,
1132
+ value: `source=${meta.source} generated_at=${meta.generatedAt} units=${meta.units}`,
737
1133
  },
1134
+ { key: "providers.pricing.source", value: `configured=${configured} active=${meta.source}` },
738
1135
  ];
739
- if (params.pricingSnapshotSource === "bundled") {
740
- pricingRows.push({
1136
+ if (configured === "bundled") {
1137
+ rows.push({
741
1138
  key: "selection_note",
742
- value: "bundled config pins the packaged snapshot and ignores runtime refresh for active pricing",
1139
+ value: "bundled: the built-in price list is used and downloaded prices are ignored",
743
1140
  });
744
1141
  }
745
- else if (params.pricingSnapshotSource === "runtime" && snapshotSource !== "runtime") {
746
- pricingRows.push({
1142
+ else if (configured === "runtime" && activeSource !== "runtime") {
1143
+ rows.push({
747
1144
  key: "selection_note",
748
- value: "runtime config requested the local runtime snapshot, but bundled fallback is active because no valid runtime snapshot is available",
1145
+ value: "runtime: no valid downloaded price list, so the built-in one is used",
749
1146
  });
750
1147
  }
751
- pricingRows.push({
1148
+ rows.push({
752
1149
  key: "runtime_paths",
753
- value: `snapshot=${runtimeSnapshotPath} refresh_state=${refreshStatePath}`,
754
- });
755
- pricingRows.push({
1150
+ value: `snapshot=${pathText(getRuntimePricingSnapshotPath(), homeDir)} refresh_state=${pathText(getRuntimePricingRefreshStatePath(), homeDir)}`,
1151
+ }, {
756
1152
  key: "staleness",
757
- value: `age_ms=${fmtInt(health.ageMs)} max_age_ms=${fmtInt(health.maxAgeMs)} stale=${health.stale ? "true" : "false"}`,
758
- });
759
- pricingRows.push({
760
- key: "refresh_policy",
761
- value: `auto_refresh_days=${fmtInt(autoRefreshDays)}`,
1153
+ value: `age_ms=${msText(health.ageMs)} max_age_ms=${msText(health.maxAgeMs)} stale=${health.stale === true}`,
1154
+ }, {
1155
+ key: "providers.pricing.autoRefreshDays",
1156
+ value: inRange(Math.round(maxAgeMs / DAY_MS), 1, MAX_REFRESH_DAYS),
762
1157
  });
763
- if (pricingRefreshState) {
764
- pricingRows.push({
1158
+ if (refreshState) {
1159
+ // The reader keeps only the results it knows.
1160
+ const result = refreshState.lastResult ?? "(none)";
1161
+ rows.push({
765
1162
  key: "refresh",
766
- value: `last_attempt_at=${pricingRefreshState.lastAttemptAt ? new Date(pricingRefreshState.lastAttemptAt).toISOString() : "(none)"} last_success_at=${pricingRefreshState.lastSuccessAt ? new Date(pricingRefreshState.lastSuccessAt).toISOString() : "(none)"} last_failure_at=${pricingRefreshState.lastFailureAt ? new Date(pricingRefreshState.lastFailureAt).toISOString() : "(none)"} last_result=${pricingRefreshState.lastResult ?? "(none)"}`,
1163
+ value: `last_attempt_at=${isoText(refreshState.lastAttemptAt)} last_success_at=${isoText(refreshState.lastSuccessAt)} last_failure_at=${isoText(refreshState.lastFailureAt)} last_result=${result}`,
767
1164
  });
768
- if (pricingRefreshState.lastError) {
769
- pricingRows.push({ key: "refresh_error", value: pricingRefreshState.lastError });
1165
+ if (refreshState.lastError) {
1166
+ rows.push({ key: "refresh_error", value: errorKind(classifyError(refreshState.lastError)) });
770
1167
  }
771
1168
  }
772
1169
  else {
773
- pricingRows.push({ key: "refresh", value: "(no runtime refresh state yet)" });
1170
+ rows.push({ key: "refresh", value: "(no runtime refresh state yet)" });
774
1171
  }
775
- pricingRows.push({ key: "providers", value: providers.join(",") });
776
- pricingRows.push({
777
- key: "coverage_seen",
778
- value: `priced_keys=${fmtInt(coverage.totals.pricedKeysSeen)} mapped_but_missing=${fmtInt(coverage.totals.mappedMissingKeysSeen)} unpriced_keys=${fmtInt(coverage.totals.unpricedKeysSeen)}`,
779
- });
780
- for (const p of providers) {
781
- const c = coverage.byProvider.get(p) ?? {
782
- pricedKeysSeen: 0,
783
- mappedMissingKeysSeen: 0,
784
- unpricedKeysSeen: 0,
785
- };
786
- pricingRows.push({
787
- key: p,
788
- value: `models=${fmtInt(getProviderModelCount(p))} priced_models_seen=${fmtInt(c.pricedKeysSeen)} mapped_but_missing_models_seen=${fmtInt(c.mappedMissingKeysSeen)} unpriced_models_seen=${fmtInt(c.unpricedKeysSeen)}`,
789
- indent: 1,
790
- });
791
- }
792
- sections.push(createKvSection("pricing_snapshot", "pricing_snapshot:", pricingRows));
793
- // === supported providers pricing ===
794
- const supported = getProviders().map((p) => p.id);
795
- const supportedRows = supported.map((id) => {
796
- const row = supportedProviderPricingRow({ id, agg, snapshotProviders: providers });
797
- return {
798
- key: row.id,
799
- value: `pricing=${row.pricing} (${row.notes})`,
800
- };
801
- });
802
- sections.push(createKvSection("supported_providers_pricing", "supported_providers_pricing:", supportedRows));
803
- // === unpriced models ===
804
- const unpricedRows = [];
805
- if (agg.unpriced.length === 0) {
806
- unpricedRows.push({ key: "none" });
807
- }
808
- else {
809
- unpricedRows.push({
810
- key: "keys",
811
- value: `${fmtInt(agg.unpriced.length)} tokens_total=${fmtInt(totalTokenBuckets(agg.totals.unpriced))}`,
812
- });
813
- for (const row of agg.unpriced.slice(0, STATUS_SAMPLE_LIMIT)) {
814
- const src = `${row.key.sourceProviderID}/${row.key.sourceModelID}`;
815
- const mapped = `${row.key.mappedProvider}/${row.key.mappedModel}`;
816
- unpricedRows.push({
817
- key: src,
818
- value: `mapped=${mapped} tokens=${fmtInt(totalTokenBuckets(row.tokens))} msgs=${fmtInt(row.messageCount)} reason=${row.key.reason}`,
819
- });
820
- }
821
- if (agg.unpriced.length > STATUS_SAMPLE_LIMIT) {
822
- unpricedRows.push({ key: `… (${fmtInt(agg.unpriced.length - STATUS_SAMPLE_LIMIT)} more)` });
823
- }
824
- }
825
- sections.push(createKvSection("unpriced_models", "unpriced_models:", unpricedRows));
826
- // === unknown pricing ===
827
- const unknownRows = [];
828
- if (agg.unknown.length === 0) {
829
- unknownRows.push({ key: "none" });
830
- }
831
- else {
832
- unknownRows.push({
833
- key: "keys",
834
- value: `${fmtInt(agg.unknown.length)} tokens_total=${fmtInt(totalTokenBuckets(agg.totals.unknown))}`,
835
- });
836
- for (const row of agg.unknown.slice(0, STATUS_SAMPLE_LIMIT)) {
837
- const src = `${row.key.sourceProviderID}/${row.key.sourceModelID}`;
838
- const mappedBase = row.key.mappedProvider && row.key.mappedModel
839
- ? `${row.key.mappedProvider}/${row.key.mappedModel}`
840
- : "(none)";
841
- const candidates = row.key.providerCandidates && row.key.providerCandidates.length > 0
842
- ? ` candidates=${row.key.providerCandidates.join(",")}`
843
- : "";
844
- unknownRows.push({
845
- key: src,
846
- value: `mapped=${mappedBase}${candidates} tokens=${fmtInt(totalTokenBuckets(row.tokens))} msgs=${fmtInt(row.messageCount)}`,
847
- });
848
- }
849
- if (agg.unknown.length > STATUS_SAMPLE_LIMIT) {
850
- unknownRows.push({ key: `… (${fmtInt(agg.unknown.length - STATUS_SAMPLE_LIMIT)} more)` });
851
- }
852
- }
853
- sections.push(createKvSection("unknown_pricing", "unknown_pricing:", unknownRows));
854
- // Users paste this report into public issues: show the home dir as "~" in every path
855
- // so it does not reveal their computer's user name.
1172
+ rows.push({ key: "price_list_providers", value: fmtInt(listProviders().length) }, ...unpricedRows(params.usage));
1173
+ return kvSection("pricing_snapshot", rows);
1174
+ }
1175
+ /** A path as the report shows it, for the status payload. */
1176
+ export function statusPathText(path, homeDir = homedir()) {
1177
+ return pathText(path, homeDir);
1178
+ }
1179
+ /** The provider ids this plugin knows, for the status payload. */
1180
+ export function knownProviderIds(ids) {
1181
+ return ids === "auto" ? ids : ids.filter((id) => PROVIDER_IDS.has(id));
1182
+ }
1183
+ export async function buildQuotaStatusReport(params) {
1184
+ return renderPlainTextReport(await buildQuotaStatusReportDocument(params));
1185
+ }
1186
+ export async function buildQuotaStatusReportDocument(params) {
1187
+ const homeDir = params.homeDir ?? homedir();
1188
+ const pluginVersion = (await getPackageVersion()) ?? "unknown";
1189
+ const snapshots = new Map(params.providers.map((provider) => [provider.providerId, provider]));
1190
+ const sections = [
1191
+ environmentSection(params, params.generatedAtMs ?? Date.now()),
1192
+ settingsSection(params, homeDir),
1193
+ providerListSection(params.providerAvailability),
1194
+ ...(params.maintainerAnnouncements ? [noticesSection(params.maintainerAnnouncements)] : []),
1195
+ await pathsSection(homeDir),
1196
+ credentialSourceSection(params.credentialSource),
1197
+ ...params.providerAvailability.map((availability) => providerSection(availability, snapshots.get(availability.id), params, homeDir)),
1198
+ await pricingSection(params, homeDir),
1199
+ ];
856
1200
  return replaceHomeDirWithTildeInDocument({
857
- heading: commandHeading({
858
- title: `Quota Status (opencode-quota v${v}) (/quota_status)`,
859
- detail: `opencode-quota v${v}`,
860
- generatedAtMs: params.generatedAtMs,
861
- }),
1201
+ heading: `# Quota Status (opencode-quota v${pluginVersion})`,
862
1202
  sections,
863
1203
  }, homeDir);
864
1204
  }