Drop COMMANDCODE_DEBUG, COMMANDCODE_DEBUG_FILE, and COMMANDCODE_ZDR env vars

This commit is contained in:
nhat.nguyenhong 2026-09-24 14:26:45 +00:00
parent 053e34d350
commit 49d518871b
6 changed files with 28 additions and 48 deletions

View File

@ -45,7 +45,7 @@ src/transform.ts LanguageModelV3CallOptions -> /alpha/generate envelope (return
src/events.ts Async iterator over the NDJSON/SSE response body. src/events.ts Async iterator over the NDJSON/SSE response body.
src/usage.ts finish event -> LanguageModelV3Usage; finish-reason unification. src/usage.ts finish event -> LanguageModelV3Usage; finish-reason unification.
src/redact.ts Credential scrubbing for error surfaces. src/redact.ts Credential scrubbing for error surfaces.
src/log.ts Opt-in tracing (COMMANDCODE_DEBUG) to a log file. src/log.ts Opt-in tracing (toggle file) to a log file.
src/toggles.ts Shared toggle file (~/.config/opencode/commandcode-toggles.json). src/toggles.ts Shared toggle file (~/.config/opencode/commandcode-toggles.json).
src/quota.ts Live 5-hour/weekly/monthly quota from the CommandCode alpha billing API. src/quota.ts Live 5-hour/weekly/monthly quota from the CommandCode alpha billing API.
src/tui.tsx opencode TUI plugin: /cc-zdr, /cc-debug, /cc-status, /cc-usage + sidebar. src/tui.tsx opencode TUI plugin: /cc-zdr, /cc-debug, /cc-status, /cc-usage + sidebar.
@ -159,9 +159,9 @@ These are load-bearing. Breaking one causes silent failures in opencode.
the same `namespace: "palette"` commands the palette lists (both filter out `hidden: true`). the same `namespace: "palette"` commands the palette lists (both filter out `hidden: true`).
A slash-only entry is not expressible; registering `/cc-*` also adds them to `Ctrl+P`. A slash-only entry is not expressible; registering `/cc-*` also adds them to `Ctrl+P`.
15. **Toggles are resolved per request, never at load.** `zdr` and `debug` are read in 15. **Toggles are resolved per request, never at load.** `zdr` and `debug` are read in
`model.ts`/`log.ts` on every call from the toggle file / env, so `/cc-*` changes take effect `model.ts`/`log.ts` on every call from the toggle file, so `/cc-*` changes take effect
without restarting opencode. Precedence for `zdr`: `providerOptions.commandcode.zdr` > without restarting opencode. Precedence for `zdr`: `providerOptions.commandcode.zdr` >
`x-cmd-zdr` header > toggle file > `COMMANDCODE_ZDR`. Do not reintroduce load-time consts. `x-cmd-zdr` header > toggle file. Do not reintroduce load-time consts.
16. **Quota is live from the alpha billing API, not `server.py`.** `src/quota.ts` reads 16. **Quota is live from the alpha billing API, not `server.py`.** `src/quota.ts` reads
`/alpha/whoami`, `/alpha/billing/credits`, `/alpha/billing/subscriptions`, and `/alpha/whoami`, `/alpha/billing/credits`, `/alpha/billing/subscriptions`, and
`/alpha/usage/summary` (the same endpoints the `cmd` CLI `/usage` uses). `server.py` predates `/alpha/usage/summary` (the same endpoints the `cmd` CLI `/usage` uses). `server.py` predates

View File

@ -156,9 +156,6 @@ CommandCode-specific:
| Name | Purpose | Default | | Name | Purpose | Default |
| --- | --- | --- | | --- | --- | --- |
| `COMMANDCODE_API_KEY` | CommandCode API key for `smoke`/`quota`/`sync-models` and the TUI's key resolution; referenced from config as `{env:COMMANDCODE_API_KEY}`. | — | | `COMMANDCODE_API_KEY` | CommandCode API key for `smoke`/`quota`/`sync-models` and the TUI's key resolution; referenced from config as `{env:COMMANDCODE_API_KEY}`. | — |
| `COMMANDCODE_DEBUG` | Enables request/stream tracing (`1`/`true`/`yes`); the toggle file wins when its `debug` key is set. | unset |
| `COMMANDCODE_DEBUG_FILE` | Path the trace is appended to. | `<os tempdir>/commandcode-debug.log` |
| `COMMANDCODE_ZDR` | Enables the `x-cmd-zdr: 1` header (`1`/`true`/`yes`); the toggle file wins when its `zdr` key is set. | unset |
| `COMMANDCODE_TOGGLES_FILE` | Overrides the shared toggle-file path. | `$XDG_CONFIG_HOME/opencode/commandcode-toggles.json` | | `COMMANDCODE_TOGGLES_FILE` | Overrides the shared toggle-file path. | `$XDG_CONFIG_HOME/opencode/commandcode-toggles.json` |
| `COMMANDCODE_QUOTA_INTERVAL_MS` | Sidebar fallback quota refresh interval in ms. | `300000` (floor `120000`) | | `COMMANDCODE_QUOTA_INTERVAL_MS` | Sidebar fallback quota refresh interval in ms. | `300000` (floor `120000`) |
@ -172,18 +169,10 @@ Standard:
## Debug tracing ## Debug tracing
Set `COMMANDCODE_DEBUG=1` to write a trace of every request and stream event to a log file. Flip the `debug` key in the shared toggle file (via `/cc-debug`) to write a trace of every
Silent (and no file is created) unless enabled. Every line is passed through `redact()` so request and stream event to a log file. Silent (and no file is created) unless enabled; the log
credentials never reach disk. It can also be flipped at runtime with `/cc-debug` (see path is `<os tempdir>/commandcode-debug.log`. Every line is passed through `redact()` so
[Runtime toggles](#runtime-toggles)). credentials never reach disk.
```powershell
$env:COMMANDCODE_DEBUG = "1"
opencode run "Reply with exactly: pong" -m commandcode/deepseek/deepseek-v4.1-flash
```
`COMMANDCODE_DEBUG` and `COMMANDCODE_DEBUG_FILE` are described in
[Environment variables](#environment-variables).
The log captures the request (model id, body byte length), each HTTP attempt (status, retry The log captures the request (model id, body byte length), each HTTP attempt (status, retry
wait, elapsed ms), every stream event payload, and the terminal finish reason + usage. It is wait, elapsed ms), every stream event payload, and the terminal finish reason + usage. It is
@ -192,8 +181,7 @@ useful for diagnosing model selection, retry, tool-call, and finish-reason issue
## ZDR header toggle ## ZDR header toggle
The `x-cmd-zdr: 1` request header is **omitted by default** because it is rejected by some The `x-cmd-zdr: 1` request header is **omitted by default** because it is rejected by some
models. It can be enabled with the `COMMANDCODE_ZDR` environment variable (see models. It can be enabled with the `/cc-zdr` slash command or
[Environment variables](#environment-variables)), the `/cc-zdr` slash command, or
`providerOptions.commandcode.zdr`. The value is resolved per request, so toggling it `providerOptions.commandcode.zdr`. The value is resolved per request, so toggling it
does not require restarting opencode. does not require restarting opencode.
@ -202,7 +190,6 @@ Precedence, highest first:
1. `providerOptions.commandcode.zdr` (`true`/`false`), e.g. a model variant or agent option. 1. `providerOptions.commandcode.zdr` (`true`/`false`), e.g. a model variant or agent option.
2. An explicit `x-cmd-zdr` header in `opencode.json` `options.headers` or the call's `headers`. 2. An explicit `x-cmd-zdr` header in `opencode.json` `options.headers` or the call's `headers`.
3. The toggle file (`zdr` key — see [Runtime toggles](#runtime-toggles)). 3. The toggle file (`zdr` key — see [Runtime toggles](#runtime-toggles)).
4. `COMMANDCODE_ZDR=1|true|yes` in the environment.
With tracing enabled, the trace logs a `zdr on|off` line per request so the toggle state is With tracing enabled, the trace logs a `zdr on|off` line per request so the toggle state is
observable. observable.
@ -212,8 +199,7 @@ observable.
`/cc-zdr` and `/cc-debug` flip the `zdr` and `debug` values in a small JSON file the provider `/cc-zdr` and `/cc-debug` flip the `zdr` and `debug` values in a small JSON file the provider
reads on every request, so both settings change without restarting opencode. `/cc-status` shows reads on every request, so both settings change without restarting opencode. `/cc-status` shows
the current state. The file is `~/.config/opencode/commandcode-toggles.json` (overridable with the current state. The file is `~/.config/opencode/commandcode-toggles.json` (overridable with
`COMMANDCODE_TOGGLES_FILE` — see [Environment variables](#environment-variables)); the environment `COMMANDCODE_TOGGLES_FILE` — see [Environment variables](#environment-variables)).
variables above are used when a key is absent.
These commands come from a small TUI plugin shipped in this package (`src/tui.tsx`). It is loaded These commands come from a small TUI plugin shipped in this package (`src/tui.tsx`). It is loaded
from **source** — opencode compiles the TSX and provides the Solid runtime itself — so there is no from **source** — opencode compiles the TSX and provides the Solid runtime itself — so there is no
@ -339,7 +325,7 @@ src/transform.ts LanguageModelV3CallOptions -> /alpha/generate envelope (JSON s
src/events.ts NDJSON/SSE line iterator over the upstream response body. src/events.ts NDJSON/SSE line iterator over the upstream response body.
src/usage.ts finish event -> V3 usage; finish-reason unification. src/usage.ts finish event -> V3 usage; finish-reason unification.
src/redact.ts Credential scrubbing for error surfaces. src/redact.ts Credential scrubbing for error surfaces.
src/log.ts Opt-in tracing (COMMANDCODE_DEBUG) to a log file. src/log.ts Opt-in tracing (toggle file) to a log file.
src/toggles.ts Shared toggle file read by the provider and written by the TUI plugin. src/toggles.ts Shared toggle file read by the provider and written by the TUI plugin.
src/quota.ts Live 5-hour/weekly/monthly quota from the alpha billing endpoints. src/quota.ts Live 5-hour/weekly/monthly quota from the alpha billing endpoints.
src/tui.tsx opencode TUI plugin: /cc-zdr, /cc-debug, /cc-status, /cc-usage + sidebar panel. src/tui.tsx opencode TUI plugin: /cc-zdr, /cc-debug, /cc-status, /cc-usage + sidebar panel.
@ -405,7 +391,7 @@ opencode run "Use the glob tool to list *.mjs and report the filenames." -m comm
| `stream ended ... no finish` / truncated output | Upstream closed the connection early. The provider emits a synthetic `finish`, but the response is incomplete; retry the turn. | | `stream ended ... no finish` / truncated output | Upstream closed the connection early. The provider emits a synthetic `finish`, but the response is incomplete; retry the turn. |
| Images are ignored by the model | The selected model is not vision-capable. Mark it with `"attachment": true` and `modalities.input: ["text","image"]` in `models`, and pick a vision model id. | | Images are ignored by the model | The selected model is not vision-capable. Mark it with `"attachment": true` and `modalities.input: ["text","image"]` in `models`, and pick a vision model id. |
| Upstream 400 about tool calls | A `tool-call` or `tool-result` without a matching pair slipped through. Pairing is enforced in `src/transform.ts`; report a repro if it still occurs. | | Upstream 400 about tool calls | A `tool-call` or `tool-result` without a matching pair slipped through. Pairing is enforced in `src/transform.ts`; report a repro if it still occurs. |
| Need to see what the provider sends/receives | Set `COMMANDCODE_DEBUG=1` and read the appended log file (see [Debug tracing](#debug-tracing)). | | Need to see what the provider sends/receives | Flip `/cc-debug` and read the appended log file (see [Debug tracing](#debug-tracing)). |
| `tool_choice` seemingly ignored | Expected for `required`; upstream cannot force a call. `none` and named-tool are emulated via the tool list. | | `tool_choice` seemingly ignored | Expected for `required`; upstream cannot force a call. `none` and named-tool are emulated via the tool list. |
| Config change had no effect | opencode reads config once at startup. Restart it. | | Config change had no effect | opencode reads config once at startup. Restart it. |
| `/cc-*` commands and the sidebar panel are missing | TUI plugin not registered: add a direct `file://` URL to `src/tui.tsx` in `tui.json` (see [Runtime toggles](#runtime-toggles)). A repo-root or `src/tui.tsx`-less entry does not load. | | `/cc-*` commands and the sidebar panel are missing | TUI plugin not registered: add a direct `file://` URL to `src/tui.tsx` in `tui.json` (see [Runtime toggles](#runtime-toggles)). A repo-root or `src/tui.tsx`-less entry does not load. |

View File

@ -1,4 +1,4 @@
// Opt-in tracing to a log file. Gated on COMMANDCODE_DEBUG=1 or the shared toggle file; // Opt-in tracing to a log file. Gated on the shared toggle file (debug key);
// silent (and no file created) by default. Uses only Node built-ins so the built output // silent (and no file created) by default. Uses only Node built-ins so the built output
// stays dependency-free. // stays dependency-free.
@ -6,9 +6,9 @@ import { appendFileSync } from "node:fs";
import { tmpdir } from "node:os"; import { tmpdir } from "node:os";
import { join } from "node:path"; import { join } from "node:path";
import { redact } from "./redact.js"; import { redact } from "./redact.js";
import { debugFromEnvOrFile } from "./toggles.js"; import { debugEnabled } from "./toggles.js";
const FILE = process.env["COMMANDCODE_DEBUG_FILE"] ?? join(tmpdir(), "commandcode-debug.log"); const FILE = join(tmpdir(), "commandcode-debug.log");
function serialize(value: unknown): string { function serialize(value: unknown): string {
if (typeof value === "string") return value; if (typeof value === "string") return value;
@ -34,14 +34,14 @@ function emit(scope: string, args: unknown[]): void {
} }
} }
/** Effective default tracing state (env var or toggle file). Re-read on every call. */ /** Effective default tracing state (toggle file). Re-read on every call. */
export function isDebugEnabled(): boolean { export function isDebugEnabled(): boolean {
return debugFromEnvOrFile(); return debugEnabled();
} }
/** Write a trace line (appended) if tracing is enabled. Values are redacted before write. */ /** Write a trace line (appended) if tracing is enabled. Values are redacted before write. */
export function debug(scope: string, ...args: unknown[]): void { export function debug(scope: string, ...args: unknown[]): void {
if (!debugFromEnvOrFile()) return; if (!debugEnabled()) return;
emit(scope, args); emit(scope, args);
} }

View File

@ -21,12 +21,12 @@ import {
import { iterateEvents } from "./events.js"; import { iterateEvents } from "./events.js";
import { debugWhen, isDebugEnabled } from "./log.js"; import { debugWhen, isDebugEnabled } from "./log.js";
import { redact } from "./redact.js"; import { redact } from "./redact.js";
import { zdrFromEnvOrFile } from "./toggles.js"; import { zdrEnabled } from "./toggles.js";
import { transform } from "./transform.js"; import { transform } from "./transform.js";
import { finishReasonFrom, usageFromFinish, costFromFinishStep, costFromProviderMetadata, type FinishEvent, type FinishStepEvent, type ProviderMetadataEvent } from "./usage.js"; import { finishReasonFrom, usageFromFinish, costFromFinishStep, costFromProviderMetadata, type FinishEvent, type FinishStepEvent, type ProviderMetadataEvent } from "./usage.js";
// x-cmd-zdr breaks some models; off by default (server.py never sends it). // x-cmd-zdr breaks some models; off by default (server.py never sends it).
// Precedence: providerOptions.commandcode.zdr > x-cmd-zdr header > toggle file > COMMANDCODE_ZDR. // Precedence: providerOptions.commandcode.zdr > x-cmd-zdr header > toggle file.
// "undefined" means the header is omitted entirely. // "undefined" means the header is omitted entirely.
function providerScoped(options: LanguageModelV3CallOptions): Record<string, unknown> | undefined { function providerScoped(options: LanguageModelV3CallOptions): Record<string, unknown> | undefined {
return options.providerOptions?.["commandcode"] as Record<string, unknown> | undefined; return options.providerOptions?.["commandcode"] as Record<string, unknown> | undefined;
@ -44,7 +44,7 @@ function resolveZdr(
if (typeof scoped === "boolean") return scoped ? "1" : "0"; if (typeof scoped === "boolean") return scoped ? "1" : "0";
const header = headerValue({ ...providerHeaders, ...(options.headers ?? {}) }, "x-cmd-zdr"); const header = headerValue({ ...providerHeaders, ...(options.headers ?? {}) }, "x-cmd-zdr");
if (header !== undefined) return truthyHeader(header) ? "1" : "0"; if (header !== undefined) return truthyHeader(header) ? "1" : "0";
return zdrFromEnvOrFile() ? "1" : undefined; return zdrEnabled() ? "1" : undefined;
} }
function resolveDebug(options: LanguageModelV3CallOptions): boolean { function resolveDebug(options: LanguageModelV3CallOptions): boolean {

View File

@ -45,17 +45,11 @@ export function toggle(name: ToggleName): Toggles {
return writeToggles({ [name]: current[name] !== true }); return writeToggles({ [name]: current[name] !== true });
} }
export function envFlag(name: string): boolean { /** Toggle state from the shared flag file; an absent key means off. */
return /^(1|true|yes)$/i.test(process.env[name] ?? ""); export function zdrEnabled(): boolean {
return readToggles().zdr === true;
} }
/** Flag file wins over the environment; unset file falls back to the env var. */ export function debugEnabled(): boolean {
export function zdrFromEnvOrFile(): boolean { return readToggles().debug === true;
const file = readToggles().zdr;
return typeof file === "boolean" ? file : envFlag("COMMANDCODE_ZDR");
}
export function debugFromEnvOrFile(): boolean {
const file = readToggles().debug;
return typeof file === "boolean" ? file : envFlag("COMMANDCODE_DEBUG");
} }

View File

@ -21,7 +21,7 @@ import { createSignal } from "solid-js";
import { fetchQuota, formatQuota, formatReset, percent, quotaBar, type QuotaResult, type QuotaWindow } from "./quota.js"; import { fetchQuota, formatQuota, formatReset, percent, quotaBar, type QuotaResult, type QuotaWindow } from "./quota.js";
import { redact } from "./redact.js"; import { redact } from "./redact.js";
import { debugFromEnvOrFile, readToggles, toggle, type ToggleName, type Toggles } from "./toggles.js"; import { debugEnabled, readToggles, toggle, type ToggleName, type Toggles } from "./toggles.js";
type ToastVariant = "info" | "success" | "warning" | "error"; type ToastVariant = "info" | "success" | "warning" | "error";
@ -86,8 +86,8 @@ function safeEnv(name: string): string | undefined {
/** Debug-only trace to the shared debug file. Silent unless debug is on; never logs the key. */ /** Debug-only trace to the shared debug file. Silent unless debug is on; never logs the key. */
function trace(...args: unknown[]): void { function trace(...args: unknown[]): void {
if (!debugFromEnvOrFile()) return; if (!debugEnabled()) return;
const file = safeEnv("COMMANDCODE_DEBUG_FILE") ?? join(tmpdir(), "commandcode-debug.log"); const file = join(tmpdir(), "commandcode-debug.log");
const parts = args.map((value) => (typeof value === "string" ? value : safeJson(value))); const parts = args.map((value) => (typeof value === "string" ? value : safeJson(value)));
const line = `[commandcode] ${new Date().toISOString()} [tui-quota] ${parts.join(" ")}`; const line = `[commandcode] ${new Date().toISOString()} [tui-quota] ${parts.join(" ")}`;
try { try {