diff --git a/AGENTS.md b/AGENTS.md index a4b384e..15054e9 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -45,7 +45,7 @@ src/transform.ts LanguageModelV3CallOptions -> /alpha/generate envelope (return src/events.ts Async iterator over the NDJSON/SSE response body. src/usage.ts finish event -> LanguageModelV3Usage; finish-reason unification. 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/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. @@ -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`). 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 - `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` > - `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 `/alpha/whoami`, `/alpha/billing/credits`, `/alpha/billing/subscriptions`, and `/alpha/usage/summary` (the same endpoints the `cmd` CLI `/usage` uses). `server.py` predates diff --git a/README.md b/README.md index cb6822a..4e9e67a 100644 --- a/README.md +++ b/README.md @@ -156,9 +156,6 @@ CommandCode-specific: | 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_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. | `/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_QUOTA_INTERVAL_MS` | Sidebar fallback quota refresh interval in ms. | `300000` (floor `120000`) | @@ -172,18 +169,10 @@ Standard: ## Debug tracing -Set `COMMANDCODE_DEBUG=1` to write a trace of every request and stream event to a log file. -Silent (and no file is created) unless enabled. Every line is passed through `redact()` so -credentials never reach disk. It can also be flipped at runtime with `/cc-debug` (see -[Runtime toggles](#runtime-toggles)). - -```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). +Flip the `debug` key in the shared toggle file (via `/cc-debug`) to write a trace of every +request and stream event to a log file. Silent (and no file is created) unless enabled; the log +path is `/commandcode-debug.log`. Every line is passed through `redact()` so +credentials never reach disk. 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 @@ -192,8 +181,7 @@ useful for diagnosing model selection, retry, tool-call, and finish-reason issue ## ZDR header toggle 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 -[Environment variables](#environment-variables)), the `/cc-zdr` slash command, or +models. It can be enabled with the `/cc-zdr` slash command or `providerOptions.commandcode.zdr`. The value is resolved per request, so toggling it 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. 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)). -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 observable. @@ -212,8 +199,7 @@ observable. `/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 the current state. The file is `~/.config/opencode/commandcode-toggles.json` (overridable with -`COMMANDCODE_TOGGLES_FILE` — see [Environment variables](#environment-variables)); the environment -variables above are used when a key is absent. +`COMMANDCODE_TOGGLES_FILE` — see [Environment variables](#environment-variables)). 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 @@ -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/usage.ts finish event -> V3 usage; finish-reason unification. 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/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. @@ -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. | | 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. | -| 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. | | 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. | diff --git a/src/log.ts b/src/log.ts index a8d46a1..726ddfc 100644 --- a/src/log.ts +++ b/src/log.ts @@ -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 // stays dependency-free. @@ -6,9 +6,9 @@ import { appendFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; 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 { 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 { - return debugFromEnvOrFile(); + return debugEnabled(); } /** Write a trace line (appended) if tracing is enabled. Values are redacted before write. */ export function debug(scope: string, ...args: unknown[]): void { - if (!debugFromEnvOrFile()) return; + if (!debugEnabled()) return; emit(scope, args); } diff --git a/src/model.ts b/src/model.ts index 45b8890..1ec18f5 100644 --- a/src/model.ts +++ b/src/model.ts @@ -21,12 +21,12 @@ import { import { iterateEvents } from "./events.js"; import { debugWhen, isDebugEnabled } from "./log.js"; import { redact } from "./redact.js"; -import { zdrFromEnvOrFile } from "./toggles.js"; +import { zdrEnabled } from "./toggles.js"; import { transform } from "./transform.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). -// 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. function providerScoped(options: LanguageModelV3CallOptions): Record | undefined { return options.providerOptions?.["commandcode"] as Record | undefined; @@ -44,7 +44,7 @@ function resolveZdr( if (typeof scoped === "boolean") return scoped ? "1" : "0"; const header = headerValue({ ...providerHeaders, ...(options.headers ?? {}) }, "x-cmd-zdr"); if (header !== undefined) return truthyHeader(header) ? "1" : "0"; - return zdrFromEnvOrFile() ? "1" : undefined; + return zdrEnabled() ? "1" : undefined; } function resolveDebug(options: LanguageModelV3CallOptions): boolean { diff --git a/src/toggles.ts b/src/toggles.ts index 9bb92e6..df0ef66 100644 --- a/src/toggles.ts +++ b/src/toggles.ts @@ -45,17 +45,11 @@ export function toggle(name: ToggleName): Toggles { return writeToggles({ [name]: current[name] !== true }); } -export function envFlag(name: string): boolean { - return /^(1|true|yes)$/i.test(process.env[name] ?? ""); +/** Toggle state from the shared flag file; an absent key means off. */ +export function zdrEnabled(): boolean { + return readToggles().zdr === true; } -/** Flag file wins over the environment; unset file falls back to the env var. */ -export function zdrFromEnvOrFile(): boolean { - 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"); +export function debugEnabled(): boolean { + return readToggles().debug === true; } diff --git a/src/tui.tsx b/src/tui.tsx index aecb81e..14a86d0 100644 --- a/src/tui.tsx +++ b/src/tui.tsx @@ -21,7 +21,7 @@ import { createSignal } from "solid-js"; import { fetchQuota, formatQuota, formatReset, percent, quotaBar, type QuotaResult, type QuotaWindow } from "./quota.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"; @@ -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. */ function trace(...args: unknown[]): void { - if (!debugFromEnvOrFile()) return; - const file = safeEnv("COMMANDCODE_DEBUG_FILE") ?? join(tmpdir(), "commandcode-debug.log"); + if (!debugEnabled()) return; + const file = join(tmpdir(), "commandcode-debug.log"); const parts = args.map((value) => (typeof value === "string" ? value : safeJson(value))); const line = `[commandcode] ${new Date().toISOString()} [tui-quota] ${parts.join(" ")}`; try {