@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.
- package/README.md +30 -44
- package/dist/agent-tools.d.ts +24 -0
- package/dist/agent-tools.d.ts.map +1 -0
- package/dist/agent-tools.js +578 -0
- package/dist/agent-tools.js.map +1 -0
- package/dist/bin/opencode-quota.d.ts.map +1 -1
- package/dist/bin/opencode-quota.js +1 -32
- package/dist/bin/opencode-quota.js.map +1 -1
- package/dist/core/errors.d.ts +12 -0
- package/dist/core/errors.d.ts.map +1 -0
- package/dist/core/errors.js +141 -0
- package/dist/core/errors.js.map +1 -0
- package/dist/core/pace.d.ts +13 -0
- package/dist/core/pace.d.ts.map +1 -0
- package/dist/core/pace.js +52 -0
- package/dist/core/pace.js.map +1 -0
- package/dist/core/persist.d.ts +44 -0
- package/dist/core/persist.d.ts.map +1 -0
- package/dist/core/persist.js +210 -0
- package/dist/core/persist.js.map +1 -0
- package/dist/core/provider-source.d.ts +40 -0
- package/dist/core/provider-source.d.ts.map +1 -0
- package/dist/core/provider-source.js +210 -0
- package/dist/core/provider-source.js.map +1 -0
- package/dist/core/redact.d.ts +17 -0
- package/dist/core/redact.d.ts.map +1 -0
- package/dist/core/redact.js +217 -0
- package/dist/core/redact.js.map +1 -0
- package/dist/core/rows.d.ts +20 -0
- package/dist/core/rows.d.ts.map +1 -0
- package/dist/core/rows.js +142 -0
- package/dist/core/rows.js.map +1 -0
- package/dist/core/selection.d.ts +15 -0
- package/dist/core/selection.d.ts.map +1 -0
- package/dist/core/selection.js +43 -0
- package/dist/core/selection.js.map +1 -0
- package/dist/core/snapshot-publisher.d.ts +15 -0
- package/dist/core/snapshot-publisher.d.ts.map +1 -0
- package/dist/core/snapshot-publisher.js +57 -0
- package/dist/core/snapshot-publisher.js.map +1 -0
- package/dist/core/store.d.ts +93 -0
- package/dist/core/store.d.ts.map +1 -0
- package/dist/core/store.js +563 -0
- package/dist/core/store.js.map +1 -0
- package/dist/core/types.d.ts +186 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js +2 -0
- package/dist/core/types.js.map +1 -0
- package/dist/core/usage-ranges.d.ts +46 -0
- package/dist/core/usage-ranges.d.ts.map +1 -0
- package/dist/core/usage-ranges.js +72 -0
- package/dist/core/usage-ranges.js.map +1 -0
- package/dist/core/usage.d.ts +100 -0
- package/dist/core/usage.d.ts.map +1 -0
- package/dist/core/usage.js +373 -0
- package/dist/core/usage.js.map +1 -0
- package/dist/core/validate.d.ts +8 -0
- package/dist/core/validate.d.ts.map +1 -0
- package/dist/core/validate.js +167 -0
- package/dist/core/validate.js.map +1 -0
- package/dist/core/windows.d.ts +24 -0
- package/dist/core/windows.d.ts.map +1 -0
- package/dist/core/windows.js +43 -0
- package/dist/core/windows.js.map +1 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/lib/accounting-format.d.ts +2 -52
- package/dist/lib/accounting-format.d.ts.map +1 -1
- package/dist/lib/accounting-format.js +6 -255
- package/dist/lib/accounting-format.js.map +1 -1
- package/dist/lib/alibaba-auth.d.ts +14 -21
- package/dist/lib/alibaba-auth.d.ts.map +1 -1
- package/dist/lib/alibaba-auth.js +36 -149
- package/dist/lib/alibaba-auth.js.map +1 -1
- package/dist/lib/alibaba-coding-plan-local-quota.d.ts +1 -6
- package/dist/lib/alibaba-coding-plan-local-quota.d.ts.map +1 -1
- package/dist/lib/alibaba-coding-plan-local-quota.js +1 -1
- package/dist/lib/alibaba-coding-plan-local-quota.js.map +1 -1
- package/dist/lib/alibaba-token-plan.d.ts +18 -27
- package/dist/lib/alibaba-token-plan.d.ts.map +1 -1
- package/dist/lib/alibaba-token-plan.js +121 -285
- package/dist/lib/alibaba-token-plan.js.map +1 -1
- package/dist/lib/anthropic-auth.d.ts +3 -5
- package/dist/lib/anthropic-auth.d.ts.map +1 -1
- package/dist/lib/anthropic-auth.js +6 -9
- package/dist/lib/anthropic-auth.js.map +1 -1
- package/dist/lib/anthropic.d.ts +12 -37
- package/dist/lib/anthropic.d.ts.map +1 -1
- package/dist/lib/anthropic.js +348 -698
- package/dist/lib/anthropic.js.map +1 -1
- package/dist/lib/api-key-resolver.d.ts +165 -192
- package/dist/lib/api-key-resolver.d.ts.map +1 -1
- package/dist/lib/api-key-resolver.js +208 -234
- package/dist/lib/api-key-resolver.js.map +1 -1
- package/dist/lib/atomic-json.d.ts +4 -1
- package/dist/lib/atomic-json.d.ts.map +1 -1
- package/dist/lib/atomic-json.js +2 -3
- package/dist/lib/atomic-json.js.map +1 -1
- package/dist/lib/calendar-date.d.ts +15 -0
- package/dist/lib/calendar-date.d.ts.map +1 -0
- package/dist/lib/calendar-date.js +21 -0
- package/dist/lib/calendar-date.js.map +1 -0
- package/dist/lib/chutes.d.ts +0 -1
- package/dist/lib/chutes.d.ts.map +1 -1
- package/dist/lib/chutes.js +2 -4
- package/dist/lib/chutes.js.map +1 -1
- package/dist/lib/cli-reports.d.ts +15 -11
- package/dist/lib/cli-reports.d.ts.map +1 -1
- package/dist/lib/cli-reports.js +34 -107
- package/dist/lib/cli-reports.js.map +1 -1
- package/dist/lib/cli-show.d.ts +9 -22
- package/dist/lib/cli-show.d.ts.map +1 -1
- package/dist/lib/cli-show.js +104 -75
- package/dist/lib/cli-show.js.map +1 -1
- package/dist/lib/config-file-utils.d.ts +3 -15
- package/dist/lib/config-file-utils.d.ts.map +1 -1
- package/dist/lib/config-file-utils.js +4 -63
- package/dist/lib/config-file-utils.js.map +1 -1
- package/dist/lib/config-write-target.d.ts +4 -7
- package/dist/lib/config-write-target.d.ts.map +1 -1
- package/dist/lib/config-write-target.js +4 -18
- package/dist/lib/config-write-target.js.map +1 -1
- package/dist/lib/copilot.d.ts +29 -20
- package/dist/lib/copilot.d.ts.map +1 -1
- package/dist/lib/copilot.js +392 -649
- package/dist/lib/copilot.js.map +1 -1
- package/dist/lib/cursor-detection.d.ts +3 -3
- package/dist/lib/cursor-detection.d.ts.map +1 -1
- package/dist/lib/cursor-detection.js +44 -88
- package/dist/lib/cursor-detection.js.map +1 -1
- package/dist/lib/cursor-pricing.d.ts +71 -8
- package/dist/lib/cursor-pricing.d.ts.map +1 -1
- package/dist/lib/cursor-pricing.js +33 -125
- package/dist/lib/cursor-pricing.js.map +1 -1
- package/dist/lib/cursor-usage.d.ts +6 -4
- package/dist/lib/cursor-usage.d.ts.map +1 -1
- package/dist/lib/cursor-usage.js +27 -40
- package/dist/lib/cursor-usage.js.map +1 -1
- package/dist/lib/deepseek.d.ts +9 -17
- package/dist/lib/deepseek.d.ts.map +1 -1
- package/dist/lib/deepseek.js +24 -54
- package/dist/lib/deepseek.js.map +1 -1
- package/dist/lib/display-sanitize.d.ts +0 -8
- package/dist/lib/display-sanitize.d.ts.map +1 -1
- package/dist/lib/display-sanitize.js +0 -139
- package/dist/lib/display-sanitize.js.map +1 -1
- package/dist/lib/entries.d.ts +13 -239
- package/dist/lib/entries.d.ts.map +1 -1
- package/dist/lib/entries.js +1 -46
- package/dist/lib/entries.js.map +1 -1
- package/dist/lib/file-lock.d.ts +23 -0
- package/dist/lib/file-lock.d.ts.map +1 -0
- package/dist/lib/file-lock.js +122 -0
- package/dist/lib/file-lock.js.map +1 -0
- package/dist/lib/format-utils.d.ts +2 -82
- package/dist/lib/format-utils.d.ts.map +1 -1
- package/dist/lib/format-utils.js +2 -191
- package/dist/lib/format-utils.js.map +1 -1
- package/dist/lib/glm-coding-plan.d.ts +6 -5
- package/dist/lib/glm-coding-plan.d.ts.map +1 -1
- package/dist/lib/glm-coding-plan.js +19 -1
- package/dist/lib/glm-coding-plan.js.map +1 -1
- package/dist/lib/google-agy.d.ts +8 -40
- package/dist/lib/google-agy.d.ts.map +1 -1
- package/dist/lib/google-agy.js +88 -519
- package/dist/lib/google-agy.js.map +1 -1
- package/dist/lib/google-companion-credentials.d.ts +9 -40
- package/dist/lib/google-companion-credentials.d.ts.map +1 -1
- package/dist/lib/google-companion-credentials.js +167 -224
- package/dist/lib/google-companion-credentials.js.map +1 -1
- package/dist/lib/google-gemini-cli.d.ts +5 -38
- package/dist/lib/google-gemini-cli.d.ts.map +1 -1
- package/dist/lib/google-gemini-cli.js +71 -442
- package/dist/lib/google-gemini-cli.js.map +1 -1
- package/dist/lib/google-oauth-quota.d.ts +76 -0
- package/dist/lib/google-oauth-quota.d.ts.map +1 -0
- package/dist/lib/google-oauth-quota.js +297 -0
- package/dist/lib/google-oauth-quota.js.map +1 -0
- package/dist/lib/google-token-cache.d.ts +3 -5
- package/dist/lib/google-token-cache.d.ts.map +1 -1
- package/dist/lib/google-token-cache.js +2 -14
- package/dist/lib/google-token-cache.js.map +1 -1
- package/dist/lib/home-dir-display.d.ts.map +1 -1
- package/dist/lib/home-dir-display.js +21 -39
- package/dist/lib/home-dir-display.js.map +1 -1
- package/dist/lib/http.d.ts +4 -1
- package/dist/lib/http.d.ts.map +1 -1
- package/dist/lib/http.js +2 -1
- package/dist/lib/http.js.map +1 -1
- package/dist/lib/json-guards.d.ts +11 -0
- package/dist/lib/json-guards.d.ts.map +1 -0
- package/dist/lib/json-guards.js +25 -0
- package/dist/lib/json-guards.js.map +1 -0
- package/dist/lib/kilo.d.ts +0 -2
- package/dist/lib/kilo.d.ts.map +1 -1
- package/dist/lib/kilo.js +33 -116
- package/dist/lib/kilo.js.map +1 -1
- package/dist/lib/kimi-endpoints.d.ts +2 -2
- package/dist/lib/kimi-endpoints.d.ts.map +1 -1
- package/dist/lib/kimi-endpoints.js +1 -1
- package/dist/lib/kimi-endpoints.js.map +1 -1
- package/dist/lib/kimi.d.ts.map +1 -1
- package/dist/lib/kimi.js +40 -86
- package/dist/lib/kimi.js.map +1 -1
- package/dist/lib/maintainer-announcements.d.ts +4 -17
- package/dist/lib/maintainer-announcements.d.ts.map +1 -1
- package/dist/lib/maintainer-announcements.js +7 -44
- package/dist/lib/maintainer-announcements.js.map +1 -1
- package/dist/lib/maintainer-home-notice.d.ts +19 -0
- package/dist/lib/maintainer-home-notice.d.ts.map +1 -0
- package/dist/lib/maintainer-home-notice.js +92 -0
- package/dist/lib/maintainer-home-notice.js.map +1 -0
- package/dist/lib/mimo-config.d.ts +6 -9
- package/dist/lib/mimo-config.d.ts.map +1 -1
- package/dist/lib/mimo-config.js +43 -74
- package/dist/lib/mimo-config.js.map +1 -1
- package/dist/lib/mimo.d.ts +2 -5
- package/dist/lib/mimo.d.ts.map +1 -1
- package/dist/lib/mimo.js +57 -109
- package/dist/lib/mimo.js.map +1 -1
- package/dist/lib/minimax-endpoints.d.ts +2 -2
- package/dist/lib/minimax-endpoints.d.ts.map +1 -1
- package/dist/lib/minimax-endpoints.js +1 -1
- package/dist/lib/minimax-endpoints.js.map +1 -1
- package/dist/lib/modelsdev-pricing.d.ts +22 -16
- package/dist/lib/modelsdev-pricing.d.ts.map +1 -1
- package/dist/lib/modelsdev-pricing.js +279 -560
- package/dist/lib/modelsdev-pricing.js.map +1 -1
- package/dist/lib/nanogpt.d.ts +6 -6
- package/dist/lib/nanogpt.d.ts.map +1 -1
- package/dist/lib/nanogpt.js +19 -72
- package/dist/lib/nanogpt.js.map +1 -1
- package/dist/lib/ollama-cloud.d.ts +3 -6
- package/dist/lib/ollama-cloud.d.ts.map +1 -1
- package/dist/lib/ollama-cloud.js +26 -37
- package/dist/lib/ollama-cloud.js.map +1 -1
- package/dist/lib/openai.d.ts +18 -29
- package/dist/lib/openai.d.ts.map +1 -1
- package/dist/lib/openai.js +108 -182
- package/dist/lib/openai.js.map +1 -1
- package/dist/lib/opencode-auth-sqlite.d.ts.map +1 -1
- package/dist/lib/opencode-auth-sqlite.js +22 -23
- package/dist/lib/opencode-auth-sqlite.js.map +1 -1
- package/dist/lib/opencode-auth.d.ts +17 -11
- package/dist/lib/opencode-auth.d.ts.map +1 -1
- package/dist/lib/opencode-auth.js +39 -46
- package/dist/lib/opencode-auth.js.map +1 -1
- package/dist/lib/opencode-config-editor.d.ts +3 -34
- package/dist/lib/opencode-config-editor.d.ts.map +1 -1
- package/dist/lib/opencode-config-editor.js +23 -142
- package/dist/lib/opencode-config-editor.js.map +1 -1
- package/dist/lib/opencode-config-providers.d.ts +4 -3
- package/dist/lib/opencode-config-providers.d.ts.map +1 -1
- package/dist/lib/opencode-config-providers.js +7 -11
- package/dist/lib/opencode-config-providers.js.map +1 -1
- package/dist/lib/opencode-config-read.d.ts +2 -1
- package/dist/lib/opencode-config-read.d.ts.map +1 -1
- package/dist/lib/opencode-config-read.js +3 -2
- package/dist/lib/opencode-config-read.js.map +1 -1
- package/dist/lib/opencode-console-auth.d.ts +2 -3
- package/dist/lib/opencode-console-auth.d.ts.map +1 -1
- package/dist/lib/opencode-console-auth.js +2 -2
- package/dist/lib/opencode-console-auth.js.map +1 -1
- package/dist/lib/opencode-go.d.ts.map +1 -1
- package/dist/lib/opencode-go.js +51 -79
- package/dist/lib/opencode-go.js.map +1 -1
- package/dist/lib/opencode-storage.d.ts +18 -3
- package/dist/lib/opencode-storage.d.ts.map +1 -1
- package/dist/lib/opencode-storage.js +134 -2
- package/dist/lib/opencode-storage.js.map +1 -1
- package/dist/lib/opencode-zen.d.ts +15 -12
- package/dist/lib/opencode-zen.d.ts.map +1 -1
- package/dist/lib/opencode-zen.js +139 -147
- package/dist/lib/opencode-zen.js.map +1 -1
- package/dist/lib/openrouter.d.ts.map +1 -1
- package/dist/lib/openrouter.js +1 -13
- package/dist/lib/openrouter.js.map +1 -1
- package/dist/lib/pricing-key.d.ts +48 -0
- package/dist/lib/pricing-key.d.ts.map +1 -0
- package/dist/lib/pricing-key.js +371 -0
- package/dist/lib/pricing-key.js.map +1 -0
- package/dist/lib/provider-metadata.d.ts +16 -20
- package/dist/lib/provider-metadata.d.ts.map +1 -1
- package/dist/lib/provider-metadata.js +69 -69
- package/dist/lib/provider-metadata.js.map +1 -1
- package/dist/lib/provider-model-matching.d.ts +2 -1
- package/dist/lib/provider-model-matching.d.ts.map +1 -1
- package/dist/lib/provider-registration.d.ts +1 -177
- package/dist/lib/provider-registration.d.ts.map +1 -1
- package/dist/lib/provider-registration.js +1 -163
- package/dist/lib/provider-registration.js.map +1 -1
- package/dist/lib/quota-dialog-commands.d.ts +87 -53
- package/dist/lib/quota-dialog-commands.d.ts.map +1 -1
- package/dist/lib/quota-dialog-commands.js +259 -519
- package/dist/lib/quota-dialog-commands.js.map +1 -1
- package/dist/lib/quota-export-types.d.ts +29 -23
- package/dist/lib/quota-export-types.d.ts.map +1 -1
- package/dist/lib/quota-export.d.ts +16 -26
- package/dist/lib/quota-export.d.ts.map +1 -1
- package/dist/lib/quota-export.js +105 -147
- package/dist/lib/quota-export.js.map +1 -1
- package/dist/lib/quota-providers-local.d.ts +12 -8
- package/dist/lib/quota-providers-local.d.ts.map +1 -1
- package/dist/lib/quota-providers-local.js +150 -223
- package/dist/lib/quota-providers-local.js.map +1 -1
- package/dist/lib/quota-providers-remote.d.ts +3 -8
- package/dist/lib/quota-providers-remote.d.ts.map +1 -1
- package/dist/lib/quota-providers-remote.js +309 -449
- package/dist/lib/quota-providers-remote.js.map +1 -1
- package/dist/lib/quota-providers.d.ts +57 -15
- package/dist/lib/quota-providers.d.ts.map +1 -1
- package/dist/lib/quota-providers.js +157 -64
- package/dist/lib/quota-providers.js.map +1 -1
- package/dist/lib/quota-report-message.d.ts +23 -10
- package/dist/lib/quota-report-message.d.ts.map +1 -1
- package/dist/lib/quota-report-message.js +37 -13
- package/dist/lib/quota-report-message.js.map +1 -1
- package/dist/lib/quota-retry-wait.d.ts +14 -6
- package/dist/lib/quota-retry-wait.d.ts.map +1 -1
- package/dist/lib/quota-retry-wait.js +37 -39
- package/dist/lib/quota-retry-wait.js.map +1 -1
- package/dist/lib/quota-status.d.ts +50 -33
- package/dist/lib/quota-status.d.ts.map +1 -1
- package/dist/lib/quota-status.js +1113 -773
- package/dist/lib/quota-status.js.map +1 -1
- package/dist/lib/quota-telemetry.d.ts +7 -14
- package/dist/lib/quota-telemetry.d.ts.map +1 -1
- package/dist/lib/quota-telemetry.js +46 -97
- package/dist/lib/quota-telemetry.js.map +1 -1
- package/dist/lib/report-document.d.ts +5 -65
- package/dist/lib/report-document.d.ts.map +1 -1
- package/dist/lib/report-document.js +14 -167
- package/dist/lib/report-document.js.map +1 -1
- package/dist/lib/resolved-auth-identity.d.ts +1 -2
- package/dist/lib/resolved-auth-identity.d.ts.map +1 -1
- package/dist/lib/resolved-auth-identity.js +0 -4
- package/dist/lib/resolved-auth-identity.js.map +1 -1
- package/dist/lib/session-tokens.d.ts +19 -19
- package/dist/lib/session-tokens.d.ts.map +1 -1
- package/dist/lib/session-tokens.js +58 -44
- package/dist/lib/session-tokens.js.map +1 -1
- package/dist/lib/synthetic.d.ts +0 -1
- package/dist/lib/synthetic.d.ts.map +1 -1
- package/dist/lib/synthetic.js +43 -94
- package/dist/lib/synthetic.js.map +1 -1
- package/dist/lib/token-buckets.d.ts +0 -1
- package/dist/lib/token-buckets.d.ts.map +1 -1
- package/dist/lib/token-buckets.js +0 -3
- package/dist/lib/token-buckets.js.map +1 -1
- package/dist/lib/token-cost.d.ts +2 -1
- package/dist/lib/token-cost.d.ts.map +1 -1
- package/dist/lib/types.d.ts +25 -333
- package/dist/lib/types.d.ts.map +1 -1
- package/dist/lib/types.js +2 -70
- package/dist/lib/types.js.map +1 -1
- package/dist/lib/xai.d.ts +6 -6
- package/dist/lib/xai.d.ts.map +1 -1
- package/dist/lib/xai.js +51 -90
- package/dist/lib/xai.js.map +1 -1
- package/dist/plugin.d.ts +6 -3
- package/dist/plugin.d.ts.map +1 -1
- package/dist/plugin.js +315 -90
- package/dist/plugin.js.map +1 -1
- package/dist/providers/alibaba-coding-plan.d.ts +4 -0
- package/dist/providers/alibaba-coding-plan.d.ts.map +1 -1
- package/dist/providers/alibaba-coding-plan.js +47 -77
- package/dist/providers/alibaba-coding-plan.js.map +1 -1
- package/dist/providers/alibaba-token-plan.d.ts.map +1 -1
- package/dist/providers/alibaba-token-plan.js +34 -46
- package/dist/providers/alibaba-token-plan.js.map +1 -1
- package/dist/providers/anthropic.d.ts +2 -4
- package/dist/providers/anthropic.d.ts.map +1 -1
- package/dist/providers/anthropic.js +80 -162
- package/dist/providers/anthropic.js.map +1 -1
- package/dist/providers/cache-policies.d.ts +0 -2
- package/dist/providers/cache-policies.d.ts.map +1 -1
- package/dist/providers/cache-policies.js +38 -88
- package/dist/providers/cache-policies.js.map +1 -1
- package/dist/providers/chutes.d.ts +0 -3
- package/dist/providers/chutes.d.ts.map +1 -1
- package/dist/providers/chutes.js +16 -28
- package/dist/providers/chutes.js.map +1 -1
- package/dist/providers/contract.d.ts +128 -0
- package/dist/providers/contract.d.ts.map +1 -0
- package/dist/providers/contract.js +101 -0
- package/dist/providers/contract.js.map +1 -0
- package/dist/providers/copilot.d.ts.map +1 -1
- package/dist/providers/copilot.js +171 -175
- package/dist/providers/copilot.js.map +1 -1
- package/dist/providers/cursor.d.ts.map +1 -1
- package/dist/providers/cursor.js +56 -109
- package/dist/providers/cursor.js.map +1 -1
- package/dist/providers/deepseek.d.ts +1 -6
- package/dist/providers/deepseek.d.ts.map +1 -1
- package/dist/providers/deepseek.js +46 -81
- package/dist/providers/deepseek.js.map +1 -1
- package/dist/providers/glm-coding-plan.d.ts +8 -0
- package/dist/providers/glm-coding-plan.d.ts.map +1 -0
- package/dist/providers/glm-coding-plan.js +106 -0
- package/dist/providers/glm-coding-plan.js.map +1 -0
- package/dist/providers/google-agy.d.ts +0 -1
- package/dist/providers/google-agy.d.ts.map +1 -1
- package/dist/providers/google-agy.js +41 -192
- package/dist/providers/google-agy.js.map +1 -1
- package/dist/providers/google-gemini-cli.d.ts.map +1 -1
- package/dist/providers/google-gemini-cli.js +51 -151
- package/dist/providers/google-gemini-cli.js.map +1 -1
- package/dist/providers/google-oauth-provider.d.ts +39 -0
- package/dist/providers/google-oauth-provider.d.ts.map +1 -0
- package/dist/providers/google-oauth-provider.js +161 -0
- package/dist/providers/google-oauth-provider.js.map +1 -0
- package/dist/providers/kilo.d.ts +0 -3
- package/dist/providers/kilo.d.ts.map +1 -1
- package/dist/providers/kilo.js +69 -123
- package/dist/providers/kilo.js.map +1 -1
- package/dist/providers/kimi-code.d.ts +5 -0
- package/dist/providers/kimi-code.d.ts.map +1 -1
- package/dist/providers/kimi-code.js +91 -136
- package/dist/providers/kimi-code.js.map +1 -1
- package/dist/providers/mimo.d.ts.map +1 -1
- package/dist/providers/mimo.js +55 -94
- package/dist/providers/mimo.js.map +1 -1
- package/dist/providers/minimax-coding-plan.d.ts +2 -23
- package/dist/providers/minimax-coding-plan.d.ts.map +1 -1
- package/dist/providers/minimax-coding-plan.js +161 -277
- package/dist/providers/minimax-coding-plan.js.map +1 -1
- package/dist/providers/nanogpt.d.ts +0 -3
- package/dist/providers/nanogpt.d.ts.map +1 -1
- package/dist/providers/nanogpt.js +61 -95
- package/dist/providers/nanogpt.js.map +1 -1
- package/dist/providers/ollama-cloud.d.ts +0 -6
- package/dist/providers/ollama-cloud.d.ts.map +1 -1
- package/dist/providers/ollama-cloud.js +86 -112
- package/dist/providers/ollama-cloud.js.map +1 -1
- package/dist/providers/openai.d.ts.map +1 -1
- package/dist/providers/openai.js +144 -166
- package/dist/providers/openai.js.map +1 -1
- package/dist/providers/opencode-go.d.ts.map +1 -1
- package/dist/providers/opencode-go.js +157 -199
- package/dist/providers/opencode-go.js.map +1 -1
- package/dist/providers/opencode-zen.d.ts.map +1 -1
- package/dist/providers/opencode-zen.js +100 -150
- package/dist/providers/opencode-zen.js.map +1 -1
- package/dist/providers/openrouter.d.ts +0 -3
- package/dist/providers/openrouter.d.ts.map +1 -1
- package/dist/providers/openrouter.js +26 -13
- package/dist/providers/openrouter.js.map +1 -1
- package/dist/providers/quota-providers.d.ts +7 -3
- package/dist/providers/quota-providers.d.ts.map +1 -1
- package/dist/providers/quota-providers.js +111 -217
- package/dist/providers/quota-providers.js.map +1 -1
- package/dist/providers/registry.d.ts.map +1 -1
- package/dist/providers/registry.js +1 -2
- package/dist/providers/registry.js.map +1 -1
- package/dist/providers/result-helpers.d.ts +10 -37
- package/dist/providers/result-helpers.d.ts.map +1 -1
- package/dist/providers/result-helpers.js +23 -56
- package/dist/providers/result-helpers.js.map +1 -1
- package/dist/providers/synthetic.d.ts +0 -3
- package/dist/providers/synthetic.d.ts.map +1 -1
- package/dist/providers/synthetic.js +16 -58
- package/dist/providers/synthetic.js.map +1 -1
- package/dist/providers/xai.d.ts +2 -1
- package/dist/providers/xai.d.ts.map +1 -1
- package/dist/providers/xai.js +49 -71
- package/dist/providers/xai.js.map +1 -1
- package/dist/rpc.d.ts +110 -38
- package/dist/rpc.d.ts.map +1 -1
- package/dist/rpc.js +125 -37
- package/dist/rpc.js.map +1 -1
- package/dist/settings/convert.d.ts +60 -0
- package/dist/settings/convert.d.ts.map +1 -0
- package/dist/settings/convert.js +473 -0
- package/dist/settings/convert.js.map +1 -0
- package/dist/settings/file.d.ts +89 -0
- package/dist/settings/file.d.ts.map +1 -0
- package/dist/settings/file.js +452 -0
- package/dist/settings/file.js.map +1 -0
- package/dist/settings/index.d.ts +10 -0
- package/dist/settings/index.d.ts.map +1 -0
- package/dist/settings/index.js +3 -0
- package/dist/settings/index.js.map +1 -0
- package/dist/settings/schema.d.ts +607 -0
- package/dist/settings/schema.d.ts.map +1 -0
- package/dist/settings/schema.js +479 -0
- package/dist/settings/schema.js.map +1 -0
- package/dist/tui/client.d.ts +86 -0
- package/dist/tui/client.d.ts.map +1 -0
- package/dist/tui/client.js +229 -0
- package/dist/tui/client.js.map +1 -0
- package/dist/tui/display.d.ts +127 -0
- package/dist/tui/display.d.ts.map +1 -0
- package/dist/tui/display.js +474 -0
- package/dist/tui/display.js.map +1 -0
- package/dist/tui/index.d.ts +21 -0
- package/dist/tui/index.d.ts.map +1 -0
- package/dist/tui/index.js +305 -0
- package/dist/tui/server-settings.d.ts +43 -0
- package/dist/tui/server-settings.d.ts.map +1 -0
- package/dist/tui/server-settings.js +233 -0
- package/dist/tui/server-settings.js.map +1 -0
- package/dist/tui/sidebar.d.ts +41 -0
- package/dist/tui/sidebar.d.ts.map +1 -0
- package/dist/tui/sidebar.js +148 -0
- package/dist/tui/status-line.d.ts +14 -0
- package/dist/tui/status-line.d.ts.map +1 -0
- package/dist/tui/status-line.js +225 -0
- package/dist/tui/theme.d.ts +41 -0
- package/dist/tui/theme.d.ts.map +1 -0
- package/dist/tui/theme.js +52 -0
- package/dist/tui/theme.js.map +1 -0
- package/dist/tui/toast-colors.d.ts +37 -0
- package/dist/tui/toast-colors.d.ts.map +1 -0
- package/dist/tui/toast-colors.js +77 -0
- package/dist/tui/toasts.d.ts +59 -0
- package/dist/tui/toasts.d.ts.map +1 -0
- package/dist/tui/toasts.js +148 -0
- package/dist/tui/toasts.js.map +1 -0
- package/dist/tui/window/drag.d.ts +26 -0
- package/dist/tui/window/drag.d.ts.map +1 -0
- package/dist/tui/window/drag.js +80 -0
- package/dist/tui/window/drag.js.map +1 -0
- package/dist/tui/window/limits.d.ts +15 -0
- package/dist/tui/window/limits.d.ts.map +1 -0
- package/dist/tui/window/limits.js +227 -0
- package/dist/tui/window/limits.js.map +1 -0
- package/dist/tui/window/rail.d.ts +21 -0
- package/dist/tui/window/rail.d.ts.map +1 -0
- package/dist/tui/window/rail.js +81 -0
- package/dist/tui/window/settings.d.ts +164 -0
- package/dist/tui/window/settings.d.ts.map +1 -0
- package/dist/tui/window/settings.js +1679 -0
- package/dist/tui/window/shell.d.ts +108 -0
- package/dist/tui/window/shell.d.ts.map +1 -0
- package/dist/tui/window/shell.js +1003 -0
- package/dist/tui/window/usage.d.ts +75 -0
- package/dist/tui/window/usage.d.ts.map +1 -0
- package/dist/tui/window/usage.js +928 -0
- package/dist/tui/window/views.d.ts +61 -0
- package/dist/tui/window/views.d.ts.map +1 -0
- package/dist/tui/window/views.js +242 -0
- package/dist/tui.d.ts +1 -5
- package/dist/tui.js +1 -894
- package/dist/view/columns.d.ts +15 -0
- package/dist/view/columns.d.ts.map +1 -0
- package/dist/view/columns.js +48 -0
- package/dist/view/columns.js.map +1 -0
- package/dist/view/format.d.ts +82 -0
- package/dist/view/format.d.ts.map +1 -0
- package/dist/view/format.js +201 -0
- package/dist/view/format.js.map +1 -0
- package/dist/view/quick-look.d.ts +28 -0
- package/dist/view/quick-look.d.ts.map +1 -0
- package/dist/view/quick-look.js +150 -0
- package/dist/view/quick-look.js.map +1 -0
- package/dist/view/select.d.ts +67 -0
- package/dist/view/select.d.ts.map +1 -0
- package/dist/view/select.js +87 -0
- package/dist/view/select.js.map +1 -0
- package/dist/view/text.d.ts +153 -0
- package/dist/view/text.d.ts.map +1 -0
- package/dist/view/text.js +642 -0
- package/dist/view/text.js.map +1 -0
- package/dist/view/usage.d.ts +86 -0
- package/dist/view/usage.d.ts.map +1 -0
- package/dist/view/usage.js +724 -0
- package/dist/view/usage.js.map +1 -0
- package/package.json +4 -6
- package/dist/lib/chutes-config.d.ts +0 -15
- package/dist/lib/chutes-config.d.ts.map +0 -1
- package/dist/lib/chutes-config.js +0 -28
- package/dist/lib/chutes-config.js.map +0 -1
- package/dist/lib/cli-status.d.ts +0 -8
- package/dist/lib/cli-status.d.ts.map +0 -1
- package/dist/lib/cli-status.js +0 -98
- package/dist/lib/cli-status.js.map +0 -1
- package/dist/lib/command-parsing.d.ts +0 -43
- package/dist/lib/command-parsing.d.ts.map +0 -1
- package/dist/lib/command-parsing.js +0 -103
- package/dist/lib/command-parsing.js.map +0 -1
- package/dist/lib/config.d.ts +0 -63
- package/dist/lib/config.d.ts.map +0 -1
- package/dist/lib/config.js +0 -1092
- package/dist/lib/config.js.map +0 -1
- package/dist/lib/deepseek-auth.d.ts +0 -15
- package/dist/lib/deepseek-auth.d.ts.map +0 -1
- package/dist/lib/deepseek-auth.js +0 -28
- package/dist/lib/deepseek-auth.js.map +0 -1
- package/dist/lib/format.d.ts +0 -25
- package/dist/lib/format.d.ts.map +0 -1
- package/dist/lib/format.js +0 -300
- package/dist/lib/format.js.map +0 -1
- package/dist/lib/google-agy-companion.d.ts +0 -8
- package/dist/lib/google-agy-companion.d.ts.map +0 -1
- package/dist/lib/google-agy-companion.js +0 -64
- package/dist/lib/google-agy-companion.js.map +0 -1
- package/dist/lib/google-gemini-cli-companion.d.ts +0 -8
- package/dist/lib/google-gemini-cli-companion.d.ts.map +0 -1
- package/dist/lib/google-gemini-cli-companion.js +0 -64
- package/dist/lib/google-gemini-cli-companion.js.map +0 -1
- package/dist/lib/grouped-entry-normalization.d.ts +0 -12
- package/dist/lib/grouped-entry-normalization.d.ts.map +0 -1
- package/dist/lib/grouped-entry-normalization.js +0 -124
- package/dist/lib/grouped-entry-normalization.js.map +0 -1
- package/dist/lib/grouped-header-format.d.ts +0 -5
- package/dist/lib/grouped-header-format.d.ts.map +0 -1
- package/dist/lib/grouped-header-format.js +0 -16
- package/dist/lib/grouped-header-format.js.map +0 -1
- package/dist/lib/init-installer.d.ts +0 -123
- package/dist/lib/init-installer.d.ts.map +0 -1
- package/dist/lib/init-installer.js +0 -1111
- package/dist/lib/init-installer.js.map +0 -1
- package/dist/lib/jsonc.d.ts +0 -17
- package/dist/lib/jsonc.d.ts.map +0 -1
- package/dist/lib/jsonc.js +0 -23
- package/dist/lib/jsonc.js.map +0 -1
- package/dist/lib/kilo-config.d.ts +0 -15
- package/dist/lib/kilo-config.d.ts.map +0 -1
- package/dist/lib/kilo-config.js +0 -27
- package/dist/lib/kilo-config.js.map +0 -1
- package/dist/lib/kimi-auth.d.ts +0 -49
- package/dist/lib/kimi-auth.d.ts.map +0 -1
- package/dist/lib/kimi-auth.js +0 -82
- package/dist/lib/kimi-auth.js.map +0 -1
- package/dist/lib/markdown-table.d.ts +0 -51
- package/dist/lib/markdown-table.d.ts.map +0 -1
- package/dist/lib/markdown-table.js +0 -179
- package/dist/lib/markdown-table.js.map +0 -1
- package/dist/lib/minimax-auth.d.ts +0 -62
- package/dist/lib/minimax-auth.d.ts.map +0 -1
- package/dist/lib/minimax-auth.js +0 -206
- package/dist/lib/minimax-auth.js.map +0 -1
- package/dist/lib/nanogpt-config.d.ts +0 -15
- package/dist/lib/nanogpt-config.d.ts.map +0 -1
- package/dist/lib/nanogpt-config.js +0 -31
- package/dist/lib/nanogpt-config.js.map +0 -1
- package/dist/lib/ollama-cloud-config.d.ts +0 -15
- package/dist/lib/ollama-cloud-config.d.ts.map +0 -1
- package/dist/lib/ollama-cloud-config.js +0 -27
- package/dist/lib/ollama-cloud-config.js.map +0 -1
- package/dist/lib/opencode-go-auth.d.ts +0 -16
- package/dist/lib/opencode-go-auth.d.ts.map +0 -1
- package/dist/lib/opencode-go-auth.js +0 -42
- package/dist/lib/opencode-go-auth.js.map +0 -1
- package/dist/lib/opencode-version.d.ts +0 -6
- package/dist/lib/opencode-version.d.ts.map +0 -1
- package/dist/lib/opencode-version.js +0 -26
- package/dist/lib/opencode-version.js.map +0 -1
- package/dist/lib/provider-add-command.d.ts +0 -35
- package/dist/lib/provider-add-command.d.ts.map +0 -1
- package/dist/lib/provider-add-command.js +0 -204
- package/dist/lib/provider-add-command.js.map +0 -1
- package/dist/lib/provider-add-json-v1-questionnaire.d.ts +0 -32
- package/dist/lib/provider-add-json-v1-questionnaire.d.ts.map +0 -1
- package/dist/lib/provider-add-json-v1-questionnaire.js +0 -336
- package/dist/lib/provider-add-json-v1-questionnaire.js.map +0 -1
- package/dist/lib/provider-add.d.ts +0 -21
- package/dist/lib/provider-add.d.ts.map +0 -1
- package/dist/lib/provider-add.js +0 -219
- package/dist/lib/provider-add.js.map +0 -1
- package/dist/lib/quota-accounting-projection.d.ts +0 -9
- package/dist/lib/quota-accounting-projection.d.ts.map +0 -1
- package/dist/lib/quota-accounting-projection.js +0 -254
- package/dist/lib/quota-accounting-projection.js.map +0 -1
- package/dist/lib/quota-columns.d.ts +0 -38
- package/dist/lib/quota-columns.d.ts.map +0 -1
- package/dist/lib/quota-columns.js +0 -129
- package/dist/lib/quota-columns.js.map +0 -1
- package/dist/lib/quota-command-format.d.ts +0 -34
- package/dist/lib/quota-command-format.d.ts.map +0 -1
- package/dist/lib/quota-command-format.js +0 -240
- package/dist/lib/quota-command-format.js.map +0 -1
- package/dist/lib/quota-dialog-command-specs.d.ts +0 -63
- package/dist/lib/quota-dialog-command-specs.d.ts.map +0 -1
- package/dist/lib/quota-dialog-command-specs.js +0 -145
- package/dist/lib/quota-dialog-command-specs.js.map +0 -1
- package/dist/lib/quota-entry-display.d.ts +0 -7
- package/dist/lib/quota-entry-display.d.ts.map +0 -1
- package/dist/lib/quota-entry-display.js +0 -59
- package/dist/lib/quota-entry-display.js.map +0 -1
- package/dist/lib/quota-exhaustion-projection.d.ts +0 -11
- package/dist/lib/quota-exhaustion-projection.d.ts.map +0 -1
- package/dist/lib/quota-exhaustion-projection.js +0 -105
- package/dist/lib/quota-exhaustion-projection.js.map +0 -1
- package/dist/lib/quota-format-style.d.ts +0 -21
- package/dist/lib/quota-format-style.d.ts.map +0 -1
- package/dist/lib/quota-format-style.js +0 -38
- package/dist/lib/quota-format-style.js.map +0 -1
- package/dist/lib/quota-render-data.d.ts +0 -102
- package/dist/lib/quota-render-data.d.ts.map +0 -1
- package/dist/lib/quota-render-data.js +0 -419
- package/dist/lib/quota-render-data.js.map +0 -1
- package/dist/lib/quota-reset-notifications.d.ts +0 -20
- package/dist/lib/quota-reset-notifications.d.ts.map +0 -1
- package/dist/lib/quota-reset-notifications.js +0 -331
- package/dist/lib/quota-reset-notifications.js.map +0 -1
- package/dist/lib/quota-runtime-context.d.ts +0 -54
- package/dist/lib/quota-runtime-context.d.ts.map +0 -1
- package/dist/lib/quota-runtime-context.js +0 -110
- package/dist/lib/quota-runtime-context.js.map +0 -1
- package/dist/lib/quota-state-codec.d.ts +0 -16
- package/dist/lib/quota-state-codec.d.ts.map +0 -1
- package/dist/lib/quota-state-codec.js +0 -412
- package/dist/lib/quota-state-codec.js.map +0 -1
- package/dist/lib/quota-state.d.ts +0 -28
- package/dist/lib/quota-state.d.ts.map +0 -1
- package/dist/lib/quota-state.js +0 -473
- package/dist/lib/quota-state.js.map +0 -1
- package/dist/lib/quota-stats-format.d.ts +0 -29
- package/dist/lib/quota-stats-format.d.ts.map +0 -1
- package/dist/lib/quota-stats-format.js +0 -459
- package/dist/lib/quota-stats-format.js.map +0 -1
- package/dist/lib/quota-stats.d.ts +0 -126
- package/dist/lib/quota-stats.d.ts.map +0 -1
- package/dist/lib/quota-stats.js +0 -695
- package/dist/lib/quota-stats.js.map +0 -1
- package/dist/lib/quota-surface-data.d.ts +0 -21
- package/dist/lib/quota-surface-data.d.ts.map +0 -1
- package/dist/lib/quota-surface-data.js +0 -362
- package/dist/lib/quota-surface-data.js.map +0 -1
- package/dist/lib/scoped-update-migration.d.ts +0 -103
- package/dist/lib/scoped-update-migration.d.ts.map +0 -1
- package/dist/lib/scoped-update-migration.js +0 -344
- package/dist/lib/scoped-update-migration.js.map +0 -1
- package/dist/lib/scoped-update.d.ts +0 -77
- package/dist/lib/scoped-update.d.ts.map +0 -1
- package/dist/lib/scoped-update.js +0 -590
- package/dist/lib/scoped-update.js.map +0 -1
- package/dist/lib/session-tokens-format.d.ts +0 -36
- package/dist/lib/session-tokens-format.d.ts.map +0 -1
- package/dist/lib/session-tokens-format.js +0 -111
- package/dist/lib/session-tokens-format.js.map +0 -1
- package/dist/lib/synthetic-config.d.ts +0 -15
- package/dist/lib/synthetic-config.d.ts.map +0 -1
- package/dist/lib/synthetic-config.js +0 -28
- package/dist/lib/synthetic-config.js.map +0 -1
- package/dist/lib/toast-format-grouped.d.ts +0 -25
- package/dist/lib/toast-format-grouped.d.ts.map +0 -1
- package/dist/lib/toast-format-grouped.js +0 -296
- package/dist/lib/toast-format-grouped.js.map +0 -1
- package/dist/lib/tui-compact-format.d.ts +0 -10
- package/dist/lib/tui-compact-format.d.ts.map +0 -1
- package/dist/lib/tui-compact-format.js +0 -460
- package/dist/lib/tui-compact-format.js.map +0 -1
- package/dist/lib/tui-panel-state.d.ts +0 -15
- package/dist/lib/tui-panel-state.d.ts.map +0 -1
- package/dist/lib/tui-panel-state.js +0 -2
- package/dist/lib/tui-panel-state.js.map +0 -1
- package/dist/lib/tui-prompt-bar-format.d.ts +0 -25
- package/dist/lib/tui-prompt-bar-format.d.ts.map +0 -1
- package/dist/lib/tui-prompt-bar-format.js +0 -206
- package/dist/lib/tui-prompt-bar-format.js.map +0 -1
- package/dist/lib/tui-sidebar-format.d.ts +0 -13
- package/dist/lib/tui-sidebar-format.d.ts.map +0 -1
- package/dist/lib/tui-sidebar-format.js +0 -34
- package/dist/lib/tui-sidebar-format.js.map +0 -1
- package/dist/lib/zai-auth.d.ts +0 -15
- package/dist/lib/zai-auth.d.ts.map +0 -1
- package/dist/lib/zai-auth.js +0 -37
- package/dist/lib/zai-auth.js.map +0 -1
- package/dist/lib/zai.d.ts +0 -5
- package/dist/lib/zai.d.ts.map +0 -1
- package/dist/lib/zai.js +0 -14
- package/dist/lib/zai.js.map +0 -1
- package/dist/lib/zhipu-auth.d.ts +0 -15
- package/dist/lib/zhipu-auth.d.ts.map +0 -1
- package/dist/lib/zhipu-auth.js +0 -42
- package/dist/lib/zhipu-auth.js.map +0 -1
- package/dist/lib/zhipu.d.ts +0 -5
- package/dist/lib/zhipu.d.ts.map +0 -1
- package/dist/lib/zhipu.js +0 -13
- package/dist/lib/zhipu.js.map +0 -1
- package/dist/providers/glm-coding-plan-provider.d.ts +0 -56
- package/dist/providers/glm-coding-plan-provider.d.ts.map +0 -1
- package/dist/providers/glm-coding-plan-provider.js +0 -115
- package/dist/providers/glm-coding-plan-provider.js.map +0 -1
- package/dist/providers/google-account-format.d.ts +0 -7
- package/dist/providers/google-account-format.d.ts.map +0 -1
- package/dist/providers/google-account-format.js +0 -52
- package/dist/providers/google-account-format.js.map +0 -1
- package/dist/providers/zai.d.ts +0 -2
- package/dist/providers/zai.d.ts.map +0 -1
- package/dist/providers/zai.js +0 -24
- package/dist/providers/zai.js.map +0 -1
- package/dist/providers/zhipu.d.ts +0 -2
- package/dist/providers/zhipu.d.ts.map +0 -1
- package/dist/providers/zhipu.js +0 -21
- package/dist/providers/zhipu.js.map +0 -1
- package/dist/tui-v2.d.ts +0 -5
- package/dist/tui-v2.d.ts.map +0 -1
- package/dist/tui-v2.jsx +0 -624
- package/dist/tui-v2.jsx.map +0 -1
- package/dist/tui.d.ts.map +0 -1
package/dist/lib/quota-status.js
CHANGED
|
@@ -1,21 +1,130 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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 {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
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
|
-
|
|
56
|
-
|
|
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
|
-
|
|
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
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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
|
-
|
|
88
|
-
|
|
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
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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
|
-
|
|
100
|
-
|
|
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
|
|
103
|
-
|
|
104
|
-
return probe ? sanitizeQuotaProviderResult(probe.result).statusDetails : undefined;
|
|
174
|
+
function joinOrNone(values) {
|
|
175
|
+
return values.length > 0 ? values.join(" | ") : "(none)";
|
|
105
176
|
}
|
|
106
|
-
function
|
|
107
|
-
|
|
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
|
-
|
|
114
|
-
|
|
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
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
return
|
|
123
|
-
|
|
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
|
-
|
|
146
|
-
|
|
147
|
-
|
|
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
|
-
|
|
154
|
-
|
|
155
|
-
|
|
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
|
-
*
|
|
171
|
-
*
|
|
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
|
|
174
|
-
|
|
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
|
-
|
|
177
|
-
|
|
178
|
-
const
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
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 (
|
|
199
|
-
|
|
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
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
return
|
|
209
|
-
}
|
|
210
|
-
function
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
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
|
-
|
|
224
|
-
|
|
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
|
-
|
|
230
|
-
|
|
231
|
-
const
|
|
232
|
-
if (
|
|
233
|
-
|
|
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
|
-
|
|
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
|
-
|
|
344
|
+
if (unknownKeys > 0) {
|
|
320
345
|
rows.push({
|
|
321
|
-
key:
|
|
322
|
-
value:
|
|
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
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
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
|
-
|
|
465
|
-
|
|
466
|
-
|
|
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
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
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
|
-
|
|
488
|
-
|
|
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
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
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.
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
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
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
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
|
-
|
|
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
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
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
|
-
|
|
540
|
-
|
|
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
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
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
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
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
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
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
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
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
|
-
|
|
606
|
-
|
|
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
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
if (
|
|
631
|
-
|
|
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
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
}
|
|
640
|
-
|
|
641
|
-
{
|
|
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 (
|
|
672
|
-
|
|
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
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
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
|
-
|
|
695
|
-
if (
|
|
696
|
-
const
|
|
697
|
-
|
|
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
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
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
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
const
|
|
726
|
-
const
|
|
727
|
-
const
|
|
728
|
-
const
|
|
729
|
-
const
|
|
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}
|
|
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 (
|
|
740
|
-
|
|
1136
|
+
if (configured === "bundled") {
|
|
1137
|
+
rows.push({
|
|
741
1138
|
key: "selection_note",
|
|
742
|
-
value: "bundled
|
|
1139
|
+
value: "bundled: the built-in price list is used and downloaded prices are ignored",
|
|
743
1140
|
});
|
|
744
1141
|
}
|
|
745
|
-
else if (
|
|
746
|
-
|
|
1142
|
+
else if (configured === "runtime" && activeSource !== "runtime") {
|
|
1143
|
+
rows.push({
|
|
747
1144
|
key: "selection_note",
|
|
748
|
-
value: "runtime
|
|
1145
|
+
value: "runtime: no valid downloaded price list, so the built-in one is used",
|
|
749
1146
|
});
|
|
750
1147
|
}
|
|
751
|
-
|
|
1148
|
+
rows.push({
|
|
752
1149
|
key: "runtime_paths",
|
|
753
|
-
value: `snapshot=${
|
|
754
|
-
}
|
|
755
|
-
pricingRows.push({
|
|
1150
|
+
value: `snapshot=${pathText(getRuntimePricingSnapshotPath(), homeDir)} refresh_state=${pathText(getRuntimePricingRefreshStatePath(), homeDir)}`,
|
|
1151
|
+
}, {
|
|
756
1152
|
key: "staleness",
|
|
757
|
-
value: `age_ms=${
|
|
758
|
-
}
|
|
759
|
-
|
|
760
|
-
|
|
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 (
|
|
764
|
-
|
|
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=${
|
|
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 (
|
|
769
|
-
|
|
1165
|
+
if (refreshState.lastError) {
|
|
1166
|
+
rows.push({ key: "refresh_error", value: errorKind(classifyError(refreshState.lastError)) });
|
|
770
1167
|
}
|
|
771
1168
|
}
|
|
772
1169
|
else {
|
|
773
|
-
|
|
1170
|
+
rows.push({ key: "refresh", value: "(no runtime refresh state yet)" });
|
|
774
1171
|
}
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
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:
|
|
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
|
}
|