Compare commits
2 Commits
78a4f42bc0
...
49d518871b
| Author | SHA1 | Date | |
|---|---|---|---|
| 49d518871b | |||
| 053e34d350 |
@ -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
|
||||||
|
|||||||
53
README.md
53
README.md
@ -146,22 +146,33 @@ global config (`baseURL`/`ccVersion` are read from there too).
|
|||||||
`options.headers` (with a leading `Bearer ` stripped). If neither is present, requests are sent
|
`options.headers` (with a leading `Bearer ` stripped). If neither is present, requests are sent
|
||||||
unauthenticated and CommandCode will reject them.
|
unauthenticated and CommandCode will reject them.
|
||||||
|
|
||||||
|
## Environment variables
|
||||||
|
|
||||||
|
All environment variables read by the provider, its scripts, and its TUI plugin. String values in
|
||||||
|
`options` also accept `{env:NAME}`, which opencode/the TUI expand generically (not a fixed set).
|
||||||
|
|
||||||
|
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_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`) |
|
||||||
|
|
||||||
|
Standard:
|
||||||
|
|
||||||
|
| Name | Purpose | Default |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `XDG_CONFIG_HOME` | Base config dir for the toggle file. | `~/.config` |
|
||||||
|
| `NO_COLOR` | Disables ANSI colors in the `sync-models` picker (any value). | unset |
|
||||||
|
| `TERM` | `dumb` disables ANSI colors in the `sync-models` picker. | — |
|
||||||
|
|
||||||
## 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
|
|
||||||
```
|
|
||||||
|
|
||||||
| Env var | Default | Description |
|
|
||||||
| --- | --- | --- |
|
|
||||||
| `COMMANDCODE_DEBUG` | unset | `1`/`true`/`yes` enables tracing. |
|
|
||||||
| `COMMANDCODE_DEBUG_FILE` | `<os tempdir>/commandcode-debug.log` | Where the trace is appended. |
|
|
||||||
|
|
||||||
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
|
||||||
@ -170,8 +181,8 @@ 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, the `/cc-zdr` slash
|
models. It can be enabled with the `/cc-zdr` slash command or
|
||||||
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.
|
||||||
|
|
||||||
Precedence, highest first:
|
Precedence, highest first:
|
||||||
@ -179,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.
|
||||||
@ -189,7 +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`); 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
|
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
|
||||||
@ -244,7 +254,8 @@ The sidebar refreshes after each completed turn (`session.idle`, plus `session.s
|
|||||||
`session.updated`, and `message.updated` as fallbacks since `session.idle` is a server-plugin
|
`session.updated`, and `message.updated` as fallbacks since `session.idle` is a server-plugin
|
||||||
event that may never reach the TUI bus — all debounced) and when the active session changes,
|
event that may never reach the TUI bus — all debounced) and when the active session changes,
|
||||||
but never more than once every 2 minutes; every 5 minutes as a fallback
|
but never more than once every 2 minutes; every 5 minutes as a fallback
|
||||||
(`COMMANDCODE_QUOTA_INTERVAL_MS` overrides the fallback interval) and the
|
(`COMMANDCODE_QUOTA_INTERVAL_MS` overrides the fallback interval — see
|
||||||
|
[Environment variables](#environment-variables)) and the
|
||||||
countdown ticks every 30 seconds. Key resolution and fetch failures are redacted and shown
|
countdown ticks every 30 seconds. Key resolution and fetch failures are redacted and shown
|
||||||
inline; the panel never stays on `loading…` and never blocks the provider. Extra trace lines
|
inline; the panel never stays on `loading…` and never blocks the provider. Extra trace lines
|
||||||
(`tui-quota`) are appended to the debug log only when debug tracing is on.
|
(`tui-quota`) are appended to the debug log only when debug tracing is on.
|
||||||
@ -314,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.
|
||||||
@ -380,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. |
|
||||||
|
|||||||
12
src/log.ts
12
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
|
// 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|||||||
@ -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");
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user