@slkiser/opencode-quota 4.10.6 → 5.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +79 -127
- package/dist/bin/opencode-quota.d.ts +6 -0
- package/dist/bin/opencode-quota.d.ts.map +1 -1
- package/dist/bin/opencode-quota.js +17 -0
- package/dist/bin/opencode-quota.js.map +1 -1
- package/dist/data/modelsdev-pricing.min.json +83 -2
- package/dist/index.d.ts +1 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -13
- package/dist/index.js.map +1 -1
- package/dist/lib/accounting-format.js +1 -1
- package/dist/lib/accounting-format.js.map +1 -1
- package/dist/lib/alibaba-auth.d.ts +5 -5
- package/dist/lib/alibaba-auth.d.ts.map +1 -1
- package/dist/lib/alibaba-auth.js +15 -8
- package/dist/lib/alibaba-auth.js.map +1 -1
- package/dist/lib/alibaba-token-plan.d.ts +7 -4
- package/dist/lib/alibaba-token-plan.d.ts.map +1 -1
- package/dist/lib/alibaba-token-plan.js +32 -10
- package/dist/lib/alibaba-token-plan.js.map +1 -1
- package/dist/lib/anthropic-auth.d.ts +4 -1
- package/dist/lib/anthropic-auth.d.ts.map +1 -1
- package/dist/lib/anthropic-auth.js +5 -1
- package/dist/lib/anthropic-auth.js.map +1 -1
- package/dist/lib/anthropic.d.ts +7 -1
- package/dist/lib/anthropic.d.ts.map +1 -1
- package/dist/lib/anthropic.js +61 -7
- package/dist/lib/anthropic.js.map +1 -1
- package/dist/lib/api-key-resolver.d.ts +27 -16
- package/dist/lib/api-key-resolver.d.ts.map +1 -1
- package/dist/lib/api-key-resolver.js +46 -19
- package/dist/lib/api-key-resolver.js.map +1 -1
- package/dist/lib/chutes-config.d.ts +2 -2
- package/dist/lib/chutes-config.d.ts.map +1 -1
- package/dist/lib/chutes-config.js +4 -4
- package/dist/lib/chutes-config.js.map +1 -1
- package/dist/lib/cli-reports.d.ts +30 -0
- package/dist/lib/cli-reports.d.ts.map +1 -0
- package/dist/lib/cli-reports.js +172 -0
- package/dist/lib/cli-reports.js.map +1 -0
- package/dist/lib/cli-show.d.ts +14 -1
- package/dist/lib/cli-show.d.ts.map +1 -1
- package/dist/lib/cli-show.js +40 -138
- package/dist/lib/cli-show.js.map +1 -1
- package/dist/lib/cli-status.d.ts.map +1 -1
- package/dist/lib/cli-status.js +9 -36
- package/dist/lib/cli-status.js.map +1 -1
- package/dist/lib/config-file-utils.d.ts +10 -13
- package/dist/lib/config-file-utils.d.ts.map +1 -1
- package/dist/lib/config-file-utils.js +28 -30
- package/dist/lib/config-file-utils.js.map +1 -1
- package/dist/lib/config.d.ts +2 -2
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +6 -17
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/copilot.d.ts +4 -1
- package/dist/lib/copilot.d.ts.map +1 -1
- package/dist/lib/copilot.js +17 -19
- package/dist/lib/copilot.js.map +1 -1
- package/dist/lib/cursor-detection.d.ts +1 -1
- package/dist/lib/cursor-detection.d.ts.map +1 -1
- package/dist/lib/cursor-detection.js +54 -51
- package/dist/lib/cursor-detection.js.map +1 -1
- package/dist/lib/cursor-pricing.d.ts +1 -1
- package/dist/lib/cursor-pricing.d.ts.map +1 -1
- package/dist/lib/cursor-pricing.js +85 -0
- package/dist/lib/cursor-pricing.js.map +1 -1
- package/dist/lib/deepseek-auth.d.ts +2 -2
- package/dist/lib/deepseek-auth.d.ts.map +1 -1
- package/dist/lib/deepseek-auth.js +4 -4
- package/dist/lib/deepseek-auth.js.map +1 -1
- package/dist/lib/display-sanitize.js +1 -1
- package/dist/lib/display-sanitize.js.map +1 -1
- package/dist/lib/entries.d.ts +7 -2
- package/dist/lib/entries.d.ts.map +1 -1
- package/dist/lib/format-utils.d.ts +1 -5
- package/dist/lib/format-utils.d.ts.map +1 -1
- package/dist/lib/format-utils.js +1 -4
- package/dist/lib/format-utils.js.map +1 -1
- package/dist/lib/glm-coding-plan.d.ts +1 -0
- package/dist/lib/glm-coding-plan.d.ts.map +1 -1
- package/dist/lib/glm-coding-plan.js +3 -1
- package/dist/lib/glm-coding-plan.js.map +1 -1
- package/dist/lib/google-agy.d.ts +6 -12
- package/dist/lib/google-agy.d.ts.map +1 -1
- package/dist/lib/google-agy.js +26 -24
- package/dist/lib/google-agy.js.map +1 -1
- package/dist/lib/google-companion-credentials.d.ts.map +1 -1
- package/dist/lib/google-companion-credentials.js +76 -26
- package/dist/lib/google-companion-credentials.js.map +1 -1
- package/dist/lib/google-gemini-cli.d.ts +7 -12
- package/dist/lib/google-gemini-cli.d.ts.map +1 -1
- package/dist/lib/google-gemini-cli.js +42 -31
- package/dist/lib/google-gemini-cli.js.map +1 -1
- package/dist/lib/init-installer.d.ts +3 -1
- package/dist/lib/init-installer.d.ts.map +1 -1
- package/dist/lib/init-installer.js +71 -174
- package/dist/lib/init-installer.js.map +1 -1
- package/dist/lib/kilo-config.d.ts +2 -2
- package/dist/lib/kilo-config.d.ts.map +1 -1
- package/dist/lib/kilo-config.js +6 -5
- package/dist/lib/kilo-config.js.map +1 -1
- package/dist/lib/kimi-auth.d.ts +2 -2
- package/dist/lib/kimi-auth.d.ts.map +1 -1
- package/dist/lib/kimi-auth.js +4 -4
- package/dist/lib/kimi-auth.js.map +1 -1
- package/dist/lib/maintainer-announcements.d.ts.map +1 -1
- package/dist/lib/maintainer-announcements.js +5 -5
- package/dist/lib/maintainer-announcements.js.map +1 -1
- package/dist/lib/markdown-table.d.ts +36 -0
- package/dist/lib/markdown-table.d.ts.map +1 -1
- package/dist/lib/markdown-table.js +56 -0
- package/dist/lib/markdown-table.js.map +1 -1
- package/dist/lib/mimo-config.d.ts.map +1 -1
- package/dist/lib/mimo-config.js +2 -3
- package/dist/lib/mimo-config.js.map +1 -1
- package/dist/lib/minimax-auth.d.ts +6 -6
- package/dist/lib/minimax-auth.d.ts.map +1 -1
- package/dist/lib/minimax-auth.js +18 -17
- package/dist/lib/minimax-auth.js.map +1 -1
- package/dist/lib/nanogpt-config.d.ts +2 -2
- package/dist/lib/nanogpt-config.d.ts.map +1 -1
- package/dist/lib/nanogpt-config.js +4 -4
- package/dist/lib/nanogpt-config.js.map +1 -1
- package/dist/lib/ollama-cloud-config.d.ts +2 -2
- package/dist/lib/ollama-cloud-config.d.ts.map +1 -1
- package/dist/lib/ollama-cloud-config.js +6 -5
- package/dist/lib/ollama-cloud-config.js.map +1 -1
- package/dist/lib/openai.d.ts +7 -2
- package/dist/lib/openai.d.ts.map +1 -1
- package/dist/lib/openai.js +22 -8
- package/dist/lib/openai.js.map +1 -1
- package/dist/lib/opencode-auth-sqlite.d.ts +17 -0
- package/dist/lib/opencode-auth-sqlite.d.ts.map +1 -0
- package/dist/lib/opencode-auth-sqlite.js +105 -0
- package/dist/lib/opencode-auth-sqlite.js.map +1 -0
- package/dist/lib/opencode-auth.d.ts +127 -14
- package/dist/lib/opencode-auth.d.ts.map +1 -1
- package/dist/lib/opencode-auth.js +374 -46
- package/dist/lib/opencode-auth.js.map +1 -1
- package/dist/lib/opencode-config-providers.d.ts.map +1 -1
- package/dist/lib/opencode-config-providers.js +17 -18
- package/dist/lib/opencode-config-providers.js.map +1 -1
- package/dist/lib/opencode-console-auth.d.ts +47 -0
- package/dist/lib/opencode-console-auth.d.ts.map +1 -0
- package/dist/lib/opencode-console-auth.js +63 -0
- package/dist/lib/opencode-console-auth.js.map +1 -0
- package/dist/lib/opencode-db-path.d.ts +9 -0
- package/dist/lib/opencode-db-path.d.ts.map +1 -0
- package/dist/lib/opencode-db-path.js +16 -0
- package/dist/lib/opencode-db-path.js.map +1 -0
- package/dist/lib/opencode-go-auth.d.ts +4 -2
- package/dist/lib/opencode-go-auth.d.ts.map +1 -1
- package/dist/lib/opencode-go-auth.js +9 -5
- package/dist/lib/opencode-go-auth.js.map +1 -1
- package/dist/lib/opencode-go.d.ts +12 -0
- package/dist/lib/opencode-go.d.ts.map +1 -1
- package/dist/lib/opencode-go.js +113 -0
- package/dist/lib/opencode-go.js.map +1 -1
- package/dist/lib/opencode-runtime-paths.d.ts +6 -12
- package/dist/lib/opencode-runtime-paths.d.ts.map +1 -1
- package/dist/lib/opencode-runtime-paths.js +12 -82
- package/dist/lib/opencode-runtime-paths.js.map +1 -1
- package/dist/lib/opencode-sqlite.d.ts +4 -0
- package/dist/lib/opencode-sqlite.d.ts.map +1 -1
- package/dist/lib/opencode-sqlite.js +17 -52
- package/dist/lib/opencode-sqlite.js.map +1 -1
- package/dist/lib/opencode-storage.d.ts +4 -8
- package/dist/lib/opencode-storage.d.ts.map +1 -1
- package/dist/lib/opencode-storage.js +66 -90
- package/dist/lib/opencode-storage.js.map +1 -1
- package/dist/lib/opencode-version.d.ts +6 -0
- package/dist/lib/opencode-version.d.ts.map +1 -0
- package/dist/lib/opencode-version.js +26 -0
- package/dist/lib/opencode-version.js.map +1 -0
- package/dist/lib/opencode-zen.d.ts +10 -4
- package/dist/lib/opencode-zen.d.ts.map +1 -1
- package/dist/lib/opencode-zen.js +11 -11
- package/dist/lib/opencode-zen.js.map +1 -1
- package/dist/lib/provider-registration.d.ts +3 -4
- package/dist/lib/provider-registration.d.ts.map +1 -1
- package/dist/lib/provider-registration.js +3 -4
- package/dist/lib/provider-registration.js.map +1 -1
- package/dist/lib/quota-columns.d.ts +38 -0
- package/dist/lib/quota-columns.d.ts.map +1 -0
- package/dist/lib/quota-columns.js +129 -0
- package/dist/lib/quota-columns.js.map +1 -0
- package/dist/lib/quota-command-format.d.ts +11 -0
- package/dist/lib/quota-command-format.d.ts.map +1 -1
- package/dist/lib/quota-command-format.js +80 -27
- package/dist/lib/quota-command-format.js.map +1 -1
- package/dist/lib/quota-dialog-command-specs.d.ts +63 -0
- package/dist/lib/quota-dialog-command-specs.d.ts.map +1 -0
- package/dist/lib/quota-dialog-command-specs.js +145 -0
- package/dist/lib/quota-dialog-command-specs.js.map +1 -0
- package/dist/lib/quota-dialog-commands.d.ts +3 -25
- package/dist/lib/quota-dialog-commands.d.ts.map +1 -1
- package/dist/lib/quota-dialog-commands.js +125 -231
- package/dist/lib/quota-dialog-commands.js.map +1 -1
- package/dist/lib/quota-export.d.ts.map +1 -1
- package/dist/lib/quota-export.js +1 -0
- package/dist/lib/quota-export.js.map +1 -1
- package/dist/lib/quota-providers-remote.d.ts +2 -2
- package/dist/lib/quota-providers-remote.d.ts.map +1 -1
- package/dist/lib/quota-providers-remote.js +6 -4
- package/dist/lib/quota-providers-remote.js.map +1 -1
- package/dist/lib/quota-render-data.d.ts +3 -0
- package/dist/lib/quota-render-data.d.ts.map +1 -1
- package/dist/lib/quota-render-data.js +2 -0
- package/dist/lib/quota-render-data.js.map +1 -1
- package/dist/lib/quota-report-message.d.ts +22 -0
- package/dist/lib/quota-report-message.d.ts.map +1 -0
- package/dist/lib/quota-report-message.js +36 -0
- package/dist/lib/quota-report-message.js.map +1 -0
- package/dist/lib/quota-runtime-context.d.ts +1 -0
- package/dist/lib/quota-runtime-context.d.ts.map +1 -1
- package/dist/lib/quota-runtime-context.js +1 -0
- package/dist/lib/quota-runtime-context.js.map +1 -1
- package/dist/lib/quota-state-codec.js +5 -5
- package/dist/lib/quota-state-codec.js.map +1 -1
- package/dist/lib/quota-stats-format.d.ts +8 -3
- package/dist/lib/quota-stats-format.d.ts.map +1 -1
- package/dist/lib/quota-stats-format.js +36 -37
- package/dist/lib/quota-stats-format.js.map +1 -1
- package/dist/lib/quota-stats.js +2 -1
- package/dist/lib/quota-stats.js.map +1 -1
- package/dist/lib/quota-status.d.ts +7 -12
- package/dist/lib/quota-status.d.ts.map +1 -1
- package/dist/lib/quota-status.js +42 -47
- package/dist/lib/quota-status.js.map +1 -1
- package/dist/lib/quota-surface-data.d.ts +26 -0
- package/dist/lib/quota-surface-data.d.ts.map +1 -0
- package/dist/lib/quota-surface-data.js +299 -0
- package/dist/lib/quota-surface-data.js.map +1 -0
- package/dist/lib/report-document.d.ts +56 -2
- package/dist/lib/report-document.d.ts.map +1 -1
- package/dist/lib/report-document.js +105 -24
- package/dist/lib/report-document.js.map +1 -1
- package/dist/lib/scoped-update-migration.d.ts +4 -4
- package/dist/lib/scoped-update-migration.d.ts.map +1 -1
- package/dist/lib/scoped-update-migration.js +11 -18
- package/dist/lib/scoped-update-migration.js.map +1 -1
- package/dist/lib/scoped-update.d.ts +5 -1
- package/dist/lib/scoped-update.d.ts.map +1 -1
- package/dist/lib/scoped-update.js +73 -51
- package/dist/lib/scoped-update.js.map +1 -1
- package/dist/lib/synthetic-config.d.ts +2 -2
- package/dist/lib/synthetic-config.d.ts.map +1 -1
- package/dist/lib/synthetic-config.js +4 -4
- package/dist/lib/synthetic-config.js.map +1 -1
- package/dist/lib/toast-format-grouped.js +1 -1
- package/dist/lib/toast-format-grouped.js.map +1 -1
- package/dist/lib/tui-panel-state.d.ts +0 -49
- package/dist/lib/tui-panel-state.d.ts.map +1 -1
- package/dist/lib/tui-panel-state.js +1 -41
- package/dist/lib/tui-panel-state.js.map +1 -1
- package/dist/lib/tui-sidebar-format.d.ts.map +1 -1
- package/dist/lib/tui-sidebar-format.js +8 -1
- package/dist/lib/tui-sidebar-format.js.map +1 -1
- package/dist/lib/types.d.ts +26 -7
- package/dist/lib/types.d.ts.map +1 -1
- package/dist/lib/types.js +1 -2
- package/dist/lib/types.js.map +1 -1
- package/dist/lib/xai.d.ts +5 -1
- package/dist/lib/xai.d.ts.map +1 -1
- package/dist/lib/xai.js +13 -4
- package/dist/lib/xai.js.map +1 -1
- package/dist/lib/zai-auth.d.ts +2 -2
- package/dist/lib/zai-auth.d.ts.map +1 -1
- package/dist/lib/zai-auth.js +4 -4
- package/dist/lib/zai-auth.js.map +1 -1
- package/dist/lib/zai.d.ts +1 -0
- package/dist/lib/zai.d.ts.map +1 -1
- package/dist/lib/zai.js.map +1 -1
- package/dist/lib/zhipu-auth.d.ts +2 -2
- package/dist/lib/zhipu-auth.d.ts.map +1 -1
- package/dist/lib/zhipu-auth.js +4 -4
- package/dist/lib/zhipu-auth.js.map +1 -1
- package/dist/lib/zhipu.d.ts +1 -0
- package/dist/lib/zhipu.d.ts.map +1 -1
- package/dist/lib/zhipu.js.map +1 -1
- package/dist/plugin.d.ts +6 -9
- package/dist/plugin.d.ts.map +1 -1
- package/dist/plugin.js +217 -259
- package/dist/plugin.js.map +1 -1
- package/dist/providers/alibaba-coding-plan.d.ts.map +1 -1
- package/dist/providers/alibaba-coding-plan.js +1 -1
- 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 +1 -0
- package/dist/providers/alibaba-token-plan.js.map +1 -1
- package/dist/providers/anthropic.d.ts.map +1 -1
- package/dist/providers/anthropic.js +83 -1
- package/dist/providers/anthropic.js.map +1 -1
- package/dist/providers/cache-policies.d.ts +2 -2
- package/dist/providers/cache-policies.d.ts.map +1 -1
- package/dist/providers/cache-policies.js +10 -9
- package/dist/providers/cache-policies.js.map +1 -1
- package/dist/providers/copilot.d.ts.map +1 -1
- package/dist/providers/copilot.js +60 -9
- package/dist/providers/copilot.js.map +1 -1
- package/dist/providers/deepseek.js +1 -1
- package/dist/providers/deepseek.js.map +1 -1
- package/dist/providers/glm-coding-plan-provider.d.ts +13 -1
- package/dist/providers/glm-coding-plan-provider.d.ts.map +1 -1
- package/dist/providers/glm-coding-plan-provider.js +53 -0
- package/dist/providers/glm-coding-plan-provider.js.map +1 -1
- package/dist/providers/google-agy.d.ts.map +1 -1
- package/dist/providers/google-agy.js +56 -7
- 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 +72 -7
- package/dist/providers/google-gemini-cli.js.map +1 -1
- package/dist/providers/kilo.js +1 -1
- package/dist/providers/kilo.js.map +1 -1
- package/dist/providers/kimi-code.d.ts.map +1 -1
- package/dist/providers/kimi-code.js +60 -1
- package/dist/providers/kimi-code.js.map +1 -1
- package/dist/providers/minimax-coding-plan.d.ts.map +1 -1
- package/dist/providers/minimax-coding-plan.js +52 -1
- package/dist/providers/minimax-coding-plan.js.map +1 -1
- package/dist/providers/nanogpt.js +1 -1
- package/dist/providers/nanogpt.js.map +1 -1
- package/dist/providers/ollama-cloud.js +1 -1
- package/dist/providers/ollama-cloud.js.map +1 -1
- package/dist/providers/openai.d.ts.map +1 -1
- package/dist/providers/openai.js +59 -29
- package/dist/providers/openai.js.map +1 -1
- package/dist/providers/opencode-go.d.ts.map +1 -1
- package/dist/providers/opencode-go.js +207 -55
- 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 +37 -51
- package/dist/providers/opencode-zen.js.map +1 -1
- package/dist/providers/openrouter.js +1 -1
- package/dist/providers/openrouter.js.map +1 -1
- package/dist/providers/quota-providers.js +9 -9
- package/dist/providers/quota-providers.js.map +1 -1
- package/dist/providers/result-helpers.d.ts +2 -2
- package/dist/providers/result-helpers.d.ts.map +1 -1
- package/dist/providers/result-helpers.js +2 -2
- package/dist/providers/result-helpers.js.map +1 -1
- package/dist/providers/xai.d.ts.map +1 -1
- package/dist/providers/xai.js +49 -1
- package/dist/providers/xai.js.map +1 -1
- package/dist/providers/zai.d.ts.map +1 -1
- package/dist/providers/zai.js +3 -1
- package/dist/providers/zai.js.map +1 -1
- package/dist/providers/zhipu.d.ts.map +1 -1
- package/dist/providers/zhipu.js +3 -1
- package/dist/providers/zhipu.js.map +1 -1
- package/dist/rpc.d.ts +72 -0
- package/dist/rpc.d.ts.map +1 -0
- package/dist/rpc.js +77 -0
- package/dist/rpc.js.map +1 -0
- package/dist/tui-v2.d.ts +5 -0
- package/dist/tui-v2.d.ts.map +1 -0
- package/dist/tui-v2.jsx +624 -0
- package/dist/tui-v2.jsx.map +1 -0
- package/dist/tui.d.ts +4 -6
- package/dist/tui.d.ts.map +1 -1
- package/dist/tui.js +793 -934
- package/package.json +7 -19
- package/dist/lib/cache.d.ts +0 -39
- package/dist/lib/cache.d.ts.map +0 -1
- package/dist/lib/cache.js +0 -124
- package/dist/lib/cache.js.map +0 -1
- package/dist/lib/command-handled.d.ts +0 -17
- package/dist/lib/command-handled.d.ts.map +0 -1
- package/dist/lib/command-handled.js +0 -36
- package/dist/lib/command-handled.js.map +0 -1
- package/dist/lib/command-surfaces.d.ts +0 -10
- package/dist/lib/command-surfaces.d.ts.map +0 -1
- package/dist/lib/command-surfaces.js +0 -9
- package/dist/lib/command-surfaces.js.map +0 -1
- package/dist/lib/opencode-zen-config.d.ts +0 -33
- package/dist/lib/opencode-zen-config.d.ts.map +0 -1
- package/dist/lib/opencode-zen-config.js +0 -158
- package/dist/lib/opencode-zen-config.js.map +0 -1
- package/dist/lib/path-pick.d.ts +0 -2
- package/dist/lib/path-pick.d.ts.map +0 -1
- package/dist/lib/path-pick.js +0 -15
- package/dist/lib/path-pick.js.map +0 -1
- package/dist/lib/quota-toast-runtime.d.ts +0 -60
- package/dist/lib/quota-toast-runtime.d.ts.map +0 -1
- package/dist/lib/quota-toast-runtime.js +0 -766
- package/dist/lib/quota-toast-runtime.js.map +0 -1
- package/dist/lib/tui-config-diagnostics.d.ts +0 -17
- package/dist/lib/tui-config-diagnostics.d.ts.map +0 -1
- package/dist/lib/tui-config-diagnostics.js +0 -72
- package/dist/lib/tui-config-diagnostics.js.map +0 -1
- package/dist/lib/tui-line-style.d.ts +0 -3
- package/dist/lib/tui-line-style.d.ts.map +0 -1
- package/dist/lib/tui-line-style.js +0 -7
- package/dist/lib/tui-line-style.js.map +0 -1
- package/dist/lib/tui-native-provider-quota.d.ts +0 -7
- package/dist/lib/tui-native-provider-quota.d.ts.map +0 -1
- package/dist/lib/tui-native-provider-quota.js +0 -25
- package/dist/lib/tui-native-provider-quota.js.map +0 -1
- package/dist/lib/tui-refresh-lifecycle.d.ts +0 -17
- package/dist/lib/tui-refresh-lifecycle.d.ts.map +0 -1
- package/dist/lib/tui-refresh-lifecycle.js +0 -82
- package/dist/lib/tui-refresh-lifecycle.js.map +0 -1
- package/dist/lib/tui-runtime.d.ts +0 -84
- package/dist/lib/tui-runtime.d.ts.map +0 -1
- package/dist/lib/tui-runtime.js +0 -527
- package/dist/lib/tui-runtime.js.map +0 -1
- package/dist/lib/tui-sidebar-content.d.ts +0 -32
- package/dist/lib/tui-sidebar-content.d.ts.map +0 -1
- package/dist/lib/tui-sidebar-content.js +0 -52
- package/dist/lib/tui-sidebar-content.js.map +0 -1
- package/dist/tui.tsx +0 -1030
package/dist/tui.js
CHANGED
|
@@ -1,1035 +1,894 @@
|
|
|
1
|
+
import { use as _$use } from "@opentui/solid";
|
|
2
|
+
import { memo as _$memo } from "@opentui/solid";
|
|
1
3
|
import { createTextNode as _$createTextNode } from "@opentui/solid";
|
|
2
4
|
import { insertNode as _$insertNode } from "@opentui/solid";
|
|
3
5
|
import { effect as _$effect } from "@opentui/solid";
|
|
4
|
-
import { memo as _$memo } from "@opentui/solid";
|
|
5
|
-
import { insert as _$insert } from "@opentui/solid";
|
|
6
6
|
import { createComponent as _$createComponent } from "@opentui/solid";
|
|
7
|
+
import { insert as _$insert } from "@opentui/solid";
|
|
7
8
|
import { setProp as _$setProp } from "@opentui/solid";
|
|
8
9
|
import { createElement as _$createElement } from "@opentui/solid";
|
|
9
10
|
/** @jsxImportSource @opentui/solid */
|
|
10
11
|
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
const SIDEBAR_ORDER = 150;
|
|
25
|
-
const COMPACT_ORDER = 90;
|
|
12
|
+
import { Plugin } from "@opencode/plugin/tui";
|
|
13
|
+
import { TextAttributes } from "@opentui/core";
|
|
14
|
+
import { useTerminalDimensions } from "@opentui/solid";
|
|
15
|
+
import { createSignal, onCleanup, Show } from "solid-js";
|
|
16
|
+
import { loadConfig } from "./lib/config.js";
|
|
17
|
+
import { resolveOpenCodeLocationRoots } from "./lib/config-file-utils.js";
|
|
18
|
+
import { sanitizeDisplayText } from "./lib/display-sanitize.js";
|
|
19
|
+
import { fitTableToWidth } from "./lib/markdown-table.js";
|
|
20
|
+
import { layoutQuotaTables } from "./lib/quota-columns.js";
|
|
21
|
+
import { parseQuotaSlashCommand, QUOTA_DIALOG_COMMANDS } from "./lib/quota-dialog-command-specs.js";
|
|
22
|
+
import { readQuotaReportMetadata } from "./lib/quota-report-message.js";
|
|
23
|
+
import { renderableSections, renderKvRow } from "./lib/report-document.js";
|
|
24
|
+
import { QuotaRpc } from "./rpc.js";
|
|
26
25
|
const REFRESH_INTERVAL_MS = 60_000;
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
sidebar: {
|
|
32
|
-
enabled: true
|
|
33
|
-
},
|
|
34
|
-
compact: {
|
|
35
|
-
enabled: false,
|
|
36
|
-
homeBottom: false,
|
|
37
|
-
sessionPrompt: false,
|
|
38
|
-
hasNativeProviderQuota: false,
|
|
39
|
-
suppressedByNativeProviderQuota: false
|
|
40
|
-
},
|
|
41
|
-
promptBar: {
|
|
42
|
-
enabled: false
|
|
43
|
-
},
|
|
44
|
-
announcements: {
|
|
45
|
-
homeBottom: false
|
|
46
|
-
},
|
|
47
|
-
homeBottom: false
|
|
48
|
-
};
|
|
49
|
-
function createTuiInitialLoadCoordinator(seed) {
|
|
50
|
-
let sessionAvailable = true;
|
|
51
|
-
let homeAvailable = true;
|
|
52
|
-
return {
|
|
53
|
-
takeInitialSession() {
|
|
54
|
-
if (!sessionAvailable) return undefined;
|
|
55
|
-
sessionAvailable = false;
|
|
56
|
-
return seed;
|
|
57
|
-
},
|
|
58
|
-
takeInitialHome() {
|
|
59
|
-
if (!homeAvailable) return undefined;
|
|
60
|
-
homeAvailable = false;
|
|
61
|
-
return seed;
|
|
62
|
-
}
|
|
63
|
-
};
|
|
26
|
+
const RPC_TIMEOUT_MS = 60_000;
|
|
27
|
+
function getSessionID(event) {
|
|
28
|
+
const sessionID = event.data?.sessionID;
|
|
29
|
+
return typeof sessionID === "string" && sessionID ? sessionID : undefined;
|
|
64
30
|
}
|
|
65
|
-
function
|
|
66
|
-
const [current, setCurrent] = createSignal({
|
|
67
|
-
status: "pending"
|
|
68
|
-
});
|
|
31
|
+
function quotaClient(context) {
|
|
69
32
|
return {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
dispose() {
|
|
80
|
-
if (current().status === "disposed") return;
|
|
81
|
-
setCurrent({
|
|
82
|
-
status: "disposed"
|
|
83
|
-
});
|
|
33
|
+
config: {
|
|
34
|
+
get: async () => ({
|
|
35
|
+
data: {}
|
|
36
|
+
}),
|
|
37
|
+
providers: async () => ({
|
|
38
|
+
data: {
|
|
39
|
+
providers: context.data.location?.provider.list(context.location ?? context.data.location.default()) ?? []
|
|
40
|
+
}
|
|
41
|
+
})
|
|
84
42
|
}
|
|
85
43
|
};
|
|
86
44
|
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
function
|
|
90
|
-
|
|
91
|
-
if (existing) return existing;
|
|
92
|
-
const next = new Map();
|
|
93
|
-
sessionResources.set(api, next);
|
|
94
|
-
return next;
|
|
45
|
+
|
|
46
|
+
/** Same project roots as the server plugin: the location's Git worktree, else the location directory. */
|
|
47
|
+
function quotaRoots(context) {
|
|
48
|
+
return resolveOpenCodeLocationRoots(context.location?.directory ?? process.cwd());
|
|
95
49
|
}
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
load: () => {
|
|
110
|
-
const initialRuntimeSeed = loadOrdinal === 0 ? initialLoads?.takeInitialSession() : undefined;
|
|
111
|
-
loadOrdinal += 1;
|
|
112
|
-
return loadTuiSessionQuotaSurfaces({
|
|
113
|
-
api,
|
|
114
|
-
sessionID,
|
|
115
|
-
...(initialRuntimeSeed ? {
|
|
116
|
-
initialRuntimeSeed
|
|
117
|
-
} : {})
|
|
118
|
-
});
|
|
119
|
-
},
|
|
120
|
-
apply: next => {
|
|
121
|
-
setSidebar(next.sidebar);
|
|
122
|
-
setCompact(next.compact);
|
|
123
|
-
setPromptBar(next.promptBar ?? {
|
|
124
|
-
status: "loading"
|
|
125
|
-
});
|
|
126
|
-
},
|
|
127
|
-
intervalMs: REFRESH_INTERVAL_MS,
|
|
128
|
-
eventRefreshDelaysMs: EVENT_REFRESH_DELAYS_MS,
|
|
129
|
-
// TUI/session state can hydrate asynchronously after mount or session switch,
|
|
130
|
-
// so retry a few times to recover from empty first-load reads.
|
|
131
|
-
recoveryDelaysMs: MOUNT_RECOVERY_DELAYS_MS,
|
|
132
|
-
subscribe: scheduleRefresh => [api.event.on("session.updated", event => {
|
|
133
|
-
if (event.properties?.info?.id === sessionID) {
|
|
134
|
-
scheduleRefresh();
|
|
135
|
-
}
|
|
136
|
-
}), api.event.on("message.updated", event => {
|
|
137
|
-
if (event.properties?.info?.sessionID === sessionID) {
|
|
138
|
-
scheduleRefresh();
|
|
139
|
-
}
|
|
140
|
-
}), api.event.on("message.removed", event => {
|
|
141
|
-
if (event.properties?.sessionID === sessionID) {
|
|
142
|
-
scheduleRefresh();
|
|
143
|
-
}
|
|
144
|
-
}), api.event.on("tui.session.select", event => {
|
|
145
|
-
if (event.properties?.sessionID === sessionID) {
|
|
146
|
-
scheduleRefresh();
|
|
147
|
-
}
|
|
148
|
-
})],
|
|
149
|
-
onDispose: () => {
|
|
150
|
-
getSessionResourceMap(api).delete(sessionID);
|
|
151
|
-
}
|
|
152
|
-
});
|
|
153
|
-
const resource = {
|
|
154
|
-
sessionID,
|
|
155
|
-
sidebar,
|
|
156
|
-
compact,
|
|
157
|
-
promptBar,
|
|
158
|
-
retain: () => {
|
|
159
|
-
lifecycle.retain();
|
|
160
|
-
return resource;
|
|
161
|
-
},
|
|
162
|
-
release: lifecycle.release
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* The server plugin computes every quota surface; the TUI asks it over the quota RPC. The
|
|
53
|
+
* RPC client is made per call, never during setup. Each call names the TUI's location, so
|
|
54
|
+
* the server uses that folder's settings, and gives up after a minute.
|
|
55
|
+
*/
|
|
56
|
+
function quotaRpc(context) {
|
|
57
|
+
return context.client.rpc(QuotaRpc);
|
|
58
|
+
}
|
|
59
|
+
function quotaRpcOptions(context) {
|
|
60
|
+
return {
|
|
61
|
+
location: context.location ?? context.data.location.default(),
|
|
62
|
+
signal: AbortSignal.timeout(RPC_TIMEOUT_MS)
|
|
163
63
|
};
|
|
164
|
-
return resource;
|
|
165
64
|
}
|
|
166
|
-
function
|
|
167
|
-
const
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
65
|
+
async function getQuotaMessage(context, sessionID, surface) {
|
|
66
|
+
const output = await quotaRpc(context).surface({
|
|
67
|
+
surface,
|
|
68
|
+
sessionID
|
|
69
|
+
}, quotaRpcOptions(context));
|
|
70
|
+
return output.quota ?? undefined;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** RPC calls reject with a plain `{ type, message }` object, not an Error. */
|
|
74
|
+
function rpcErrorMessage(error) {
|
|
75
|
+
if (error instanceof Error) return error.message;
|
|
76
|
+
if (typeof error === "object" && error !== null && "message" in error && typeof error.message === "string") {
|
|
77
|
+
return error.message;
|
|
78
|
+
}
|
|
79
|
+
return String(error);
|
|
173
80
|
}
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* One view's refresh loop, as in v4 (tui-refresh-lifecycle.ts): a refresh that
|
|
84
|
+
* arrives while a load runs is coalesced into one follow-up load, and a result is
|
|
85
|
+
* dropped when the view was disposed or a newer load started.
|
|
86
|
+
*/
|
|
87
|
+
function createViewRefresh(load, apply) {
|
|
88
|
+
let disposed = false;
|
|
89
|
+
let loadVersion = 0;
|
|
90
|
+
let inFlight = false;
|
|
91
|
+
let queued = false;
|
|
92
|
+
const refresh = () => {
|
|
93
|
+
if (disposed) return;
|
|
94
|
+
if (inFlight) {
|
|
95
|
+
queued = true;
|
|
96
|
+
return;
|
|
181
97
|
}
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
api
|
|
201
|
-
}).catch(err => {
|
|
202
|
-
console.warn(`[opencode-quota] quota export write failed: ${String(err)}`);
|
|
203
|
-
});
|
|
204
|
-
},
|
|
205
|
-
intervalMs: REFRESH_INTERVAL_MS,
|
|
206
|
-
eventRefreshDelaysMs: EVENT_REFRESH_DELAYS_MS,
|
|
207
|
-
subscribe: scheduleRefresh => [api.event.on("session.updated", scheduleRefresh), api.event.on("message.updated", scheduleRefresh), api.event.on("message.removed", scheduleRefresh), api.event.on("tui.session.select", scheduleRefresh)],
|
|
208
|
-
onDispose: () => {
|
|
209
|
-
homeResources.delete(api);
|
|
98
|
+
inFlight = true;
|
|
99
|
+
const currentVersion = ++loadVersion;
|
|
100
|
+
void load().then(value => {
|
|
101
|
+
if (disposed || currentVersion !== loadVersion) return;
|
|
102
|
+
apply(value);
|
|
103
|
+
}).catch(reportFailure).finally(() => {
|
|
104
|
+
if (disposed) return;
|
|
105
|
+
inFlight = false;
|
|
106
|
+
if (queued) {
|
|
107
|
+
queued = false;
|
|
108
|
+
refresh();
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
};
|
|
112
|
+
return {
|
|
113
|
+
refresh,
|
|
114
|
+
dispose: () => {
|
|
115
|
+
disposed = true;
|
|
210
116
|
}
|
|
211
|
-
});
|
|
212
|
-
const resource = {
|
|
213
|
-
bottom,
|
|
214
|
-
retain: () => {
|
|
215
|
-
lifecycle.retain();
|
|
216
|
-
return resource;
|
|
217
|
-
},
|
|
218
|
-
release: lifecycle.release
|
|
219
117
|
};
|
|
220
|
-
return resource;
|
|
221
118
|
}
|
|
222
|
-
function
|
|
223
|
-
const
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
119
|
+
function QuotaFooter(props) {
|
|
120
|
+
const [lines, setLines] = createSignal([]);
|
|
121
|
+
const view = createViewRefresh(async () => {
|
|
122
|
+
const output = await quotaRpc(props.context).footer({
|
|
123
|
+
surface: props.surface,
|
|
124
|
+
sessionID: props.sessionID
|
|
125
|
+
}, quotaRpcOptions(props.context));
|
|
126
|
+
return output.lines;
|
|
127
|
+
}, next => {
|
|
128
|
+
setLines(next);
|
|
129
|
+
if (props.surface !== "home") return;
|
|
130
|
+
// Fire-and-forget: the server writes the export file if enabled. A failed write
|
|
131
|
+
// must never affect rendering, so log a warning and continue.
|
|
132
|
+
void quotaRpc(props.context).writeExport({}, quotaRpcOptions(props.context)).catch(error => {
|
|
133
|
+
console.warn(`[opencode-quota] quota export write failed: ${rpcErrorMessage(error)}`);
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
view.refresh();
|
|
137
|
+
const interval = setInterval(view.refresh, REFRESH_INTERVAL_MS);
|
|
138
|
+
const stop = props.context.data.on("session.step.ended", event => {
|
|
139
|
+
if (props.surface === "home" || getSessionID(event) === props.sessionID) view.refresh();
|
|
239
140
|
});
|
|
240
141
|
onCleanup(() => {
|
|
241
|
-
|
|
142
|
+
view.dispose();
|
|
143
|
+
clearInterval(interval);
|
|
144
|
+
stop();
|
|
242
145
|
});
|
|
243
|
-
return resource;
|
|
244
|
-
}
|
|
245
|
-
function SidebarContentView(props) {
|
|
246
|
-
const resource = useSessionQuotaResource(props.api, () => props.sessionID, props.initialLoads);
|
|
247
|
-
const panel = () => resource().sidebar();
|
|
248
|
-
const lines = () => getSidebarPanelLines(panel());
|
|
249
|
-
const hasDetailLines = () => Boolean(panel().linesExpanded?.length);
|
|
250
|
-
const [collapsed, setCollapsed] = createSignal(props.api.kv?.get("quota-sidebar-collapsed", true) ?? true);
|
|
251
|
-
const toggleCollapsed = () => {
|
|
252
|
-
if (!hasDetailLines()) return;
|
|
253
|
-
const next = !collapsed();
|
|
254
|
-
setCollapsed(next);
|
|
255
|
-
props.api.kv?.set("quota-sidebar-collapsed", next);
|
|
256
|
-
};
|
|
257
|
-
const displayLines = () => {
|
|
258
|
-
if (!hasDetailLines()) return lines();
|
|
259
|
-
return collapsed() ? lines() : getSidebarPanelLinesExpanded(panel());
|
|
260
|
-
};
|
|
261
|
-
const contentRows = () => {
|
|
262
|
-
const heading = panel().headerPercentMode ? formatQuotaModeHeading(panel().headerPercentMode) : "Quota";
|
|
263
|
-
return buildSidebarContentRows({
|
|
264
|
-
collapsed: collapsed(),
|
|
265
|
-
heading,
|
|
266
|
-
hasDetailLines: hasDetailLines(),
|
|
267
|
-
providerCount: panel().providerCount ?? 0,
|
|
268
|
-
lines: displayLines()
|
|
269
|
-
});
|
|
270
|
-
};
|
|
271
146
|
return _$createComponent(Show, {
|
|
272
147
|
get when() {
|
|
273
|
-
return
|
|
148
|
+
return lines().length;
|
|
274
149
|
},
|
|
275
150
|
get children() {
|
|
276
151
|
var _el$ = _$createElement("box");
|
|
277
|
-
_$setProp(_el$, "
|
|
278
|
-
_$
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
var _el$2 = _$createElement("text");
|
|
285
|
-
_$setProp(_el$2, "wrapMode", "none");
|
|
286
|
-
_$setProp(_el$2, "width", "100%");
|
|
287
|
-
_$insert(_el$2, (() => {
|
|
288
|
-
var _c$ = _$memo(() => row().kind === "header");
|
|
289
|
-
return () => _c$() ? row().segments.map(segment => segment.style === "muted" ? (() => {
|
|
290
|
-
var _el$3 = _$createElement("span");
|
|
291
|
-
_$insert(_el$3, () => segment.text);
|
|
292
|
-
_$effect(_$p => _$setProp(_el$3, "fg", props.api.theme.current.textMuted, _$p));
|
|
293
|
-
return _el$3;
|
|
294
|
-
})() : (() => {
|
|
295
|
-
var _el$4 = _$createElement("b");
|
|
296
|
-
_$insert(_el$4, () => segment.text);
|
|
297
|
-
return _el$4;
|
|
298
|
-
})()) : row().text || " ";
|
|
299
|
-
})());
|
|
300
|
-
_$effect(_p$ => {
|
|
301
|
-
var _v$ = row().kind === "header" ? props.api.theme.current.text : getSidebarBodyLineColor(row().text, props.api.theme.current),
|
|
302
|
-
_v$2 = row().kind === "header" ? toggleCollapsed : undefined;
|
|
303
|
-
_v$ !== _p$.e && (_p$.e = _$setProp(_el$2, "fg", _v$, _p$.e));
|
|
304
|
-
_v$2 !== _p$.t && (_p$.t = _$setProp(_el$2, "onMouseDown", _v$2, _p$.t));
|
|
305
|
-
return _p$;
|
|
306
|
-
}, {
|
|
307
|
-
e: undefined,
|
|
308
|
-
t: undefined
|
|
309
|
-
});
|
|
310
|
-
return _el$2;
|
|
311
|
-
})()
|
|
312
|
-
}));
|
|
152
|
+
_$setProp(_el$, "flexDirection", "column");
|
|
153
|
+
_$insert(_el$, () => lines().map(line => (() => {
|
|
154
|
+
var _el$2 = _$createElement("text");
|
|
155
|
+
_$insert(_el$2, line);
|
|
156
|
+
_$effect(_$p => _$setProp(_el$2, "fg", props.context.theme.text.muted, _$p));
|
|
157
|
+
return _el$2;
|
|
158
|
+
})()));
|
|
313
159
|
return _el$;
|
|
314
160
|
}
|
|
315
161
|
});
|
|
316
162
|
}
|
|
317
|
-
function
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
return getCompactStatusText(panel);
|
|
322
|
-
};
|
|
323
|
-
const line = () => (() => {
|
|
324
|
-
var _el$5 = _$createElement("box"),
|
|
325
|
-
_el$6 = _$createElement("text");
|
|
326
|
-
_$insertNode(_el$5, _el$6);
|
|
327
|
-
_$setProp(_el$5, "flexDirection", "row");
|
|
328
|
-
_$setProp(_el$6, "wrapMode", "none");
|
|
329
|
-
_$insert(_el$6, text);
|
|
330
|
-
_$effect(_p$ => {
|
|
331
|
-
var _v$3 = props.justifyContent,
|
|
332
|
-
_v$4 = props.api.theme.current.textMuted;
|
|
333
|
-
_v$3 !== _p$.e && (_p$.e = _$setProp(_el$5, "justifyContent", _v$3, _p$.e));
|
|
334
|
-
_v$4 !== _p$.t && (_p$.t = _$setProp(_el$6, "fg", _v$4, _p$.t));
|
|
335
|
-
return _p$;
|
|
336
|
-
}, {
|
|
337
|
-
e: undefined,
|
|
338
|
-
t: undefined
|
|
339
|
-
});
|
|
340
|
-
return _el$5;
|
|
341
|
-
})();
|
|
342
|
-
return _$createComponent(Show, {
|
|
343
|
-
get when() {
|
|
344
|
-
return text();
|
|
345
|
-
},
|
|
346
|
-
get children() {
|
|
347
|
-
return _$createComponent(Show, {
|
|
348
|
-
get when() {
|
|
349
|
-
return props.blankLineBefore;
|
|
350
|
-
},
|
|
351
|
-
get fallback() {
|
|
352
|
-
return line();
|
|
353
|
-
},
|
|
354
|
-
get children() {
|
|
355
|
-
var _el$7 = _$createElement("box"),
|
|
356
|
-
_el$8 = _$createElement("text");
|
|
357
|
-
_$insertNode(_el$7, _el$8);
|
|
358
|
-
_$setProp(_el$7, "gap", 0);
|
|
359
|
-
_$insertNode(_el$8, _$createTextNode(` `));
|
|
360
|
-
_$insert(_el$7, line, null);
|
|
361
|
-
return _el$7;
|
|
362
|
-
}
|
|
363
|
-
});
|
|
364
|
-
}
|
|
365
|
-
});
|
|
366
|
-
}
|
|
367
|
-
function SessionPromptWithCompactStatus(props) {
|
|
368
|
-
const resource = useSessionQuotaResource(props.api, () => props.sessionID, props.initialLoads);
|
|
369
|
-
const panel = () => resource().compact();
|
|
370
|
-
return (() => {
|
|
371
|
-
var _el$0 = _$createElement("box");
|
|
372
|
-
_$setProp(_el$0, "gap", 0);
|
|
373
|
-
_$insert(_el$0, _$createComponent(props.api.ui.Prompt, {
|
|
374
|
-
get sessionID() {
|
|
375
|
-
return props.sessionID;
|
|
376
|
-
},
|
|
377
|
-
get visible() {
|
|
378
|
-
return props.visible;
|
|
379
|
-
},
|
|
380
|
-
get disabled() {
|
|
381
|
-
return props.disabled;
|
|
382
|
-
},
|
|
383
|
-
get onSubmit() {
|
|
384
|
-
return props.onSubmit;
|
|
385
|
-
},
|
|
386
|
-
ref(r$) {
|
|
387
|
-
var _ref$ = props.promptRef;
|
|
388
|
-
typeof _ref$ === "function" ? _ref$(r$) : props.promptRef = r$;
|
|
389
|
-
}
|
|
390
|
-
}), null);
|
|
391
|
-
_$insert(_el$0, _$createComponent(CompactStatusLine, {
|
|
392
|
-
get api() {
|
|
393
|
-
return props.api;
|
|
394
|
-
},
|
|
395
|
-
panel: panel,
|
|
396
|
-
justifyContent: "flex-end"
|
|
397
|
-
}), null);
|
|
398
|
-
return _el$0;
|
|
399
|
-
})();
|
|
400
|
-
}
|
|
401
|
-
function shouldRenderPromptBar(bar) {
|
|
402
|
-
return bar.status === "ready" && Boolean(bar.entry);
|
|
403
|
-
}
|
|
404
|
-
function useSessionRunning(api, sessionID) {
|
|
405
|
-
const [running, setRunning] = createSignal(false);
|
|
406
|
-
createEffect(() => {
|
|
407
|
-
const id = sessionID();
|
|
408
|
-
if (!id) {
|
|
409
|
-
setRunning(false);
|
|
410
|
-
return;
|
|
411
|
-
}
|
|
412
|
-
const update = () => {
|
|
413
|
-
try {
|
|
414
|
-
const sessionState = api.state.session;
|
|
415
|
-
const status = sessionState.status?.(id);
|
|
416
|
-
setRunning(status?.type === "busy" || status?.type === "retry");
|
|
417
|
-
} catch {
|
|
418
|
-
setRunning(false);
|
|
419
|
-
}
|
|
420
|
-
};
|
|
421
|
-
update();
|
|
422
|
-
const disposers = [api.event.on("session.status", event => {
|
|
423
|
-
if (event.properties?.sessionID === id) {
|
|
424
|
-
update();
|
|
425
|
-
}
|
|
426
|
-
}), api.event.on("session.updated", event => {
|
|
427
|
-
if (event.properties?.info?.id === id) {
|
|
428
|
-
update();
|
|
429
|
-
}
|
|
430
|
-
})];
|
|
431
|
-
onCleanup(() => {
|
|
432
|
-
for (const dispose of disposers) {
|
|
433
|
-
if (typeof dispose === "function") {
|
|
434
|
-
dispose();
|
|
435
|
-
}
|
|
436
|
-
}
|
|
437
|
-
});
|
|
438
|
-
});
|
|
439
|
-
return running;
|
|
163
|
+
function reportFailure(error) {
|
|
164
|
+
// OpenCode answers rpc.unavailable when no server plugin registered the quota RPC here.
|
|
165
|
+
const hint = typeof error === "object" && error !== null && "type" in error && error.type === "rpc.unavailable" ? " (OpenCode Quota's server plugin is not loaded for this folder)" : "";
|
|
166
|
+
console.warn(`[opencode-quota] failed to load quota: ${rpcErrorMessage(error)}${hint}`);
|
|
440
167
|
}
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
compactRounded: true,
|
|
450
|
-
decimals: bar.resetTimeDecimals
|
|
451
|
-
} : {
|
|
452
|
-
spaced: bar.resetTimeSpaced
|
|
453
|
-
}) : "";
|
|
454
|
-
return {
|
|
455
|
-
label: resolvePromptBarLabel(entry),
|
|
456
|
-
barText: "",
|
|
457
|
-
meta: reset
|
|
458
|
-
};
|
|
459
|
-
}
|
|
460
|
-
const p = Math.min(100, resolveDisplayedPercent(entry.percentRemaining ?? 0, bar.percentDisplayMode ?? "remaining"));
|
|
461
|
-
const filled = Math.round(p / 100 * PROMPT_BAR_WIDTH);
|
|
462
|
-
const empty = PROMPT_BAR_WIDTH - filled;
|
|
463
|
-
let barText = "█".repeat(filled) + "░".repeat(empty);
|
|
464
|
-
if (params.running() && filled > 0) {
|
|
465
|
-
const cells = Array(filled).fill("▓");
|
|
466
|
-
const center = params.phase() % filled;
|
|
467
|
-
const gradient = ["▒", "▓", "█", "▓", "▒"];
|
|
468
|
-
for (let offset = -2; offset <= 2; offset++) {
|
|
469
|
-
const position = (center + offset + filled) % filled;
|
|
470
|
-
cells[position] = gradient[offset + 2];
|
|
471
|
-
}
|
|
472
|
-
barText = cells.join("") + "░".repeat(empty);
|
|
473
|
-
}
|
|
168
|
+
|
|
169
|
+
/** OpenCode's dialog widths (dialogWidth in its ui/dialog.tsx). */
|
|
170
|
+
const DIALOG_WIDTHS = {
|
|
171
|
+
medium: 60,
|
|
172
|
+
large: 88,
|
|
173
|
+
xlarge: 116
|
|
174
|
+
};
|
|
175
|
+
function sanitizeQuotaRow(row) {
|
|
474
176
|
return {
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
resetTimeDecimals: bar.resetTimeDecimals,
|
|
482
|
-
resetTimeSpaced: bar.resetTimeSpaced,
|
|
483
|
-
runway: entry.runway
|
|
484
|
-
})
|
|
177
|
+
...row,
|
|
178
|
+
label: sanitizeDisplayText(row.label),
|
|
179
|
+
value: sanitizeDisplayText(row.value),
|
|
180
|
+
usage: row.usage === undefined ? undefined : sanitizeDisplayText(row.usage),
|
|
181
|
+
reset: row.reset === undefined ? undefined : sanitizeDisplayText(row.reset),
|
|
182
|
+
notes: row.notes.map(sanitizeDisplayText)
|
|
485
183
|
};
|
|
486
184
|
}
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
_$
|
|
512
|
-
_$
|
|
513
|
-
|
|
185
|
+
|
|
186
|
+
/** A report's /quota blocks, sanitized, for layoutQuotaTables. */
|
|
187
|
+
function quotaBlocks(section) {
|
|
188
|
+
return section.blocks.flatMap(block => block.kind === "quota" ? [{
|
|
189
|
+
...block,
|
|
190
|
+
provider: sanitizeDisplayText(block.provider),
|
|
191
|
+
rows: block.rows.map(sanitizeQuotaRow)
|
|
192
|
+
}] : []);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* A /quota table (see layoutQuotaTables): its title bold, like a section title, then its
|
|
197
|
+
* header bold in OpenCode's primary accent, like the token tables, and its providers bold.
|
|
198
|
+
* In the rows, labels, reset times, and notes are muted; bars, percents, and values base.
|
|
199
|
+
*/
|
|
200
|
+
function QuotaTableView(props) {
|
|
201
|
+
return (() => {
|
|
202
|
+
var _el$3 = _$createElement("box"),
|
|
203
|
+
_el$4 = _$createElement("text");
|
|
204
|
+
_$insertNode(_el$3, _el$4);
|
|
205
|
+
_$setProp(_el$3, "flexDirection", "column");
|
|
206
|
+
_$insert(_el$4, () => props.table.title);
|
|
207
|
+
_$insert(_el$3, () => props.table.lines.map(line => line.style === "row" ? (() => {
|
|
208
|
+
var _el$5 = _$createElement("text");
|
|
209
|
+
_$setProp(_el$5, "wrapMode", "none");
|
|
210
|
+
_$insert(_el$5, () => line.segments.map(segment => (() => {
|
|
211
|
+
var _el$6 = _$createElement("span");
|
|
212
|
+
_$insert(_el$6, () => segment.text);
|
|
213
|
+
_$effect(_$p => _$setProp(_el$6, "style", {
|
|
214
|
+
fg: segment.muted ? props.theme.text.muted : props.theme.text.base
|
|
215
|
+
}, _$p));
|
|
216
|
+
return _el$6;
|
|
217
|
+
})()));
|
|
218
|
+
_$effect(_$p => _$setProp(_el$5, "fg", props.theme.text.base, _$p));
|
|
219
|
+
return _el$5;
|
|
220
|
+
})() : (() => {
|
|
221
|
+
var _el$7 = _$createElement("text");
|
|
222
|
+
_$insert(_el$7, () => line.segments.map(segment => segment.text).join(""));
|
|
514
223
|
_$effect(_p$ => {
|
|
515
|
-
var _v$
|
|
516
|
-
_v$
|
|
517
|
-
_v$
|
|
518
|
-
_v$
|
|
519
|
-
_v$
|
|
520
|
-
_v$
|
|
224
|
+
var _v$3 = TextAttributes.BOLD,
|
|
225
|
+
_v$4 = line.style === "header" ? props.theme.text.action.primary.selected : props.theme.text.base,
|
|
226
|
+
_v$5 = line.style === "header" ? "none" : undefined;
|
|
227
|
+
_v$3 !== _p$.e && (_p$.e = _$setProp(_el$7, "attributes", _v$3, _p$.e));
|
|
228
|
+
_v$4 !== _p$.t && (_p$.t = _$setProp(_el$7, "fg", _v$4, _p$.t));
|
|
229
|
+
_v$5 !== _p$.a && (_p$.a = _$setProp(_el$7, "wrapMode", _v$5, _p$.a));
|
|
521
230
|
return _p$;
|
|
522
231
|
}, {
|
|
523
232
|
e: undefined,
|
|
524
233
|
t: undefined,
|
|
525
234
|
a: undefined
|
|
526
235
|
});
|
|
527
|
-
return _el$
|
|
528
|
-
}
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
}
|
|
541
|
-
const interval = setInterval(() => setPhase(p => p + 1), 160);
|
|
542
|
-
onCleanup(() => clearInterval(interval));
|
|
543
|
-
});
|
|
544
|
-
return (() => {
|
|
545
|
-
var _el$13 = _$createElement("box");
|
|
546
|
-
_$setProp(_el$13, "gap", 0);
|
|
547
|
-
_$insert(_el$13, _$createComponent(props.api.ui.Prompt, {
|
|
548
|
-
get sessionID() {
|
|
549
|
-
return props.sessionID;
|
|
550
|
-
},
|
|
551
|
-
get visible() {
|
|
552
|
-
return props.visible;
|
|
553
|
-
},
|
|
554
|
-
get disabled() {
|
|
555
|
-
return props.disabled;
|
|
556
|
-
},
|
|
557
|
-
get onSubmit() {
|
|
558
|
-
return props.onSubmit;
|
|
559
|
-
},
|
|
560
|
-
ref(r$) {
|
|
561
|
-
var _ref$2 = props.promptRef;
|
|
562
|
-
typeof _ref$2 === "function" ? _ref$2(r$) : props.promptRef = r$;
|
|
563
|
-
}
|
|
564
|
-
}), null);
|
|
565
|
-
_$insert(_el$13, _$createComponent(PromptQuotaHint, {
|
|
566
|
-
get api() {
|
|
567
|
-
return props.api;
|
|
568
|
-
},
|
|
569
|
-
bar: promptBar,
|
|
570
|
-
running: running,
|
|
571
|
-
phase: phase
|
|
572
|
-
}), null);
|
|
573
|
-
return _el$13;
|
|
236
|
+
return _el$7;
|
|
237
|
+
})()), null);
|
|
238
|
+
_$effect(_p$ => {
|
|
239
|
+
var _v$ = TextAttributes.BOLD,
|
|
240
|
+
_v$2 = props.theme.text.base;
|
|
241
|
+
_v$ !== _p$.e && (_p$.e = _$setProp(_el$4, "attributes", _v$, _p$.e));
|
|
242
|
+
_v$2 !== _p$.t && (_p$.t = _$setProp(_el$4, "fg", _v$2, _p$.t));
|
|
243
|
+
return _p$;
|
|
244
|
+
}, {
|
|
245
|
+
e: undefined,
|
|
246
|
+
t: undefined
|
|
247
|
+
});
|
|
248
|
+
return _el$3;
|
|
574
249
|
})();
|
|
575
250
|
}
|
|
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
|
-
_$
|
|
604
|
-
_$setProp(_el$
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* One block of a report. Lines, and the chat lines of /quota rows the dialog's tables do not
|
|
254
|
+
* fit, and key-value rows wrap at words. A table keeps each row on one line and spans `width`
|
|
255
|
+
* columns when it fits (see fitTableToWidth), with the header row bold in OpenCode's primary
|
|
256
|
+
* accent (its text color for a selected tab). Like OpenCode's sidebar, labels are muted and
|
|
257
|
+
* values are base.
|
|
258
|
+
*/
|
|
259
|
+
function ReportBlockView(props) {
|
|
260
|
+
const block = props.block;
|
|
261
|
+
if (block.kind === "lines" || block.kind === "quota") {
|
|
262
|
+
return (() => {
|
|
263
|
+
var _el$8 = _$createElement("text");
|
|
264
|
+
_$insert(_el$8, () => sanitizeDisplayText(block.lines.join("\n")));
|
|
265
|
+
_$effect(_$p => _$setProp(_el$8, "fg", props.theme.text.muted, _$p));
|
|
266
|
+
return _el$8;
|
|
267
|
+
})();
|
|
268
|
+
}
|
|
269
|
+
if (block.kind === "kv") {
|
|
270
|
+
return (() => {
|
|
271
|
+
var _el$9 = _$createElement("box");
|
|
272
|
+
_$setProp(_el$9, "flexDirection", "column");
|
|
273
|
+
_$insert(_el$9, () => block.rows.map(({
|
|
274
|
+
value,
|
|
275
|
+
...key
|
|
276
|
+
}) => value === undefined ? (() => {
|
|
277
|
+
var _el$0 = _$createElement("text");
|
|
278
|
+
_$insert(_el$0, () => sanitizeDisplayText(renderKvRow(key)));
|
|
279
|
+
_$effect(_$p => _$setProp(_el$0, "fg", props.theme.text.muted, _$p));
|
|
280
|
+
return _el$0;
|
|
281
|
+
})() : (() => {
|
|
282
|
+
var _el$1 = _$createElement("text"),
|
|
283
|
+
_el$10 = _$createElement("span"),
|
|
284
|
+
_el$11 = _$createTextNode(` `);
|
|
285
|
+
_$insertNode(_el$1, _el$10);
|
|
286
|
+
_$insert(_el$1, () => sanitizeDisplayText(renderKvRow({
|
|
287
|
+
...key,
|
|
288
|
+
trailingColon: true
|
|
289
|
+
})), _el$10);
|
|
290
|
+
_$insertNode(_el$10, _el$11);
|
|
291
|
+
_$insert(_el$10, () => sanitizeDisplayText(value), null);
|
|
292
|
+
_$effect(_p$ => {
|
|
293
|
+
var _v$6 = props.theme.text.muted,
|
|
294
|
+
_v$7 = {
|
|
295
|
+
fg: props.theme.text.base
|
|
296
|
+
};
|
|
297
|
+
_v$6 !== _p$.e && (_p$.e = _$setProp(_el$1, "fg", _v$6, _p$.e));
|
|
298
|
+
_v$7 !== _p$.t && (_p$.t = _$setProp(_el$10, "style", _v$7, _p$.t));
|
|
299
|
+
return _p$;
|
|
300
|
+
}, {
|
|
301
|
+
e: undefined,
|
|
302
|
+
t: undefined
|
|
622
303
|
});
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
}
|
|
628
|
-
function getActiveTuiSessionID(api) {
|
|
629
|
-
if (api.route.current.name !== "session") return undefined;
|
|
630
|
-
return normalizeTuiSessionID(api.route.current.params?.sessionID);
|
|
631
|
-
}
|
|
632
|
-
function getTuiCommandArguments(input) {
|
|
633
|
-
if (!input || typeof input !== "object") return undefined;
|
|
634
|
-
const record = input;
|
|
635
|
-
for (const key of ["arguments", "args", "query"]) {
|
|
636
|
-
const value = record[key];
|
|
637
|
-
if (typeof value === "string" && value.trim()) return value.trim();
|
|
304
|
+
return _el$1;
|
|
305
|
+
})()));
|
|
306
|
+
return _el$9;
|
|
307
|
+
})();
|
|
638
308
|
}
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
309
|
+
// Read in the JSX, so the table is laid out again when the terminal is resized.
|
|
310
|
+
const table = () => fitTableToWidth({
|
|
311
|
+
headers: block.headers.map(sanitizeDisplayText),
|
|
312
|
+
fullHeaders: block.fullHeaders?.map(sanitizeDisplayText),
|
|
313
|
+
rows: block.rows.map(row => row.map(sanitizeDisplayText)),
|
|
314
|
+
aligns: block.aligns,
|
|
315
|
+
width: props.width
|
|
316
|
+
});
|
|
642
317
|
return (() => {
|
|
643
|
-
var _el$
|
|
644
|
-
_el$
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
_$
|
|
648
|
-
_$
|
|
649
|
-
_$
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
318
|
+
var _el$12 = _$createElement("box"),
|
|
319
|
+
_el$13 = _$createElement("text");
|
|
320
|
+
_$insertNode(_el$12, _el$13);
|
|
321
|
+
_$setProp(_el$12, "flexDirection", "column");
|
|
322
|
+
_$setProp(_el$13, "wrapMode", "none");
|
|
323
|
+
_$insert(_el$13, () => table().header);
|
|
324
|
+
_$insert(_el$12, () => table().rows.map(row => (() => {
|
|
325
|
+
var _el$14 = _$createElement("text");
|
|
326
|
+
_$setProp(_el$14, "wrapMode", "none");
|
|
327
|
+
_$insert(_el$14, row);
|
|
328
|
+
_$effect(_$p => _$setProp(_el$14, "fg", props.theme.text.base, _$p));
|
|
329
|
+
return _el$14;
|
|
330
|
+
})()), null);
|
|
656
331
|
_$effect(_p$ => {
|
|
657
|
-
var _v$8 =
|
|
658
|
-
_v$9 = props.
|
|
659
|
-
_v$8 !== _p$.e && (_p$.e = _$setProp(_el$
|
|
660
|
-
_v$9 !== _p$.t && (_p$.t = _$setProp(_el$
|
|
332
|
+
var _v$8 = TextAttributes.BOLD,
|
|
333
|
+
_v$9 = props.theme.text.action.primary.selected;
|
|
334
|
+
_v$8 !== _p$.e && (_p$.e = _$setProp(_el$13, "attributes", _v$8, _p$.e));
|
|
335
|
+
_v$9 !== _p$.t && (_p$.t = _$setProp(_el$13, "fg", _v$9, _p$.t));
|
|
661
336
|
return _p$;
|
|
662
337
|
}, {
|
|
663
338
|
e: undefined,
|
|
664
339
|
t: undefined
|
|
665
340
|
});
|
|
666
|
-
return _el$
|
|
341
|
+
return _el$12;
|
|
342
|
+
})();
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Draws a report document's sections with the same spacing as its plain text: one blank row
|
|
347
|
+
* between the sections and between the blocks of a section, and a section title directly
|
|
348
|
+
* above its first block. Section titles are bold. A /quota report's provider sections come
|
|
349
|
+
* first; when their rows fit the dialog's tables (see layoutQuotaTables), the tables take
|
|
350
|
+
* their place. The dialog shows the heading's subtitle under its own title, so the heading
|
|
351
|
+
* line is left out.
|
|
352
|
+
*/
|
|
353
|
+
function ReportDocumentView(props) {
|
|
354
|
+
const sections = renderableSections(props.document);
|
|
355
|
+
const blocks = sections.flatMap(quotaBlocks);
|
|
356
|
+
// Read in the JSX, so the tables are laid out again when the terminal is resized.
|
|
357
|
+
const tables = () => blocks.length > 0 ? layoutQuotaTables(blocks, props.width) : undefined;
|
|
358
|
+
return (() => {
|
|
359
|
+
var _el$15 = _$createElement("box");
|
|
360
|
+
_$setProp(_el$15, "flexDirection", "column");
|
|
361
|
+
_$setProp(_el$15, "gap", 1);
|
|
362
|
+
_$insert(_el$15, () => [...(tables() ?? []).map(table => _$createComponent(QuotaTableView, {
|
|
363
|
+
table: table,
|
|
364
|
+
get theme() {
|
|
365
|
+
return props.theme;
|
|
366
|
+
}
|
|
367
|
+
})), ...sections.filter(section => !tables() || !section.blocks.some(block => block.kind === "quota")).map(section => (() => {
|
|
368
|
+
var _el$16 = _$createElement("box"),
|
|
369
|
+
_el$17 = _$createElement("box");
|
|
370
|
+
_$insertNode(_el$16, _el$17);
|
|
371
|
+
_$setProp(_el$16, "flexDirection", "column");
|
|
372
|
+
_$insert(_el$16, (() => {
|
|
373
|
+
var _c$ = _$memo(() => !!section.title);
|
|
374
|
+
return () => _c$() ? (() => {
|
|
375
|
+
var _el$18 = _$createElement("text");
|
|
376
|
+
_$insert(_el$18, () => sanitizeDisplayText(section.title));
|
|
377
|
+
_$effect(_p$ => {
|
|
378
|
+
var _v$0 = TextAttributes.BOLD,
|
|
379
|
+
_v$1 = props.theme.text.base;
|
|
380
|
+
_v$0 !== _p$.e && (_p$.e = _$setProp(_el$18, "attributes", _v$0, _p$.e));
|
|
381
|
+
_v$1 !== _p$.t && (_p$.t = _$setProp(_el$18, "fg", _v$1, _p$.t));
|
|
382
|
+
return _p$;
|
|
383
|
+
}, {
|
|
384
|
+
e: undefined,
|
|
385
|
+
t: undefined
|
|
386
|
+
});
|
|
387
|
+
return _el$18;
|
|
388
|
+
})() : null;
|
|
389
|
+
})(), _el$17);
|
|
390
|
+
_$setProp(_el$17, "flexDirection", "column");
|
|
391
|
+
_$setProp(_el$17, "gap", 1);
|
|
392
|
+
_$insert(_el$17, () => section.blocks.map(block => _$createComponent(ReportBlockView, {
|
|
393
|
+
block: block,
|
|
394
|
+
get theme() {
|
|
395
|
+
return props.theme;
|
|
396
|
+
},
|
|
397
|
+
get width() {
|
|
398
|
+
return props.width;
|
|
399
|
+
}
|
|
400
|
+
})));
|
|
401
|
+
return _el$16;
|
|
402
|
+
})())]);
|
|
403
|
+
return _el$15;
|
|
667
404
|
})();
|
|
668
405
|
}
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
406
|
+
|
|
407
|
+
/**
|
|
408
|
+
* Shows command output like OpenCode's alert dialog, but inside a scrollbox so long
|
|
409
|
+
* reports (/quota_status, /tokens_*) stay reachable. The mouse wheel scrolls the box;
|
|
410
|
+
* arrows, PageUp/PageDown, and Home/End scroll it from the keyboard. Esc is handled by
|
|
411
|
+
* the host dialog; Enter and the ok/esc labels close it.
|
|
412
|
+
*/
|
|
413
|
+
function QuotaOutputDialog(props) {
|
|
414
|
+
const theme = () => props.context.theme.surface("dialog");
|
|
415
|
+
const dimensions = useTerminalDimensions();
|
|
416
|
+
// OpenCode's dialog is its size's width, at most the terminal width minus 2. Inside it
|
|
417
|
+
// come this box's paddings (2 + 2) and one column for the scrollbox's scrollbar, which
|
|
418
|
+
// shows when the report is taller than the box.
|
|
419
|
+
const tableWidth = () => Math.min(DIALOG_WIDTHS[props.size], dimensions().width - 2) - 4 - 1;
|
|
420
|
+
const subtitle = props.document.heading?.subtitle;
|
|
421
|
+
// The host dialog starts a quarter of the way down the terminal. The remaining
|
|
422
|
+
// 8 rows cover the title, ok button, paddings, gaps, and one spare row; the subtitle
|
|
423
|
+
// takes one more.
|
|
424
|
+
const maxHeight = () => Math.max(1, Math.floor(dimensions().height * 0.75) - 8 - (subtitle ? 1 : 0));
|
|
425
|
+
let scroll;
|
|
426
|
+
const close = () => props.context.ui.dialog.clear();
|
|
427
|
+
props.context.keymap.layer(() => ({
|
|
428
|
+
mode: "modal",
|
|
429
|
+
commands: [{
|
|
430
|
+
bind: "return",
|
|
431
|
+
title: "Close",
|
|
432
|
+
group: "Dialog",
|
|
433
|
+
run: close
|
|
434
|
+
}, {
|
|
435
|
+
bind: "up",
|
|
436
|
+
title: "Scroll up",
|
|
437
|
+
group: "Dialog",
|
|
438
|
+
run: () => scroll?.scrollBy(-1)
|
|
439
|
+
}, {
|
|
440
|
+
bind: "down",
|
|
441
|
+
title: "Scroll down",
|
|
442
|
+
group: "Dialog",
|
|
443
|
+
run: () => scroll?.scrollBy(1)
|
|
444
|
+
}, {
|
|
445
|
+
bind: "pageup",
|
|
446
|
+
title: "Scroll up one page",
|
|
447
|
+
group: "Dialog",
|
|
448
|
+
run: () => scroll?.scrollBy(-maxHeight())
|
|
449
|
+
}, {
|
|
450
|
+
bind: "pagedown",
|
|
451
|
+
title: "Scroll down one page",
|
|
452
|
+
group: "Dialog",
|
|
453
|
+
run: () => scroll?.scrollBy(maxHeight())
|
|
454
|
+
}, {
|
|
455
|
+
bind: "home",
|
|
456
|
+
title: "Scroll to top",
|
|
457
|
+
group: "Dialog",
|
|
458
|
+
run: () => scroll?.scrollTo(0)
|
|
459
|
+
}, {
|
|
460
|
+
bind: "end",
|
|
461
|
+
title: "Scroll to bottom",
|
|
462
|
+
group: "Dialog",
|
|
463
|
+
run: () => scroll?.scrollTo(scroll.scrollHeight)
|
|
464
|
+
}]
|
|
465
|
+
}));
|
|
672
466
|
return (() => {
|
|
673
|
-
var _el$
|
|
674
|
-
_el$
|
|
675
|
-
_el$
|
|
676
|
-
_el$
|
|
467
|
+
var _el$19 = _$createElement("box"),
|
|
468
|
+
_el$20 = _$createElement("box"),
|
|
469
|
+
_el$21 = _$createElement("box"),
|
|
470
|
+
_el$22 = _$createElement("text"),
|
|
471
|
+
_el$23 = _$createElement("text"),
|
|
472
|
+
_el$25 = _$createElement("box"),
|
|
473
|
+
_el$26 = _$createElement("scrollbox"),
|
|
474
|
+
_el$27 = _$createElement("box"),
|
|
677
475
|
_el$28 = _$createElement("box"),
|
|
678
476
|
_el$29 = _$createElement("text");
|
|
679
|
-
_$insertNode(_el$
|
|
680
|
-
_$insertNode(_el$
|
|
681
|
-
_$insertNode(_el$
|
|
682
|
-
_$setProp(_el$
|
|
683
|
-
_$setProp(_el$
|
|
684
|
-
_$setProp(_el$
|
|
685
|
-
_$
|
|
686
|
-
_$
|
|
687
|
-
_$
|
|
688
|
-
_$
|
|
689
|
-
_$
|
|
690
|
-
_$
|
|
691
|
-
_$
|
|
692
|
-
_$setProp(_el$
|
|
693
|
-
_$
|
|
694
|
-
_$setProp(_el$28, "gap", 0);
|
|
695
|
-
_$setProp(_el$28, "width", "100%");
|
|
696
|
-
_$setProp(_el$28, "minWidth", 0);
|
|
697
|
-
_$insert(_el$28, () => lines().map(line => (() => {
|
|
477
|
+
_$insertNode(_el$19, _el$20);
|
|
478
|
+
_$insertNode(_el$19, _el$25);
|
|
479
|
+
_$insertNode(_el$19, _el$27);
|
|
480
|
+
_$setProp(_el$19, "paddingLeft", 2);
|
|
481
|
+
_$setProp(_el$19, "paddingRight", 2);
|
|
482
|
+
_$setProp(_el$19, "gap", 1);
|
|
483
|
+
_$insertNode(_el$20, _el$21);
|
|
484
|
+
_$insertNode(_el$21, _el$22);
|
|
485
|
+
_$insertNode(_el$21, _el$23);
|
|
486
|
+
_$setProp(_el$21, "flexDirection", "row");
|
|
487
|
+
_$setProp(_el$21, "justifyContent", "space-between");
|
|
488
|
+
_$insert(_el$22, () => props.title);
|
|
489
|
+
_$insertNode(_el$23, _$createTextNode(`esc`));
|
|
490
|
+
_$setProp(_el$23, "onMouseUp", close);
|
|
491
|
+
_$insert(_el$20, subtitle ? (() => {
|
|
698
492
|
var _el$31 = _$createElement("text");
|
|
699
|
-
_$
|
|
700
|
-
_$setProp(_el$31, "
|
|
701
|
-
_$insert(_el$31, line || " ");
|
|
702
|
-
_$effect(_$p => _$setProp(_el$31, "fg", props.api.theme.current.text, _$p));
|
|
493
|
+
_$insert(_el$31, () => sanitizeDisplayText(subtitle));
|
|
494
|
+
_$effect(_$p => _$setProp(_el$31, "fg", theme().text.muted, _$p));
|
|
703
495
|
return _el$31;
|
|
704
|
-
})())
|
|
705
|
-
_$insertNode(_el$
|
|
706
|
-
_$
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
_el$38 = _$createElement("text");
|
|
731
|
-
_$insertNode(_el$32, _el$33);
|
|
732
|
-
_$insertNode(_el$32, _el$35);
|
|
733
|
-
_$insertNode(_el$32, _el$37);
|
|
734
|
-
_$insertNode(_el$32, _el$38);
|
|
735
|
-
_$setProp(_el$32, "gap", 1);
|
|
736
|
-
_$setProp(_el$32, "paddingLeft", 2);
|
|
737
|
-
_$setProp(_el$32, "paddingRight", 2);
|
|
738
|
-
_$setProp(_el$32, "paddingBottom", 1);
|
|
739
|
-
_$insertNode(_el$33, _el$34);
|
|
740
|
-
_$insert(_el$34, () => props.title);
|
|
741
|
-
_$insertNode(_el$35, _$createTextNode(`OpenCode Quota command failed.`));
|
|
742
|
-
_$setProp(_el$37, "wrapMode", "none");
|
|
743
|
-
_$insert(_el$37, message || "Unknown error");
|
|
744
|
-
_$insertNode(_el$38, _$createTextNode(`esc closes`));
|
|
496
|
+
})() : null, null);
|
|
497
|
+
_$insertNode(_el$25, _el$26);
|
|
498
|
+
_$setProp(_el$25, "paddingBottom", 1);
|
|
499
|
+
_$use(element => {
|
|
500
|
+
scroll = element;
|
|
501
|
+
}, _el$26);
|
|
502
|
+
_$insert(_el$26, _$createComponent(ReportDocumentView, {
|
|
503
|
+
get document() {
|
|
504
|
+
return props.document;
|
|
505
|
+
},
|
|
506
|
+
get theme() {
|
|
507
|
+
return theme();
|
|
508
|
+
},
|
|
509
|
+
get width() {
|
|
510
|
+
return tableWidth();
|
|
511
|
+
}
|
|
512
|
+
}));
|
|
513
|
+
_$insertNode(_el$27, _el$28);
|
|
514
|
+
_$setProp(_el$27, "flexDirection", "row");
|
|
515
|
+
_$setProp(_el$27, "justifyContent", "flex-end");
|
|
516
|
+
_$setProp(_el$27, "paddingBottom", 1);
|
|
517
|
+
_$insertNode(_el$28, _el$29);
|
|
518
|
+
_$setProp(_el$28, "paddingLeft", 3);
|
|
519
|
+
_$setProp(_el$28, "paddingRight", 3);
|
|
520
|
+
_$setProp(_el$28, "onMouseUp", close);
|
|
521
|
+
_$insertNode(_el$29, _$createTextNode(`ok`));
|
|
745
522
|
_$effect(_p$ => {
|
|
746
|
-
var _v$
|
|
747
|
-
_v$
|
|
748
|
-
_v$
|
|
749
|
-
_v$
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
_v$
|
|
753
|
-
_v$
|
|
523
|
+
var _v$10 = TextAttributes.BOLD,
|
|
524
|
+
_v$11 = theme().text.base,
|
|
525
|
+
_v$12 = theme().text.muted,
|
|
526
|
+
_v$13 = maxHeight(),
|
|
527
|
+
_v$14 = theme().background.action.primary.focused,
|
|
528
|
+
_v$15 = theme().text.action.primary.focused;
|
|
529
|
+
_v$10 !== _p$.e && (_p$.e = _$setProp(_el$22, "attributes", _v$10, _p$.e));
|
|
530
|
+
_v$11 !== _p$.t && (_p$.t = _$setProp(_el$22, "fg", _v$11, _p$.t));
|
|
531
|
+
_v$12 !== _p$.a && (_p$.a = _$setProp(_el$23, "fg", _v$12, _p$.a));
|
|
532
|
+
_v$13 !== _p$.o && (_p$.o = _$setProp(_el$26, "maxHeight", _v$13, _p$.o));
|
|
533
|
+
_v$14 !== _p$.i && (_p$.i = _$setProp(_el$28, "backgroundColor", _v$14, _p$.i));
|
|
534
|
+
_v$15 !== _p$.n && (_p$.n = _$setProp(_el$29, "fg", _v$15, _p$.n));
|
|
754
535
|
return _p$;
|
|
755
536
|
}, {
|
|
756
537
|
e: undefined,
|
|
757
538
|
t: undefined,
|
|
758
539
|
a: undefined,
|
|
759
|
-
o: undefined
|
|
540
|
+
o: undefined,
|
|
541
|
+
i: undefined,
|
|
542
|
+
n: undefined
|
|
760
543
|
});
|
|
761
|
-
return _el$
|
|
544
|
+
return _el$19;
|
|
762
545
|
})();
|
|
763
546
|
}
|
|
764
|
-
function
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
title: "OpenCode Quota Token Range",
|
|
769
|
-
placeholder: "YYYY-MM-DD YYYY-MM-DD",
|
|
770
|
-
description: "Enter start and end dates, for example: 2026-01-01 2026-01-15"
|
|
771
|
-
};
|
|
772
|
-
default:
|
|
773
|
-
return {
|
|
774
|
-
title: spec.title,
|
|
775
|
-
placeholder: "Optional arguments",
|
|
776
|
-
description: "Leave blank to run with no arguments."
|
|
777
|
-
};
|
|
778
|
-
}
|
|
779
|
-
}
|
|
780
|
-
function replaceDialog(api, size, render) {
|
|
781
|
-
api.ui.dialog.replace(render);
|
|
782
|
-
// OpenCode dialog.replace() resets size to medium.
|
|
783
|
-
api.ui.dialog.setSize(size);
|
|
784
|
-
}
|
|
785
|
-
async function runQuotaDialogCommandAsync(api, command, commandDisplay, rawInput, state) {
|
|
786
|
-
const spec = QUOTA_DIALOG_COMMANDS.find(item => item.id === command);
|
|
787
|
-
const argumentsText = getTuiCommandArguments(rawInput);
|
|
788
|
-
const sessionID = getActiveTuiSessionID(api);
|
|
789
|
-
if (spec.acceptsArguments && rawInput === undefined) {
|
|
790
|
-
const prompt = getCommandPromptCopy(spec);
|
|
791
|
-
replaceDialog(api, "medium", () => _$createComponent(api.ui.DialogPrompt, {
|
|
547
|
+
function showQuotaOutputDialog(context, output) {
|
|
548
|
+
return new Promise(resolve => {
|
|
549
|
+
context.ui.dialog.show(() => _$createComponent(QuotaOutputDialog, {
|
|
550
|
+
context: context,
|
|
792
551
|
get title() {
|
|
793
|
-
return
|
|
552
|
+
return output.title;
|
|
794
553
|
},
|
|
795
|
-
get
|
|
796
|
-
return
|
|
554
|
+
get document() {
|
|
555
|
+
return output.document;
|
|
797
556
|
},
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
_$setProp(_el$40, "wrapMode", "word");
|
|
801
|
-
_$insert(_el$40, () => prompt.description);
|
|
802
|
-
_$effect(_$p => _$setProp(_el$40, "fg", api.theme.current.textMuted, _$p));
|
|
803
|
-
return _el$40;
|
|
804
|
-
})(),
|
|
805
|
-
onCancel: () => api.ui.dialog.clear(),
|
|
806
|
-
onConfirm: value => {
|
|
807
|
-
void runQuotaDialogCommandAsync(api, command, commandDisplay, {
|
|
808
|
-
arguments: value.trim()
|
|
809
|
-
}, state);
|
|
810
|
-
}
|
|
811
|
-
}));
|
|
812
|
-
return;
|
|
813
|
-
}
|
|
814
|
-
const destination = commandDisplay === "inline" && sessionID ? {
|
|
815
|
-
type: "inline",
|
|
816
|
-
sessionID
|
|
817
|
-
} : {
|
|
818
|
-
type: "dialog"
|
|
819
|
-
};
|
|
820
|
-
if (destination.type === "dialog") {
|
|
821
|
-
replaceDialog(api, spec.dialogSize, () => _$createComponent(CommandLoadingDialog, {
|
|
822
|
-
api: api,
|
|
823
|
-
get title() {
|
|
824
|
-
return spec.title;
|
|
557
|
+
get size() {
|
|
558
|
+
return output.dialogSize;
|
|
825
559
|
}
|
|
826
|
-
}));
|
|
560
|
+
}), resolve);
|
|
561
|
+
context.ui.dialog.set({
|
|
562
|
+
size: output.dialogSize
|
|
563
|
+
});
|
|
564
|
+
});
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
/**
|
|
568
|
+
* Runs a quota command from the command palette or the prompt. It opens the dialog and
|
|
569
|
+
* posts no message. `typed` holds the arguments typed after the command name in the prompt.
|
|
570
|
+
*/
|
|
571
|
+
async function runQuotaCommand(context, command, sessionID, typed) {
|
|
572
|
+
const spec = QUOTA_DIALOG_COMMANDS.find(item => item.id === command);
|
|
573
|
+
let argumentsText = typed?.argumentsText;
|
|
574
|
+
// Only /tokens_between needs arguments; the palette asks for them.
|
|
575
|
+
if (command === "tokens_between" && !typed) {
|
|
576
|
+
const value = await context.ui.dialog.prompt({
|
|
577
|
+
title: spec.title,
|
|
578
|
+
placeholder: "YYYY-MM-DD YYYY-MM-DD"
|
|
579
|
+
});
|
|
580
|
+
if (value === undefined) return;
|
|
581
|
+
argumentsText = value.trim() || undefined;
|
|
827
582
|
}
|
|
828
583
|
try {
|
|
829
|
-
const result = await
|
|
584
|
+
const result = await quotaRpc(context).command({
|
|
830
585
|
command,
|
|
831
586
|
arguments: argumentsText,
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
} : undefined,
|
|
840
|
-
log: async (message, extra) => {
|
|
841
|
-
await api.client.app.log({
|
|
842
|
-
body: {
|
|
843
|
-
service: "quota-toast",
|
|
844
|
-
level: "debug",
|
|
845
|
-
message,
|
|
846
|
-
extra
|
|
847
|
-
}
|
|
848
|
-
});
|
|
849
|
-
}
|
|
587
|
+
sessionID
|
|
588
|
+
}, quotaRpcOptions(context));
|
|
589
|
+
if (result.state === "noop") return;
|
|
590
|
+
await showQuotaOutputDialog(context, {
|
|
591
|
+
title: result.title,
|
|
592
|
+
document: result.document,
|
|
593
|
+
dialogSize: result.dialogSize
|
|
850
594
|
});
|
|
851
|
-
if (result.state === "noop") {
|
|
852
|
-
if (destination.type === "dialog") api.ui.dialog.clear();
|
|
853
|
-
return;
|
|
854
|
-
}
|
|
855
|
-
if (destination.type === "inline") {
|
|
856
|
-
await api.client.session.prompt({
|
|
857
|
-
sessionID: destination.sessionID,
|
|
858
|
-
noReply: true,
|
|
859
|
-
parts: [{
|
|
860
|
-
type: "text",
|
|
861
|
-
text: result.output,
|
|
862
|
-
ignored: true
|
|
863
|
-
}]
|
|
864
|
-
});
|
|
865
|
-
return;
|
|
866
|
-
}
|
|
867
|
-
replaceDialog(api, result.dialogSize, () => _$createComponent(CommandOutputDialog, {
|
|
868
|
-
api: api,
|
|
869
|
-
get title() {
|
|
870
|
-
return result.title;
|
|
871
|
-
},
|
|
872
|
-
get output() {
|
|
873
|
-
return result.output;
|
|
874
|
-
}
|
|
875
|
-
}));
|
|
876
595
|
} catch (error) {
|
|
877
|
-
|
|
878
|
-
api: api,
|
|
879
|
-
get title() {
|
|
880
|
-
return spec.title;
|
|
881
|
-
},
|
|
882
|
-
error: error
|
|
883
|
-
}));
|
|
884
|
-
api.ui.toast({
|
|
596
|
+
context.ui.toast.show({
|
|
885
597
|
variant: "error",
|
|
886
|
-
|
|
598
|
+
title: "OpenCode Quota",
|
|
599
|
+
message: sanitizeDisplayText(rpcErrorMessage(error))
|
|
887
600
|
});
|
|
888
601
|
}
|
|
889
602
|
}
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
603
|
+
|
|
604
|
+
/**
|
|
605
|
+
* Quota slash commands that reach the server (from Web or Desktop, queued in the TUI, or
|
|
606
|
+
* typed in "inline" mode) post the report into the chat as a pending inbox item. When a
|
|
607
|
+
* report arrives for the session on screen, tuiCommandDisplay decides what the TUI shows: "dialog" opens the report in the dialog and cancels the pending
|
|
608
|
+
* item, so no chat message remains; "inline" leaves the report in the chat.
|
|
609
|
+
* A command sent from Home creates its session, and OpenCode opens that session before the
|
|
610
|
+
* server runs the command, so its report also arrives for the session on screen.
|
|
611
|
+
* The TUI cannot tell which client typed the command, so in "dialog" mode it also removes a
|
|
612
|
+
* report that Web or Desktop requested for the same session while the TUI shows it.
|
|
613
|
+
*/
|
|
614
|
+
async function showPostedQuotaReport(context, event) {
|
|
615
|
+
const sessionID = getSessionID(event);
|
|
616
|
+
if (!sessionID || sessionID !== getRouteSessionID(context)) return;
|
|
617
|
+
const inboxID = event.data?.inboxID;
|
|
618
|
+
const item = event.data?.item;
|
|
619
|
+
if (typeof inboxID !== "string") return;
|
|
620
|
+
if (item?.type !== "user" || typeof item.payload?.text !== "string") return;
|
|
621
|
+
const report = readQuotaReportMetadata(item.payload.metadata);
|
|
622
|
+
if (!report) return;
|
|
623
|
+
const spec = QUOTA_DIALOG_COMMANDS.find(candidate => candidate.id === report.command);
|
|
624
|
+
if (!spec) return;
|
|
625
|
+
const config = await loadConfig(quotaClient(context), undefined, {
|
|
626
|
+
configRootDir: quotaRoots(context).configRoot
|
|
627
|
+
});
|
|
628
|
+
if (config.tuiCommandDisplay !== "dialog") return;
|
|
629
|
+
// Cancelling deletes the pending item before it becomes a stored message, the same
|
|
630
|
+
// request the TUI sends when the user deletes a pending prompt.
|
|
631
|
+
void context.client.session.inbox.cancel({
|
|
632
|
+
sessionID,
|
|
633
|
+
inboxID
|
|
634
|
+
}).catch(error => {
|
|
635
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
636
|
+
console.warn(`[opencode-quota] failed to remove the quota report from the chat: ${message}`);
|
|
637
|
+
});
|
|
638
|
+
void showQuotaOutputDialog(context, {
|
|
639
|
+
title: report.title,
|
|
640
|
+
document: report.document,
|
|
641
|
+
dialogSize: spec.dialogSize
|
|
642
|
+
});
|
|
643
|
+
}
|
|
644
|
+
function getRouteSessionID(context) {
|
|
645
|
+
const route = context.ui.router.current();
|
|
646
|
+
return route.type === "session" ? route.sessionID : undefined;
|
|
647
|
+
}
|
|
648
|
+
function registerQuotaCommands(context) {
|
|
649
|
+
context.keymap.layer(() => ({
|
|
650
|
+
mode: "global",
|
|
893
651
|
commands: QUOTA_DIALOG_COMMANDS.map(spec => ({
|
|
894
|
-
|
|
895
|
-
name: `opencode-quota.${spec.id}`,
|
|
652
|
+
id: `quota.${spec.id}`,
|
|
896
653
|
title: spec.title,
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
run(
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
654
|
+
group: "OpenCode Quota",
|
|
655
|
+
// No slash entry: the server plugin registers the "/" commands, so each is listed once.
|
|
656
|
+
palette: true,
|
|
657
|
+
run: () => runQuotaCommand(context, spec.id, getRouteSessionID(context))
|
|
658
|
+
}))
|
|
659
|
+
}));
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
/**
|
|
663
|
+
* Runs typed quota slash commands in the TUI. On Enter, before OpenCode submits the prompt,
|
|
664
|
+
* a prompt that holds exactly a quota command is cleared and the command opens its dialog.
|
|
665
|
+
* So the report never shows in the chat, and on Home no session is created for it.
|
|
666
|
+
* Everything else, and every command in "inline" mode, goes on to OpenCode's submit, and
|
|
667
|
+
* the server command posts the report as before.
|
|
668
|
+
* The layer has no mode, so it is off while the "/" list or a dialog is open. tuiCommandDisplay
|
|
669
|
+
* must be known when Enter is pressed, so it is read at start and again after each typed
|
|
670
|
+
* quota command. Until the first read finishes, typed commands follow the default, "dialog".
|
|
671
|
+
*/
|
|
672
|
+
function registerTypedQuotaCommands(context) {
|
|
673
|
+
let commandDisplay;
|
|
674
|
+
const readCommandDisplay = () => {
|
|
675
|
+
void loadConfig(quotaClient(context), undefined, {
|
|
676
|
+
configRootDir: quotaRoots(context).configRoot
|
|
677
|
+
}).then(config => {
|
|
678
|
+
commandDisplay = config.tuiCommandDisplay;
|
|
679
|
+
}).catch(reportFailure);
|
|
680
|
+
};
|
|
681
|
+
readCommandDisplay();
|
|
682
|
+
context.keymap.layer(() => ({
|
|
683
|
+
// Above OpenCode's textarea layer, which submits the prompt on Enter.
|
|
684
|
+
priority: 1,
|
|
685
|
+
commands: [{
|
|
686
|
+
bind: "return",
|
|
687
|
+
// Synchronous on purpose: a returned promise counts as handled.
|
|
688
|
+
run: () => {
|
|
689
|
+
const editor = context.renderer.currentFocusedEditor;
|
|
690
|
+
const typed = editor ? parseQuotaSlashCommand(editor.plainText) : undefined;
|
|
691
|
+
if (!editor || !typed) return false;
|
|
692
|
+
const display = commandDisplay;
|
|
693
|
+
readCommandDisplay();
|
|
694
|
+
if (display === "inline") return false;
|
|
695
|
+
editor.clear();
|
|
696
|
+
void runQuotaCommand(context, typed.command, getRouteSessionID(context), typed);
|
|
904
697
|
}
|
|
905
|
-
}
|
|
906
|
-
|
|
907
|
-
});
|
|
908
|
-
api.lifecycle.onDispose(dispose);
|
|
698
|
+
}]
|
|
699
|
+
}));
|
|
909
700
|
}
|
|
910
|
-
function
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
701
|
+
function SidebarQuotaView(props) {
|
|
702
|
+
const [open, setOpen] = createSignal(true);
|
|
703
|
+
const [quota, setQuota] = createSignal(undefined);
|
|
704
|
+
const lines = () => quota()?.message.split("\n") ?? [];
|
|
705
|
+
const expandable = () => lines().length > 2;
|
|
706
|
+
const view = createViewRefresh(() => getQuotaMessage(props.context, props.sessionID, "sidebar"), setQuota);
|
|
707
|
+
view.refresh();
|
|
708
|
+
const interval = setInterval(view.refresh, REFRESH_INTERVAL_MS);
|
|
709
|
+
const unsubscribe = props.context.data.on("session.step.ended", event => {
|
|
710
|
+
if (getSessionID(event) === props.sessionID) view.refresh();
|
|
711
|
+
});
|
|
712
|
+
onCleanup(() => {
|
|
713
|
+
view.dispose();
|
|
714
|
+
clearInterval(interval);
|
|
715
|
+
unsubscribe();
|
|
716
|
+
});
|
|
717
|
+
return (() => {
|
|
718
|
+
var _el$32 = _$createElement("box"),
|
|
719
|
+
_el$33 = _$createElement("box"),
|
|
720
|
+
_el$35 = _$createElement("text"),
|
|
721
|
+
_el$36 = _$createElement("b");
|
|
722
|
+
_$insertNode(_el$32, _el$33);
|
|
723
|
+
_$setProp(_el$32, "flexDirection", "column");
|
|
724
|
+
_$insertNode(_el$33, _el$35);
|
|
725
|
+
_$setProp(_el$33, "flexDirection", "row");
|
|
726
|
+
_$setProp(_el$33, "gap", 1);
|
|
727
|
+
_$setProp(_el$33, "onMouseDown", () => expandable() && setOpen(value => !value));
|
|
728
|
+
_$insert(_el$33, _$createComponent(Show, {
|
|
729
|
+
get when() {
|
|
730
|
+
return expandable();
|
|
731
|
+
},
|
|
732
|
+
get children() {
|
|
733
|
+
var _el$34 = _$createElement("text");
|
|
734
|
+
_$insert(_el$34, () => open() ? "▼" : "▶");
|
|
735
|
+
_$effect(_$p => _$setProp(_el$34, "fg", props.context.theme.text.base, _$p));
|
|
736
|
+
return _el$34;
|
|
737
|
+
}
|
|
738
|
+
}), _el$35);
|
|
739
|
+
_$insertNode(_el$35, _el$36);
|
|
740
|
+
_$insertNode(_el$36, _$createTextNode(`Quota`));
|
|
741
|
+
_$insert(_el$35, _$createComponent(Show, {
|
|
742
|
+
get when() {
|
|
743
|
+
return _$memo(() => !!(expandable() && !open()))() && quota()?.activeProviderCount;
|
|
744
|
+
},
|
|
745
|
+
children: count => ` (${count()} active)`
|
|
746
|
+
}), null);
|
|
747
|
+
_$insert(_el$32, _$createComponent(Show, {
|
|
748
|
+
get when() {
|
|
749
|
+
return quota();
|
|
750
|
+
},
|
|
751
|
+
get fallback() {
|
|
752
|
+
return (() => {
|
|
753
|
+
var _el$38 = _$createElement("text");
|
|
754
|
+
_$insertNode(_el$38, _$createTextNode(`No quota data available`));
|
|
755
|
+
_$effect(_$p => _$setProp(_el$38, "fg", props.context.theme.text.muted, _$p));
|
|
756
|
+
return _el$38;
|
|
757
|
+
})();
|
|
758
|
+
},
|
|
759
|
+
get children() {
|
|
760
|
+
return _$createComponent(Show, {
|
|
761
|
+
get when() {
|
|
762
|
+
return !expandable() || open();
|
|
921
763
|
},
|
|
922
|
-
get
|
|
923
|
-
return
|
|
764
|
+
get children() {
|
|
765
|
+
return lines().map(line =>
|
|
766
|
+
// Provider headers such as "[Copilot] (individual)" are bold, like section titles.
|
|
767
|
+
line.startsWith("[") ? (() => {
|
|
768
|
+
var _el$40 = _$createElement("text"),
|
|
769
|
+
_el$41 = _$createElement("b");
|
|
770
|
+
_$insertNode(_el$40, _el$41);
|
|
771
|
+
_$setProp(_el$40, "wrapMode", "none");
|
|
772
|
+
_$insert(_el$41, line);
|
|
773
|
+
_$effect(_$p => _$setProp(_el$40, "fg", props.context.theme.text.base, _$p));
|
|
774
|
+
return _el$40;
|
|
775
|
+
})() : (() => {
|
|
776
|
+
var _el$42 = _$createElement("text");
|
|
777
|
+
_$setProp(_el$42, "wrapMode", "none");
|
|
778
|
+
_$insert(_el$42, line || " ");
|
|
779
|
+
_$effect(_$p => _$setProp(_el$42, "fg", props.context.theme.text.muted, _$p));
|
|
780
|
+
return _el$42;
|
|
781
|
+
})());
|
|
924
782
|
}
|
|
925
783
|
});
|
|
926
784
|
}
|
|
927
|
-
}
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
785
|
+
}), null);
|
|
786
|
+
_$effect(_$p => _$setProp(_el$35, "fg", props.context.theme.text.base, _$p));
|
|
787
|
+
return _el$32;
|
|
788
|
+
})();
|
|
789
|
+
}
|
|
790
|
+
const plugin = Plugin.define({
|
|
791
|
+
id: "@slkiser/opencode-quota",
|
|
792
|
+
setup(context) {
|
|
793
|
+
const api = context;
|
|
794
|
+
let disposeEvents;
|
|
795
|
+
const questionToolCalls = new Set();
|
|
796
|
+
const disposeApp = api.ui.slot({
|
|
797
|
+
append: "app",
|
|
798
|
+
render: () => {
|
|
799
|
+
// OpenCode mounts this slot again when it reconnects to its server, which can also
|
|
800
|
+
// happen right after it starts. Leaving a mount drops its keymap layers and event
|
|
801
|
+
// listeners, so each mount replaces the listeners and registers everything again.
|
|
802
|
+
disposeEvents?.();
|
|
803
|
+
registerQuotaCommands(api);
|
|
804
|
+
registerTypedQuotaCommands(api);
|
|
805
|
+
const trigger = (event, reason) => {
|
|
806
|
+
const sessionID = getSessionID(event);
|
|
807
|
+
if (!sessionID) return;
|
|
808
|
+
// As in v4, subagent (child) sessions never show quota toasts.
|
|
809
|
+
if (api.data.session.get(sessionID)?.parentID) return;
|
|
810
|
+
void getQuotaMessage(api, sessionID, reason).then(quota => {
|
|
811
|
+
if (!quota) return;
|
|
812
|
+
api.ui.toast.show({
|
|
813
|
+
variant: "info",
|
|
814
|
+
title: "OpenCode Quota",
|
|
815
|
+
message: quota.message,
|
|
816
|
+
duration: quota.duration
|
|
817
|
+
});
|
|
818
|
+
if (quota.resetNotification) {
|
|
819
|
+
api.ui.toast.show({
|
|
820
|
+
variant: "success",
|
|
821
|
+
title: "Quota available",
|
|
822
|
+
message: sanitizeDisplayText(quota.resetNotification),
|
|
823
|
+
duration: quota.duration
|
|
824
|
+
});
|
|
955
825
|
}
|
|
956
|
-
});
|
|
957
|
-
}
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
get initialLoads() {
|
|
965
|
-
return state.initialLoads;
|
|
966
|
-
},
|
|
967
|
-
get visible() {
|
|
968
|
-
return props.visible;
|
|
969
|
-
},
|
|
970
|
-
get disabled() {
|
|
971
|
-
return props.disabled;
|
|
972
|
-
},
|
|
973
|
-
get onSubmit() {
|
|
974
|
-
return props.on_submit;
|
|
975
|
-
},
|
|
976
|
-
get promptRef() {
|
|
977
|
-
return props.ref;
|
|
826
|
+
}).catch(reportFailure);
|
|
827
|
+
};
|
|
828
|
+
const onExecutionSucceeded = api.data.on("session.execution.succeeded", event => trigger(event, "idle"));
|
|
829
|
+
const onCompacted = api.data.on("session.compaction.ended", event => trigger(event, "compacted"));
|
|
830
|
+
const onQuestionStarted = api.data.on("session.tool.input.started", event => {
|
|
831
|
+
const id = event.data?.id;
|
|
832
|
+
if (event.data?.name === "question" && typeof id === "string") {
|
|
833
|
+
questionToolCalls.add(id);
|
|
978
834
|
}
|
|
979
835
|
});
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
if (state.status !== "active" || !state.registration.homeBottom) return null;
|
|
984
|
-
return _$createComponent(HomeBottomView, {
|
|
985
|
-
api: api,
|
|
986
|
-
get compactHomeBottomEnabled() {
|
|
987
|
-
return state.registration.compact.homeBottom;
|
|
988
|
-
},
|
|
989
|
-
get initialLoads() {
|
|
990
|
-
return state.initialLoads;
|
|
991
|
-
}
|
|
836
|
+
const onQuestionSucceeded = api.data.on("session.tool.success", event => {
|
|
837
|
+
const id = event.data?.id;
|
|
838
|
+
if (typeof id === "string" && questionToolCalls.delete(id)) trigger(event, "question");
|
|
992
839
|
});
|
|
840
|
+
const onQuestionFailed = api.data.on("session.tool.failed", event => {
|
|
841
|
+
const id = event.data?.id;
|
|
842
|
+
if (typeof id === "string") questionToolCalls.delete(id);
|
|
843
|
+
});
|
|
844
|
+
const onInboxEnqueued = api.data.on("session.inbox.enqueued", event => {
|
|
845
|
+
void showPostedQuotaReport(api, event).catch(reportFailure);
|
|
846
|
+
});
|
|
847
|
+
disposeEvents = () => {
|
|
848
|
+
onExecutionSucceeded();
|
|
849
|
+
onCompacted();
|
|
850
|
+
onQuestionStarted();
|
|
851
|
+
onQuestionSucceeded();
|
|
852
|
+
onQuestionFailed();
|
|
853
|
+
onInboxEnqueued();
|
|
854
|
+
questionToolCalls.clear();
|
|
855
|
+
};
|
|
856
|
+
return null;
|
|
993
857
|
}
|
|
994
|
-
}
|
|
995
|
-
});
|
|
996
|
-
}
|
|
997
|
-
async function initializeTuiRegistration(api, gate) {
|
|
998
|
-
let initialRuntimeSeed;
|
|
999
|
-
let surfaceRegistration;
|
|
1000
|
-
try {
|
|
1001
|
-
surfaceRegistration = resolveTuiSurfaceRegistration(api, {
|
|
1002
|
-
captureInitialRuntime(seed) {
|
|
1003
|
-
initialRuntimeSeed = seed;
|
|
1004
|
-
}
|
|
1005
|
-
}).then(registration => ({
|
|
1006
|
-
registration,
|
|
1007
|
-
initialRuntimeSeed
|
|
1008
|
-
})).catch(() => ({
|
|
1009
|
-
registration: FALLBACK_SURFACE_REGISTRATION
|
|
1010
|
-
}));
|
|
1011
|
-
} catch {
|
|
1012
|
-
surfaceRegistration = Promise.resolve({
|
|
1013
|
-
registration: FALLBACK_SURFACE_REGISTRATION
|
|
1014
858
|
});
|
|
859
|
+
const disposeSidebar = api.ui.slot({
|
|
860
|
+
append: "sidebar.content",
|
|
861
|
+
render: props => _$createComponent(SidebarQuotaView, {
|
|
862
|
+
context: api,
|
|
863
|
+
get sessionID() {
|
|
864
|
+
return props.sessionID;
|
|
865
|
+
}
|
|
866
|
+
})
|
|
867
|
+
});
|
|
868
|
+
const disposePrompt = api.ui.slot({
|
|
869
|
+
append: "prompt.footer",
|
|
870
|
+
render: props => _$createComponent(QuotaFooter, {
|
|
871
|
+
context: api,
|
|
872
|
+
get sessionID() {
|
|
873
|
+
return props.sessionID;
|
|
874
|
+
},
|
|
875
|
+
surface: "prompt"
|
|
876
|
+
})
|
|
877
|
+
});
|
|
878
|
+
const disposeHome = api.ui.slot({
|
|
879
|
+
append: "home.footer.status",
|
|
880
|
+
render: () => _$createComponent(QuotaFooter, {
|
|
881
|
+
context: api,
|
|
882
|
+
surface: "home"
|
|
883
|
+
})
|
|
884
|
+
});
|
|
885
|
+
return () => {
|
|
886
|
+
disposeEvents?.();
|
|
887
|
+
disposeApp();
|
|
888
|
+
disposeSidebar();
|
|
889
|
+
disposePrompt();
|
|
890
|
+
disposeHome();
|
|
891
|
+
};
|
|
1015
892
|
}
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
registration,
|
|
1019
|
-
initialRuntimeSeed
|
|
1020
|
-
}) => gate.activate(registration, initialRuntimeSeed ? createTuiInitialLoadCoordinator(initialRuntimeSeed) : undefined));
|
|
1021
|
-
registerStableTuiSlots(api, gate.current);
|
|
1022
|
-
}
|
|
1023
|
-
const tui = async api => {
|
|
1024
|
-
const registrationGate = createTuiRegistrationGate();
|
|
1025
|
-
api.lifecycle.onDispose(() => {
|
|
1026
|
-
registrationGate.dispose();
|
|
1027
|
-
disposeQuotaTelemetryOwner(createTuiQuotaClient(api));
|
|
1028
|
-
});
|
|
1029
|
-
void initializeTuiRegistration(api, registrationGate).catch(() => {});
|
|
1030
|
-
};
|
|
1031
|
-
const pluginModule = {
|
|
1032
|
-
id,
|
|
1033
|
-
tui
|
|
1034
|
-
};
|
|
1035
|
-
export default pluginModule;
|
|
893
|
+
});
|
|
894
|
+
export default plugin;
|