diff --git a/bin/cli/commands/providers.mjs b/bin/cli/commands/providers.mjs index 10b15414bb..83953997dd 100644 --- a/bin/cli/commands/providers.mjs +++ b/bin/cli/commands/providers.mjs @@ -344,7 +344,10 @@ export async function runProvidersRotateCommand(selector, opts = {}) { const readline = await import("node:readline"); const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); newKey = await new Promise((resolve) => - rl.question(`New API key for ${connection.name}: `, (a) => { rl.close(); resolve(a.trim()); }) + rl.question(`New API key for ${connection.name}: `, (a) => { + rl.close(); + resolve(a.trim()); + }) ); if (!newKey) { console.error("No key provided."); @@ -354,7 +357,9 @@ export async function runProvidersRotateCommand(selector, opts = {}) { // --- Dry-run --- if (opts.dryRun) { - console.log(t("providers.rotate.dryRunResult", { name: connection.name, id: connection.id.slice(0, 8) })); + console.log( + t("providers.rotate.dryRunResult", { name: connection.name, id: connection.id.slice(0, 8) }) + ); return 0; } @@ -363,7 +368,13 @@ export async function runProvidersRotateCommand(selector, opts = {}) { const readline = await import("node:readline"); const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); const answer = await new Promise((resolve) => - rl.question(t("providers.rotate.confirmPrompt", { name: connection.name, id: connection.id.slice(0, 8) }), resolve) + rl.question( + t("providers.rotate.confirmPrompt", { + name: connection.name, + id: connection.id.slice(0, 8), + }), + resolve + ) ); rl.close(); if (!/^y(es|s)?$/i.test(answer)) { @@ -378,7 +389,13 @@ export async function runProvidersRotateCommand(selector, opts = {}) { try { const res = await apiFetch(`/api/providers/${encodeURIComponent(connection.id)}`, { method: "PATCH", - body: { apiKey: newKey, testStatus: "unknown", lastError: null, rateLimitedUntil: null, backoffLevel: 0 }, + body: { + apiKey: newKey, + testStatus: "unknown", + lastError: null, + rateLimitedUntil: null, + backoffLevel: 0, + }, retry: false, acceptNotOk: true, }); @@ -404,7 +421,9 @@ export async function runProvidersRotateCommand(selector, opts = {}) { } } - console.log(t("providers.rotate.success", { name: connection.name, id: connection.id.slice(0, 8) })); + console.log( + t("providers.rotate.success", { name: connection.name, id: connection.id.slice(0, 8) }) + ); // --- Post-rotation test --- if (!opts.skipTest) { @@ -468,8 +487,8 @@ export async function runProvidersStatusCommand(opts = {}) { const testColor = statusColor(testStatus); console.log( `${shortId.padEnd(10)} ${String(item.provider || "").padEnd(14)} ${String(item.name || "").padEnd(24)} ` + - `${expiry.padEnd(12)} ${expiryColor}${expiryStatus.padEnd(8)}\x1b[0m ` + - `${testColor}${testStatus.padEnd(12)}\x1b[0m ${cooldown}` + `${expiry.padEnd(12)} ${expiryColor}${expiryStatus.padEnd(8)}\x1b[0m ` + + `${testColor}${testStatus.padEnd(12)}\x1b[0m ${cooldown}` ); } @@ -543,7 +562,10 @@ export function registerProviders(program) { .option("--dry-run", t("providers.rotate.dryRunOpt")) .action(async (idOrName, opts, cmd) => { const globalOpts = cmd.parent.optsWithGlobals(); - const exitCode = await runProvidersRotateCommand(idOrName, { ...opts, output: globalOpts.output }); + const exitCode = await runProvidersRotateCommand(idOrName, { + ...opts, + output: globalOpts.output, + }); if (exitCode !== 0) process.exit(exitCode); }); diff --git a/docs/guides/KIRO_SETUP.md b/docs/guides/KIRO_SETUP.md index 63f4f97eeb..9466a89ff8 100644 --- a/docs/guides/KIRO_SETUP.md +++ b/docs/guides/KIRO_SETUP.md @@ -27,12 +27,12 @@ re-authenticating one account does not affect any other account's refresh token. The isolation applies to all three import methods: -| Import method | Isolation status | -|---|---| -| AWS Builder ID / IDC device-code flow | Isolated since the device-code flow was introduced | -| **Import Token** (manual refresh token paste) | Isolated from v3.8.0 | -| **Google / GitHub social login** | Isolated from v3.8.0 | -| **Auto-Import** (kiro-cli SQLite) | Isolated from v3.8.0 (SQLite path was already isolated; SSO-cache fallback is now also isolated) | +| Import method | Isolation status | +| --------------------------------------------- | ------------------------------------------------------------------------------------------------ | +| AWS Builder ID / IDC device-code flow | Isolated since the device-code flow was introduced | +| **Import Token** (manual refresh token paste) | Isolated from v3.8.0 | +| **Google / GitHub social login** | Isolated from v3.8.0 | +| **Auto-Import** (kiro-cli SQLite) | Isolated from v3.8.0 (SQLite path was already isolated; SSO-cache fallback is now also isolated) | --- diff --git a/docs/providers/ZED-DOCKER.md b/docs/providers/ZED-DOCKER.md index b5ccea499f..633499412a 100644 --- a/docs/providers/ZED-DOCKER.md +++ b/docs/providers/ZED-DOCKER.md @@ -114,9 +114,9 @@ On success it returns: ## Troubleshooting -| Symptom | Cause | Fix | -|---------|-------|-----| -| 422 + `zedDockerEnvironment: true` | Running inside Docker | Use Manual Token Import tab | -| 404 + `zedInstalled: false` | Zed not installed on host | Install Zed or use manual import | -| 403 + keychain access denied | OS denied keychain access | Grant permission in OS prompt | -| 404 + keychain service not available | `libsecret` missing on Linux | Install `libsecret-1-dev` | +| Symptom | Cause | Fix | +| ------------------------------------ | ---------------------------- | -------------------------------- | +| 422 + `zedDockerEnvironment: true` | Running inside Docker | Use Manual Token Import tab | +| 404 + `zedInstalled: false` | Zed not installed on host | Install Zed or use manual import | +| 403 + keychain access denied | OS denied keychain access | Grant permission in OS prompt | +| 404 + keychain service not available | `libsecret` missing on Linux | Install `libsecret-1-dev` | diff --git a/docs/reference/PROVIDER_REFERENCE.md b/docs/reference/PROVIDER_REFERENCE.md index b897a2542c..af2c3fb87b 100644 --- a/docs/reference/PROVIDER_REFERENCE.md +++ b/docs/reference/PROVIDER_REFERENCE.md @@ -59,14 +59,14 @@ Use the dashboard at `/dashboard/providers` to enable, configure, and test each ## Web Cookie Providers (7) -| ID | Alias | Name | Tags | Website | Notes | -| ---------------- | ---------- | --------------------------- | ---------- | --------------------------------- | ------------------------------------------------------------------------------------------- | -| `blackbox-web` | `bb-web` | Blackbox Web (Subscription) | Web cookie | [link](https://app.blackbox.ai) | Paste your \_\_Secure-authjs.session-token value or full cookie header from app.blackbox.ai | -| `chatgpt-web` | `cgpt-web` | ChatGPT Web (Plus/Pro) | Web cookie | [link](https://chatgpt.com) | Paste your \_\_Secure-next-auth.session-token cookie value from chatgpt.com | -| `deepseek-web` | `ds-web` | DeepSeek Web | Web cookie | [link](https://chat.deepseek.com) | Paste your ds_session_id cookie from chat.deepseek.com | -| `grok-web` | `gw` | Grok Web (Subscription) | Web cookie | [link](https://grok.com) | Paste your sso= cookie value from grok.com | -| `muse-spark-web` | `ms-web` | Muse Spark Web (Meta AI) | Web cookie | [link](https://www.meta.ai) | Paste your abra_sess value or full cookie header from meta.ai | -| `perplexity-web` | `pplx-web` | Perplexity Web (Pro/Max) | Web cookie | [link](https://www.perplexity.ai) | Paste your \_\_Secure-next-auth.session-token cookie value from perplexity.ai | +| ID | Alias | Name | Tags | Website | Notes | +| ---------------- | ---------- | --------------------------- | ---------- | --------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `blackbox-web` | `bb-web` | Blackbox Web (Subscription) | Web cookie | [link](https://app.blackbox.ai) | Paste your \_\_Secure-authjs.session-token value or full cookie header from app.blackbox.ai | +| `chatgpt-web` | `cgpt-web` | ChatGPT Web (Plus/Pro) | Web cookie | [link](https://chatgpt.com) | Paste your \_\_Secure-next-auth.session-token cookie value from chatgpt.com | +| `deepseek-web` | `ds-web` | DeepSeek Web | Web cookie | [link](https://chat.deepseek.com) | Paste your ds_session_id cookie from chat.deepseek.com | +| `grok-web` | `gw` | Grok Web (Subscription) | Web cookie | [link](https://grok.com) | Paste your sso= cookie value from grok.com | +| `muse-spark-web` | `ms-web` | Muse Spark Web (Meta AI) | Web cookie | [link](https://www.meta.ai) | Paste your abra_sess value or full cookie header from meta.ai | +| `perplexity-web` | `pplx-web` | Perplexity Web (Pro/Max) | Web cookie | [link](https://www.perplexity.ai) | Paste your \_\_Secure-next-auth.session-token cookie value from perplexity.ai | | `t3-web` | `t3chat` | t3.chat (Pro/Free) | Web cookie | [link](https://t3.chat) | Pro: $8/mo, 50+ models. Free tier: limited models. Requires Cookie header + convex-session-id from DevTools. **Skeleton — endpoint URL not yet confirmed (TODO post-devtools-capture).** | ## API Key Providers (paid / paid-with-free-credits) (122) diff --git a/docs/routing/AUTO-COMBO.md b/docs/routing/AUTO-COMBO.md index f3f2ed0134..af684a56c4 100644 --- a/docs/routing/AUTO-COMBO.md +++ b/docs/routing/AUTO-COMBO.md @@ -87,17 +87,17 @@ The Auto-Combo Engine dynamically selects the best provider/model for each reque > Source: [diagrams/auto-combo-9factor.mmd](../diagrams/auto-combo-9factor.mmd) -| Factor | Default Weight | Description | -| :----------------- | :------------- | :---------------------------------------------------------------------- | -| `health` | 0.22 | Health score from circuit breaker (CLOSED=1.0, HALF_OPEN=0.5, OPEN=0.0) | -| `quota` | 0.17 | Remaining quota / rate-limit headroom [0..1] | +| Factor | Default Weight | Description | +| :----------------- | :------------- | :------------------------------------------------------------------------------------------------- | +| `health` | 0.22 | Health score from circuit breaker (CLOSED=1.0, HALF_OPEN=0.5, OPEN=0.0) | +| `quota` | 0.17 | Remaining quota / rate-limit headroom [0..1] | | `costInv` | 0.17 | Inverse **blended** cost (60% input + 40% output token price, normalized) — cheaper = higher score | -| `latencyInv` | 0.13 | Inverse p95 latency normalized to pool — faster = higher score | -| `taskFit` | 0.08 | Task-type fitness (coding, review, planning, analysis, debugging, docs) | -| `specificityMatch` | 0.08 | Match between request specificity (manifest hint) and model tier | -| `stability` | 0.05 | Variance-based stability (low latency stdDev / error rate) | -| `tierPriority` | 0.05 | Account-tier priority — Ultra=1.0, Pro=0.67, Standard=0.33, Free=0.0 | -| `tierAffinity` | 0.05 | Affinity between the candidate's tier and the manifest-recommended tier | +| `latencyInv` | 0.13 | Inverse p95 latency normalized to pool — faster = higher score | +| `taskFit` | 0.08 | Task-type fitness (coding, review, planning, analysis, debugging, docs) | +| `specificityMatch` | 0.08 | Match between request specificity (manifest hint) and model tier | +| `stability` | 0.05 | Variance-based stability (low latency stdDev / error rate) | +| `tierPriority` | 0.05 | Account-tier priority — Ultra=1.0, Pro=0.67, Standard=0.33, Free=0.0 | +| `tierAffinity` | 0.05 | Affinity between the candidate's tier and the manifest-recommended tier | **Sum:** `0.22 + 0.17 + 0.17 + 0.13 + 0.08 + 0.08 + 0.05 + 0.05 + 0.05 = 1.0` (validated by `validateWeights()`). @@ -210,16 +210,16 @@ Including the bare `auto` (default) plus the 6 `AutoVariant` values declared in The 9-factor scoring function (`open-sse/services/autoCombo/scoring.ts`) treats tier membership as one signal via the `tierPriority` weight. Default weights (from `DEFAULT_WEIGHTS`): -| Factor | Default weight | Notes | -| ------------------------ | -------------- | --------------------------------- | -| Tier priority | 0.05 | Tier 1 premium → higher score | -| Latency (p50 inverse) | 0.35 | Fastest wins | +| Factor | Default weight | Notes | +| ------------------------ | -------------- | -------------------------------------------------------------- | +| Tier priority | 0.05 | Tier 1 premium → higher score | +| Latency (p50 inverse) | 0.35 | Fastest wins | | Cost ($/1M inverse) | 0.20 | Cheapest **blended** price wins (60% input + 40% output ratio) | -| Recent health/error rate | 0.15 | Unhealthy deprioritized | -| Quota remaining | 0.10 | Near-exhausted deprioritized | -| Context window match | 0.08 | Penalizes short windows | -| Task fitness | 0.10 | Coding → coding-specialist models | -| Stability | 0.00 | Disabled by default | +| Recent health/error rate | 0.15 | Unhealthy deprioritized | +| Quota remaining | 0.10 | Near-exhausted deprioritized | +| Context window match | 0.08 | Penalizes short windows | +| Task fitness | 0.10 | Coding → coding-specialist models | +| Stability | 0.00 | Disabled by default | Tier alone does **not** force Tier 1 first — if Tier 1 latency is bad or cost-vs-quality is suboptimal, Tier 2 wins. To force tier ordering, use combo diff --git a/docs/security/ERROR_SANITIZATION.md b/docs/security/ERROR_SANITIZATION.md index 1b15304c8e..4c47a109d0 100644 --- a/docs/security/ERROR_SANITIZATION.md +++ b/docs/security/ERROR_SANITIZATION.md @@ -139,6 +139,7 @@ parsed body from the upstream provider). When provided, it is sanitized by `sanitizeUpstreamDetails` before inclusion in the response as `upstream_details`. Sanitization rules applied to `upstreamDetails`: + 1. String leaves: run through `sanitizeErrorMessage` (strips stacks + absolute paths). 2. Key blocklist: keys matching `/stack|trace|path|file|cwd|dir|password|secret|token|key/i` are removed. diff --git a/open-sse/executors/t3-chat-web.ts b/open-sse/executors/t3-chat-web.ts index 0467d54757..aebf8fc3dd 100644 --- a/open-sse/executors/t3-chat-web.ts +++ b/open-sse/executors/t3-chat-web.ts @@ -58,8 +58,7 @@ export interface T3ChatCredentials { // ── Helpers ────────────────────────────────────────────────────────────── function validateCredentials(creds: unknown): creds is T3ChatCredentials { - const raw = - typeof creds === "object" && creds !== null ? (creds as Record) : {}; + const raw = typeof creds === "object" && creds !== null ? (creds as Record) : {}; return ( typeof raw.cookies === "string" && raw.cookies.length > 0 && @@ -373,10 +372,7 @@ export class T3ChatWebExecutor extends BaseExecutor { // TODO(post-devtools-capture): Map the actual t3.chat non-streaming response // shape to OpenAI format once the real field names are confirmed. const content = - (json as any)?.content ?? - (json as any)?.text ?? - (json as any)?.message?.content ?? - ""; + (json as any)?.content ?? (json as any)?.text ?? (json as any)?.message?.content ?? ""; const openaiResponse = { id: `chatcmpl-t3-${Date.now()}`, object: "chat.completion", diff --git a/open-sse/services/combo.ts b/open-sse/services/combo.ts index 6a9db8ce0e..02d4f24425 100644 --- a/open-sse/services/combo.ts +++ b/open-sse/services/combo.ts @@ -2658,10 +2658,7 @@ async function handleRoundRobinCombo({ // same-provider targets are skipped immediately. if (provider && isProviderExhaustedReason(fallbackResult)) { exhaustedProviders.add(provider); - log.info( - "COMBO-RR", - `Provider ${provider} quota exhausted — marking for skip (#1731)` - ); + log.info("COMBO-RR", `Provider ${provider} quota exhausted — marking for skip (#1731)`); } const isAllAccountsRateLimited = isAllAccountsRateLimitedResponse( diff --git a/src/app/(dashboard)/dashboard/providers/[id]/page.tsx b/src/app/(dashboard)/dashboard/providers/[id]/page.tsx index 7d67f8606d..aecf37d9ad 100644 --- a/src/app/(dashboard)/dashboard/providers/[id]/page.tsx +++ b/src/app/(dashboard)/dashboard/providers/[id]/page.tsx @@ -3373,8 +3373,8 @@ export default function ProviderDetailPage() { Import from Zed Keychain

- Discover AI provider credentials (OpenAI, Anthropic, Google, Mistral, xAI) that Zed - IDE stored in the OS keychain and import them as connections. Requires Zed IDE + Discover AI provider credentials (OpenAI, Anthropic, Google, Mistral, xAI) that + Zed IDE stored in the OS keychain and import them as connections. Requires Zed IDE installed on this machine.

@@ -3408,8 +3408,8 @@ export default function ProviderDetailPage() {

Use this when OmniRoute runs in Docker or the keychain is unavailable. Paste the API key that Zed stored under{" "} - ~/.config/zed/settings.json or copy it - from the Zed AI settings panel. + ~/.config/zed/settings.json or copy + it from the Zed AI settings panel.