diff --git a/.env.example b/.env.example index aac4c3234470..8e201f221ad7 100644 --- a/.env.example +++ b/.env.example @@ -1027,7 +1027,7 @@ GITHUB_OAUTH_CLIENT_ID=Iv1.b507a08c87ecfe98 # Used by: open-sse/executors/base.ts — buildHeaders() dynamic lookup. # Update these when providers release new CLI versions to avoid blocks. -CLAUDE_USER_AGENT="claude-cli/2.1.207 (external, cli)" +CLAUDE_USER_AGENT="claude-cli/2.1.219 (external, cli)" # Disable the deterministic tool-name cloak applied on both Anthropic-bound paths # (executors/base.ts native OAuth + executors/cliproxyapi.ts CLIProxyAPI) — diff --git a/config/quality/eslint-suppressions.json b/config/quality/eslint-suppressions.json index 3fce34179942..f57f0d02bb05 100644 --- a/config/quality/eslint-suppressions.json +++ b/config/quality/eslint-suppressions.json @@ -4,11 +4,6 @@ "count": 1 } }, - "open-sse/executors/claudeIdentity.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 2 - } - }, "open-sse/executors/cliproxyapi.ts": { "@typescript-eslint/no-explicit-any": { "count": 1 diff --git a/docs/guides/CLAUDE-CODE-CONFIGURATION.md b/docs/guides/CLAUDE-CODE-CONFIGURATION.md index a34ee464fda4..a42ad3783e51 100644 --- a/docs/guides/CLAUDE-CODE-CONFIGURATION.md +++ b/docs/guides/CLAUDE-CODE-CONFIGURATION.md @@ -140,7 +140,7 @@ extra flags needed. Override per-invocation with `--remote` / `--api-key`. handles this for you. **`/model` picker is empty / missing gateway models** — needs Claude Code -v2.1.129+ and `CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY=1`. Only `claude*` / +v2.1.219+ and `CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY=1`. Only `claude*` / `anthropic*` model IDs appear in the picker; force any other model with `ANTHROPIC_MODEL=` (this is what profiles do). diff --git a/docs/i18n/ar/docs/reference/ENVIRONMENT.md b/docs/i18n/ar/docs/reference/ENVIRONMENT.md index 617d102679c1..fbda74516d2e 100644 --- a/docs/i18n/ar/docs/reference/ENVIRONMENT.md +++ b/docs/i18n/ar/docs/reference/ENVIRONMENT.md @@ -336,7 +336,7 @@ process.env[`${PROVIDER_ID}_USER_AGENT`] | Variable | Default Value | When to Update | | ------------------------ | --------------------------------------------- | ------------------------------------------------------------- | -| `CLAUDE_USER_AGENT` | `claude-cli/2.1.145 (external, cli)` | When Anthropic releases a new CLI version | +| `CLAUDE_USER_AGENT` | `claude-cli/2.1.219 (external, cli)` | When Anthropic releases a new CLI version | | `CODEX_USER_AGENT` | `codex-cli/0.132.0 (Windows 10.0.26200; x64)` | When OpenAI updates the Codex CLI | | `CODEX_CLIENT_VERSION` | `0.131.0` | Override Codex client version independently of full UA string | | `GITHUB_USER_AGENT` | `GitHubCopilotChat/0.45.1` | When GitHub Copilot Chat updates | diff --git a/docs/i18n/az/docs/reference/ENVIRONMENT.md b/docs/i18n/az/docs/reference/ENVIRONMENT.md index 40ff0ddc5479..e3adbd5aa767 100644 --- a/docs/i18n/az/docs/reference/ENVIRONMENT.md +++ b/docs/i18n/az/docs/reference/ENVIRONMENT.md @@ -336,7 +336,7 @@ process.env[`${PROVIDER_ID}_USER_AGENT`] | Variable | Default Value | When to Update | | ------------------------ | --------------------------------------------- | ------------------------------------------------------------- | -| `CLAUDE_USER_AGENT` | `claude-cli/2.1.145 (external, cli)` | When Anthropic releases a new CLI version | +| `CLAUDE_USER_AGENT` | `claude-cli/2.1.219 (external, cli)` | When Anthropic releases a new CLI version | | `CODEX_USER_AGENT` | `codex-cli/0.132.0 (Windows 10.0.26200; x64)` | When OpenAI updates the Codex CLI | | `CODEX_CLIENT_VERSION` | `0.131.0` | Override Codex client version independently of full UA string | | `GITHUB_USER_AGENT` | `GitHubCopilotChat/0.45.1` | When GitHub Copilot Chat updates | diff --git a/docs/i18n/bg/docs/reference/ENVIRONMENT.md b/docs/i18n/bg/docs/reference/ENVIRONMENT.md index bbaf0c7a70e1..23d09f406f65 100644 --- a/docs/i18n/bg/docs/reference/ENVIRONMENT.md +++ b/docs/i18n/bg/docs/reference/ENVIRONMENT.md @@ -336,7 +336,7 @@ process.env[`${PROVIDER_ID}_USER_AGENT`] | Variable | Default Value | When to Update | | ------------------------ | --------------------------------------------- | ------------------------------------------------------------- | -| `CLAUDE_USER_AGENT` | `claude-cli/2.1.145 (external, cli)` | When Anthropic releases a new CLI version | +| `CLAUDE_USER_AGENT` | `claude-cli/2.1.219 (external, cli)` | When Anthropic releases a new CLI version | | `CODEX_USER_AGENT` | `codex-cli/0.132.0 (Windows 10.0.26200; x64)` | When OpenAI updates the Codex CLI | | `CODEX_CLIENT_VERSION` | `0.131.0` | Override Codex client version independently of full UA string | | `GITHUB_USER_AGENT` | `GitHubCopilotChat/0.45.1` | When GitHub Copilot Chat updates | diff --git a/docs/i18n/bn/docs/reference/ENVIRONMENT.md b/docs/i18n/bn/docs/reference/ENVIRONMENT.md index 01947bb475f2..930f0c6db1a1 100644 --- a/docs/i18n/bn/docs/reference/ENVIRONMENT.md +++ b/docs/i18n/bn/docs/reference/ENVIRONMENT.md @@ -336,7 +336,7 @@ process.env[`${PROVIDER_ID}_USER_AGENT`] | Variable | Default Value | When to Update | | ------------------------ | --------------------------------------------- | ------------------------------------------------------------- | -| `CLAUDE_USER_AGENT` | `claude-cli/2.1.145 (external, cli)` | When Anthropic releases a new CLI version | +| `CLAUDE_USER_AGENT` | `claude-cli/2.1.219 (external, cli)` | When Anthropic releases a new CLI version | | `CODEX_USER_AGENT` | `codex-cli/0.132.0 (Windows 10.0.26200; x64)` | When OpenAI updates the Codex CLI | | `CODEX_CLIENT_VERSION` | `0.131.0` | Override Codex client version independently of full UA string | | `GITHUB_USER_AGENT` | `GitHubCopilotChat/0.45.1` | When GitHub Copilot Chat updates | diff --git a/docs/i18n/cs/docs/reference/ENVIRONMENT.md b/docs/i18n/cs/docs/reference/ENVIRONMENT.md index df0b88c3d10b..f9e203345e37 100644 --- a/docs/i18n/cs/docs/reference/ENVIRONMENT.md +++ b/docs/i18n/cs/docs/reference/ENVIRONMENT.md @@ -336,7 +336,7 @@ process.env[`${PROVIDER_ID}_USER_AGENT`] | Variable | Default Value | When to Update | | ------------------------ | --------------------------------------------- | ------------------------------------------------------------- | -| `CLAUDE_USER_AGENT` | `claude-cli/2.1.145 (external, cli)` | When Anthropic releases a new CLI version | +| `CLAUDE_USER_AGENT` | `claude-cli/2.1.219 (external, cli)` | When Anthropic releases a new CLI version | | `CODEX_USER_AGENT` | `codex-cli/0.132.0 (Windows 10.0.26200; x64)` | When OpenAI updates the Codex CLI | | `CODEX_CLIENT_VERSION` | `0.131.0` | Override Codex client version independently of full UA string | | `GITHUB_USER_AGENT` | `GitHubCopilotChat/0.45.1` | When GitHub Copilot Chat updates | diff --git a/docs/i18n/da/docs/reference/ENVIRONMENT.md b/docs/i18n/da/docs/reference/ENVIRONMENT.md index baba753dbdeb..6c48888ad6fb 100644 --- a/docs/i18n/da/docs/reference/ENVIRONMENT.md +++ b/docs/i18n/da/docs/reference/ENVIRONMENT.md @@ -336,7 +336,7 @@ process.env[`${PROVIDER_ID}_USER_AGENT`] | Variable | Default Value | When to Update | | ------------------------ | --------------------------------------------- | ------------------------------------------------------------- | -| `CLAUDE_USER_AGENT` | `claude-cli/2.1.145 (external, cli)` | When Anthropic releases a new CLI version | +| `CLAUDE_USER_AGENT` | `claude-cli/2.1.219 (external, cli)` | When Anthropic releases a new CLI version | | `CODEX_USER_AGENT` | `codex-cli/0.132.0 (Windows 10.0.26200; x64)` | When OpenAI updates the Codex CLI | | `CODEX_CLIENT_VERSION` | `0.131.0` | Override Codex client version independently of full UA string | | `GITHUB_USER_AGENT` | `GitHubCopilotChat/0.45.1` | When GitHub Copilot Chat updates | diff --git a/docs/i18n/de/docs/reference/ENVIRONMENT.md b/docs/i18n/de/docs/reference/ENVIRONMENT.md index 2efa0f6f6118..61d54d53db79 100644 --- a/docs/i18n/de/docs/reference/ENVIRONMENT.md +++ b/docs/i18n/de/docs/reference/ENVIRONMENT.md @@ -336,7 +336,7 @@ process.env[`${PROVIDER_ID}_USER_AGENT`] | Variable | Default Value | When to Update | | ------------------------ | --------------------------------------------- | ------------------------------------------------------------- | -| `CLAUDE_USER_AGENT` | `claude-cli/2.1.145 (external, cli)` | When Anthropic releases a new CLI version | +| `CLAUDE_USER_AGENT` | `claude-cli/2.1.219 (external, cli)` | When Anthropic releases a new CLI version | | `CODEX_USER_AGENT` | `codex-cli/0.132.0 (Windows 10.0.26200; x64)` | When OpenAI updates the Codex CLI | | `CODEX_CLIENT_VERSION` | `0.131.0` | Override Codex client version independently of full UA string | | `GITHUB_USER_AGENT` | `GitHubCopilotChat/0.45.1` | When GitHub Copilot Chat updates | diff --git a/docs/i18n/es/docs/reference/ENVIRONMENT.md b/docs/i18n/es/docs/reference/ENVIRONMENT.md index 62535ca0c77a..50bbb41e3ef7 100644 --- a/docs/i18n/es/docs/reference/ENVIRONMENT.md +++ b/docs/i18n/es/docs/reference/ENVIRONMENT.md @@ -336,7 +336,7 @@ process.env[`${PROVIDER_ID}_USER_AGENT`] | Variable | Default Value | When to Update | | ------------------------ | --------------------------------------------- | ------------------------------------------------------------- | -| `CLAUDE_USER_AGENT` | `claude-cli/2.1.145 (external, cli)` | When Anthropic releases a new CLI version | +| `CLAUDE_USER_AGENT` | `claude-cli/2.1.219 (external, cli)` | When Anthropic releases a new CLI version | | `CODEX_USER_AGENT` | `codex-cli/0.132.0 (Windows 10.0.26200; x64)` | When OpenAI updates the Codex CLI | | `CODEX_CLIENT_VERSION` | `0.131.0` | Override Codex client version independently of full UA string | | `GITHUB_USER_AGENT` | `GitHubCopilotChat/0.45.1` | When GitHub Copilot Chat updates | diff --git a/docs/i18n/fa/docs/reference/ENVIRONMENT.md b/docs/i18n/fa/docs/reference/ENVIRONMENT.md index f2d97d3e0df7..2906e5e48e78 100644 --- a/docs/i18n/fa/docs/reference/ENVIRONMENT.md +++ b/docs/i18n/fa/docs/reference/ENVIRONMENT.md @@ -336,7 +336,7 @@ process.env[`${PROVIDER_ID}_USER_AGENT`] | Variable | Default Value | When to Update | | ------------------------ | --------------------------------------------- | ------------------------------------------------------------- | -| `CLAUDE_USER_AGENT` | `claude-cli/2.1.145 (external, cli)` | When Anthropic releases a new CLI version | +| `CLAUDE_USER_AGENT` | `claude-cli/2.1.219 (external, cli)` | When Anthropic releases a new CLI version | | `CODEX_USER_AGENT` | `codex-cli/0.132.0 (Windows 10.0.26200; x64)` | When OpenAI updates the Codex CLI | | `CODEX_CLIENT_VERSION` | `0.131.0` | Override Codex client version independently of full UA string | | `GITHUB_USER_AGENT` | `GitHubCopilotChat/0.45.1` | When GitHub Copilot Chat updates | diff --git a/docs/i18n/fi/docs/reference/ENVIRONMENT.md b/docs/i18n/fi/docs/reference/ENVIRONMENT.md index ee75d7d0db3c..a5c85d6efc37 100644 --- a/docs/i18n/fi/docs/reference/ENVIRONMENT.md +++ b/docs/i18n/fi/docs/reference/ENVIRONMENT.md @@ -336,7 +336,7 @@ process.env[`${PROVIDER_ID}_USER_AGENT`] | Variable | Default Value | When to Update | | ------------------------ | --------------------------------------------- | ------------------------------------------------------------- | -| `CLAUDE_USER_AGENT` | `claude-cli/2.1.145 (external, cli)` | When Anthropic releases a new CLI version | +| `CLAUDE_USER_AGENT` | `claude-cli/2.1.219 (external, cli)` | When Anthropic releases a new CLI version | | `CODEX_USER_AGENT` | `codex-cli/0.132.0 (Windows 10.0.26200; x64)` | When OpenAI updates the Codex CLI | | `CODEX_CLIENT_VERSION` | `0.131.0` | Override Codex client version independently of full UA string | | `GITHUB_USER_AGENT` | `GitHubCopilotChat/0.45.1` | When GitHub Copilot Chat updates | diff --git a/docs/i18n/fr/docs/reference/ENVIRONMENT.md b/docs/i18n/fr/docs/reference/ENVIRONMENT.md index 868b8e3ce7f6..74b3eacb4b98 100644 --- a/docs/i18n/fr/docs/reference/ENVIRONMENT.md +++ b/docs/i18n/fr/docs/reference/ENVIRONMENT.md @@ -336,7 +336,7 @@ process.env[`${PROVIDER_ID}_USER_AGENT`] | Variable | Default Value | When to Update | | ------------------------ | --------------------------------------------- | ------------------------------------------------------------- | -| `CLAUDE_USER_AGENT` | `claude-cli/2.1.145 (external, cli)` | When Anthropic releases a new CLI version | +| `CLAUDE_USER_AGENT` | `claude-cli/2.1.219 (external, cli)` | When Anthropic releases a new CLI version | | `CODEX_USER_AGENT` | `codex-cli/0.132.0 (Windows 10.0.26200; x64)` | When OpenAI updates the Codex CLI | | `CODEX_CLIENT_VERSION` | `0.131.0` | Override Codex client version independently of full UA string | | `GITHUB_USER_AGENT` | `GitHubCopilotChat/0.45.1` | When GitHub Copilot Chat updates | diff --git a/docs/i18n/gu/docs/reference/ENVIRONMENT.md b/docs/i18n/gu/docs/reference/ENVIRONMENT.md index 77e51456886a..7596561b9086 100644 --- a/docs/i18n/gu/docs/reference/ENVIRONMENT.md +++ b/docs/i18n/gu/docs/reference/ENVIRONMENT.md @@ -336,7 +336,7 @@ process.env[`${PROVIDER_ID}_USER_AGENT`] | Variable | Default Value | When to Update | | ------------------------ | --------------------------------------------- | ------------------------------------------------------------- | -| `CLAUDE_USER_AGENT` | `claude-cli/2.1.145 (external, cli)` | When Anthropic releases a new CLI version | +| `CLAUDE_USER_AGENT` | `claude-cli/2.1.219 (external, cli)` | When Anthropic releases a new CLI version | | `CODEX_USER_AGENT` | `codex-cli/0.132.0 (Windows 10.0.26200; x64)` | When OpenAI updates the Codex CLI | | `CODEX_CLIENT_VERSION` | `0.131.0` | Override Codex client version independently of full UA string | | `GITHUB_USER_AGENT` | `GitHubCopilotChat/0.45.1` | When GitHub Copilot Chat updates | diff --git a/docs/i18n/he/docs/reference/ENVIRONMENT.md b/docs/i18n/he/docs/reference/ENVIRONMENT.md index 2fa4e30d62bf..8529aa9fffe0 100644 --- a/docs/i18n/he/docs/reference/ENVIRONMENT.md +++ b/docs/i18n/he/docs/reference/ENVIRONMENT.md @@ -336,7 +336,7 @@ process.env[`${PROVIDER_ID}_USER_AGENT`] | Variable | Default Value | When to Update | | ------------------------ | --------------------------------------------- | ------------------------------------------------------------- | -| `CLAUDE_USER_AGENT` | `claude-cli/2.1.145 (external, cli)` | When Anthropic releases a new CLI version | +| `CLAUDE_USER_AGENT` | `claude-cli/2.1.219 (external, cli)` | When Anthropic releases a new CLI version | | `CODEX_USER_AGENT` | `codex-cli/0.132.0 (Windows 10.0.26200; x64)` | When OpenAI updates the Codex CLI | | `CODEX_CLIENT_VERSION` | `0.131.0` | Override Codex client version independently of full UA string | | `GITHUB_USER_AGENT` | `GitHubCopilotChat/0.45.1` | When GitHub Copilot Chat updates | diff --git a/docs/i18n/hi/docs/reference/ENVIRONMENT.md b/docs/i18n/hi/docs/reference/ENVIRONMENT.md index b2c5e0872547..57c46cf699ea 100644 --- a/docs/i18n/hi/docs/reference/ENVIRONMENT.md +++ b/docs/i18n/hi/docs/reference/ENVIRONMENT.md @@ -336,7 +336,7 @@ process.env[`${PROVIDER_ID}_USER_AGENT`] | Variable | Default Value | When to Update | | ------------------------ | --------------------------------------------- | ------------------------------------------------------------- | -| `CLAUDE_USER_AGENT` | `claude-cli/2.1.145 (external, cli)` | When Anthropic releases a new CLI version | +| `CLAUDE_USER_AGENT` | `claude-cli/2.1.219 (external, cli)` | When Anthropic releases a new CLI version | | `CODEX_USER_AGENT` | `codex-cli/0.132.0 (Windows 10.0.26200; x64)` | When OpenAI updates the Codex CLI | | `CODEX_CLIENT_VERSION` | `0.131.0` | Override Codex client version independently of full UA string | | `GITHUB_USER_AGENT` | `GitHubCopilotChat/0.45.1` | When GitHub Copilot Chat updates | diff --git a/docs/i18n/hu/docs/reference/ENVIRONMENT.md b/docs/i18n/hu/docs/reference/ENVIRONMENT.md index c45ff83c214c..b8df6d27841b 100644 --- a/docs/i18n/hu/docs/reference/ENVIRONMENT.md +++ b/docs/i18n/hu/docs/reference/ENVIRONMENT.md @@ -336,7 +336,7 @@ process.env[`${PROVIDER_ID}_USER_AGENT`] | Variable | Default Value | When to Update | | ------------------------ | --------------------------------------------- | ------------------------------------------------------------- | -| `CLAUDE_USER_AGENT` | `claude-cli/2.1.145 (external, cli)` | When Anthropic releases a new CLI version | +| `CLAUDE_USER_AGENT` | `claude-cli/2.1.219 (external, cli)` | When Anthropic releases a new CLI version | | `CODEX_USER_AGENT` | `codex-cli/0.132.0 (Windows 10.0.26200; x64)` | When OpenAI updates the Codex CLI | | `CODEX_CLIENT_VERSION` | `0.131.0` | Override Codex client version independently of full UA string | | `GITHUB_USER_AGENT` | `GitHubCopilotChat/0.45.1` | When GitHub Copilot Chat updates | diff --git a/docs/i18n/id/docs/reference/ENVIRONMENT.md b/docs/i18n/id/docs/reference/ENVIRONMENT.md index 52daf2c47578..682d53ad804b 100644 --- a/docs/i18n/id/docs/reference/ENVIRONMENT.md +++ b/docs/i18n/id/docs/reference/ENVIRONMENT.md @@ -336,7 +336,7 @@ process.env[`${PROVIDER_ID}_USER_AGENT`] | Variable | Nilai Default | Kapan Diperbarui | | ------------------------ | --------------------------------------------- | ----------------------------------------------------------------- | -| `CLAUDE_USER_AGENT` | `claude-cli/2.1.145 (external, cli)` | Saat Anthropic merilis versi CLI baru | +| `CLAUDE_USER_AGENT` | `claude-cli/2.1.219 (external, cli)` | Saat Anthropic merilis versi CLI baru | | `CODEX_USER_AGENT` | `codex-cli/0.132.0 (Windows 10.0.26200; x64)` | Saat OpenAI memperbarui CLI Codex | | `CODEX_CLIENT_VERSION` | `0.131.0` | Override versi klien Codex secara independen dari string UA penuh | | `GITHUB_USER_AGENT` | `GitHubCopilotChat/0.45.1` | Saat GitHub Copilot Chat diperbarui | diff --git a/docs/i18n/in/docs/reference/ENVIRONMENT.md b/docs/i18n/in/docs/reference/ENVIRONMENT.md index 11dad0d63e0a..2a54e2ca2186 100644 --- a/docs/i18n/in/docs/reference/ENVIRONMENT.md +++ b/docs/i18n/in/docs/reference/ENVIRONMENT.md @@ -336,7 +336,7 @@ process.env[`${PROVIDER_ID}_USER_AGENT`] | Variable | Default Value | When to Update | | ------------------------ | --------------------------------------------- | ------------------------------------------------------------- | -| `CLAUDE_USER_AGENT` | `claude-cli/2.1.145 (external, cli)` | When Anthropic releases a new CLI version | +| `CLAUDE_USER_AGENT` | `claude-cli/2.1.219 (external, cli)` | When Anthropic releases a new CLI version | | `CODEX_USER_AGENT` | `codex-cli/0.132.0 (Windows 10.0.26200; x64)` | When OpenAI updates the Codex CLI | | `CODEX_CLIENT_VERSION` | `0.131.0` | Override Codex client version independently of full UA string | | `GITHUB_USER_AGENT` | `GitHubCopilotChat/0.45.1` | When GitHub Copilot Chat updates | diff --git a/docs/i18n/it/docs/reference/ENVIRONMENT.md b/docs/i18n/it/docs/reference/ENVIRONMENT.md index d58ea6cfcca5..d40abc01ce04 100644 --- a/docs/i18n/it/docs/reference/ENVIRONMENT.md +++ b/docs/i18n/it/docs/reference/ENVIRONMENT.md @@ -336,7 +336,7 @@ process.env[`${PROVIDER_ID}_USER_AGENT`] | Variable | Default Value | When to Update | | ------------------------ | --------------------------------------------- | ------------------------------------------------------------- | -| `CLAUDE_USER_AGENT` | `claude-cli/2.1.145 (external, cli)` | When Anthropic releases a new CLI version | +| `CLAUDE_USER_AGENT` | `claude-cli/2.1.219 (external, cli)` | When Anthropic releases a new CLI version | | `CODEX_USER_AGENT` | `codex-cli/0.132.0 (Windows 10.0.26200; x64)` | When OpenAI updates the Codex CLI | | `CODEX_CLIENT_VERSION` | `0.131.0` | Override Codex client version independently of full UA string | | `GITHUB_USER_AGENT` | `GitHubCopilotChat/0.45.1` | When GitHub Copilot Chat updates | diff --git a/docs/i18n/ja/docs/reference/ENVIRONMENT.md b/docs/i18n/ja/docs/reference/ENVIRONMENT.md index 1a904abf8dda..669352ec37b5 100644 --- a/docs/i18n/ja/docs/reference/ENVIRONMENT.md +++ b/docs/i18n/ja/docs/reference/ENVIRONMENT.md @@ -336,7 +336,7 @@ process.env[`${PROVIDER_ID}_USER_AGENT`] | Variable | Default Value | When to Update | | ------------------------ | --------------------------------------------- | ------------------------------------------------------------- | -| `CLAUDE_USER_AGENT` | `claude-cli/2.1.145 (external, cli)` | When Anthropic releases a new CLI version | +| `CLAUDE_USER_AGENT` | `claude-cli/2.1.219 (external, cli)` | When Anthropic releases a new CLI version | | `CODEX_USER_AGENT` | `codex-cli/0.132.0 (Windows 10.0.26200; x64)` | When OpenAI updates the Codex CLI | | `CODEX_CLIENT_VERSION` | `0.131.0` | Override Codex client version independently of full UA string | | `GITHUB_USER_AGENT` | `GitHubCopilotChat/0.45.1` | When GitHub Copilot Chat updates | diff --git a/docs/i18n/ko/docs/reference/ENVIRONMENT.md b/docs/i18n/ko/docs/reference/ENVIRONMENT.md index b4aab967737d..d0f3ca78dd0e 100644 --- a/docs/i18n/ko/docs/reference/ENVIRONMENT.md +++ b/docs/i18n/ko/docs/reference/ENVIRONMENT.md @@ -336,7 +336,7 @@ process.env[`${PROVIDER_ID}_USER_AGENT`] | Variable | Default Value | When to Update | | ------------------------ | --------------------------------------------- | ------------------------------------------------------------- | -| `CLAUDE_USER_AGENT` | `claude-cli/2.1.145 (external, cli)` | When Anthropic releases a new CLI version | +| `CLAUDE_USER_AGENT` | `claude-cli/2.1.219 (external, cli)` | When Anthropic releases a new CLI version | | `CODEX_USER_AGENT` | `codex-cli/0.132.0 (Windows 10.0.26200; x64)` | When OpenAI updates the Codex CLI | | `CODEX_CLIENT_VERSION` | `0.131.0` | Override Codex client version independently of full UA string | | `GITHUB_USER_AGENT` | `GitHubCopilotChat/0.45.1` | When GitHub Copilot Chat updates | diff --git a/docs/i18n/mr/docs/reference/ENVIRONMENT.md b/docs/i18n/mr/docs/reference/ENVIRONMENT.md index 36109f039926..73b6495c5602 100644 --- a/docs/i18n/mr/docs/reference/ENVIRONMENT.md +++ b/docs/i18n/mr/docs/reference/ENVIRONMENT.md @@ -336,7 +336,7 @@ process.env[`${PROVIDER_ID}_USER_AGENT`] | Variable | Default Value | When to Update | | ------------------------ | --------------------------------------------- | ------------------------------------------------------------- | -| `CLAUDE_USER_AGENT` | `claude-cli/2.1.145 (external, cli)` | When Anthropic releases a new CLI version | +| `CLAUDE_USER_AGENT` | `claude-cli/2.1.219 (external, cli)` | When Anthropic releases a new CLI version | | `CODEX_USER_AGENT` | `codex-cli/0.132.0 (Windows 10.0.26200; x64)` | When OpenAI updates the Codex CLI | | `CODEX_CLIENT_VERSION` | `0.131.0` | Override Codex client version independently of full UA string | | `GITHUB_USER_AGENT` | `GitHubCopilotChat/0.45.1` | When GitHub Copilot Chat updates | diff --git a/docs/i18n/ms/docs/reference/ENVIRONMENT.md b/docs/i18n/ms/docs/reference/ENVIRONMENT.md index 4d1543edc988..862eeb13f5b9 100644 --- a/docs/i18n/ms/docs/reference/ENVIRONMENT.md +++ b/docs/i18n/ms/docs/reference/ENVIRONMENT.md @@ -336,7 +336,7 @@ process.env[`${PROVIDER_ID}_USER_AGENT`] | Variable | Default Value | When to Update | | ------------------------ | --------------------------------------------- | ------------------------------------------------------------- | -| `CLAUDE_USER_AGENT` | `claude-cli/2.1.145 (external, cli)` | When Anthropic releases a new CLI version | +| `CLAUDE_USER_AGENT` | `claude-cli/2.1.219 (external, cli)` | When Anthropic releases a new CLI version | | `CODEX_USER_AGENT` | `codex-cli/0.132.0 (Windows 10.0.26200; x64)` | When OpenAI updates the Codex CLI | | `CODEX_CLIENT_VERSION` | `0.131.0` | Override Codex client version independently of full UA string | | `GITHUB_USER_AGENT` | `GitHubCopilotChat/0.45.1` | When GitHub Copilot Chat updates | diff --git a/docs/i18n/nl/docs/reference/ENVIRONMENT.md b/docs/i18n/nl/docs/reference/ENVIRONMENT.md index 0140d6b28321..2e8c3d8e1c99 100644 --- a/docs/i18n/nl/docs/reference/ENVIRONMENT.md +++ b/docs/i18n/nl/docs/reference/ENVIRONMENT.md @@ -336,7 +336,7 @@ process.env[`${PROVIDER_ID}_USER_AGENT`] | Variable | Default Value | When to Update | | ------------------------ | --------------------------------------------- | ------------------------------------------------------------- | -| `CLAUDE_USER_AGENT` | `claude-cli/2.1.145 (external, cli)` | When Anthropic releases a new CLI version | +| `CLAUDE_USER_AGENT` | `claude-cli/2.1.219 (external, cli)` | When Anthropic releases a new CLI version | | `CODEX_USER_AGENT` | `codex-cli/0.132.0 (Windows 10.0.26200; x64)` | When OpenAI updates the Codex CLI | | `CODEX_CLIENT_VERSION` | `0.131.0` | Override Codex client version independently of full UA string | | `GITHUB_USER_AGENT` | `GitHubCopilotChat/0.45.1` | When GitHub Copilot Chat updates | diff --git a/docs/i18n/no/docs/reference/ENVIRONMENT.md b/docs/i18n/no/docs/reference/ENVIRONMENT.md index 7603a3372b6a..435dace726a5 100644 --- a/docs/i18n/no/docs/reference/ENVIRONMENT.md +++ b/docs/i18n/no/docs/reference/ENVIRONMENT.md @@ -336,7 +336,7 @@ process.env[`${PROVIDER_ID}_USER_AGENT`] | Variable | Default Value | When to Update | | ------------------------ | --------------------------------------------- | ------------------------------------------------------------- | -| `CLAUDE_USER_AGENT` | `claude-cli/2.1.145 (external, cli)` | When Anthropic releases a new CLI version | +| `CLAUDE_USER_AGENT` | `claude-cli/2.1.219 (external, cli)` | When Anthropic releases a new CLI version | | `CODEX_USER_AGENT` | `codex-cli/0.132.0 (Windows 10.0.26200; x64)` | When OpenAI updates the Codex CLI | | `CODEX_CLIENT_VERSION` | `0.131.0` | Override Codex client version independently of full UA string | | `GITHUB_USER_AGENT` | `GitHubCopilotChat/0.45.1` | When GitHub Copilot Chat updates | diff --git a/docs/i18n/phi/docs/reference/ENVIRONMENT.md b/docs/i18n/phi/docs/reference/ENVIRONMENT.md index 674f82048e94..cc4160541c6d 100644 --- a/docs/i18n/phi/docs/reference/ENVIRONMENT.md +++ b/docs/i18n/phi/docs/reference/ENVIRONMENT.md @@ -336,7 +336,7 @@ process.env[`${PROVIDER_ID}_USER_AGENT`] | Variable | Default Value | When to Update | | ------------------------ | --------------------------------------------- | ------------------------------------------------------------- | -| `CLAUDE_USER_AGENT` | `claude-cli/2.1.145 (external, cli)` | When Anthropic releases a new CLI version | +| `CLAUDE_USER_AGENT` | `claude-cli/2.1.219 (external, cli)` | When Anthropic releases a new CLI version | | `CODEX_USER_AGENT` | `codex-cli/0.132.0 (Windows 10.0.26200; x64)` | When OpenAI updates the Codex CLI | | `CODEX_CLIENT_VERSION` | `0.131.0` | Override Codex client version independently of full UA string | | `GITHUB_USER_AGENT` | `GitHubCopilotChat/0.45.1` | When GitHub Copilot Chat updates | diff --git a/docs/i18n/pl/docs/reference/ENVIRONMENT.md b/docs/i18n/pl/docs/reference/ENVIRONMENT.md index 3b9b5b900b85..a9bccccf2759 100644 --- a/docs/i18n/pl/docs/reference/ENVIRONMENT.md +++ b/docs/i18n/pl/docs/reference/ENVIRONMENT.md @@ -336,7 +336,7 @@ process.env[`${PROVIDER_ID}_USER_AGENT`] | Variable | Default Value | When to Update | | ------------------------ | --------------------------------------------- | ------------------------------------------------------------- | -| `CLAUDE_USER_AGENT` | `claude-cli/2.1.145 (external, cli)` | When Anthropic releases a new CLI version | +| `CLAUDE_USER_AGENT` | `claude-cli/2.1.219 (external, cli)` | When Anthropic releases a new CLI version | | `CODEX_USER_AGENT` | `codex-cli/0.132.0 (Windows 10.0.26200; x64)` | When OpenAI updates the Codex CLI | | `CODEX_CLIENT_VERSION` | `0.131.0` | Override Codex client version independently of full UA string | | `GITHUB_USER_AGENT` | `GitHubCopilotChat/0.45.1` | When GitHub Copilot Chat updates | diff --git a/docs/i18n/pt-BR/docs/reference/ENVIRONMENT.md b/docs/i18n/pt-BR/docs/reference/ENVIRONMENT.md index 4ab50782d22a..c6745bf31f91 100644 --- a/docs/i18n/pt-BR/docs/reference/ENVIRONMENT.md +++ b/docs/i18n/pt-BR/docs/reference/ENVIRONMENT.md @@ -336,7 +336,7 @@ process.env[`${PROVIDER_ID}_USER_AGENT`] | Variable | Default Value | When to Update | | ------------------------ | --------------------------------------------- | ------------------------------------------------------------- | -| `CLAUDE_USER_AGENT` | `claude-cli/2.1.145 (external, cli)` | When Anthropic releases a new CLI version | +| `CLAUDE_USER_AGENT` | `claude-cli/2.1.219 (external, cli)` | When Anthropic releases a new CLI version | | `CODEX_USER_AGENT` | `codex-cli/0.132.0 (Windows 10.0.26200; x64)` | When OpenAI updates the Codex CLI | | `CODEX_CLIENT_VERSION` | `0.131.0` | Override Codex client version independently of full UA string | | `GITHUB_USER_AGENT` | `GitHubCopilotChat/0.45.1` | When GitHub Copilot Chat updates | diff --git a/docs/i18n/pt/docs/reference/ENVIRONMENT.md b/docs/i18n/pt/docs/reference/ENVIRONMENT.md index 7fd851bab43f..f946d7c8c4b1 100644 --- a/docs/i18n/pt/docs/reference/ENVIRONMENT.md +++ b/docs/i18n/pt/docs/reference/ENVIRONMENT.md @@ -336,7 +336,7 @@ process.env[`${PROVIDER_ID}_USER_AGENT`] | Variable | Default Value | When to Update | | ------------------------ | --------------------------------------------- | ------------------------------------------------------------- | -| `CLAUDE_USER_AGENT` | `claude-cli/2.1.145 (external, cli)` | When Anthropic releases a new CLI version | +| `CLAUDE_USER_AGENT` | `claude-cli/2.1.219 (external, cli)` | When Anthropic releases a new CLI version | | `CODEX_USER_AGENT` | `codex-cli/0.132.0 (Windows 10.0.26200; x64)` | When OpenAI updates the Codex CLI | | `CODEX_CLIENT_VERSION` | `0.131.0` | Override Codex client version independently of full UA string | | `GITHUB_USER_AGENT` | `GitHubCopilotChat/0.45.1` | When GitHub Copilot Chat updates | diff --git a/docs/i18n/ro/docs/reference/ENVIRONMENT.md b/docs/i18n/ro/docs/reference/ENVIRONMENT.md index e1dde71b89ef..419fa46aaafe 100644 --- a/docs/i18n/ro/docs/reference/ENVIRONMENT.md +++ b/docs/i18n/ro/docs/reference/ENVIRONMENT.md @@ -336,7 +336,7 @@ process.env[`${PROVIDER_ID}_USER_AGENT`] | Variable | Default Value | When to Update | | ------------------------ | --------------------------------------------- | ------------------------------------------------------------- | -| `CLAUDE_USER_AGENT` | `claude-cli/2.1.145 (external, cli)` | When Anthropic releases a new CLI version | +| `CLAUDE_USER_AGENT` | `claude-cli/2.1.219 (external, cli)` | When Anthropic releases a new CLI version | | `CODEX_USER_AGENT` | `codex-cli/0.132.0 (Windows 10.0.26200; x64)` | When OpenAI updates the Codex CLI | | `CODEX_CLIENT_VERSION` | `0.131.0` | Override Codex client version independently of full UA string | | `GITHUB_USER_AGENT` | `GitHubCopilotChat/0.45.1` | When GitHub Copilot Chat updates | diff --git a/docs/i18n/ru/docs/reference/ENVIRONMENT.md b/docs/i18n/ru/docs/reference/ENVIRONMENT.md index b6a01ae258a9..95f508460a58 100644 --- a/docs/i18n/ru/docs/reference/ENVIRONMENT.md +++ b/docs/i18n/ru/docs/reference/ENVIRONMENT.md @@ -336,7 +336,7 @@ process.env[`${PROVIDER_ID}_USER_AGENT`] | Variable | Default Value | When to Update | | ------------------------ | --------------------------------------------- | ------------------------------------------------------------- | -| `CLAUDE_USER_AGENT` | `claude-cli/2.1.145 (external, cli)` | When Anthropic releases a new CLI version | +| `CLAUDE_USER_AGENT` | `claude-cli/2.1.219 (external, cli)` | When Anthropic releases a new CLI version | | `CODEX_USER_AGENT` | `codex-cli/0.132.0 (Windows 10.0.26200; x64)` | When OpenAI updates the Codex CLI | | `CODEX_CLIENT_VERSION` | `0.131.0` | Override Codex client version independently of full UA string | | `GITHUB_USER_AGENT` | `GitHubCopilotChat/0.45.1` | When GitHub Copilot Chat updates | diff --git a/docs/i18n/sk/docs/reference/ENVIRONMENT.md b/docs/i18n/sk/docs/reference/ENVIRONMENT.md index a9e2a987cf8e..c492af2265c5 100644 --- a/docs/i18n/sk/docs/reference/ENVIRONMENT.md +++ b/docs/i18n/sk/docs/reference/ENVIRONMENT.md @@ -336,7 +336,7 @@ process.env[`${PROVIDER_ID}_USER_AGENT`] | Variable | Default Value | When to Update | | ------------------------ | --------------------------------------------- | ------------------------------------------------------------- | -| `CLAUDE_USER_AGENT` | `claude-cli/2.1.145 (external, cli)` | When Anthropic releases a new CLI version | +| `CLAUDE_USER_AGENT` | `claude-cli/2.1.219 (external, cli)` | When Anthropic releases a new CLI version | | `CODEX_USER_AGENT` | `codex-cli/0.132.0 (Windows 10.0.26200; x64)` | When OpenAI updates the Codex CLI | | `CODEX_CLIENT_VERSION` | `0.131.0` | Override Codex client version independently of full UA string | | `GITHUB_USER_AGENT` | `GitHubCopilotChat/0.45.1` | When GitHub Copilot Chat updates | diff --git a/docs/i18n/sv/docs/reference/ENVIRONMENT.md b/docs/i18n/sv/docs/reference/ENVIRONMENT.md index 87ccfa98a54f..c7dd3ff1ce56 100644 --- a/docs/i18n/sv/docs/reference/ENVIRONMENT.md +++ b/docs/i18n/sv/docs/reference/ENVIRONMENT.md @@ -336,7 +336,7 @@ process.env[`${PROVIDER_ID}_USER_AGENT`] | Variable | Default Value | When to Update | | ------------------------ | --------------------------------------------- | ------------------------------------------------------------- | -| `CLAUDE_USER_AGENT` | `claude-cli/2.1.145 (external, cli)` | When Anthropic releases a new CLI version | +| `CLAUDE_USER_AGENT` | `claude-cli/2.1.219 (external, cli)` | When Anthropic releases a new CLI version | | `CODEX_USER_AGENT` | `codex-cli/0.132.0 (Windows 10.0.26200; x64)` | When OpenAI updates the Codex CLI | | `CODEX_CLIENT_VERSION` | `0.131.0` | Override Codex client version independently of full UA string | | `GITHUB_USER_AGENT` | `GitHubCopilotChat/0.45.1` | When GitHub Copilot Chat updates | diff --git a/docs/i18n/sw/docs/reference/ENVIRONMENT.md b/docs/i18n/sw/docs/reference/ENVIRONMENT.md index 5668d53c13a5..c4fd0efc3716 100644 --- a/docs/i18n/sw/docs/reference/ENVIRONMENT.md +++ b/docs/i18n/sw/docs/reference/ENVIRONMENT.md @@ -336,7 +336,7 @@ process.env[`${PROVIDER_ID}_USER_AGENT`] | Variable | Default Value | When to Update | | ------------------------ | --------------------------------------------- | ------------------------------------------------------------- | -| `CLAUDE_USER_AGENT` | `claude-cli/2.1.145 (external, cli)` | When Anthropic releases a new CLI version | +| `CLAUDE_USER_AGENT` | `claude-cli/2.1.219 (external, cli)` | When Anthropic releases a new CLI version | | `CODEX_USER_AGENT` | `codex-cli/0.132.0 (Windows 10.0.26200; x64)` | When OpenAI updates the Codex CLI | | `CODEX_CLIENT_VERSION` | `0.131.0` | Override Codex client version independently of full UA string | | `GITHUB_USER_AGENT` | `GitHubCopilotChat/0.45.1` | When GitHub Copilot Chat updates | diff --git a/docs/i18n/ta/docs/reference/ENVIRONMENT.md b/docs/i18n/ta/docs/reference/ENVIRONMENT.md index 62403546e7dc..a89d4008c678 100644 --- a/docs/i18n/ta/docs/reference/ENVIRONMENT.md +++ b/docs/i18n/ta/docs/reference/ENVIRONMENT.md @@ -336,7 +336,7 @@ process.env[`${PROVIDER_ID}_USER_AGENT`] | Variable | Default Value | When to Update | | ------------------------ | --------------------------------------------- | ------------------------------------------------------------- | -| `CLAUDE_USER_AGENT` | `claude-cli/2.1.145 (external, cli)` | When Anthropic releases a new CLI version | +| `CLAUDE_USER_AGENT` | `claude-cli/2.1.219 (external, cli)` | When Anthropic releases a new CLI version | | `CODEX_USER_AGENT` | `codex-cli/0.132.0 (Windows 10.0.26200; x64)` | When OpenAI updates the Codex CLI | | `CODEX_CLIENT_VERSION` | `0.131.0` | Override Codex client version independently of full UA string | | `GITHUB_USER_AGENT` | `GitHubCopilotChat/0.45.1` | When GitHub Copilot Chat updates | diff --git a/docs/i18n/te/docs/reference/ENVIRONMENT.md b/docs/i18n/te/docs/reference/ENVIRONMENT.md index f3e0c2f8020c..ef8855306352 100644 --- a/docs/i18n/te/docs/reference/ENVIRONMENT.md +++ b/docs/i18n/te/docs/reference/ENVIRONMENT.md @@ -336,7 +336,7 @@ process.env[`${PROVIDER_ID}_USER_AGENT`] | Variable | Default Value | When to Update | | ------------------------ | --------------------------------------------- | ------------------------------------------------------------- | -| `CLAUDE_USER_AGENT` | `claude-cli/2.1.145 (external, cli)` | When Anthropic releases a new CLI version | +| `CLAUDE_USER_AGENT` | `claude-cli/2.1.219 (external, cli)` | When Anthropic releases a new CLI version | | `CODEX_USER_AGENT` | `codex-cli/0.132.0 (Windows 10.0.26200; x64)` | When OpenAI updates the Codex CLI | | `CODEX_CLIENT_VERSION` | `0.131.0` | Override Codex client version independently of full UA string | | `GITHUB_USER_AGENT` | `GitHubCopilotChat/0.45.1` | When GitHub Copilot Chat updates | diff --git a/docs/i18n/th/docs/reference/ENVIRONMENT.md b/docs/i18n/th/docs/reference/ENVIRONMENT.md index 2ab580bc1f6d..eb17e8a41a43 100644 --- a/docs/i18n/th/docs/reference/ENVIRONMENT.md +++ b/docs/i18n/th/docs/reference/ENVIRONMENT.md @@ -336,7 +336,7 @@ process.env[`${PROVIDER_ID}_USER_AGENT`] | Variable | Default Value | When to Update | | ------------------------ | --------------------------------------------- | ------------------------------------------------------------- | -| `CLAUDE_USER_AGENT` | `claude-cli/2.1.145 (external, cli)` | When Anthropic releases a new CLI version | +| `CLAUDE_USER_AGENT` | `claude-cli/2.1.219 (external, cli)` | When Anthropic releases a new CLI version | | `CODEX_USER_AGENT` | `codex-cli/0.132.0 (Windows 10.0.26200; x64)` | When OpenAI updates the Codex CLI | | `CODEX_CLIENT_VERSION` | `0.131.0` | Override Codex client version independently of full UA string | | `GITHUB_USER_AGENT` | `GitHubCopilotChat/0.45.1` | When GitHub Copilot Chat updates | diff --git a/docs/i18n/tr/docs/reference/ENVIRONMENT.md b/docs/i18n/tr/docs/reference/ENVIRONMENT.md index 5970a7c0be86..1c3336c5b5dc 100644 --- a/docs/i18n/tr/docs/reference/ENVIRONMENT.md +++ b/docs/i18n/tr/docs/reference/ENVIRONMENT.md @@ -336,7 +336,7 @@ process.env[`${PROVIDER_ID}_USER_AGENT`] | Variable | Default Value | When to Update | | ------------------------ | --------------------------------------------- | ------------------------------------------------------------- | -| `CLAUDE_USER_AGENT` | `claude-cli/2.1.145 (external, cli)` | When Anthropic releases a new CLI version | +| `CLAUDE_USER_AGENT` | `claude-cli/2.1.219 (external, cli)` | When Anthropic releases a new CLI version | | `CODEX_USER_AGENT` | `codex-cli/0.132.0 (Windows 10.0.26200; x64)` | When OpenAI updates the Codex CLI | | `CODEX_CLIENT_VERSION` | `0.131.0` | Override Codex client version independently of full UA string | | `GITHUB_USER_AGENT` | `GitHubCopilotChat/0.45.1` | When GitHub Copilot Chat updates | diff --git a/docs/i18n/uk-UA/docs/reference/ENVIRONMENT.md b/docs/i18n/uk-UA/docs/reference/ENVIRONMENT.md index 86f872d7301b..ba283ad7e87b 100644 --- a/docs/i18n/uk-UA/docs/reference/ENVIRONMENT.md +++ b/docs/i18n/uk-UA/docs/reference/ENVIRONMENT.md @@ -336,7 +336,7 @@ process.env[`${PROVIDER_ID}_USER_AGENT`] | Variable | Default Value | When to Update | | ------------------------ | --------------------------------------------- | ------------------------------------------------------------- | -| `CLAUDE_USER_AGENT` | `claude-cli/2.1.145 (external, cli)` | When Anthropic releases a new CLI version | +| `CLAUDE_USER_AGENT` | `claude-cli/2.1.219 (external, cli)` | When Anthropic releases a new CLI version | | `CODEX_USER_AGENT` | `codex-cli/0.132.0 (Windows 10.0.26200; x64)` | When OpenAI updates the Codex CLI | | `CODEX_CLIENT_VERSION` | `0.131.0` | Override Codex client version independently of full UA string | | `GITHUB_USER_AGENT` | `GitHubCopilotChat/0.45.1` | When GitHub Copilot Chat updates | diff --git a/docs/i18n/ur/docs/reference/ENVIRONMENT.md b/docs/i18n/ur/docs/reference/ENVIRONMENT.md index c92b3f0af989..24ef0b51ca76 100644 --- a/docs/i18n/ur/docs/reference/ENVIRONMENT.md +++ b/docs/i18n/ur/docs/reference/ENVIRONMENT.md @@ -336,7 +336,7 @@ process.env[`${PROVIDER_ID}_USER_AGENT`] | Variable | Default Value | When to Update | | ------------------------ | --------------------------------------------- | ------------------------------------------------------------- | -| `CLAUDE_USER_AGENT` | `claude-cli/2.1.145 (external, cli)` | When Anthropic releases a new CLI version | +| `CLAUDE_USER_AGENT` | `claude-cli/2.1.219 (external, cli)` | When Anthropic releases a new CLI version | | `CODEX_USER_AGENT` | `codex-cli/0.132.0 (Windows 10.0.26200; x64)` | When OpenAI updates the Codex CLI | | `CODEX_CLIENT_VERSION` | `0.131.0` | Override Codex client version independently of full UA string | | `GITHUB_USER_AGENT` | `GitHubCopilotChat/0.45.1` | When GitHub Copilot Chat updates | diff --git a/docs/i18n/vi/docs/reference/ENVIRONMENT.md b/docs/i18n/vi/docs/reference/ENVIRONMENT.md index bbc5d3da7d20..9f9386753a43 100644 --- a/docs/i18n/vi/docs/reference/ENVIRONMENT.md +++ b/docs/i18n/vi/docs/reference/ENVIRONMENT.md @@ -336,7 +336,7 @@ process.env[`${PROVIDER_ID}_USER_AGENT`] | Variable | Default Value | When to Update | | ------------------------ | --------------------------------------------- | ------------------------------------------------------------- | -| `CLAUDE_USER_AGENT` | `claude-cli/2.1.145 (external, cli)` | When Anthropic releases a new CLI version | +| `CLAUDE_USER_AGENT` | `claude-cli/2.1.219 (external, cli)` | When Anthropic releases a new CLI version | | `CODEX_USER_AGENT` | `codex-cli/0.132.0 (Windows 10.0.26200; x64)` | When OpenAI updates the Codex CLI | | `CODEX_CLIENT_VERSION` | `0.131.0` | Override Codex client version independently of full UA string | | `GITHUB_USER_AGENT` | `GitHubCopilotChat/0.45.1` | When GitHub Copilot Chat updates | diff --git a/docs/i18n/zh-CN/docs/reference/ENVIRONMENT.md b/docs/i18n/zh-CN/docs/reference/ENVIRONMENT.md index 782d23d7471f..75923197cdd8 100644 --- a/docs/i18n/zh-CN/docs/reference/ENVIRONMENT.md +++ b/docs/i18n/zh-CN/docs/reference/ENVIRONMENT.md @@ -1,6 +1,7 @@ # 环境变量参考 (中文 (简体)) --- + title: "Environment Variables Reference" version: 3.8.40 lastUpdated: 2026-06-28 @@ -55,13 +56,13 @@ lastUpdated: 2026-06-28 这些变量 **必须** 在首次运行前设置。不设置则应用将拒绝启动,或使用不安全的默认值运行。 -| 变量 | 必需 | 默认值 | 源文件 | 说明 | -| --- | --- | --- | --- | --- | -| `JWT_SECRET` | **是** | _(无)_ | `src/lib/auth` | 签名/校验所有 Dashboard 会话 Cookie(JWT)。使用 `openssl rand -base64 48` 生成。 | -| `API_KEY_SECRET` | **是** | _(无)_ | `src/lib/db/apiKeys.ts` | SQLite 中 API key 静态加密的 AES 密钥。使用 `openssl rand -hex 32` 生成。 | -| `INITIAL_PASSWORD` | **是** | `CHANGEME` | 引导脚本 | 设置初始管理员 Dashboard 密码(与 `.env.example` 默认值一致 — 故意保持不安全以强制更换)。**首次使用前请修改。** 登录后,请通过 Dashboard → Settings → Security 修改。 | -| `OMNIROUTE_WS_BRIDGE_SECRET` | **是**(生产环境) | _(未设置)_ | `src/app/api/internal/codex-responses-ws/route.ts` | 内部 Codex Responses WebSocket 桥接的共享密钥。用于认证 Electron/浏览器 WS 中继与 OmniRoute 之间的桥接请求。⚠️ **生产环境必须设置 — 未设置时所有 WS 桥接请求都会被拒绝。** 使用 `openssl rand -base64 32` 生成。 | -| `OMNIROUTE_PEER_STAMP_TOKEN` | 否(自动) | _(每次启动自动生成)_ | `src/server/authz/policies/management.ts` | 每个进程的密钥,用于证明可信的对等 IP 戳记来自 OmniRoute 自身的 HTTP 服务器(`scripts/dev/peer-stamp.mjs`)。authz 中间件仅在戳记携带此 Token 时才信任请求的本地性(对 LOCAL_ONLY 路由进行 loopback/LAN 门控)。每次启动自动生成 — 保持未设置;仅在必须共享戳记的多进程场景下固定此值。 | +| 变量 | 必需 | 默认值 | 源文件 | 说明 | +| ---------------------------- | ------------------ | -------------------- | -------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `JWT_SECRET` | **是** | _(无)_ | `src/lib/auth` | 签名/校验所有 Dashboard 会话 Cookie(JWT)。使用 `openssl rand -base64 48` 生成。 | +| `API_KEY_SECRET` | **是** | _(无)_ | `src/lib/db/apiKeys.ts` | SQLite 中 API key 静态加密的 AES 密钥。使用 `openssl rand -hex 32` 生成。 | +| `INITIAL_PASSWORD` | **是** | `CHANGEME` | 引导脚本 | 设置初始管理员 Dashboard 密码(与 `.env.example` 默认值一致 — 故意保持不安全以强制更换)。**首次使用前请修改。** 登录后,请通过 Dashboard → Settings → Security 修改。 | +| `OMNIROUTE_WS_BRIDGE_SECRET` | **是**(生产环境) | _(未设置)_ | `src/app/api/internal/codex-responses-ws/route.ts` | 内部 Codex Responses WebSocket 桥接的共享密钥。用于认证 Electron/浏览器 WS 中继与 OmniRoute 之间的桥接请求。⚠️ **生产环境必须设置 — 未设置时所有 WS 桥接请求都会被拒绝。** 使用 `openssl rand -base64 32` 生成。 | +| `OMNIROUTE_PEER_STAMP_TOKEN` | 否(自动) | _(每次启动自动生成)_ | `src/server/authz/policies/management.ts` | 每个进程的密钥,用于证明可信的对等 IP 戳记来自 OmniRoute 自身的 HTTP 服务器(`scripts/dev/peer-stamp.mjs`)。authz 中间件仅在戳记携带此 Token 时才信任请求的本地性(对 LOCAL_ONLY 路由进行 loopback/LAN 门控)。每次启动自动生成 — 保持未设置;仅在必须共享戳记的多进程场景下固定此值。 | ### 生成命令 @@ -82,64 +83,64 @@ echo "OMNIROUTE_WS_BRIDGE_SECRET=$(openssl rand -base64 32)" OmniRoute 使用 **SQLite**(通过 `better-sqlite3`)进行所有持久化存储。以下变量控制数据位置、加密和生命周期。 -| 变量 | 默认值 | 源文件 | 说明 | -| --- | --- | --- | --- | -| `DATA_DIR` | `~/.omniroute/` | `src/lib/db/core.ts` | SQLite 数据库、备份和数据文件的根目录。在 Docker 卷或自定义路径中可覆盖。 | -| `STORAGE_ENCRYPTION_KEY` | _(空 = 禁用)_ | `src/lib/db/encryption.ts` | 用于 SQLite 数据库静态全量加密的 AES 密钥。使用 `openssl rand -hex 32` 生成。 | -| `STORAGE_ENCRYPTION_KEY_VERSION` | `v1` | `scripts/build/bootstrap-env.mjs`, `electron/main.js` | 加密密钥的版本标签。进行密钥轮换时递增,以支持解密旧备份。 | -| `DISABLE_SQLITE_AUTO_BACKUP` | `false` | `src/lib/db/backup.ts` | 设为 `true` 时,跳过每次启动前迁移时运行的自动数据库备份。 | -| `OMNIROUTE_CRYPT_KEY` | _(未设置)_ | `src/lib/db/encryption.ts` | `STORAGE_ENCRYPTION_KEY` 的**旧版别名**。主变量缺失时作为回退被接受。 | -| `OMNIROUTE_API_KEY_BASE64` | _(未设置)_ | `src/lib/db/encryption.ts` | **旧版别名**(Base64 编码形式),作为回退被接受。使用前自动解码。 | -| `OMNIROUTE_DB_HEALTHCHECK_INTERVAL_MS` | _(未设置)_ | `src/lib/db/core.ts` | 覆盖定期 SQLite 健康检查的间隔(毫秒)。未设置时根据 `NODE_ENV` 推导默认值。 | -| `OMNIROUTE_SKIP_DB_HEALTHCHECK` | `0` | `src/lib/db/core.ts`, `src/lib/db/healthCheck.ts` | 设为 `1` 可在启动时完全跳过数据库健康检查。适用于短生命周期任务和集成测试。 | -| `OMNIROUTE_FORCE_DB_HEALTHCHECK` | `0` | `src/lib/db/core.ts` | 设为 `1` 可强制开启数据库健康检查循环,即使正常会被跳过(如短生命周期任务)。 | -| `OMNIROUTE_SKIP_POSTINSTALL` | `0` | `scripts/postinstall.mjs` | 设为 `1` 可在 `npm install` 期间跳过原生运行时预热。适用于 CI/无头安装,此时 sqlite 已构建好。 | -| `OMNIROUTE_MIGRATIONS_DIR` | _(自动检测)_ | `src/lib/db/migrationRunner.ts` | 覆盖迁移运行器扫描的目录。在自定义构建中打包迁移文件时很有用。 | -| `OMNIROUTE_MAX_PENDING_MIGRATIONS` | `50` | `src/lib/db/migrationRunner.ts` | 大量待处理迁移的安全阈值(#3416)。如果现有数据库上有超过此数量的待处理迁移,启动将中止(防止跟踪表被清空)。恢复旧备份时提高此值;设为 `0` 可禁用检查。 | -| `OMNIROUTE_SPEND_FLUSH_INTERVAL_MS` | _(代码内默认值)_ | `src/lib/spend/batchWriter.ts` | 批量消费/成本写入器的刷新间隔(毫秒)。值越小写合并越少;值越大数据库争用越少。 | -| `OMNIROUTE_SPEND_MAX_BUFFER_SIZE` | _(代码内默认值)_ | `src/lib/spend/batchWriter.ts` | 强制刷新前的最大缓存消费条目数。在高 QPS 部署中提高;在内存受限场景下降低。 | -| `OMNIROUTE_PROXY_FETCH_DEBUG` | _(未设置)_ | `open-sse/utils/proxyFetch.ts` | 设为 `"true"` 可在 Vercel 中继路径上发出 `[ProxyFetch]` 调试日志。默认关闭以避免泄露路由提示。 | -| `BATCH_RETRY_DURATION_MS` | `86400000`(24小时) | `open-sse/services/batchProcessor.ts` | 单个批次项的最大重试窗口(毫秒)。超过此时间的项被标记为失败。 | -| `BATCH_BACKOFF_BASE_MS` | `5000` | `open-sse/services/batchProcessor.ts` | 批次项重试时指数退避的基础延迟(毫秒)。 | -| `BATCH_BACKOFF_MAX_MS` | `3600000`(1小时) | `open-sse/services/batchProcessor.ts` | 批次项重试时指数退避的上限(毫秒)。 | -| `BATCH_MAX_CONCURRENT` | `1` | `open-sse/services/batchProcessor.ts` | 并发处理的批次最大数量。提高以增加吞吐量;保持低值以避免速率限制风暴。 | +| 变量 | 默认值 | 源文件 | 说明 | +| -------------------------------------- | -------------------- | ----------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `DATA_DIR` | `~/.omniroute/` | `src/lib/db/core.ts` | SQLite 数据库、备份和数据文件的根目录。在 Docker 卷或自定义路径中可覆盖。 | +| `STORAGE_ENCRYPTION_KEY` | _(空 = 禁用)_ | `src/lib/db/encryption.ts` | 用于 SQLite 数据库静态全量加密的 AES 密钥。使用 `openssl rand -hex 32` 生成。 | +| `STORAGE_ENCRYPTION_KEY_VERSION` | `v1` | `scripts/build/bootstrap-env.mjs`, `electron/main.js` | 加密密钥的版本标签。进行密钥轮换时递增,以支持解密旧备份。 | +| `DISABLE_SQLITE_AUTO_BACKUP` | `false` | `src/lib/db/backup.ts` | 设为 `true` 时,跳过每次启动前迁移时运行的自动数据库备份。 | +| `OMNIROUTE_CRYPT_KEY` | _(未设置)_ | `src/lib/db/encryption.ts` | `STORAGE_ENCRYPTION_KEY` 的**旧版别名**。主变量缺失时作为回退被接受。 | +| `OMNIROUTE_API_KEY_BASE64` | _(未设置)_ | `src/lib/db/encryption.ts` | **旧版别名**(Base64 编码形式),作为回退被接受。使用前自动解码。 | +| `OMNIROUTE_DB_HEALTHCHECK_INTERVAL_MS` | _(未设置)_ | `src/lib/db/core.ts` | 覆盖定期 SQLite 健康检查的间隔(毫秒)。未设置时根据 `NODE_ENV` 推导默认值。 | +| `OMNIROUTE_SKIP_DB_HEALTHCHECK` | `0` | `src/lib/db/core.ts`, `src/lib/db/healthCheck.ts` | 设为 `1` 可在启动时完全跳过数据库健康检查。适用于短生命周期任务和集成测试。 | +| `OMNIROUTE_FORCE_DB_HEALTHCHECK` | `0` | `src/lib/db/core.ts` | 设为 `1` 可强制开启数据库健康检查循环,即使正常会被跳过(如短生命周期任务)。 | +| `OMNIROUTE_SKIP_POSTINSTALL` | `0` | `scripts/postinstall.mjs` | 设为 `1` 可在 `npm install` 期间跳过原生运行时预热。适用于 CI/无头安装,此时 sqlite 已构建好。 | +| `OMNIROUTE_MIGRATIONS_DIR` | _(自动检测)_ | `src/lib/db/migrationRunner.ts` | 覆盖迁移运行器扫描的目录。在自定义构建中打包迁移文件时很有用。 | +| `OMNIROUTE_MAX_PENDING_MIGRATIONS` | `50` | `src/lib/db/migrationRunner.ts` | 大量待处理迁移的安全阈值(#3416)。如果现有数据库上有超过此数量的待处理迁移,启动将中止(防止跟踪表被清空)。恢复旧备份时提高此值;设为 `0` 可禁用检查。 | +| `OMNIROUTE_SPEND_FLUSH_INTERVAL_MS` | _(代码内默认值)_ | `src/lib/spend/batchWriter.ts` | 批量消费/成本写入器的刷新间隔(毫秒)。值越小写合并越少;值越大数据库争用越少。 | +| `OMNIROUTE_SPEND_MAX_BUFFER_SIZE` | _(代码内默认值)_ | `src/lib/spend/batchWriter.ts` | 强制刷新前的最大缓存消费条目数。在高 QPS 部署中提高;在内存受限场景下降低。 | +| `OMNIROUTE_PROXY_FETCH_DEBUG` | _(未设置)_ | `open-sse/utils/proxyFetch.ts` | 设为 `"true"` 可在 Vercel 中继路径上发出 `[ProxyFetch]` 调试日志。默认关闭以避免泄露路由提示。 | +| `BATCH_RETRY_DURATION_MS` | `86400000`(24小时) | `open-sse/services/batchProcessor.ts` | 单个批次项的最大重试窗口(毫秒)。超过此时间的项被标记为失败。 | +| `BATCH_BACKOFF_BASE_MS` | `5000` | `open-sse/services/batchProcessor.ts` | 批次项重试时指数退避的基础延迟(毫秒)。 | +| `BATCH_BACKOFF_MAX_MS` | `3600000`(1小时) | `open-sse/services/batchProcessor.ts` | 批次项重试时指数退避的上限(毫秒)。 | +| `BATCH_MAX_CONCURRENT` | `1` | `open-sse/services/batchProcessor.ts` | 并发处理的批次最大数量。提高以增加吞吐量;保持低值以避免速率限制风暴。 | ### 场景 -| 场景 | 配置 | -| --- | --- | -| **本地开发** | 保留所有默认值。数据库位于 `~/.omniroute/omniroute.db`。 | -| **Docker** | `DATA_DIR=/data` + 挂载卷到 `/data`。 | +| 场景 | 配置 | +| ------------ | --------------------------------------------------------------- | +| **本地开发** | 保留所有默认值。数据库位于 `~/.omniroute/omniroute.db`。 | +| **Docker** | `DATA_DIR=/data` + 挂载卷到 `/data`。 | | **静态加密** | 设置 `STORAGE_ENCRYPTION_KEY` + 备份密钥!丢失密钥 = 丢失数据。 | -| **CI/测试** | `DATA_DIR=/tmp/omniroute-test` — 临时目录,无需加密。 | +| **CI/测试** | `DATA_DIR=/tmp/omniroute-test` — 临时目录,无需加密。 | --- ## 3. 网络与端口 -| 变量 | 默认值 | 源文件 | 说明 | -| --- | --- | --- | --- | -| `PORT` | `20128` | `src/lib/runtime/ports.ts` | Dashboard UI 和 API 端点共用的主端口(单端口模式)。 | -| `API_PORT` | _(未设置)_ | `src/lib/runtime/ports.ts` | 设置时,在另外的端口上提供 `/v1/*` 代理 API。 | -| `API_HOST` | `0.0.0.0` | `src/lib/runtime/ports.ts` | API 端口的绑定地址。 | -| `DASHBOARD_PORT` | _(未设置)_ | `src/lib/runtime/ports.ts` | 设置时,在另外的端口上提供 Dashboard UI。 | -| `PROD_DASHBOARD_PORT` | `20130` | `docker-compose.prod.yml` | Docker 生产模式下 Dashboard 的主机侧发布端口。 | -| `PROD_API_PORT` | `20131` | `docker-compose.prod.yml` | Docker 生产模式下 API 的主机侧发布端口。 | -| `OMNIROUTE_PORT` | _(未设置)_ | `src/lib/runtime/ports.ts` | 在 Electron 或其他包装器中运行时优先于 `PORT`。 | -| `LIVE_WS_PORT` | `20129` | `src/server/ws/liveServer.ts` | 实时 WebSocket 监控服务器的端口。 | -| `LIVE_WS_HOST` | `127.0.0.1` | `src/server/ws/liveServer.ts` | 实时 WebSocket 服务器的绑定地址。设为 `0.0.0.0` 可暴露到 LAN(还需配置 `LIVE_WS_ALLOWED_ORIGINS`)。 | -| `LIVE_WS_ALLOWED_ORIGINS` | _(未设置)_ | `src/server/ws/liveServer.ts` | 逗号分隔的额外允许打开实时 WebSocket 的源。loopback Dashboard 源已默认允许。 | -| `OMNIROUTE_ENABLE_LIVE_WS` | `true` | `src/server/ws/liveServer.ts` | 设为 `0` 或 `false` 可禁用实时 WebSocket 服务器(默认启用,绑定 loopback)。 | -| `OMNIROUTE_DISABLE_LIVE_WS` | `false` | `scripts/start-ws-server.mjs` | CI/测试工具开关,禁用独立的实时 WebSocket 辅助脚本。 | -| `RELAY_IP_PER_MINUTE` | `30` | `src/app/api/v1/relay/chat/completions/route.ts` | 每个 (Token, IP) 的中继速率限制,请求数/分钟。基于内存,每个实例独立。`0` 或负数可禁用 IP 维度门控(每个 Token 的数据库限制仍然生效)。 | -| `NODE_ENV` | `production` | Next.js 核心 | 控制日志详细程度、缓存、错误详情暴露和 Next.js 优化。 | -| `OMNIROUTE_USE_TURBOPACK` | `1`(`.env.example` 中默认值) | `package.json` / Next.js 16 | 在 `npm run dev` 和 `npm run build` 中切换 Next.js 16 Turbopack 打包器。在 Windows 或遇到原生绑定不兼容时设为 `0`。 | -| `OMNIROUTE_SKIP_DB_HEALTHCHECK` | _(未设置)_ | `src/lib/db/core.ts` / `src/lib/db/healthCheck.ts` | 设为 `1` 可跳过启动时的 SQLite 完整性健康检查。适用于大型数据库需要更快启动时。 | -| `CREDENTIAL_HEALTH_CHECK_INTERVAL` | `300000` | `open-sse/config/constants.ts` / `src/lib/credentialHealth/scheduler.ts` | 后台凭证健康检查调度器的间隔(毫秒)。最低:10000(10 秒)。 | -| `CREDENTIAL_HEALTH_CACHE_TTL` | `300000` | `open-sse/config/constants.ts` / `src/lib/credentialHealth/cache.ts` | 凭证健康状态缓存的 TTL(毫秒)。 | -| `OMNIROUTE_DISABLE_CREDENTIAL_HEALTH_CHECK` | `false` | `src/lib/credentialHealth/scheduler.ts` | 设为 `1` 或 `true` 可禁用后台定期服务商连接测试。 | -| `HOST` | `0.0.0.0` | `scripts/dev/run-next.mjs` | Next.js 开发/启动服务器的绑定地址。设置时覆盖默认的 `0.0.0.0`。 | -| `HOSTNAME` | `127.0.0.1` | `scripts/dev/run-next-playwright.mjs` | Playwright 启动 Next.js 时使用的绑定地址。默认为 `127.0.0.1` 以确保测试隔离。 | +| 变量 | 默认值 | 源文件 | 说明 | +| ------------------------------------------- | ------------------------------ | ------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------- | +| `PORT` | `20128` | `src/lib/runtime/ports.ts` | Dashboard UI 和 API 端点共用的主端口(单端口模式)。 | +| `API_PORT` | _(未设置)_ | `src/lib/runtime/ports.ts` | 设置时,在另外的端口上提供 `/v1/*` 代理 API。 | +| `API_HOST` | `0.0.0.0` | `src/lib/runtime/ports.ts` | API 端口的绑定地址。 | +| `DASHBOARD_PORT` | _(未设置)_ | `src/lib/runtime/ports.ts` | 设置时,在另外的端口上提供 Dashboard UI。 | +| `PROD_DASHBOARD_PORT` | `20130` | `docker-compose.prod.yml` | Docker 生产模式下 Dashboard 的主机侧发布端口。 | +| `PROD_API_PORT` | `20131` | `docker-compose.prod.yml` | Docker 生产模式下 API 的主机侧发布端口。 | +| `OMNIROUTE_PORT` | _(未设置)_ | `src/lib/runtime/ports.ts` | 在 Electron 或其他包装器中运行时优先于 `PORT`。 | +| `LIVE_WS_PORT` | `20129` | `src/server/ws/liveServer.ts` | 实时 WebSocket 监控服务器的端口。 | +| `LIVE_WS_HOST` | `127.0.0.1` | `src/server/ws/liveServer.ts` | 实时 WebSocket 服务器的绑定地址。设为 `0.0.0.0` 可暴露到 LAN(还需配置 `LIVE_WS_ALLOWED_ORIGINS`)。 | +| `LIVE_WS_ALLOWED_ORIGINS` | _(未设置)_ | `src/server/ws/liveServer.ts` | 逗号分隔的额外允许打开实时 WebSocket 的源。loopback Dashboard 源已默认允许。 | +| `OMNIROUTE_ENABLE_LIVE_WS` | `true` | `src/server/ws/liveServer.ts` | 设为 `0` 或 `false` 可禁用实时 WebSocket 服务器(默认启用,绑定 loopback)。 | +| `OMNIROUTE_DISABLE_LIVE_WS` | `false` | `scripts/start-ws-server.mjs` | CI/测试工具开关,禁用独立的实时 WebSocket 辅助脚本。 | +| `RELAY_IP_PER_MINUTE` | `30` | `src/app/api/v1/relay/chat/completions/route.ts` | 每个 (Token, IP) 的中继速率限制,请求数/分钟。基于内存,每个实例独立。`0` 或负数可禁用 IP 维度门控(每个 Token 的数据库限制仍然生效)。 | +| `NODE_ENV` | `production` | Next.js 核心 | 控制日志详细程度、缓存、错误详情暴露和 Next.js 优化。 | +| `OMNIROUTE_USE_TURBOPACK` | `1`(`.env.example` 中默认值) | `package.json` / Next.js 16 | 在 `npm run dev` 和 `npm run build` 中切换 Next.js 16 Turbopack 打包器。在 Windows 或遇到原生绑定不兼容时设为 `0`。 | +| `OMNIROUTE_SKIP_DB_HEALTHCHECK` | _(未设置)_ | `src/lib/db/core.ts` / `src/lib/db/healthCheck.ts` | 设为 `1` 可跳过启动时的 SQLite 完整性健康检查。适用于大型数据库需要更快启动时。 | +| `CREDENTIAL_HEALTH_CHECK_INTERVAL` | `300000` | `open-sse/config/constants.ts` / `src/lib/credentialHealth/scheduler.ts` | 后台凭证健康检查调度器的间隔(毫秒)。最低:10000(10 秒)。 | +| `CREDENTIAL_HEALTH_CACHE_TTL` | `300000` | `open-sse/config/constants.ts` / `src/lib/credentialHealth/cache.ts` | 凭证健康状态缓存的 TTL(毫秒)。 | +| `OMNIROUTE_DISABLE_CREDENTIAL_HEALTH_CHECK` | `false` | `src/lib/credentialHealth/scheduler.ts` | 设为 `1` 或 `true` 可禁用后台定期服务商连接测试。 | +| `HOST` | `0.0.0.0` | `scripts/dev/run-next.mjs` | Next.js 开发/启动服务器的绑定地址。设置时覆盖默认的 `0.0.0.0`。 | +| `HOSTNAME` | `127.0.0.1` | `scripts/dev/run-next-playwright.mjs` | Playwright 启动 Next.js 时使用的绑定地址。默认为 `127.0.0.1` 以确保测试隔离。 | ### 端口模式 @@ -169,22 +170,22 @@ OmniRoute 使用 **SQLite**(通过 `better-sqlite3`)进行所有持久化存 ## 4. 安全与认证 -| 变量 | 默认值 | 源文件 | 说明 | -| --- | --- | --- | --- | -| `MACHINE_ID_SALT` | `endpoint-proxy-salt` | `src/lib/auth` | 与硬件标识符组合用于机器指纹的盐值。按部署修改以实现隔离。 | -| `OMNIROUTE_CLI_SALT` | `omniroute-cli-auth-v1` | `src/lib/machineToken.ts` | 用于派生本地 CLI 认证 Token 的 HMAC 盐值。修改此值将轮换机器上的所有 CLI Token。参阅 `docs/security/CLI_TOKEN.md`。 | -| `AUTH_COOKIE_SECURE` | `false` | `src/lib/auth` | 设置会话 Cookie 的 `Secure` 标志。运行在 HTTPS 背后时 **必须设为 `true`**。 | -| `REQUIRE_API_KEY` | `false` | API 中间件 | 设为 `true` 后,所有 `/v1/*` 代理请求必须包含有效的 API key。 | -| `ALLOW_API_KEY_REVEAL` | `false` | `src/shared/constants/featureFlagDefinitions.ts` | 允许在 Dashboard UI 中显示完整 API key 值。可从 Dashboard Feature Flags 配置;共享实例上有安全风险。 | -| `NO_LOG_API_KEY_IDS` | _(空)_ | `src/lib/compliance/index.ts` | 逗号分隔的 API key ID 列表,其请求将绕过日志记录(GDPR 合规)。 | -| `DEFAULT_RATE_LIMIT_PER_DAY` | `1000` | `src/shared/utils/apiKeyPolicy.ts` | 应用于 `rate_limits` 列为 null 的 API key 的回退每日请求预算。默认(未设置/空/格式错误)保持传统的 1000/天、5000/周、20000/月 窗口。显式设为 `0` 可选择退出(无限制)。任意正整数 N 则启用 N/天、5N/周、20N/月。Zod 校验;无效值记录警告并使用传统默认值。 | -| `MAX_BODY_SIZE_BYTES` | `10485760`(10 MB) | `src/shared/middleware/bodySizeGuard.ts` | 允许的最大请求体大小。拒绝超限载荷。 | -| `CORS_ORIGIN` | _(未设置)_ | `src/server/cors/origins.ts` | 旧版单一源 CORS 允许列表。新部署推荐使用 `CORS_ALLOWED_ORIGINS`。CORS 仅用于跨源浏览器 API 客户端;反向代理后的同源 Dashboard 请求使用 `NEXT_PUBLIC_BASE_URL` / 公共源校验。 | -| `CORS_ALLOWED_ORIGINS` | _(未设置)_ | `src/server/cors/origins.ts` | 逗号分隔的 CORS 允许列表。除非显式配置 `CORS_ALLOW_ALL=true`,否则不发送通配符。 | -| `CORS_ALLOW_ALL` | `false` | `src/server/cors/origins.ts` | 仅供开发使用的逃生口,可回显任意浏览器 `Origin`。不要在共享或生产部署中启用。 | -| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuard.ts` | 阻止目标为私有/loopback/链路本地 IP 范围的服务商调用。仅在隔离的测试环境中禁用。 | -| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | `false` | `src/shared/network/outboundUrlGuard.ts` | 允许指向私有/本地网络(localhost、192.168.x.x、10.x.x.x 等)的服务商 URL。**自托管服务商必需**(LM Studio、Ollama、vLLM、Llamafile、Triton、SearXNG)。设为 `false` 时,Dashboard 拒绝校验本地 URL。 | -| `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` | `true` | `src/shared/network/outboundUrlGuard.ts` | 允许在本地/私有地址上添加/校验服务商(127.0.0.1、localhost、LAN、私有范围)— 仅影响服务商校验路径。**默认 `true`**(本地优先);设为 `false` 可强制严格的仅公网阻断。云元数据端点(169.254.169.254, metadata.google.internal)始终被阻断。(#5066) | +| 变量 | 默认值 | 源文件 | 说明 | +| --------------------------------------- | ----------------------- | ------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `MACHINE_ID_SALT` | `endpoint-proxy-salt` | `src/lib/auth` | 与硬件标识符组合用于机器指纹的盐值。按部署修改以实现隔离。 | +| `OMNIROUTE_CLI_SALT` | `omniroute-cli-auth-v1` | `src/lib/machineToken.ts` | 用于派生本地 CLI 认证 Token 的 HMAC 盐值。修改此值将轮换机器上的所有 CLI Token。参阅 `docs/security/CLI_TOKEN.md`。 | +| `AUTH_COOKIE_SECURE` | `false` | `src/lib/auth` | 设置会话 Cookie 的 `Secure` 标志。运行在 HTTPS 背后时 **必须设为 `true`**。 | +| `REQUIRE_API_KEY` | `false` | API 中间件 | 设为 `true` 后,所有 `/v1/*` 代理请求必须包含有效的 API key。 | +| `ALLOW_API_KEY_REVEAL` | `false` | `src/shared/constants/featureFlagDefinitions.ts` | 允许在 Dashboard UI 中显示完整 API key 值。可从 Dashboard Feature Flags 配置;共享实例上有安全风险。 | +| `NO_LOG_API_KEY_IDS` | _(空)_ | `src/lib/compliance/index.ts` | 逗号分隔的 API key ID 列表,其请求将绕过日志记录(GDPR 合规)。 | +| `DEFAULT_RATE_LIMIT_PER_DAY` | `1000` | `src/shared/utils/apiKeyPolicy.ts` | 应用于 `rate_limits` 列为 null 的 API key 的回退每日请求预算。默认(未设置/空/格式错误)保持传统的 1000/天、5000/周、20000/月 窗口。显式设为 `0` 可选择退出(无限制)。任意正整数 N 则启用 N/天、5N/周、20N/月。Zod 校验;无效值记录警告并使用传统默认值。 | +| `MAX_BODY_SIZE_BYTES` | `10485760`(10 MB) | `src/shared/middleware/bodySizeGuard.ts` | 允许的最大请求体大小。拒绝超限载荷。 | +| `CORS_ORIGIN` | _(未设置)_ | `src/server/cors/origins.ts` | 旧版单一源 CORS 允许列表。新部署推荐使用 `CORS_ALLOWED_ORIGINS`。CORS 仅用于跨源浏览器 API 客户端;反向代理后的同源 Dashboard 请求使用 `NEXT_PUBLIC_BASE_URL` / 公共源校验。 | +| `CORS_ALLOWED_ORIGINS` | _(未设置)_ | `src/server/cors/origins.ts` | 逗号分隔的 CORS 允许列表。除非显式配置 `CORS_ALLOW_ALL=true`,否则不发送通配符。 | +| `CORS_ALLOW_ALL` | `false` | `src/server/cors/origins.ts` | 仅供开发使用的逃生口,可回显任意浏览器 `Origin`。不要在共享或生产部署中启用。 | +| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuard.ts` | 阻止目标为私有/loopback/链路本地 IP 范围的服务商调用。仅在隔离的测试环境中禁用。 | +| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | `false` | `src/shared/network/outboundUrlGuard.ts` | 允许指向私有/本地网络(localhost、192.168.x.x、10.x.x.x 等)的服务商 URL。**自托管服务商必需**(LM Studio、Ollama、vLLM、Llamafile、Triton、SearXNG)。设为 `false` 时,Dashboard 拒绝校验本地 URL。 | +| `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` | `true` | `src/shared/network/outboundUrlGuard.ts` | 允许在本地/私有地址上添加/校验服务商(127.0.0.1、localhost、LAN、私有范围)— 仅影响服务商校验路径。**默认 `true`**(本地优先);设为 `false` 可强制严格的仅公网阻断。云元数据端点(169.254.169.254, metadata.google.internal)始终被阻断。(#5066) | ### 加固清单 @@ -205,84 +206,84 @@ OmniRoute 提供两层防护:请求侧的注入扫描和响应侧的 PII 脱 ### 请求侧:提示注入安全护栏 -| 变量 | 默认值 | 源文件 | 说明 | -| --- | --- | --- | --- | -| `INPUT_SANITIZER_ENABLED` | `true` | `src/middleware/promptInjectionGuard.ts` | 启用扫描传入消息中的提示注入模式。 | -| `INPUT_SANITIZER_MODE` | `warn` | `src/middleware/promptInjectionGuard.ts` | `warn` = 仅记录日志,`block` = 以 400 拒绝请求,`redact` = 脱敏可疑模式。 | -| `INJECTION_GUARD_MODE` | _(未设置)_ | `src/middleware/promptInjectionGuard.ts` | `INPUT_SANITIZER_MODE` 的旧版别名 — 行为相同。 | -| `PII_REDACTION_ENABLED` | `false` | `src/middleware/promptInjectionGuard.ts` | 检测传入请求中的 PII(邮箱、电话、社会安全号码等)。 | +| 变量 | 默认值 | 源文件 | 说明 | +| ------------------------- | ---------- | ---------------------------------------- | ------------------------------------------------------------------------- | +| `INPUT_SANITIZER_ENABLED` | `true` | `src/middleware/promptInjectionGuard.ts` | 启用扫描传入消息中的提示注入模式。 | +| `INPUT_SANITIZER_MODE` | `warn` | `src/middleware/promptInjectionGuard.ts` | `warn` = 仅记录日志,`block` = 以 400 拒绝请求,`redact` = 脱敏可疑模式。 | +| `INJECTION_GUARD_MODE` | _(未设置)_ | `src/middleware/promptInjectionGuard.ts` | `INPUT_SANITIZER_MODE` 的旧版别名 — 行为相同。 | +| `PII_REDACTION_ENABLED` | `false` | `src/middleware/promptInjectionGuard.ts` | 检测传入请求中的 PII(邮箱、电话、社会安全号码等)。 | ### 响应侧:PII 脱敏器 -| 变量 | 默认值 | 源文件 | 说明 | -| --- | --- | --- | --- | -| `PII_RESPONSE_SANITIZATION` | `false` | `src/lib/piiSanitizer.ts` | 在返回给客户端之前扫描大语言模型响应中的泄露 PII。 | +| 变量 | 默认值 | 源文件 | 说明 | +| -------------------------------- | -------- | ------------------------- | ------------------------------------------------------------------ | +| `PII_RESPONSE_SANITIZATION` | `false` | `src/lib/piiSanitizer.ts` | 在返回给客户端之前扫描大语言模型响应中的泄露 PII。 | | `PII_RESPONSE_SANITIZATION_MODE` | `redact` | `src/lib/piiSanitizer.ts` | `redact` = 脱敏 PII,`warn` = 仅记录日志,`block` = 丢弃整个响应。 | ### VS Code Token 化路由上下文净化器 -| 变量 | 默认值 | 源文件 | 说明 | -| --- | --- | --- | --- | -| `OMNIROUTE_VSCODE_SANITIZE_CONTEXT` | `1` | `src/app/api/v1/vscode/contextSanitizer.ts` | 从 `/v1/vscode/[token]/*` 请求中剥离隐式的活跃编辑器上下文(`editorContext`, `activeEditor`, `currentFile`, `selection`, `openTabs`...),并脱敏显式附加的敏感文件内容。安全默认启用;设为 `0` 可禁用。 | +| 变量 | 默认值 | 源文件 | 说明 | +| ----------------------------------- | ------ | ------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `OMNIROUTE_VSCODE_SANITIZE_CONTEXT` | `1` | `src/app/api/v1/vscode/contextSanitizer.ts` | 从 `/v1/vscode/[token]/*` 请求中剥离隐式的活跃编辑器上下文(`editorContext`, `activeEditor`, `currentFile`, `selection`, `openTabs`...),并脱敏显式附加的敏感文件内容。安全默认启用;设为 `0` 可禁用。 | ### 场景 -| 场景 | 配置 | -| --- | --- | +| 场景 | 配置 | +| ------------ | ---------------------------------------------------------------------------------------------------------------------------- | | **企业合规** | `INPUT_SANITIZER_ENABLED=true`, `INPUT_SANITIZER_MODE=block`, `PII_REDACTION_ENABLED=true`, `PII_RESPONSE_SANITIZATION=true` | -| **仅监控** | `INPUT_SANITIZER_ENABLED=true`, `INPUT_SANITIZER_MODE=warn` — 记录日志但永不阻断 | -| **个人使用** | 全部禁用 — 零开销 | +| **仅监控** | `INPUT_SANITIZER_ENABLED=true`, `INPUT_SANITIZER_MODE=warn` — 记录日志但永不阻断 | +| **个人使用** | 全部禁用 — 零开销 | --- ## 6. 工具与路由策略 -| 变量 | 默认值 | 源文件 | 说明 | -| --- | --- | --- | --- | -| `TOOL_POLICY_MODE` | `disabled` | `src/lib/toolPolicy.ts` | 控制大语言模型的工具/函数调用访问。`allowlist` = 仅允许列表中的工具,`denylist` = 除列表外全部允许,`disabled` = 无限制。 | -| `OMNIROUTE_PAYLOAD_RULES_PATH` | `./config/payloadRules.json` | `open-sse/services/payloadRules.ts` | 载荷操作规则 JSON 文件路径(按模型/协议进行上游调整)。 | -| `OMNIROUTE_PAYLOAD_RULES_RELOAD_MS` | `5000` | `open-sse/services/payloadRules.ts` | 热加载载荷规则文件的重载间隔(毫秒)。最低 `1000`。 | -| `OMNIROUTE_PREFER_CLAUDE_CODE_FOR_UNPREFIXED_CLAUDE_MODELS` | `false` | `open-sse/services/model.ts` | 启用后:将来自 Claude Code 客户端的无前缀 `claude-*` 模型 ID 通过 Claude Code OAuth 账户路由,而非要求服务商前缀。显式服务商前缀优先级更高。也可通过 Dashboard 中 Claude 服务商页面的开关配置。 | +| 变量 | 默认值 | 源文件 | 说明 | +| ----------------------------------------------------------- | ---------------------------- | ----------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `TOOL_POLICY_MODE` | `disabled` | `src/lib/toolPolicy.ts` | 控制大语言模型的工具/函数调用访问。`allowlist` = 仅允许列表中的工具,`denylist` = 除列表外全部允许,`disabled` = 无限制。 | +| `OMNIROUTE_PAYLOAD_RULES_PATH` | `./config/payloadRules.json` | `open-sse/services/payloadRules.ts` | 载荷操作规则 JSON 文件路径(按模型/协议进行上游调整)。 | +| `OMNIROUTE_PAYLOAD_RULES_RELOAD_MS` | `5000` | `open-sse/services/payloadRules.ts` | 热加载载荷规则文件的重载间隔(毫秒)。最低 `1000`。 | +| `OMNIROUTE_PREFER_CLAUDE_CODE_FOR_UNPREFIXED_CLAUDE_MODELS` | `false` | `open-sse/services/model.ts` | 启用后:将来自 Claude Code 客户端的无前缀 `claude-*` 模型 ID 通过 Claude Code OAuth 账户路由,而非要求服务商前缀。显式服务商前缀优先级更高。也可通过 Dashboard 中 Claude 服务商页面的开关配置。 | --- ## 7. URL 与云同步 -| 变量 | 默认值 | 源文件 | 说明 | -| --- | --- | --- | --- | -| `BASE_URL` | `http://localhost:20128` | `src/lib/cloudSync.ts` | 内部同步任务调用 `/api/sync/cloud` 的服务器端 URL。即使应用被公共代理,也保持为 loopback/容器 URL。 | -| `CLOUD_URL` | _(空)_ | `src/lib/cloudSync.ts` | 云中继端点 URL(高级功能)。 | -| `CLOUD_SYNC_TIMEOUT_MS` | `12000` | `src/lib/cloudSync.ts` | 云同步请求的 HTTP 超时。 | -| `OMNIROUTE_BUILD_PROFILE` | `full` | Webpack 构建配置 | 构建时配置文件(设为 `minimal` 可物理排除特权模块不打包)。 | -| `OMNIROUTE_CLOUD_SYNC_SECRET` | _(空)_ | `src/lib/cloudSync.ts` | 用于校验云同步响应 HMAC-SHA256 签名的共享密钥。 | -| `OMNIROUTE_CLOUD_SYNC_SECRETS` | `false` | `src/lib/cloudSync.ts` | 设为 `true` 允许云同步端点覆盖本地凭证。默认 `false`。 | -| `OMNIROUTE_ZED_IMPORT_LEGACY_ONE_STEP` | `false` | `src/app/api/providers/zed/import/route.ts` | 设为 `true` 可回退到 v3.8.5 的一步式"导入全部"行为,无需用户确认。 | -| `NEXT_PUBLIC_BASE_URL` | `http://localhost:20128` | OAuth、Dashboard、同步 | 面向公共的 URL,用于 OAuth redirect_uri、Dashboard 链接、生成的公共 URL 以及同源浏览器变更检查。**在反向代理背后时,必须匹配你的公共 URL。** | -| `NEXT_PUBLIC_CLOUD_URL` | _(空)_ | 客户端侧 | `CLOUD_URL` 的客户端镜像。 | -| `NEXT_PUBLIC_APP_URL` | _(未设置)_ | `src/shared/services/cloudSyncScheduler.ts` | `NEXT_PUBLIC_BASE_URL` 的旧版回退。 | -| `OMNIROUTE_PUBLIC_BASE_URL` | _(未设置)_ | 公共源解析器、图片 URL | 最高优先级的浏览器侧 OmniRoute 源,用于公共 URL 生成和源校验(例如 `/v1/chatgpt-web/image/`)。当 OpenWebUI 或其他中继通过内部 URL 访问 OmniRoute,但用户浏览器必须从 LAN、隧道或公共源获取图片时设置。**不要**包含 `/v1`。 | -| `OMNIROUTE_TRUST_PROXY` | _(未设置)_ | `src/server/origin/publicOrigin.ts` | 可选的转发公共源头信任模式。未设置 = 出于安全考虑不信任 `Forwarded` / `X-Forwarded-*`。`true` / `loopback` 仅信任来自经过 Token 戳记的 loopback 代理的转发 host/proto。`private` / `lan` 还信任私有 LAN 代理对端。生产环境中推荐显式设置 `NEXT_PUBLIC_BASE_URL`。 | -| `OMNIROUTE_CGPT_WEB_IMAGE_TIMEOUT_MS` | `180000`(3 分钟) | `open-sse/executors/chatgpt-web.ts` | 等待异步 chatgpt-web 图片通过 Celsius WebSocket 到达的最大时间。在上游排队窗口较长时提高此值。 | -| `OMNIROUTE_CGPT_WEB_IMAGE_CACHE_MAX_MB` | `256` | `open-sse/services/chatgptImageCache.ts` | 为 `/v1/chatgpt-web/image/` 提供服务的 chatgpt-web 图片缓存的内存预算总额(MB)。在内存受限的主机上降低;图片生成量大且客户端争抢 30 分钟 TTL 时提高。 | -| `OMNIROUTE_CGPT_WEB_PRO_TIMEOUT_MS` | `1200000`(20 分钟) | `open-sse/executors/chatgpt-web.ts` | chatgpt-web GPT-5.5 Pro 后台轮询交接的总体等待预算。Pro 推理在带外完成,OmniRoute 轮询直到结果到达或预算耗尽。如果 Pro 请求完成前超时,请提高此值。 | -| `OMNIROUTE_CGPT_WEB_PRO_POLL_INTERVAL_MS` | `4000`(4 秒) | `open-sse/executors/chatgpt-web.ts` | chatgpt-web GPT-5.5 Pro 后台轮询尝试的间隔。降低可更快完成但增加上游轮询;提高可减少请求量。 | -| `THEOLDLLM_NAV_TIMEOUT_MS` | `30000`(30 秒) | `open-sse/executors/theoldllm.ts` | 浏览器端 Token 捕获(The Old LLM (theoldllm) 免费服务商使用)的 Playwright 导航超时(毫秒)。如果中继页面加载慢,可在慢速网络上提高。 | -| `KIE_CALLBACK_URL` | _(未设置)_ | `open-sse/utils/kieTask.ts` | 异步 kie.ai 任务的公共回调 URL。优先级高于 `OMNIROUTE_KIE_CALLBACK_URL` 和 `OMNIROUTE_PUBLIC_URL`。 | -| `OMNIROUTE_KIE_CALLBACK_URL` | _(未设置)_ | `open-sse/utils/kieTask.ts` | `KIE_CALLBACK_URL` 的替代写法。主变量未设置时的回退。 | -| `OMNIROUTE_PUBLIC_URL` | _(未设置)_ | `open-sse/utils/kieTask.ts` | 用于组合异步回调 URL 的公共源。kie.ai 回调的最低优先级回退;也用作其他中继的通用公共 URL。 | -| `OMNIROUTE_CROF_USAGE_URL` | `https://crof.ai/usage_api/` | `open-sse/services/usage.ts` | Usage 页面使用的 CrofAI 配额查询端点。可覆盖为中继/测试固定件。 | -| `OMNIROUTE_OPENCODE_QUOTA_URL` | `https://opencode.ai/zen/go/v1/quota` | `open-sse/services/opencodeQuotaFetcher.ts` | Usage 页面使用的 OpenCode (zen/go) 配额查询端点。可覆盖为中继/测试固定件。 | -| `OMNIROUTE_OPENCODE_GO_QUOTA_URL` | _(未设置)_ | `open-sse/services/opencodeOllamaUsage.ts` | Usage 页面使用的 OpenCode Go 配额查询端点。OpenCode Go 没有公开的配额 API,因此没有默认值;除非运维人员显式设置该变量选择接入自建/镜像端点,否则不会发起网络请求。 | -| `OMNIROUTE_OPENCODE_GO_DASHBOARD_URL` | `https://opencode.ai/workspace` | `open-sse/services/usage.ts` | 配置了 workspace ID 和 auth Cookie 时用于配额抓取的 OpenCode Go Dashboard 基础 URL。可覆盖为中继/测试固定件。 | -| `OPENCODE_GO_WORKSPACE_ID` | _(未设置)_ | `open-sse/services/usage.ts` | 用于 Dashboard 配额抓取的 OpenCode Go workspace ID。配置多个账户时,推荐使用每个连接的 Dashboard 字段。 | -| `OMNIROUTE_OPENCODE_GO_WORKSPACE_ID` | _(未设置)_ | `open-sse/services/usage.ts` | OpenCode Go workspace ID 环境变量的备选名,在较短的别名之前使用。配置多个账户时,推荐使用每个连接的 Dashboard 字段。 | -| `OPENCODE_GO_AUTH_COOKIE` | _(未设置)_ | `open-sse/services/usage.ts` | 用于 Dashboard 配额抓取的 OpenCode Go `auth` Cookie。敏感信息;配置多个账户时,推荐使用每个连接的 Dashboard 字段。 | -| `OMNIROUTE_OPENCODE_GO_AUTH_COOKIE` | _(未设置)_ | `open-sse/services/usage.ts` | OpenCode Go `auth` Cookie 环境变量的备选名,在较短的别名之前使用。敏感信息;配置多个账户时,推荐使用每个连接的 Dashboard 字段。 | -| `OMNIROUTE_OLLAMA_CLOUD_USAGE_URL` | `https://ollama.com/settings` | `open-sse/services/usage.ts` | 用于配额抓取的 Ollama Cloud settings URL。可覆盖为中继/测试固定件。 | -| `OLLAMA_USAGE_COOKIE` | _(未设置)_ | `open-sse/services/usage.ts` | 用于设置页面配额抓取的 Ollama Cloud `__Secure-session` Cookie。敏感信息;配置多个账户时,推荐使用每个连接的 Dashboard 字段。 | -| `OLLAMA_CLOUD_USAGE_COOKIE` | _(未设置)_ | `open-sse/services/usage.ts` | Ollama Cloud `__Secure-session` Cookie 环境变量的备选名。敏感信息;配置多个账户时,推荐使用每个连接的 Dashboard 字段。 | -| `OMNIROUTE_OLLAMA_USAGE_COOKIE` | _(未设置)_ | `open-sse/services/usage.ts` | Ollama Cloud `__Secure-session` Cookie 环境变量的备选名,在较短的别名之前使用。敏感信息;配置多个账户时,推荐使用每个连接的 Dashboard 字段。 | -| `OMNIROUTE_CODEWHISPERER_BASE_URL` | `https://codewhisperer.us-east-1.amazonaws.com` | `open-sse/services/usage.ts` | CodeWhisperer (AWS Kiro) 用量限制端点。可覆盖为中继/测试固定件。 | +| 变量 | 默认值 | 源文件 | 说明 | +| ----------------------------------------- | ----------------------------------------------- | ------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `BASE_URL` | `http://localhost:20128` | `src/lib/cloudSync.ts` | 内部同步任务调用 `/api/sync/cloud` 的服务器端 URL。即使应用被公共代理,也保持为 loopback/容器 URL。 | +| `CLOUD_URL` | _(空)_ | `src/lib/cloudSync.ts` | 云中继端点 URL(高级功能)。 | +| `CLOUD_SYNC_TIMEOUT_MS` | `12000` | `src/lib/cloudSync.ts` | 云同步请求的 HTTP 超时。 | +| `OMNIROUTE_BUILD_PROFILE` | `full` | Webpack 构建配置 | 构建时配置文件(设为 `minimal` 可物理排除特权模块不打包)。 | +| `OMNIROUTE_CLOUD_SYNC_SECRET` | _(空)_ | `src/lib/cloudSync.ts` | 用于校验云同步响应 HMAC-SHA256 签名的共享密钥。 | +| `OMNIROUTE_CLOUD_SYNC_SECRETS` | `false` | `src/lib/cloudSync.ts` | 设为 `true` 允许云同步端点覆盖本地凭证。默认 `false`。 | +| `OMNIROUTE_ZED_IMPORT_LEGACY_ONE_STEP` | `false` | `src/app/api/providers/zed/import/route.ts` | 设为 `true` 可回退到 v3.8.5 的一步式"导入全部"行为,无需用户确认。 | +| `NEXT_PUBLIC_BASE_URL` | `http://localhost:20128` | OAuth、Dashboard、同步 | 面向公共的 URL,用于 OAuth redirect_uri、Dashboard 链接、生成的公共 URL 以及同源浏览器变更检查。**在反向代理背后时,必须匹配你的公共 URL。** | +| `NEXT_PUBLIC_CLOUD_URL` | _(空)_ | 客户端侧 | `CLOUD_URL` 的客户端镜像。 | +| `NEXT_PUBLIC_APP_URL` | _(未设置)_ | `src/shared/services/cloudSyncScheduler.ts` | `NEXT_PUBLIC_BASE_URL` 的旧版回退。 | +| `OMNIROUTE_PUBLIC_BASE_URL` | _(未设置)_ | 公共源解析器、图片 URL | 最高优先级的浏览器侧 OmniRoute 源,用于公共 URL 生成和源校验(例如 `/v1/chatgpt-web/image/`)。当 OpenWebUI 或其他中继通过内部 URL 访问 OmniRoute,但用户浏览器必须从 LAN、隧道或公共源获取图片时设置。**不要**包含 `/v1`。 | +| `OMNIROUTE_TRUST_PROXY` | _(未设置)_ | `src/server/origin/publicOrigin.ts` | 可选的转发公共源头信任模式。未设置 = 出于安全考虑不信任 `Forwarded` / `X-Forwarded-*`。`true` / `loopback` 仅信任来自经过 Token 戳记的 loopback 代理的转发 host/proto。`private` / `lan` 还信任私有 LAN 代理对端。生产环境中推荐显式设置 `NEXT_PUBLIC_BASE_URL`。 | +| `OMNIROUTE_CGPT_WEB_IMAGE_TIMEOUT_MS` | `180000`(3 分钟) | `open-sse/executors/chatgpt-web.ts` | 等待异步 chatgpt-web 图片通过 Celsius WebSocket 到达的最大时间。在上游排队窗口较长时提高此值。 | +| `OMNIROUTE_CGPT_WEB_IMAGE_CACHE_MAX_MB` | `256` | `open-sse/services/chatgptImageCache.ts` | 为 `/v1/chatgpt-web/image/` 提供服务的 chatgpt-web 图片缓存的内存预算总额(MB)。在内存受限的主机上降低;图片生成量大且客户端争抢 30 分钟 TTL 时提高。 | +| `OMNIROUTE_CGPT_WEB_PRO_TIMEOUT_MS` | `1200000`(20 分钟) | `open-sse/executors/chatgpt-web.ts` | chatgpt-web GPT-5.5 Pro 后台轮询交接的总体等待预算。Pro 推理在带外完成,OmniRoute 轮询直到结果到达或预算耗尽。如果 Pro 请求完成前超时,请提高此值。 | +| `OMNIROUTE_CGPT_WEB_PRO_POLL_INTERVAL_MS` | `4000`(4 秒) | `open-sse/executors/chatgpt-web.ts` | chatgpt-web GPT-5.5 Pro 后台轮询尝试的间隔。降低可更快完成但增加上游轮询;提高可减少请求量。 | +| `THEOLDLLM_NAV_TIMEOUT_MS` | `30000`(30 秒) | `open-sse/executors/theoldllm.ts` | 浏览器端 Token 捕获(The Old LLM (theoldllm) 免费服务商使用)的 Playwright 导航超时(毫秒)。如果中继页面加载慢,可在慢速网络上提高。 | +| `KIE_CALLBACK_URL` | _(未设置)_ | `open-sse/utils/kieTask.ts` | 异步 kie.ai 任务的公共回调 URL。优先级高于 `OMNIROUTE_KIE_CALLBACK_URL` 和 `OMNIROUTE_PUBLIC_URL`。 | +| `OMNIROUTE_KIE_CALLBACK_URL` | _(未设置)_ | `open-sse/utils/kieTask.ts` | `KIE_CALLBACK_URL` 的替代写法。主变量未设置时的回退。 | +| `OMNIROUTE_PUBLIC_URL` | _(未设置)_ | `open-sse/utils/kieTask.ts` | 用于组合异步回调 URL 的公共源。kie.ai 回调的最低优先级回退;也用作其他中继的通用公共 URL。 | +| `OMNIROUTE_CROF_USAGE_URL` | `https://crof.ai/usage_api/` | `open-sse/services/usage.ts` | Usage 页面使用的 CrofAI 配额查询端点。可覆盖为中继/测试固定件。 | +| `OMNIROUTE_OPENCODE_QUOTA_URL` | `https://opencode.ai/zen/go/v1/quota` | `open-sse/services/opencodeQuotaFetcher.ts` | Usage 页面使用的 OpenCode (zen/go) 配额查询端点。可覆盖为中继/测试固定件。 | +| `OMNIROUTE_OPENCODE_GO_QUOTA_URL` | _(未设置)_ | `open-sse/services/opencodeOllamaUsage.ts` | Usage 页面使用的 OpenCode Go 配额查询端点。OpenCode Go 没有公开的配额 API,因此没有默认值;除非运维人员显式设置该变量选择接入自建/镜像端点,否则不会发起网络请求。 | +| `OMNIROUTE_OPENCODE_GO_DASHBOARD_URL` | `https://opencode.ai/workspace` | `open-sse/services/usage.ts` | 配置了 workspace ID 和 auth Cookie 时用于配额抓取的 OpenCode Go Dashboard 基础 URL。可覆盖为中继/测试固定件。 | +| `OPENCODE_GO_WORKSPACE_ID` | _(未设置)_ | `open-sse/services/usage.ts` | 用于 Dashboard 配额抓取的 OpenCode Go workspace ID。配置多个账户时,推荐使用每个连接的 Dashboard 字段。 | +| `OMNIROUTE_OPENCODE_GO_WORKSPACE_ID` | _(未设置)_ | `open-sse/services/usage.ts` | OpenCode Go workspace ID 环境变量的备选名,在较短的别名之前使用。配置多个账户时,推荐使用每个连接的 Dashboard 字段。 | +| `OPENCODE_GO_AUTH_COOKIE` | _(未设置)_ | `open-sse/services/usage.ts` | 用于 Dashboard 配额抓取的 OpenCode Go `auth` Cookie。敏感信息;配置多个账户时,推荐使用每个连接的 Dashboard 字段。 | +| `OMNIROUTE_OPENCODE_GO_AUTH_COOKIE` | _(未设置)_ | `open-sse/services/usage.ts` | OpenCode Go `auth` Cookie 环境变量的备选名,在较短的别名之前使用。敏感信息;配置多个账户时,推荐使用每个连接的 Dashboard 字段。 | +| `OMNIROUTE_OLLAMA_CLOUD_USAGE_URL` | `https://ollama.com/settings` | `open-sse/services/usage.ts` | 用于配额抓取的 Ollama Cloud settings URL。可覆盖为中继/测试固定件。 | +| `OLLAMA_USAGE_COOKIE` | _(未设置)_ | `open-sse/services/usage.ts` | 用于设置页面配额抓取的 Ollama Cloud `__Secure-session` Cookie。敏感信息;配置多个账户时,推荐使用每个连接的 Dashboard 字段。 | +| `OLLAMA_CLOUD_USAGE_COOKIE` | _(未设置)_ | `open-sse/services/usage.ts` | Ollama Cloud `__Secure-session` Cookie 环境变量的备选名。敏感信息;配置多个账户时,推荐使用每个连接的 Dashboard 字段。 | +| `OMNIROUTE_OLLAMA_USAGE_COOKIE` | _(未设置)_ | `open-sse/services/usage.ts` | Ollama Cloud `__Secure-session` Cookie 环境变量的备选名,在较短的别名之前使用。敏感信息;配置多个账户时,推荐使用每个连接的 Dashboard 字段。 | +| `OMNIROUTE_CODEWHISPERER_BASE_URL` | `https://codewhisperer.us-east-1.amazonaws.com` | `open-sse/services/usage.ts` | CodeWhisperer (AWS Kiro) 用量限制端点。可覆盖为中继/测试固定件。 | > [!IMPORTANT] > 当部署在反向代理(nginx、Caddy)之后时,**必须**将 `NEXT_PUBLIC_BASE_URL` 设置为你的公共 URL(例如 `https://omniroute.example.com`)。否则 OAuth 回调可能因 redirect_uri 不匹配而失败,生成的公共链接可能指向内部容器源,同源 Dashboard 变更可能被浏览器源检查拒绝。 @@ -297,29 +298,29 @@ OmniRoute 提供两层防护:请求侧的注入扫描和响应侧的 PII 脱 将上游大语言模型服务商调用通过 HTTP 或 SOCKS5 代理路由,以实现出口控制、异地路由或 IP 掩码。 -| 变量 | 默认值 | 源文件 | 说明 | -| --- | --- | --- | --- | -| `ENABLE_SOCKS5_PROXY` | `true` | `open-sse/executors` | 启用上游调用的 SOCKS5 代理代理。可用 `false` 退出。 | -| `NEXT_PUBLIC_ENABLE_SOCKS5_PROXY` | `true` | 客户端侧 | 客户端对 SOCKS5 可用性的感知。 | -| `HTTP_PROXY` | _(未设置)_ | Node.js 标准 | 上游调用的 HTTP 代理。 | -| `HTTPS_PROXY` | _(未设置)_ | Node.js 标准 | 上游调用的 HTTPS 代理。 | -| `ALL_PROXY` | _(未设置)_ | Node.js 标准 | 通用代理(支持 `socks5://`)。 | -| `NO_PROXY` | _(未设置)_ | Node.js 标准 | 逗号分隔的绕过代理的主机名/IP。 | -| `OMNIROUTE_PROXY_DISPATCHER_CONNECTIONS` | `32` | `open-sse/utils/proxyDispatcher.ts` | 每个缓存的 HTTP/SOCKS 代理调度器的最大并发套接字数。长连接 SSE 流(如 Codex `/v1/responses`)在多个请求共享同一账户级代理时需要不止一个连接。超过 `256` 的值会被截断。 | -| `SOCKS_HANDSHAKE_TIMEOUT_MS` | `10000` | `open-sse/utils/socksConnectorWithFamily.ts` | SOCKS5 握手(连接)超时,毫秒。当单个住宅网关主机高并发(如 100 个并发请求)时提高 — 在池饱和情况下实际握手可能超过 10 秒,即使代理可达,否则会显示为虚假的 `[Proxy Fast-Fail] Proxy unreachable`。上限为 `120000`。 | -| `PROXY_FAIL_OPEN` | `false` | `src/sse/handlers/chatHelpers.ts` | 设为 `false`(默认)时,代理解析失败的请求会被**拒绝(fail-closed)**,而不会回退到直连 — 防止真实 IP 泄露。设为 `true` 可恢复旧版的 DIRECT 回退。 | -| `ENABLE_TLS_FINGERPRINT` | `false` | `open-sse/executors` | 使用 wreq-js 伪装 TLS 指纹(模拟 Chrome 124)。对抗 JA3/JA4 阻断。 | -| `OMNIROUTE_TURNSTILE_IGNORE_TLS_ERRORS` | `false` | `open-sse/services/claudeTurnstileSolver.ts` | 允许 Claude Turnstile 的 Playwright 浏览器上下文忽略 HTTPS 证书错误。 | +| 变量 | 默认值 | 源文件 | 说明 | +| ---------------------------------------- | ---------- | -------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `ENABLE_SOCKS5_PROXY` | `true` | `open-sse/executors` | 启用上游调用的 SOCKS5 代理代理。可用 `false` 退出。 | +| `NEXT_PUBLIC_ENABLE_SOCKS5_PROXY` | `true` | 客户端侧 | 客户端对 SOCKS5 可用性的感知。 | +| `HTTP_PROXY` | _(未设置)_ | Node.js 标准 | 上游调用的 HTTP 代理。 | +| `HTTPS_PROXY` | _(未设置)_ | Node.js 标准 | 上游调用的 HTTPS 代理。 | +| `ALL_PROXY` | _(未设置)_ | Node.js 标准 | 通用代理(支持 `socks5://`)。 | +| `NO_PROXY` | _(未设置)_ | Node.js 标准 | 逗号分隔的绕过代理的主机名/IP。 | +| `OMNIROUTE_PROXY_DISPATCHER_CONNECTIONS` | `32` | `open-sse/utils/proxyDispatcher.ts` | 每个缓存的 HTTP/SOCKS 代理调度器的最大并发套接字数。长连接 SSE 流(如 Codex `/v1/responses`)在多个请求共享同一账户级代理时需要不止一个连接。超过 `256` 的值会被截断。 | +| `SOCKS_HANDSHAKE_TIMEOUT_MS` | `10000` | `open-sse/utils/socksConnectorWithFamily.ts` | SOCKS5 握手(连接)超时,毫秒。当单个住宅网关主机高并发(如 100 个并发请求)时提高 — 在池饱和情况下实际握手可能超过 10 秒,即使代理可达,否则会显示为虚假的 `[Proxy Fast-Fail] Proxy unreachable`。上限为 `120000`。 | +| `PROXY_FAIL_OPEN` | `false` | `src/sse/handlers/chatHelpers.ts` | 设为 `false`(默认)时,代理解析失败的请求会被**拒绝(fail-closed)**,而不会回退到直连 — 防止真实 IP 泄露。设为 `true` 可恢复旧版的 DIRECT 回退。 | +| `ENABLE_TLS_FINGERPRINT` | `false` | `open-sse/executors` | 使用 wreq-js 伪装 TLS 指纹(模拟 Chrome 124)。对抗 JA3/JA4 阻断。 | +| `OMNIROUTE_TURNSTILE_IGNORE_TLS_ERRORS` | `false` | `open-sse/services/claudeTurnstileSolver.ts` | 允许 Claude Turnstile 的 Playwright 浏览器上下文忽略 HTTPS 证书错误。 | ### 场景 -| 场景 | 配置 | -| --- | --- | -| **通过 SSH 隧道走 SOCKS5** | `ALL_PROXY=socks5://127.0.0.1:7890`, `ENABLE_SOCKS5_PROXY=true` | -| **企业 HTTP 代理** | `HTTP_PROXY=http://proxy.corp.com:3128`, `HTTPS_PROXY=http://proxy.corp.com:3128`, `NO_PROXY=localhost,internal.corp.com` | -| **反指纹** | `ENABLE_TLS_FINGERPRINT=true` — 需要 `wreq-js`(已包含) | -| **出口受控 / 无直连访问** | 保持 `PROXY_FAIL_OPEN=false`(默认)。代理不可用时请求直接失败,不会通过直连泄露。 | -| **旧版/开发 — 允许直连回退** | `PROXY_FAIL_OPEN=true`。恢复加固前行为:代理解析失败时使用直连连接。 | +| 场景 | 配置 | +| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------- | +| **通过 SSH 隧道走 SOCKS5** | `ALL_PROXY=socks5://127.0.0.1:7890`, `ENABLE_SOCKS5_PROXY=true` | +| **企业 HTTP 代理** | `HTTP_PROXY=http://proxy.corp.com:3128`, `HTTPS_PROXY=http://proxy.corp.com:3128`, `NO_PROXY=localhost,internal.corp.com` | +| **反指纹** | `ENABLE_TLS_FINGERPRINT=true` — 需要 `wreq-js`(已包含) | +| **出口受控 / 无直连访问** | 保持 `PROXY_FAIL_OPEN=false`(默认)。代理不可用时请求直接失败,不会通过直连泄露。 | +| **旧版/开发 — 允许直连回退** | `PROXY_FAIL_OPEN=true`。恢复加固前行为:代理解析失败时使用直连连接。 | > **注意(NVIDIA 校验绕过 — #3226):** NVIDIA 的 API Key 校验端点 > 在通过全局代理/TLS 修补 fetch(undici dispatcher → 504)路由时会卡住。 @@ -334,23 +335,23 @@ OmniRoute 提供两层防护:请求侧的注入扫描和响应侧的 PII 脱 控制 OmniRoute 如何发现和启动 CLI sidecar(Claude Code、Codex 等)。 -| 变量 | 默认值 | 源文件 | 说明 | -| --- | --- | --- | --- | -| `CLI_MODE` | `auto` | `src/shared/services/cliRuntime.ts` | `auto` = 搜索系统 PATH;`manual` = 仅使用显式路径。 | -| `CLI_EXTRA_PATHS` | _(未设置)_ | `src/shared/services/cliRuntime.ts` | 用于 CLI 二进制文件发现的额外 PATH 条目(冒号分隔)。 | -| `CLI_CONFIG_HOME` | _(未设置)_ | `src/shared/services/cliRuntime.ts` | 覆盖读取 CLI 配置(`~/.claude`、`~/.codex`)的主目录。 | -| `CLI_ALLOW_CONFIG_WRITES` | `false` | `src/shared/services/cliRuntime.ts` | 允许 OmniRoute 写入 CLI 配置文件(Token 刷新、会话数据)。 | -| `CLI_CLAUDE_BIN` | `claude` | `src/shared/services/cliRuntime.ts` | Claude CLI 二进制文件的自定义路径。 | -| `CLI_CODEX_BIN` | `codex` | `src/shared/services/cliRuntime.ts` | Codex CLI 二进制文件的自定义路径。 | -| `CLI_DROID_BIN` | `droid` | `src/shared/services/cliRuntime.ts` | Droid CLI 二进制文件的自定义路径。 | -| `CLI_OPENCLAW_BIN` | `openclaw` | `src/shared/services/cliRuntime.ts` | OpenClaw CLI 二进制文件的自定义路径。 | -| `CLI_CURSOR_BIN` | `agent` | `src/shared/services/cliRuntime.ts` | Cursor agent 二进制文件的自定义路径。 | -| `CLI_CLINE_BIN` | `cline` | `src/shared/services/cliRuntime.ts` | Cline CLI 二进制文件的自定义路径。 | -| `CLI_CONTINUE_BIN` | `cn` | `src/shared/services/cliRuntime.ts` | Continue CLI 二进制文件的自定义路径。 | -| `CLI_QODER_BIN` | `qoder` | `src/shared/services/cliRuntime.ts` | Qoder CLI 二进制文件的自定义路径。 | -| `CLI_QWEN_BIN` | `qwen` | `src/shared/services/cliRuntime.ts` | Qwen Code CLI 二进制文件的自定义路径。 | -| `CLI_DEVIN_BIN` | `devin` | `open-sse/executors/devin-cli.ts` | Devin CLI 二进制文件的自定义路径(v3.8.0)。由 Windsurf/Devin executor 使用。 | -| `HERMES_HOME` | `~/.hermes` | `src/lib/cli-helper/config-generator/hermesHome.ts` | Hermes Agent 主目录,OmniRoute 从此处读取/写入 Hermes CLI 配置。与 Hermes PowerShell 安装程序在 Windows 上设置的环境变量(`%LOCALAPPDATA%\hermes`)一致。 | +| 变量 | 默认值 | 源文件 | 说明 | +| ------------------------- | ----------- | --------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `CLI_MODE` | `auto` | `src/shared/services/cliRuntime.ts` | `auto` = 搜索系统 PATH;`manual` = 仅使用显式路径。 | +| `CLI_EXTRA_PATHS` | _(未设置)_ | `src/shared/services/cliRuntime.ts` | 用于 CLI 二进制文件发现的额外 PATH 条目(冒号分隔)。 | +| `CLI_CONFIG_HOME` | _(未设置)_ | `src/shared/services/cliRuntime.ts` | 覆盖读取 CLI 配置(`~/.claude`、`~/.codex`)的主目录。 | +| `CLI_ALLOW_CONFIG_WRITES` | `false` | `src/shared/services/cliRuntime.ts` | 允许 OmniRoute 写入 CLI 配置文件(Token 刷新、会话数据)。 | +| `CLI_CLAUDE_BIN` | `claude` | `src/shared/services/cliRuntime.ts` | Claude CLI 二进制文件的自定义路径。 | +| `CLI_CODEX_BIN` | `codex` | `src/shared/services/cliRuntime.ts` | Codex CLI 二进制文件的自定义路径。 | +| `CLI_DROID_BIN` | `droid` | `src/shared/services/cliRuntime.ts` | Droid CLI 二进制文件的自定义路径。 | +| `CLI_OPENCLAW_BIN` | `openclaw` | `src/shared/services/cliRuntime.ts` | OpenClaw CLI 二进制文件的自定义路径。 | +| `CLI_CURSOR_BIN` | `agent` | `src/shared/services/cliRuntime.ts` | Cursor agent 二进制文件的自定义路径。 | +| `CLI_CLINE_BIN` | `cline` | `src/shared/services/cliRuntime.ts` | Cline CLI 二进制文件的自定义路径。 | +| `CLI_CONTINUE_BIN` | `cn` | `src/shared/services/cliRuntime.ts` | Continue CLI 二进制文件的自定义路径。 | +| `CLI_QODER_BIN` | `qoder` | `src/shared/services/cliRuntime.ts` | Qoder CLI 二进制文件的自定义路径。 | +| `CLI_QWEN_BIN` | `qwen` | `src/shared/services/cliRuntime.ts` | Qwen Code CLI 二进制文件的自定义路径。 | +| `CLI_DEVIN_BIN` | `devin` | `open-sse/executors/devin-cli.ts` | Devin CLI 二进制文件的自定义路径(v3.8.0)。由 Windsurf/Devin executor 使用。 | +| `HERMES_HOME` | `~/.hermes` | `src/lib/cli-helper/config-generator/hermesHome.ts` | Hermes Agent 主目录,OmniRoute 从此处读取/写入 Hermes CLI 配置。与 Hermes PowerShell 安装程序在 Windows 上设置的环境变量(`%LOCALAPPDATA%\hermes`)一致。 | ### Docker 示例 @@ -366,59 +367,59 @@ CLI_CLAUDE_BIN=/host-cli/bin/claude 以下变量调优 `omniroute` CLI 二进制文件自身的行为(而非上面的 sidecar 检测)。 -| 变量 | 默认值 | 源文件 | 说明 | -| --- | --- | --- | --- | -| `OMNIROUTE_LANG` | _(系统)_ | `bin/cli/i18n.mjs` | 强制 CLI 输出语言。BCP-47 locale(如 `en`、`pt-BR`)。覆盖系统 locale 环境变量(LC_ALL, LC_MESSAGES)。 | -| `OMNIROUTE_SHOW_LOG` | _(未设置)_ | `bin/cli/runtime/processSupervisor.mjs` | 设为 `1` 可在受监管模式下将服务器 stdout/stderr 转发到终端。等同于 `omniroute serve` 的 `--log` 标志。 | -| `OMNIROUTE_CLI_TOKEN` | _(未设置)_ | `bin/cli/api.mjs` | 作为 `x-omniroute-cli-token` 头注入的机器认证 Token。在任务 8.12 中自动生成。 | -| `OMNIROUTE_HTTP_TIMEOUT_MS` | `30000` | `bin/cli/api.mjs` | CLI → 服务器请求的单次尝试 HTTP 超时(毫秒)。 | -| `OMNIROUTE_VERBOSE` | `0` | `bin/cli/api.mjs` | 设为 `1` 可在 CLI 命令期间将重试/退避诊断信息打印到 stderr。 | -| `OMNIROUTE_PLUGIN_PATH` | _(未设置)_ | `bin/cli/plugins.mjs` | CLI 插件发现的自定义目录(`omniroute-cmd-*` 包)。未设置时默认为 `~/.omniroute/plugins/`。 | -| `OMNIROUTE_PLUGINS_ALLOW_EXEC` | `0` | `src/lib/plugins/pluginWorker.ts` | 设为 `1` 允许插件请求 `exec` 权限(从 Worker 沙箱中生成子进程)。仅供本地运维人员。 | +| 变量 | 默认值 | 源文件 | 说明 | +| ------------------------------ | ---------- | --------------------------------------- | ------------------------------------------------------------------------------------------------------- | +| `OMNIROUTE_LANG` | _(系统)_ | `bin/cli/i18n.mjs` | 强制 CLI 输出语言。BCP-47 locale(如 `en`、`pt-BR`)。覆盖系统 locale 环境变量(LC_ALL, LC_MESSAGES)。 | +| `OMNIROUTE_SHOW_LOG` | _(未设置)_ | `bin/cli/runtime/processSupervisor.mjs` | 设为 `1` 可在受监管模式下将服务器 stdout/stderr 转发到终端。等同于 `omniroute serve` 的 `--log` 标志。 | +| `OMNIROUTE_CLI_TOKEN` | _(未设置)_ | `bin/cli/api.mjs` | 作为 `x-omniroute-cli-token` 头注入的机器认证 Token。在任务 8.12 中自动生成。 | +| `OMNIROUTE_HTTP_TIMEOUT_MS` | `30000` | `bin/cli/api.mjs` | CLI → 服务器请求的单次尝试 HTTP 超时(毫秒)。 | +| `OMNIROUTE_VERBOSE` | `0` | `bin/cli/api.mjs` | 设为 `1` 可在 CLI 命令期间将重试/退避诊断信息打印到 stderr。 | +| `OMNIROUTE_PLUGIN_PATH` | _(未设置)_ | `bin/cli/plugins.mjs` | CLI 插件发现的自定义目录(`omniroute-cmd-*` 包)。未设置时默认为 `~/.omniroute/plugins/`。 | +| `OMNIROUTE_PLUGINS_ALLOW_EXEC` | `0` | `src/lib/plugins/pluginWorker.ts` | 设为 `1` 允许插件请求 `exec` 权限(从 Worker 沙箱中生成子进程)。仅供本地运维人员。 | --- ## 10. 内部 Agent 与 MCP 集成 -| 变量 | 默认值 | 源文件 | 说明 | -| --- | --- | --- | --- | -| `OMNIROUTE_BASE_URL` | 自动检测 | `open-sse/mcp-server/server.ts` | MCP/A2A 工具访问 OmniRoute 的显式 URL。覆盖 localhost 自动检测。 | -| `OMNIROUTE_API_KEY` | _(未设置)_ | MCP/A2A 模块 | 内部 MCP 工具和 A2A 技能调用的 API key。 | -| `OMNIROUTE_API_KEY_ID` | _(未设置)_ | `open-sse/mcp-server/audit.ts` | 用于 MCP 审计日志归属的 Key ID。 | -| `ROUTER_API_KEY` | _(未设置)_ | 旧版 | `OMNIROUTE_API_KEY` 的旧版别名。 | -| `OMNIROUTE_CONTEXT` | _(活跃上下文)_ | `bin/cli/program.mjs`, `bin/cli/api.mjs` | `omniroute` 命令的 CLI 远程模式上下文/配置文件;覆盖本地上下文存储中的活跃上下文。等同于 `--context `。 | -| `OMNIROUTE_MCP_ENFORCE_SCOPES` | `true` | `open-sse/mcp-server/server.ts` | 对 MCP 工具调用强制执行基于权限域的访问控制。 | -| `OMNIROUTE_MCP_SCOPES` | _(全部)_ | `open-sse/mcp-server/server.ts` | 逗号分隔的权限域:`admin`、`combos`、`health`、`models`、`routing`、`budget`、`metrics`、`pricing`、`memory`、`skills`。 | -| `OMNIROUTE_MCP_COMPRESS_DESCRIPTIONS` | `false` | `open-sse/mcp-server/descriptionCompressor.ts` | 在序列化清单之前压缩 MCP 工具描述。启用值:`1`、`true`、`on`。 | -| `OMNIROUTE_MCP_DESCRIPTION_COMPRESSION` | `rtk` | `open-sse/mcp-server/descriptionCompressor.ts` | 压缩算法/配置文件。禁用值:`0`、`false`、`off`。 | -| `MODEL_SYNC_INTERVAL_HOURS` | `24` | `src/shared/services/modelSyncScheduler.ts` | 模型目錄同步间隔,小时。 | -| `PROVIDER_LIMITS_SYNC_INTERVAL_MINUTES` | `70` | `src/server-init.ts` | 服务商速率限制和配额轮询间隔。 | -| `PROVIDER_LIMITS_SYNC_SPACING_MS` | `1500` | `src/lib/usage/providerLimits.ts` | 批量同步中连续 OAuth 配额获取之间的间隔(毫秒);OAuth 连接逐个获取以避免冲击上游。`0` 表示退出(并发)。 | -| `PROVIDER_LIMITS_POST_USAGE_REFRESH_DELAY_MS` | `5000` | `src/lib/usage/providerLimits.ts` | 真实用量事件后刷新服务商限制前的延迟(毫秒),给上游配额 API 时间记录消费。 | -| `OMNIROUTE_DISABLE_BACKGROUND_SERVICES` | `false` | `src/instrumentation-node.ts` | 禁用所有后台服务(同步、价格、模型刷新)。适用于 CI/测试。 | -| `OMNIROUTE_ENABLE_RUNTIME_BACKGROUND_TASKS` | _(未设置)_ | `src/lib/config/runtimeSettings.ts` | 在自动测试检测下强制运行后台任务。设为 `1` 可覆盖测试推断。 | -| `OMNIROUTE_BUDGET_RESET_JOB_INTERVAL_MS` | `600000` | `src/lib/jobs/budgetResetJob.ts` | 预算重置检查频率(毫秒)。最低 `10000`。 | -| `OMNIROUTE_CONNECTION_RECOVERY_INTERVAL_MS` | `60000` | `src/lib/quota/connectionRecovery.ts` | 主动连接冷却恢复频率(毫秒):对瞬态 `rate_limited_until` 已过期的连接进行重新校验,脱离请求热路径。最低 `5000`。 | -| `OMNIROUTE_DISABLE_CONNECTION_RECOVERY` | `false` | `src/lib/quota/connectionRecovery.ts` | 禁用主动连接冷却恢复调度器(`getProviderCredentials` 中的惰性恢复仍然生效)。 | -| `OMNIROUTE_REASONING_CACHE_CLEANUP_INTERVAL_MS` | `1800000` | `src/lib/jobs/reasoningCacheCleanupJob.ts` | 推理缓存清理频率(毫秒)。最低 `60000`。 | -| `OMNIROUTE_CONFIG_HOT_RELOAD_MS` | `5000` | `src/lib/config/hotReload.ts` | 配置热加载的轮询间隔(毫秒)。低于 `1000` 会被拒绝。 | -| `OMNIROUTE_DISABLE_REDIS_AUTH_CACHE` | _(启用)_ | `src/lib/db/apiKeys.ts` | 设为 `1` 可绕过 Redis 支持的 API Key 认证缓存(强制走数据库读取)。 | -| `OMNIROUTE_RTK_TRUST_PROJECT_FILTERS` | `0` | `open-sse/services/compression/engines/rtk/filterLoader.ts` | 信任用户管理的 RTK 项目过滤器规则,无需严格的签名检查。 | -| `OMNIROUTE_BOOTSTRAPPED` | `false` | `src/app/(dashboard)/dashboard/page.tsx` | 引导脚本在初始设置后设为 `true`。控制设置向导的可见性。 | -| `OMNIROUTE_ALLOW_BODY_PROJECT_OVERRIDE` | `0` | `open-sse/executors/antigravity.ts` | 逃生口:允许请求体覆盖 Antigravity 项目字段。 | -| `ANTIGRAVITY_CREDITS` | _(未设置)_ | `open-sse/services/antigravityCredits.ts` | 覆盖 Antigravity 的广告剩余积分(测试/强制值)。 | -| `AGY_TOKEN_FILE` | `~/.gemini/antigravity-cli/antigravity-oauth-token` | `src/app/api/providers/agy-auth/apply-local/route.ts` | 覆盖自动检测本地登录导入的 Antigravity CLI (agy) Token 文件路径。 | +| 变量 | 默认值 | 源文件 | 说明 | +| ----------------------------------------------- | --------------------------------------------------- | ----------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ | +| `OMNIROUTE_BASE_URL` | 自动检测 | `open-sse/mcp-server/server.ts` | MCP/A2A 工具访问 OmniRoute 的显式 URL。覆盖 localhost 自动检测。 | +| `OMNIROUTE_API_KEY` | _(未设置)_ | MCP/A2A 模块 | 内部 MCP 工具和 A2A 技能调用的 API key。 | +| `OMNIROUTE_API_KEY_ID` | _(未设置)_ | `open-sse/mcp-server/audit.ts` | 用于 MCP 审计日志归属的 Key ID。 | +| `ROUTER_API_KEY` | _(未设置)_ | 旧版 | `OMNIROUTE_API_KEY` 的旧版别名。 | +| `OMNIROUTE_CONTEXT` | _(活跃上下文)_ | `bin/cli/program.mjs`, `bin/cli/api.mjs` | `omniroute` 命令的 CLI 远程模式上下文/配置文件;覆盖本地上下文存储中的活跃上下文。等同于 `--context `。 | +| `OMNIROUTE_MCP_ENFORCE_SCOPES` | `true` | `open-sse/mcp-server/server.ts` | 对 MCP 工具调用强制执行基于权限域的访问控制。 | +| `OMNIROUTE_MCP_SCOPES` | _(全部)_ | `open-sse/mcp-server/server.ts` | 逗号分隔的权限域:`admin`、`combos`、`health`、`models`、`routing`、`budget`、`metrics`、`pricing`、`memory`、`skills`。 | +| `OMNIROUTE_MCP_COMPRESS_DESCRIPTIONS` | `false` | `open-sse/mcp-server/descriptionCompressor.ts` | 在序列化清单之前压缩 MCP 工具描述。启用值:`1`、`true`、`on`。 | +| `OMNIROUTE_MCP_DESCRIPTION_COMPRESSION` | `rtk` | `open-sse/mcp-server/descriptionCompressor.ts` | 压缩算法/配置文件。禁用值:`0`、`false`、`off`。 | +| `MODEL_SYNC_INTERVAL_HOURS` | `24` | `src/shared/services/modelSyncScheduler.ts` | 模型目錄同步间隔,小时。 | +| `PROVIDER_LIMITS_SYNC_INTERVAL_MINUTES` | `70` | `src/server-init.ts` | 服务商速率限制和配额轮询间隔。 | +| `PROVIDER_LIMITS_SYNC_SPACING_MS` | `1500` | `src/lib/usage/providerLimits.ts` | 批量同步中连续 OAuth 配额获取之间的间隔(毫秒);OAuth 连接逐个获取以避免冲击上游。`0` 表示退出(并发)。 | +| `PROVIDER_LIMITS_POST_USAGE_REFRESH_DELAY_MS` | `5000` | `src/lib/usage/providerLimits.ts` | 真实用量事件后刷新服务商限制前的延迟(毫秒),给上游配额 API 时间记录消费。 | +| `OMNIROUTE_DISABLE_BACKGROUND_SERVICES` | `false` | `src/instrumentation-node.ts` | 禁用所有后台服务(同步、价格、模型刷新)。适用于 CI/测试。 | +| `OMNIROUTE_ENABLE_RUNTIME_BACKGROUND_TASKS` | _(未设置)_ | `src/lib/config/runtimeSettings.ts` | 在自动测试检测下强制运行后台任务。设为 `1` 可覆盖测试推断。 | +| `OMNIROUTE_BUDGET_RESET_JOB_INTERVAL_MS` | `600000` | `src/lib/jobs/budgetResetJob.ts` | 预算重置检查频率(毫秒)。最低 `10000`。 | +| `OMNIROUTE_CONNECTION_RECOVERY_INTERVAL_MS` | `60000` | `src/lib/quota/connectionRecovery.ts` | 主动连接冷却恢复频率(毫秒):对瞬态 `rate_limited_until` 已过期的连接进行重新校验,脱离请求热路径。最低 `5000`。 | +| `OMNIROUTE_DISABLE_CONNECTION_RECOVERY` | `false` | `src/lib/quota/connectionRecovery.ts` | 禁用主动连接冷却恢复调度器(`getProviderCredentials` 中的惰性恢复仍然生效)。 | +| `OMNIROUTE_REASONING_CACHE_CLEANUP_INTERVAL_MS` | `1800000` | `src/lib/jobs/reasoningCacheCleanupJob.ts` | 推理缓存清理频率(毫秒)。最低 `60000`。 | +| `OMNIROUTE_CONFIG_HOT_RELOAD_MS` | `5000` | `src/lib/config/hotReload.ts` | 配置热加载的轮询间隔(毫秒)。低于 `1000` 会被拒绝。 | +| `OMNIROUTE_DISABLE_REDIS_AUTH_CACHE` | _(启用)_ | `src/lib/db/apiKeys.ts` | 设为 `1` 可绕过 Redis 支持的 API Key 认证缓存(强制走数据库读取)。 | +| `OMNIROUTE_RTK_TRUST_PROJECT_FILTERS` | `0` | `open-sse/services/compression/engines/rtk/filterLoader.ts` | 信任用户管理的 RTK 项目过滤器规则,无需严格的签名检查。 | +| `OMNIROUTE_BOOTSTRAPPED` | `false` | `src/app/(dashboard)/dashboard/page.tsx` | 引导脚本在初始设置后设为 `true`。控制设置向导的可见性。 | +| `OMNIROUTE_ALLOW_BODY_PROJECT_OVERRIDE` | `0` | `open-sse/executors/antigravity.ts` | 逃生口:允许请求体覆盖 Antigravity 项目字段。 | +| `ANTIGRAVITY_CREDITS` | _(未设置)_ | `open-sse/services/antigravityCredits.ts` | 覆盖 Antigravity 的广告剩余积分(测试/强制值)。 | +| `AGY_TOKEN_FILE` | `~/.gemini/antigravity-cli/antigravity-oauth-token` | `src/app/api/providers/agy-auth/apply-local/route.ts` | 覆盖自动检测本地登录导入的 Antigravity CLI (agy) Token 文件路径。 | ### OAuth CLI 桥接(内部) -| 变量 | 默认值 | 源文件 | 说明 | -| --- | --- | --- | --- | -| `OMNIROUTE_SERVER` | 自动检测 | `src/lib/oauth/config/index.ts` | CLI↔OmniRoute 认证桥接的服务器 URL。 | -| `OMNIROUTE_TOKEN` | _(未设置)_ | `src/lib/oauth/config/index.ts` | CLI 桥接的认证 Token。 | -| `OMNIROUTE_USER_ID` | `cli` | `src/lib/oauth/config/index.ts` | CLI 桥接会话的用户 ID。 | -| `SERVER_URL` | _(未设置)_ | `src/lib/oauth/config/index.ts` | `OMNIROUTE_SERVER` 的旧版别名。 | -| `CLI_TOKEN` | _(未设置)_ | `src/lib/oauth/config/index.ts` | `OMNIROUTE_TOKEN` 的旧版别名。 | -| `CLI_USER_ID` | _(未设置)_ | `src/lib/oauth/config/index.ts` | `OMNIROUTE_USER_ID` 的旧版别名。 | +| 变量 | 默认值 | 源文件 | 说明 | +| ------------------- | ---------- | ------------------------------- | ------------------------------------ | +| `OMNIROUTE_SERVER` | 自动检测 | `src/lib/oauth/config/index.ts` | CLI↔OmniRoute 认证桥接的服务器 URL。 | +| `OMNIROUTE_TOKEN` | _(未设置)_ | `src/lib/oauth/config/index.ts` | CLI 桥接的认证 Token。 | +| `OMNIROUTE_USER_ID` | `cli` | `src/lib/oauth/config/index.ts` | CLI 桥接会话的用户 ID。 | +| `SERVER_URL` | _(未设置)_ | `src/lib/oauth/config/index.ts` | `OMNIROUTE_SERVER` 的旧版别名。 | +| `CLI_TOKEN` | _(未设置)_ | `src/lib/oauth/config/index.ts` | `OMNIROUTE_TOKEN` 的旧版别名。 | +| `CLI_USER_ID` | _(未设置)_ | `src/lib/oauth/config/index.ts` | `OMNIROUTE_USER_ID` 的旧版别名。 | --- @@ -426,38 +427,38 @@ CLI_CLAUDE_BIN=/host-cli/bin/claude 用于 **localhost 开发** 的内置凭证。对于远程部署,请在各个服务商的开发者控制台中注册你自己的凭证。 -| 变量 | 服务商 | 备注 | -| --- | --- | --- | -| `CLAUDE_OAUTH_CLIENT_ID` | Claude Code (Anthropic) | 公共客户端 — 无需 secret。 | -| `CLAUDE_CODE_REDIRECT_URI` | Claude Code | 覆盖重定向 URI。默认值:`https://platform.claude.com/oauth/code/callback` | -| `CODEX_OAUTH_CLIENT_ID` | Codex / OpenAI | 公共客户端。 | -| `GEMINI_OAUTH_CLIENT_ID` | Gemini (Google) | 需要匹配的 `_SECRET`。 | -| `GEMINI_OAUTH_CLIENT_SECRET` | Gemini (Google) | — | -| `QWEN_OAUTH_CLIENT_ID` | Qwen (Alibaba) | 公共客户端。 | -| `KIMI_CODING_OAUTH_CLIENT_ID` | Kimi Coding (Moonshot) | 公共客户端。 | -| `ANTIGRAVITY_OAUTH_CLIENT_ID` | Antigravity (Google) | 需要匹配的 `_SECRET`。 | -| `ANTIGRAVITY_OAUTH_CLIENT_SECRET` | Antigravity (Google) | — | -| `GITHUB_OAUTH_CLIENT_ID` | GitHub Copilot | 公共客户端。 | -| `WINDSURF_FIREBASE_API_KEY` | Windsurf / Devin (v3.8) | Windsurf 安全 Token 服务用于刷新的公共 Firebase Web API key。客户端凭证(非密钥)。长期导入 Token 完全跳过此步骤。来源:从 Devin CLI 二进制文件中提取。 | -| `WINDSURF_API_KEY` | Windsurf / Devin (v3.8) | 无每个连接凭证时 `open-sse/executors/devin-cli.ts` 使用的 API key 回退。可选。 | -| `CLI_DEVIN_BIN` | Devin CLI (v3.8) | Devin CLI 二进制文件(`devin`)的自定义路径。由 `open-sse/executors/devin-cli.ts` 解析。 | -| `GITLAB_DUO_OAUTH_CLIENT_ID` | GitLab Duo (v3.8) | GitLab Duo 的 OAuth client ID。在 `https://gitlab.com/-/profile/applications` 注册应用,redirect URI 为 `/callback`,权限域为 `api, read_user, openid, profile, email`。回退到 `GITLAB_OAUTH_CLIENT_ID`。 | -| `GITLAB_DUO_OAUTH_CLIENT_SECRET` | GitLab Duo (v3.8) | GitLab Duo 的 OAuth client secret。可选 — PKCE 流程不需要 secret。回退到 `GITLAB_OAUTH_CLIENT_SECRET`。 | -| `GITLAB_DUO_BASE_URL` | GitLab Duo (v3.8) | 覆盖 GitLab 基础 URL(自托管 GitLab)。默认为 `https://gitlab.com`。回退到 `GITLAB_BASE_URL`。 | -| `GITLAB_BASE_URL` | GitLab Duo (v3.8) | `GITLAB_DUO_BASE_URL` 的旧版回退。在 `_DUO_` 变体未设置时使用。 | -| `GITLAB_OAUTH_CLIENT_ID` | GitLab Duo (v3.8) | `GITLAB_DUO_OAUTH_CLIENT_ID` 的旧版回退,由 `src/lib/oauth/constants/oauth.ts` 使用。 | -| `GITLAB_OAUTH_CLIENT_SECRET` | GitLab Duo (v3.8) | `GITLAB_DUO_OAUTH_CLIENT_SECRET` 的旧版回退,由 `src/lib/oauth/constants/oauth.ts` 使用。 | -| `QODER_OAUTH_CLIENT_SECRET` | Qoder | — | -| `QODER_OAUTH_AUTHORIZE_URL` | Qoder | 设置以启用 Qoder OAuth。 | -| `QODER_OAUTH_TOKEN_URL` | Qoder | — | -| `QODER_OAUTH_USERINFO_URL` | Qoder | — | -| `QODER_OAUTH_CLIENT_ID` | Qoder | — | -| `QODER_PERSONAL_ACCESS_TOKEN` | Qoder | 直接 API key 回退(绕过 OAuth)。 | -| `QODER_CLI_WORKSPACE` | Qoder | Qoder CLI 的 workspace ID。 | -| `OMNIROUTE_QODER_WORKSPACE` | Qoder | `QODER_CLI_WORKSPACE` 的别名。 | -| `BLACKBOX_WEB_VALIDATED_TOKEN` | Blackbox Web | 作为 `validated` 发送到 `/api/chat` 的前端 `tk` Token。当 Blackbox 强制 Token 匹配时必需;否则 OmniRoute 回退到随机 UUID。参阅 issue #2252。 | -| `VISION_BRIDGE_BASE_URL` | Vision Bridge 安全护栏 | 非 Anthropic 视觉桥接调用的 OpenAI 兼容基础 URL。默认为旧版 OpenAI URL 或 api.openai.com。指向 OmniRoute 的 `/v1` 自循环或任意 OpenAI 兼容端点(Gemini OpenAI-compat、OpenRouter)。Issue #2232。 | -| `VISION_BRIDGE_API_KEY` | Vision Bridge 安全护栏 | 上面 URL 的 API key。对于非 Anthropic 视觉桥接调用,覆盖每个服务商的 OpenAI / Google 环境变量。Anthropic 模型保留其专用的 Anthropic key 路径。Issue #2232。 | +| 变量 | 服务商 | 备注 | +| --------------------------------- | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `CLAUDE_OAUTH_CLIENT_ID` | Claude Code (Anthropic) | 公共客户端 — 无需 secret。 | +| `CLAUDE_CODE_REDIRECT_URI` | Claude Code | 覆盖重定向 URI。默认值:`https://platform.claude.com/oauth/code/callback` | +| `CODEX_OAUTH_CLIENT_ID` | Codex / OpenAI | 公共客户端。 | +| `GEMINI_OAUTH_CLIENT_ID` | Gemini (Google) | 需要匹配的 `_SECRET`。 | +| `GEMINI_OAUTH_CLIENT_SECRET` | Gemini (Google) | — | +| `QWEN_OAUTH_CLIENT_ID` | Qwen (Alibaba) | 公共客户端。 | +| `KIMI_CODING_OAUTH_CLIENT_ID` | Kimi Coding (Moonshot) | 公共客户端。 | +| `ANTIGRAVITY_OAUTH_CLIENT_ID` | Antigravity (Google) | 需要匹配的 `_SECRET`。 | +| `ANTIGRAVITY_OAUTH_CLIENT_SECRET` | Antigravity (Google) | — | +| `GITHUB_OAUTH_CLIENT_ID` | GitHub Copilot | 公共客户端。 | +| `WINDSURF_FIREBASE_API_KEY` | Windsurf / Devin (v3.8) | Windsurf 安全 Token 服务用于刷新的公共 Firebase Web API key。客户端凭证(非密钥)。长期导入 Token 完全跳过此步骤。来源:从 Devin CLI 二进制文件中提取。 | +| `WINDSURF_API_KEY` | Windsurf / Devin (v3.8) | 无每个连接凭证时 `open-sse/executors/devin-cli.ts` 使用的 API key 回退。可选。 | +| `CLI_DEVIN_BIN` | Devin CLI (v3.8) | Devin CLI 二进制文件(`devin`)的自定义路径。由 `open-sse/executors/devin-cli.ts` 解析。 | +| `GITLAB_DUO_OAUTH_CLIENT_ID` | GitLab Duo (v3.8) | GitLab Duo 的 OAuth client ID。在 `https://gitlab.com/-/profile/applications` 注册应用,redirect URI 为 `/callback`,权限域为 `api, read_user, openid, profile, email`。回退到 `GITLAB_OAUTH_CLIENT_ID`。 | +| `GITLAB_DUO_OAUTH_CLIENT_SECRET` | GitLab Duo (v3.8) | GitLab Duo 的 OAuth client secret。可选 — PKCE 流程不需要 secret。回退到 `GITLAB_OAUTH_CLIENT_SECRET`。 | +| `GITLAB_DUO_BASE_URL` | GitLab Duo (v3.8) | 覆盖 GitLab 基础 URL(自托管 GitLab)。默认为 `https://gitlab.com`。回退到 `GITLAB_BASE_URL`。 | +| `GITLAB_BASE_URL` | GitLab Duo (v3.8) | `GITLAB_DUO_BASE_URL` 的旧版回退。在 `_DUO_` 变体未设置时使用。 | +| `GITLAB_OAUTH_CLIENT_ID` | GitLab Duo (v3.8) | `GITLAB_DUO_OAUTH_CLIENT_ID` 的旧版回退,由 `src/lib/oauth/constants/oauth.ts` 使用。 | +| `GITLAB_OAUTH_CLIENT_SECRET` | GitLab Duo (v3.8) | `GITLAB_DUO_OAUTH_CLIENT_SECRET` 的旧版回退,由 `src/lib/oauth/constants/oauth.ts` 使用。 | +| `QODER_OAUTH_CLIENT_SECRET` | Qoder | — | +| `QODER_OAUTH_AUTHORIZE_URL` | Qoder | 设置以启用 Qoder OAuth。 | +| `QODER_OAUTH_TOKEN_URL` | Qoder | — | +| `QODER_OAUTH_USERINFO_URL` | Qoder | — | +| `QODER_OAUTH_CLIENT_ID` | Qoder | — | +| `QODER_PERSONAL_ACCESS_TOKEN` | Qoder | 直接 API key 回退(绕过 OAuth)。 | +| `QODER_CLI_WORKSPACE` | Qoder | Qoder CLI 的 workspace ID。 | +| `OMNIROUTE_QODER_WORKSPACE` | Qoder | `QODER_CLI_WORKSPACE` 的别名。 | +| `BLACKBOX_WEB_VALIDATED_TOKEN` | Blackbox Web | 作为 `validated` 发送到 `/api/chat` 的前端 `tk` Token。当 Blackbox 强制 Token 匹配时必需;否则 OmniRoute 回退到随机 UUID。参阅 issue #2252。 | +| `VISION_BRIDGE_BASE_URL` | Vision Bridge 安全护栏 | 非 Anthropic 视觉桥接调用的 OpenAI 兼容基础 URL。默认为旧版 OpenAI URL 或 api.openai.com。指向 OmniRoute 的 `/v1` 自循环或任意 OpenAI 兼容端点(Gemini OpenAI-compat、OpenRouter)。Issue #2232。 | +| `VISION_BRIDGE_API_KEY` | Vision Bridge 安全护栏 | 上面 URL 的 API key。对于非 Anthropic 视觉桥接调用,覆盖每个服务商的 OpenAI / Google 环境变量。Anthropic 模型保留其专用的 Anthropic key 路径。Issue #2232。 | > [!WARNING] > @@ -478,20 +479,20 @@ process.env[`${PROVIDER_ID}_USER_AGENT`] > **来源:** `open-sse/executors/base.ts` → `buildHeaders()` -| 变量 | 默认值 | 何时更新 | -| --- | --- | --- | -| `CLAUDE_USER_AGENT` | `claude-cli/2.1.207 (external, cli)` | Anthropic 发布新的 CLI 版本时 | -| `CLAUDE_DISABLE_TOOL_NAME_CLOAK` | `false` | `executors/base.ts` + `executors/cliproxyapi.ts` | 设为 `1`/`true` 可将第三方测试工具的工具名称原封不动地转发到 Anthropic 的两条绑定路径上(原生 OAuth 和 CLIProxyAPI)。默认情况下 executor 会将非 Claude Code 的工具名称确定性别名化(Claude Code 存在规范映射的用规范映射,否则用 PascalCase),并通过 `_toolNameMap` 在响应中还原,从而确保带 snake_case 工具的测试工具不会被视为指纹化第三方客户端而被拒绝。仅供调试。 | -| `CODEX_USER_AGENT` | `codex-cli/0.142.0 (Windows 10.0.26200; x64)` | OpenAI 更新 Codex CLI 时 | -| `CODEX_CLIENT_VERSION` | `0.131.0` | 独立于完整 UA 字符串覆盖 Codex 客户端版本 | -| `GITHUB_USER_AGENT` | `GitHubCopilotChat/0.54.0` | GitHub Copilot Chat 更新时 | -| `ANTIGRAVITY_USER_AGENT` | `antigravity/2.0.1 darwin/arm64` | Antigravity IDE 更新时 | -| `KIRO_USER_AGENT` | `AWS-SDK-JS/3.0.0 kiro-ide/1.0.0` | Kiro IDE 更新时 | -| `KIRO_OAUTH_CLIENT_ID` | `kiro-cli` | 覆盖 Kiro social device-code `clientId`(公共 ID) | -| `KIRO_VERIFY_FULL_CRC` | `false` | 启用:在 Kiro 事件流上全帧消息 CRC 校验(调试损坏的流) | -| `QODER_USER_AGENT` | `Qoder-Cli` | Qoder CLI 更新时 | -| `QWEN_USER_AGENT` | `QwenCode/0.19.3 (linux; x64)` | Qwen Code 更新时 | -| `CURSOR_USER_AGENT` | `Cursor/3.3` | Cursor 更新时 | +| 变量 | 默认值 | 何时更新 | +| -------------------------------- | --------------------------------------------- | ------------------------------------------------------- | +| `CLAUDE_USER_AGENT` | `claude-cli/2.1.219 (external, cli)` | Anthropic 发布新的 CLI 版本时 | +| `CLAUDE_DISABLE_TOOL_NAME_CLOAK` | `false` | `executors/base.ts` + `executors/cliproxyapi.ts` | 设为 `1`/`true` 可将第三方测试工具的工具名称原封不动地转发到 Anthropic 的两条绑定路径上(原生 OAuth 和 CLIProxyAPI)。默认情况下 executor 会将非 Claude Code 的工具名称确定性别名化(Claude Code 存在规范映射的用规范映射,否则用 PascalCase),并通过 `_toolNameMap` 在响应中还原,从而确保带 snake_case 工具的测试工具不会被视为指纹化第三方客户端而被拒绝。仅供调试。 | +| `CODEX_USER_AGENT` | `codex-cli/0.142.0 (Windows 10.0.26200; x64)` | OpenAI 更新 Codex CLI 时 | +| `CODEX_CLIENT_VERSION` | `0.131.0` | 独立于完整 UA 字符串覆盖 Codex 客户端版本 | +| `GITHUB_USER_AGENT` | `GitHubCopilotChat/0.54.0` | GitHub Copilot Chat 更新时 | +| `ANTIGRAVITY_USER_AGENT` | `antigravity/2.0.1 darwin/arm64` | Antigravity IDE 更新时 | +| `KIRO_USER_AGENT` | `AWS-SDK-JS/3.0.0 kiro-ide/1.0.0` | Kiro IDE 更新时 | +| `KIRO_OAUTH_CLIENT_ID` | `kiro-cli` | 覆盖 Kiro social device-code `clientId`(公共 ID) | +| `KIRO_VERIFY_FULL_CRC` | `false` | 启用:在 Kiro 事件流上全帧消息 CRC 校验(调试损坏的流) | +| `QODER_USER_AGENT` | `Qoder-Cli` | Qoder CLI 更新时 | +| `QWEN_USER_AGENT` | `QwenCode/0.19.3 (linux; x64)` | Qwen Code 更新时 | +| `CURSOR_USER_AGENT` | `Cursor/3.3` | Cursor 更新时 | > [!TIP] > 你可以通过 `{PROVIDER_ID}_USER_AGENT` 模式为 **任意** 服务商添加 User-Agent 覆盖。Executor 会动态构建环境变量名。 @@ -506,30 +507,30 @@ process.env[`${PROVIDER_ID}_USER_AGENT`] ### 按服务商 -| 变量 | 激活方式 | 效果 | -| --- | --- | --- | -| `CLI_COMPAT_CODEX` | `=1` | 模拟 Codex CLI 请求签名 | -| `CLI_COMPAT_CLAUDE` | `=1` | 模拟 Claude Code 请求签名 | -| `CLI_COMPAT_GITHUB` | `=1` | 模拟 GitHub Copilot 请求签名 | -| `CLI_COMPAT_ANTIGRAVITY` | `=1` | 模拟 Antigravity 请求签名 | -| `CLI_COMPAT_CURSOR` | `=1` | 模拟 Cursor 请求签名 | -| `CLI_COMPAT_KIMI_CODING` | `=1` | 模拟 Kimi Coding 请求签名 | -| `CLI_COMPAT_KILOCODE` | `=1` | 模拟 Kilo Code 请求签名 | -| `CLI_COMPAT_CLINE` | `=1` | 模拟 Cline 请求签名 | -| `CLI_COMPAT_QWEN` | `=1` | 模拟 Qwen Code 请求签名 | +| 变量 | 激活方式 | 效果 | +| ------------------------ | -------- | ---------------------------- | +| `CLI_COMPAT_CODEX` | `=1` | 模拟 Codex CLI 请求签名 | +| `CLI_COMPAT_CLAUDE` | `=1` | 模拟 Claude Code 请求签名 | +| `CLI_COMPAT_GITHUB` | `=1` | 模拟 GitHub Copilot 请求签名 | +| `CLI_COMPAT_ANTIGRAVITY` | `=1` | 模拟 Antigravity 请求签名 | +| `CLI_COMPAT_CURSOR` | `=1` | 模拟 Cursor 请求签名 | +| `CLI_COMPAT_KIMI_CODING` | `=1` | 模拟 Kimi Coding 请求签名 | +| `CLI_COMPAT_KILOCODE` | `=1` | 模拟 Kilo Code 请求签名 | +| `CLI_COMPAT_CLINE` | `=1` | 模拟 Cline 请求签名 | +| `CLI_COMPAT_QWEN` | `=1` | 模拟 Qwen Code 请求签名 | ### 全局 -| 变量 | 激活方式 | 效果 | -| --- | --- | --- | -| `CLI_COMPAT_ALL` | `=1` | 一次性为**所有**服务商启用指纹兼容。 | +| 变量 | 激活方式 | 效果 | +| ---------------- | -------- | ------------------------------------ | +| `CLI_COMPAT_ALL` | `=1` | 一次性为**所有**服务商启用指纹兼容。 | ### Kimi Coding CLI 身份标识覆盖 -| 变量 | 默认值 | 源文件 | 说明 | -| --- | --- | --- | --- | -| `KIMI_CLI_VERSION` | `1.36.0` | `src/lib/oauth/providers/kimi-coding.ts` | 覆盖 OAuth/API 调用时发送的 Kimi CLI 版本。 | -| `KIMI_CODING_DEVICE_ID` | _(已捕获的默认值)_ | `src/lib/oauth/providers/kimi-coding.ts` | 覆盖客户端头中使用的已捕获 Kimi 设备 ID。 | +| 变量 | 默认值 | 源文件 | 说明 | +| ----------------------- | ------------------ | ---------------------------------------- | ------------------------------------------- | +| `KIMI_CLI_VERSION` | `1.36.0` | `src/lib/oauth/providers/kimi-coding.ts` | 覆盖 OAuth/API 调用时发送的 Kimi CLI 版本。 | +| `KIMI_CODING_DEVICE_ID` | _(已捕获的默认值)_ | `src/lib/oauth/providers/kimi-coding.ts` | 覆盖客户端头中使用的已捕获 Kimi 设备 ID。 | > [!NOTE] > 此功能与 User-Agent 覆盖(§12)协同工作。指纹系统处理头部排序和 body 字段排序,User-Agent 覆盖处理具体的 UA 字符串。两者可独立启用。 @@ -544,10 +545,10 @@ process.env[`${PROVIDER_ID}_USER_AGENT`] 识别模式:`{PROVIDER_ID}_API_KEY` -| 变量 | 服务商 | -| --- | --- | -| `DEEPSEEK_API_KEY` | DeepSeek | -| `NVIDIA_API_KEY` | NVIDIA NIM | +| 变量 | 服务商 | +| ------------------ | ---------- | +| `DEEPSEEK_API_KEY` | DeepSeek | +| `NVIDIA_API_KEY` | NVIDIA NIM | > [!NOTE] > 在 v3.8.0 中移除了 Groq、xAI、Mistral、Perplexity、Together AI、Fireworks、Cerebras、Cohere、Nebius 和 Qianfan 的静态 `${PROVIDER}_API_KEY` 条目,因为运行时不再读取它们 — 这些服务商仅通过 Dashboard / `data/provider-credentials.json` / 加密数据库获取凭证。参阅本文档末尾的 _审计:已移除/废弃的变量_ 部分了解迁移路径。 @@ -580,36 +581,36 @@ REQUEST_TIMEOUT_MS (全局覆盖) └── API_BRIDGE_SERVER_SOCKET_TIMEOUT_MS (默认值: 0 = 禁用) ``` -| 变量 | 默认值 | 说明 | -| --- | --- | --- | -| `REQUEST_TIMEOUT_MS` | _(未设置)_ | 全局快捷方式 — 覆盖 `FETCH_TIMEOUT_MS` 和 `STREAM_IDLE_TIMEOUT_MS` 两者的默认值。 | -| `FETCH_TIMEOUT_MS` | `600000` | 上游服务商调用的 HTTP 请求总超时。 | -| `STREAM_IDLE_TIMEOUT_MS` | `600000` | SSE 块之间的最长静默时间,超时则中止。扩展推理模型很少暂停超过 90 秒。 | -| `STREAM_READINESS_TIMEOUT_MS` | `80000` | 接收第一个非 ping SSE 事件的超时时间。设置时继承 `REQUEST_TIMEOUT_MS`。 | -| `OMNIROUTE_CODEX_DROP_NONSTANDARD_EVENTS` | _(关闭)_ | 剥离会导致 OpenAI SDK 的 `responses.stream()` 以 502 报错的非标准 `codex.*` SSE 事件(如 `codex.rate_limits`)。设为 `true`/`1`/`yes` 可启用。 | -| `FETCH_HEADERS_TIMEOUT_MS` | = `FETCH_TIMEOUT_MS` | 接收响应头的超时时间。 | -| `FETCH_BODY_TIMEOUT_MS` | = `FETCH_TIMEOUT_MS` | 接收完整响应体的超时时间。 | -| `FETCH_CONNECT_TIMEOUT_MS` | `30000` | TCP 连接建立超时。 | -| `FETCH_KEEPALIVE_TIMEOUT_MS` | `4000` | Keep-alive 套接字空闲超时。 | -| `TLS_CLIENT_TIMEOUT_MS` | = `FETCH_TIMEOUT_MS` | TLS 指纹代理(wreq-js)超时。 | -| `API_BRIDGE_PROXY_TIMEOUT_MS` | `30000` | `/v1` 桥接请求的代理跳超时。 | -| `API_BRIDGE_SERVER_REQUEST_TIMEOUT_MS` | `300000` | 桥接的服务器请求总超时。 | -| `API_BRIDGE_SERVER_HEADERS_TIMEOUT_MS` | `60000` | 通过桥接发送响应头的超时时间。 | -| `API_BRIDGE_SERVER_KEEPALIVE_TIMEOUT_MS` | `5000` | 桥接 keep-alive 空闲超时。 | -| `API_BRIDGE_SERVER_SOCKET_TIMEOUT_MS` | `0` | 原始套接字超时(0 = 禁用)。 | -| `SHUTDOWN_TIMEOUT_MS` | `30000` | SIGTERM/SIGINT 后强制退出前的宽限期。 | -| `OMNIROUTE_DEFAULT_FETCH_TIMEOUT_MS` | `120000` | `FETCH_TIMEOUT_MS` 未设置时 `src/shared/utils/fetchTimeout.ts` 使用的回退值。 | -| `OMNIROUTE_CHATGPT_TLS_TIMEOUT_MS` | `60000` | bogdanfinn/tls-client koffi 绑定的线路级超时(`chatgptTlsClient.ts`)。 | -| `OMNIROUTE_CHATGPT_TLS_GRACE_MS` | `10000` | 原生绑定卡住时在线路超时之上添加的 JS 侧宽恕时间。 | -| `OMNIROUTE_CHATGPT_STREAM_FIRST_BYTE_TIMEOUT_MS` | `30000`(30 秒) | ChatGPT TLS sidecar(`chatgptTlsClient.ts`)在中止死流前等待第一个流式字节的最大时间。如果上游冷启动超过窗口则提高。 | -| `OMNIROUTE_CLAUDE_TLS_TIMEOUT_MS` | `60000` | bogdanfinn/tls-client koffi 绑定的线路级超时(`claudeTlsClient.ts`)。 | -| `OMNIROUTE_CLAUDE_TLS_GRACE_MS` | `10000` | 原生绑定卡住时在线路超时之上添加的 JS 侧宽恕时间。 | -| `OMNIROUTE_PPLX_TLS_TIMEOUT_MS` | `30000` | bogdanfinn/tls-client koffi 绑定的线路级超时(`perplexityTlsClient.ts`)。 | -| `OMNIROUTE_PPLX_TLS_GRACE_MS` | `10000` | 原生绑定卡住时在线路超时之上添加的 JS 侧宽恕时间。 | -| `OMNIROUTE_GROK_TLS_TIMEOUT_MS` | `60000` | bogdanfinn/tls-client koffi 绑定的线路级超时(`grokTlsClient.ts`)。 | -| `OMNIROUTE_GROK_TLS_GRACE_MS` | `10000` | 原生绑定卡住时在线路超时之上添加的 JS 侧宽恕时间。 | -| `OMNIROUTE_BROWSER_POOL` | `on` | 用于浏览器端 Web Cookie 聊天的共享 Playwright 浏览器池(`browserPool.ts`);设为 `off` 可禁用。 | -| `WEB_COOKIE_USE_BROWSER` | `0` | 将 Web Cookie 聊天请求选择进入浏览器端路径(`browserBackedChat.ts`);`1` 启用。 | +| 变量 | 默认值 | 说明 | +| ------------------------------------------------ | -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | +| `REQUEST_TIMEOUT_MS` | _(未设置)_ | 全局快捷方式 — 覆盖 `FETCH_TIMEOUT_MS` 和 `STREAM_IDLE_TIMEOUT_MS` 两者的默认值。 | +| `FETCH_TIMEOUT_MS` | `600000` | 上游服务商调用的 HTTP 请求总超时。 | +| `STREAM_IDLE_TIMEOUT_MS` | `600000` | SSE 块之间的最长静默时间,超时则中止。扩展推理模型很少暂停超过 90 秒。 | +| `STREAM_READINESS_TIMEOUT_MS` | `80000` | 接收第一个非 ping SSE 事件的超时时间。设置时继承 `REQUEST_TIMEOUT_MS`。 | +| `OMNIROUTE_CODEX_DROP_NONSTANDARD_EVENTS` | _(关闭)_ | 剥离会导致 OpenAI SDK 的 `responses.stream()` 以 502 报错的非标准 `codex.*` SSE 事件(如 `codex.rate_limits`)。设为 `true`/`1`/`yes` 可启用。 | +| `FETCH_HEADERS_TIMEOUT_MS` | = `FETCH_TIMEOUT_MS` | 接收响应头的超时时间。 | +| `FETCH_BODY_TIMEOUT_MS` | = `FETCH_TIMEOUT_MS` | 接收完整响应体的超时时间。 | +| `FETCH_CONNECT_TIMEOUT_MS` | `30000` | TCP 连接建立超时。 | +| `FETCH_KEEPALIVE_TIMEOUT_MS` | `4000` | Keep-alive 套接字空闲超时。 | +| `TLS_CLIENT_TIMEOUT_MS` | = `FETCH_TIMEOUT_MS` | TLS 指纹代理(wreq-js)超时。 | +| `API_BRIDGE_PROXY_TIMEOUT_MS` | `30000` | `/v1` 桥接请求的代理跳超时。 | +| `API_BRIDGE_SERVER_REQUEST_TIMEOUT_MS` | `300000` | 桥接的服务器请求总超时。 | +| `API_BRIDGE_SERVER_HEADERS_TIMEOUT_MS` | `60000` | 通过桥接发送响应头的超时时间。 | +| `API_BRIDGE_SERVER_KEEPALIVE_TIMEOUT_MS` | `5000` | 桥接 keep-alive 空闲超时。 | +| `API_BRIDGE_SERVER_SOCKET_TIMEOUT_MS` | `0` | 原始套接字超时(0 = 禁用)。 | +| `SHUTDOWN_TIMEOUT_MS` | `30000` | SIGTERM/SIGINT 后强制退出前的宽限期。 | +| `OMNIROUTE_DEFAULT_FETCH_TIMEOUT_MS` | `120000` | `FETCH_TIMEOUT_MS` 未设置时 `src/shared/utils/fetchTimeout.ts` 使用的回退值。 | +| `OMNIROUTE_CHATGPT_TLS_TIMEOUT_MS` | `60000` | bogdanfinn/tls-client koffi 绑定的线路级超时(`chatgptTlsClient.ts`)。 | +| `OMNIROUTE_CHATGPT_TLS_GRACE_MS` | `10000` | 原生绑定卡住时在线路超时之上添加的 JS 侧宽恕时间。 | +| `OMNIROUTE_CHATGPT_STREAM_FIRST_BYTE_TIMEOUT_MS` | `30000`(30 秒) | ChatGPT TLS sidecar(`chatgptTlsClient.ts`)在中止死流前等待第一个流式字节的最大时间。如果上游冷启动超过窗口则提高。 | +| `OMNIROUTE_CLAUDE_TLS_TIMEOUT_MS` | `60000` | bogdanfinn/tls-client koffi 绑定的线路级超时(`claudeTlsClient.ts`)。 | +| `OMNIROUTE_CLAUDE_TLS_GRACE_MS` | `10000` | 原生绑定卡住时在线路超时之上添加的 JS 侧宽恕时间。 | +| `OMNIROUTE_PPLX_TLS_TIMEOUT_MS` | `30000` | bogdanfinn/tls-client koffi 绑定的线路级超时(`perplexityTlsClient.ts`)。 | +| `OMNIROUTE_PPLX_TLS_GRACE_MS` | `10000` | 原生绑定卡住时在线路超时之上添加的 JS 侧宽恕时间。 | +| `OMNIROUTE_GROK_TLS_TIMEOUT_MS` | `60000` | bogdanfinn/tls-client koffi 绑定的线路级超时(`grokTlsClient.ts`)。 | +| `OMNIROUTE_GROK_TLS_GRACE_MS` | `10000` | 原生绑定卡住时在线路超时之上添加的 JS 侧宽恕时间。 | +| `OMNIROUTE_BROWSER_POOL` | `on` | 用于浏览器端 Web Cookie 聊天的共享 Playwright 浏览器池(`browserPool.ts`);设为 `off` 可禁用。 | +| `WEB_COOKIE_USE_BROWSER` | `0` | 将 Web Cookie 聊天请求选择进入浏览器端路径(`browserBackedChat.ts`);`1` 启用。 | Combo 目标尝试继承已解析的上游请求超时(`FETCH_TIMEOUT_MS`,或当它提供 fetch 默认值时的 `REQUEST_TIMEOUT_MS`)。仅在 Combo 中设置 `targetTimeoutMs`、Combo 默认值或服务商覆盖值以加快 Combo 回退;超过当前上游超时的值会被截断到上游超时。 @@ -617,24 +618,24 @@ Combo 目标尝试继承已解析的上游请求超时(`FETCH_TIMEOUT_MS`, 服务商级熔断器调优。默认值反映了 v3.6 以来用于 500+ 连接的缩放值。 -| 变量 | 默认值 | 源文件 | 说明 | -| --- | --- | --- | --- | -| `OMNIROUTE_CIRCUIT_BREAKER_OAUTH_THRESHOLD` | `8` | `open-sse/config/constants.ts` | OAuth 服务商的连续失败阈值,超过则熔断器跳开。 | -| `OMNIROUTE_CIRCUIT_BREAKER_OAUTH_RESET_MS` | `60000` | `open-sse/config/constants.ts` | OAuth 服务商熔断器的重置窗口(毫秒)。 | -| `OMNIROUTE_CIRCUIT_BREAKER_API_KEY_THRESHOLD` | `12` | `open-sse/config/constants.ts` | API-key 服务商的连续失败阈值。 | -| `OMNIROUTE_CIRCUIT_BREAKER_API_KEY_RESET_MS` | `30000` | `open-sse/config/constants.ts` | API-key 服务商熔断器的重置窗口(毫秒)。 | -| `OMNIROUTE_CIRCUIT_BREAKER_LOCAL_THRESHOLD` | `2` | `open-sse/config/constants.ts` | 本地服务商(Ollama、LM Studio 等)的连续失败阈值。 | -| `OMNIROUTE_CIRCUIT_BREAKER_LOCAL_RESET_MS` | `15000` | `open-sse/config/constants.ts` | 本地服务商熔断器的重置窗口(毫秒)。 | -| `PIN_DROP_BACKOFF_LEVEL` | `2` | `open-sse/services/combo.ts` | 退避深度,达到此值后上下文缓存 pin 的服务商被视为持续不健康,pin 被丢弃以进行故障转移。 | -| `PIN_DROP_GRACE_MS` | `20000` | `open-sse/services/combo.ts` | 防抖窗口(毫秒),在丢弃上下文缓存 pin 之前容忍短暂的瞬态冷却。 | +| 变量 | 默认值 | 源文件 | 说明 | +| --------------------------------------------- | ------- | ------------------------------ | --------------------------------------------------------------------------------------- | +| `OMNIROUTE_CIRCUIT_BREAKER_OAUTH_THRESHOLD` | `8` | `open-sse/config/constants.ts` | OAuth 服务商的连续失败阈值,超过则熔断器跳开。 | +| `OMNIROUTE_CIRCUIT_BREAKER_OAUTH_RESET_MS` | `60000` | `open-sse/config/constants.ts` | OAuth 服务商熔断器的重置窗口(毫秒)。 | +| `OMNIROUTE_CIRCUIT_BREAKER_API_KEY_THRESHOLD` | `12` | `open-sse/config/constants.ts` | API-key 服务商的连续失败阈值。 | +| `OMNIROUTE_CIRCUIT_BREAKER_API_KEY_RESET_MS` | `30000` | `open-sse/config/constants.ts` | API-key 服务商熔断器的重置窗口(毫秒)。 | +| `OMNIROUTE_CIRCUIT_BREAKER_LOCAL_THRESHOLD` | `2` | `open-sse/config/constants.ts` | 本地服务商(Ollama、LM Studio 等)的连续失败阈值。 | +| `OMNIROUTE_CIRCUIT_BREAKER_LOCAL_RESET_MS` | `15000` | `open-sse/config/constants.ts` | 本地服务商熔断器的重置窗口(毫秒)。 | +| `PIN_DROP_BACKOFF_LEVEL` | `2` | `open-sse/services/combo.ts` | 退避深度,达到此值后上下文缓存 pin 的服务商被视为持续不健康,pin 被丢弃以进行故障转移。 | +| `PIN_DROP_GRACE_MS` | `20000` | `open-sse/services/combo.ts` | 防抖窗口(毫秒),在丢弃上下文缓存 pin 之前容忍短暂的瞬态冷却。 | ### 场景 -| 场景 | 配置 | -| --- | --- | -| **长时间代码生成** | `REQUEST_TIMEOUT_MS=900000`(15 分钟) | -| **生产 API 快速失败** | `API_BRIDGE_PROXY_TIMEOUT_MS=10000` | -| **扩展推理模型** | `STREAM_IDLE_TIMEOUT_MS=300000`(块间 5 分钟) | +| 场景 | 配置 | +| --------------------- | ---------------------------------------------- | +| **长时间代码生成** | `REQUEST_TIMEOUT_MS=900000`(15 分钟) | +| **生产 API 快速失败** | `API_BRIDGE_PROXY_TIMEOUT_MS=10000` | +| **扩展推理模型** | `STREAM_IDLE_TIMEOUT_MS=300000`(块间 5 分钟) | --- @@ -642,65 +643,65 @@ Combo 目标尝试继承已解析的上游请求超时(`FETCH_TIMEOUT_MS`, 日志系统同时写入 stdout 和轮转日志文件。所有配置由 `src/lib/logEnv.ts` 读取。 -| 变量 | 默认值 | 说明 | -| --- | --- | --- | -| `APP_LOG_LEVEL` | `info` | 最低日志级别:`debug`、`info`、`warn`、`error`。 | -| `APP_LOG_FORMAT` | `text` | 输出格式:`text`(人类可读)或 `json`(结构化)。 | -| `APP_LOG_TO_FILE` | `true` | 同时写入日志文件和 stdout。 | -| `APP_LOG_FILE_PATH` | `logs/application/app.log` | 日志文件路径(相对于项目根目录或 `DATA_DIR`)。 | -| `APP_LOG_MAX_FILE_SIZE` | `50M` | 轮转前的最大文件大小。可接受:`50M`、`1G`、`512K` 或纯字节数。 | -| `APP_LOG_RETENTION_DAYS` | `7` | 保留轮转后应用日志文件的天数。 | -| `APP_LOG_MAX_FILES` | `20` | 最大轮转日志文件备份数。 | -| `CALL_LOG_RETENTION_DAYS` | `7` | 在数据库中保留请求/调用日志条目的天数。 | -| `CALL_LOG_MAX_ENTRIES` | `10000` | 内存缓冲区中最大调用日志条目数。 | -| `CALL_LOGS_TABLE_MAX_ROWS` | `100000` | `call_logs` SQLite 表在清理前的最大行数。 | -| `MAX_PENDING_REQUEST_AGE_MS` | `3600000`(1 小时) | 孤立活跃请求日志条目在内存清理前的最大生存时间。 | -| `CALL_LOG_PIPELINE_CAPTURE_STREAM_CHUNKS` | `true` | 当 `call_log_pipeline_enabled=true` 时在 pipeline artifacts 中存储流块。 | -| `CALL_LOG_PIPELINE_MAX_SIZE_KB` | `512` | 当 `call_log_pipeline_enabled=true` 时的最大 pipeline 调用日志工件大小(KB)。 | -| `PROXY_LOGS_TABLE_MAX_ROWS` | `100000` | `proxy_logs` SQLite 表在清理前的最大行数。 | -| `APP_LOG_ROTATION_CHECK_INTERVAL_MS` | `60000`(1 分钟) | `src/lib/logRotation.ts` 重新检查活跃日志文件大小的频率。 | -| `CHAT_LOG_TEXT_LIMIT` | `65536` | 聊天日志工件中保留的最大字符串长度(默认 64 KB)。 | -| `CHAT_LOG_ARRAY_TAIL_ITEMS` | `24` | 截断聊天日志载荷时从尾部保留的数组项数量。 | -| `CHAT_LOG_MAX_DEPTH` | `6` | 聊天日志载荷被截断前的最大嵌套深度。 | -| `CHAT_DEBUG_FILE` | `false` | 设为 true 时,`serializeArtifactForStorage` 跳过基于大小的截断。仅供调试。 | +| 变量 | 默认值 | 说明 | +| ----------------------------------------- | -------------------------- | ------------------------------------------------------------------------------ | +| `APP_LOG_LEVEL` | `info` | 最低日志级别:`debug`、`info`、`warn`、`error`。 | +| `APP_LOG_FORMAT` | `text` | 输出格式:`text`(人类可读)或 `json`(结构化)。 | +| `APP_LOG_TO_FILE` | `true` | 同时写入日志文件和 stdout。 | +| `APP_LOG_FILE_PATH` | `logs/application/app.log` | 日志文件路径(相对于项目根目录或 `DATA_DIR`)。 | +| `APP_LOG_MAX_FILE_SIZE` | `50M` | 轮转前的最大文件大小。可接受:`50M`、`1G`、`512K` 或纯字节数。 | +| `APP_LOG_RETENTION_DAYS` | `7` | 保留轮转后应用日志文件的天数。 | +| `APP_LOG_MAX_FILES` | `20` | 最大轮转日志文件备份数。 | +| `CALL_LOG_RETENTION_DAYS` | `7` | 在数据库中保留请求/调用日志条目的天数。 | +| `CALL_LOG_MAX_ENTRIES` | `10000` | 内存缓冲区中最大调用日志条目数。 | +| `CALL_LOGS_TABLE_MAX_ROWS` | `100000` | `call_logs` SQLite 表在清理前的最大行数。 | +| `MAX_PENDING_REQUEST_AGE_MS` | `3600000`(1 小时) | 孤立活跃请求日志条目在内存清理前的最大生存时间。 | +| `CALL_LOG_PIPELINE_CAPTURE_STREAM_CHUNKS` | `true` | 当 `call_log_pipeline_enabled=true` 时在 pipeline artifacts 中存储流块。 | +| `CALL_LOG_PIPELINE_MAX_SIZE_KB` | `512` | 当 `call_log_pipeline_enabled=true` 时的最大 pipeline 调用日志工件大小(KB)。 | +| `PROXY_LOGS_TABLE_MAX_ROWS` | `100000` | `proxy_logs` SQLite 表在清理前的最大行数。 | +| `APP_LOG_ROTATION_CHECK_INTERVAL_MS` | `60000`(1 分钟) | `src/lib/logRotation.ts` 重新检查活跃日志文件大小的频率。 | +| `CHAT_LOG_TEXT_LIMIT` | `65536` | 聊天日志工件中保留的最大字符串长度(默认 64 KB)。 | +| `CHAT_LOG_ARRAY_TAIL_ITEMS` | `24` | 截断聊天日志载荷时从尾部保留的数组项数量。 | +| `CHAT_LOG_MAX_DEPTH` | `6` | 聊天日志载荷被截断前的最大嵌套深度。 | +| `CHAT_DEBUG_FILE` | `false` | 设为 true 时,`serializeArtifactForStorage` 跳过基于大小的截断。仅供调试。 | --- ## 17. 内存优化 -| 变量 | 默认值 | 说明 | -| --- | --- | --- | -| `OMNIROUTE_MEMORY_MB` | _自动_ | 运行时 V8 堆限制(MB)。未设置时动态校准(约 35% 系统内存,限制在 `[512, 4096]`);`512` 仅是总内存不可读取时的下限。显式设置以覆盖。Docker 独立运行和 `omniroute serve` 用它设置 `--max-old-space-size`。 | -| `PROMPT_CACHE_MAX_SIZE` | `50` | 最大缓存系统提示条目数。 | -| `PROMPT_CACHE_MAX_BYTES` | `2097152`(2 MB) | 最大提示缓存总大小。 | -| `PROMPT_CACHE_TTL_MS` | `300000`(5 分钟) | 提示缓存条目 TTL。 | -| `SEMANTIC_CACHE_MAX_SIZE` | `100` | 最大缓存 temperature=0 响应数。 | -| `SEMANTIC_CACHE_MAX_BYTES` | `4194304`(4 MB) | 最大语义缓存总大小。 | -| `SEMANTIC_CACHE_TTL_MS` | `1800000`(30 分钟) | 语义缓存条目 TTL。 | -| `STREAM_HISTORY_MAX` | `50` | Dashboard 实时视图缓冲区中最大近期流事件数。 | -| `CONTEXT_LENGTH_DEFAULT` | `128000` | 没有显式配置的模型的全局回退最大上下文长度。 | -| `USAGE_TOKEN_BUFFER` | `100` | 跟踪用量配额时保留的额外 Token 余量。 | +| 变量 | 默认值 | 说明 | +| -------------------------- | -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `OMNIROUTE_MEMORY_MB` | _自动_ | 运行时 V8 堆限制(MB)。未设置时动态校准(约 35% 系统内存,限制在 `[512, 4096]`);`512` 仅是总内存不可读取时的下限。显式设置以覆盖。Docker 独立运行和 `omniroute serve` 用它设置 `--max-old-space-size`。 | +| `PROMPT_CACHE_MAX_SIZE` | `50` | 最大缓存系统提示条目数。 | +| `PROMPT_CACHE_MAX_BYTES` | `2097152`(2 MB) | 最大提示缓存总大小。 | +| `PROMPT_CACHE_TTL_MS` | `300000`(5 分钟) | 提示缓存条目 TTL。 | +| `SEMANTIC_CACHE_MAX_SIZE` | `100` | 最大缓存 temperature=0 响应数。 | +| `SEMANTIC_CACHE_MAX_BYTES` | `4194304`(4 MB) | 最大语义缓存总大小。 | +| `SEMANTIC_CACHE_TTL_MS` | `1800000`(30 分钟) | 语义缓存条目 TTL。 | +| `STREAM_HISTORY_MAX` | `50` | Dashboard 实时视图缓冲区中最大近期流事件数。 | +| `CONTEXT_LENGTH_DEFAULT` | `128000` | 没有显式配置的模型的全局回退最大上下文长度。 | +| `USAGE_TOKEN_BUFFER` | `100` | 跟踪用量配额时保留的额外 Token 余量。 | ### 压缩 -| 变量 | 默认值 | 说明 | -| --- | --- | --- | +| 变量 | 默认值 | 说明 | +| ------------------------------------- | ------ | --------------------------------------------------------------------------------------- | | `OMNIROUTE_RTK_TRUST_PROJECT_FILTERS` | 未设置 | 无需 `.rtk/trust.json` 哈希即可信任项目 `.rtk/filters.json`。仅在受控的本地开发中使用。 | ### 记忆引擎(plan 21) 持久记忆子系统(`src/lib/memory/`)的嵌入层、向量存储和重排序开关。 -| 变量 | 默认值 | 说明 | -| --- | --- | --- | -| `MEMORY_EMBEDDING_CACHE_TTL_MS` | `300000`(5 分钟) | 内存嵌入缓存(每个源/模型/维度签名)的 TTL。 | -| `MEMORY_EMBEDDING_CACHE_MAX` | `1000` | 嵌入缓存中保留的最大 LRU 条目数。 | -| `MEMORY_TRANSFORMERS_MODEL` | `Xenova/all-MiniLM-L6-v2` | 用于可选的 `@huggingface/transformers` 本地 MiniLM 管线的 HF 仓库 ID(约 23 MB int8,约 400 MB RAM)。 | -| `MEMORY_STATIC_MODEL` | `minishlab/potion-base-8M` | 用于静态 potion/Model2Vec 查找表嵌入器的 HF 仓库 ID。懒加载下载到缓存目录。 | -| `MEMORY_STATIC_CACHE_DIR` | `/embeddings` | 用于缓存静态 potion 模型文件的目录。未设置时默认为 `DATA_DIR` 下。 | -| `MEMORY_VEC_TOP_K` | `20` | `src/lib/memory/vectorStore.ts` 内部 `sqlite-vec` 暴力向量搜索使用的默认 top-K。 | -| `MEMORY_RRF_K` | `60` | FTS5 + 向量混合检索的 Reciprocal Rank Fusion 常数 `k`(sqlite-vec 方案)。 | -| `HF_HUB_ENDPOINT` | `https://huggingface.co` | 覆盖 `staticPotion.ts` 使用的 Hugging Face Hub 基础 URL(如气隙环境下的镜像端点)。 | +| 变量 | 默认值 | 说明 | +| ------------------------------- | -------------------------- | ------------------------------------------------------------------------------------------------------ | +| `MEMORY_EMBEDDING_CACHE_TTL_MS` | `300000`(5 分钟) | 内存嵌入缓存(每个源/模型/维度签名)的 TTL。 | +| `MEMORY_EMBEDDING_CACHE_MAX` | `1000` | 嵌入缓存中保留的最大 LRU 条目数。 | +| `MEMORY_TRANSFORMERS_MODEL` | `Xenova/all-MiniLM-L6-v2` | 用于可选的 `@huggingface/transformers` 本地 MiniLM 管线的 HF 仓库 ID(约 23 MB int8,约 400 MB RAM)。 | +| `MEMORY_STATIC_MODEL` | `minishlab/potion-base-8M` | 用于静态 potion/Model2Vec 查找表嵌入器的 HF 仓库 ID。懒加载下载到缓存目录。 | +| `MEMORY_STATIC_CACHE_DIR` | `/embeddings` | 用于缓存静态 potion 模型文件的目录。未设置时默认为 `DATA_DIR` 下。 | +| `MEMORY_VEC_TOP_K` | `20` | `src/lib/memory/vectorStore.ts` 内部 `sqlite-vec` 暴力向量搜索使用的默认 top-K。 | +| `MEMORY_RRF_K` | `60` | FTS5 + 向量混合检索的 Reciprocal Rank Fusion 常数 `k`(sqlite-vec 方案)。 | +| `HF_HUB_ENDPOINT` | `https://huggingface.co` | 覆盖 `staticPotion.ts` 使用的 Hugging Face Hub 基础 URL(如气隙环境下的镜像端点)。 | ### 低内存 Docker 示例 @@ -719,60 +720,60 @@ STREAM_HISTORY_MAX=10 从外部源自动同步模型价格数据。 -| 变量 | 默认值 | 源文件 | 说明 | -| --- | --- | --- | --- | -| `PRICING_SYNC_ENABLED` | `false` | `src/lib/pricingSync.ts` | 可选的定期价格同步。 | -| `PRICING_SYNC_INTERVAL` | `86400`(24 小时) | `src/lib/pricingSync.ts` | 同步间隔,秒。 | -| `PRICING_SYNC_SOURCES` | `litellm` | `src/lib/pricingSync.ts` | 逗号分隔的数据源。 | +| 变量 | 默认值 | 源文件 | 说明 | +| ----------------------- | ------------------ | ------------------------ | -------------------- | +| `PRICING_SYNC_ENABLED` | `false` | `src/lib/pricingSync.ts` | 可选的定期价格同步。 | +| `PRICING_SYNC_INTERVAL` | `86400`(24 小时) | `src/lib/pricingSync.ts` | 同步间隔,秒。 | +| `PRICING_SYNC_SOURCES` | `litellm` | `src/lib/pricingSync.ts` | 逗号分隔的数据源。 | --- ## Arena ELO 同步 -| 变量 | 默认值 | 源文件 | 说明 | -| --- | --- | --- | --- | -| `ARENA_ELO_SYNC_ENABLED` | `true` | `src/shared/constants/featureFlagDefinitions.ts` | 定期 Arena AI 排行榜 ELO 同步,可从 Dashboard Feature Flags 配置或设为 `false` 退出。 | -| `ARENA_ELO_SYNC_INTERVAL` | `86400`(24 小时) | `src/lib/arenaEloSync.ts` | 同步间隔,秒。 | +| 变量 | 默认值 | 源文件 | 说明 | +| ------------------------- | ------------------ | ------------------------------------------------ | ------------------------------------------------------------------------------------- | +| `ARENA_ELO_SYNC_ENABLED` | `true` | `src/shared/constants/featureFlagDefinitions.ts` | 定期 Arena AI 排行榜 ELO 同步,可从 Dashboard Feature Flags 配置或设为 `false` 退出。 | +| `ARENA_ELO_SYNC_INTERVAL` | `86400`(24 小时) | `src/lib/arenaEloSync.ts` | 同步间隔,秒。 | --- ## 19. 模型同步(开发) -| 变量 | 默认值 | 源文件 | 说明 | -| --- | --- | --- | --- | +| 变量 | 默认值 | 源文件 | 说明 | +| -------------------------- | ------------------ | -------------------------- | ------------------------------ | | `MODELS_DEV_SYNC_INTERVAL` | `86400`(24 小时) | `src/lib/modelsDevSync.ts` | 开发时的模型目錄同步间隔,秒。 | --- ## 20. 服务商特定设置 -| 变量 | 默认值 | 源文件 | 说明 | -| --- | --- | --- | --- | -| `OPENROUTER_CATALOG_TTL_MS` | `86400000`(24 小时) | `src/lib/catalog/openrouterCatalog.ts` | OpenRouter 模型目錄缓存 TTL。 | -| `MODEL_CATALOG_INCLUDE_NAMES` | `true` | `src/shared/constants/featureFlagDefinitions.ts` | 在 `/v1/models` 响应中包含显示友好的 `name` 字段。对于只需要 ID 的客户端可禁用。 | -| `NANOBANANA_POLL_TIMEOUT_MS` | `120000` | `open-sse/handlers/imageGeneration.ts` | NanoBanana 图片生成任务的最大等待时间。 | -| `NANOBANANA_POLL_INTERVAL_MS` | `2500` | `open-sse/handlers/imageGeneration.ts` | NanoBanana 任务轮询频率。 | -| `AWS_REGION` | _(未设置)_ | `src/lib/providers/validation.ts`, `open-sse/handlers/audioSpeech.ts` | 用于构建 AWS Bedrock 端点的区域(Kiro、音频)。 | -| `AWS_DEFAULT_REGION` | _(未设置)_ | `src/lib/providers/validation.ts`, `open-sse/handlers/audioSpeech.ts` | `AWS_REGION` 未设置时的回退。 | -| `CLOUDFLARE_ACCOUNT_ID` | _(未设置)_ | `open-sse/executors/cloudflare-ai.ts` | Cloudflare Workers AI 的 Account ID。 | -| `CLOUDFLARE_API_BASE` | `https://api.cloudflare.com/client/v4` | `src/app/api/settings/proxy/cloudflare-deploy/route.ts` | 覆盖代理池 Workers 中继部署器使用的 Cloudflare REST API 基础 URL(#4640 / 9router#1360)。 | -| `NEXT_PUBLIC_CLOUDFLARE_RELAY_DEFAULT_PROJECT` | `omniroute-relay` | `src/app/(dashboard)/dashboard/settings/components/proxy/CloudflareRelayModal.tsx` | 代理池"Deploy Relay"弹窗中建议的默认 worker 项目名。 | -| `NEXT_PUBLIC_CLOUDFLARE_RELAY_ENABLED` | `true` | `src/app/(dashboard)/dashboard/settings/components/proxy/ProxyPoolTab.tsx` | 设为 `false` 可从 Proxy Pool 选项卡中隐藏 Cloudflare Workers 中继选项。 | -| `CLOUDFLARED_BIN` | 自动检测 | `src/lib/cloudflaredTunnel.ts` | `cloudflared` 二进制文件的自定义路径。 | -| `DENO_DEPLOY_API_BASE` | `https://api.deno.com/v2` | `src/app/api/settings/proxy/deno-deploy/route.ts` | 覆盖代理池中继部署器使用的 Deno Deploy REST API 基础 URL(#4643 / 9router#1437)。 | -| `NEXT_PUBLIC_DENO_RELAY_DEFAULT_PROJECT` | `omniroute-deno-relay` | `src/app/(dashboard)/dashboard/settings/components/proxy/DenoRelayModal.tsx` | 代理池"Deploy Relay"弹窗中建议的默认 Deno Deploy 应用名。 | -| `NEXT_PUBLIC_DENO_RELAY_ENABLED` | `true` | `src/app/(dashboard)/dashboard/settings/components/proxy/ProxyPoolTab.tsx` | 设为 `false` 可从 Proxy Pool 选项卡中隐藏 Deno Deploy 中继选项。 | -| `SEARCH_CACHE_TTL_MS` | `300000`(5 分钟) | `open-sse/services/searchCache.ts` | 搜索 API(Perplexity、Brave 等)响应缓存的 TTL。 | -| `ALLOW_MULTI_CONNECTIONS_PER_COMPAT_NODE` | `false` | `src/app/api/providers/route.ts` | 允许每个 OpenAI 兼容服务商同时建立多个连接。 | -| `ENABLE_CC_COMPATIBLE_PROVIDER` | `false` | `src/shared/utils/featureFlags.ts` | 为仅 Claude Code 中继显示实验性 CC 兼容服务商 UI。 | -| `NINEROUTER_HOST` | `127.0.0.1` | `open-sse/executors/ninerouter.ts` | 覆盖嵌入式 9router 实例监听的主机。 | -| `NINEROUTER_PORT` | `20130` | `open-sse/executors/ninerouter.ts` | 覆盖嵌入式 9router 实例监听的端口。 | -| `EMBED_WS_PROXY_HOST` | `127.0.0.1` | `src/lib/services/embedWsProxy.ts` | 嵌入式服务 WebSocket 代理的绑定主机(默认仅 loopback)。 | -| `EMBED_WS_PROXY_PORT` | `20131` | `src/lib/services/embedWsProxy.ts` | 嵌入式服务 WebSocket 代理服务器的端口。 | -| `CLIPROXYAPI_HOST` | `127.0.0.1` | `open-sse/executors/cliproxyapi.ts` | CLIProxyAPI 桥接主机(旧版集成)。 | -| `CLIPROXYAPI_PORT` | `5544` | `open-sse/executors/cliproxyapi.ts` | CLIProxyAPI 桥接端口。 | -| `CLIPROXYAPI_CONFIG_DIR` | `~/.cli-proxy-api` | `src/lib/versionManager/processManager.ts` | CLIProxyAPI 配置目录。 | -| `LOCAL_HOSTNAMES` | _(空)_ | `open-sse/config/providerRegistry.ts` | 逗号分隔的额外被视为"本地"的主机名(Docker 服务名称等)。 | +| 变量 | 默认值 | 源文件 | 说明 | +| ---------------------------------------------- | -------------------------------------- | ---------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------ | +| `OPENROUTER_CATALOG_TTL_MS` | `86400000`(24 小时) | `src/lib/catalog/openrouterCatalog.ts` | OpenRouter 模型目錄缓存 TTL。 | +| `MODEL_CATALOG_INCLUDE_NAMES` | `true` | `src/shared/constants/featureFlagDefinitions.ts` | 在 `/v1/models` 响应中包含显示友好的 `name` 字段。对于只需要 ID 的客户端可禁用。 | +| `NANOBANANA_POLL_TIMEOUT_MS` | `120000` | `open-sse/handlers/imageGeneration.ts` | NanoBanana 图片生成任务的最大等待时间。 | +| `NANOBANANA_POLL_INTERVAL_MS` | `2500` | `open-sse/handlers/imageGeneration.ts` | NanoBanana 任务轮询频率。 | +| `AWS_REGION` | _(未设置)_ | `src/lib/providers/validation.ts`, `open-sse/handlers/audioSpeech.ts` | 用于构建 AWS Bedrock 端点的区域(Kiro、音频)。 | +| `AWS_DEFAULT_REGION` | _(未设置)_ | `src/lib/providers/validation.ts`, `open-sse/handlers/audioSpeech.ts` | `AWS_REGION` 未设置时的回退。 | +| `CLOUDFLARE_ACCOUNT_ID` | _(未设置)_ | `open-sse/executors/cloudflare-ai.ts` | Cloudflare Workers AI 的 Account ID。 | +| `CLOUDFLARE_API_BASE` | `https://api.cloudflare.com/client/v4` | `src/app/api/settings/proxy/cloudflare-deploy/route.ts` | 覆盖代理池 Workers 中继部署器使用的 Cloudflare REST API 基础 URL(#4640 / 9router#1360)。 | +| `NEXT_PUBLIC_CLOUDFLARE_RELAY_DEFAULT_PROJECT` | `omniroute-relay` | `src/app/(dashboard)/dashboard/settings/components/proxy/CloudflareRelayModal.tsx` | 代理池"Deploy Relay"弹窗中建议的默认 worker 项目名。 | +| `NEXT_PUBLIC_CLOUDFLARE_RELAY_ENABLED` | `true` | `src/app/(dashboard)/dashboard/settings/components/proxy/ProxyPoolTab.tsx` | 设为 `false` 可从 Proxy Pool 选项卡中隐藏 Cloudflare Workers 中继选项。 | +| `CLOUDFLARED_BIN` | 自动检测 | `src/lib/cloudflaredTunnel.ts` | `cloudflared` 二进制文件的自定义路径。 | +| `DENO_DEPLOY_API_BASE` | `https://api.deno.com/v2` | `src/app/api/settings/proxy/deno-deploy/route.ts` | 覆盖代理池中继部署器使用的 Deno Deploy REST API 基础 URL(#4643 / 9router#1437)。 | +| `NEXT_PUBLIC_DENO_RELAY_DEFAULT_PROJECT` | `omniroute-deno-relay` | `src/app/(dashboard)/dashboard/settings/components/proxy/DenoRelayModal.tsx` | 代理池"Deploy Relay"弹窗中建议的默认 Deno Deploy 应用名。 | +| `NEXT_PUBLIC_DENO_RELAY_ENABLED` | `true` | `src/app/(dashboard)/dashboard/settings/components/proxy/ProxyPoolTab.tsx` | 设为 `false` 可从 Proxy Pool 选项卡中隐藏 Deno Deploy 中继选项。 | +| `SEARCH_CACHE_TTL_MS` | `300000`(5 分钟) | `open-sse/services/searchCache.ts` | 搜索 API(Perplexity、Brave 等)响应缓存的 TTL。 | +| `ALLOW_MULTI_CONNECTIONS_PER_COMPAT_NODE` | `false` | `src/app/api/providers/route.ts` | 允许每个 OpenAI 兼容服务商同时建立多个连接。 | +| `ENABLE_CC_COMPATIBLE_PROVIDER` | `false` | `src/shared/utils/featureFlags.ts` | 为仅 Claude Code 中继显示实验性 CC 兼容服务商 UI。 | +| `NINEROUTER_HOST` | `127.0.0.1` | `open-sse/executors/ninerouter.ts` | 覆盖嵌入式 9router 实例监听的主机。 | +| `NINEROUTER_PORT` | `20130` | `open-sse/executors/ninerouter.ts` | 覆盖嵌入式 9router 实例监听的端口。 | +| `EMBED_WS_PROXY_HOST` | `127.0.0.1` | `src/lib/services/embedWsProxy.ts` | 嵌入式服务 WebSocket 代理的绑定主机(默认仅 loopback)。 | +| `EMBED_WS_PROXY_PORT` | `20131` | `src/lib/services/embedWsProxy.ts` | 嵌入式服务 WebSocket 代理服务器的端口。 | +| `CLIPROXYAPI_HOST` | `127.0.0.1` | `open-sse/executors/cliproxyapi.ts` | CLIProxyAPI 桥接主机(旧版集成)。 | +| `CLIPROXYAPI_PORT` | `5544` | `open-sse/executors/cliproxyapi.ts` | CLIProxyAPI 桥接端口。 | +| `CLIPROXYAPI_CONFIG_DIR` | `~/.cli-proxy-api` | `src/lib/versionManager/processManager.ts` | CLIProxyAPI 配置目录。 | +| `LOCAL_HOSTNAMES` | _(空)_ | `open-sse/config/providerRegistry.ts` | 逗号分隔的额外被视为"本地"的主机名(Docker 服务名称等)。 | `ENABLE_CC_COMPATIBLE_PROVIDER` 仅适用于接受 Claude Code 客户端的第三方中继。 OmniRoute 会重写请求以使这些中继接受。如果你只想使用 @@ -783,23 +784,23 @@ Anthropic 兼容服务商。 ## 21. 代理健康 -| 变量 | 默认值 | 源文件 | 说明 | -| --- | --- | --- | --- | -| `PROXY_FAST_FAIL_TIMEOUT_MS` | `2000` | `src/lib/proxyHealth.ts` | 快速失败健康检查超时。 | -| `PROXY_HEALTH_CACHE_TTL_MS` | `30000` | `src/lib/proxyHealth.ts` | 健康检查结果缓存 TTL。 | -| `PROXY_HEALTH_UNHEALTHY_CACHE_TTL_MS` | `2000` | `src/lib/proxyHealth.ts` | 代理健康探测失败的缓存 TTL。保持在低于 `PROXY_HEALTH_CACHE_TTL_MS` 的值,以便高并发下的瞬态代理超时能快速重试,同时不会为真正死掉的代理禁用快速失败。 | -| `OMNIROUTE_CONTROL_PLANE_PROXY_DIRECT_FALLBACK` | `false` | `src/shared/constants/featureFlagDefinitions.ts` | 允许 OAuth 和服务商校验流程在代理可达性预检失败时绕过固定代理直接连接。有效优先级为 Feature Flags DB 覆盖 > 环境变量 > 默认值。 | -| `RATE_LIMIT_MAX_WAIT_MS` | `120000`(2 分钟) | `open-sse/services/rateLimitManager.ts` | 在请求失败前等待 429 的最长时间。 | -| `RATE_LIMIT_AUTO_ENABLE` | _(未设置)_ | `open-sse/services/rateLimitManager.ts` | 强制打开/关闭自动启用速率限制安全网,不管持久化的 Dashboard 设置。接受 `true`/`1`/`on` 强制开启,`false`/`0`/`off` 强制关闭。 | -| `PROVIDER_COOLDOWN_ENABLED` | _(未设置 → 关闭)_ | `open-sse/services/providerCooldownTracker.ts` | 启用全局跨请求服务商/连接冷却跟踪。默认关闭(与 Connection Cooldown / Provider Circuit Breaker 重叠)。接受 `true`/`1`/`on` 启用。 | -| `PROVIDER_COOLDOWN_MIN_MS` | `5000` | `open-sse/services/providerCooldownTracker.ts` | 失败的服务商/连接重试前的最短冷却时间(毫秒)。随连续失败次数指数级增长。仅在 `PROVIDER_COOLDOWN_ENABLED` 时使用。 | -| `PROVIDER_COOLDOWN_MAX_MS` | `300000`(5 分钟) | `open-sse/services/providerCooldownTracker.ts` | 失败的服务商/连接重试前的最大冷却上限(毫秒)。仅在 `PROVIDER_COOLDOWN_ENABLED` 时使用。 | -| `STREAM_RECOVERY_ENABLED` | _(未设置 → 关闭)_ | `src/lib/resilience/settings.ts`(种子) → `open-sse/services/streamRecovery.ts`(逻辑) | **这是什么:** 透明恢复被截断的上游流(free-claude-code 端口)。将打开 SSE 窗口保持最多 `STREAM_RECOVERY.HOLDBACK_MS`(750 毫秒),使得 _提交前_ 截断 — 即任何字节到达客户端之前 — 被透明地重新打开和重试。**何时启用:** 频繁在流开始时 0 字节截断的不稳定上游;如果无法承受每个流最多 750 毫秒的首 Token 延迟增加,请保持关闭。接受 `true`/`1`/`on`。为持久化容灾设置提供种子;Dashboard 设置一旦设置即生效。 | -| `STREAM_RECOVERY_MIDSTREAM_ENABLED` | _(未设置 → 关闭)_ | `src/lib/resilience/settings.ts`(种子) → `open-sse/services/streamRecovery.ts`(逻辑) | **这是什么:** 流中续传(Fase 4.4)— _提交后_ 截断(字节已到达客户端)之后,用部分文本作为 assistant 预填充重新请求,拼接缺失的后缀。仅限纯文本 OpenAI 兼容流;有工具调用在进行时永不触发。**何时启用:** 长生成被中途截断,且你接受恢复的后缀以一次性爆发而非逐 Token 到达。独立于 `STREAM_RECOVERY_ENABLED`(不同的风险特征)。接受 `true`/`1`/`on`。 | -| `HEALTHCHECK_STAGGER_MS` | `3000` | `src/lib/tokenHealthCheck.ts` | 启动时服务商 Token 健康检查之间的错开间隔(毫秒)。 | -| `REQUEST_RETRY` | `2` | `src/sse/services/cooldownAwareRetry.ts` | 模型作用域冷却响应上的自动重试次数,之后将错误返回给客户端。 | -| `MAX_RETRY_INTERVAL_SEC` | `30` | `src/sse/services/cooldownAwareRetry.ts` | 冷却重试之间的最大退避间隔(秒)。不管上游 `Retry-After` 如何,均受此值限制。 | -| `HEADROOM_URL` | `http://localhost:8787` | `src/lib/headroom/detect.ts` | Headroom Token 节省器代理 URL。Dashboard 生命周期(`api/headroom/*`)默认在 loopback 上启动本地 `headroom-ai` CLI;覆盖以指向外部 Docker sidecar 代理。 | +| 变量 | 默认值 | 源文件 | 说明 | +| ----------------------------------------------- | ----------------------- | ---------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `PROXY_FAST_FAIL_TIMEOUT_MS` | `2000` | `src/lib/proxyHealth.ts` | 快速失败健康检查超时。 | +| `PROXY_HEALTH_CACHE_TTL_MS` | `30000` | `src/lib/proxyHealth.ts` | 健康检查结果缓存 TTL。 | +| `PROXY_HEALTH_UNHEALTHY_CACHE_TTL_MS` | `2000` | `src/lib/proxyHealth.ts` | 代理健康探测失败的缓存 TTL。保持在低于 `PROXY_HEALTH_CACHE_TTL_MS` 的值,以便高并发下的瞬态代理超时能快速重试,同时不会为真正死掉的代理禁用快速失败。 | +| `OMNIROUTE_CONTROL_PLANE_PROXY_DIRECT_FALLBACK` | `false` | `src/shared/constants/featureFlagDefinitions.ts` | 允许 OAuth 和服务商校验流程在代理可达性预检失败时绕过固定代理直接连接。有效优先级为 Feature Flags DB 覆盖 > 环境变量 > 默认值。 | +| `RATE_LIMIT_MAX_WAIT_MS` | `120000`(2 分钟) | `open-sse/services/rateLimitManager.ts` | 在请求失败前等待 429 的最长时间。 | +| `RATE_LIMIT_AUTO_ENABLE` | _(未设置)_ | `open-sse/services/rateLimitManager.ts` | 强制打开/关闭自动启用速率限制安全网,不管持久化的 Dashboard 设置。接受 `true`/`1`/`on` 强制开启,`false`/`0`/`off` 强制关闭。 | +| `PROVIDER_COOLDOWN_ENABLED` | _(未设置 → 关闭)_ | `open-sse/services/providerCooldownTracker.ts` | 启用全局跨请求服务商/连接冷却跟踪。默认关闭(与 Connection Cooldown / Provider Circuit Breaker 重叠)。接受 `true`/`1`/`on` 启用。 | +| `PROVIDER_COOLDOWN_MIN_MS` | `5000` | `open-sse/services/providerCooldownTracker.ts` | 失败的服务商/连接重试前的最短冷却时间(毫秒)。随连续失败次数指数级增长。仅在 `PROVIDER_COOLDOWN_ENABLED` 时使用。 | +| `PROVIDER_COOLDOWN_MAX_MS` | `300000`(5 分钟) | `open-sse/services/providerCooldownTracker.ts` | 失败的服务商/连接重试前的最大冷却上限(毫秒)。仅在 `PROVIDER_COOLDOWN_ENABLED` 时使用。 | +| `STREAM_RECOVERY_ENABLED` | _(未设置 → 关闭)_ | `src/lib/resilience/settings.ts`(种子) → `open-sse/services/streamRecovery.ts`(逻辑) | **这是什么:** 透明恢复被截断的上游流(free-claude-code 端口)。将打开 SSE 窗口保持最多 `STREAM_RECOVERY.HOLDBACK_MS`(750 毫秒),使得 _提交前_ 截断 — 即任何字节到达客户端之前 — 被透明地重新打开和重试。**何时启用:** 频繁在流开始时 0 字节截断的不稳定上游;如果无法承受每个流最多 750 毫秒的首 Token 延迟增加,请保持关闭。接受 `true`/`1`/`on`。为持久化容灾设置提供种子;Dashboard 设置一旦设置即生效。 | +| `STREAM_RECOVERY_MIDSTREAM_ENABLED` | _(未设置 → 关闭)_ | `src/lib/resilience/settings.ts`(种子) → `open-sse/services/streamRecovery.ts`(逻辑) | **这是什么:** 流中续传(Fase 4.4)— _提交后_ 截断(字节已到达客户端)之后,用部分文本作为 assistant 预填充重新请求,拼接缺失的后缀。仅限纯文本 OpenAI 兼容流;有工具调用在进行时永不触发。**何时启用:** 长生成被中途截断,且你接受恢复的后缀以一次性爆发而非逐 Token 到达。独立于 `STREAM_RECOVERY_ENABLED`(不同的风险特征)。接受 `true`/`1`/`on`。 | +| `HEALTHCHECK_STAGGER_MS` | `3000` | `src/lib/tokenHealthCheck.ts` | 启动时服务商 Token 健康检查之间的错开间隔(毫秒)。 | +| `REQUEST_RETRY` | `2` | `src/sse/services/cooldownAwareRetry.ts` | 模型作用域冷却响应上的自动重试次数,之后将错误返回给客户端。 | +| `MAX_RETRY_INTERVAL_SEC` | `30` | `src/sse/services/cooldownAwareRetry.ts` | 冷却重试之间的最大退避间隔(秒)。不管上游 `Retry-After` 如何,均受此值限制。 | +| `HEADROOM_URL` | `http://localhost:8787` | `src/lib/headroom/detect.ts` | Headroom Token 节省器代理 URL。Dashboard 生命周期(`api/headroom/*`)默认在 loopback 上启动本地 `headroom-ai` CLI;覆盖以指向外部 Docker sidecar 代理。 | ### 流恢复调优常量(非环境变量) @@ -828,19 +829,19 @@ Anthropic 兼容服务商。 > [!CAUTION] > 这些变量会产生**详细输出**,并可能泄露敏感数据。**切勿在生产环境中启用。** -| 变量 | 默认值 | 源文件 | 说明 | -| --- | --- | --- | --- | -| `CURSOR_DEBUG` | _(未设置)_ | `open-sse/executors/cursor.ts` | 设为 `1` 可启用详细的 Cursor executor 日志(解码的 SSE 块等)。 | -| `CURSOR_STREAM_DEBUG` | _(未设置)_ | `open-sse/executors/cursor.ts` | `CURSOR_DEBUG` 的向后兼容别名。 | -| `CURSOR_DUMP_FILE` | _(未设置)_ | `open-sse/executors/cursor.ts` | 当 `CURSOR_DEBUG=1` 时,接收原始解码 Cursor 块的可选文件路径。 | -| `CURSOR_STREAM_TIMEOUT_MS` | `300000` | `open-sse/executors/cursor.ts` | Cursor executor 的流空闲超时(毫秒)。 | -| `CURSOR_TOOL_DIRECTIVE` | 启用 (`!== "0"`) | `open-sse/executors/cursor.ts` | 使 composer-2.5 可靠发出工具调用的工具提交指令。设为 `0` 可禁用。 | -| `CURSOR_IMAGE_FETCH_TIMEOUT_MS` | `15000` | `open-sse/utils/cursorImages.ts` | 远程 `image_url` 视觉输入的单张图片获取超时(毫秒)。 | -| `CURSOR_STATE_DB_PATH` | _(探测)_ | `open-sse/utils/cursorVersionDetector.ts` | 覆盖版本检测使用的 Cursor 状态数据库查询。 | -| `CURSOR_TOKEN` | _(未设置)_ | `scripts/ad-hoc/cursor-tap.cjs` | 开发工具使用的直接 Cursor bearer Token。 | -| `OMNIROUTE_LOG_REQUEST_SHAPE` | 启用 (`!== "0"`) | `src/app/api/v1/chat/completions/route.ts` | 记录大型聊天载荷的 content-type/length 标记。设为 `"0"` 可静默。 | -| `DEBUG_RESPONSES_SSE_TO_JSON` | _(未设置)_ | `open-sse/handlers/responseTranslator.ts` | 设为 `true` 可记录 Responses API SSE→JSON 转换详情。 | -| `NEXT_PUBLIC_OMNIROUTE_E2E_MODE` | _(未设置)_ | E2E 测试工具 | 设为 `true` 可启用 E2E 测试模式(放宽认证、测试钩子)。 | +| 变量 | 默认值 | 源文件 | 说明 | +| -------------------------------- | ---------------- | ------------------------------------------ | ----------------------------------------------------------------- | +| `CURSOR_DEBUG` | _(未设置)_ | `open-sse/executors/cursor.ts` | 设为 `1` 可启用详细的 Cursor executor 日志(解码的 SSE 块等)。 | +| `CURSOR_STREAM_DEBUG` | _(未设置)_ | `open-sse/executors/cursor.ts` | `CURSOR_DEBUG` 的向后兼容别名。 | +| `CURSOR_DUMP_FILE` | _(未设置)_ | `open-sse/executors/cursor.ts` | 当 `CURSOR_DEBUG=1` 时,接收原始解码 Cursor 块的可选文件路径。 | +| `CURSOR_STREAM_TIMEOUT_MS` | `300000` | `open-sse/executors/cursor.ts` | Cursor executor 的流空闲超时(毫秒)。 | +| `CURSOR_TOOL_DIRECTIVE` | 启用 (`!== "0"`) | `open-sse/executors/cursor.ts` | 使 composer-2.5 可靠发出工具调用的工具提交指令。设为 `0` 可禁用。 | +| `CURSOR_IMAGE_FETCH_TIMEOUT_MS` | `15000` | `open-sse/utils/cursorImages.ts` | 远程 `image_url` 视觉输入的单张图片获取超时(毫秒)。 | +| `CURSOR_STATE_DB_PATH` | _(探测)_ | `open-sse/utils/cursorVersionDetector.ts` | 覆盖版本检测使用的 Cursor 状态数据库查询。 | +| `CURSOR_TOKEN` | _(未设置)_ | `scripts/ad-hoc/cursor-tap.cjs` | 开发工具使用的直接 Cursor bearer Token。 | +| `OMNIROUTE_LOG_REQUEST_SHAPE` | 启用 (`!== "0"`) | `src/app/api/v1/chat/completions/route.ts` | 记录大型聊天载荷的 content-type/length 标记。设为 `"0"` 可静默。 | +| `DEBUG_RESPONSES_SSE_TO_JSON` | _(未设置)_ | `open-sse/handlers/responseTranslator.ts` | 设为 `true` 可记录 Responses API SSE→JSON 转换详情。 | +| `NEXT_PUBLIC_OMNIROUTE_E2E_MODE` | _(未设置)_ | E2E 测试工具 | 设为 `true` 可启用 E2E 测试模式(放宽认证、测试钩子)。 | --- @@ -848,11 +849,11 @@ Anthropic 兼容服务商。 允许用户直接从 Dashboard 报告问题。 -| 变量 | 默认值 | 源文件 | 说明 | -| --- | --- | --- | --- | -| `GITHUB_ISSUES_REPO` | _(未设置)_ | `src/app/api/v1/issues/report/route.ts` | 仓库,`owner/repo` 格式。 | -| `GITHUB_ISSUES_TOKEN` | _(未设置)_ | `src/app/api/v1/issues/report/route.ts` | 具有 `issues:write` 权限域的 GitHub Personal Access Token。 | -| `GITHUB_TOKEN` | _(未设置)_ | issue 分类 / 云代理辅助 | 通用 GitHub 访问 Token,用作 `GITHUB_ISSUES_TOKEN` 的回退,并被 `src/lib/cloudAgent/*` 中的云代理辅助使用。 | +| 变量 | 默认值 | 源文件 | 说明 | +| --------------------- | ---------- | --------------------------------------- | ----------------------------------------------------------------------------------------------------------- | +| `GITHUB_ISSUES_REPO` | _(未设置)_ | `src/app/api/v1/issues/report/route.ts` | 仓库,`owner/repo` 格式。 | +| `GITHUB_ISSUES_TOKEN` | _(未设置)_ | `src/app/api/v1/issues/report/route.ts` | 具有 `issues:write` 权限域的 GitHub Personal Access Token。 | +| `GITHUB_TOKEN` | _(未设置)_ | issue 分类 / 云代理辅助 | 通用 GitHub 访问 Token,用作 `GITHUB_ISSUES_TOKEN` 的回退,并被 `src/lib/cloudAgent/*` 中的云代理辅助使用。 | --- @@ -922,16 +923,16 @@ CLI_COMPAT_ALL=1 技能框架(`src/lib/skills/`)在沙箱环境中执行用户定义自动化时应用的限制和安全开关。 -| 变量 | 默认值 | 源文件 | 说明 | -| --- | --- | --- | --- | -| `SKILLS_SANDBOX_TIMEOUT_MS` | `10000`(10 秒) | `src/lib/skills/builtins.ts` | 沙箱技能代码的每次执行挂钟超时时间。硬限制;超时则杀死。 | -| `SKILLS_EXECUTION_TIMEOUT_MS` | _(回退到 `SKILLS_SANDBOX_TIMEOUT_MS`)_ | `src/lib/skills/` | 高级技能编排超时。设为高于 `SKILLS_SANDBOX_TIMEOUT_MS` 以允许多步工作流。 | -| `SKILLS_MAX_FILE_BYTES` | `1048576`(1 MB) | `src/lib/skills/builtins.ts` | 技能可从单个沙箱文件读取的最大字节数。 | -| `SKILLS_MAX_HTTP_RESPONSE_BYTES` | `256000`(250 KB) | `src/lib/skills/builtins.ts` | 技能内从单个 HTTP 响应捕获的最大字节数。 | -| `SKILLS_MAX_SANDBOX_OUTPUT_CHARS` | `100000` | `src/lib/skills/builtins.ts` | 沙箱调用返回的 stdout/stderr 字符硬上限。 | -| `SKILLS_SANDBOX_NETWORK_ENABLED` | `false` | `src/lib/skills/builtins.ts` | 设为 `1`/`true` 允许沙箱内部向外联网。默认**隔离**以确保安全。 | -| `SKILLS_ALLOWED_SANDBOX_IMAGES` | _(空)_ | `src/lib/skills/builtins.ts` | 逗号分隔的允许用于沙箱执行的容器镜像允许列表。空意味着仅使用内置默认值。 | -| `SKILLS_SANDBOX_DOCKER_IMAGE` | _(内置默认值)_ | `src/lib/skills/` | 启动 Docker 沙箱时使用的容器镜像。覆盖以固定自定义加固的基础镜像。 | +| 变量 | 默认值 | 源文件 | 说明 | +| --------------------------------- | -------------------------------------- | ---------------------------- | ------------------------------------------------------------------------- | +| `SKILLS_SANDBOX_TIMEOUT_MS` | `10000`(10 秒) | `src/lib/skills/builtins.ts` | 沙箱技能代码的每次执行挂钟超时时间。硬限制;超时则杀死。 | +| `SKILLS_EXECUTION_TIMEOUT_MS` | _(回退到 `SKILLS_SANDBOX_TIMEOUT_MS`)_ | `src/lib/skills/` | 高级技能编排超时。设为高于 `SKILLS_SANDBOX_TIMEOUT_MS` 以允许多步工作流。 | +| `SKILLS_MAX_FILE_BYTES` | `1048576`(1 MB) | `src/lib/skills/builtins.ts` | 技能可从单个沙箱文件读取的最大字节数。 | +| `SKILLS_MAX_HTTP_RESPONSE_BYTES` | `256000`(250 KB) | `src/lib/skills/builtins.ts` | 技能内从单个 HTTP 响应捕获的最大字节数。 | +| `SKILLS_MAX_SANDBOX_OUTPUT_CHARS` | `100000` | `src/lib/skills/builtins.ts` | 沙箱调用返回的 stdout/stderr 字符硬上限。 | +| `SKILLS_SANDBOX_NETWORK_ENABLED` | `false` | `src/lib/skills/builtins.ts` | 设为 `1`/`true` 允许沙箱内部向外联网。默认**隔离**以确保安全。 | +| `SKILLS_ALLOWED_SANDBOX_IMAGES` | _(空)_ | `src/lib/skills/builtins.ts` | 逗号分隔的允许用于沙箱执行的容器镜像允许列表。空意味着仅使用内置默认值。 | +| `SKILLS_SANDBOX_DOCKER_IMAGE` | _(内置默认值)_ | `src/lib/skills/` | 启动 Docker 沙箱时使用的容器镜像。覆盖以固定自定义加固的基础镜像。 | > [!CAUTION] > 启用 `SKILLS_SANDBOX_NETWORK_ENABLED=true` 会打开任意技能代码的出口路径。在共享部署中请搭配 `OUTBOUND_SSRF_GUARD_ENABLED=true` 和严格的 `CORS_ORIGIN`/代理策略。 @@ -942,88 +943,88 @@ CLI_COMPAT_ALL=1 服务商配额端点、网络隧道(Tailscale、Ngrok、MITM 调试代理)、1Proxy 出口池、数据库备份以及 executor 层或脚本引用的小型按功能覆盖。 -| 变量 | 默认值 | 源文件 | 说明 | -| --- | --- | --- | --- | -| `REDIS_URL` | `redis://localhost:6379` | `src/shared/utils/rateLimiter.ts` | 速率限制器后端的 Redis 连接字符串。 | -| `ALIBABA_CODING_PLAN_HOST` | _(生产主机)_ | `open-sse/services/bailianQuotaFetcher.ts` | 覆盖用于获取阿里巴巴 Bailian coding-plan 配额的主机。 | -| `ALIBABA_CODING_PLAN_QUOTA_URL` | 派生自主机 | `open-sse/services/bailianQuotaFetcher.ts` | 阿里巴巴 Bailian 的完整配额 URL 覆盖。 | -| `CONTEXT_RESERVE_TOKENS` | `1024` | `open-sse/services/contextManager.ts` | 计算提示预算时为补全输出保留的 Token 数。 | -| `MODEL_ALIAS_COMPAT_ENABLED` | 启用 | `open-sse/services/model.ts` | 切换旧客户端使用的旧版模型别名兼容层。 | -| `OMNIROUTE_EMERGENCY_FALLBACK` | 启用 | `open-sse/services/emergencyFallback.ts` | 设为 `false`(或 `0`)可禁用紧急预算耗尽回退,该回退将失败的请求重新路由到免费 `nvidia`/`openai/gpt-oss-120b` 模型。有效优先级为 Feature Flags DB 覆盖 > 环境变量 > 默认值;如果不可用,服务回退到原始环境变量值。 | -| `COMMAND_CODE_CALLBACK_PORT` | _(未设置)_ | `src/app/api/providers/command-code/auth/shared.ts` | Command Code CLI 辅助使用的 OAuth 风格回调的本地端口。 | -| `COMMAND_CODE_VERSION` | `0.33.2` | `open-sse/executors/commandCode.ts` | 作为 `x-command-code-version` 头发送到 Command Code 上游的值。覆盖以升级 CLI 版本。 | -| `MITM_LOCAL_PORT` | `443` | `src/mitm/server.cjs` | MITM 调试代理的本地绑定端口。 | -| `MITM_DISABLE_TLS_VERIFY` | `0` | `src/mitm/server.cjs` | 设为 `1` 可禁用上游 TLS 校验(仅限开发)。 | -| `MITM_IDLE_TIMEOUT_MS` | `60000` | `src/mitm/socketTimeouts.ts`, `src/mitm/server.cjs` | 代理连接的空闲套接字超时(毫秒);超过该时间的空闲套接字会被拆除,避免泄露半打开隧道。 | -| `MITM_VERBOSE` | `1` | `src/mitm/server.cjs`, `src/mitm/_internal/bypass.cjs` | 路由决策日志详细程度:`0` 静默,值越大记录越多 bypass/路由决策。 | -| `ONEPROXY_ENABLED` | `true` | `src/lib/oneproxySync.ts` | 启用 1Proxy 出口池同步。 | -| `ONEPROXY_API_URL` | `https://1proxy-api.aitradepulse.com` | `src/lib/oneproxySync.ts` | 1Proxy 服务 API URL 覆盖。 | -| `ONEPROXY_MAX_PROXIES` | `500` | `src/lib/oneproxySync.ts` | 每次同步导入的最大代理数。 | -| `ONEPROXY_MIN_QUALITY_THRESHOLD` | `50` | `src/lib/oneproxySync.ts` | 导入代理的最低质量分。 | -| `FREE_PROXY_1PROXY_ENABLED` | `true` | `src/lib/freeProxyProviders/oneproxy.ts` | 启用 1proxy 免费代理源。设为 `false` 可禁用。 | -| `FREE_PROXY_1PROXY_API_URL` | _(见 oneproxy.ts)_ | `src/lib/freeProxyProviders/oneproxy.ts` | 1proxy API URL 覆盖。 | -| `FREE_PROXY_1PROXY_MAX` | `500` | `src/lib/freeProxyProviders/oneproxy.ts` | 从 1proxy 每次同步获取的最大代理数。 | -| `FREE_PROXY_1PROXY_MIN_QUALITY` | `50` | `src/lib/freeProxyProviders/oneproxy.ts` | 1proxy 导入的最低质量分阈值。 | -| `FREE_PROXY_PROXIFLY_ENABLED` | `true` | `src/lib/freeProxyProviders/proxifly.ts` | 启用 Proxifly 免费代理源。设为 `false` 可禁用。 | -| `FREE_PROXY_PROXIFLY_QUANTITY` | `100` | `src/lib/freeProxyProviders/proxifly.ts` | 每次 Proxifly 同步获取的代理数量。 | -| `FREE_PROXY_PROXIFLY_ANONYMITY` | `elite` | `src/lib/freeProxyProviders/proxifly.ts` | Proxifly 的匿名级别过滤(`elite`、`anonymous`、`transparent`)。 | -| `FREE_PROXY_IPLOCATE_ENABLED` | `false` | `src/lib/freeProxyProviders/iplocate.ts` | 启用 IPLocate 免费代理源。仅手动启用。 | -| `FREE_PROXY_IPLOCATE_BASE_URL` | `https://raw.githubusercontent.com/iplocate/free-proxy-list/main/protocols` | `src/lib/freeProxyProviders/iplocate.ts` | IPLocate 代理列表基础 URL 覆盖。 | -| `NEXT_PUBLIC_VERCEL_RELAY_ENABLED` | `true` | `src/app/(dashboard)/…/ProxyPoolTab.tsx` | 在 Proxy Pool 选项卡中显示/隐藏 Deploy Vercel Relay 按钮。 | -| `VERCEL_API_BASE` | `https://api.vercel.com` | `src/app/api/settings/proxy/vercel-deploy/route.ts` | Vercel API 基础 URL 覆盖(用于测试)。 | -| `NEXT_PUBLIC_VERCEL_RELAY_DEFAULT_PROJECT` | `omniroute-relay` | `src/app/(dashboard)/…/VercelRelayModal.tsx` | Vercel Relay 部署弹窗中预填的默认项目名。 | -| `TAILSCALE_BIN` | _(自动检测)_ | `src/lib/tailscaleTunnel.ts` | `tailscale` 二进制文件的显式路径。 | -| `TAILSCALED_BIN` | _(自动检测)_ | `src/lib/tailscaleTunnel.ts` | `tailscaled` 守护进程二进制文件的显式路径。 | -| `TAILSCALE_AUTHKEY` | _(未设置)_ | `src/lib/tailscaleTunnel.ts` | 非交互式/无头 `tailscale up` 的预共享 Tailscale 认证密钥(通过 `--auth-key=` 传递)。未设置时,登录回退到交互式浏览器认证 URL。 | -| `NGROK_AUTHTOKEN` | _(未设置)_ | `src/lib/ngrokTunnel.ts` | 认证出口 ngrok 隧道。 | -| `DB_BACKUP_MAX_FILES` | `20` | `src/lib/db/backup.ts` | 磁盘上保留的最大 SQLite 备份文件数。覆盖从 Settings → Database backup retention 保存的值。 | -| `DB_BACKUP_RETENTION_DAYS` | `0` | `src/lib/db/backup.ts` | 保留备份的最大天数。`0` 禁用基于时间的清理。覆盖从 Settings → Database backup retention 保存的值。 | -| `OMNIROUTE_TLS_PROXY_URL` | _(未设置)_ | `open-sse/services/chatgptTlsClient.ts` | 覆盖测试用的 TLS sidecar URL。生产环境应保持未设置。 | -| `CONTAINER_HOST` | `docker` | `scripts/check-permissions.sh` | 入口点权限检查的容器运行时提示。在无根 Podman 下设为 `podman`,以便修复指令使用 `podman unshare chown` 而非 `sudo chown`。 | -| `QUOTA_STORE_DRIVER` | `sqlite` | `src/lib/quota/storeFactory.ts` | 配额共享消费存储后端:`sqlite`(默认)或 `redis`。 | -| `QUOTA_STORE_REDIS_URL` | _(未设置)_ | `src/lib/quota/storeFactory.ts` | `QUOTA_STORE_DRIVER=redis` 时使用的 Redis 连接字符串(如 `redis://localhost:6379`)。 | -| `QUOTA_SATURATION_THRESHOLD` | `0.5` | `src/lib/quota/enforce.ts` | 池饱和比率(0..1);达到或超过该值时池进入严格模式(不允许借用)。 | -| `QUOTA_SOFT_DEPRIORITIZE_FACTOR` | `0.7` | `open-sse/services/combo.ts` | 软配额策略降低目标优先级时应用的分数乘数(0..1)。 | -| `STATUS_SOFT_DEPRIORITIZE_FACTOR` | `0.5` | `open-sse/services/combo/autoStrategy.ts` | 预检配额截止关闭时(#4540),在 auto-combo 评分中对已耗尽的服务商(`credits_exhausted`/`rate_limited`)应用的分数乘数(0..1)。 | -| `QUOTA_CONSUMPTION_RETENTION_DAYS` | `14` | `src/lib/db/quotaConsumption.ts` | `quota_consumption` 桶在 GC(`gcQuotaConsumption`)前的保留窗口(天)。 | -| `QUOTA_PREFLIGHT_CUTOFF_ENABLED` | `false` | `src/lib/resilience/settings.ts` | 启用(默认关闭):在评分前丢弃低配额候选项的自动路由硬配额截止。 | -| `OMNIROUTE_AUTO_FREE_FALLBACK_TO_FULL_POOL` | `false` | `open-sse/services/autoCombo/virtualFactory.ts` | 启用(默认关闭):当 `auto/:` 过滤器没有匹配到已连接候选项时,恢复旧版回退到完整(未过滤)池的行为,而非返回空池。默认关闭使 `:free` 意为"仅免费层"。 | -| `AGENTBRIDGE_UPSTREAM_CA_CERT` | _(未设置)_ | `src/mitm/manager.ts` | AgentBridge 上游 TLS 连接信任的额外 CA 证书(PEM)。 | -| `INSPECTOR_BUFFER_SIZE` | `1000` | `src/mitm/inspector/buffer.ts` | Traffic Inspector 环形缓冲区中保留的最大捕获请求数。 | -| `INSPECTOR_MAX_BODY_KB` | `1024` | `src/mitm/inspector/buffer.ts` | 截断前捕获的请求/响应体最大大小(KB)。 | -| `INSPECTOR_HTTP_PROXY_PORT` | `8080` | `src/mitm/inspector/httpProxyServer.ts` | Traffic Inspector HTTP 代理的本地端口。 | -| `INSPECTOR_HTTP_PROXY_AUTOSTART` | `false` | `src/mitm/inspector/httpProxyServer.ts` | 启动时自动启动 inspector HTTP 代理。 | -| `INSPECTOR_TLS_INTERCEPT` | `false` | `src/lib/inspector/captureState.ts` | 对捕获的 HTTPS 流量启用 TLS 拦截(MITM)。 | -| `INSPECTOR_LLM_HOSTS_EXTRA` | _(未设置)_ | `src/lib/inspector/captureState.ts` | 额外的主机名(逗号分隔),被视为大语言模型端点以进行捕获。 | -| `INSPECTOR_MASK_SECRETS` | `true` | `src/mitm/inspector/buffer.ts` | 在捕获流量中脱敏(认证头 / API key)。 | -| `INSPECTOR_SYSTEM_PROXY_GUARD_MINUTES` | `30` | `src/app/api/tools/traffic-inspector/capture-modes/system-proxy/route.ts` | 系统代理安全护栏自动还原操作系统代理设置前的分钟数。 | -| `INSPECTOR_INTERNAL_INGEST_TOKEN` | _(自动)_ | `src/app/api/tools/traffic-inspector/internal/ingest/route.ts` | 认证进入 inspector 的内部捕获摄入的 Token。 | -| `PLAYGROUND_COMPARE_MAX_COLUMNS` | `4` | `src/app/(dashboard)/dashboard/playground/` | Playground 比较模式下的最大并排列数。 | -| `PLAYGROUND_IMPROVE_PROMPT_DEFAULT_MODEL` | _(未设置)_ | `src/app/(dashboard)/dashboard/playground/` | Playground 'improve prompt' 操作的默认模型(未设置时回退到活跃模型)。 | -| `BIFROST_ENABLED` | `1` | `src/app/api/v1/relay/chat/completions/bifrost/route.ts` | bifrost sidecar 代理的主开关。设为 `0` 时,路由返回 503 并带 `X-Bifrost-Killswitch` 头,运维人员被弹回 TS 路径。无需重新部署即可禁用 sidecar(tier-1 路由事件、密钥轮换)。 | -| `BIFROST_BASE_URL` | _(未设置)_ | `src/app/api/v1/relay/chat/completions/bifrost/route.ts` | 设置时,Bifrost sidecar 代理路由将 `/v1/chat/completions` 流量转发到此 Go 网关而非 TS 中继处理器。未设置 → 503-with-fallback。尾部斜杠会被剥离。 | -| `BIFROST_API_KEY` | _(未设置)_ | `src/app/api/v1/relay/chat/completions/bifrost/route.ts` | Bifrost 网关的 API key(作为 `Authorization: Bearer ...` 发送)。未设置时,路由期望请求携带有效的 OmniRoute API key;此 key 仅用于网关侧认证。 | -| `BIFROST_STREAMING_ENABLED` | `true` | `src/app/api/v1/relay/chat/completions/bifrost/route.ts` | 设为 true 时,Bifrost sidecar 路由通过网关以 SSE 流式返回响应,而非 TS 流式 executor。设为 `0` 可强制通过网关返回非流式 JSON 响应。 | -| `BIFROST_TIMEOUT_MS` | `30000` | `src/app/api/v1/relay/chat/completions/bifrost/route.ts` | 代理到 Bifrost 网关时的每个请求超时(毫秒)。超时时路由通过 `X-Bifrost-Fallback` 头返回 TS 中继路径。 | -| `OMNIROUTE_BIFROST_KEY` | _(未设置)_ | `src/app/api/v1/relay/chat/completions/bifrost/route.ts` | `BIFROST_API_KEY` 的别名(供通过 `OMNIROUTE_*` 读取环境变量的脚本使用)。两者同时设置时 `BIFROST_API_KEY` 优先。 | -| `OMNIROUTE_RELAY_BACKEND` | `ts` / `auto` | `src/app/api/v1/relay/chat/completions/routingBackend.ts` | `/api/v1/relay/chat/completions` 的中继后端:`ts | bifrost | auto`。`ts` = TypeScript 中继(未配置 Bifrost 时的默认值);`auto` 在 `BIFROST_BASE_URL` 已设置且 `BIFROST_ENABLED` ≠ `0` 时选择 Bifrost,若 sidecar 不可达则自动回退到 TS;`bifrost` 强制 Bifrost(严格,无回退)。认证/速率限制/注入安全护栏/allowlist 始终在 Next 路由中首先运行。响应携带 `X-Routing-Backend` / `X-Routing-Fallback`。 | -| `RELAY_ROUTING_BACKEND` | _(未设置)_ | `src/app/api/v1/relay/chat/completions/routingBackend.ts` | `OMNIROUTE_RELAY_BACKEND` 的已接受别名(相同的 `ts | bifrost | auto` 值)。两者同时设置时 `OMNIROUTE_RELAY_BACKEND` 优先。 | -| `OMNIROUTE_BIFROST_FAILURE_COOLDOWN_MS` | `5000` | `src/app/api/v1/relay/chat/completions/bifrostCooldown.ts` | 在 `auto` 模式下 Bifrost sidecar 跳失败后的冷却时间(毫秒),在此期间中继重新尝试 sidecar 之前直接走 TS 路径;冷却过后再次探测。`0` 禁用。仅在 `OMNIROUTE_RELAY_BACKEND=auto` 时生效。 | -| `OMNIROUTE_TLS_CERT` | _(未设置)_ | `bin/cli/commands/serve.mjs` | PEM TLS 证书路径,用于 `omniroute serve` 通过 HTTPS 提供服务(等同于 `--tls-cert`)。必须与 `OMNIROUTE_TLS_KEY` 配对;独立服务器随后在同一监听器上终止 TLS(`wss://` 工作不变)。未设置 → 纯 HTTP。仅提供证书或密钥中的一个,或路径不可读,会记录警告并保持 HTTP。 | -| `OMNIROUTE_TLS_KEY` | _(未设置)_ | `bin/cli/commands/serve.mjs` | `omniroute serve` HTTPS 的 PEM TLS 私钥路径(等同于 `--tls-key`)。必须与 `OMNIROUTE_TLS_CERT` 配对。参阅 `OMNIROUTE_TLS_CERT`。 | -| `OMNIROUTE_LOCAL_ENDPOINTS_ENABLED` | `0` | `src/lib/security/localEndpoints.ts` | `/api/local/*` 路由的主开关。未设置或设为 `0` 时,所有 `/api/local/*` 路由在生产环境中返回 503。在非 loopback 部署中必须设为 `1` 才能启用 Redis 启动器及类似的一键本地服务启动器。与 `isLocalOnlyPath()` 路由守卫分类(`src/server/authz/routeGuard.ts` 中的 `LOCAL_ONLY_API_PREFIXES`)构成双保险。 | -| `OMNIROUTE_LOCAL_ENDPOINTS_TOKEN` | _(未设置)_ | `src/lib/security/localEndpoints.ts` | 非 loopback 上的 `/api/local/*` 调用者的 Bearer Token(如桌面应用)。设置时,来自非 loopback IP 的请求必须携带 `Authorization: Bearer `。在非 loopback 部署中且 `OMNIROUTE_LOCAL_ENDPOINTS_ENABLED=1` 时必需。 | -| `OMNIROUTE_REDIS_CONTAINER_NAME` | `omniroute-redis` | `bin/cli/commands/redis.mjs` | 一键 Redis 启动器(`omniroute redis up`)的容器名。CLI 和 `RedisLauncherPanel` GUI 均使用。 | -| `OMNIROUTE_REDIS_HOST_PORT` | `6379` | `bin/cli/commands/redis.mjs` | 一键 Redis 启动器的主机端口。如果主机已绑定 6379 则提升。容器内部端口保持 6379。 | -| `OMNIROUTE_REDIS_IMAGE` | `redis:7-alpine` | `bin/cli/commands/redis.mjs` | 一键 Redis 启动器使用的 Redis 镜像。根据需要覆盖为 `redis:8-alpine` 或私有注册表镜像。 | -| `QDRANT_HOST` | `qdrant` | _(可选集群配置文件)_ | `--profile memory` 活跃时 Qdrant sidecar 的主机名。默认指向网内 qdrant 服务名;覆盖为外部部署。仅在代码中 `qdrantEnabled` 为 `true` 时使用(`src/lib/memory/vectorStore.ts:108`)。 | -| `QDRANT_PORT` | `6333` | _(可选集群配置文件)_ | Qdrant sidecar 的 REST 端口。 | -| `QDRANT_GRPC_PORT` | `6334` | _(可选集群配置文件)_ | Qdrant sidecar 的 gRPC 端口。偏好在流式操作中使用 gRPC 而非 REST 的客户端库使用。 | -| `QDRANT_API_KEY` | _(未设置)_ | _(可选集群配置文件)_ | Qdrant Cloud 或认证的本地实例的可选 API key。空 → 不发送 `api-key` 头。 | -| `QDRANT_COLLECTION` | `omniroute-memory` | _(可选集群配置文件)_ | OmniRoute 的对话记忆嵌入的 collection 名称。首次运行时以 `QDRANT_VECTOR_SIZE` 维度创建。 | -| `QDRANT_EMBEDDING_MODEL` | `text-embedding-3-small` | _(可选集群配置文件)_ | Qdrant collection 元数据中记录的默认嵌入模型名称。实际嵌入由 OmniRoute 设置中 `embeddingModel` 字段指向的任意服务商生成。 | -| `QDRANT_VECTOR_SIZE` | `1536` | _(可选集群配置文件)_ | 嵌入向量维度。必须与用于嵌入的模型匹配(text-embedding-3-small → 1536;ada-002 → 1536;nomic-embed-text → 768)。 | -| `QDRANT_HNSW_EF_CONSTRUCT` | `128` | _(可选集群配置文件)_ | HNSW 索引构建时精度。值越高构建越慢、搜索越快。 | +| 变量 | 默认值 | 源文件 | 说明 | +| ------------------------------------------- | --------------------------------------------------------------------------- | ------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `REDIS_URL` | `redis://localhost:6379` | `src/shared/utils/rateLimiter.ts` | 速率限制器后端的 Redis 连接字符串。 | +| `ALIBABA_CODING_PLAN_HOST` | _(生产主机)_ | `open-sse/services/bailianQuotaFetcher.ts` | 覆盖用于获取阿里巴巴 Bailian coding-plan 配额的主机。 | +| `ALIBABA_CODING_PLAN_QUOTA_URL` | 派生自主机 | `open-sse/services/bailianQuotaFetcher.ts` | 阿里巴巴 Bailian 的完整配额 URL 覆盖。 | +| `CONTEXT_RESERVE_TOKENS` | `1024` | `open-sse/services/contextManager.ts` | 计算提示预算时为补全输出保留的 Token 数。 | +| `MODEL_ALIAS_COMPAT_ENABLED` | 启用 | `open-sse/services/model.ts` | 切换旧客户端使用的旧版模型别名兼容层。 | +| `OMNIROUTE_EMERGENCY_FALLBACK` | 启用 | `open-sse/services/emergencyFallback.ts` | 设为 `false`(或 `0`)可禁用紧急预算耗尽回退,该回退将失败的请求重新路由到免费 `nvidia`/`openai/gpt-oss-120b` 模型。有效优先级为 Feature Flags DB 覆盖 > 环境变量 > 默认值;如果不可用,服务回退到原始环境变量值。 | +| `COMMAND_CODE_CALLBACK_PORT` | _(未设置)_ | `src/app/api/providers/command-code/auth/shared.ts` | Command Code CLI 辅助使用的 OAuth 风格回调的本地端口。 | +| `COMMAND_CODE_VERSION` | `0.33.2` | `open-sse/executors/commandCode.ts` | 作为 `x-command-code-version` 头发送到 Command Code 上游的值。覆盖以升级 CLI 版本。 | +| `MITM_LOCAL_PORT` | `443` | `src/mitm/server.cjs` | MITM 调试代理的本地绑定端口。 | +| `MITM_DISABLE_TLS_VERIFY` | `0` | `src/mitm/server.cjs` | 设为 `1` 可禁用上游 TLS 校验(仅限开发)。 | +| `MITM_IDLE_TIMEOUT_MS` | `60000` | `src/mitm/socketTimeouts.ts`, `src/mitm/server.cjs` | 代理连接的空闲套接字超时(毫秒);超过该时间的空闲套接字会被拆除,避免泄露半打开隧道。 | +| `MITM_VERBOSE` | `1` | `src/mitm/server.cjs`, `src/mitm/_internal/bypass.cjs` | 路由决策日志详细程度:`0` 静默,值越大记录越多 bypass/路由决策。 | +| `ONEPROXY_ENABLED` | `true` | `src/lib/oneproxySync.ts` | 启用 1Proxy 出口池同步。 | +| `ONEPROXY_API_URL` | `https://1proxy-api.aitradepulse.com` | `src/lib/oneproxySync.ts` | 1Proxy 服务 API URL 覆盖。 | +| `ONEPROXY_MAX_PROXIES` | `500` | `src/lib/oneproxySync.ts` | 每次同步导入的最大代理数。 | +| `ONEPROXY_MIN_QUALITY_THRESHOLD` | `50` | `src/lib/oneproxySync.ts` | 导入代理的最低质量分。 | +| `FREE_PROXY_1PROXY_ENABLED` | `true` | `src/lib/freeProxyProviders/oneproxy.ts` | 启用 1proxy 免费代理源。设为 `false` 可禁用。 | +| `FREE_PROXY_1PROXY_API_URL` | _(见 oneproxy.ts)_ | `src/lib/freeProxyProviders/oneproxy.ts` | 1proxy API URL 覆盖。 | +| `FREE_PROXY_1PROXY_MAX` | `500` | `src/lib/freeProxyProviders/oneproxy.ts` | 从 1proxy 每次同步获取的最大代理数。 | +| `FREE_PROXY_1PROXY_MIN_QUALITY` | `50` | `src/lib/freeProxyProviders/oneproxy.ts` | 1proxy 导入的最低质量分阈值。 | +| `FREE_PROXY_PROXIFLY_ENABLED` | `true` | `src/lib/freeProxyProviders/proxifly.ts` | 启用 Proxifly 免费代理源。设为 `false` 可禁用。 | +| `FREE_PROXY_PROXIFLY_QUANTITY` | `100` | `src/lib/freeProxyProviders/proxifly.ts` | 每次 Proxifly 同步获取的代理数量。 | +| `FREE_PROXY_PROXIFLY_ANONYMITY` | `elite` | `src/lib/freeProxyProviders/proxifly.ts` | Proxifly 的匿名级别过滤(`elite`、`anonymous`、`transparent`)。 | +| `FREE_PROXY_IPLOCATE_ENABLED` | `false` | `src/lib/freeProxyProviders/iplocate.ts` | 启用 IPLocate 免费代理源。仅手动启用。 | +| `FREE_PROXY_IPLOCATE_BASE_URL` | `https://raw.githubusercontent.com/iplocate/free-proxy-list/main/protocols` | `src/lib/freeProxyProviders/iplocate.ts` | IPLocate 代理列表基础 URL 覆盖。 | +| `NEXT_PUBLIC_VERCEL_RELAY_ENABLED` | `true` | `src/app/(dashboard)/…/ProxyPoolTab.tsx` | 在 Proxy Pool 选项卡中显示/隐藏 Deploy Vercel Relay 按钮。 | +| `VERCEL_API_BASE` | `https://api.vercel.com` | `src/app/api/settings/proxy/vercel-deploy/route.ts` | Vercel API 基础 URL 覆盖(用于测试)。 | +| `NEXT_PUBLIC_VERCEL_RELAY_DEFAULT_PROJECT` | `omniroute-relay` | `src/app/(dashboard)/…/VercelRelayModal.tsx` | Vercel Relay 部署弹窗中预填的默认项目名。 | +| `TAILSCALE_BIN` | _(自动检测)_ | `src/lib/tailscaleTunnel.ts` | `tailscale` 二进制文件的显式路径。 | +| `TAILSCALED_BIN` | _(自动检测)_ | `src/lib/tailscaleTunnel.ts` | `tailscaled` 守护进程二进制文件的显式路径。 | +| `TAILSCALE_AUTHKEY` | _(未设置)_ | `src/lib/tailscaleTunnel.ts` | 非交互式/无头 `tailscale up` 的预共享 Tailscale 认证密钥(通过 `--auth-key=` 传递)。未设置时,登录回退到交互式浏览器认证 URL。 | +| `NGROK_AUTHTOKEN` | _(未设置)_ | `src/lib/ngrokTunnel.ts` | 认证出口 ngrok 隧道。 | +| `DB_BACKUP_MAX_FILES` | `20` | `src/lib/db/backup.ts` | 磁盘上保留的最大 SQLite 备份文件数。覆盖从 Settings → Database backup retention 保存的值。 | +| `DB_BACKUP_RETENTION_DAYS` | `0` | `src/lib/db/backup.ts` | 保留备份的最大天数。`0` 禁用基于时间的清理。覆盖从 Settings → Database backup retention 保存的值。 | +| `OMNIROUTE_TLS_PROXY_URL` | _(未设置)_ | `open-sse/services/chatgptTlsClient.ts` | 覆盖测试用的 TLS sidecar URL。生产环境应保持未设置。 | +| `CONTAINER_HOST` | `docker` | `scripts/check-permissions.sh` | 入口点权限检查的容器运行时提示。在无根 Podman 下设为 `podman`,以便修复指令使用 `podman unshare chown` 而非 `sudo chown`。 | +| `QUOTA_STORE_DRIVER` | `sqlite` | `src/lib/quota/storeFactory.ts` | 配额共享消费存储后端:`sqlite`(默认)或 `redis`。 | +| `QUOTA_STORE_REDIS_URL` | _(未设置)_ | `src/lib/quota/storeFactory.ts` | `QUOTA_STORE_DRIVER=redis` 时使用的 Redis 连接字符串(如 `redis://localhost:6379`)。 | +| `QUOTA_SATURATION_THRESHOLD` | `0.5` | `src/lib/quota/enforce.ts` | 池饱和比率(0..1);达到或超过该值时池进入严格模式(不允许借用)。 | +| `QUOTA_SOFT_DEPRIORITIZE_FACTOR` | `0.7` | `open-sse/services/combo.ts` | 软配额策略降低目标优先级时应用的分数乘数(0..1)。 | +| `STATUS_SOFT_DEPRIORITIZE_FACTOR` | `0.5` | `open-sse/services/combo/autoStrategy.ts` | 预检配额截止关闭时(#4540),在 auto-combo 评分中对已耗尽的服务商(`credits_exhausted`/`rate_limited`)应用的分数乘数(0..1)。 | +| `QUOTA_CONSUMPTION_RETENTION_DAYS` | `14` | `src/lib/db/quotaConsumption.ts` | `quota_consumption` 桶在 GC(`gcQuotaConsumption`)前的保留窗口(天)。 | +| `QUOTA_PREFLIGHT_CUTOFF_ENABLED` | `false` | `src/lib/resilience/settings.ts` | 启用(默认关闭):在评分前丢弃低配额候选项的自动路由硬配额截止。 | +| `OMNIROUTE_AUTO_FREE_FALLBACK_TO_FULL_POOL` | `false` | `open-sse/services/autoCombo/virtualFactory.ts` | 启用(默认关闭):当 `auto/:` 过滤器没有匹配到已连接候选项时,恢复旧版回退到完整(未过滤)池的行为,而非返回空池。默认关闭使 `:free` 意为"仅免费层"。 | +| `AGENTBRIDGE_UPSTREAM_CA_CERT` | _(未设置)_ | `src/mitm/manager.ts` | AgentBridge 上游 TLS 连接信任的额外 CA 证书(PEM)。 | +| `INSPECTOR_BUFFER_SIZE` | `1000` | `src/mitm/inspector/buffer.ts` | Traffic Inspector 环形缓冲区中保留的最大捕获请求数。 | +| `INSPECTOR_MAX_BODY_KB` | `1024` | `src/mitm/inspector/buffer.ts` | 截断前捕获的请求/响应体最大大小(KB)。 | +| `INSPECTOR_HTTP_PROXY_PORT` | `8080` | `src/mitm/inspector/httpProxyServer.ts` | Traffic Inspector HTTP 代理的本地端口。 | +| `INSPECTOR_HTTP_PROXY_AUTOSTART` | `false` | `src/mitm/inspector/httpProxyServer.ts` | 启动时自动启动 inspector HTTP 代理。 | +| `INSPECTOR_TLS_INTERCEPT` | `false` | `src/lib/inspector/captureState.ts` | 对捕获的 HTTPS 流量启用 TLS 拦截(MITM)。 | +| `INSPECTOR_LLM_HOSTS_EXTRA` | _(未设置)_ | `src/lib/inspector/captureState.ts` | 额外的主机名(逗号分隔),被视为大语言模型端点以进行捕获。 | +| `INSPECTOR_MASK_SECRETS` | `true` | `src/mitm/inspector/buffer.ts` | 在捕获流量中脱敏(认证头 / API key)。 | +| `INSPECTOR_SYSTEM_PROXY_GUARD_MINUTES` | `30` | `src/app/api/tools/traffic-inspector/capture-modes/system-proxy/route.ts` | 系统代理安全护栏自动还原操作系统代理设置前的分钟数。 | +| `INSPECTOR_INTERNAL_INGEST_TOKEN` | _(自动)_ | `src/app/api/tools/traffic-inspector/internal/ingest/route.ts` | 认证进入 inspector 的内部捕获摄入的 Token。 | +| `PLAYGROUND_COMPARE_MAX_COLUMNS` | `4` | `src/app/(dashboard)/dashboard/playground/` | Playground 比较模式下的最大并排列数。 | +| `PLAYGROUND_IMPROVE_PROMPT_DEFAULT_MODEL` | _(未设置)_ | `src/app/(dashboard)/dashboard/playground/` | Playground 'improve prompt' 操作的默认模型(未设置时回退到活跃模型)。 | +| `BIFROST_ENABLED` | `1` | `src/app/api/v1/relay/chat/completions/bifrost/route.ts` | bifrost sidecar 代理的主开关。设为 `0` 时,路由返回 503 并带 `X-Bifrost-Killswitch` 头,运维人员被弹回 TS 路径。无需重新部署即可禁用 sidecar(tier-1 路由事件、密钥轮换)。 | +| `BIFROST_BASE_URL` | _(未设置)_ | `src/app/api/v1/relay/chat/completions/bifrost/route.ts` | 设置时,Bifrost sidecar 代理路由将 `/v1/chat/completions` 流量转发到此 Go 网关而非 TS 中继处理器。未设置 → 503-with-fallback。尾部斜杠会被剥离。 | +| `BIFROST_API_KEY` | _(未设置)_ | `src/app/api/v1/relay/chat/completions/bifrost/route.ts` | Bifrost 网关的 API key(作为 `Authorization: Bearer ...` 发送)。未设置时,路由期望请求携带有效的 OmniRoute API key;此 key 仅用于网关侧认证。 | +| `BIFROST_STREAMING_ENABLED` | `true` | `src/app/api/v1/relay/chat/completions/bifrost/route.ts` | 设为 true 时,Bifrost sidecar 路由通过网关以 SSE 流式返回响应,而非 TS 流式 executor。设为 `0` 可强制通过网关返回非流式 JSON 响应。 | +| `BIFROST_TIMEOUT_MS` | `30000` | `src/app/api/v1/relay/chat/completions/bifrost/route.ts` | 代理到 Bifrost 网关时的每个请求超时(毫秒)。超时时路由通过 `X-Bifrost-Fallback` 头返回 TS 中继路径。 | +| `OMNIROUTE_BIFROST_KEY` | _(未设置)_ | `src/app/api/v1/relay/chat/completions/bifrost/route.ts` | `BIFROST_API_KEY` 的别名(供通过 `OMNIROUTE_*` 读取环境变量的脚本使用)。两者同时设置时 `BIFROST_API_KEY` 优先。 | +| `OMNIROUTE_RELAY_BACKEND` | `ts` / `auto` | `src/app/api/v1/relay/chat/completions/routingBackend.ts` | `/api/v1/relay/chat/completions` 的中继后端:`ts | bifrost | auto`。`ts` = TypeScript 中继(未配置 Bifrost 时的默认值);`auto`在`BIFROST_BASE_URL`已设置且`BIFROST_ENABLED`≠`0` 时选择 Bifrost,若 sidecar 不可达则自动回退到 TS;`bifrost`强制 Bifrost(严格,无回退)。认证/速率限制/注入安全护栏/allowlist 始终在 Next 路由中首先运行。响应携带`X-Routing-Backend`/`X-Routing-Fallback`。 | +| `RELAY_ROUTING_BACKEND` | _(未设置)_ | `src/app/api/v1/relay/chat/completions/routingBackend.ts` | `OMNIROUTE_RELAY_BACKEND` 的已接受别名(相同的 `ts | bifrost | auto`值)。两者同时设置时`OMNIROUTE_RELAY_BACKEND` 优先。 | +| `OMNIROUTE_BIFROST_FAILURE_COOLDOWN_MS` | `5000` | `src/app/api/v1/relay/chat/completions/bifrostCooldown.ts` | 在 `auto` 模式下 Bifrost sidecar 跳失败后的冷却时间(毫秒),在此期间中继重新尝试 sidecar 之前直接走 TS 路径;冷却过后再次探测。`0` 禁用。仅在 `OMNIROUTE_RELAY_BACKEND=auto` 时生效。 | +| `OMNIROUTE_TLS_CERT` | _(未设置)_ | `bin/cli/commands/serve.mjs` | PEM TLS 证书路径,用于 `omniroute serve` 通过 HTTPS 提供服务(等同于 `--tls-cert`)。必须与 `OMNIROUTE_TLS_KEY` 配对;独立服务器随后在同一监听器上终止 TLS(`wss://` 工作不变)。未设置 → 纯 HTTP。仅提供证书或密钥中的一个,或路径不可读,会记录警告并保持 HTTP。 | +| `OMNIROUTE_TLS_KEY` | _(未设置)_ | `bin/cli/commands/serve.mjs` | `omniroute serve` HTTPS 的 PEM TLS 私钥路径(等同于 `--tls-key`)。必须与 `OMNIROUTE_TLS_CERT` 配对。参阅 `OMNIROUTE_TLS_CERT`。 | +| `OMNIROUTE_LOCAL_ENDPOINTS_ENABLED` | `0` | `src/lib/security/localEndpoints.ts` | `/api/local/*` 路由的主开关。未设置或设为 `0` 时,所有 `/api/local/*` 路由在生产环境中返回 503。在非 loopback 部署中必须设为 `1` 才能启用 Redis 启动器及类似的一键本地服务启动器。与 `isLocalOnlyPath()` 路由守卫分类(`src/server/authz/routeGuard.ts` 中的 `LOCAL_ONLY_API_PREFIXES`)构成双保险。 | +| `OMNIROUTE_LOCAL_ENDPOINTS_TOKEN` | _(未设置)_ | `src/lib/security/localEndpoints.ts` | 非 loopback 上的 `/api/local/*` 调用者的 Bearer Token(如桌面应用)。设置时,来自非 loopback IP 的请求必须携带 `Authorization: Bearer `。在非 loopback 部署中且 `OMNIROUTE_LOCAL_ENDPOINTS_ENABLED=1` 时必需。 | +| `OMNIROUTE_REDIS_CONTAINER_NAME` | `omniroute-redis` | `bin/cli/commands/redis.mjs` | 一键 Redis 启动器(`omniroute redis up`)的容器名。CLI 和 `RedisLauncherPanel` GUI 均使用。 | +| `OMNIROUTE_REDIS_HOST_PORT` | `6379` | `bin/cli/commands/redis.mjs` | 一键 Redis 启动器的主机端口。如果主机已绑定 6379 则提升。容器内部端口保持 6379。 | +| `OMNIROUTE_REDIS_IMAGE` | `redis:7-alpine` | `bin/cli/commands/redis.mjs` | 一键 Redis 启动器使用的 Redis 镜像。根据需要覆盖为 `redis:8-alpine` 或私有注册表镜像。 | +| `QDRANT_HOST` | `qdrant` | _(可选集群配置文件)_ | `--profile memory` 活跃时 Qdrant sidecar 的主机名。默认指向网内 qdrant 服务名;覆盖为外部部署。仅在代码中 `qdrantEnabled` 为 `true` 时使用(`src/lib/memory/vectorStore.ts:108`)。 | +| `QDRANT_PORT` | `6333` | _(可选集群配置文件)_ | Qdrant sidecar 的 REST 端口。 | +| `QDRANT_GRPC_PORT` | `6334` | _(可选集群配置文件)_ | Qdrant sidecar 的 gRPC 端口。偏好在流式操作中使用 gRPC 而非 REST 的客户端库使用。 | +| `QDRANT_API_KEY` | _(未设置)_ | _(可选集群配置文件)_ | Qdrant Cloud 或认证的本地实例的可选 API key。空 → 不发送 `api-key` 头。 | +| `QDRANT_COLLECTION` | `omniroute-memory` | _(可选集群配置文件)_ | OmniRoute 的对话记忆嵌入的 collection 名称。首次运行时以 `QDRANT_VECTOR_SIZE` 维度创建。 | +| `QDRANT_EMBEDDING_MODEL` | `text-embedding-3-small` | _(可选集群配置文件)_ | Qdrant collection 元数据中记录的默认嵌入模型名称。实际嵌入由 OmniRoute 设置中 `embeddingModel` 字段指向的任意服务商生成。 | +| `QDRANT_VECTOR_SIZE` | `1536` | _(可选集群配置文件)_ | 嵌入向量维度。必须与用于嵌入的模型匹配(text-embedding-3-small → 1536;ada-002 → 1536;nomic-embed-text → 768)。 | +| `QDRANT_HNSW_EF_CONSTRUCT` | `128` | _(可选集群配置文件)_ | HNSW 索引构建时精度。值越高构建越慢、搜索越快。 | --- @@ -1033,38 +1034,38 @@ CLI_COMPAT_ALL=1 `scripts/dev/run-ecosystem-tests.mjs` 和 `scripts/build/uninstall.mjs` 使用。 在生产部署中保持下面所有值未设置。 -| 变量 | 默认值 | 源文件 | 说明 | -| --- | --- | --- | --- | -| `OMNIROUTE_E2E_BOOTSTRAP_MODE` | `auth` | `scripts/dev/run-next-playwright.mjs` | Playwright 运行器的 E2E 引导模式(`auth`、`fresh`、`reuse`)。 | -| `OMNIROUTE_E2E_PASSWORD` | 回退到 `INITIAL_PASSWORD` | `scripts/dev/run-next-playwright.mjs` | 注入到 Playwright 环境的管理员密码。 | -| `OMNIROUTE_DISABLE_LOCAL_HEALTHCHECK` | `true` | `scripts/dev/run-next-playwright.mjs` | Playwright 运行期间禁用本地健康检查轮询。 | -| `OMNIROUTE_DISABLE_TOKEN_HEALTHCHECK` | `true` | `scripts/dev/run-next-playwright.mjs` | 测试期间禁用 OAuth Token 健康检查循环。 | -| `OMNIROUTE_HEALTHCHECK_SKIP_PROVIDERS` | _(未设置)_ | `src/lib/tokenHealthCheck.ts` | 逗号分隔的排除在主动 Token 刷新扫除之外的服务商(如 `codex,openai`)。完全禁用健康检查的目标替代方案 — 短 TTL 的服务商保持刷新,级联服务商保持仅响应式。 | -| `OMNIROUTE_HIDE_HEALTHCHECK_LOGS` | `true` | `scripts/dev/run-next-playwright.mjs` | Playwright stdout 中的健康检查日志静默。 | -| `OMNIROUTE_PLAYWRIGHT_SKIP_BUILD` | `0` | `scripts/dev/run-next-playwright.mjs` | Playwright 启动前跳过 Next.js 生产构建(CI 优化)。 | -| `OMNIROUTE_SKIP_UNINSTALL_HOOK` | `0` | `scripts/build/uninstall.mjs` | 跳过 OmniRoute 卸载钩子(CI 用,保持 `node_modules` 完整)。 | -| `ECOSYSTEM_SERVER_WAIT_MS` | `180000` | `scripts/dev/run-ecosystem-tests.mjs` | 服务器在运行生态系统/协议测试前变为健康的等待时间(毫秒)。 | -| `ELECTRON_SMOKE_URL` | `http://127.0.0.1:20128/login` | `scripts/dev/smoke-electron-packaged.mjs` | Electron 烟雾测试工具期望打包应用提供服务的 URL。 | -| `ELECTRON_SMOKE_TIMEOUT_MS` | `45000` | `scripts/dev/smoke-electron-packaged.mjs` | 烟雾测试工具放弃前的总超时时间(毫秒)。 | -| `ELECTRON_SMOKE_SETTLE_MS` | `2000` | `scripts/dev/smoke-electron-packaged.mjs` | 页面加载后的稳定窗口(毫秒)。 | -| `ELECTRON_SMOKE_APP_EXECUTABLE` | _(自动)_ | `scripts/dev/smoke-electron-packaged.mjs` | 打包的 Electron 可执行文件的显式路径。 | -| `ELECTRON_SMOKE_DATA_DIR` | _(临时目录)_ | `scripts/dev/smoke-electron-packaged.mjs` | Electron 烟雾测试运行的数据目录。 | -| `ELECTRON_SMOKE_KEEP_DATA` | `0` | `scripts/dev/smoke-electron-packaged.mjs` | 设为 `1` 可在运行后保留烟雾测试数据目录。 | -| `ELECTRON_SMOKE_STREAM_LOGS` | `0` | `scripts/dev/smoke-electron-packaged.mjs` | 设为 `1` 可在运行期间将 Electron 日志流式输出到 stdout。 | -| `CLI_DEVIN_BIN` | _(PATH 查找)_ | `open-sse/executors/devin-cli.ts` | 覆盖 Devin CLI 二进制文件路径。 | +| 变量 | 默认值 | 源文件 | 说明 | +| -------------------------------------- | ------------------------------ | ----------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `OMNIROUTE_E2E_BOOTSTRAP_MODE` | `auth` | `scripts/dev/run-next-playwright.mjs` | Playwright 运行器的 E2E 引导模式(`auth`、`fresh`、`reuse`)。 | +| `OMNIROUTE_E2E_PASSWORD` | 回退到 `INITIAL_PASSWORD` | `scripts/dev/run-next-playwright.mjs` | 注入到 Playwright 环境的管理员密码。 | +| `OMNIROUTE_DISABLE_LOCAL_HEALTHCHECK` | `true` | `scripts/dev/run-next-playwright.mjs` | Playwright 运行期间禁用本地健康检查轮询。 | +| `OMNIROUTE_DISABLE_TOKEN_HEALTHCHECK` | `true` | `scripts/dev/run-next-playwright.mjs` | 测试期间禁用 OAuth Token 健康检查循环。 | +| `OMNIROUTE_HEALTHCHECK_SKIP_PROVIDERS` | _(未设置)_ | `src/lib/tokenHealthCheck.ts` | 逗号分隔的排除在主动 Token 刷新扫除之外的服务商(如 `codex,openai`)。完全禁用健康检查的目标替代方案 — 短 TTL 的服务商保持刷新,级联服务商保持仅响应式。 | +| `OMNIROUTE_HIDE_HEALTHCHECK_LOGS` | `true` | `scripts/dev/run-next-playwright.mjs` | Playwright stdout 中的健康检查日志静默。 | +| `OMNIROUTE_PLAYWRIGHT_SKIP_BUILD` | `0` | `scripts/dev/run-next-playwright.mjs` | Playwright 启动前跳过 Next.js 生产构建(CI 优化)。 | +| `OMNIROUTE_SKIP_UNINSTALL_HOOK` | `0` | `scripts/build/uninstall.mjs` | 跳过 OmniRoute 卸载钩子(CI 用,保持 `node_modules` 完整)。 | +| `ECOSYSTEM_SERVER_WAIT_MS` | `180000` | `scripts/dev/run-ecosystem-tests.mjs` | 服务器在运行生态系统/协议测试前变为健康的等待时间(毫秒)。 | +| `ELECTRON_SMOKE_URL` | `http://127.0.0.1:20128/login` | `scripts/dev/smoke-electron-packaged.mjs` | Electron 烟雾测试工具期望打包应用提供服务的 URL。 | +| `ELECTRON_SMOKE_TIMEOUT_MS` | `45000` | `scripts/dev/smoke-electron-packaged.mjs` | 烟雾测试工具放弃前的总超时时间(毫秒)。 | +| `ELECTRON_SMOKE_SETTLE_MS` | `2000` | `scripts/dev/smoke-electron-packaged.mjs` | 页面加载后的稳定窗口(毫秒)。 | +| `ELECTRON_SMOKE_APP_EXECUTABLE` | _(自动)_ | `scripts/dev/smoke-electron-packaged.mjs` | 打包的 Electron 可执行文件的显式路径。 | +| `ELECTRON_SMOKE_DATA_DIR` | _(临时目录)_ | `scripts/dev/smoke-electron-packaged.mjs` | Electron 烟雾测试运行的数据目录。 | +| `ELECTRON_SMOKE_KEEP_DATA` | `0` | `scripts/dev/smoke-electron-packaged.mjs` | 设为 `1` 可在运行后保留烟雾测试数据目录。 | +| `ELECTRON_SMOKE_STREAM_LOGS` | `0` | `scripts/dev/smoke-electron-packaged.mjs` | 设为 `1` 可在运行期间将 Electron 日志流式输出到 stdout。 | +| `CLI_DEVIN_BIN` | _(PATH 查找)_ | `open-sse/executors/devin-cli.ts` | 覆盖 Devin CLI 二进制文件路径。 | ### 文档翻译管线 由 `scripts/i18n/run-translation.mjs`(`npm run i18n:run` 命令)使用。 所有五个变量默认未设置 — 仅在能够运行文档翻译器的机器上的 `.env` 中设置。 -| 变量 | 默认值 | 源文件 | 说明 | -| --- | --- | --- | --- | -| `OMNIROUTE_TRANSLATION_API_URL` | _(未设置)_ | `scripts/i18n/run-translation.mjs` | 翻译后端的 OpenAI 兼容基础 URL。 | -| `OMNIROUTE_TRANSLATION_API_KEY` | _(未设置)_ | `scripts/i18n/run-translation.mjs` | 翻译后端的 Bearer Token(永不被记录)。 | -| `OMNIROUTE_TRANSLATION_MODEL` | _(未设置)_ | `scripts/i18n/run-translation.mjs` | 模型 ID,如 `gpt-4o-mini` 或 `cx/gpt-5.4-mini`。 | -| `OMNIROUTE_TRANSLATION_TIMEOUT_MS` | `60000` | `scripts/i18n/run-translation.mjs` | 每个请求的超时,毫秒。 | -| `OMNIROUTE_TRANSLATION_CONCURRENCY` | `4` | `scripts/i18n/run-translation.mjs` | 跨多个文件/locale 运行时的并行翻译请求数。 | +| 变量 | 默认值 | 源文件 | 说明 | +| ----------------------------------- | ---------- | ---------------------------------- | ------------------------------------------------ | +| `OMNIROUTE_TRANSLATION_API_URL` | _(未设置)_ | `scripts/i18n/run-translation.mjs` | 翻译后端的 OpenAI 兼容基础 URL。 | +| `OMNIROUTE_TRANSLATION_API_KEY` | _(未设置)_ | `scripts/i18n/run-translation.mjs` | 翻译后端的 Bearer Token(永不被记录)。 | +| `OMNIROUTE_TRANSLATION_MODEL` | _(未设置)_ | `scripts/i18n/run-translation.mjs` | 模型 ID,如 `gpt-4o-mini` 或 `cx/gpt-5.4-mini`。 | +| `OMNIROUTE_TRANSLATION_TIMEOUT_MS` | `60000` | `scripts/i18n/run-translation.mjs` | 每个请求的超时,毫秒。 | +| `OMNIROUTE_TRANSLATION_CONCURRENCY` | `4` | `scripts/i18n/run-translation.mjs` | 跨多个文件/locale 运行时的并行翻译请求数。 | --- @@ -1072,26 +1073,26 @@ CLI_COMPAT_ALL=1 以下变量出现在旧版 `.env.example` 中,但在当前代码库中**没有运行时引用**。它们已被移除: -| 变量 | 原因 | -| --- | --- | -| `STORAGE_DRIVER=sqlite` | 任何源文件均未读取。SQLite 是唯一支持的驱动 — 无需选择。 | -| `INSTANCE_NAME=omniroute` | 存在于旧文档/环境模板中但运行时未使用。可能在未来多实例功能中回归。 | -| `SQLITE_MAX_SIZE_MB=2048` | 源代码中未引用。数据库大小未被人为限制。 | -| `SQLITE_CLEAN_LEGACY_FILES=true` | 源代码中未引用。旧版清理可能已被移除。 | -| `CLI_ROO_BIN` | 未在 `src/shared/services/cliRuntime.ts` 中注册。 | -| `CLI_KIMI_CODING_BIN` | 未在 `src/shared/services/cliRuntime.ts` 中注册(Kimi Coding 使用 OAuth,而非 CLI 二进制文件)。 | -| `IFLOW_OAUTH_CLIENT_ID` / `IFLOW_OAUTH_CLIENT_SECRET` | 源代码中任何地方均未引用。 | +| 变量 | 原因 | +| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- | +| `STORAGE_DRIVER=sqlite` | 任何源文件均未读取。SQLite 是唯一支持的驱动 — 无需选择。 | +| `INSTANCE_NAME=omniroute` | 存在于旧文档/环境模板中但运行时未使用。可能在未来多实例功能中回归。 | +| `SQLITE_MAX_SIZE_MB=2048` | 源代码中未引用。数据库大小未被人为限制。 | +| `SQLITE_CLEAN_LEGACY_FILES=true` | 源代码中未引用。旧版清理可能已被移除。 | +| `CLI_ROO_BIN` | 未在 `src/shared/services/cliRuntime.ts` 中注册。 | +| `CLI_KIMI_CODING_BIN` | 未在 `src/shared/services/cliRuntime.ts` 中注册(Kimi Coding 使用 OAuth,而非 CLI 二进制文件)。 | +| `IFLOW_OAUTH_CLIENT_ID` / `IFLOW_OAUTH_CLIENT_SECRET` | 源代码中任何地方均未引用。 | | `CEREBRAS_API_KEY` / `COHERE_API_KEY` / `FIREWORKS_API_KEY` / `GROQ_API_KEY` / `MISTRAL_API_KEY` / `NEBIUS_API_KEY` / `PERPLEXITY_API_KEY` / `TOGETHER_API_KEY` / `XAI_API_KEY` | 在 v3.8.0 中移除。运行时不再读取这些环境变量 — 凭证来自 Dashboard / `data/provider-credentials.json` / 加密数据库。 | -| `CURSOR_PROTOBUF_DEBUG` | 在 v3.8.0 中移除。Cursor executor 使用 `CURSOR_DEBUG` / `CURSOR_STREAM_DEBUG`(参阅 §22)。 | -| `CLI_COMPAT_KIRO` | 在 v3.8.0 中移除。Kiro 在 `CLI_COMPAT_OMITTED_PROVIDER_IDS` 中 — 其开关无效。 | -| `QIANFAN_API_KEY` | 在 v3.8.0 中随其他未使用的服务商 API key 桩一起移除。 | +| `CURSOR_PROTOBUF_DEBUG` | 在 v3.8.0 中移除。Cursor executor 使用 `CURSOR_DEBUG` / `CURSOR_STREAM_DEBUG`(参阅 §22)。 | +| `CLI_COMPAT_KIRO` | 在 v3.8.0 中移除。Kiro 在 `CLI_COMPAT_OMITTED_PROVIDER_IDS` 中 — 其开关无效。 | +| `QIANFAN_API_KEY` | 在 v3.8.0 中随其他未使用的服务商 API key 桩一起移除。 | ### 默认值修正 -| 变量 | 旧 `.env.example` 值 | 实际代码默认值 | 修正 | -| --- | --- | --- | --- | -| `APP_LOG_RETENTION_DAYS` | `90` | `7` | ✅ 已移除误导性值;记录 `7` 为默认值 | -| `CALL_LOG_RETENTION_DAYS` | `90` | `7` | ✅ 已移除误导性值;记录 `7` 为默认值 | +| 变量 | 旧 `.env.example` 值 | 实际代码默认值 | 修正 | +| ------------------------- | -------------------- | -------------- | ------------------------------------ | +| `APP_LOG_RETENTION_DAYS` | `90` | `7` | ✅ 已移除误导性值;记录 `7` 为默认值 | +| `CALL_LOG_RETENTION_DAYS` | `90` | `7` | ✅ 已移除误导性值;记录 `7` 为默认值 | ### OpenCode 配置重新生成(临时工具) @@ -1099,17 +1100,17 @@ CLI_COMPAT_ALL=1 其中包含从运行中的 OmniRoute 实例拉取的准确的 `limit.context` 和 `limit.output` 值。 这些变量都不是正常运行所需的 — 该脚本仅供开发者工具使用。 -| 变量 | 默认值 | 源文件 | 说明 | -| --- | --- | --- | --- | -| `OMNIROUTE_URL` | `http://localhost:20128` | `scripts/ad-hoc/regen-opencode-config.ts` | 查询 `/v1/models` 的 OmniRoute 实例基础 URL。 | -| `OMNIROUTE_KEY` | _(未设置)_ | `scripts/ad-hoc/regen-opencode-config.ts` | 认证 OmniRoute `/v1/models` 端点的 API key。未设置时回退到 `OPENCODE_API_KEY`。 | -| `OPENCODE_API_KEY` | _(未设置)_ | `scripts/ad-hoc/regen-opencode-config.ts` | 写入重新生成的 `opencode.json` 的 OpenCode 风格 API key (`sk-...`)。未设置时回退到 `OMNIROUTE_KEY`。 | +| 变量 | 默认值 | 源文件 | 说明 | +| ------------------ | ------------------------ | ----------------------------------------- | ---------------------------------------------------------------------------------------------------- | +| `OMNIROUTE_URL` | `http://localhost:20128` | `scripts/ad-hoc/regen-opencode-config.ts` | 查询 `/v1/models` 的 OmniRoute 实例基础 URL。 | +| `OMNIROUTE_KEY` | _(未设置)_ | `scripts/ad-hoc/regen-opencode-config.ts` | 认证 OmniRoute `/v1/models` 端点的 API key。未设置时回退到 `OPENCODE_API_KEY`。 | +| `OPENCODE_API_KEY` | _(未设置)_ | `scripts/ad-hoc/regen-opencode-config.ts` | 写入重新生成的 `opencode.json` 的 OpenCode 风格 API key (`sk-...`)。未设置时回退到 `OMNIROUTE_KEY`。 | ### 压缩离线评估工具(临时工具) 由离线压缩评估 CLI `scripts/compression-eval/index.ts` 使用。 正常运行不需要 — 仅供开发者工具使用。 -| 变量 | 默认值 | 源文件 | 说明 | -| --- | --- | --- | --- | +| 变量 | 默认值 | 源文件 | 说明 | +| ---------------------------- | ---------- | ----------------------------------- | ------------------------------------------------------------------------------------------------------------- | | `OMNIROUTE_EVAL_CREDENTIALS` | `{}`(空) | `scripts/compression-eval/index.ts` | 运维人员提供的 JSON 凭证,供离线压缩评估 CLI 使用的服务商使用(通过 `JSON.parse` 解析)。未设置时进行试运行。 | diff --git a/docs/i18n/zh-TW/docs/reference/ENVIRONMENT.md b/docs/i18n/zh-TW/docs/reference/ENVIRONMENT.md index 1d37957a59e6..b0647778683e 100644 --- a/docs/i18n/zh-TW/docs/reference/ENVIRONMENT.md +++ b/docs/i18n/zh-TW/docs/reference/ENVIRONMENT.md @@ -336,7 +336,7 @@ process.env[`${PROVIDER_ID}_USER_AGENT`] | Variable | Default Value | When to Update | | ------------------------ | --------------------------------------------- | ------------------------------------------------------------- | -| `CLAUDE_USER_AGENT` | `claude-cli/2.1.145 (external, cli)` | When Anthropic releases a new CLI version | +| `CLAUDE_USER_AGENT` | `claude-cli/2.1.219 (external, cli)` | When Anthropic releases a new CLI version | | `CODEX_USER_AGENT` | `codex-cli/0.132.0 (Windows 10.0.26200; x64)` | When OpenAI updates the Codex CLI | | `CODEX_CLIENT_VERSION` | `0.131.0` | Override Codex client version independently of full UA string | | `GITHUB_USER_AGENT` | `GitHubCopilotChat/0.45.1` | When GitHub Copilot Chat updates | diff --git a/docs/providers/AGENTROUTER.md b/docs/providers/AGENTROUTER.md index 6f4e39b6b2e2..aa28834c75df 100644 --- a/docs/providers/AGENTROUTER.md +++ b/docs/providers/AGENTROUTER.md @@ -130,7 +130,7 @@ request (see `open-sse/services/claudeCodeCompatible.ts`): | Header | Value | | ------------------------------------------- | ------------------------------------------------------------------------------------------------------- | | `Authorization` | `Bearer ` | -| `User-Agent` | `claude-cli/2.1.207 (external, sdk-cli)` | +| `User-Agent` | `claude-cli/2.1.219 (external, sdk-cli)` | | `anthropic-version` | `2023-06-01` | | `anthropic-beta` | `claude-code-20250219,interleaved-thinking-2025-05-14,effort-2025-11-24` | | Per-connection redact-thinking beta toggle | Adds `redact-thinking-2026-02-12` for upstreams that specifically require redacted thinking streams | diff --git a/docs/reference/ENVIRONMENT.md b/docs/reference/ENVIRONMENT.md index d3a5885badd7..f2ebe214eacc 100644 --- a/docs/reference/ENVIRONMENT.md +++ b/docs/reference/ENVIRONMENT.md @@ -528,7 +528,7 @@ process.env[`${PROVIDER_ID}_USER_AGENT`] | Variable | Default Value | When to Update | | -------------------------------- | --------------------------------------------- | ------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `CLAUDE_USER_AGENT` | `claude-cli/2.1.207 (external, cli)` | When Anthropic releases a new CLI version | +| `CLAUDE_USER_AGENT` | `claude-cli/2.1.219 (external, cli)` | When Anthropic releases a new CLI version | | `CLAUDE_DISABLE_TOOL_NAME_CLOAK` | `false` | `executors/base.ts` + `executors/cliproxyapi.ts` | Set to `1`/`true` to forward third-party harness tool names verbatim to Anthropic on both Anthropic-bound paths (native OAuth and CLIProxyAPI). By default the executor deterministically aliases non-Claude-Code tool names (Claude Code canonical mapping where one exists, otherwise PascalCase) and reverses them on the response via `_toolNameMap`, so harnesses with snake_case tools are not refused as fingerprinted third-party clients. Debugging only. | | `CODEX_USER_AGENT` | `codex-cli/0.142.0 (Windows 10.0.26200; x64)` | When OpenAI updates the Codex CLI | | `CODEX_CLIENT_VERSION` | `0.131.0` | Override Codex client version independently of full UA string | diff --git a/docs/security/STEALTH_GUIDE.md b/docs/security/STEALTH_GUIDE.md index dd737cb764b3..07f2b7dc6a54 100644 --- a/docs/security/STEALTH_GUIDE.md +++ b/docs/security/STEALTH_GUIDE.md @@ -88,9 +88,9 @@ Applied to: `system` blocks, all `messages[].content`, and `tools[].description` For third-party Anthropic relays that only accept "real Claude Code" traffic: -- `CLAUDE_CODE_COMPATIBLE_USER_AGENT = "claude-cli/2.1.207 (external, sdk-cli)"` +- `CLAUDE_CODE_COMPATIBLE_USER_AGENT = "claude-cli/2.1.219 (external, sdk-cli)"` - `CLAUDE_CODE_COMPATIBLE_STAINLESS_PACKAGE_VERSION = "0.94.0"` -- `CLAUDE_CODE_COMPATIBLE_STAINLESS_RUNTIME_VERSION = "v24.3.0"` +- `CLAUDE_CODE_COMPATIBLE_STAINLESS_RUNTIME_VERSION = "v26.3.0"` - `anthropic-beta = "claude-code-20250219,interleaved-thinking-2025-05-14,effort-2025-11-24"` by default - The per-connection "Enable redact-thinking beta" toggle adds `redact-thinking-2026-02-12` when a CC Compatible upstream specifically requires redacted thinking streams - The per-connection "Enable summarized thinking display" toggle stores `providerSpecificData.requestDefaults.summarizeThinking` and adds `display: "summarized"` to CC Compatible thinking requests that did not already set a display mode @@ -212,7 +212,7 @@ All MITM endpoints require management auth (`requireCliToolsAuth`). The sudo pas | Variable | Default | | ------------------------ | --------------------------------------------------------------- | -| `CLAUDE_USER_AGENT` | `claude-cli/2.1.207 (external, cli)` | +| `CLAUDE_USER_AGENT` | `claude-cli/2.1.219 (external, cli)` | | `CODEX_USER_AGENT` | `codex-cli/0.142.0 (Windows 10.0.26200; x64)` | | `GITHUB_USER_AGENT` | `GitHubCopilotChat/0.54.0` | | `ANTIGRAVITY_USER_AGENT` | `antigravity/2.0.1 linux/arm64 google-api-nodejs-client/10.3.0` | diff --git a/open-sse/config/anthropicHeaders.ts b/open-sse/config/anthropicHeaders.ts index fa8d1e221245..7b04eb6c456c 100644 --- a/open-sse/config/anthropicHeaders.ts +++ b/open-sse/config/anthropicHeaders.ts @@ -1,3 +1,12 @@ +import { + CLAUDE_CODE_CLIENT_BILLING_VERSION, + CLAUDE_CODE_CLIENT_BUILD_REVISION, + CLAUDE_CODE_CLIENT_VERSION, + CLAUDE_CODE_RUNTIME_VERSION, + CLAUDE_CODE_SDK_PACKAGE_VERSION, + getClaudeCodeUserAgent, +} from "@/shared/constants/claudeCodeClient"; + export const ANTHROPIC_VERSION_HEADER = "2023-06-01"; const ANTHROPIC_BETA_BASE = Object.freeze([ @@ -121,7 +130,9 @@ export function normalizeAnthropicHeaderVariants(headers: Record } } -export const CLAUDE_CLI_VERSION = "2.1.207"; -export const CLAUDE_CLI_USER_AGENT = `claude-cli/${CLAUDE_CLI_VERSION} (external, cli)`; -export const CLAUDE_CLI_STAINLESS_PACKAGE_VERSION = "0.94.0"; -export const CLAUDE_CLI_STAINLESS_RUNTIME_VERSION = "v24.3.0"; +export const CLAUDE_CLI_VERSION = CLAUDE_CODE_CLIENT_VERSION; +export const CLAUDE_CLI_BUILD_REVISION = CLAUDE_CODE_CLIENT_BUILD_REVISION; +export const CLAUDE_CLI_BILLING_VERSION = CLAUDE_CODE_CLIENT_BILLING_VERSION; +export const CLAUDE_CLI_USER_AGENT = getClaudeCodeUserAgent("cli"); +export const CLAUDE_CLI_STAINLESS_PACKAGE_VERSION = CLAUDE_CODE_SDK_PACKAGE_VERSION; +export const CLAUDE_CLI_STAINLESS_RUNTIME_VERSION = CLAUDE_CODE_RUNTIME_VERSION; diff --git a/open-sse/config/claudeCodeCompatibleIdentity.ts b/open-sse/config/claudeCodeCompatibleIdentity.ts new file mode 100644 index 000000000000..31b7997f2aa3 --- /dev/null +++ b/open-sse/config/claudeCodeCompatibleIdentity.ts @@ -0,0 +1,23 @@ +import { + CLAUDE_CODE_CLIENT_VERSION, + CLAUDE_CODE_RUNTIME_VERSION, + CLAUDE_CODE_SDK_PACKAGE_VERSION, + getClaudeCodeUserAgent, +} from "@/shared/constants/claudeCodeClient"; + +export const CLAUDE_CODE_COMPATIBLE_VERSION = CLAUDE_CODE_CLIENT_VERSION; +export const CLAUDE_CODE_COMPATIBLE_USER_AGENT = getClaudeCodeUserAgent("sdk-cli"); +export const CLAUDE_CODE_COMPATIBLE_STAINLESS_PACKAGE_VERSION = CLAUDE_CODE_SDK_PACKAGE_VERSION; +export const CLAUDE_CODE_COMPATIBLE_STAINLESS_RUNTIME_VERSION = CLAUDE_CODE_RUNTIME_VERSION; +const CONTEXT_1M_NATIVE_MODELS = ["claude-opus-5"]; + +export function modelHasNativeContext1m(model: string | null | undefined): boolean { + const normalizedModel = String(model || "") + .trim() + .toLowerCase() + .replace(/-\d{8}$/, ""); + + return CONTEXT_1M_NATIVE_MODELS.some( + (supported) => normalizedModel === supported || normalizedModel.startsWith(`${supported}-`) + ); +} diff --git a/open-sse/config/glmProvider.ts b/open-sse/config/glmProvider.ts index 951e9e550d46..f8acfd4031fb 100644 --- a/open-sse/config/glmProvider.ts +++ b/open-sse/config/glmProvider.ts @@ -1,3 +1,5 @@ +import { getClaudeCodeUserAgent } from "@/shared/constants/claudeCodeClient"; + import { ANTHROPIC_VERSION_HEADER } from "./anthropicHeaders.ts"; type JsonRecord = Record; @@ -150,7 +152,7 @@ export const GLMT_REQUEST_DEFAULTS = Object.freeze({ }); export const GLM_COUNT_TOKENS_TIMEOUT_MS = 3_000; -export const GLM_CLAUDE_CODE_USER_AGENT = "claude-cli/2.1.207 (external, sdk-cli)"; +export const GLM_CLAUDE_CODE_USER_AGENT = getClaudeCodeUserAgent("sdk-cli"); export const GLM_ANTHROPIC_BETA = [ "claude-code-20250219", "interleaved-thinking-2025-05-14", diff --git a/open-sse/config/providers/registry/anthropic/index.ts b/open-sse/config/providers/registry/anthropic/index.ts index 4ea19e8381da..2a3726276d2f 100644 --- a/open-sse/config/providers/registry/anthropic/index.ts +++ b/open-sse/config/providers/registry/anthropic/index.ts @@ -16,6 +16,20 @@ export const anthropicProvider: RegistryEntry = { "Anthropic-Beta": ANTHROPIC_BETA_API_KEY, }, models: [ + { + id: "claude-fable-5", + name: "Claude Fable 5", + contextLength: 1048576, + unsupportedParams: ["temperature", "top_p", "top_k"], + }, + { + id: "claude-opus-5", + name: "Claude Opus 5", + contextLength: 1000000, + maxOutputTokens: 128000, + supportsXHighEffort: true, + unsupportedParams: ["temperature", "top_p", "top_k"], + }, { id: "claude-opus-4.7", name: "Claude Opus 4.7", @@ -30,12 +44,6 @@ export const anthropicProvider: RegistryEntry = { // Opus 4.7+ (incl. 4.8, Fable 5) reject non-default sampling with a 400. Mirrors claude-opus-4.7. unsupportedParams: ["temperature", "top_p", "top_k"], }, - { - id: "claude-fable-5", - name: "Claude Fable 5", - contextLength: 1048576, - unsupportedParams: ["temperature", "top_p", "top_k"], - }, { id: "claude-opus-4.6", name: "Claude Opus 4.6" }, { id: "claude-opus-4.5", name: "Claude Opus 4.5" }, { diff --git a/open-sse/config/providers/registry/claude/index.ts b/open-sse/config/providers/registry/claude/index.ts index d142872de842..481c831c2f12 100644 --- a/open-sse/config/providers/registry/claude/index.ts +++ b/open-sse/config/providers/registry/claude/index.ts @@ -37,6 +37,14 @@ export const claudeProvider: RegistryEntry = { // is fixed; reasoning is steered by output_config.effort). Strip them before dispatch. unsupportedParams: ["temperature", "top_p", "top_k"], }, + { + id: "claude-opus-5", + name: "Claude Opus 5", + contextLength: 1000000, + maxOutputTokens: 128000, + supportsXHighEffort: true, + unsupportedParams: ["temperature", "top_p", "top_k"], + }, { id: "claude-opus-4-8", name: "Claude Opus 4.8", diff --git a/open-sse/config/providers/registry/claude/web/index.ts b/open-sse/config/providers/registry/claude/web/index.ts index f3a62ca67604..c701ff9aa3ff 100644 --- a/open-sse/config/providers/registry/claude/web/index.ts +++ b/open-sse/config/providers/registry/claude/web/index.ts @@ -10,6 +10,15 @@ export const claude_webProvider: RegistryEntry = { authHeader: "cookie", models: [ { id: "claude-fable-5", name: "Claude Fable 5 (web)", toolCalling: false }, + { + id: "claude-opus-5", + name: "Claude Opus 5 (web)", + toolCalling: false, + supportsReasoning: true, + supportsVision: true, + contextLength: 1000000, + maxOutputTokens: 128000, + }, { id: "claude-opus-4-8", name: "Claude Opus 4.8 (web)", toolCalling: false }, { id: "claude-opus-4-7", name: "Claude Opus 4.7 (web)", toolCalling: false }, { id: "claude-opus-4-6", name: "Claude Opus 4.6 (web)", toolCalling: false }, diff --git a/open-sse/config/providers/registry/ghe-copilot/index.ts b/open-sse/config/providers/registry/ghe-copilot/index.ts index fb5c6ca438ad..5e88116d69aa 100644 --- a/open-sse/config/providers/registry/ghe-copilot/index.ts +++ b/open-sse/config/providers/registry/ghe-copilot/index.ts @@ -33,6 +33,13 @@ export const gheCopilotProvider: RegistryEntry = { contextLength: 1000000, maxOutputTokens: 64000, }, + { + id: "claude-opus-5", + name: "Claude Opus 5", + contextLength: 1000000, + maxOutputTokens: 64000, + unsupportedParams: ["temperature", "top_p", "top_k"], + }, { id: "claude-opus-4.8-fast", name: "Claude Opus 4.8 (fast mode)", diff --git a/open-sse/config/providers/registry/github/index.ts b/open-sse/config/providers/registry/github/index.ts index 1dc6a22764b9..9513afdc1e49 100644 --- a/open-sse/config/providers/registry/github/index.ts +++ b/open-sse/config/providers/registry/github/index.ts @@ -43,6 +43,14 @@ export const githubProvider: RegistryEntry = { contextLength: 1000000, maxOutputTokens: 64000, }, + { + id: "claude-opus-5", + name: "Claude Opus 5", + targetFormat: "claude", + contextLength: 1000000, + maxOutputTokens: 64000, + unsupportedParams: ["temperature", "top_p", "top_k"], + }, { id: "claude-opus-4.8-fast", name: "Claude Opus 4.8 (fast mode)", diff --git a/open-sse/executors/base.ts b/open-sse/executors/base.ts index 3aa1e7ce0711..4ad99520db71 100644 --- a/open-sse/executors/base.ts +++ b/open-sse/executors/base.ts @@ -1,5 +1,7 @@ import { HTTP_STATUS, FETCH_TIMEOUT_MS } from "../config/constants.ts"; import { + CLAUDE_CLI_BILLING_VERSION, + CLAUDE_CLI_STAINLESS_RUNTIME_VERSION, mergeClientAnthropicBeta, normalizeAnthropicHeaderVariants, } from "../config/anthropicHeaders.ts"; @@ -45,6 +47,7 @@ import { appendAnthropicBetaHeader, CONTEXT_1M_BETA_HEADER, enforceThinkingTemperature, + modelHasNativeContext1m, modelSupportsContext1mBeta, } from "../services/claudeCodeCompatible.ts"; import { getClaudeCodeCompatibleRequestDefaults } from "@/lib/providers/requestDefaults"; @@ -67,7 +70,6 @@ import { randomUUID } from "node:crypto"; import { CLAUDE_CODE_VERSION, CLAUDE_CODE_STAINLESS_VERSION, - buildHashFor, buildUserIdJson, getSessionId, parseUpstreamMetadataUserId, @@ -77,7 +79,6 @@ import { selectBetaFlags, stainlessArch, stainlessOS, - stainlessRuntimeVersion, stripProxyToolPrefix, } from "./claudeIdentity.ts"; import { withForcedResponsesUpstream } from "./forceResponsesUpstream.ts"; @@ -728,7 +729,9 @@ export class BaseExecutor { modelSupportsContext1mBeta(model) && !isClaudeCodeCompatible(this.provider); const shouldForwardCcCompatibleContext1m = - isClaudeCodeCompatible(this.provider) && ccRequestDefaults.context1m === true; + isClaudeCodeCompatible(this.provider) && + ccRequestDefaults.context1m === true && + !modelHasNativeContext1m(model); if (shouldForwardExtendedContext || shouldForwardCcCompatibleContext1m) { appendAnthropicBetaHeader(headers, CONTEXT_1M_BETA_HEADER); } @@ -991,9 +994,7 @@ export class BaseExecutor { // system[0] (billing) and system[1] (sentinel) must not carry // cache_control — that belongs on upstream prompt blocks at [2..]. - const dayStamp = new Date().toISOString().slice(0, 10); - const buildHash = buildHashFor(CLAUDE_CODE_VERSION, dayStamp); - const billingLine = `x-anthropic-billing-header: cc_version=${CLAUDE_CODE_VERSION}.${buildHash}; cc_entrypoint=cli; cch=00000;`; + const billingLine = `x-anthropic-billing-header: cc_version=${CLAUDE_CLI_BILLING_VERSION}; cc_entrypoint=cli; cch=00000;`; const SENTINEL = "You are Claude Code, Anthropic's official CLI for Claude."; const sysBlocks: Array> = Array.isArray(tb.system) @@ -1081,13 +1082,13 @@ export class BaseExecutor { Object.assign(headers, ccHeaders); delete headers["X-Stainless-Helper-Method"]; - // Stainless OS/Arch/Runtime are host-derived (Stainless SDK does the - // same at runtime). Hardcoding them was a unique-per-deployment tell. + // OS/arch follow the host running the signed binary. Runtime version + // is pinned to the captured CLI wire image, not OmniRoute's Node. headers["X-Stainless-Arch"] = stainlessArch(); headers["X-Stainless-Lang"] = "js"; headers["X-Stainless-OS"] = stainlessOS(); headers["X-Stainless-Runtime"] = "node"; - headers["X-Stainless-Runtime-Version"] = stainlessRuntimeVersion(); + headers["X-Stainless-Runtime-Version"] = CLAUDE_CLI_STAINLESS_RUNTIME_VERSION; headers["X-Stainless-Retry-Count"] = "0"; delete headers["X-Stainless-Os"]; diff --git a/open-sse/executors/claude-web/payload.ts b/open-sse/executors/claude-web/payload.ts index f933f977c103..74a88ab1b496 100644 --- a/open-sse/executors/claude-web/payload.ts +++ b/open-sse/executors/claude-web/payload.ts @@ -176,6 +176,7 @@ export function wantsExtendedThinking(body: Record): boolean { } const CLAUDE_WEB_REASONING_EFFORTS = ["low", "medium", "high", "xhigh", "max"] as const; +const CLAUDE_WEB_OPUS_5_MODEL = "claude-opus-5"; /** * Resolve the caller's explicit reasoning level into the effort values @@ -251,12 +252,26 @@ function createConversationParams(model: string) { }; } +function resolveClaudeWebThinkingMode( + model: string, + reasoningEffort: ReturnType +): { effort: string; thinkingMode: string } { + if (model.trim().toLowerCase() === CLAUDE_WEB_OPUS_5_MODEL) { + return { effort: reasoningEffort ?? "high", thinkingMode: "auto" }; + } + if (reasoningEffort) { + return { effort: reasoningEffort, thinkingMode: "extended" }; + } + return { effort: "low", thinkingMode: "off" }; +} + function buildClaudeWebPayload( body: Record, model: string, reasoningEffort: ReturnType, turn: ClaudeWebTurnFields ): ClaudeWebRequestPayload { + const thinking = resolveClaudeWebThinkingMode(model, reasoningEffort); return { prompt: turn.prompt, model, @@ -270,11 +285,11 @@ function buildClaudeWebPayload( }, ...(turn.parentMessageUuid ? { parent_message_uuid: turn.parentMessageUuid } : {}), attachments: [], - effort: reasoningEffort ?? "low", + effort: thinking.effort, files: [], sync_sources: [], rendering_mode: "messages", - thinking_mode: reasoningEffort ? "extended" : "off", + thinking_mode: thinking.thinkingMode, ...(turn.toolStates ? { tool_states: turn.toolStates } : {}), ...(turn.isNewConversation ? { create_conversation_params: createConversationParams(model) } diff --git a/open-sse/executors/claude-web/stream.ts b/open-sse/executors/claude-web/stream.ts index e880cace9de8..617a0d99fa89 100644 --- a/open-sse/executors/claude-web/stream.ts +++ b/open-sse/executors/claude-web/stream.ts @@ -182,6 +182,14 @@ function deltaText(delta: Record, fields: string[]): string { throw new ClaudeWebProtocolError("Content delta text is invalid"); } +function thinkingSummaryText(delta: Record): string { + if (typeof delta.summary === "string") return delta.summary; + if (delta.summary && typeof delta.summary === "object" && !Array.isArray(delta.summary)) { + return deltaText(delta.summary as Record, ["summary", "text", "thinking"]); + } + return deltaText(delta, ["text", "thinking"]); +} + interface ProtocolState { phase: StreamPhase; openBlocks: Map; @@ -265,7 +273,7 @@ function handleContentBlockDelta( return { kind: "reasoning", text: deltaText(delta, ["thinking", "text"]) }; } if (delta.type === "thinking_summary_delta" && block === "thinking") { - return { kind: "reasoning", text: deltaText(delta, ["summary", "text", "thinking"]) }; + return { kind: "reasoning", text: thinkingSummaryText(delta) }; } return protocolFailure(state, "Content delta type does not match its block"); } diff --git a/open-sse/executors/claudeIdentity.ts b/open-sse/executors/claudeIdentity.ts index f8a94a32cea2..c9544c6743fc 100644 --- a/open-sse/executors/claudeIdentity.ts +++ b/open-sse/executors/claudeIdentity.ts @@ -10,11 +10,16 @@ import { createHash, randomBytes, randomUUID } from "node:crypto"; +import { + CLAUDE_CODE_CLIENT_VERSION, + CLAUDE_CODE_SDK_PACKAGE_VERSION, +} from "@/shared/constants/claudeCodeClient"; + // ---------- Versions ------------------------------------------------------ -export const CLAUDE_CODE_VERSION = "2.1.207"; +export const CLAUDE_CODE_VERSION = CLAUDE_CODE_CLIENT_VERSION; /** Bundled @anthropic-ai/sdk version for the pinned CLI release. */ -export const CLAUDE_CODE_STAINLESS_VERSION = "0.94.0"; +export const CLAUDE_CODE_STAINLESS_VERSION = CLAUDE_CODE_SDK_PACKAGE_VERSION; // ---------- Stainless OS / Arch / Runtime -------------------------------- @@ -157,16 +162,19 @@ export async function fetchClaudeBootstrap(accessToken: string): Promise null); - const acct = data?.oauth_account; + const data: unknown = await res.json().catch(() => null); + const acct = + data && typeof data === "object" ? (data as Record).oauth_account : null; if (!acct || typeof acct !== "object") return null; + const account = acct as Record; + const stringOrNull = (value: unknown) => (typeof value === "string" ? value : null); return { - account_uuid: acct.account_uuid || null, - account_email: acct.account_email || null, - organization_uuid: acct.organization_uuid || null, - organization_name: acct.organization_name || null, - organization_type: acct.organization_type || null, - organization_rate_limit_tier: acct.organization_rate_limit_tier || null, + account_uuid: stringOrNull(account.account_uuid), + account_email: stringOrNull(account.account_email), + organization_uuid: stringOrNull(account.organization_uuid), + organization_name: stringOrNull(account.organization_name), + organization_type: stringOrNull(account.organization_type), + organization_rate_limit_tier: stringOrNull(account.organization_rate_limit_tier), }; } catch { return null; @@ -261,14 +269,14 @@ export function parseUpstreamMetadataUserId( const md = body.metadata as Record | undefined; const raw = md?.user_id; if (typeof raw !== "string" || raw.length === 0) return null; - let parsed: any; + let parsed: unknown; try { parsed = JSON.parse(raw); } catch { return null; } if (!parsed || typeof parsed !== "object") return null; - const { device_id, account_uuid, session_id } = parsed; + const { device_id, account_uuid, session_id } = parsed as Record; if ( typeof device_id !== "string" || !HEX64_RE.test(device_id) || @@ -293,8 +301,10 @@ const HEAVY_AGENT_BETA_MODEL_PREFIXES = ["claude-opus", "claude-sonnet"]; /** * Models that support the context-1m beta tier. Only Opus is eligible; * Sonnet trips long-context credit gates under OAuth full-agent traffic. + * Opus 5 is excluded because its 1M context window is native. */ const CONTEXT_1M_BETA_MODEL_PREFIXES = ["claude-opus"]; +const CONTEXT_1M_NATIVE_MODEL_PREFIXES = ["claude-opus-5"]; function matchesModelPrefix(model: unknown, prefixes: string[]): boolean { if (typeof model !== "string") return false; @@ -307,7 +317,10 @@ function isHeavyAgentModel(model: unknown): boolean { } function isContext1mModel(model: unknown): boolean { - return matchesModelPrefix(model, CONTEXT_1M_BETA_MODEL_PREFIXES); + return ( + matchesModelPrefix(model, CONTEXT_1M_BETA_MODEL_PREFIXES) && + !matchesModelPrefix(model, CONTEXT_1M_NATIVE_MODEL_PREFIXES) + ); } /** @@ -360,14 +373,15 @@ export function selectBetaFlags( const isFullAgent = hasTools && hasSystem; const effectiveModel = model ?? (typeof b.model === "string" ? b.model : ""); const isHeavyAgent = isFullAgent && isHeavyAgentModel(effectiveModel); + const isOpusAgent = + isFullAgent && matchesModelPrefix(effectiveModel, CONTEXT_1M_BETA_MODEL_PREFIXES); const isContext1m = isFullAgent && isContext1mModel(effectiveModel); const flags: string[] = []; if (isFullAgent) flags.push("claude-code-20250219"); flags.push("oauth-2025-04-20"); - if (isContext1m) { - flags.push("context-1m-2025-08-07", "mid-conversation-system-2026-04-07"); - } + if (isContext1m) flags.push("context-1m-2025-08-07"); + if (isOpusAgent) flags.push("mid-conversation-system-2026-04-07"); // Thinking betas: gated on the client header (#3415). interleaved-thinking forces // interleaved-thinking semantics that conflict with a tool_choice-forced turn, // producing malformed opus tool_use streams when the client never asked for it. @@ -392,18 +406,6 @@ export function selectBetaFlags( return flags.join(","); } -// ---------- billing-header build hash ------------------------------------ - -/** - * 3-char build hash for the billing header `cc_version=X.Y.Z.HASH`. Stable - * per (day, version) — Anthropic does not appear to validate the value, so - * we keep prompt-cache prefix stable within a day for a given version - * without coupling to any captured value. - */ -export function buildHashFor(version: string, dayStamp: string): string { - return createHash("sha256").update(`${dayStamp}${version}`).digest("hex").slice(0, 3); -} - // ---------- Tool-name normalisation -------------------------------------- const TOOL_PREFIX = "proxy_"; diff --git a/open-sse/handlers/chatCore.ts b/open-sse/handlers/chatCore.ts index 2cbb8eb56980..8f014162e6d2 100644 --- a/open-sse/handlers/chatCore.ts +++ b/open-sse/handlers/chatCore.ts @@ -114,7 +114,10 @@ import { isOpencodeGoProvider, stripBooleanReasoning, } from "../services/opencodeReasoningSanitizer.ts"; -import { normalizeClaudeAdaptiveThinking } from "../services/claudeAdaptiveThinking.ts"; +import { + normalizeClaudeAdaptiveThinking, + normalizeClaudeDisabledThinkingEffort, +} from "../services/claudeAdaptiveThinking.ts"; import { normalizeClaudeHaikuConstraints } from "../services/claudeHaikuConstraints.ts"; import { applyDefaultReasoningEffort } from "../services/defaultReasoningEffort.ts"; import { echoModelInObject } from "../services/responseModelEcho.ts"; @@ -2244,6 +2247,14 @@ export async function handleChatCore({ // defaults) to `{type:"adaptive"}` — effort stays on `output_config.effort`. Keyed on // the resolved upstream model, so it covers every routing mode. See claudeAdaptiveThinking.ts. translatedBody = normalizeClaudeAdaptiveThinking(translatedBody, finalModelToUpstream); + // Opus 5 allows disabled thinking only through high effort on Anthropic's direct + // Messages API. The helper scopes this constraint to `anthropic` and `claude`; + // GitHub Copilot and Claude Web use separate upstream contracts. + translatedBody = normalizeClaudeDisabledThinkingEffort( + translatedBody, + finalModelToUpstream, + provider + ); // Claude Haiku rejects `thinking.type:"adaptive"` and `output_config.effort` // (both Sonnet 4.6 / Opus 4.5+ only). Several paths can still emit those // shapes on a Haiku target — native passthrough, reasoning_effort buckets, @@ -2989,11 +3000,7 @@ export async function handleChatCore({ rawResult._executionCredentials?.connectionId && rawResult._executionCredentials?.apiKey ) { - recordKeyHealthStatus( - status, - rawResult._executionCredentials, - rawResult.transport - ); + recordKeyHealthStatus(status, rawResult._executionCredentials, rawResult.transport); } releaseRawResultAccountSemaphore = typeof rawResult._accountSemaphoreRelease === "function" diff --git a/open-sse/services/ccBridgeTransforms.ts b/open-sse/services/ccBridgeTransforms.ts index 41deb102a426..d13adbeacc06 100644 --- a/open-sse/services/ccBridgeTransforms.ts +++ b/open-sse/services/ccBridgeTransforms.ts @@ -21,6 +21,11 @@ */ import { createHash } from "node:crypto"; +import { + CLAUDE_CODE_CLIENT_BUILD_REVISION, + CLAUDE_CODE_CLIENT_VERSION, +} from "@/shared/constants/claudeCodeClient"; + // ──────────────────────────────────────────────────────────────────────────── // DSL types // ──────────────────────────────────────────────────────────────────────────── @@ -96,8 +101,10 @@ export interface InjectBillingHeaderOp { * - static-zero: emit "00000" (relay endpoints don't validate) */ cchAlgo: "sha256-first-user" | "xxhash64-body" | "static-zero"; - /** Override the embedded `cc_version=` value. Defaults to `2.1.207`. */ + /** Override the embedded `cc_version=` value. Defaults to `2.1.219`. */ version?: string; + /** Override its captured build revision. Defaults to a computed compatibility suffix. */ + buildRevision?: string; } export interface CcBridgeTransformsConfig { @@ -114,7 +121,7 @@ export const CCH_SALT = "59cf53e54c78"; /** Character positions sampled from the first user message text. */ export const CCH_POSITIONS = [4, 7, 20] as const; /** Default `cc_version=` value embedded in the billing header. */ -export const DEFAULT_CLAUDE_CODE_VERSION = "2.1.207"; +export const DEFAULT_CLAUDE_CODE_VERSION = CLAUDE_CODE_CLIENT_VERSION; /** Identity sentinel prepended for Claude Agent SDK callers. */ export const CLAUDE_AGENT_SDK_IDENTITY = "You are a Claude agent, built on Anthropic's Claude Agent SDK."; @@ -176,6 +183,7 @@ export const DEFAULT_CC_BRIDGE_PIPELINE: TransformOp[] = [ entrypoint: "sdk-cli", versionFormat: "ex-machina", cchAlgo: "sha256-first-user", + buildRevision: CLAUDE_CODE_CLIENT_BUILD_REVISION, }, ]; @@ -267,6 +275,7 @@ interface BuildBillingHeaderOptions { versionFormat: "ex-machina" | "omniroute-daystamp"; cchAlgo: "sha256-first-user" | "xxhash64-body" | "static-zero"; version?: string; + buildRevision?: string; now?: Date; } @@ -287,9 +296,10 @@ export function buildBillingHeaderValue( const firstUserText = extractFirstUserMessageText(messages); const suffix = - options.versionFormat === "omniroute-daystamp" + options.buildRevision ?? + (options.versionFormat === "omniroute-daystamp" ? computeDaystampVersionSuffix(version, options.now) - : computeExMachinaVersionSuffix(firstUserText, version); + : computeExMachinaVersionSuffix(firstUserText, version)); let cch: string; switch (options.cchAlgo) { @@ -462,6 +472,7 @@ function applyInjectBillingHeader( versionFormat: op.versionFormat, cchAlgo: op.cchAlgo, version: op.version, + buildRevision: op.buildRevision, }); // Idempotency: replace any existing billing header block (ex-machina + native diff --git a/open-sse/services/claudeAdaptiveThinking.ts b/open-sse/services/claudeAdaptiveThinking.ts index 1239eabc04c0..d65c79de2b6a 100644 --- a/open-sse/services/claudeAdaptiveThinking.ts +++ b/open-sse/services/claudeAdaptiveThinking.ts @@ -1,6 +1,10 @@ -import { isAdaptiveThinkingOnly } from "@/shared/constants/modelSpecs.ts"; +import { + getMaxEffortWhenThinkingDisabled, + isAdaptiveThinkingOnly, +} from "@/shared/constants/modelSpecs.ts"; type JsonRecord = Record; +const DIRECT_ANTHROPIC_API_PROVIDERS = new Set(["anthropic", "claude"]); function asRecord(value: unknown): JsonRecord | null { return value && typeof value === "object" && !Array.isArray(value) ? (value as JsonRecord) : null; @@ -9,20 +13,22 @@ function asRecord(value: unknown): JsonRecord | null { /** * Collapse manual extended thinking to adaptive for Claude models that no longer accept it. * - * Claude Opus 4.7 and later (Opus 4.7/4.8, Fable 5) removed manual extended thinking: the + * Claude Opus 4.7 and later (Opus 4.7/4.8/5, Fable 5) removed manual extended thinking: the * Messages API returns HTTP 400 for `thinking.type:"enabled"` and for ANY * `thinking.budget_tokens`. Reasoning is steered exclusively by `output_config.effort` - * (Anthropic migration guide, 2026-05-19). OmniRoute can still produce a manual thinking - * block on these models from several paths — a Claude-native passthrough client sending the - * legacy shape, the OpenAI→Claude translator's reasoning_effort buckets, or a per-model - * thinking default — so this is the final, provider-agnostic guard keyed on the target model. + * (Anthropic's current model migration guidance). OmniRoute can still produce a manual + * thinking block on these models from several paths — a Claude-native passthrough client + * sending the legacy shape, the OpenAI→Claude translator's reasoning_effort buckets, or a + * per-model thinking default — so this is the final, provider-agnostic guard keyed on the + * target model. * * Returns a NEW object only when it changes the body: - * - `thinking.type:"enabled"` → `"adaptive"` (the only supported mode); + * - `thinking.type:"enabled"` → `"adaptive"` (the only supported enabled mode); * - `thinking.budget_tokens` / `thinking.max_tokens` → dropped (rejected extras). * `thinking.type:"adaptive"` is left as-is (just stripped of any stray budget), and * `thinking.type:"disabled"` is left untouched — that's handled separately by - * `normalizeThinkingForModel` for the models that reject `disabled` (#3554). + * `normalizeThinkingForModel` for models that reject `disabled` (#3554), and by + * `normalizeClaudeDisabledThinkingEffort` for direct Anthropic API constraints. * * No-op (returns the same reference) when the model is not adaptive-only, when there is no * thinking object, or when the thinking object already carries no manual-budget signal — @@ -50,3 +56,35 @@ export function normalizeClaudeAdaptiveThinking>( + body: T, + model: string | null | undefined, + provider: string | null | undefined +): T { + if (!provider || !DIRECT_ANTHROPIC_API_PROVIDERS.has(provider)) return body; + + const disabledEffortCap = getMaxEffortWhenThinkingDisabled(model); + if (disabledEffortCap !== "high") return body; + + const record = asRecord(body); + const thinking = asRecord(record?.thinking); + const outputConfig = asRecord(record?.output_config); + const effort = typeof outputConfig?.effort === "string" ? outputConfig.effort.toLowerCase() : ""; + if (thinking?.type !== "disabled" || !outputConfig || (effort !== "xhigh" && effort !== "max")) { + return body; + } + + return { + ...record, + output_config: { ...outputConfig, effort: disabledEffortCap }, + } as T; +} diff --git a/open-sse/services/claudeCodeCompatible.ts b/open-sse/services/claudeCodeCompatible.ts index 5e3f5c7ae8d6..79d277988141 100644 --- a/open-sse/services/claudeCodeCompatible.ts +++ b/open-sse/services/claudeCodeCompatible.ts @@ -2,6 +2,11 @@ import { createHash, randomUUID } from "node:crypto"; import { getStainlessTimeoutSeconds } from "@/shared/utils/runtimeTimeouts"; import { ANTHROPIC_VERSION_HEADER } from "../config/anthropicHeaders.ts"; +import { + CLAUDE_CODE_COMPATIBLE_STAINLESS_PACKAGE_VERSION, + CLAUDE_CODE_COMPATIBLE_STAINLESS_RUNTIME_VERSION, + CLAUDE_CODE_COMPATIBLE_USER_AGENT, +} from "../config/claudeCodeCompatibleIdentity.ts"; import { supportsClaudeMaxEffort, supportsXHighEffort } from "../config/providerModels.ts"; import { prepareClaudeRequest } from "../translator/helpers/claudeHelper.ts"; import { signRequestBody } from "./claudeCodeCCH.ts"; @@ -43,10 +48,7 @@ export { CLAUDE_CODE_COMPATIBLE_REDACT_THINKING_BETA, resolveClaudeCodeCompatibleAnthropicBeta, } from "./claudeCodeCompatibleBeta.ts"; -export const CLAUDE_CODE_COMPATIBLE_VERSION = "2.1.207"; -export const CLAUDE_CODE_COMPATIBLE_USER_AGENT = "claude-cli/2.1.207 (external, sdk-cli)"; -export const CLAUDE_CODE_COMPATIBLE_STAINLESS_PACKAGE_VERSION = "0.94.0"; -export const CLAUDE_CODE_COMPATIBLE_STAINLESS_RUNTIME_VERSION = "v24.3.0"; +export * from "../config/claudeCodeCompatibleIdentity.ts"; export const CONTEXT_1M_BETA_HEADER = "context-1m-2025-08-07"; const CLAUDE_CODE_COMPATIBLE_DEFAULT_SYSTEM_BLOCKS = [ { @@ -65,7 +67,6 @@ const CONTEXT_1M_SUPPORTED_MODELS = [ export const CLAUDE_CODE_COMPATIBLE_STAINLESS_TIMEOUT_SECONDS = getStainlessTimeoutSeconds( process.env ); - type HeaderLike = | Headers | Record diff --git a/open-sse/services/claudeTlsClient.ts b/open-sse/services/claudeTlsClient.ts index 3e8d90a19915..157c5996aa4f 100644 --- a/open-sse/services/claudeTlsClient.ts +++ b/open-sse/services/claudeTlsClient.ts @@ -405,7 +405,12 @@ export async function tlsFetchStreaming( // that race; if the request actually fails before producing any bytes, // the timeout falls through to the requestPromise drain below (returning // the real upstream status). - const ready = await waitForContent(path, 5_000, requestPromise); + // Do not impose a second, shorter first-byte timeout here. Opus-class + // models can legitimately take more than five seconds before emitting the + // first SSE event. `requestPromise` is already guarded by the configured + // wire timeout plus the JS hard-timeout grace, so waiting until either the + // file has data or that promise settles remains bounded. + const ready = await waitForContent(path, requestPromise); if (!ready) { const r = await requestPromise.catch( (e) => ({ status: 502, headers: {}, body: String(e) }) as TlsResponseLike @@ -501,7 +506,6 @@ async function readFirstBytes(path: string, n: number): Promise { */ async function waitForContent( path: string, - timeoutMs: number, requestPromise: Promise ): Promise { let requestSettled = false; @@ -513,8 +517,7 @@ async function waitForContent( requestSettled = true; } ); - const start = Date.now(); - while (Date.now() - start < timeoutMs) { + while (true) { try { const s = await stat(path); if (s.size > 0) return true; @@ -526,7 +529,6 @@ async function waitForContent( if (requestSettled) return false; await sleep(25); } - return false; } function tailFile( diff --git a/open-sse/services/githubCopilotModels.ts b/open-sse/services/githubCopilotModels.ts index 766666ecbe26..b1066716f28c 100644 --- a/open-sse/services/githubCopilotModels.ts +++ b/open-sse/services/githubCopilotModels.ts @@ -22,6 +22,7 @@ import { getGitHubCopilotChatHeaders } from "../config/providerHeaderProfiles.ts export const GITHUB_COPILOT_MODELS_URL = "https://api.githubcopilot.com/models"; export const GITHUB_COPILOT_MODEL_ALLOWLIST = [ "claude-fable-5", + "claude-opus-5", "claude-opus-4.8-fast", "claude-opus-4.8", "claude-opus-4.7", diff --git a/open-sse/services/modelFamilyFallback.ts b/open-sse/services/modelFamilyFallback.ts index 3ebfb92bfe34..9f3ff882f32f 100644 --- a/open-sse/services/modelFamilyFallback.ts +++ b/open-sse/services/modelFamilyFallback.ts @@ -78,6 +78,7 @@ const MODEL_FAMILIES: Record = { "claude-fable-5": ["claude-opus-4-8", "claude-opus-4-7", "claude-sonnet-5"], // Claude Opus family + "claude-opus-5": ["claude-opus-4-8", "claude-opus-4-7", "claude-sonnet-5"], "claude-opus-4-8": ["claude-opus-4-7", "claude-opus-4-6", "claude-sonnet-5"], "claude-opus-4-7": ["claude-opus-4-6", "claude-opus-4-5-20251101", "claude-sonnet-5"], "claude-opus-4-6": ["claude-opus-4-6-thinking", "claude-opus-4-5-20251101", "claude-sonnet-5"], diff --git a/open-sse/services/providerCostData.ts b/open-sse/services/providerCostData.ts index b9809dea9991..d9a4e0a8abe4 100644 --- a/open-sse/services/providerCostData.ts +++ b/open-sse/services/providerCostData.ts @@ -12,6 +12,7 @@ export const KNOWN_MODEL_PRICING: Record = { "gpt-4o": { inputCostPer1M: 2.5, outputCostPer1M: 10.0, isFree: false }, "gpt-4o-mini": { inputCostPer1M: 0.15, outputCostPer1M: 0.6, isFree: false }, "claude-fable-5": { inputCostPer1M: 15.0, outputCostPer1M: 75.0, isFree: false }, + "claude-opus-5": { inputCostPer1M: 5.0, outputCostPer1M: 25.0, isFree: false }, "claude-opus-4-8": { inputCostPer1M: 15.0, outputCostPer1M: 75.0, isFree: false }, "claude-opus-4-7": { inputCostPer1M: 15.0, outputCostPer1M: 75.0, isFree: false }, "claude-sonnet-4-6": { inputCostPer1M: 3.0, outputCostPer1M: 15.0, isFree: false }, diff --git a/src/app/(dashboard)/dashboard/settings/components/RoutingTab.tsx b/src/app/(dashboard)/dashboard/settings/components/RoutingTab.tsx index de394df2f4e1..04e8a2f9f9c5 100644 --- a/src/app/(dashboard)/dashboard/settings/components/RoutingTab.tsx +++ b/src/app/(dashboard)/dashboard/settings/components/RoutingTab.tsx @@ -171,6 +171,7 @@ const DEFAULT_SYSTEM_TRANSFORMS_CLIENT = { entrypoint: "sdk-cli", versionFormat: "ex-machina", cchAlgo: "sha256-first-user", + buildRevision: "250", }, ], }, diff --git a/src/lib/db/settings.ts b/src/lib/db/settings.ts index 647a31801379..03b2740304a5 100644 --- a/src/lib/db/settings.ts +++ b/src/lib/db/settings.ts @@ -165,7 +165,13 @@ export async function getSettings() { codexServiceTier: { enabled: false }, claudeFastMode: { enabled: false, - supportedModels: ["claude-fable-5", "claude-opus-4-8", "claude-opus-4-7", "claude-opus-4-6"], + supportedModels: [ + "claude-fable-5", + "claude-opus-5", + "claude-opus-4-8", + "claude-opus-4-7", + "claude-opus-4-6", + ], }, // #7274: renamed from codexSessionAffinityTtlMs — session affinity now // applies to any provider, not just Codex. No default here on purpose: diff --git a/src/lib/providers/claudeFastMode.ts b/src/lib/providers/claudeFastMode.ts index 8122fc53f301..a007b2fee4f6 100644 --- a/src/lib/providers/claudeFastMode.ts +++ b/src/lib/providers/claudeFastMode.ts @@ -3,11 +3,12 @@ type JsonRecord = Record; /** * Default models that support Anthropic Fast Mode (speed:"fast"). * - * Mirrors the binary-side gate observed in claude-code v2.1.145 (KT() check): - * only the latest Opus tiers can request the priority service path. + * Opus 5 support is public; the older entries mirror the binary-side gate + * observed in claude-code v2.1.145 (KT() check). */ export const CLAUDE_FAST_MODE_DEFAULT_MODELS = [ "claude-fable-5", + "claude-opus-5", "claude-opus-4-8", "claude-opus-4-7", "claude-opus-4-6", @@ -29,8 +30,7 @@ function asStringArray(value: unknown): string[] | null { /** * Returns true if the user has globally opted into Claude Fast Mode. * - * Anthropic does not officially expose `speed:"fast"` via the public SDK; this - * toggle is only meaningful when paired with a CPA-side opt-in spoof that + * This toggle is meaningful when paired with the CPA-side opt-in path that * rewrites the entrypoint for SDK-shaped traffic. The flag is forwarded to CPA * via the `X-CPA-Force-Fast-Mode` outbound header. */ @@ -43,8 +43,8 @@ export function isClaudeFastModeEnabled(settings: unknown): boolean { } /** - * Returns the configured supported-model list, defaulting to the conservative - * Opus 4-8 / 4-7 / 4-6 set. + * Returns the configured supported-model list, defaulting to the supported + * flagship and Opus tiers. */ export function getClaudeFastModeSupportedModels(settings: unknown): string[] { const record = asRecord(settings); diff --git a/src/lib/providers/staticModels.ts b/src/lib/providers/staticModels.ts index ed77a0cfe382..bedda44f7b8a 100644 --- a/src/lib/providers/staticModels.ts +++ b/src/lib/providers/staticModels.ts @@ -36,6 +36,7 @@ const STATIC_MODEL_PROVIDERS: Record Array<{ id: string; name: str antigravity: () => ANTIGRAVITY_PUBLIC_MODELS.map((model) => ({ ...model })), claude: () => [ { id: "claude-fable-5", name: "Claude Fable 5" }, + { id: "claude-opus-5", name: "Claude Opus 5" }, { id: "claude-opus-4-8", name: "Claude Opus 4.8" }, { id: "claude-opus-4-7", name: "Claude Opus 4.7" }, { id: "claude-opus-4-6", name: "Claude Opus 4.6" }, diff --git a/src/shared/constants/claudeCodeClient.ts b/src/shared/constants/claudeCodeClient.ts new file mode 100644 index 000000000000..0dfa308856fb --- /dev/null +++ b/src/shared/constants/claudeCodeClient.ts @@ -0,0 +1,17 @@ +/** + * Wire-version data captured from the signed Claude Code binary. + * + * Keep this leaf dependency-free so server executors, compatibility bridges, + * and client-facing identity presets can share one source of truth. + */ +export const CLAUDE_CODE_CLIENT_VERSION = "2.1.219"; +export const CLAUDE_CODE_CLIENT_BUILD_REVISION = "250"; +export const CLAUDE_CODE_CLIENT_BILLING_VERSION = `${CLAUDE_CODE_CLIENT_VERSION}.${CLAUDE_CODE_CLIENT_BUILD_REVISION}`; +export const CLAUDE_CODE_SDK_PACKAGE_VERSION = "0.94.0"; +export const CLAUDE_CODE_RUNTIME_VERSION = "v26.3.0"; + +export type ClaudeCodeEntrypoint = "cli" | "sdk-cli"; + +export function getClaudeCodeUserAgent(entrypoint: ClaudeCodeEntrypoint): string { + return `claude-cli/${CLAUDE_CODE_CLIENT_VERSION} (external, ${entrypoint})`; +} diff --git a/src/shared/constants/clientIdentityProfiles.ts b/src/shared/constants/clientIdentityProfiles.ts index 3788bd507b96..3798ff04c249 100644 --- a/src/shared/constants/clientIdentityProfiles.ts +++ b/src/shared/constants/clientIdentityProfiles.ts @@ -14,6 +14,8 @@ * header) tries to set — no new precedence logic is needed here. */ +import { getClaudeCodeUserAgent } from "./claudeCodeClient"; + export interface ClientIdentityProfile { readonly id: string; readonly label: string; @@ -30,7 +32,7 @@ const CLAUDE_CLI_PROFILE: ClientIdentityProfile = Object.freeze({ id: "claude-cli", label: "Claude CLI", headers: Object.freeze({ - "User-Agent": "claude-cli/2.1.207 (external, cli)", + "User-Agent": getClaudeCodeUserAgent("cli"), "X-App": "cli", }), }); diff --git a/src/shared/constants/modelSpecs.ts b/src/shared/constants/modelSpecs.ts index c4703a30be0e..31288774539b 100644 --- a/src/shared/constants/modelSpecs.ts +++ b/src/shared/constants/modelSpecs.ts @@ -22,9 +22,12 @@ export interface ModelSpec { // Model ONLY supports adaptive thinking: manual extended thinking was removed. Sending // `thinking.type:"enabled"` or any `thinking.budget_tokens` returns HTTP 400; reasoning // is steered exclusively by `output_config.effort` (low/medium/high/xhigh/max). True for - // Claude Opus 4.7 and later (Opus 4.7/4.8, Fable 5). Per Anthropic's migration guide - // (2026-05-19): "Any request that tries to set a fixed thinking budget gets a 400 error." + // Claude Opus 4.7 and later (Opus 4.7/4.8/5, Fable 5). Per Anthropic's migration guide, + // any request that tries to set a fixed thinking budget gets a 400 error. adaptiveThinkingOnly?: boolean; + // Highest effort accepted while `thinking.type:"disabled"` is present. Claude Opus 5 + // rejects disabled thinking with xhigh/max, while accepting it through high. + maxEffortWhenThinkingDisabled?: "high"; // Explicit operator override for the no-thinking gateway alias (Fase 8.1). When unset, // the catalog auto-advertises a `no-think/…` variant for // Claude-family thinking-capable models that honor `disabled`. Set `true` to force the @@ -336,6 +339,20 @@ export const MODEL_SPECS: Record = { aliases: BEDROCK_CLAUDE_ALIASES("claude-fable-5"), }, + // ── Claude Opus 5 ─────────────────────────────────────────────── + "claude-opus-5": { + maxOutputTokens: 128000, + contextWindow: 1000000, + defaultThinkingBudget: 32000, + thinkingBudgetCap: 120000, + supportsThinking: true, + supportsTools: true, + supportsVision: true, + adaptiveThinkingOnly: true, + maxEffortWhenThinkingDisabled: "high", + aliases: BEDROCK_CLAUDE_ALIASES("claude-opus-5"), + }, + // ── Claude Opus 4.8 ───────────────────────────────────────────── "claude-opus-4-8": { maxOutputTokens: 128000, @@ -692,6 +709,13 @@ export function isAdaptiveThinkingOnly(modelId: string | null | undefined): bool return getModelSpec(modelId)?.adaptiveThinkingOnly === true; } +export function getMaxEffortWhenThinkingDisabled( + modelId: string | null | undefined +): "high" | null { + if (typeof modelId !== "string" || modelId.length === 0) return null; + return getModelSpec(modelId)?.maxEffortWhenThinkingDisabled ?? null; +} + export function capThinkingBudget(modelId: string, budget: number): number { const cap = getModelSpec(modelId)?.thinkingBudgetCap ?? budget; return Math.min(budget, cap); diff --git a/src/shared/constants/pricing/frontier-labs.ts b/src/shared/constants/pricing/frontier-labs.ts index 0c7117cb57f3..59e580e97111 100644 --- a/src/shared/constants/pricing/frontier-labs.ts +++ b/src/shared/constants/pricing/frontier-labs.ts @@ -8,6 +8,7 @@ import { GPT_5_6_SOL_PRICING, GPT_5_6_TERRA_PRICING, CLAUDE_FABLE_5_PRICING, + CLAUDE_OPUS_5_PRICING, CLAUDE_OPUS_4_PRICING, CLAUDE_SONNET_4_PRICING, CLAUDE_OPUS_46_PRICING, @@ -212,6 +213,7 @@ export const DEFAULT_PRICING_FRONTIER = { // Intentional duplicates of dot-notation variants (e.g. claude-opus-4.6) // to cover hyphen-notation IDs (claude-opus-4-6) used by some clients "claude-fable-5": CLAUDE_FABLE_5_PRICING, + "claude-opus-5": CLAUDE_OPUS_5_PRICING, "claude-sonnet-5": CLAUDE_SONNET_5_PRICING, "claude-opus-4.8": CLAUDE_OPUS_4_PRICING, "claude-opus-4-8": CLAUDE_OPUS_4_PRICING, diff --git a/src/shared/constants/pricing/oauth-subscriptions.ts b/src/shared/constants/pricing/oauth-subscriptions.ts index bf7f2136c146..19c642eda923 100644 --- a/src/shared/constants/pricing/oauth-subscriptions.ts +++ b/src/shared/constants/pricing/oauth-subscriptions.ts @@ -3,6 +3,7 @@ * Pure data; merged by default-pricing.ts via spread (god-file decomposition; semantic split). */ import { + CLAUDE_OPUS_5_PRICING, GPT_5_3_CODEX_PRICING, GPT_5_5_PRICING, GPT_5_6_LUNA_PRICING, @@ -19,6 +20,7 @@ export const DEFAULT_PRICING_OAUTH = { reasoning: 50.0, cache_creation: 12.5, }, + "claude-opus-5": CLAUDE_OPUS_5_PRICING, "claude-opus-4-8": { input: 5.0, output: 25.0, @@ -361,6 +363,7 @@ export const DEFAULT_PRICING_OAUTH = { }, }, gh: { + "claude-opus-5": CLAUDE_OPUS_5_PRICING, "gpt-5": { input: 3.0, output: 12.0, diff --git a/src/shared/constants/pricing/shared-tiers.ts b/src/shared/constants/pricing/shared-tiers.ts index 596fe1744650..26976b95812b 100644 --- a/src/shared/constants/pricing/shared-tiers.ts +++ b/src/shared/constants/pricing/shared-tiers.ts @@ -49,6 +49,14 @@ export const CLAUDE_FABLE_5_PRICING = { cache_creation: 15.0, }; +export const CLAUDE_OPUS_5_PRICING = { + input: 5.0, + output: 25.0, + cached: 0.5, + reasoning: 25.0, + cache_creation: 6.25, +}; + export const CLAUDE_OPUS_4_PRICING = { input: 15.0, output: 75.0, diff --git a/src/shared/validation/settingsSchemas.ts b/src/shared/validation/settingsSchemas.ts index b39984e54534..7df36c5f0265 100644 --- a/src/shared/validation/settingsSchemas.ts +++ b/src/shared/validation/settingsSchemas.ts @@ -68,6 +68,7 @@ const transformInjectBillingHeaderSchema = z.object({ versionFormat: z.enum(["ex-machina", "omniroute-daystamp"]), cchAlgo: z.enum(["sha256-first-user", "xxhash64-body", "static-zero"]), version: z.string().max(50).optional(), + buildRevision: z.string().min(1).max(20).optional(), }); const commonSystemTransformOperationSchemas = [ diff --git a/src/sse/handlers/chat.ts b/src/sse/handlers/chat.ts index 20e96b268448..e4125beda7eb 100644 --- a/src/sse/handlers/chat.ts +++ b/src/sse/handlers/chat.ts @@ -138,6 +138,7 @@ import { registerGrokWebQuotaFetcher } from "@omniroute/open-sse/services/grokQu import { registerGenericQuotaFetchers } from "@omniroute/open-sse/services/genericQuotaFetcher.ts"; import "@omniroute/open-sse/services/quotaTrackersBatch.ts"; import { + disableCooldownAwareRetry, getCooldownAwareRetryDecision, resolveCooldownAwareRetrySettings, waitForCooldownAwareRetry, @@ -1225,18 +1226,13 @@ async function handleSingleModelChat( const baseRetrySettings = resolveCooldownAwareRetrySettings( runtimeOptions.cachedSettings ?? (await getCachedSettings().catch(() => ({}))) ); - const disableCooldownAwareRetry = - isCombo || forceLiveComboTest || runtimeOptions.emergencyFallbackTried === true; - const retrySettings = disableCooldownAwareRetry - ? { - ...baseRetrySettings, - enabled: false, - maxRetries: 0, - maxRetryWaitSec: 0, - maxRetryWaitMs: 0, - budgetMs: 0, - } - : baseRetrySettings; + const retrySettings = disableCooldownAwareRetry( + baseRetrySettings, + provider === "claude-web" || + isCombo || + forceLiveComboTest || + runtimeOptions.emergencyFallbackTried === true + ); const requestSignal = request?.signal ?? null; // Cumulative cap across all waits for this request (#7360 follow-up) — mirrors // combo.ts's comboCooldownBudgetLeftMs. Declared outside requestAttemptLoop so diff --git a/src/sse/services/cooldownAwareRetry.ts b/src/sse/services/cooldownAwareRetry.ts index fcdbb63bae84..51d4bb00f570 100644 --- a/src/sse/services/cooldownAwareRetry.ts +++ b/src/sse/services/cooldownAwareRetry.ts @@ -67,6 +67,21 @@ export function resolveCooldownAwareRetrySettings( }; } +export function disableCooldownAwareRetry( + settings: CooldownAwareRetrySettings, + disabled: boolean +): CooldownAwareRetrySettings { + if (!disabled) return settings; + return { + ...settings, + enabled: false, + maxRetries: 0, + maxRetryWaitSec: 0, + maxRetryWaitMs: 0, + budgetMs: 0, + }; +} + export function computeClosestRetryAfter(retryAfter: unknown): { retryAfter: string | null; retryAfterHuman: string; diff --git a/tests/snapshots/provider/translate-path.json b/tests/snapshots/provider/translate-path.json index df8173206092..97007c8965c4 100644 --- a/tests/snapshots/provider/translate-path.json +++ b/tests/snapshots/provider/translate-path.json @@ -28,14 +28,14 @@ "apiKey": { "Accept": "text/event-stream", "Content-Type": "application/json", - "User-Agent": "claude-cli/2.1.207 (external, sdk-cli)", + "User-Agent": "claude-cli/2.1.219 (external, sdk-cli)", "X-Stainless-Arch": "", "X-Stainless-Lang": "js", "X-Stainless-OS": "MacOS", "X-Stainless-Package-Version": "0.94.0", "X-Stainless-Retry-Count": "0", "X-Stainless-Runtime": "node", - "X-Stainless-Runtime-Version": "v24.3.0", + "X-Stainless-Runtime-Version": "v26.3.0", "X-Stainless-Timeout": "600", "accept-encoding": "gzip, deflate, br, zstd", "anthropic-beta": "claude-code-20250219,interleaved-thinking-2025-05-14,effort-2025-11-24", @@ -47,14 +47,14 @@ "nonStream": { "Accept": "application/json", "Content-Type": "application/json", - "User-Agent": "claude-cli/2.1.207 (external, sdk-cli)", + "User-Agent": "claude-cli/2.1.219 (external, sdk-cli)", "X-Stainless-Arch": "", "X-Stainless-Lang": "js", "X-Stainless-OS": "MacOS", "X-Stainless-Package-Version": "0.94.0", "X-Stainless-Retry-Count": "0", "X-Stainless-Runtime": "node", - "X-Stainless-Runtime-Version": "v24.3.0", + "X-Stainless-Runtime-Version": "v26.3.0", "X-Stainless-Timeout": "600", "accept-encoding": "gzip, deflate, br, zstd", "anthropic-beta": "claude-code-20250219,interleaved-thinking-2025-05-14,effort-2025-11-24", @@ -66,14 +66,14 @@ "oauth": { "Accept": "text/event-stream", "Content-Type": "application/json", - "User-Agent": "claude-cli/2.1.207 (external, sdk-cli)", + "User-Agent": "claude-cli/2.1.219 (external, sdk-cli)", "X-Stainless-Arch": "", "X-Stainless-Lang": "js", "X-Stainless-OS": "MacOS", "X-Stainless-Package-Version": "0.94.0", "X-Stainless-Retry-Count": "0", "X-Stainless-Runtime": "node", - "X-Stainless-Runtime-Version": "v24.3.0", + "X-Stainless-Runtime-Version": "v26.3.0", "X-Stainless-Timeout": "600", "accept-encoding": "gzip, deflate, br, zstd", "anthropic-beta": "claude-code-20250219,interleaved-thinking-2025-05-14,effort-2025-11-24", @@ -851,7 +851,7 @@ "Anthropic-Dangerous-Direct-Browser-Access": "true", "Anthropic-Version": "2023-06-01", "Content-Type": "application/json", - "User-Agent": "claude-cli/2.1.207 (external, cli)", + "User-Agent": "claude-cli/2.1.219 (external, cli)", "X-App": "cli", "X-Stainless-Arch": "", "X-Stainless-Helper-Method": "stream", @@ -860,7 +860,7 @@ "X-Stainless-Package-Version": "0.94.0", "X-Stainless-Retry-Count": "0", "X-Stainless-Runtime": "node", - "X-Stainless-Runtime-Version": "v24.3.0", + "X-Stainless-Runtime-Version": "v26.3.0", "X-Stainless-Timeout": "600", "x-api-key": "" }, @@ -869,7 +869,7 @@ "Anthropic-Dangerous-Direct-Browser-Access": "true", "Anthropic-Version": "2023-06-01", "Content-Type": "application/json", - "User-Agent": "claude-cli/2.1.207 (external, cli)", + "User-Agent": "claude-cli/2.1.219 (external, cli)", "X-App": "cli", "X-Stainless-Arch": "", "X-Stainless-Helper-Method": "stream", @@ -878,7 +878,7 @@ "X-Stainless-Package-Version": "0.94.0", "X-Stainless-Retry-Count": "0", "X-Stainless-Runtime": "node", - "X-Stainless-Runtime-Version": "v24.3.0", + "X-Stainless-Runtime-Version": "v26.3.0", "X-Stainless-Timeout": "600", "x-api-key": "" }, @@ -888,7 +888,7 @@ "Anthropic-Dangerous-Direct-Browser-Access": "true", "Anthropic-Version": "2023-06-01", "Content-Type": "application/json", - "User-Agent": "claude-cli/2.1.207 (external, cli)", + "User-Agent": "claude-cli/2.1.219 (external, cli)", "X-App": "cli", "X-Stainless-Arch": "", "X-Stainless-Helper-Method": "stream", @@ -897,7 +897,7 @@ "X-Stainless-Package-Version": "0.94.0", "X-Stainless-Retry-Count": "0", "X-Stainless-Runtime": "node", - "X-Stainless-Runtime-Version": "v24.3.0", + "X-Stainless-Runtime-Version": "v26.3.0", "X-Stainless-Timeout": "600", "x-api-key": "" } diff --git a/tests/unit/anthropic-cache-fingerprint.test.ts b/tests/unit/anthropic-cache-fingerprint.test.ts index 110a97e64260..8272cb1bb211 100644 --- a/tests/unit/anthropic-cache-fingerprint.test.ts +++ b/tests/unit/anthropic-cache-fingerprint.test.ts @@ -1,74 +1,10 @@ -/** - * Tests for Anthropic billing header fingerprint stability (#1638). - * - * Validates that the billing header fingerprint is stable across different - * messages within the same day, preventing prompt-cache prefix invalidation. - */ import { describe, it } from "node:test"; import assert from "node:assert/strict"; -import { createHash } from "node:crypto"; -// Replicate the stabilized fingerprint logic from base.ts -function computeStableFingerprint(ccVersion: string): string { - const dayStamp = new Date().toISOString().slice(0, 10); // YYYY-MM-DD - return createHash("sha256").update(`${dayStamp}${ccVersion}`).digest("hex").slice(0, 3); -} - -// The old implementation for comparison -function computeOldFingerprint(firstUserMessageText: string, version: string): string { - const FINGERPRINT_SALT = "59cf53e54c78"; - const indices = [4, 7, 20]; - const chars = indices.map((i) => firstUserMessageText[i] || "0").join(""); - const input = `${FINGERPRINT_SALT}${chars}${version}`; - return createHash("sha256").update(input).digest("hex").slice(0, 3); -} +import { CLAUDE_CODE_CLIENT_BILLING_VERSION } from "../../src/shared/constants/claudeCodeClient.ts"; describe("Anthropic billing header fingerprint (#1638)", () => { - const ccVersion = "2.1.137"; - - it("should produce the same fingerprint for different messages (stable)", () => { - const fp1 = computeStableFingerprint(ccVersion); - const fp2 = computeStableFingerprint(ccVersion); - assert.equal(fp1, fp2, "Same-day fingerprints should be identical"); - }); - - it("should produce a 3-character hex fingerprint", () => { - const fp = computeStableFingerprint(ccVersion); - assert.equal(fp.length, 3, "Fingerprint should be 3 chars"); - assert.ok(/^[a-f0-9]{3}$/.test(fp), `Fingerprint '${fp}' should be lowercase hex`); - }); - - it("old implementation produces DIFFERENT fingerprints for different messages", () => { - const msg1 = "Hello, how can I help you with your code?"; - const msg2 = "Please fix the bug in my application"; - const fp1 = computeOldFingerprint(msg1, ccVersion); - const fp2 = computeOldFingerprint(msg2, ccVersion); - assert.notEqual(fp1, fp2, "Old method should differ per message — this was the bug"); - }); - - it("new implementation produces SAME fingerprint regardless of message content", () => { - // The new implementation doesn't use message content at all - const fp1 = computeStableFingerprint(ccVersion); - const fp2 = computeStableFingerprint(ccVersion); - const fp3 = computeStableFingerprint(ccVersion); - assert.equal(fp1, fp2); - assert.equal(fp2, fp3); - }); - - it("billing header line should be deterministic within the same day", () => { - const fp = computeStableFingerprint(ccVersion); - const billingLine1 = `x-anthropic-billing-header: cc_version=${ccVersion}.${fp}; cc_entrypoint=cli; cch=00000;`; - const billingLine2 = `x-anthropic-billing-header: cc_version=${ccVersion}.${fp}; cc_entrypoint=cli; cch=00000;`; - assert.equal(billingLine1, billingLine2, "Billing lines should be byte-identical"); - }); - - it("should produce different fingerprints for different days", () => { - // Simulate different days by computing manually - const day1 = "2026-04-27"; - const day2 = "2026-04-28"; - const fp1 = createHash("sha256").update(`${day1}${ccVersion}`).digest("hex").slice(0, 3); - const fp2 = createHash("sha256").update(`${day2}${ccVersion}`).digest("hex").slice(0, 3); - // They should be different (extremely high probability with SHA-256) - assert.notEqual(fp1, fp2, "Different days should produce different fingerprints"); + it("uses the immutable build revision captured from the signed CLI", () => { + assert.equal(CLAUDE_CODE_CLIENT_BILLING_VERSION, "2.1.219.250"); }); }); diff --git a/tests/unit/bootstrap-env.test.ts b/tests/unit/bootstrap-env.test.ts index 204ceea672ba..93dee00dd518 100644 --- a/tests/unit/bootstrap-env.test.ts +++ b/tests/unit/bootstrap-env.test.ts @@ -70,14 +70,14 @@ test("bootstrapEnv strips matching quotes from env values", () => { fs.mkdirSync(dataDir, { recursive: true }); fs.writeFileSync( path.join(dataDir, "server.env"), - 'JWT_SECRET="jwt-from-server-env"\nCLAUDE_USER_AGENT="claude-cli/2.1.145 (external, cli)"\n', + 'JWT_SECRET="jwt-from-server-env"\nCLAUDE_USER_AGENT="claude-cli/2.1.219 (external, cli)"\n', "utf8" ); const env = bootstrapEnv({ quiet: true }); assert.equal(env.JWT_SECRET, "jwt-from-server-env"); - assert.equal(env.CLAUDE_USER_AGENT, "claude-cli/2.1.145 (external, cli)"); + assert.equal(env.CLAUDE_USER_AGENT, "claude-cli/2.1.219 (external, cli)"); }); }); diff --git a/tests/unit/catalog-updates-v3x.test.ts b/tests/unit/catalog-updates-v3x.test.ts index 9e3c3558fedd..c95581b569c2 100644 --- a/tests/unit/catalog-updates-v3x.test.ts +++ b/tests/unit/catalog-updates-v3x.test.ts @@ -3,6 +3,7 @@ import assert from "node:assert/strict"; import { getModelsByProviderId } from "../../open-sse/config/providerModels.ts"; import { resolveCanonicalProviderModel } from "../../open-sse/services/model.ts"; +import { getStaticModelsForProvider } from "../../src/lib/providers/staticModels.ts"; import { DEFAULT_PRICING } from "../../src/shared/constants/pricing.ts"; test("Pollinations catalog mirrors the current public text model lineup", () => { @@ -66,6 +67,34 @@ test("Fable 5 catalog exposes claude-fable-5 in cc — but NOT via Kiro (fabrica ); }); +test("Opus 5 catalog is limited to verified first-party, web, and Copilot providers", () => { + for (const providerId of ["claude", "github", "claude-web", "anthropic"]) { + const model = getModelsByProviderId(providerId).find((entry) => entry.id === "claude-opus-5"); + assert.ok(model, `${providerId} must expose claude-opus-5`); + } + + const claude = getModelsByProviderId("claude").find((entry) => entry.id === "claude-opus-5"); + assert.equal(claude?.contextLength, 1000000); + assert.equal(claude?.maxOutputTokens, 128000); + assert.ok( + getStaticModelsForProvider("claude")?.some((entry) => entry.id === "claude-opus-5"), + "claude OAuth discovery must expose claude-opus-5" + ); + + const github = getModelsByProviderId("github").find((entry) => entry.id === "claude-opus-5"); + assert.equal(github?.targetFormat, "claude"); + + const kiroIds = new Set(getModelsByProviderId("kiro").map((entry) => entry.id)); + assert.equal(kiroIds.has("claude-opus-5"), false, "do not fabricate Kiro availability"); + + const pricing = DEFAULT_PRICING as Record>; + for (const providerId of ["cc", "gh", "anthropic"]) { + const price = pricing[providerId]["claude-opus-5"] as { input: number; output: number }; + assert.equal(price.input, 5.0, `${providerId} Opus 5 input price`); + assert.equal(price.output, 25.0, `${providerId} Opus 5 output price`); + } +}); + test("Sonnet 5 catalog exposes claude-sonnet-5 across cc/kiro/anthropic/blackbox with Sonnet-tier pricing", () => { // Sonnet 5 must be wired everywhere the last flagship (Fable 5) was — but as a // Sonnet-tier model: $3/$15 pricing (NOT the Opus/Fable $15/$75), 1M ctx / 128K out. diff --git a/tests/unit/cc-bridge-transforms.test.ts b/tests/unit/cc-bridge-transforms.test.ts index 01bcb814f3ba..680500a9cd0a 100644 --- a/tests/unit/cc-bridge-transforms.test.ts +++ b/tests/unit/cc-bridge-transforms.test.ts @@ -52,7 +52,7 @@ test("DEFAULT_CC_BRIDGE_PIPELINE places billing header at [0] and identity at [1 DEFAULT_CC_BRIDGE_PIPELINE ); const blocks = result.body.system as any[]; - assert.ok(blocks[0].text.startsWith("x-anthropic-billing-header:")); + assert.ok(blocks[0].text.startsWith("x-anthropic-billing-header: cc_version=2.1.219.250;")); assert.equal(blocks[1].text, CLAUDE_AGENT_SDK_IDENTITY); }); diff --git a/tests/unit/chat-cooldown-aware-retry.test.ts b/tests/unit/chat-cooldown-aware-retry.test.ts index ac276ebed644..0ab22ec70338 100644 --- a/tests/unit/chat-cooldown-aware-retry.test.ts +++ b/tests/unit/chat-cooldown-aware-retry.test.ts @@ -9,6 +9,8 @@ process.env.STREAM_READINESS_TIMEOUT_MS = "50"; const harness = await createChatPipelineHarness("chat-cooldown-aware-retry"); const auth = await import("../../src/sse/services/auth.ts"); const { getProviderConnectionById } = await import("../../src/lib/db/providers.ts"); +const { __setTlsFetchOverrideForTesting } = + await import("../../open-sse/services/claudeTlsClient.ts"); const { BaseExecutor, buildOpenAIResponse, @@ -57,6 +59,7 @@ test.beforeEach(async () => { }); test.afterEach(async () => { + __setTlsFetchOverrideForTesting(null); BaseExecutor.RETRY_CONFIG.maxAttempts = originalRetryConfig.maxAttempts; BaseExecutor.RETRY_CONFIG.delayMs = originalRetryConfig.delayMs; await resetStorage(); @@ -66,6 +69,49 @@ test.after(async () => { await harness.cleanup(); }); +test("handleChat does not probe Claude Web repeatedly after an upstream 429", async () => { + await seedConnection("claude-web", { + apiKey: "sessionKey=fake-session", + }); + await settingsDb.updateSettings({ + requestRetry: 3, + maxRetryIntervalSec: 3, + }); + + let completionCalls = 0; + __setTlsFetchOverrideForTesting(async (url) => { + if (url.endsWith("/organizations")) { + return { + status: 200, + headers: new Headers({ "Content-Type": "application/json" }), + text: JSON.stringify([{ uuid: "org-test" }]), + body: null, + }; + } + + completionCalls += 1; + return { + status: 429, + headers: new Headers({ "Content-Type": "application/json" }), + text: JSON.stringify({ error: { message: "Rate limited." } }), + body: null, + }; + }); + + const response = await handleChat( + buildRequest({ + body: { + model: "claude-web/claude-opus-5", + stream: false, + messages: [{ role: "user", content: "do not retry a Claude Web 429" }], + }, + }) + ); + + assert.equal(response.status, 429); + assert.equal(completionCalls, 1); +}); + test("handleChat waits for a short cooldown and retries once within the configured budget", async () => { await seedConnection("openai", { apiKey: "sk-openai-cooldown-short", diff --git a/tests/unit/claude-adaptive-sampling-params.test.ts b/tests/unit/claude-adaptive-sampling-params.test.ts index 753d0194902b..d26e4952e80e 100644 --- a/tests/unit/claude-adaptive-sampling-params.test.ts +++ b/tests/unit/claude-adaptive-sampling-params.test.ts @@ -1,5 +1,5 @@ /** - * Claude Opus 4.7+/Fable 5 sampling-param strip + adaptive-only flag. + * Claude Opus 4.7+/Opus 5/Fable 5 sampling-param strip + adaptive-only flag. * * Anthropic's Opus 4.7+ generation rejects non-default `temperature`/`top_p`/`top_k` with a * 400 (sampling is fixed; reasoning is steered by output_config.effort). These tests pin both @@ -15,7 +15,7 @@ import { isAdaptiveThinkingOnly } from "../../src/shared/constants/modelSpecs.ts const SAMPLING = ["temperature", "top_p", "top_k"]; test("claude registry strips temperature/top_p/top_k for Opus 4.7+/Fable 5", () => { - for (const model of ["claude-opus-4-8", "claude-opus-4-7", "claude-fable-5"]) { + for (const model of ["claude-opus-5", "claude-opus-4-8", "claude-opus-4-7", "claude-fable-5"]) { const unsupported = getUnsupportedParams("claude", model); for (const param of SAMPLING) { assert.ok( @@ -26,10 +26,12 @@ test("claude registry strips temperature/top_p/top_k for Opus 4.7+/Fable 5", () } }); -test("anthropic registry (dotted ids) strips sampling params for Opus 4.7", () => { - const unsupported = getUnsupportedParams("anthropic", "claude-opus-4.7"); - for (const param of SAMPLING) { - assert.ok(unsupported.includes(param), `claude-opus-4.7 must list ${param} as unsupported`); +test("anthropic registry strips sampling params for current adaptive Opus models", () => { + for (const model of ["claude-opus-5", "claude-opus-4.8", "claude-opus-4.7"]) { + const unsupported = getUnsupportedParams("anthropic", model); + for (const param of SAMPLING) { + assert.ok(unsupported.includes(param), `${model} must list ${param} as unsupported`); + } } }); @@ -52,7 +54,7 @@ test("pre-4.7 Claude models still accept sampling params (regression guard)", () }); test("isAdaptiveThinkingOnly is true only for Opus 4.7+/Fable 5", () => { - for (const model of ["claude-opus-4-8", "claude-opus-4-7", "claude-fable-5"]) { + for (const model of ["claude-opus-5", "claude-opus-4-8", "claude-opus-4-7", "claude-fable-5"]) { assert.equal(isAdaptiveThinkingOnly(model), true, `${model} is adaptive-only`); } for (const model of [ @@ -68,5 +70,6 @@ test("isAdaptiveThinkingOnly is true only for Opus 4.7+/Fable 5", () => { }); test("isAdaptiveThinkingOnly resolves Bedrock/dated aliases", () => { + assert.equal(isAdaptiveThinkingOnly("anthropic.claude-opus-5"), true); assert.equal(isAdaptiveThinkingOnly("anthropic.claude-opus-4-8"), true); }); diff --git a/tests/unit/claude-adaptive-thinking-normalize.test.ts b/tests/unit/claude-adaptive-thinking-normalize.test.ts index d7f9c67c7cf4..ce594204b98d 100644 --- a/tests/unit/claude-adaptive-thinking-normalize.test.ts +++ b/tests/unit/claude-adaptive-thinking-normalize.test.ts @@ -2,14 +2,17 @@ * Claude adaptive-thinking normalization — `normalizeClaudeAdaptiveThinking`. * * Claude Opus 4.7+/Fable 5 removed manual extended thinking: `thinking.type:"enabled"` and - * any `thinking.budget_tokens` return HTTP 400 (Anthropic migration guide, 2026-05-19). + * any `thinking.budget_tokens` return HTTP 400. * These tests pin the final guard that collapses any manual thinking that reached the * dispatch point to `{type:"adaptive"}`, while leaving non-adaptive-only models and * already-adaptive bodies untouched. */ import test from "node:test"; import assert from "node:assert/strict"; -import { normalizeClaudeAdaptiveThinking } from "../../open-sse/services/claudeAdaptiveThinking.ts"; +import { + normalizeClaudeAdaptiveThinking, + normalizeClaudeDisabledThinkingEffort, +} from "../../open-sse/services/claudeAdaptiveThinking.ts"; test("manual thinking:{type:'enabled', budget_tokens} → adaptive, budget dropped (Opus 4.8)", () => { const body = { @@ -37,6 +40,51 @@ test("type:'enabled' with no budget still flips to adaptive (manual mode is gone assert.deepEqual(result.thinking, { type: "adaptive" }); }); +test("Opus 5 manual thinking is adaptive and drops fixed budgets", () => { + const body = { + model: "claude-opus-5", + messages: [], + thinking: { type: "enabled", budget_tokens: 64000 }, + }; + const result = normalizeClaudeAdaptiveThinking(body, "claude-opus-5"); + assert.deepEqual(result.thinking, { type: "adaptive" }); +}); + +test("direct Anthropic API providers clamp Opus 5 disabled thinking to high effort", () => { + for (const provider of ["anthropic", "claude"]) { + for (const effort of ["xhigh", "max"]) { + const body = { + model: "claude-opus-5", + thinking: { type: "disabled" }, + output_config: { effort, format: "compact" }, + }; + const result = normalizeClaudeDisabledThinkingEffort(body, "claude-opus-5", provider); + assert.deepEqual(result.thinking, { type: "disabled" }); + assert.deepEqual(result.output_config, { effort: "high", format: "compact" }); + } + } +}); + +test("GitHub Copilot and Claude Web do not inherit the Anthropic API effort cap", () => { + for (const provider of ["github", "claude-web"]) { + const body = { + model: "claude-opus-5", + thinking: { type: "disabled" }, + output_config: { effort: "max" }, + }; + assert.equal(normalizeClaudeDisabledThinkingEffort(body, "claude-opus-5", provider), body); + } +}); + +test("direct Anthropic API leaves Opus 5 disabled thinking at high effort untouched", () => { + const body = { + model: "claude-opus-5", + thinking: { type: "disabled" }, + output_config: { effort: "high" }, + }; + assert.equal(normalizeClaudeDisabledThinkingEffort(body, "claude-opus-5", "anthropic"), body); +}); + test("thinking:{type:'adaptive'} is returned UNTOUCHED (same reference)", () => { const body = { model: "claude-opus-4-8", messages: [], thinking: { type: "adaptive" } }; const result = normalizeClaudeAdaptiveThinking(body, "claude-opus-4-8"); diff --git a/tests/unit/claude-beta-flags-2454.test.ts b/tests/unit/claude-beta-flags-2454.test.ts index d38f194af69c..75fd35c40295 100644 --- a/tests/unit/claude-beta-flags-2454.test.ts +++ b/tests/unit/claude-beta-flags-2454.test.ts @@ -41,13 +41,25 @@ test("#2454 Sonnet full-agent includes heavy-agent flags but omits context-1m", assert.ok(flags.includes("thinking-token-count-2026-05-13")); assert.ok(flags.includes("redact-thinking-2026-02-12"), "Sonnet sends redact-thinking"); assert.ok(!flags.includes("afk-mode-2026-01-31"), "afk-mode removed — not in any CC capture"); - assert.ok(!flags.includes("mid-conversation-system-2026-04-07"), "Sonnet must NOT receive mid-conversation-system"); + assert.ok( + !flags.includes("mid-conversation-system-2026-04-07"), + "Sonnet must NOT receive mid-conversation-system" + ); }); test("#2454 Opus full-agent includes context-1m and mid-conversation-system", () => { const flags = selectBetaFlags(fullAgentBody("claude-opus-4-7")); assert.ok(flags.includes("context-1m-2025-08-07"), "Opus should receive context-1m"); - assert.ok(flags.includes("mid-conversation-system-2026-04-07"), "Opus should receive mid-conversation-system"); + assert.ok( + flags.includes("mid-conversation-system-2026-04-07"), + "Opus should receive mid-conversation-system" + ); +}); + +test("Opus 5 full-agent omits the legacy context-1m beta", () => { + const flags = selectBetaFlags(fullAgentBody("claude-opus-5")); + assert.ok(!flags.includes("context-1m-2025-08-07")); + assert.ok(flags.includes("mid-conversation-system-2026-04-07")); }); test("#2454 explicit model arg overrides body.model for tiering", () => { diff --git a/tests/unit/claude-codex-identity-version-sync.test.ts b/tests/unit/claude-codex-identity-version-sync.test.ts index cd64c725068a..756b7faeaeba 100644 --- a/tests/unit/claude-codex-identity-version-sync.test.ts +++ b/tests/unit/claude-codex-identity-version-sync.test.ts @@ -18,9 +18,11 @@ const hdr = await import("../../open-sse/config/anthropicHeaders.ts"); const compat = await import("../../open-sse/services/claudeCodeCompatible.ts"); const bridge = await import("../../open-sse/services/ccBridgeTransforms.ts"); const codexCfg = await import("../../open-sse/config/codexClient.ts"); +const canonical = await import("../../src/shared/constants/claudeCodeClient.ts"); test("Claude CLI version constants are in lockstep across all 4 sources", () => { - const V = id.CLAUDE_CODE_VERSION; + const V = canonical.CLAUDE_CODE_CLIENT_VERSION; + assert.equal(id.CLAUDE_CODE_VERSION, V, "claudeIdentity.CLAUDE_CODE_VERSION drift"); assert.equal(hdr.CLAUDE_CLI_VERSION, V, "anthropicHeaders.CLAUDE_CLI_VERSION drift"); assert.equal(compat.CLAUDE_CODE_COMPATIBLE_VERSION, V, "claudeCodeCompatible version drift"); assert.equal(bridge.DEFAULT_CLAUDE_CODE_VERSION, V, "ccBridgeTransforms version drift"); @@ -36,8 +38,23 @@ test("Claude CLI version constants are in lockstep across all 4 sources", () => ); }); -test("Claude CLI is pinned to the captured 2.1.207 release", () => { - assert.equal(id.CLAUDE_CODE_VERSION, "2.1.207"); +test("Claude CLI wire versions match the captured 2.1.219 binary", () => { + assert.equal(canonical.CLAUDE_CODE_CLIENT_VERSION, "2.1.219"); + assert.equal(canonical.CLAUDE_CODE_CLIENT_BUILD_REVISION, "250"); + assert.equal(canonical.CLAUDE_CODE_CLIENT_BILLING_VERSION, "2.1.219.250"); + assert.equal(canonical.CLAUDE_CODE_SDK_PACKAGE_VERSION, "0.94.0"); + assert.equal(canonical.CLAUDE_CODE_RUNTIME_VERSION, "v26.3.0"); + assert.equal( + compat.CLAUDE_CODE_COMPATIBLE_STAINLESS_PACKAGE_VERSION, + canonical.CLAUDE_CODE_SDK_PACKAGE_VERSION + ); + assert.equal( + compat.CLAUDE_CODE_COMPATIBLE_STAINLESS_RUNTIME_VERSION, + canonical.CLAUDE_CODE_RUNTIME_VERSION + ); + assert.equal(hdr.CLAUDE_CLI_STAINLESS_PACKAGE_VERSION, canonical.CLAUDE_CODE_SDK_PACKAGE_VERSION); + assert.equal(hdr.CLAUDE_CLI_STAINLESS_RUNTIME_VERSION, canonical.CLAUDE_CODE_RUNTIME_VERSION); + assert.equal(hdr.CLAUDE_CLI_BILLING_VERSION, canonical.CLAUDE_CODE_CLIENT_BILLING_VERSION); }); test("Codex client is pinned to the captured 0.144.1 release", () => { diff --git a/tests/unit/claude-context-1m-supported-models.test.ts b/tests/unit/claude-context-1m-supported-models.test.ts index dbac4c55e295..202f90ba6378 100644 --- a/tests/unit/claude-context-1m-supported-models.test.ts +++ b/tests/unit/claude-context-1m-supported-models.test.ts @@ -5,18 +5,19 @@ import path from "node:path"; const REPO_ROOT = path.resolve(import.meta.dirname, "../.."); -// ─── Parse CONTEXT_1M_SUPPORTED_MODELS from source (no module import needed) ─── - -function parseSupportedModels(): string[] { - const src = fs.readFileSync( - path.join(REPO_ROOT, "open-sse/services/claudeCodeCompatible.ts"), - "utf8" - ); +// ─── Parse 1M model lists from source (no module import needed) ─── + +function parseModelList(constantName: string): string[] { + const sourceFile = + constantName === "CONTEXT_1M_NATIVE_MODELS" + ? "open-sse/config/claudeCodeCompatibleIdentity.ts" + : "open-sse/services/claudeCodeCompatible.ts"; + const src = fs.readFileSync(path.join(REPO_ROOT, sourceFile), "utf8"); // Strip type annotations before matching to handle `const X: string[] = [...]` const match = src .replace(/:\s*\w+(\[\])?/, "") - .match(/CONTEXT_1M_SUPPORTED_MODELS\s*=\s*\[([\s\S]*?)\]/); - if (!match) throw new Error("CONTEXT_1M_SUPPORTED_MODELS not found in source"); + .match(new RegExp(`${constantName}\\s*=\\s*\\[([\\s\\S]*?)\\]`)); + if (!match) throw new Error(`${constantName} not found in source`); return match[1] .split(",") .map((s) => s.replace(/["'\s]/g, "").toLowerCase()) @@ -107,8 +108,11 @@ test("parser finds at least one Claude model in the registry", () => { // ─── Forward: every high-context Claude model must be in the allowlist ─── -test("every Claude model with contextLength > 200K is in CONTEXT_1M_SUPPORTED_MODELS", () => { - const supportedModels = parseSupportedModels(); +test("every Claude model with contextLength > 200K is in a known 1M model list", () => { + const supportedModels = [ + ...parseModelList("CONTEXT_1M_SUPPORTED_MODELS"), + ...parseModelList("CONTEXT_1M_NATIVE_MODELS"), + ]; const claudeModels = parseClaudeRegistryModels(); const violations: string[] = []; @@ -121,16 +125,19 @@ test("every Claude model with contextLength > 200K is in CONTEXT_1M_SUPPORTED_MO assert.deepEqual( violations, [], - `Claude models with contextLength > 200K missing from CONTEXT_1M_SUPPORTED_MODELS.\n` + - `Add the model prefix to the allowlist in claudeCodeCompatible.ts.\n` + + `Claude models with contextLength > 200K missing from the beta/native 1M lists.\n` + + `Add the model prefix to the correct source list.\n` + `Violations:\n ${violations.join("\n ")}` ); }); // ─── Reverse: every allowlist entry must have a matching high-context model ─── -test("every CONTEXT_1M_SUPPORTED_MODELS entry has a matching Claude model with contextLength > 200K", () => { - const supportedModels = parseSupportedModels(); +test("every known 1M model entry has a matching high-context Claude model", () => { + const supportedModels = [ + ...parseModelList("CONTEXT_1M_SUPPORTED_MODELS"), + ...parseModelList("CONTEXT_1M_NATIVE_MODELS"), + ]; const claudeModels = parseClaudeRegistryModels(); const orphans: string[] = []; @@ -146,8 +153,13 @@ test("every CONTEXT_1M_SUPPORTED_MODELS entry has a matching Claude model with c assert.deepEqual( orphans, [], - `CONTEXT_1M_SUPPORTED_MODELS entries with no matching high-context Claude model.\n` + + `Known 1M entries with no matching high-context Claude model.\n` + `Remove stale entries or check model id spelling.\n` + `Orphans:\n ${orphans.join("\n ")}` ); }); + +test("Claude Opus 5 uses native 1M context without the legacy beta header", () => { + assert.equal(parseModelList("CONTEXT_1M_SUPPORTED_MODELS").includes("claude-opus-5"), false); + assert.equal(parseModelList("CONTEXT_1M_NATIVE_MODELS").includes("claude-opus-5"), true); +}); diff --git a/tests/unit/claude-fast-mode.test.ts b/tests/unit/claude-fast-mode.test.ts index 92b6be05e245..ad545f222a24 100644 --- a/tests/unit/claude-fast-mode.test.ts +++ b/tests/unit/claude-fast-mode.test.ts @@ -27,6 +27,10 @@ test("shouldRequestClaudeFastMode returns true for claude-opus-4-8 exact match", assert.equal(shouldRequestClaudeFastMode(enabledSettings, "claude-opus-4-8"), true); }); +test("shouldRequestClaudeFastMode returns true for claude-opus-5", () => { + assert.equal(shouldRequestClaudeFastMode(enabledSettings, "claude-opus-5"), true); +}); + test("shouldRequestClaudeFastMode prefix-matches claude-opus-4-8 with dated suffix", () => { assert.equal(shouldRequestClaudeFastMode(enabledSettings, "claude-opus-4-8-20260528"), true); assert.equal(shouldRequestClaudeFastMode(enabledSettings, "claude-opus-4-8-20260101"), true); @@ -45,6 +49,10 @@ test("shouldRequestClaudeFastMode returns false for non-Opus models", () => { }); test("CLAUDE_FAST_MODE_DEFAULT_MODELS includes claude-opus-4-8", () => { + assert.ok( + CLAUDE_FAST_MODE_DEFAULT_MODELS.includes("claude-opus-5"), + "claude-opus-5 must be in CLAUDE_FAST_MODE_DEFAULT_MODELS" + ); assert.ok( CLAUDE_FAST_MODE_DEFAULT_MODELS.includes("claude-opus-4-8"), "claude-opus-4-8 must be in CLAUDE_FAST_MODE_DEFAULT_MODELS" @@ -53,6 +61,7 @@ test("CLAUDE_FAST_MODE_DEFAULT_MODELS includes claude-opus-4-8", () => { test("getClaudeFastModeSupportedModels returns default list when none configured", () => { const models = getClaudeFastModeSupportedModels({}); + assert.ok(models.includes("claude-opus-5")); assert.ok(models.includes("claude-opus-4-8")); assert.ok(models.includes("claude-opus-4-7")); assert.ok(models.includes("claude-opus-4-6")); diff --git a/tests/unit/claude-identity-version-sync.test.ts b/tests/unit/claude-identity-version-sync.test.ts index b695826fc75d..91fa3fce1d6a 100644 --- a/tests/unit/claude-identity-version-sync.test.ts +++ b/tests/unit/claude-identity-version-sync.test.ts @@ -1,10 +1,9 @@ import test from "node:test"; import assert from "node:assert/strict"; -// Claude-Code identity version is hand-bumped in lockstep across several modules -// (2.1.158 → .187 → .195 → .207 …). A silent partial bump makes one surface advertise a stale -// `claude-cli/` and can break Anthropic identity gating. This guard fails on drift. -// (quota-share-hardening Phase 2 — gaps v3.8.42.) +// Claude-Code identity aliases are consumed across several transports. They now +// resolve through one captured profile; this guard protects the compatibility +// exports and their generated User-Agent strings from drift. const claudeIdentity = await import("../../open-sse/executors/claudeIdentity.ts"); const ccBridge = await import("../../open-sse/services/ccBridgeTransforms.ts"); const claudeCompat = await import("../../open-sse/services/claudeCodeCompatible.ts"); @@ -13,7 +12,7 @@ const glmProvider = await import("../../open-sse/config/glmProvider.ts"); const CANONICAL = claudeIdentity.CLAUDE_CODE_VERSION; -// "claude-cli/2.1.207 (external, sdk-cli)" → "2.1.207". String ops only — never a RegExp over +// "claude-cli/2.1.219 (external, sdk-cli)" → "2.1.219". String ops only — never a RegExp over // the value, per the project's anti-ReDoS contract. function versionFromUserAgent(userAgent: string): string { const afterSlash = userAgent.split("claude-cli/")[1] ?? ""; diff --git a/tests/unit/claude-web-live-alignment.test.ts b/tests/unit/claude-web-live-alignment.test.ts index 5f943e55511c..18c4d261277b 100644 --- a/tests/unit/claude-web-live-alignment.test.ts +++ b/tests/unit/claude-web-live-alignment.test.ts @@ -48,6 +48,39 @@ afterEach(() => { }); describe("Claude Web live request alignment", () => { + it("uses the current Claude Web Opus 5 high/auto defaults", () => { + const payload = transformToClaude( + { messages: [{ role: "user", content: "Hello" }] }, + "claude-opus-5" + ); + + assert.equal(payload.effort, "high"); + assert.equal(payload.thinking_mode, "auto"); + }); + + it("keeps Opus 5 thinking on auto when the caller selects an effort", () => { + const payload = transformToClaude( + { + messages: [{ role: "user", content: "Think carefully" }], + reasoning_effort: "max", + }, + "claude-opus-5" + ); + + assert.equal(payload.effort, "max"); + assert.equal(payload.thinking_mode, "auto"); + }); + + it("does not apply the Opus 5 thinking contract to other Claude Web models", () => { + const payload = transformToClaude( + { messages: [{ role: "user", content: "Hello" }] }, + "claude-sonnet-5" + ); + + assert.equal(payload.effort, "low"); + assert.equal(payload.thinking_mode, "off"); + }); + it("maps an explicit reasoning effort to Claude Web extended thinking", () => { const payload = transformToClaude( { diff --git a/tests/unit/claude-web-slow-first-byte.test.ts b/tests/unit/claude-web-slow-first-byte.test.ts new file mode 100644 index 000000000000..7a67e74151c4 --- /dev/null +++ b/tests/unit/claude-web-slow-first-byte.test.ts @@ -0,0 +1,45 @@ +import assert from "node:assert/strict"; +import { writeFile } from "node:fs/promises"; +import test from "node:test"; + +import { tlsFetchStreaming } from "../../open-sse/services/claudeTlsClient.ts"; + +const SLOW_FIRST_BYTE_MS = 5_100; +const SSE_BODY = [ + 'event: message_start\ndata: {"type":"message_start"}', + 'event: content_block_delta\ndata: {"type":"content_block_delta","delta":{"type":"text_delta","text":"OK"}}', + 'event: message_stop\ndata: {"type":"message_stop"}', + "data: [DONE]", + "", +].join("\n\n"); + +test("Claude Web keeps waiting when the first Opus SSE event takes longer than five seconds", async () => { + const client = { + request: async (_url: string, options: Record) => { + await new Promise((resolve) => setTimeout(resolve, SLOW_FIRST_BYTE_MS)); + await writeFile(String(options.streamOutputPath), SSE_BODY); + return { + status: 200, + headers: {}, + body: "", + cookies: {}, + text: async () => "", + json: async () => ({}), + bytes: async () => new Uint8Array(), + }; + }, + }; + + const result = await tlsFetchStreaming( + client, + "https://claude.ai/api/organizations/x/chat_conversations/y/completion", + { method: "POST" }, + "[DONE]", + null, + 7_000 + ); + + assert.equal(result.status, 200); + assert.ok(result.body); + assert.match(await new Response(result.body).text(), /"text":"OK"/); +}); diff --git a/tests/unit/claude-web-sonnet5-registry-6209.test.ts b/tests/unit/claude-web-sonnet5-registry-6209.test.ts index b449cd232e5f..9930a04ae2df 100644 --- a/tests/unit/claude-web-sonnet5-registry-6209.test.ts +++ b/tests/unit/claude-web-sonnet5-registry-6209.test.ts @@ -11,6 +11,7 @@ test("claude-web registry matches the current selectable model set", () => { [ "claude-fable-5", "claude-haiku-4-5-20251001", + "claude-opus-5", "claude-opus-4-8", "claude-opus-4-7", "claude-opus-4-6", diff --git a/tests/unit/claude-web-stream.test.ts b/tests/unit/claude-web-stream.test.ts index 477643764b82..f09162ef9cc8 100644 --- a/tests/unit/claude-web-stream.test.ts +++ b/tests/unit/claude-web-stream.test.ts @@ -40,7 +40,10 @@ function validEvents(): Array> { { type: "content_block_delta", index: 0, - delta: { type: "thinking_summary_delta", summary: "summary" }, + delta: { + type: "thinking_summary_delta", + summary: { summary: "summary" }, + }, }, { type: "content_block_stop", index: 0 }, { type: "content_block_start", index: 1, content_block: { type: "text" } }, diff --git a/tests/unit/client-identity-profiles.test.ts b/tests/unit/client-identity-profiles.test.ts index d42e250d9594..d592e68a6194 100644 --- a/tests/unit/client-identity-profiles.test.ts +++ b/tests/unit/client-identity-profiles.test.ts @@ -39,7 +39,7 @@ test("getClientIdentityProfileHeaders: unknown profile id falls back to no heade test("getClientIdentityProfileHeaders: known CLI profiles expose their preset headers", () => { const claudeCli = getClientIdentityProfileHeaders("claude-cli"); - assert.equal(claudeCli["User-Agent"], "claude-cli/2.1.207 (external, cli)"); + assert.equal(claudeCli["User-Agent"], "claude-cli/2.1.219 (external, cli)"); assert.equal(claudeCli["X-App"], "cli"); const codexCli = getClientIdentityProfileHeaders("codex-cli"); @@ -55,7 +55,7 @@ test("getClientIdentityProfileHeaders: returns a fresh mutable copy (catalog sta headers["User-Agent"] = "tampered"; assert.equal( CLIENT_IDENTITY_PROFILES["claude-cli"].headers["User-Agent"], - "claude-cli/2.1.207 (external, cli)" + "claude-cli/2.1.219 (external, cli)" ); }); @@ -100,7 +100,7 @@ test("profile headers merged into customHeaders survive applyCustomHeaders sanit true ) as Record; - assert.equal(headers["User-Agent"], "claude-cli/2.1.207 (external, cli)"); + assert.equal(headers["User-Agent"], "claude-cli/2.1.219 (external, cli)"); assert.equal(headers["X-App"], "cli"); assert.equal(headers["Authorization"], "Bearer test-key"); }); diff --git a/tests/unit/executor-claude-identity.test.ts b/tests/unit/executor-claude-identity.test.ts index 6ed2dc23f673..8d6ed1755d13 100644 --- a/tests/unit/executor-claude-identity.test.ts +++ b/tests/unit/executor-claude-identity.test.ts @@ -178,6 +178,15 @@ describe("claudeIdentity — selectBetaFlags", () => { assert.ok(flags.includes("context-1m-2025-08-07")); }); + it("omits the legacy context-1m beta for Opus 5", () => { + const body = { + system: "test", + tools: [{ name: "test_tool" }], + }; + const flags = mod.selectBetaFlags(body, "claude-opus-5"); + assert.ok(!flags.includes("context-1m-2025-08-07")); + }); + it("does not include context-1m for sonnet", () => { const body = { system: "test", @@ -188,20 +197,6 @@ describe("claudeIdentity — selectBetaFlags", () => { }); }); -describe("claudeIdentity — buildHashFor", () => { - it("returns 3-char hex string", () => { - const hash = mod.buildHashFor("1.0.0", "2026-01-01"); - assert.equal(hash.length, 3); - assert.ok(/^[0-9a-f]{3}$/.test(hash)); - }); - - it("returns same hash for same inputs", () => { - const a = mod.buildHashFor("1.0.0", "2026-01-01"); - const b = mod.buildHashFor("1.0.0", "2026-01-01"); - assert.equal(a, b); - }); -}); - describe("claudeIdentity — stripProxyToolPrefix", () => { it("strips proxy_ prefix from tools", () => { const body = { tools: [{ name: "proxy_search" }, { name: "native_tool" }] }; diff --git a/tests/unit/executor-default-base.test.ts b/tests/unit/executor-default-base.test.ts index f73f66be582d..110d40a65d96 100644 --- a/tests/unit/executor-default-base.test.ts +++ b/tests/unit/executor-default-base.test.ts @@ -645,6 +645,23 @@ test("DefaultExecutor.execute uses CC-compatible connection defaults to append 1 }, extendedContext: true, }); + await cc.execute({ + model: "claude-opus-5", + body: { + model: "claude-opus-5", + messages: [{ role: "user", content: "hi" }], + max_tokens: 1, + }, + stream: false, + credentials: { + apiKey: "cc-key", + providerSpecificData: { + ccSessionId: "session-1", + requestDefaults: { context1m: true }, + }, + }, + extendedContext: true, + }); } finally { globalThis.fetch = originalFetch; } @@ -664,6 +681,8 @@ test("DefaultExecutor.execute uses CC-compatible connection defaults to append 1 // gets the context-1m beta header (shouldForwardExtendedContext in base.ts), same as any other // 1M-capable model. assert.equal(calls[2].headers["anthropic-beta"].includes(CONTEXT_1M_BETA_HEADER), true); + // Opus 5 has a native 1M window and must not receive the legacy context beta. + assert.equal(calls[3].headers["anthropic-beta"].includes(CONTEXT_1M_BETA_HEADER), false); }); test("DefaultExecutor.execute reports the exact serialized provider request before fetch", async () => { @@ -1450,10 +1469,12 @@ test("DefaultExecutor.execute does not produce duplicate anthropic-version heade const executor = new DefaultExecutor("claude"); const originalFetch = globalThis.fetch; let capturedHeaders: Record = {}; + let capturedBody = ""; globalThis.fetch = async (_url, init = {}) => { // Capture raw headers without normalisation so case-variant duplicate keys are visible. capturedHeaders = (init.headers as Record) || {}; + capturedBody = String(init.body ?? ""); return new Response(JSON.stringify({ ok: true }), { status: 200, headers: { "Content-Type": "application/json" }, @@ -1486,4 +1507,12 @@ test("DefaultExecutor.execute does not produce duplicate anthropic-version heade ); assert.equal(versionKeys.length, 1, "Duplicate anthropic-version header keys found"); assert.equal(capturedHeaders[versionKeys[0]], "2023-06-01"); + assert.equal(capturedHeaders["X-Stainless-Runtime-Version"], "v26.3.0"); + assert.equal(capturedHeaders["X-Stainless-Package-Version"], "0.94.0"); + + const sentBody = JSON.parse(capturedBody) as { system?: Array<{ text?: string }> }; + assert.match( + sentBody.system?.[0]?.text ?? "", + /^x-anthropic-billing-header: cc_version=2\.1\.219\.250; cc_entrypoint=cli; cch=[0-9a-f]{5};$/ + ); }); diff --git a/tests/unit/ghe-copilot.test.ts b/tests/unit/ghe-copilot.test.ts index 234755785503..aed9f933ed30 100644 --- a/tests/unit/ghe-copilot.test.ts +++ b/tests/unit/ghe-copilot.test.ts @@ -1,9 +1,22 @@ import test from "node:test"; import assert from "node:assert/strict"; import { GheCopilotExecutor } from "../../open-sse/executors/ghe-copilot.ts"; +import { gheCopilotProvider } from "../../open-sse/config/providers/registry/ghe-copilot/index.ts"; import { GHE_COPILOT_TARGET } from "../../src/mitm/targets/ghe-copilot.ts"; import type { ProviderCredentials } from "../../open-sse/executors/base.ts"; +test("GHE Copilot registry exposes Claude Opus 5", () => { + const opus5 = gheCopilotProvider.models.find((model) => model.id === "claude-opus-5"); + + assert.deepStrictEqual(opus5, { + id: "claude-opus-5", + name: "Claude Opus 5", + contextLength: 1000000, + maxOutputTokens: 64000, + unsupportedParams: ["temperature", "top_p", "top_k"], + }); +}); + test("GHE_COPILOT_TARGET has correct id and patterns", () => { assert.strictEqual(GHE_COPILOT_TARGET.id, "ghe-copilot"); assert.deepStrictEqual(GHE_COPILOT_TARGET.endpointPatterns, [ diff --git a/tests/unit/github-copilot-model-discovery.test.ts b/tests/unit/github-copilot-model-discovery.test.ts index 1c612eb7aba5..5e0cb3c9fe17 100644 --- a/tests/unit/github-copilot-model-discovery.test.ts +++ b/tests/unit/github-copilot-model-discovery.test.ts @@ -130,6 +130,7 @@ test("curated Copilot allowlist contains the final approved model ids only", () [...GITHUB_COPILOT_MODEL_ALLOWLIST], [ "claude-fable-5", + "claude-opus-5", "claude-opus-4.8-fast", "claude-opus-4.8", "claude-opus-4.7", @@ -159,7 +160,7 @@ test("curated Copilot allowlist contains the final approved model ids only", () }); test("newly approved Copilot models survive live and fallback discovery", async () => { - const expected = ["gpt-5.6-sol", "gpt-5.6-terra", "gpt-5.6-luna"]; + const expected = ["claude-opus-5", "gpt-5.6-sol", "gpt-5.6-terra", "gpt-5.6-luna"]; const live = parseGitHubCopilotModels({ data: expected.map((id) => ({ id, name: id })) }); assert.deepEqual( live.map((model) => model.id), diff --git a/tests/unit/glm-executor.test.ts b/tests/unit/glm-executor.test.ts index 0ae127d7e851..f0b0d9e1882f 100644 --- a/tests/unit/glm-executor.test.ts +++ b/tests/unit/glm-executor.test.ts @@ -181,7 +181,7 @@ test("GlmExecutor separates OpenAI-compatible coding headers from Anthropic head assert.equal(anthropicHeaders["anthropic-version"], "2023-06-01"); assert.match(anthropicHeaders["anthropic-beta"], /claude-code-20250219/); assert.equal(anthropicHeaders["anthropic-dangerous-direct-browser-access"], "true"); - assert.match(anthropicHeaders["User-Agent"], /^claude-cli\/2\.1\.207 \(external, sdk-cli\)$/); + assert.match(anthropicHeaders["User-Agent"], /^claude-cli\/2\.1\.219 \(external, sdk-cli\)$/); assert.equal(anthropicHeaders["X-Stainless-Lang"], "js"); assert.equal(anthropicHeaders["X-Stainless-Runtime"], "node"); }); diff --git a/tests/unit/model-family-fallback-notation.test.ts b/tests/unit/model-family-fallback-notation.test.ts index b52e58e8d1af..e1fdc815b26f 100644 --- a/tests/unit/model-family-fallback-notation.test.ts +++ b/tests/unit/model-family-fallback-notation.test.ts @@ -10,6 +10,11 @@ import assert from "node:assert/strict"; // removed the fabricated ids; `anthropic` genuinely serves them in dot notation. const { getNextFamilyFallback } = await import("../../open-sse/services/modelFamilyFallback.ts"); +test("Opus 5 falls back to the previous Opus tier first", () => { + const next = getNextFamilyFallback("cc/claude-opus-5", new Set(["cc/claude-opus-5"])); + assert.equal(next, "claude/claude-opus-4-8"); +}); + test("Fable 5 falls back to the next-best Opus tier first (not Sonnet) — cc→claude", () => { // `cc` is an alias parseModel normalizes to the `claude` provider. const next = getNextFamilyFallback("cc/claude-fable-5", new Set(["cc/claude-fable-5"])); @@ -18,13 +23,19 @@ test("Fable 5 falls back to the next-best Opus tier first (not Sonnet) — cc→ test("Fable 5 fallback resolves to anthropic's dot-notation model id", () => { // anthropic registry exposes `claude-opus-4.8` (dot), not `claude-opus-4-8`. - const next = getNextFamilyFallback("anthropic/claude-fable-5", new Set(["anthropic/claude-fable-5"])); + const next = getNextFamilyFallback( + "anthropic/claude-fable-5", + new Set(["anthropic/claude-fable-5"]) + ); assert.equal(next, "anthropic/claude-opus-4.8"); }); test("dot-notation current model is normalized for the family lookup", () => { // anthropic/claude-opus-4.8 must find the claude-opus-4-8 family entry. - const next = getNextFamilyFallback("anthropic/claude-opus-4.8", new Set(["anthropic/claude-opus-4.8"])); + const next = getNextFamilyFallback( + "anthropic/claude-opus-4.8", + new Set(["anthropic/claude-opus-4.8"]) + ); assert.equal(next, "anthropic/claude-opus-4.7"); }); diff --git a/tests/unit/pricing-cc-anthropic-rates.test.ts b/tests/unit/pricing-cc-anthropic-rates.test.ts index 4c905ede025d..bb008f1f33e8 100644 --- a/tests/unit/pricing-cc-anthropic-rates.test.ts +++ b/tests/unit/pricing-cc-anthropic-rates.test.ts @@ -13,6 +13,15 @@ import { getDefaultPricing } from "../../src/shared/constants/pricing.ts"; // - cache hit (cached) = 0.1x input // - reasoning tokens are billed at the OUTPUT rate +test("cc/claude-opus-5 matches Anthropic Opus 5 pricing", () => { + const p = getDefaultPricing().cc["claude-opus-5"]; + assert.equal(p.input, 5.0); + assert.equal(p.output, 25.0); + assert.equal(p.cached, 0.5); + assert.equal(p.reasoning, 25.0); + assert.equal(p.cache_creation, 6.25); +}); + test("cc/claude-opus-4-6 matches Anthropic Opus 4.6 pricing", () => { const p = getDefaultPricing().cc["claude-opus-4-6"]; assert.equal(p.input, 5.0); diff --git a/tests/unit/provider-models-config.test.ts b/tests/unit/provider-models-config.test.ts index 1e2a307a24ba..234528395935 100644 --- a/tests/unit/provider-models-config.test.ts +++ b/tests/unit/provider-models-config.test.ts @@ -88,6 +88,7 @@ test("GitHub Copilot registry reflects the current supported model lineup", () = const ids = githubModels.map((model) => model.id); assert.deepEqual(ids, [...GITHUB_COPILOT_MODEL_ALLOWLIST]); + assert.equal(getModelTargetFormat("gh", "claude-opus-5"), "claude"); assert.equal(getModelTargetFormat("gh", "gpt-5.3-codex"), "openai-responses"); // "claude-opus-4.6" is not a real Copilot model id (unlike claude-sonnet-4.6); // it never appears in the registry, so its target format stays null. @@ -110,6 +111,16 @@ test("GitHub Copilot registry reflects the current supported model lineup", () = assert.equal(ids.includes("gemini-3-flash-preview"), false); }); +test("Claude flagship catalogs keep Fable 5 first", () => { + for (const provider of ["anthropic", "cc", "cw", "gh", "ghe-copilot"]) { + assert.equal( + getProviderModels(provider)[0]?.id, + "claude-fable-5", + `${provider} must list the strongest Claude model first` + ); + } +}); + test("Kiro registry exposes the current CLI model lineup with context windows", () => { const kiroModels = getProviderModels("kr"); const byId = new Map(kiroModels.map((model) => [model.id, model])); @@ -143,6 +154,8 @@ test("Claude max effort support excludes Haiku family and non-Claude IDs", () => test("xhigh effort support defaults to pass-through and opts out explicit false models", () => { const claudeModels = new Set(getModelsByProviderId("claude").map((model) => model.id)); + assert.ok(claudeModels.has("claude-opus-5")); + assert.equal(supportsXHighEffort("claude", "claude-opus-5"), true); assert.ok(claudeModels.has("claude-opus-4-8")); assert.equal(supportsXHighEffort("claude", "claude-opus-4-8"), true); assert.equal(supportsXHighEffort("claude", "claude-opus-4-7"), true); diff --git a/tests/unit/provider-registry-github-copilot-targetformat.test.ts b/tests/unit/provider-registry-github-copilot-targetformat.test.ts index 22fd9c30f04e..10433e1da8b4 100644 --- a/tests/unit/provider-registry-github-copilot-targetformat.test.ts +++ b/tests/unit/provider-registry-github-copilot-targetformat.test.ts @@ -30,6 +30,7 @@ function githubModel(id: string): ModelEntry | undefined { // Claude/Gemini models that must NOT route through the Responses API. const MUST_NOT_BE_RESPONSES = [ "claude-fable-5", + "claude-opus-5", "claude-opus-4.7", "claude-opus-4.8", "claude-opus-4.8-fast", diff --git a/tests/unit/settings-transform-schema.test.ts b/tests/unit/settings-transform-schema.test.ts index a1eb6d16159f..33c8d9ecb962 100644 --- a/tests/unit/settings-transform-schema.test.ts +++ b/tests/unit/settings-transform-schema.test.ts @@ -44,6 +44,7 @@ const commonOperations = [ versionFormat: "ex-machina", cchAlgo: "sha256-first-user", version: "1.0.0", + buildRevision: "250", }, ] as const; diff --git a/tests/unit/sync-env.test.ts b/tests/unit/sync-env.test.ts index be958e6117d9..415e689a3b09 100644 --- a/tests/unit/sync-env.test.ts +++ b/tests/unit/sync-env.test.ts @@ -25,7 +25,7 @@ function writeEnvExample(rootDir: string) { "MACHINE_ID_SALT=", "CLAUDE_OAUTH_CLIENT_ID=claude-default", "CODEX_OAUTH_CLIENT_ID=codex-default", - 'CLAUDE_USER_AGENT="claude-cli/2.1.145 (external, cli)"', + 'CLAUDE_USER_AGENT="claude-cli/2.1.219 (external, cli)"', "# COMMENTED_KEY=skip-me", "", ].join("\n"), @@ -72,7 +72,7 @@ test("syncEnv creates .env from .env.example and generates install-time secrets" assert.match(envContent, /^MACHINE_ID_SALT=omniroute-/m); assert.match(envContent, /^CLAUDE_OAUTH_CLIENT_ID=claude-default$/m); assert.match(envContent, /^CODEX_OAUTH_CLIENT_ID=codex-default$/m); - assert.match(envContent, /^CLAUDE_USER_AGENT="claude-cli\/2\.1\.145 \(external, cli\)"$/m); + assert.match(envContent, /^CLAUDE_USER_AGENT="claude-cli\/2\.1\.219 \(external, cli\)"$/m); assert.doesNotMatch(envContent, /^COMMENTED_KEY=/m); } finally { process.env.DATA_DIR = origDataDir; @@ -107,7 +107,7 @@ test("syncEnv appends only missing keys and preserves existing values", () => { assert.match(envContent, /^STORAGE_ENCRYPTION_KEY=$/m); assert.match(envContent, /^MACHINE_ID_SALT=omniroute-/m); assert.match(envContent, /^CODEX_OAUTH_CLIENT_ID=codex-default$/m); - assert.match(envContent, /^CLAUDE_USER_AGENT=claude-cli\/2\.1\.145 \(external, cli\)$/m); + assert.match(envContent, /^CLAUDE_USER_AGENT=claude-cli\/2\.1\.219 \(external, cli\)$/m); assert.match(envContent, /Auto-added by sync-env/); } finally { process.env.DATA_DIR = origDataDir; @@ -130,7 +130,7 @@ test("syncEnv treats quoted and unquoted values as equivalent", () => { "MACHINE_ID_SALT=machine-salt", "CLAUDE_OAUTH_CLIENT_ID=claude-default", "CODEX_OAUTH_CLIENT_ID=codex-default", - 'CLAUDE_USER_AGENT="claude-cli/2.1.145 (external, cli)"', + 'CLAUDE_USER_AGENT="claude-cli/2.1.219 (external, cli)"', "", ].join("\n"), "utf8" diff --git a/tests/unit/system-transforms.test.ts b/tests/unit/system-transforms.test.ts index 8c576b4e4b73..30b5a178c7c1 100644 --- a/tests/unit/system-transforms.test.ts +++ b/tests/unit/system-transforms.test.ts @@ -542,6 +542,7 @@ const UI_DEFAULTS_SNAPSHOT = { entrypoint: "sdk-cli", versionFormat: "ex-machina", cchAlgo: "sha256-first-user", + buildRevision: "250", }, ], }, diff --git a/tests/unit/t12-pricing-updates.test.ts b/tests/unit/t12-pricing-updates.test.ts index 799f7cec76ff..5f66d6ea184f 100644 --- a/tests/unit/t12-pricing-updates.test.ts +++ b/tests/unit/t12-pricing-updates.test.ts @@ -37,6 +37,10 @@ test("T12: pricing table includes current Codex, MiniMax, GLM and Kimi entries", assert.ok(pricing.kimi["kimi-k2.5-thinking"], "missing kimi/kimi-k2.5-thinking"); assert.ok(pricing.kimi["kimi-for-coding"], "missing kimi/kimi-for-coding"); + assert.ok(pricing.anthropic["claude-opus-5"], "missing anthropic/claude-opus-5"); + assert.equal(pricing.anthropic["claude-opus-5"].input, 5); + assert.equal(pricing.anthropic["claude-opus-5"].output, 25); + assert.ok(pricing.gh["claude-opus-5"], "missing gh/claude-opus-5"); assert.ok(pricing.anthropic["claude-opus-4.8"], "missing anthropic/claude-opus-4.8"); assert.ok(pricing.anthropic["claude-opus-4-8"], "missing anthropic/claude-opus-4-8"); assert.ok(pricing.anthropic["claude-opus-4-7"], "missing anthropic/claude-opus-4-7"); diff --git a/tests/unit/t31-t33-t34-t38-model-specs.test.ts b/tests/unit/t31-t33-t34-t38-model-specs.test.ts index 998a2d615715..f47f22d8b24c 100644 --- a/tests/unit/t31-t33-t34-t38-model-specs.test.ts +++ b/tests/unit/t31-t33-t34-t38-model-specs.test.ts @@ -52,6 +52,7 @@ test("T34: max output tokens are capped by model spec", () => { assert.equal(capMaxOutputTokens("gemini-3-flash", 131072), 65536); assert.equal(capMaxOutputTokens("gemini-3-flash"), 65536); assert.equal(capMaxOutputTokens("gemini-3.1-pro-high", 131072), 65535); + assert.equal(capMaxOutputTokens("claude-opus-5", 200000), 128000); assert.equal(capMaxOutputTokens("claude-opus-4-8", 200000), 128000); assert.equal(capMaxOutputTokens("claude-opus-4-7", 200000), 128000); assert.equal(capMaxOutputTokens("anthropic.claude-sonnet-4-6", 200000), 64000); @@ -67,6 +68,8 @@ test("T38: modelSpecs exposes centralized helpers with alias and prefix lookup", assert.equal(getModelSpec("gemini-3-flash-preview").maxOutputTokens, 65536); assert.equal(getModelSpec("gemini-3.1-pro-preview").maxOutputTokens, 65535); assert.equal(getModelSpec("gemini-3.1-pro-preview-customtools").maxOutputTokens, 65535); + assert.equal(getModelSpec("claude-opus-5").contextWindow, 1000000); + assert.equal(getModelSpec("anthropic.claude-opus-5").maxOutputTokens, 128000); assert.equal(getModelSpec("claude-opus-4-7").contextWindow, 1000000); assert.equal(getModelSpec("claude-opus-4.8").maxOutputTokens, 128000); assert.equal(getModelSpec("claude-opus-4.7").maxOutputTokens, 128000);