diff --git a/.env.example b/.env.example
index 1a4aa95f8c..058bfc547d 100644
--- a/.env.example
+++ b/.env.example
@@ -895,9 +895,6 @@ CODEX_OAUTH_CLIENT_ID=app_EMoamEEZ73f0CkXaXp7hrann
# ANTIGRAVITY_OAUTH_CLIENT_SECRET=
# WINDSURF_FIREBASE_API_KEY=
-# ── Qwen (Alibaba) ──
-QWEN_OAUTH_CLIENT_ID=f0304373b74a44d2b584a3fb70ca9e56
-
# ── Kimi Coding (Moonshot) ──
KIMI_CODING_OAUTH_CLIENT_ID=17e5f671-d194-4dfb-9706-5516cb48c098
@@ -1038,7 +1035,6 @@ KIRO_USER_AGENT="AWS-SDK-JS/3.0.0 kiro-ide/1.0.0"
# Used by: open-sse/executors/kiro.ts
# KIRO_VERIFY_FULL_CRC=false
QODER_USER_AGENT="Qoder-Cli"
-QWEN_USER_AGENT="QwenCode/0.19.3 (linux; x64)"
CURSOR_USER_AGENT="Cursor/3.4"
# Override Codex client version sent in headers independently of the
@@ -1068,8 +1064,6 @@ CURSOR_USER_AGENT="Cursor/3.4"
# CLI_COMPAT_KIMI_CODING=1
# CLI_COMPAT_KILOCODE=1
# CLI_COMPAT_CLINE=1
-# CLI_COMPAT_QWEN=1
-
# Or enable for all providers at once:
# CLI_COMPAT_ALL=1
diff --git a/AGENTS.md b/AGENTS.md
index d996a87055..b8af5518dd 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -267,8 +267,8 @@ Zod schemas, and unit tests aligned when editing.
### Provider Categories
-- **Free** (3): Qoder AI, Qwen Code, Kiro AI
-- **OAuth** (14): Claude Code, Antigravity, Codex, GitHub Copilot, Cursor, Kimi Coding, Kilo Code, Cline, Qwen (⚠️ free tier discontinued 2026-04-15), Kiro, Qoder, Gemini, Windsurf (v3.8), GitLab Duo (v3.8)
+- **Free** (2): Qoder AI, Kiro AI
+- **OAuth** (13): Claude Code, Antigravity, Codex, GitHub Copilot, Cursor, Kimi Coding, Kilo Code, Cline, Kiro, Qoder, Gemini, Windsurf (v3.8), GitLab Duo (v3.8)
- **API Key** (120+): OpenAI, Anthropic, Gemini, DeepSeek, Groq, xAI, Mistral, Perplexity,
Together, Fireworks, Cerebras, Cohere, NVIDIA, Nebius, SiliconFlow, Hyperbolic,
HuggingFace, OpenRouter, Vertex AI, Cloudflare AI, Scaleway, AI/ML API, Pollinations,
diff --git a/README.md b/README.md
index 4236e4790e..aa7a70210d 100644
--- a/README.md
+++ b/README.md
@@ -292,7 +292,6 @@ All **18** strategies — mix & match per combo step:
 Kilo Code |
  Roo Code |
 Continue |
-  Qwen Code |
 Aider |
 ForgeCode |
diff --git a/SECURITY.md b/SECURITY.md
index 6b9c5a4f60..9bafdbd286 100644
--- a/SECURITY.md
+++ b/SECURITY.md
@@ -42,7 +42,7 @@ Request → CORS → Authz pipeline (classify → policies → enforce)
| --------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
| **Dashboard Login** | Password-based auth with JWT tokens (HttpOnly cookies) |
| **API Key Auth** | HMAC-signed keys with CRC validation |
-| **OAuth 2.0 + PKCE** | 14 providers (Claude, Codex, GitHub, Cursor, Antigravity, Gemini, Kimi Coding, Kilo Code, Cline, Qwen, Kiro, Qoder, Windsurf, GitLab Duo) |
+| **OAuth 2.0 + PKCE** | 13 providers (Claude, Codex, GitHub, Cursor, Antigravity, Gemini, Kimi Coding, Kilo Code, Cline, Kiro, Qoder, Windsurf, GitLab Duo) |
| **Token Refresh** | Automatic OAuth token refresh before expiry |
| **Secure Cookies** | `AUTH_COOKIE_SECURE=true` for HTTPS environments |
| **Authz Pipeline** | Route classification (PUBLIC / CLIENT_API / MANAGEMENT) — see `docs/architecture/AUTHZ_GUIDE.md` |
diff --git a/bin/cli/commands/oauth.mjs b/bin/cli/commands/oauth.mjs
index 2d907df39f..9bcbc92d6c 100644
--- a/bin/cli/commands/oauth.mjs
+++ b/bin/cli/commands/oauth.mjs
@@ -7,7 +7,6 @@ const PROVIDERS_WITH_OAUTH = [
{ id: "gemini", name: "Google Gemini", flow: "browser" },
{ id: "antigravity", name: "Antigravity", flow: "browser" },
{ id: "windsurf", name: "Windsurf", flow: "browser" },
- { id: "qwen", name: "Qwen Code", flow: "browser" },
{ id: "cursor", name: "Cursor", flow: "import" },
{ id: "zed", name: "Zed", flow: "import" },
{ id: "kiro", name: "Amazon Kiro", flow: "social" },
diff --git a/bin/cli/commands/registry.mjs b/bin/cli/commands/registry.mjs
index 010392f601..84c71bdf06 100644
--- a/bin/cli/commands/registry.mjs
+++ b/bin/cli/commands/registry.mjs
@@ -70,8 +70,8 @@ import { registerSetupCursor } from "./setup-cursor.mjs";
import { registerSetupRoo } from "./setup-roo.mjs";
import { registerSetupCrush } from "./setup-crush.mjs";
import { registerSetupGoose } from "./setup-goose.mjs";
-import { registerSetupQwen } from "./setup-qwen.mjs";
import { registerSetupAider } from "./setup-aider.mjs";
+import { registerSetupQwen } from "./setup-qwen.mjs";
import { registerConnect } from "./connect.mjs";
import { registerContexts } from "./contexts.mjs";
import { registerTokens } from "./tokens.mjs";
@@ -153,8 +153,8 @@ export function registerCommands(program) {
registerSetupRoo(program);
registerSetupCrush(program);
registerSetupGoose(program);
- registerSetupQwen(program);
registerSetupAider(program);
+ registerSetupQwen(program);
registerConnect(program);
registerContexts(program);
registerTokens(program);
diff --git a/bin/cli/commands/setup-qwen.mjs b/bin/cli/commands/setup-qwen.mjs
index dd34300560..ee5ec6d18d 100644
--- a/bin/cli/commands/setup-qwen.mjs
+++ b/bin/cli/commands/setup-qwen.mjs
@@ -1,156 +1,166 @@
-/**
- * omniroute setup-qwen — configure Qwen Code (QwenLM/qwen-code) for OmniRoute.
- *
- * Qwen Code is a terminal AI agent with a file-based config at
- * ~/.qwen/settings.json. For a custom OpenAI-compatible endpoint it uses a
- * `modelProviders` entry with authType "openai", baseUrl WITH /v1, and an
- * `envKey` naming the env var holding the key (secret stays in the env, never the
- * file). Remote-aware; headless test via `qwen -p "..."`.
- */
+/** Configure Qwen Code's OpenAI-compatible provider for OmniRoute. */
-import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
-import { join } from "node:path";
+import {
+ chmodSync,
+ existsSync,
+ mkdirSync,
+ readFileSync,
+ renameSync,
+ unlinkSync,
+ writeFileSync,
+} from "node:fs";
import os from "node:os";
-import { printHeading, printInfo, printSuccess, printError, createPrompt } from "../io.mjs";
+import path from "node:path";
+
+import {
+ mergeQwenCodeEnv,
+ mergeQwenCodeSettings,
+ normalizeQwenCodeBaseUrl,
+} from "../../../src/shared/services/qwenCodeConfig.ts";
import { resolveActiveContext } from "../contexts.mjs";
+import { createPrompt, printError, printHeading, printInfo, printSuccess } from "../io.mjs";
-function ensureV1(url) {
- const s = String(url || "").replace(/\/+$/, "");
- return s.endsWith("/v1") ? s : `${s}/v1`;
-}
-
-/** Resolve baseUrl (WITH /v1) + apiKey from flags → active context → localhost. */
+/** Resolve base URL and key from flags, active context, then local defaults. */
export function resolveQwenTarget(opts = {}) {
- let root;
- if (opts.remote) root = String(opts.remote).replace(/\/+$/, "");
- else {
+ let root = opts.remote ? String(opts.remote) : "";
+ let context;
+
+ if (!root || !(opts.apiKey ?? opts["api-key"])) {
try {
- root = resolveActiveContext(opts.context ?? process.env.OMNIROUTE_CONTEXT)?.baseUrl;
+ context = resolveActiveContext(opts.context ?? process.env.OMNIROUTE_CONTEXT);
} catch {
- /* none */
- }
- if (!root) root = `http://localhost:${Number(opts.port ?? process.env.PORT ?? 20128) || 20128}`;
- }
- let apiKey = opts.apiKey ?? opts["api-key"];
- if (!apiKey) {
- try {
- const c = resolveActiveContext(opts.context ?? process.env.OMNIROUTE_CONTEXT);
- apiKey = c?.accessToken || c?.apiKey;
- } catch {
- /* none */
+ // An active context is optional for local setup.
}
}
- if (!apiKey) apiKey = process.env.OMNIROUTE_API_KEY || "";
- return { baseUrl: ensureV1(root), apiKey };
-}
-/** Merge the OmniRoute modelProvider into Qwen's settings.json (preserve rest). */
-export function buildQwenSettings(existing, { baseUrl, model }) {
- const s = existing && typeof existing === "object" ? { ...existing } : {};
- const providers = Array.isArray(s.modelProviders)
- ? s.modelProviders.filter((p) => p?.id !== "omniroute")
- : [];
- providers.push({
- id: "omniroute",
- name: "OmniRoute",
- authType: "openai",
- baseUrl,
- envKey: "OMNIROUTE_API_KEY",
- });
- s.modelProviders = providers;
- if (model) {
- s.selectedProvider = "omniroute";
- s.model = model;
+ if (!root) root = context?.baseUrl || "";
+ if (!root) {
+ const port = Number(opts.port ?? process.env.PORT ?? 20128) || 20128;
+ root = `http://localhost:${port}`;
}
- return s;
+
+ const apiKey =
+ opts.apiKey ??
+ opts["api-key"] ??
+ context?.accessToken ??
+ context?.apiKey ??
+ process.env.OMNIROUTE_API_KEY ??
+ "sk_omniroute";
+
+ return { baseUrl: normalizeQwenCodeBaseUrl(root), apiKey };
}
-function readJson(path) {
- try {
- if (existsSync(path)) return JSON.parse(readFileSync(path, "utf8"));
- } catch {
- /* corrupt/missing */
+const readSettings = (filePath) => {
+ if (!existsSync(filePath)) return {};
+ const parsed = JSON.parse(readFileSync(filePath, "utf8"));
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
+ throw new Error("Qwen Code settings.json must contain a JSON object");
}
- return {};
-}
+ return parsed;
+};
-async function fetchModelIds(baseUrl, apiKey) {
+const readText = (filePath) => (existsSync(filePath) ? readFileSync(filePath, "utf8") : "");
+
+const writeAtomic = (filePath, content, mode) => {
+ const tempPath = `${filePath}.${process.pid}.${Date.now()}.tmp`;
try {
- const headers = { "Content-Type": "application/json" };
- if (apiKey) headers["Authorization"] = `Bearer ${apiKey}`;
- const res = await fetch(`${baseUrl.replace(/\/v1$/, "")}/v1/models`, {
- headers,
+ writeFileSync(tempPath, content, { encoding: "utf8", mode });
+ if (mode !== undefined) chmodSync(tempPath, mode);
+ renameSync(tempPath, filePath);
+ } catch (error) {
+ try {
+ unlinkSync(tempPath);
+ } catch {
+ // The temporary file may not have been created.
+ }
+ throw error;
+ }
+};
+
+const fetchModelIds = async (baseUrl, apiKey) => {
+ try {
+ const response = await fetch(`${baseUrl}/models`, {
+ headers: { Authorization: `Bearer ${apiKey}` },
signal: AbortSignal.timeout(8000),
});
- if (!res.ok) return [];
- const body = await res.json();
- const list = Array.isArray(body) ? body : (body.data ?? body.models ?? []);
- return list.map((m) => (typeof m === "string" ? m : m?.id)).filter(Boolean);
+ if (!response.ok) return [];
+ const body = await response.json();
+ const models = Array.isArray(body) ? body : (body.data ?? body.models ?? []);
+ return models.map((entry) => (typeof entry === "string" ? entry : entry?.id)).filter(Boolean);
} catch {
return [];
}
-}
+};
export async function runSetupQwenCommand(opts = {}) {
const { baseUrl, apiKey } = resolveQwenTarget(opts);
const dryRun = Boolean(opts.dryRun ?? opts["dry-run"]);
- const configPath =
- opts.configPath ?? opts["config-path"] ?? join(os.homedir(), ".qwen", "settings.json");
+ const settingsPath =
+ opts.configPath ?? opts["config-path"] ?? path.join(os.homedir(), ".qwen", "settings.json");
+ const envPath = opts.envPath ?? opts["env-path"] ?? path.join(path.dirname(settingsPath), ".env");
- printHeading("OmniRoute → Qwen Code (openai-compatible)");
+ printHeading("OmniRoute → Qwen Code (OpenAI-compatible)");
printInfo(`baseUrl: ${baseUrl}`);
- let model = opts.model;
- if (!model) {
- const ids = await fetchModelIds(baseUrl, apiKey);
- if (ids.length && !opts.yes) {
- printInfo(`Examples: ${ids.slice(0, 20).join(", ")}${ids.length > 20 ? " …" : ""}`);
- const prompt = createPrompt();
- try {
- model = await prompt.ask("Model id for Qwen");
- } finally {
- prompt.close();
- }
+ let model = String(opts.model || "").trim();
+ if (!model && !opts.yes) {
+ const modelIds = await fetchModelIds(baseUrl, apiKey);
+ if (modelIds.length > 0) {
+ printInfo(`Examples: ${modelIds.slice(0, 20).join(", ")}${modelIds.length > 20 ? " …" : ""}`);
+ }
+ const prompt = createPrompt();
+ try {
+ model = String(await prompt.ask("Model id for Qwen Code")).trim();
+ } finally {
+ prompt.close();
}
}
+
if (!model) {
printError("A model is required. Pass --model .");
return 2;
}
- const merged = buildQwenSettings(readJson(configPath), { baseUrl, model });
- const out = JSON.stringify(merged, null, 2) + "\n";
+ try {
+ const settings = mergeQwenCodeSettings(readSettings(settingsPath), { baseUrl, model });
+ const envText = mergeQwenCodeEnv(readText(envPath), apiKey);
+ const settingsText = `${JSON.stringify(settings, null, 2)}\n`;
- if (dryRun) {
- console.log("\n" + out);
- printInfo(`[dry-run] → ${configPath}`);
- } else {
- mkdirSync(join(configPath, ".."), { recursive: true });
- writeFileSync(configPath, out, "utf8");
- printSuccess(`Wrote ${configPath}`);
+ if (dryRun) {
+ console.log(`\n${settingsText}`);
+ printInfo(`[dry-run] settings → ${settingsPath}`);
+ printInfo(`[dry-run] credential → ${envPath} (OMNIROUTE_API_KEY)`);
+ return 0;
+ }
+
+ mkdirSync(path.dirname(settingsPath), { recursive: true, mode: 0o700 });
+ mkdirSync(path.dirname(envPath), { recursive: true, mode: 0o700 });
+ writeAtomic(settingsPath, settingsText);
+ writeAtomic(envPath, envText, 0o600);
+ printSuccess(`Wrote ${settingsPath}`);
+ printSuccess(`Updated ${envPath} (OMNIROUTE_API_KEY only)`);
+ printInfo('Run: qwen (or headless: qwen -p "reply OK")');
+ return 0;
+ } catch (error) {
+ printError(`Failed to configure Qwen Code: ${error?.message || error}`);
+ return 1;
}
- printInfo(
- "\nProvide the key (settings reference OMNIROUTE_API_KEY): export OMNIROUTE_API_KEY=..."
- );
- printInfo('Then run: qwen (or headless: qwen -p "reply OK")');
- return 0;
}
export function registerSetupQwen(program) {
program
.command("setup-qwen")
- .description(
- "Configure Qwen Code for OmniRoute: write ~/.qwen/settings.json (openai modelProvider)"
- )
+ .description("Configure Qwen Code's upstream V4 modelProviders format for OmniRoute")
.option("--port ", "Local OmniRoute port (ignored when --remote is set)", "20128")
- .option("--remote ", "Remote OmniRoute URL, e.g. http://192.168.0.15:20128")
- .option("--api-key ", "OmniRoute API key (defaults to OMNIROUTE_API_KEY env var)")
- .option("--model ", "Model id for Qwen (required unless picked interactively)")
- .option("--config-path ", "settings.json path (default: ~/.qwen/settings.json)")
- .option("--yes", "Non-interactive: do not prompt (requires --model)")
- .option("--dry-run", "Print what would be written without touching the filesystem")
+ .option("--remote ", "Remote OmniRoute URL")
+ .option("--api-key ", "OmniRoute API key")
+ .option("--model ", "Model id for Qwen Code")
+ .option("--config-path ", "Qwen Code settings.json path")
+ .option("--env-path ", "Qwen Code .env path")
+ .option("--yes", "Non-interactive; requires --model")
+ .option("--dry-run", "Print settings without writing files or secrets")
.action(async (opts) => {
const code = await runSetupQwenCommand(opts);
- if (code !== 0) process.exit(code);
+ if (code !== 0) process.exitCode = code;
});
}
diff --git a/changelog.d/maintenance/7690-basered-full-suite-realignment.md b/changelog.d/maintenance/7690-basered-full-suite-realignment.md
index f746b0dc68..b95e13a7d7 100644
--- a/changelog.d/maintenance/7690-basered-full-suite-realignment.md
+++ b/changelog.d/maintenance/7690-basered-full-suite-realignment.md
@@ -1 +1 @@
-- Realigned 13 test files that had drifted red on the release tip after the 102-PR merge campaign (full-suite sweep): provider counts/goldens/docs regenerated, qwen OAuth + 1M-beta assertions caught up with the live-validated behavior, and two real fixes — legacy `refresh_token` column healed before its index is created, and `shouldSkipCloudSyncInitialization` no longer swaps its `(env, argv)` arguments.
+- Realigned 13 test files that had drifted red on the release tip after the 102-PR merge campaign (full-suite sweep): provider counts/goldens/docs regenerated, 1M-beta assertions caught up with the live-validated behavior, and two real fixes — legacy `refresh_token` column healed before its index is created, and `shouldSkipCloudSyncInitialization` no longer swaps its `(env, argv)` arguments.
diff --git a/changelog.d/maintenance/tokenrefresh-provider-extraction.md b/changelog.d/maintenance/tokenrefresh-provider-extraction.md
index 74b3fb37b8..a813551b0c 100644
--- a/changelog.d/maintenance/tokenrefresh-provider-extraction.md
+++ b/changelog.d/maintenance/tokenrefresh-provider-extraction.md
@@ -1 +1 @@
-- **chore(quality):** split `open-sse/services/tokenRefresh.ts` (2249 lines, frozen) into per-provider leaves — `open-sse/services/tokenRefresh/providers/{windsurf,codebuddyCn,cline,kimiCoding,gitlabDuo,claudeOAuth,google,qwen,codex,kiro,qoder,github,copilot}.ts` plus a `tokenRefresh/shared.ts` for the cross-provider OAuth-error classifier and form-body helper — pure move, zero behavior change; the file shrinks to ~989 lines and every previously-public export (`refreshCopilotToken`, `getAccessToken`, `extractOAuthErrorCode`, etc.) is re-exported unchanged so no importer needed to change. Idea originally proposed by @KooshaPari in #7338 against a much older base; redone here against the current tip.
+- **chore(quality):** split `open-sse/services/tokenRefresh.ts` (2249 lines, frozen) into per-provider leaves — `open-sse/services/tokenRefresh/providers/{windsurf,codebuddyCn,cline,kimiCoding,gitlabDuo,claudeOAuth,google,codex,kiro,qoder,github,copilot}.ts` plus a `tokenRefresh/shared.ts` for the cross-provider OAuth-error classifier and form-body helper — pure move, zero behavior change; the file shrinks to ~989 lines and every previously-public export (`refreshCopilotToken`, `getAccessToken`, `extractOAuthErrorCode`, etc.) is re-exported unchanged so no importer needed to change. Idea originally proposed by @KooshaPari in #7338 against a much older base; redone here against the current tip.
diff --git a/config/quality/eslint-suppressions.json b/config/quality/eslint-suppressions.json
index 37c9219ae1..27a834b377 100644
--- a/config/quality/eslint-suppressions.json
+++ b/config/quality/eslint-suppressions.json
@@ -202,11 +202,6 @@
"count": 1
}
},
- "src/app/(dashboard)/dashboard/cli-code/components/DefaultToolCard.tsx": {
- "react-hooks/exhaustive-deps": {
- "count": 2
- }
- },
"src/app/(dashboard)/dashboard/costs/costExplorerUtils.ts": {
"no-restricted-syntax": {
"count": 1
@@ -709,7 +704,7 @@
},
"tests/unit/chatcore-translation-paths.test.ts": {
"@typescript-eslint/no-explicit-any": {
- "count": 40
+ "count": 34
}
},
"tests/unit/chatgpt-web-tools-5240.test.ts": {
@@ -1354,7 +1349,7 @@
},
"tests/unit/executor-default-base.test.ts": {
"@typescript-eslint/no-explicit-any": {
- "count": 43
+ "count": 42
}
},
"tests/unit/executor-github.test.ts": {
@@ -1457,11 +1452,6 @@
"count": 25
}
},
- "tests/unit/guide-settings-route.test.ts": {
- "@typescript-eslint/no-explicit-any": {
- "count": 4
- }
- },
"tests/unit/i18n-nest-dotted-keys.test.ts": {
"@typescript-eslint/no-explicit-any": {
"count": 4
@@ -1749,7 +1739,7 @@
},
"tests/unit/provider-models-route.test.ts": {
"@typescript-eslint/no-explicit-any": {
- "count": 55
+ "count": 54
}
},
"tests/unit/provider-models-token-limits.test.ts": {
@@ -2234,7 +2224,7 @@
},
"tests/unit/usage-service-hardening.test.ts": {
"@typescript-eslint/no-explicit-any": {
- "count": 59
+ "count": 56
}
},
"tests/unit/v1-ws-bridge.test.ts": {
@@ -2297,4 +2287,4 @@
"count": 5
}
}
-}
\ No newline at end of file
+}
diff --git a/config/quality/file-size-baseline.json b/config/quality/file-size-baseline.json
index 0672067e33..56c421b099 100644
--- a/config/quality/file-size-baseline.json
+++ b/config/quality/file-size-baseline.json
@@ -91,7 +91,7 @@
"_rebaseline_2026_06_18_qg9_chatcore_split_prB": "QG v2 Fase 9 T5 C6-C7: chatCore.ts 5265->5060 (shrink, stacked on prA #4188) — two pure, byte-identical extractions into open-sse/handlers/chatCore/. (1) comboContextCache.ts (5265 (wc -l 5264 + 1). Fase 2 of the chatCore split following #4159 (which created 10 leaf modules). Three more sibling leaves created under open-sse/handlers/chatCore/, all 866 (+21) and EditConnectionModal.tsx 1174->1204 (+30) = the 'import only free models' connection option — a free-models Toggle gated by providerHasFreeModels() plus its form-state wiring (importFreeModelsOnly field + providerSpecificData persistence, explicit-false on edit so the PUT merge doesn't keep a stale true) added to both connection modals, mirroring the prior per-modal toggle bumps #3879 (redact-thinking) and #2997 (disable-cooling). Detection lives in the new shared src/shared/utils/freeModels.ts (112 LOC, 2531 (+19 = one PROVIDER_MODELS_CONFIG entry for `qwen-web` + a 4-line comment). qwen-web was missing from the config map so its model-discovery page returned nothing (the OAuth fallback only fires for provider===qwen). Pure additive config entry pointing at the public chat.qwen.ai/api/v2/models endpoint; standard per-provider addition, not extractable.",
+ "_rebaseline_2026_06_18_3931_qwen_web_models": "Issue #3931 (Problem #3) own growth: providers/[id]/models/route.ts 2512->2531 (+19 = one PROVIDER_MODELS_CONFIG entry for `qwen-web` + a 4-line comment). qwen-web was missing from the config map so its model-discovery page returned nothing. Pure additive config entry pointing at the public chat.qwen.ai/api/v2/models endpoint; standard per-provider addition, not extractable.",
"_rebaseline_2026_06_18_4165_queue_timeout_msg": "Issue #4165 own growth: rateLimitManager.ts 1022->1035 (+13 at the existing withRateLimit catch chokepoint). Bottleneck's raw `This job timed out after ms.` is rewritten into a clear OmniRoute-owned error (names resilienceSettings.requestQueue.maxWaitMs, disclaims upstream, keeps the original as `cause`, tags code=RATE_LIMIT_QUEUE_TIMEOUT) so queue-saturation 502s stop masquerading as provider outages. The branch already existed (it only logged); this adds the error construction at the same point. Not extractable — closes over provider/model/maxWaitMs locals of the single catch.",
"_rebaseline_2026_06_18_qg9_combo_split_d4": "QG v2 Fase 9 T5 D4: combo.ts 4740->4589 — shadow routing extracted byte-identically to the new open-sse/services/combo/shadowRouting.ts (4430 — target sorters extracted byte-identically to the new open-sse/services/combo/targetSorters.ts (873 (+3: antigravity projectId assertion + 50ms tick for the now fire-and-forget onboarding, matching the no-PKCE/no-openid flow).",
"_rebaseline_2026_07_02_5928_base_red": "web-cookie-providers-new.test.ts 845->850: #5928 (test(security) Kimi Web URL host parse, CodeQL #689) grew the file +5 lines and merged into release/v3.8.44 WITHOUT rebaselining, leaving a fast-gates base-red that blocked every subsequent PR->release. Test growth is legitimate (a security regression test); maintainer absorbs the drift here. Frozen at 850.",
"_rebaseline_basered_codebuddy_cn": "Base-red fix (#4664 CodeBuddy CN): oauth-providers-config.test.ts 867->870 (+3) to align the EXPECTED provider list/config with the codebuddy-cn provider that #4664 added to the registry without updating this test (it asserts 'exactly once').",
- "_rebaseline_pr4561_qwen_oauth_url": "Reconcile #4561 (port decolua/9router#683) already-merged growth: oauth-providers-config.test.ts 855->867 (+12, qwen.ai URL regression-pin test). Fast-gate PR->release does not run check:file-size, so this surfaced post-merge.",
"_rebaseline_pr4613_compatible_provider_groups": "Reconcile #4613 already-merged growth: providers-page-utils.test.ts 1004->1052 (+48, buildCompatibleProviderGroups partition unit test). Fast-gate PR->release does not run check:file-size, so this surfaced post-merge.",
"tests/integration/chat-pipeline.test.ts": 1601,
"tests/integration/chatcore-compression-integration.test.ts": 1111,
@@ -423,4 +422,4 @@
"_rebaseline_2026_07_07_6525_chirag_image_guard": "PR #6525 (@chirag127, #6457) own growth: chat.ts ->1778 (reject image-only models on /v1/chat/completions; stacks on #6515). Owner-approved. Frozen.",
"_rebaseline_2026_07_15_7045_perf_instrumentation": "PR #7045 (@oyi77) own growth: open-sse/utils/stream.ts 2796->2814 (+18) from performance.mark/measure instrumentation around the SSE dispatch chokepoint (b48ba21c4), a TextEncoder hoisting fix to avoid a per-chunk allocation on the hot path (c35e8a9b4), and clearing the fixed-name \"omni-request-body-size\" mark immediately after creation (babysit fix, addressing a review-flagged unbounded-growth leak in Node's global performance timeline). Cohesive wiring at the existing stream-dispatch chokepoint; not extractable. Covered by tests/unit/chatcore-streaming-pipeline.test.ts + tests/unit/stream-request-body-size-mark-7045.test.ts.",
"_rebaseline_2026_07_18_basereds_test_realignment": "Base-red sweep own growth (post 102-PR campaign, full-suite realignment): tests/unit/combo-routing-engine.test.ts 3209->3243 (+34 = least-used tests now prime usage through real handleComboChat calls so recordComboRequest keys by the resolved executionKey exactly as production does — #7015 keying); tests/unit/db-migration-runner.test.ts 1491->1499 (+8 = withNonTestEnvironment now also strips node --test tokens from process.execArgv, matching the #7359 isAutomatedTestProcess widening); tests/unit/executor-default-base.test.ts 1523->1527 (+4 = 1M-beta assertion updated for claude-sonnet-4-6 GA #7129). All three are test-fidelity realignments, not extractable."
-}
\ No newline at end of file
+}
diff --git a/docs/architecture/ARCHITECTURE.md b/docs/architecture/ARCHITECTURE.md
index 6841590774..53b11e7e8e 100644
--- a/docs/architecture/ARCHITECTURE.md
+++ b/docs/architecture/ARCHITECTURE.md
@@ -175,7 +175,7 @@ flowchart LR
end
subgraph Upstreams[Upstream Providers]
- P1[OAuth Providers\nClaude/Codex/Gemini/Qwen/Qoder/GitHub/Kiro/Cursor/Antigravity]
+ P1[OAuth Providers\nClaude/Codex/Gemini/Qoder/GitHub/Kiro/Cursor/Antigravity]
P2[API Key Providers\nOpenAI/Anthropic/OpenRouter/GLM/Kimi/MiniMax\nDeepSeek/Groq/xAI/Mistral/Perplexity\nTogether/Fireworks/Cerebras/Cohere/NVIDIA]
P3[Compatible Nodes\nOpenAI-compatible / Anthropic-compatible]
end
@@ -948,7 +948,6 @@ All other providers (including custom compatible nodes) use the `DefaultExecutor
| GitHub Copilot | openai | OAuth + Copilot Token | ✅ | ✅ | ✅ | ✅ Quota snapshots |
| Cursor | cursor | Custom checksum | ✅ | ✅ | ❌ | ❌ |
| Kiro | kiro | AWS SSO OIDC | ✅ (EventStream) | ❌ | ✅ | ✅ Usage limits |
-| Qwen | openai | OAuth | ✅ | ✅ | ✅ | ⚠️ Per request |
| Qoder | openai | OAuth / PAT | ✅ | ✅ | ✅ | ⚠️ Per request |
| Kilo Code | openai | OAuth | ✅ | ✅ | ✅ | ❌ |
| Cline | openai | OAuth | ✅ | ✅ | ✅ | ❌ |
diff --git a/docs/architecture/CODEBASE_DOCUMENTATION.md b/docs/architecture/CODEBASE_DOCUMENTATION.md
index fe0e187eb2..91b1c989f6 100644
--- a/docs/architecture/CODEBASE_DOCUMENTATION.md
+++ b/docs/architecture/CODEBASE_DOCUMENTATION.md
@@ -293,7 +293,7 @@ table groups the actual directories and notable top-level files.
| `jobs/` | Background jobs (`autoUpdate.ts`, …) |
| `memory/` | Persistent memory: `store.ts`, `cache.ts`, `retrieval.ts`, `summarization.ts`, `extraction.ts`, `injection.ts`, `qdrant.ts`, `settings.ts`, `verify.ts`, `schemas.ts`, `types.ts` |
| `monitoring/` | `observability.ts` |
-| `oauth/` | OAuth providers (14): `antigravity`, `claude`, `cline`, `codex`, `cursor`, `gemini`, `github`, `gitlab-duo`, `kilocode`, `kimi-coding`, `kiro`, `qoder`, `qwen`, `windsurf` plus `services/`, `utils/{pkce, server, banner, codexAuthFile, ui}`, `constants/oauth.ts` |
+| `oauth/` | OAuth providers (13): `antigravity`, `claude`, `cline`, `codex`, `cursor`, `gemini`, `github`, `gitlab-duo`, `kilocode`, `kimi-coding`, `kiro`, `qoder`, `windsurf` plus `services/`, `utils/{pkce, server, banner, codexAuthFile, ui}`, `constants/oauth.ts` |
| `plugins/` | Plugin loader (`index.ts`) |
| `promptCache/` | `prefixAnalyzer.ts`, `index.ts` |
| `providerModels/` | Managed model lifecycle: `modelDiscovery.ts`, `managedModelImport.ts`, `managedAvailableModels.ts`, `cursorAgent.ts` |
diff --git a/docs/architecture/REPOSITORY_MAP.md b/docs/architecture/REPOSITORY_MAP.md
index 2b1c5676d0..302fc1e31f 100644
--- a/docs/architecture/REPOSITORY_MAP.md
+++ b/docs/architecture/REPOSITORY_MAP.md
@@ -182,7 +182,7 @@ src/
| `compliance/` | Audit log + provider audit — see `docs/security/COMPLIANCE.md` |
| `compression/` | Compression engine glue (engines live in `open-sse/services/compression/`) |
| `config/` | Runtime config helpers |
-| `db/` | 95+ domain DB modules + 110+ migrations (always go through here for SQLite) |
+| `db/` | 95+ domain DB modules + 110+ migrations (always go through here for SQLite) |
| `quota/` | Quota Sharing Engine: `dimensions.ts` (types/Zod), `types.ts` (QuotaStore interface), `sqliteQuotaStore.ts`, `redisQuotaStore.ts`, `storeFactory.ts`, `fairShare.ts`, `burnRate.ts`, `planResolver.ts`, `planRegistry.ts`, `saturationSignals.ts`, `enforce.ts`, `spendRecorder.ts` — see `docs/routing/QUOTA_SHARE.md` |
| `display/` | UI formatting helpers (cost, latency, etc.) |
| `embeddings/` | Embeddings service helpers |
@@ -195,7 +195,7 @@ src/
| `memory/vectorStore.ts` | sqlite-vec v0.1.9 wrapper — KNN brute-force + hybrid RRF (FTS5 + vector, k=60). Lazy-init, degrades gracefully when sqlite-vec unavailable. (plan 21) |
| `memory/reindex.ts` | `runReindexBatch()` — processes memories with `needs_reindex=1` in background; called by `POST /api/memory/reindex` and lazy-backfill path. (plan 21) |
| `monitoring/` | Health checks, metrics emission |
-| `oauth/` | OAuth flows for 14 providers (claude, codex, antigravity, cursor, github, gemini, kimi-coding, kilocode, cline, qwen, kiro, qoder, gitlab-duo, windsurf) |
+| `oauth/` | OAuth flows for 13 providers (claude, codex, antigravity, cursor, github, gemini, kimi-coding, kilocode, cline, kiro, qoder, gitlab-duo, windsurf) |
| `plugins/` | Plugin registry |
| `promptCache/` | Anthropic-style prompt cache breakpoints |
| `skills/` | Skills framework (built-in + marketplace + SkillsSH) — see `docs/frameworks/SKILLS.md` |
diff --git a/docs/compression/COMPRESSION_GUIDE.md b/docs/compression/COMPRESSION_GUIDE.md
index 314458f287..1a23310fde 100644
--- a/docs/compression/COMPRESSION_GUIDE.md
+++ b/docs/compression/COMPRESSION_GUIDE.md
@@ -184,7 +184,7 @@ Combo: "free-forever"
Pipeline: RTK -> Caveman
Targets:
1. if/kimi-k2-thinking
- 2. qw/qwen3-coder-plus
+ 2. if/qwen3-coder-plus
```
This lets you use stacked compression on free/coding providers while keeping lite mode on paid
diff --git a/docs/frameworks/ACP.md b/docs/frameworks/ACP.md
index fcfa08d3fc..3a7b386220 100644
--- a/docs/frameworks/ACP.md
+++ b/docs/frameworks/ACP.md
@@ -19,7 +19,7 @@ ACP (Agent Client Protocol) is a **"CLI-as-backend" transport** for OmniRoute. I
| **No API keys needed** | Uses your existing CLI authentication |
| **Native protocol** | Uses each CLI's native input/output format |
| **Auto-discovery** | Detects installed CLIs on your system |
-| **14 built-in agents** | Pre-configured for popular CLI tools |
+| **13 built-in agents** | Pre-configured for popular CLI tools |
| **Custom agents** | Add your own CLI tools via settings |
| **Process management** | Handles lifecycle (spawn, send, kill) |
@@ -27,7 +27,7 @@ ACP (Agent Client Protocol) is a **"CLI-as-backend" transport** for OmniRoute. I
## Supported CLI Agents
-ACP supports **14 built-in CLI agents** out of the box:
+ACP supports **13 built-in CLI agents** out of the box:
| Agent ID | Display Name | Binary | Protocol |
| ------------- | ------------------ | ------------- | -------- |
@@ -38,7 +38,7 @@ ACP supports **14 built-in CLI agents** out of the box:
| `aider` | Aider | `aider` | stdio |
| `opencode` | OpenCode | `opencode` | stdio |
| `cline` | Cline | `cline` | stdio |
-| `qwen-code` | Qwen Code | `qwen` | stdio |
+| `qwen` | Qwen Code | `qwen --acp` | stdio |
| `forge` | ForgeCode | `forge` | stdio |
| `amazon-q` | Amazon Q Developer | `q` | stdio |
| `interpreter` | Open Interpreter | `interpreter` | stdio |
@@ -407,7 +407,7 @@ Each ACP session runs in its own child process. The process is killed when the s
**Problem**: `acpManager.spawn()` throws `Unknown agent: `
-**Solution**: Only 4 agents are allowed in `spawn()`:
+**Solution**: Only these agents are allowed in `spawn()`:
- `claude`
- `codex`
diff --git a/docs/guides/CLI-INTEGRATIONS.md b/docs/guides/CLI-INTEGRATIONS.md
index ca4d90a363..cd2a3890b1 100644
--- a/docs/guides/CLI-INTEGRATIONS.md
+++ b/docs/guides/CLI-INTEGRATIONS.md
@@ -11,8 +11,8 @@ CLI (Codex, Claude Code, OpenCode, Cline, …) to use OmniRoute as its backend
the tool talks to **one** endpoint and OmniRoute routes to the right provider with
auto-fallback. Each command reads the **live** model catalog from a running
OmniRoute (local or remote) and writes the tool's own config file on **your**
-machine. The API key is referenced by env var wherever the tool supports it, so the
-secret is never written to disk (the exceptions are noted below).
+machine. The API key is referenced by an environment variable wherever the tool
+supports it. Commands that persist a tool-local environment file are noted below.
There are also two launchers — `omniroute launch` (Claude Code) and
`omniroute launch-codex` (Codex) — that spawn the CLI with the right env injected,
@@ -47,8 +47,8 @@ server and writes the config locally.
| `omniroute setup-roo` | Roo Code | `~/.omniroute/roo-settings.json` (import doc) + sets `roo-cline.autoImportSettingsPath` if a VS Code `settings.json` exists | `--remote` `--api-key` `--model` `--yes` `--dry-run` `--port` `--import-path` `--vscode-settings` | Both |
| `omniroute setup-crush` | Crush | `~/.config/crush/crush.json` — `openai-compat` provider, key via `$OMNIROUTE_API_KEY` | `--remote` `--api-key` `--only` `--dry-run` `--port` `--config-path` | Both |
| `omniroute setup-goose` | Goose | `~/.config/goose/config.yaml` (`GOOSE_PROVIDER`/`OPENAI_HOST`/`GOOSE_MODEL`) + prints env recipe | `--remote` `--api-key` `--model` `--yes` `--dry-run` `--port` `--config-path` | Both |
-| `omniroute setup-qwen` | Qwen Code | `~/.qwen/settings.json` — openai `modelProvider`, key via `envKey` (`OMNIROUTE_API_KEY`) | `--remote` `--api-key` `--model` `--yes` `--dry-run` `--port` `--config-path` | Both |
| `omniroute setup-aider` | Aider | `~/.aider.conf.yml` (`openai-api-base` + `model: openai/`) + prints env recipe | `--remote` `--api-key` `--model` `--yes` `--dry-run` `--port` `--config-path` | Both |
+| `omniroute setup-qwen` | Qwen Code | `~/.qwen/settings.json` — V4 `modelProviders.openai` array + `OMNIROUTE_API_KEY` in `~/.qwen/.env` | `--remote` `--api-key` `--model` `--yes` `--dry-run` `--port` `--config-path` `--env-path` | Both |
| `omniroute launch` | Claude Code | Nothing — spawns `claude` with `ANTHROPIC_BASE_URL`/`ANTHROPIC_AUTH_TOKEN` injected | `--remote` `--api-key` `--token` `--profile` `--port` | Both |
| `omniroute launch-codex` | OpenAI Codex CLI | Nothing — spawns `codex` with the `omniroute` provider injected via `-c` flags | `--remote` `--api-key` `--profile` (`-p`) `--port` | Both |
@@ -101,7 +101,7 @@ opencode -m omniroute/glm/glm-5.2 "..."
# Tools without auto-discovery need an explicit model:
omniroute setup-aider --model glm/glm-5.2
-omniroute setup-qwen --model kmc/kimi-k2.7
+omniroute setup-qwen --model qwen/qwen3.8-max-preview
# Preview without writing anything:
omniroute setup-continue --dry-run
@@ -161,9 +161,10 @@ tool expects (verified in the command source):
| `setup-cline` (`openAiBaseUrl`) | root | No — Cline appends `/v1/chat/completions` |
| `setup-goose` (`OPENAI_HOST`) | root | No — Goose appends the path |
| `setup-aider` (`OPENAI_API_BASE`) | root | No — LiteLLM appends `/v1/chat/completions` |
-| `setup-kilo`, `setup-roo`, `setup-continue`, `setup-crush`, `setup-qwen`, `setup-cursor` | with `/v1` | Yes |
+| `setup-kilo`, `setup-roo`, `setup-continue`, `setup-crush`, `setup-cursor` | with `/v1` | Yes |
| `setup-claude` (`ANTHROPIC_BASE_URL`), `launch` | root | No — Claude Code appends `/v1/messages` |
| `setup-codex`, `launch-codex` (`model_providers.omniroute.base_url`) | with `/v1` | Yes |
+| `setup-qwen` (`modelProviders.openai[].baseUrl`) | with `/v1` | Yes |
---
diff --git a/docs/guides/FEATURES.md b/docs/guides/FEATURES.md
index 6f7ece1eb3..92f2a9f388 100644
--- a/docs/guides/FEATURES.md
+++ b/docs/guides/FEATURES.md
@@ -51,7 +51,7 @@ The v3.7.x → v3.8.0 cycle added zero-config auto routing, new providers, OAuth
## 🔌 Providers
-Manage AI provider connections: OAuth providers (Claude Code, Codex), API key providers (Groq, DeepSeek, OpenRouter), and free providers (Qoder, Qwen, Kiro). Kiro accounts include credit balance tracking — remaining credits, total allowance, and renewal date visible in Dashboard → Usage.
+Manage AI provider connections: OAuth providers (Claude Code, Codex), API key providers (Groq, DeepSeek, OpenRouter), and free providers (Qoder, Kiro). Kiro accounts include credit balance tracking — remaining credits, total allowance, and renewal date visible in Dashboard → Usage.
OpenRouter connections can store a per-connection `preset` in Advanced Settings. When set, OmniRoute sends it as the OpenRouter top-level request field, for example `"preset": "email-copywriter"`, unless the client request already supplied its own `preset`.
@@ -136,7 +136,7 @@ One-click configuration for AI coding tools: Claude Code, Codex CLI, OpenClaw, K
## 🤖 CLI Agents _(v2.0.11+)_
-Dashboard for discovering and managing CLI agents. Shows a grid of 17 built-in agents (Codex, Claude, Goose, OpenClaw, Aider, OpenCode, Cline, Qwen Code, ForgeCode, Amazon Q, Open Interpreter, Cursor CLI, Warp, **Windsurf**, **Devin CLI**, **Kimi Coding**, **Command Code**) with:
+Dashboard for discovering and managing CLI agents. Shows a grid of 16 built-in agents (Codex, Claude, Goose, OpenClaw, Aider, OpenCode, Cline, ForgeCode, Amazon Q, Open Interpreter, Cursor CLI, Warp, **Windsurf**, **Devin CLI**, **Kimi Coding**, **Command Code**) with:
- **Installation status** — Installed / Not Found with version detection
- **Protocol badges** — stdio, HTTP, etc.
diff --git a/docs/guides/REMOTE-MODE.md b/docs/guides/REMOTE-MODE.md
index 7edf52eb2d..e6bdd04e73 100644
--- a/docs/guides/REMOTE-MODE.md
+++ b/docs/guides/REMOTE-MODE.md
@@ -215,8 +215,8 @@ context, or `--remote --api-key `):
| Roo Code | `omniroute setup-roo` | writes a Roo import JSON (`~/.omniroute/roo-settings.json`) + sets `roo-cline.autoImportSettingsPath` + prints UI steps (OpenAI-compatible, Base URL **with** `/v1`) |
| Crush | `omniroute setup-crush` | `~/.config/crush/crush.json` — `openai-compat` provider, `base_url` **with** `/v1`, key via `$OMNIROUTE_API_KEY` |
| Goose | `omniroute setup-goose` | `~/.config/goose/config.yaml` (`GOOSE_PROVIDER=openai` + `OPENAI_HOST` **without** `/v1` + `GOOSE_MODEL`) + env recipe |
-| Qwen Code | `omniroute setup-qwen` | `~/.qwen/settings.json` — openai `modelProvider`, `baseUrl` **with** `/v1`, key via `envKey` (OMNIROUTE_API_KEY) |
| Aider | `omniroute setup-aider` | `~/.aider.conf.yml` (`openai-api-base` **without** `/v1` + `model: openai/`) + env recipe (`aider --message --yes`) |
+| Qwen Code | `omniroute setup-qwen` | `~/.qwen/settings.json` V4 `modelProviders.openai` entry + `OMNIROUTE_API_KEY` in `~/.qwen/.env` |
```bash
# OpenCode (openai-compatible provider, all catalog models, remote VPS)
diff --git a/docs/guides/SETUP_GUIDE.md b/docs/guides/SETUP_GUIDE.md
index 0e6564311c..4f02548857 100644
--- a/docs/guides/SETUP_GUIDE.md
+++ b/docs/guides/SETUP_GUIDE.md
@@ -179,8 +179,8 @@ omniroute setup-cursor # prints Cursor's in-app steps
omniroute setup-roo # Roo Code import + autoImport pointer
omniroute setup-crush # ~/.config/crush/crush.json
omniroute setup-goose # ~/.config/goose/config.yaml
-omniroute setup-qwen # ~/.qwen/settings.json
omniroute setup-aider # ~/.aider.conf.yml
+omniroute setup-qwen # ~/.qwen/settings.json + ~/.qwen/.env
```
Each accepts `--remote --api-key ` to configure a local tool against a
diff --git a/docs/guides/USER_GUIDE.md b/docs/guides/USER_GUIDE.md
index 80505b7c76..2aa7d35f86 100644
--- a/docs/guides/USER_GUIDE.md
+++ b/docs/guides/USER_GUIDE.md
@@ -83,7 +83,7 @@ vs. $20 + hitting limits = frustration
```
Combo: "free-forever"
1. if/kimi-k2 (unlimited free)
- 2. qw/qwen3-coder-plus (unlimited free)
+ 2. kr/qwen3-coder-next (free fallback)
Monthly cost: $0
Quality: Production-ready models
@@ -252,7 +252,7 @@ Use in CLI: premium-coding
Name: free-combo
Models:
1. if/kimi-k2 (unlimited)
- 2. qw/coder-model (unlimited)
+ 2. kr/qwen3-coder-next (free fallback)
Cost: $0 forever!
```
@@ -576,8 +576,6 @@ For the full environment variable reference, see the [README](../README.md).
**Qoder (`if/`)** — FREE OAuth: `if/kimi-k2-0905`, `if/kimi-k2`, `if/qwen3-coder-plus`, `if/qwen3-max`, `if/qwen3-max-preview`, `if/qwen3-vl-plus`, `if/qwen3-32b`, `if/qwen3-235b-a22b-thinking-2507`, `if/qwen3-235b-a22b-instruct`, `if/qwen3-235b`, `if/deepseek-v3.2`, `if/deepseek-v3`, `if/deepseek-r1`, `if/qoder-rome-30ba3b`
-**Qwen (`qw/`)** — FREE OAuth (chat.qwen.ai): `qw/coder-model`, `qw/vision-model`
-
**GLM (`glm/`, `glm-cn/`, `zai/`, `glmt/`)** — $0.2–0.6/1M: `glm/glm-5.1`, `glm/glm-5`, `glm/glm-5-turbo`, `glm/glm-4.7`, `glm/glm-4.7-flash`, `glm/glm-4.6`, `glm/glm-4.6v`, `glm/glm-4.5`, `glm/glm-4.5v`, `glm/glm-4.5-air`
**MiniMax (`minimax/`, `minimax-cn/`)** — $0.2/1M: `minimax/MiniMax-M2.7`, `minimax/MiniMax-M2.7-highspeed`, `minimax/MiniMax-M2.5`, `minimax/MiniMax-M2.5-highspeed`
diff --git a/docs/i18n/ar/llm.txt b/docs/i18n/ar/llm.txt
index bbf5c27c78..d10256c0ea 100644
--- a/docs/i18n/ar/llm.txt
+++ b/docs/i18n/ar/llm.txt
@@ -364,9 +364,9 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **pool**, **notion**, *
### Provider Categories
-**Free Providers (4):** Qoder AI, Qwen Code (deprecated), Kiro AI, Windsurf
+**Free Providers (3):** Qoder AI, Kiro AI, Windsurf
-**OAuth Providers (14):** Claude Code, Antigravity, OpenAI Codex, GitHub Copilot, Cursor IDE, Kimi Coding, Kilo Code, Cline, Qwen, Kiro, Qoder, Gemini, Windsurf, GitLab Duo
+**OAuth Providers (13):** Claude Code, Antigravity, OpenAI Codex, GitHub Copilot, Cursor IDE, Kimi Coding, Kilo Code, Cline, Kiro, Qoder, Gemini, Windsurf, GitLab Duo
**API Key Providers (48+):** OpenAI, Anthropic, Gemini (Google AI Studio), DeepSeek, Groq, xAI (Grok), Mistral, Perplexity, Together AI, Fireworks AI, Cerebras, Cohere, NVIDIA NIM, Nebius AI, SiliconFlow, Hyperbolic, HuggingFace, OpenRouter, Vertex AI, Cloudflare Workers AI, Scaleway AI, AI/ML API, Pollinations AI, Puter AI, LongCat AI, Alibaba, Alibaba (China), Kimi, Kimi Coding (API Key), Minimax, Minimax (China), Blackbox AI, Synthetic, Kilo Gateway, Z.AI, GLM Coding, Deepgram, AssemblyAI, ElevenLabs, Cartesia, PlayHT, Inworld, NanoBanana, SD WebUI, ComfyUI, Ollama Cloud, Perplexity Search, Serper Search, Brave Search, Exa Search, Tavily Search, OpenCode Zen, OpenCode Go, Alibaba Coding Plan
diff --git a/docs/i18n/az/llm.txt b/docs/i18n/az/llm.txt
index f687ce262d..777d36e91b 100644
--- a/docs/i18n/az/llm.txt
+++ b/docs/i18n/az/llm.txt
@@ -364,9 +364,9 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **pool**, **notion**, *
### Provider Categories
-**Free Providers (4):** Qoder AI, Qwen Code (deprecated), Kiro AI, Windsurf
+**Free Providers (3):** Qoder AI, Kiro AI, Windsurf
-**OAuth Providers (14):** Claude Code, Antigravity, OpenAI Codex, GitHub Copilot, Cursor IDE, Kimi Coding, Kilo Code, Cline, Qwen, Kiro, Qoder, Gemini, Windsurf, GitLab Duo
+**OAuth Providers (13):** Claude Code, Antigravity, OpenAI Codex, GitHub Copilot, Cursor IDE, Kimi Coding, Kilo Code, Cline, Kiro, Qoder, Gemini, Windsurf, GitLab Duo
**API Key Providers (48+):** OpenAI, Anthropic, Gemini (Google AI Studio), DeepSeek, Groq, xAI (Grok), Mistral, Perplexity, Together AI, Fireworks AI, Cerebras, Cohere, NVIDIA NIM, Nebius AI, SiliconFlow, Hyperbolic, HuggingFace, OpenRouter, Vertex AI, Cloudflare Workers AI, Scaleway AI, AI/ML API, Pollinations AI, Puter AI, LongCat AI, Alibaba, Alibaba (China), Kimi, Kimi Coding (API Key), Minimax, Minimax (China), Blackbox AI, Synthetic, Kilo Gateway, Z.AI, GLM Coding, Deepgram, AssemblyAI, ElevenLabs, Cartesia, PlayHT, Inworld, NanoBanana, SD WebUI, ComfyUI, Ollama Cloud, Perplexity Search, Serper Search, Brave Search, Exa Search, Tavily Search, OpenCode Zen, OpenCode Go, Alibaba Coding Plan
diff --git a/docs/i18n/bg/llm.txt b/docs/i18n/bg/llm.txt
index f687ce262d..777d36e91b 100644
--- a/docs/i18n/bg/llm.txt
+++ b/docs/i18n/bg/llm.txt
@@ -364,9 +364,9 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **pool**, **notion**, *
### Provider Categories
-**Free Providers (4):** Qoder AI, Qwen Code (deprecated), Kiro AI, Windsurf
+**Free Providers (3):** Qoder AI, Kiro AI, Windsurf
-**OAuth Providers (14):** Claude Code, Antigravity, OpenAI Codex, GitHub Copilot, Cursor IDE, Kimi Coding, Kilo Code, Cline, Qwen, Kiro, Qoder, Gemini, Windsurf, GitLab Duo
+**OAuth Providers (13):** Claude Code, Antigravity, OpenAI Codex, GitHub Copilot, Cursor IDE, Kimi Coding, Kilo Code, Cline, Kiro, Qoder, Gemini, Windsurf, GitLab Duo
**API Key Providers (48+):** OpenAI, Anthropic, Gemini (Google AI Studio), DeepSeek, Groq, xAI (Grok), Mistral, Perplexity, Together AI, Fireworks AI, Cerebras, Cohere, NVIDIA NIM, Nebius AI, SiliconFlow, Hyperbolic, HuggingFace, OpenRouter, Vertex AI, Cloudflare Workers AI, Scaleway AI, AI/ML API, Pollinations AI, Puter AI, LongCat AI, Alibaba, Alibaba (China), Kimi, Kimi Coding (API Key), Minimax, Minimax (China), Blackbox AI, Synthetic, Kilo Gateway, Z.AI, GLM Coding, Deepgram, AssemblyAI, ElevenLabs, Cartesia, PlayHT, Inworld, NanoBanana, SD WebUI, ComfyUI, Ollama Cloud, Perplexity Search, Serper Search, Brave Search, Exa Search, Tavily Search, OpenCode Zen, OpenCode Go, Alibaba Coding Plan
diff --git a/docs/i18n/bn/llm.txt b/docs/i18n/bn/llm.txt
index 14269bdeaa..df9132a1c1 100644
--- a/docs/i18n/bn/llm.txt
+++ b/docs/i18n/bn/llm.txt
@@ -364,9 +364,9 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **pool**, **notion**, *
### Provider Categories
-**Free Providers (4):** Qoder AI, Qwen Code (deprecated), Kiro AI, Windsurf
+**Free Providers (3):** Qoder AI, Kiro AI, Windsurf
-**OAuth Providers (14):** Claude Code, Antigravity, OpenAI Codex, GitHub Copilot, Cursor IDE, Kimi Coding, Kilo Code, Cline, Qwen, Kiro, Qoder, Gemini, Windsurf, GitLab Duo
+**OAuth Providers (13):** Claude Code, Antigravity, OpenAI Codex, GitHub Copilot, Cursor IDE, Kimi Coding, Kilo Code, Cline, Kiro, Qoder, Gemini, Windsurf, GitLab Duo
**API Key Providers (48+):** OpenAI, Anthropic, Gemini (Google AI Studio), DeepSeek, Groq, xAI (Grok), Mistral, Perplexity, Together AI, Fireworks AI, Cerebras, Cohere, NVIDIA NIM, Nebius AI, SiliconFlow, Hyperbolic, HuggingFace, OpenRouter, Vertex AI, Cloudflare Workers AI, Scaleway AI, AI/ML API, Pollinations AI, Puter AI, LongCat AI, Alibaba, Alibaba (China), Kimi, Kimi Coding (API Key), Minimax, Minimax (China), Blackbox AI, Synthetic, Kilo Gateway, Z.AI, GLM Coding, Deepgram, AssemblyAI, ElevenLabs, Cartesia, PlayHT, Inworld, NanoBanana, SD WebUI, ComfyUI, Ollama Cloud, Perplexity Search, Serper Search, Brave Search, Exa Search, Tavily Search, OpenCode Zen, OpenCode Go, Alibaba Coding Plan
diff --git a/docs/i18n/cs/llm.txt b/docs/i18n/cs/llm.txt
index d26dc026f3..66741f71c4 100644
--- a/docs/i18n/cs/llm.txt
+++ b/docs/i18n/cs/llm.txt
@@ -364,9 +364,9 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **pool**, **notion**, *
### Provider Categories
-**Free Providers (4):** Qoder AI, Qwen Code (deprecated), Kiro AI, Windsurf
+**Free Providers (3):** Qoder AI, Kiro AI, Windsurf
-**OAuth Providers (14):** Claude Code, Antigravity, OpenAI Codex, GitHub Copilot, Cursor IDE, Kimi Coding, Kilo Code, Cline, Qwen, Kiro, Qoder, Gemini, Windsurf, GitLab Duo
+**OAuth Providers (13):** Claude Code, Antigravity, OpenAI Codex, GitHub Copilot, Cursor IDE, Kimi Coding, Kilo Code, Cline, Kiro, Qoder, Gemini, Windsurf, GitLab Duo
**API Key Providers (48+):** OpenAI, Anthropic, Gemini (Google AI Studio), DeepSeek, Groq, xAI (Grok), Mistral, Perplexity, Together AI, Fireworks AI, Cerebras, Cohere, NVIDIA NIM, Nebius AI, SiliconFlow, Hyperbolic, HuggingFace, OpenRouter, Vertex AI, Cloudflare Workers AI, Scaleway AI, AI/ML API, Pollinations AI, Puter AI, LongCat AI, Alibaba, Alibaba (China), Kimi, Kimi Coding (API Key), Minimax, Minimax (China), Blackbox AI, Synthetic, Kilo Gateway, Z.AI, GLM Coding, Deepgram, AssemblyAI, ElevenLabs, Cartesia, PlayHT, Inworld, NanoBanana, SD WebUI, ComfyUI, Ollama Cloud, Perplexity Search, Serper Search, Brave Search, Exa Search, Tavily Search, OpenCode Zen, OpenCode Go, Alibaba Coding Plan
diff --git a/docs/i18n/da/llm.txt b/docs/i18n/da/llm.txt
index 8c54b16c7f..c4958aa523 100644
--- a/docs/i18n/da/llm.txt
+++ b/docs/i18n/da/llm.txt
@@ -364,9 +364,9 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **pool**, **notion**, *
### Provider Categories
-**Free Providers (4):** Qoder AI, Qwen Code (deprecated), Kiro AI, Windsurf
+**Free Providers (3):** Qoder AI, Kiro AI, Windsurf
-**OAuth Providers (14):** Claude Code, Antigravity, OpenAI Codex, GitHub Copilot, Cursor IDE, Kimi Coding, Kilo Code, Cline, Qwen, Kiro, Qoder, Gemini, Windsurf, GitLab Duo
+**OAuth Providers (13):** Claude Code, Antigravity, OpenAI Codex, GitHub Copilot, Cursor IDE, Kimi Coding, Kilo Code, Cline, Kiro, Qoder, Gemini, Windsurf, GitLab Duo
**API Key Providers (48+):** OpenAI, Anthropic, Gemini (Google AI Studio), DeepSeek, Groq, xAI (Grok), Mistral, Perplexity, Together AI, Fireworks AI, Cerebras, Cohere, NVIDIA NIM, Nebius AI, SiliconFlow, Hyperbolic, HuggingFace, OpenRouter, Vertex AI, Cloudflare Workers AI, Scaleway AI, AI/ML API, Pollinations AI, Puter AI, LongCat AI, Alibaba, Alibaba (China), Kimi, Kimi Coding (API Key), Minimax, Minimax (China), Blackbox AI, Synthetic, Kilo Gateway, Z.AI, GLM Coding, Deepgram, AssemblyAI, ElevenLabs, Cartesia, PlayHT, Inworld, NanoBanana, SD WebUI, ComfyUI, Ollama Cloud, Perplexity Search, Serper Search, Brave Search, Exa Search, Tavily Search, OpenCode Zen, OpenCode Go, Alibaba Coding Plan
diff --git a/docs/i18n/de/llm.txt b/docs/i18n/de/llm.txt
index fc82d6b08e..050cd8bc4e 100644
--- a/docs/i18n/de/llm.txt
+++ b/docs/i18n/de/llm.txt
@@ -364,9 +364,9 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **pool**, **notion**, *
### Provider Categories
-**Free Providers (4):** Qoder AI, Qwen Code (deprecated), Kiro AI, Windsurf
+**Free Providers (3):** Qoder AI, Kiro AI, Windsurf
-**OAuth Providers (14):** Claude Code, Antigravity, OpenAI Codex, GitHub Copilot, Cursor IDE, Kimi Coding, Kilo Code, Cline, Qwen, Kiro, Qoder, Gemini, Windsurf, GitLab Duo
+**OAuth Providers (13):** Claude Code, Antigravity, OpenAI Codex, GitHub Copilot, Cursor IDE, Kimi Coding, Kilo Code, Cline, Kiro, Qoder, Gemini, Windsurf, GitLab Duo
**API Key Providers (48+):** OpenAI, Anthropic, Gemini (Google AI Studio), DeepSeek, Groq, xAI (Grok), Mistral, Perplexity, Together AI, Fireworks AI, Cerebras, Cohere, NVIDIA NIM, Nebius AI, SiliconFlow, Hyperbolic, HuggingFace, OpenRouter, Vertex AI, Cloudflare Workers AI, Scaleway AI, AI/ML API, Pollinations AI, Puter AI, LongCat AI, Alibaba, Alibaba (China), Kimi, Kimi Coding (API Key), Minimax, Minimax (China), Blackbox AI, Synthetic, Kilo Gateway, Z.AI, GLM Coding, Deepgram, AssemblyAI, ElevenLabs, Cartesia, PlayHT, Inworld, NanoBanana, SD WebUI, ComfyUI, Ollama Cloud, Perplexity Search, Serper Search, Brave Search, Exa Search, Tavily Search, OpenCode Zen, OpenCode Go, Alibaba Coding Plan
diff --git a/docs/i18n/es/llm.txt b/docs/i18n/es/llm.txt
index fa6930d790..54d1c60d56 100644
--- a/docs/i18n/es/llm.txt
+++ b/docs/i18n/es/llm.txt
@@ -364,9 +364,9 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **pool**, **notion**, *
### Provider Categories
-**Free Providers (4):** Qoder AI, Qwen Code (deprecated), Kiro AI, Windsurf
+**Free Providers (3):** Qoder AI, Kiro AI, Windsurf
-**OAuth Providers (14):** Claude Code, Antigravity, OpenAI Codex, GitHub Copilot, Cursor IDE, Kimi Coding, Kilo Code, Cline, Qwen, Kiro, Qoder, Gemini, Windsurf, GitLab Duo
+**OAuth Providers (13):** Claude Code, Antigravity, OpenAI Codex, GitHub Copilot, Cursor IDE, Kimi Coding, Kilo Code, Cline, Kiro, Qoder, Gemini, Windsurf, GitLab Duo
**API Key Providers (48+):** OpenAI, Anthropic, Gemini (Google AI Studio), DeepSeek, Groq, xAI (Grok), Mistral, Perplexity, Together AI, Fireworks AI, Cerebras, Cohere, NVIDIA NIM, Nebius AI, SiliconFlow, Hyperbolic, HuggingFace, OpenRouter, Vertex AI, Cloudflare Workers AI, Scaleway AI, AI/ML API, Pollinations AI, Puter AI, LongCat AI, Alibaba, Alibaba (China), Kimi, Kimi Coding (API Key), Minimax, Minimax (China), Blackbox AI, Synthetic, Kilo Gateway, Z.AI, GLM Coding, Deepgram, AssemblyAI, ElevenLabs, Cartesia, PlayHT, Inworld, NanoBanana, SD WebUI, ComfyUI, Ollama Cloud, Perplexity Search, Serper Search, Brave Search, Exa Search, Tavily Search, OpenCode Zen, OpenCode Go, Alibaba Coding Plan
diff --git a/docs/i18n/fa/llm.txt b/docs/i18n/fa/llm.txt
index 8758d5925b..70d59867f2 100644
--- a/docs/i18n/fa/llm.txt
+++ b/docs/i18n/fa/llm.txt
@@ -364,9 +364,9 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **pool**, **notion**, *
### Provider Categories
-**Free Providers (4):** Qoder AI, Qwen Code (deprecated), Kiro AI, Windsurf
+**Free Providers (3):** Qoder AI, Kiro AI, Windsurf
-**OAuth Providers (14):** Claude Code, Antigravity, OpenAI Codex, GitHub Copilot, Cursor IDE, Kimi Coding, Kilo Code, Cline, Qwen, Kiro, Qoder, Gemini, Windsurf, GitLab Duo
+**OAuth Providers (13):** Claude Code, Antigravity, OpenAI Codex, GitHub Copilot, Cursor IDE, Kimi Coding, Kilo Code, Cline, Kiro, Qoder, Gemini, Windsurf, GitLab Duo
**API Key Providers (48+):** OpenAI, Anthropic, Gemini (Google AI Studio), DeepSeek, Groq, xAI (Grok), Mistral, Perplexity, Together AI, Fireworks AI, Cerebras, Cohere, NVIDIA NIM, Nebius AI, SiliconFlow, Hyperbolic, HuggingFace, OpenRouter, Vertex AI, Cloudflare Workers AI, Scaleway AI, AI/ML API, Pollinations AI, Puter AI, LongCat AI, Alibaba, Alibaba (China), Kimi, Kimi Coding (API Key), Minimax, Minimax (China), Blackbox AI, Synthetic, Kilo Gateway, Z.AI, GLM Coding, Deepgram, AssemblyAI, ElevenLabs, Cartesia, PlayHT, Inworld, NanoBanana, SD WebUI, ComfyUI, Ollama Cloud, Perplexity Search, Serper Search, Brave Search, Exa Search, Tavily Search, OpenCode Zen, OpenCode Go, Alibaba Coding Plan
diff --git a/docs/i18n/fi/llm.txt b/docs/i18n/fi/llm.txt
index 7b832d7f64..b453fd313e 100644
--- a/docs/i18n/fi/llm.txt
+++ b/docs/i18n/fi/llm.txt
@@ -364,9 +364,9 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **pool**, **notion**, *
### Provider Categories
-**Free Providers (4):** Qoder AI, Qwen Code (deprecated), Kiro AI, Windsurf
+**Free Providers (3):** Qoder AI, Kiro AI, Windsurf
-**OAuth Providers (14):** Claude Code, Antigravity, OpenAI Codex, GitHub Copilot, Cursor IDE, Kimi Coding, Kilo Code, Cline, Qwen, Kiro, Qoder, Gemini, Windsurf, GitLab Duo
+**OAuth Providers (13):** Claude Code, Antigravity, OpenAI Codex, GitHub Copilot, Cursor IDE, Kimi Coding, Kilo Code, Cline, Kiro, Qoder, Gemini, Windsurf, GitLab Duo
**API Key Providers (48+):** OpenAI, Anthropic, Gemini (Google AI Studio), DeepSeek, Groq, xAI (Grok), Mistral, Perplexity, Together AI, Fireworks AI, Cerebras, Cohere, NVIDIA NIM, Nebius AI, SiliconFlow, Hyperbolic, HuggingFace, OpenRouter, Vertex AI, Cloudflare Workers AI, Scaleway AI, AI/ML API, Pollinations AI, Puter AI, LongCat AI, Alibaba, Alibaba (China), Kimi, Kimi Coding (API Key), Minimax, Minimax (China), Blackbox AI, Synthetic, Kilo Gateway, Z.AI, GLM Coding, Deepgram, AssemblyAI, ElevenLabs, Cartesia, PlayHT, Inworld, NanoBanana, SD WebUI, ComfyUI, Ollama Cloud, Perplexity Search, Serper Search, Brave Search, Exa Search, Tavily Search, OpenCode Zen, OpenCode Go, Alibaba Coding Plan
diff --git a/docs/i18n/fr/llm.txt b/docs/i18n/fr/llm.txt
index 7978e1e722..4d8f1606ae 100644
--- a/docs/i18n/fr/llm.txt
+++ b/docs/i18n/fr/llm.txt
@@ -364,9 +364,9 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **pool**, **notion**, *
### Provider Categories
-**Free Providers (4):** Qoder AI, Qwen Code (deprecated), Kiro AI, Windsurf
+**Free Providers (3):** Qoder AI, Kiro AI, Windsurf
-**OAuth Providers (14):** Claude Code, Antigravity, OpenAI Codex, GitHub Copilot, Cursor IDE, Kimi Coding, Kilo Code, Cline, Qwen, Kiro, Qoder, Gemini, Windsurf, GitLab Duo
+**OAuth Providers (13):** Claude Code, Antigravity, OpenAI Codex, GitHub Copilot, Cursor IDE, Kimi Coding, Kilo Code, Cline, Kiro, Qoder, Gemini, Windsurf, GitLab Duo
**API Key Providers (48+):** OpenAI, Anthropic, Gemini (Google AI Studio), DeepSeek, Groq, xAI (Grok), Mistral, Perplexity, Together AI, Fireworks AI, Cerebras, Cohere, NVIDIA NIM, Nebius AI, SiliconFlow, Hyperbolic, HuggingFace, OpenRouter, Vertex AI, Cloudflare Workers AI, Scaleway AI, AI/ML API, Pollinations AI, Puter AI, LongCat AI, Alibaba, Alibaba (China), Kimi, Kimi Coding (API Key), Minimax, Minimax (China), Blackbox AI, Synthetic, Kilo Gateway, Z.AI, GLM Coding, Deepgram, AssemblyAI, ElevenLabs, Cartesia, PlayHT, Inworld, NanoBanana, SD WebUI, ComfyUI, Ollama Cloud, Perplexity Search, Serper Search, Brave Search, Exa Search, Tavily Search, OpenCode Zen, OpenCode Go, Alibaba Coding Plan
diff --git a/docs/i18n/gu/llm.txt b/docs/i18n/gu/llm.txt
index 716a3c1a50..5dc7dfee2d 100644
--- a/docs/i18n/gu/llm.txt
+++ b/docs/i18n/gu/llm.txt
@@ -364,9 +364,9 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **pool**, **notion**, *
### Provider Categories
-**Free Providers (4):** Qoder AI, Qwen Code (deprecated), Kiro AI, Windsurf
+**Free Providers (3):** Qoder AI, Kiro AI, Windsurf
-**OAuth Providers (14):** Claude Code, Antigravity, OpenAI Codex, GitHub Copilot, Cursor IDE, Kimi Coding, Kilo Code, Cline, Qwen, Kiro, Qoder, Gemini, Windsurf, GitLab Duo
+**OAuth Providers (13):** Claude Code, Antigravity, OpenAI Codex, GitHub Copilot, Cursor IDE, Kimi Coding, Kilo Code, Cline, Kiro, Qoder, Gemini, Windsurf, GitLab Duo
**API Key Providers (48+):** OpenAI, Anthropic, Gemini (Google AI Studio), DeepSeek, Groq, xAI (Grok), Mistral, Perplexity, Together AI, Fireworks AI, Cerebras, Cohere, NVIDIA NIM, Nebius AI, SiliconFlow, Hyperbolic, HuggingFace, OpenRouter, Vertex AI, Cloudflare Workers AI, Scaleway AI, AI/ML API, Pollinations AI, Puter AI, LongCat AI, Alibaba, Alibaba (China), Kimi, Kimi Coding (API Key), Minimax, Minimax (China), Blackbox AI, Synthetic, Kilo Gateway, Z.AI, GLM Coding, Deepgram, AssemblyAI, ElevenLabs, Cartesia, PlayHT, Inworld, NanoBanana, SD WebUI, ComfyUI, Ollama Cloud, Perplexity Search, Serper Search, Brave Search, Exa Search, Tavily Search, OpenCode Zen, OpenCode Go, Alibaba Coding Plan
diff --git a/docs/i18n/he/llm.txt b/docs/i18n/he/llm.txt
index 17466a0dce..40366a4e91 100644
--- a/docs/i18n/he/llm.txt
+++ b/docs/i18n/he/llm.txt
@@ -364,9 +364,9 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **pool**, **notion**, *
### Provider Categories
-**Free Providers (4):** Qoder AI, Qwen Code (deprecated), Kiro AI, Windsurf
+**Free Providers (3):** Qoder AI, Kiro AI, Windsurf
-**OAuth Providers (14):** Claude Code, Antigravity, OpenAI Codex, GitHub Copilot, Cursor IDE, Kimi Coding, Kilo Code, Cline, Qwen, Kiro, Qoder, Gemini, Windsurf, GitLab Duo
+**OAuth Providers (13):** Claude Code, Antigravity, OpenAI Codex, GitHub Copilot, Cursor IDE, Kimi Coding, Kilo Code, Cline, Kiro, Qoder, Gemini, Windsurf, GitLab Duo
**API Key Providers (48+):** OpenAI, Anthropic, Gemini (Google AI Studio), DeepSeek, Groq, xAI (Grok), Mistral, Perplexity, Together AI, Fireworks AI, Cerebras, Cohere, NVIDIA NIM, Nebius AI, SiliconFlow, Hyperbolic, HuggingFace, OpenRouter, Vertex AI, Cloudflare Workers AI, Scaleway AI, AI/ML API, Pollinations AI, Puter AI, LongCat AI, Alibaba, Alibaba (China), Kimi, Kimi Coding (API Key), Minimax, Minimax (China), Blackbox AI, Synthetic, Kilo Gateway, Z.AI, GLM Coding, Deepgram, AssemblyAI, ElevenLabs, Cartesia, PlayHT, Inworld, NanoBanana, SD WebUI, ComfyUI, Ollama Cloud, Perplexity Search, Serper Search, Brave Search, Exa Search, Tavily Search, OpenCode Zen, OpenCode Go, Alibaba Coding Plan
diff --git a/docs/i18n/hi/llm.txt b/docs/i18n/hi/llm.txt
index 45f2f5ee0e..3733c814c4 100644
--- a/docs/i18n/hi/llm.txt
+++ b/docs/i18n/hi/llm.txt
@@ -364,9 +364,9 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **pool**, **notion**, *
### Provider Categories
-**Free Providers (4):** Qoder AI, Qwen Code (deprecated), Kiro AI, Windsurf
+**Free Providers (3):** Qoder AI, Kiro AI, Windsurf
-**OAuth Providers (14):** Claude Code, Antigravity, OpenAI Codex, GitHub Copilot, Cursor IDE, Kimi Coding, Kilo Code, Cline, Qwen, Kiro, Qoder, Gemini, Windsurf, GitLab Duo
+**OAuth Providers (13):** Claude Code, Antigravity, OpenAI Codex, GitHub Copilot, Cursor IDE, Kimi Coding, Kilo Code, Cline, Kiro, Qoder, Gemini, Windsurf, GitLab Duo
**API Key Providers (48+):** OpenAI, Anthropic, Gemini (Google AI Studio), DeepSeek, Groq, xAI (Grok), Mistral, Perplexity, Together AI, Fireworks AI, Cerebras, Cohere, NVIDIA NIM, Nebius AI, SiliconFlow, Hyperbolic, HuggingFace, OpenRouter, Vertex AI, Cloudflare Workers AI, Scaleway AI, AI/ML API, Pollinations AI, Puter AI, LongCat AI, Alibaba, Alibaba (China), Kimi, Kimi Coding (API Key), Minimax, Minimax (China), Blackbox AI, Synthetic, Kilo Gateway, Z.AI, GLM Coding, Deepgram, AssemblyAI, ElevenLabs, Cartesia, PlayHT, Inworld, NanoBanana, SD WebUI, ComfyUI, Ollama Cloud, Perplexity Search, Serper Search, Brave Search, Exa Search, Tavily Search, OpenCode Zen, OpenCode Go, Alibaba Coding Plan
diff --git a/docs/i18n/hu/llm.txt b/docs/i18n/hu/llm.txt
index ba6d27cb24..9f974a8ea7 100644
--- a/docs/i18n/hu/llm.txt
+++ b/docs/i18n/hu/llm.txt
@@ -364,9 +364,9 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **pool**, **notion**, *
### Provider Categories
-**Free Providers (4):** Qoder AI, Qwen Code (deprecated), Kiro AI, Windsurf
+**Free Providers (3):** Qoder AI, Kiro AI, Windsurf
-**OAuth Providers (14):** Claude Code, Antigravity, OpenAI Codex, GitHub Copilot, Cursor IDE, Kimi Coding, Kilo Code, Cline, Qwen, Kiro, Qoder, Gemini, Windsurf, GitLab Duo
+**OAuth Providers (13):** Claude Code, Antigravity, OpenAI Codex, GitHub Copilot, Cursor IDE, Kimi Coding, Kilo Code, Cline, Kiro, Qoder, Gemini, Windsurf, GitLab Duo
**API Key Providers (48+):** OpenAI, Anthropic, Gemini (Google AI Studio), DeepSeek, Groq, xAI (Grok), Mistral, Perplexity, Together AI, Fireworks AI, Cerebras, Cohere, NVIDIA NIM, Nebius AI, SiliconFlow, Hyperbolic, HuggingFace, OpenRouter, Vertex AI, Cloudflare Workers AI, Scaleway AI, AI/ML API, Pollinations AI, Puter AI, LongCat AI, Alibaba, Alibaba (China), Kimi, Kimi Coding (API Key), Minimax, Minimax (China), Blackbox AI, Synthetic, Kilo Gateway, Z.AI, GLM Coding, Deepgram, AssemblyAI, ElevenLabs, Cartesia, PlayHT, Inworld, NanoBanana, SD WebUI, ComfyUI, Ollama Cloud, Perplexity Search, Serper Search, Brave Search, Exa Search, Tavily Search, OpenCode Zen, OpenCode Go, Alibaba Coding Plan
diff --git a/docs/i18n/id/llm.txt b/docs/i18n/id/llm.txt
index 281a63458b..abd125d652 100644
--- a/docs/i18n/id/llm.txt
+++ b/docs/i18n/id/llm.txt
@@ -364,9 +364,9 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **pool**, **notion**, *
### Provider Categories
-**Free Providers (4):** Qoder AI, Qwen Code (deprecated), Kiro AI, Windsurf
+**Free Providers (3):** Qoder AI, Kiro AI, Windsurf
-**OAuth Providers (14):** Claude Code, Antigravity, OpenAI Codex, GitHub Copilot, Cursor IDE, Kimi Coding, Kilo Code, Cline, Qwen, Kiro, Qoder, Gemini, Windsurf, GitLab Duo
+**OAuth Providers (13):** Claude Code, Antigravity, OpenAI Codex, GitHub Copilot, Cursor IDE, Kimi Coding, Kilo Code, Cline, Kiro, Qoder, Gemini, Windsurf, GitLab Duo
**API Key Providers (48+):** OpenAI, Anthropic, Gemini (Google AI Studio), DeepSeek, Groq, xAI (Grok), Mistral, Perplexity, Together AI, Fireworks AI, Cerebras, Cohere, NVIDIA NIM, Nebius AI, SiliconFlow, Hyperbolic, HuggingFace, OpenRouter, Vertex AI, Cloudflare Workers AI, Scaleway AI, AI/ML API, Pollinations AI, Puter AI, LongCat AI, Alibaba, Alibaba (China), Kimi, Kimi Coding (API Key), Minimax, Minimax (China), Blackbox AI, Synthetic, Kilo Gateway, Z.AI, GLM Coding, Deepgram, AssemblyAI, ElevenLabs, Cartesia, PlayHT, Inworld, NanoBanana, SD WebUI, ComfyUI, Ollama Cloud, Perplexity Search, Serper Search, Brave Search, Exa Search, Tavily Search, OpenCode Zen, OpenCode Go, Alibaba Coding Plan
diff --git a/docs/i18n/in/llm.txt b/docs/i18n/in/llm.txt
index 3a9d45ef20..a91aec3d08 100644
--- a/docs/i18n/in/llm.txt
+++ b/docs/i18n/in/llm.txt
@@ -364,9 +364,9 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **pool**, **notion**, *
### Provider Categories
-**Free Providers (4):** Qoder AI, Qwen Code (deprecated), Kiro AI, Windsurf
+**Free Providers (3):** Qoder AI, Kiro AI, Windsurf
-**OAuth Providers (14):** Claude Code, Antigravity, OpenAI Codex, GitHub Copilot, Cursor IDE, Kimi Coding, Kilo Code, Cline, Qwen, Kiro, Qoder, Gemini, Windsurf, GitLab Duo
+**OAuth Providers (13):** Claude Code, Antigravity, OpenAI Codex, GitHub Copilot, Cursor IDE, Kimi Coding, Kilo Code, Cline, Kiro, Qoder, Gemini, Windsurf, GitLab Duo
**API Key Providers (48+):** OpenAI, Anthropic, Gemini (Google AI Studio), DeepSeek, Groq, xAI (Grok), Mistral, Perplexity, Together AI, Fireworks AI, Cerebras, Cohere, NVIDIA NIM, Nebius AI, SiliconFlow, Hyperbolic, HuggingFace, OpenRouter, Vertex AI, Cloudflare Workers AI, Scaleway AI, AI/ML API, Pollinations AI, Puter AI, LongCat AI, Alibaba, Alibaba (China), Kimi, Kimi Coding (API Key), Minimax, Minimax (China), Blackbox AI, Synthetic, Kilo Gateway, Z.AI, GLM Coding, Deepgram, AssemblyAI, ElevenLabs, Cartesia, PlayHT, Inworld, NanoBanana, SD WebUI, ComfyUI, Ollama Cloud, Perplexity Search, Serper Search, Brave Search, Exa Search, Tavily Search, OpenCode Zen, OpenCode Go, Alibaba Coding Plan
diff --git a/docs/i18n/it/llm.txt b/docs/i18n/it/llm.txt
index c25fa077ba..d407555863 100644
--- a/docs/i18n/it/llm.txt
+++ b/docs/i18n/it/llm.txt
@@ -364,9 +364,9 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **pool**, **notion**, *
### Provider Categories
-**Free Providers (4):** Qoder AI, Qwen Code (deprecated), Kiro AI, Windsurf
+**Free Providers (3):** Qoder AI, Kiro AI, Windsurf
-**OAuth Providers (14):** Claude Code, Antigravity, OpenAI Codex, GitHub Copilot, Cursor IDE, Kimi Coding, Kilo Code, Cline, Qwen, Kiro, Qoder, Gemini, Windsurf, GitLab Duo
+**OAuth Providers (13):** Claude Code, Antigravity, OpenAI Codex, GitHub Copilot, Cursor IDE, Kimi Coding, Kilo Code, Cline, Kiro, Qoder, Gemini, Windsurf, GitLab Duo
**API Key Providers (48+):** OpenAI, Anthropic, Gemini (Google AI Studio), DeepSeek, Groq, xAI (Grok), Mistral, Perplexity, Together AI, Fireworks AI, Cerebras, Cohere, NVIDIA NIM, Nebius AI, SiliconFlow, Hyperbolic, HuggingFace, OpenRouter, Vertex AI, Cloudflare Workers AI, Scaleway AI, AI/ML API, Pollinations AI, Puter AI, LongCat AI, Alibaba, Alibaba (China), Kimi, Kimi Coding (API Key), Minimax, Minimax (China), Blackbox AI, Synthetic, Kilo Gateway, Z.AI, GLM Coding, Deepgram, AssemblyAI, ElevenLabs, Cartesia, PlayHT, Inworld, NanoBanana, SD WebUI, ComfyUI, Ollama Cloud, Perplexity Search, Serper Search, Brave Search, Exa Search, Tavily Search, OpenCode Zen, OpenCode Go, Alibaba Coding Plan
diff --git a/docs/i18n/ja/llm.txt b/docs/i18n/ja/llm.txt
index 0469265645..ff989e9a41 100644
--- a/docs/i18n/ja/llm.txt
+++ b/docs/i18n/ja/llm.txt
@@ -364,9 +364,9 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **pool**, **notion**, *
### Provider Categories
-**Free Providers (4):** Qoder AI, Qwen Code (deprecated), Kiro AI, Windsurf
+**Free Providers (3):** Qoder AI, Kiro AI, Windsurf
-**OAuth Providers (14):** Claude Code, Antigravity, OpenAI Codex, GitHub Copilot, Cursor IDE, Kimi Coding, Kilo Code, Cline, Qwen, Kiro, Qoder, Gemini, Windsurf, GitLab Duo
+**OAuth Providers (13):** Claude Code, Antigravity, OpenAI Codex, GitHub Copilot, Cursor IDE, Kimi Coding, Kilo Code, Cline, Kiro, Qoder, Gemini, Windsurf, GitLab Duo
**API Key Providers (48+):** OpenAI, Anthropic, Gemini (Google AI Studio), DeepSeek, Groq, xAI (Grok), Mistral, Perplexity, Together AI, Fireworks AI, Cerebras, Cohere, NVIDIA NIM, Nebius AI, SiliconFlow, Hyperbolic, HuggingFace, OpenRouter, Vertex AI, Cloudflare Workers AI, Scaleway AI, AI/ML API, Pollinations AI, Puter AI, LongCat AI, Alibaba, Alibaba (China), Kimi, Kimi Coding (API Key), Minimax, Minimax (China), Blackbox AI, Synthetic, Kilo Gateway, Z.AI, GLM Coding, Deepgram, AssemblyAI, ElevenLabs, Cartesia, PlayHT, Inworld, NanoBanana, SD WebUI, ComfyUI, Ollama Cloud, Perplexity Search, Serper Search, Brave Search, Exa Search, Tavily Search, OpenCode Zen, OpenCode Go, Alibaba Coding Plan
diff --git a/docs/i18n/ko/llm.txt b/docs/i18n/ko/llm.txt
index f33c181566..50f4b60ce8 100644
--- a/docs/i18n/ko/llm.txt
+++ b/docs/i18n/ko/llm.txt
@@ -364,9 +364,9 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **pool**, **notion**, *
### Provider Categories
-**Free Providers (4):** Qoder AI, Qwen Code (deprecated), Kiro AI, Windsurf
+**Free Providers (3):** Qoder AI, Kiro AI, Windsurf
-**OAuth Providers (14):** Claude Code, Antigravity, OpenAI Codex, GitHub Copilot, Cursor IDE, Kimi Coding, Kilo Code, Cline, Qwen, Kiro, Qoder, Gemini, Windsurf, GitLab Duo
+**OAuth Providers (13):** Claude Code, Antigravity, OpenAI Codex, GitHub Copilot, Cursor IDE, Kimi Coding, Kilo Code, Cline, Kiro, Qoder, Gemini, Windsurf, GitLab Duo
**API Key Providers (48+):** OpenAI, Anthropic, Gemini (Google AI Studio), DeepSeek, Groq, xAI (Grok), Mistral, Perplexity, Together AI, Fireworks AI, Cerebras, Cohere, NVIDIA NIM, Nebius AI, SiliconFlow, Hyperbolic, HuggingFace, OpenRouter, Vertex AI, Cloudflare Workers AI, Scaleway AI, AI/ML API, Pollinations AI, Puter AI, LongCat AI, Alibaba, Alibaba (China), Kimi, Kimi Coding (API Key), Minimax, Minimax (China), Blackbox AI, Synthetic, Kilo Gateway, Z.AI, GLM Coding, Deepgram, AssemblyAI, ElevenLabs, Cartesia, PlayHT, Inworld, NanoBanana, SD WebUI, ComfyUI, Ollama Cloud, Perplexity Search, Serper Search, Brave Search, Exa Search, Tavily Search, OpenCode Zen, OpenCode Go, Alibaba Coding Plan
diff --git a/docs/i18n/mr/llm.txt b/docs/i18n/mr/llm.txt
index f052ca9bd4..a4f7728f17 100644
--- a/docs/i18n/mr/llm.txt
+++ b/docs/i18n/mr/llm.txt
@@ -364,9 +364,9 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **pool**, **notion**, *
### Provider Categories
-**Free Providers (4):** Qoder AI, Qwen Code (deprecated), Kiro AI, Windsurf
+**Free Providers (3):** Qoder AI, Kiro AI, Windsurf
-**OAuth Providers (14):** Claude Code, Antigravity, OpenAI Codex, GitHub Copilot, Cursor IDE, Kimi Coding, Kilo Code, Cline, Qwen, Kiro, Qoder, Gemini, Windsurf, GitLab Duo
+**OAuth Providers (13):** Claude Code, Antigravity, OpenAI Codex, GitHub Copilot, Cursor IDE, Kimi Coding, Kilo Code, Cline, Kiro, Qoder, Gemini, Windsurf, GitLab Duo
**API Key Providers (48+):** OpenAI, Anthropic, Gemini (Google AI Studio), DeepSeek, Groq, xAI (Grok), Mistral, Perplexity, Together AI, Fireworks AI, Cerebras, Cohere, NVIDIA NIM, Nebius AI, SiliconFlow, Hyperbolic, HuggingFace, OpenRouter, Vertex AI, Cloudflare Workers AI, Scaleway AI, AI/ML API, Pollinations AI, Puter AI, LongCat AI, Alibaba, Alibaba (China), Kimi, Kimi Coding (API Key), Minimax, Minimax (China), Blackbox AI, Synthetic, Kilo Gateway, Z.AI, GLM Coding, Deepgram, AssemblyAI, ElevenLabs, Cartesia, PlayHT, Inworld, NanoBanana, SD WebUI, ComfyUI, Ollama Cloud, Perplexity Search, Serper Search, Brave Search, Exa Search, Tavily Search, OpenCode Zen, OpenCode Go, Alibaba Coding Plan
diff --git a/docs/i18n/ms/llm.txt b/docs/i18n/ms/llm.txt
index 8850fb0de7..71c07aec12 100644
--- a/docs/i18n/ms/llm.txt
+++ b/docs/i18n/ms/llm.txt
@@ -364,9 +364,9 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **pool**, **notion**, *
### Provider Categories
-**Free Providers (4):** Qoder AI, Qwen Code (deprecated), Kiro AI, Windsurf
+**Free Providers (3):** Qoder AI, Kiro AI, Windsurf
-**OAuth Providers (14):** Claude Code, Antigravity, OpenAI Codex, GitHub Copilot, Cursor IDE, Kimi Coding, Kilo Code, Cline, Qwen, Kiro, Qoder, Gemini, Windsurf, GitLab Duo
+**OAuth Providers (13):** Claude Code, Antigravity, OpenAI Codex, GitHub Copilot, Cursor IDE, Kimi Coding, Kilo Code, Cline, Kiro, Qoder, Gemini, Windsurf, GitLab Duo
**API Key Providers (48+):** OpenAI, Anthropic, Gemini (Google AI Studio), DeepSeek, Groq, xAI (Grok), Mistral, Perplexity, Together AI, Fireworks AI, Cerebras, Cohere, NVIDIA NIM, Nebius AI, SiliconFlow, Hyperbolic, HuggingFace, OpenRouter, Vertex AI, Cloudflare Workers AI, Scaleway AI, AI/ML API, Pollinations AI, Puter AI, LongCat AI, Alibaba, Alibaba (China), Kimi, Kimi Coding (API Key), Minimax, Minimax (China), Blackbox AI, Synthetic, Kilo Gateway, Z.AI, GLM Coding, Deepgram, AssemblyAI, ElevenLabs, Cartesia, PlayHT, Inworld, NanoBanana, SD WebUI, ComfyUI, Ollama Cloud, Perplexity Search, Serper Search, Brave Search, Exa Search, Tavily Search, OpenCode Zen, OpenCode Go, Alibaba Coding Plan
diff --git a/docs/i18n/nl/llm.txt b/docs/i18n/nl/llm.txt
index 6465050541..5f40de82a8 100644
--- a/docs/i18n/nl/llm.txt
+++ b/docs/i18n/nl/llm.txt
@@ -364,9 +364,9 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **pool**, **notion**, *
### Provider Categories
-**Free Providers (4):** Qoder AI, Qwen Code (deprecated), Kiro AI, Windsurf
+**Free Providers (3):** Qoder AI, Kiro AI, Windsurf
-**OAuth Providers (14):** Claude Code, Antigravity, OpenAI Codex, GitHub Copilot, Cursor IDE, Kimi Coding, Kilo Code, Cline, Qwen, Kiro, Qoder, Gemini, Windsurf, GitLab Duo
+**OAuth Providers (13):** Claude Code, Antigravity, OpenAI Codex, GitHub Copilot, Cursor IDE, Kimi Coding, Kilo Code, Cline, Kiro, Qoder, Gemini, Windsurf, GitLab Duo
**API Key Providers (48+):** OpenAI, Anthropic, Gemini (Google AI Studio), DeepSeek, Groq, xAI (Grok), Mistral, Perplexity, Together AI, Fireworks AI, Cerebras, Cohere, NVIDIA NIM, Nebius AI, SiliconFlow, Hyperbolic, HuggingFace, OpenRouter, Vertex AI, Cloudflare Workers AI, Scaleway AI, AI/ML API, Pollinations AI, Puter AI, LongCat AI, Alibaba, Alibaba (China), Kimi, Kimi Coding (API Key), Minimax, Minimax (China), Blackbox AI, Synthetic, Kilo Gateway, Z.AI, GLM Coding, Deepgram, AssemblyAI, ElevenLabs, Cartesia, PlayHT, Inworld, NanoBanana, SD WebUI, ComfyUI, Ollama Cloud, Perplexity Search, Serper Search, Brave Search, Exa Search, Tavily Search, OpenCode Zen, OpenCode Go, Alibaba Coding Plan
diff --git a/docs/i18n/no/llm.txt b/docs/i18n/no/llm.txt
index be667f94f0..2efe3ac90a 100644
--- a/docs/i18n/no/llm.txt
+++ b/docs/i18n/no/llm.txt
@@ -364,9 +364,9 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **pool**, **notion**, *
### Provider Categories
-**Free Providers (4):** Qoder AI, Qwen Code (deprecated), Kiro AI, Windsurf
+**Free Providers (3):** Qoder AI, Kiro AI, Windsurf
-**OAuth Providers (14):** Claude Code, Antigravity, OpenAI Codex, GitHub Copilot, Cursor IDE, Kimi Coding, Kilo Code, Cline, Qwen, Kiro, Qoder, Gemini, Windsurf, GitLab Duo
+**OAuth Providers (13):** Claude Code, Antigravity, OpenAI Codex, GitHub Copilot, Cursor IDE, Kimi Coding, Kilo Code, Cline, Kiro, Qoder, Gemini, Windsurf, GitLab Duo
**API Key Providers (48+):** OpenAI, Anthropic, Gemini (Google AI Studio), DeepSeek, Groq, xAI (Grok), Mistral, Perplexity, Together AI, Fireworks AI, Cerebras, Cohere, NVIDIA NIM, Nebius AI, SiliconFlow, Hyperbolic, HuggingFace, OpenRouter, Vertex AI, Cloudflare Workers AI, Scaleway AI, AI/ML API, Pollinations AI, Puter AI, LongCat AI, Alibaba, Alibaba (China), Kimi, Kimi Coding (API Key), Minimax, Minimax (China), Blackbox AI, Synthetic, Kilo Gateway, Z.AI, GLM Coding, Deepgram, AssemblyAI, ElevenLabs, Cartesia, PlayHT, Inworld, NanoBanana, SD WebUI, ComfyUI, Ollama Cloud, Perplexity Search, Serper Search, Brave Search, Exa Search, Tavily Search, OpenCode Zen, OpenCode Go, Alibaba Coding Plan
diff --git a/docs/i18n/phi/llm.txt b/docs/i18n/phi/llm.txt
index fae6a95de1..02f2f1cf0d 100644
--- a/docs/i18n/phi/llm.txt
+++ b/docs/i18n/phi/llm.txt
@@ -364,9 +364,9 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **pool**, **notion**, *
### Provider Categories
-**Free Providers (4):** Qoder AI, Qwen Code (deprecated), Kiro AI, Windsurf
+**Free Providers (3):** Qoder AI, Kiro AI, Windsurf
-**OAuth Providers (14):** Claude Code, Antigravity, OpenAI Codex, GitHub Copilot, Cursor IDE, Kimi Coding, Kilo Code, Cline, Qwen, Kiro, Qoder, Gemini, Windsurf, GitLab Duo
+**OAuth Providers (13):** Claude Code, Antigravity, OpenAI Codex, GitHub Copilot, Cursor IDE, Kimi Coding, Kilo Code, Cline, Kiro, Qoder, Gemini, Windsurf, GitLab Duo
**API Key Providers (48+):** OpenAI, Anthropic, Gemini (Google AI Studio), DeepSeek, Groq, xAI (Grok), Mistral, Perplexity, Together AI, Fireworks AI, Cerebras, Cohere, NVIDIA NIM, Nebius AI, SiliconFlow, Hyperbolic, HuggingFace, OpenRouter, Vertex AI, Cloudflare Workers AI, Scaleway AI, AI/ML API, Pollinations AI, Puter AI, LongCat AI, Alibaba, Alibaba (China), Kimi, Kimi Coding (API Key), Minimax, Minimax (China), Blackbox AI, Synthetic, Kilo Gateway, Z.AI, GLM Coding, Deepgram, AssemblyAI, ElevenLabs, Cartesia, PlayHT, Inworld, NanoBanana, SD WebUI, ComfyUI, Ollama Cloud, Perplexity Search, Serper Search, Brave Search, Exa Search, Tavily Search, OpenCode Zen, OpenCode Go, Alibaba Coding Plan
diff --git a/docs/i18n/pl/llm.txt b/docs/i18n/pl/llm.txt
index 50a20e3320..f395947b7a 100644
--- a/docs/i18n/pl/llm.txt
+++ b/docs/i18n/pl/llm.txt
@@ -364,9 +364,9 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **pool**, **notion**, *
### Provider Categories
-**Free Providers (4):** Qoder AI, Qwen Code (deprecated), Kiro AI, Windsurf
+**Free Providers (3):** Qoder AI, Kiro AI, Windsurf
-**OAuth Providers (14):** Claude Code, Antigravity, OpenAI Codex, GitHub Copilot, Cursor IDE, Kimi Coding, Kilo Code, Cline, Qwen, Kiro, Qoder, Gemini, Windsurf, GitLab Duo
+**OAuth Providers (13):** Claude Code, Antigravity, OpenAI Codex, GitHub Copilot, Cursor IDE, Kimi Coding, Kilo Code, Cline, Kiro, Qoder, Gemini, Windsurf, GitLab Duo
**API Key Providers (48+):** OpenAI, Anthropic, Gemini (Google AI Studio), DeepSeek, Groq, xAI (Grok), Mistral, Perplexity, Together AI, Fireworks AI, Cerebras, Cohere, NVIDIA NIM, Nebius AI, SiliconFlow, Hyperbolic, HuggingFace, OpenRouter, Vertex AI, Cloudflare Workers AI, Scaleway AI, AI/ML API, Pollinations AI, Puter AI, LongCat AI, Alibaba, Alibaba (China), Kimi, Kimi Coding (API Key), Minimax, Minimax (China), Blackbox AI, Synthetic, Kilo Gateway, Z.AI, GLM Coding, Deepgram, AssemblyAI, ElevenLabs, Cartesia, PlayHT, Inworld, NanoBanana, SD WebUI, ComfyUI, Ollama Cloud, Perplexity Search, Serper Search, Brave Search, Exa Search, Tavily Search, OpenCode Zen, OpenCode Go, Alibaba Coding Plan
diff --git a/docs/i18n/pt-BR/llm.txt b/docs/i18n/pt-BR/llm.txt
index b40da47c95..4bcf2731c5 100644
--- a/docs/i18n/pt-BR/llm.txt
+++ b/docs/i18n/pt-BR/llm.txt
@@ -364,9 +364,9 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **pool**, **notion**, *
### Provider Categories
-**Free Providers (4):** Qoder AI, Qwen Code (deprecated), Kiro AI, Windsurf
+**Free Providers (3):** Qoder AI, Kiro AI, Windsurf
-**OAuth Providers (14):** Claude Code, Antigravity, OpenAI Codex, GitHub Copilot, Cursor IDE, Kimi Coding, Kilo Code, Cline, Qwen, Kiro, Qoder, Gemini, Windsurf, GitLab Duo
+**OAuth Providers (13):** Claude Code, Antigravity, OpenAI Codex, GitHub Copilot, Cursor IDE, Kimi Coding, Kilo Code, Cline, Kiro, Qoder, Gemini, Windsurf, GitLab Duo
**API Key Providers (48+):** OpenAI, Anthropic, Gemini (Google AI Studio), DeepSeek, Groq, xAI (Grok), Mistral, Perplexity, Together AI, Fireworks AI, Cerebras, Cohere, NVIDIA NIM, Nebius AI, SiliconFlow, Hyperbolic, HuggingFace, OpenRouter, Vertex AI, Cloudflare Workers AI, Scaleway AI, AI/ML API, Pollinations AI, Puter AI, LongCat AI, Alibaba, Alibaba (China), Kimi, Kimi Coding (API Key), Minimax, Minimax (China), Blackbox AI, Synthetic, Kilo Gateway, Z.AI, GLM Coding, Deepgram, AssemblyAI, ElevenLabs, Cartesia, PlayHT, Inworld, NanoBanana, SD WebUI, ComfyUI, Ollama Cloud, Perplexity Search, Serper Search, Brave Search, Exa Search, Tavily Search, OpenCode Zen, OpenCode Go, Alibaba Coding Plan
diff --git a/docs/i18n/pt/llm.txt b/docs/i18n/pt/llm.txt
index 9568237c5d..57718023e2 100644
--- a/docs/i18n/pt/llm.txt
+++ b/docs/i18n/pt/llm.txt
@@ -364,9 +364,9 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **pool**, **notion**, *
### Provider Categories
-**Free Providers (4):** Qoder AI, Qwen Code (deprecated), Kiro AI, Windsurf
+**Free Providers (3):** Qoder AI, Kiro AI, Windsurf
-**OAuth Providers (14):** Claude Code, Antigravity, OpenAI Codex, GitHub Copilot, Cursor IDE, Kimi Coding, Kilo Code, Cline, Qwen, Kiro, Qoder, Gemini, Windsurf, GitLab Duo
+**OAuth Providers (13):** Claude Code, Antigravity, OpenAI Codex, GitHub Copilot, Cursor IDE, Kimi Coding, Kilo Code, Cline, Kiro, Qoder, Gemini, Windsurf, GitLab Duo
**API Key Providers (48+):** OpenAI, Anthropic, Gemini (Google AI Studio), DeepSeek, Groq, xAI (Grok), Mistral, Perplexity, Together AI, Fireworks AI, Cerebras, Cohere, NVIDIA NIM, Nebius AI, SiliconFlow, Hyperbolic, HuggingFace, OpenRouter, Vertex AI, Cloudflare Workers AI, Scaleway AI, AI/ML API, Pollinations AI, Puter AI, LongCat AI, Alibaba, Alibaba (China), Kimi, Kimi Coding (API Key), Minimax, Minimax (China), Blackbox AI, Synthetic, Kilo Gateway, Z.AI, GLM Coding, Deepgram, AssemblyAI, ElevenLabs, Cartesia, PlayHT, Inworld, NanoBanana, SD WebUI, ComfyUI, Ollama Cloud, Perplexity Search, Serper Search, Brave Search, Exa Search, Tavily Search, OpenCode Zen, OpenCode Go, Alibaba Coding Plan
diff --git a/docs/i18n/ro/llm.txt b/docs/i18n/ro/llm.txt
index f92f0b2444..862eb15736 100644
--- a/docs/i18n/ro/llm.txt
+++ b/docs/i18n/ro/llm.txt
@@ -364,9 +364,9 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **pool**, **notion**, *
### Provider Categories
-**Free Providers (4):** Qoder AI, Qwen Code (deprecated), Kiro AI, Windsurf
+**Free Providers (3):** Qoder AI, Kiro AI, Windsurf
-**OAuth Providers (14):** Claude Code, Antigravity, OpenAI Codex, GitHub Copilot, Cursor IDE, Kimi Coding, Kilo Code, Cline, Qwen, Kiro, Qoder, Gemini, Windsurf, GitLab Duo
+**OAuth Providers (13):** Claude Code, Antigravity, OpenAI Codex, GitHub Copilot, Cursor IDE, Kimi Coding, Kilo Code, Cline, Kiro, Qoder, Gemini, Windsurf, GitLab Duo
**API Key Providers (48+):** OpenAI, Anthropic, Gemini (Google AI Studio), DeepSeek, Groq, xAI (Grok), Mistral, Perplexity, Together AI, Fireworks AI, Cerebras, Cohere, NVIDIA NIM, Nebius AI, SiliconFlow, Hyperbolic, HuggingFace, OpenRouter, Vertex AI, Cloudflare Workers AI, Scaleway AI, AI/ML API, Pollinations AI, Puter AI, LongCat AI, Alibaba, Alibaba (China), Kimi, Kimi Coding (API Key), Minimax, Minimax (China), Blackbox AI, Synthetic, Kilo Gateway, Z.AI, GLM Coding, Deepgram, AssemblyAI, ElevenLabs, Cartesia, PlayHT, Inworld, NanoBanana, SD WebUI, ComfyUI, Ollama Cloud, Perplexity Search, Serper Search, Brave Search, Exa Search, Tavily Search, OpenCode Zen, OpenCode Go, Alibaba Coding Plan
diff --git a/docs/i18n/ru/llm.txt b/docs/i18n/ru/llm.txt
index ccd8bcecc4..b71aa5071c 100644
--- a/docs/i18n/ru/llm.txt
+++ b/docs/i18n/ru/llm.txt
@@ -364,9 +364,9 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **pool**, **notion**, *
### Provider Categories
-**Free Providers (4):** Qoder AI, Qwen Code (deprecated), Kiro AI, Windsurf
+**Free Providers (3):** Qoder AI, Kiro AI, Windsurf
-**OAuth Providers (14):** Claude Code, Antigravity, OpenAI Codex, GitHub Copilot, Cursor IDE, Kimi Coding, Kilo Code, Cline, Qwen, Kiro, Qoder, Gemini, Windsurf, GitLab Duo
+**OAuth Providers (13):** Claude Code, Antigravity, OpenAI Codex, GitHub Copilot, Cursor IDE, Kimi Coding, Kilo Code, Cline, Kiro, Qoder, Gemini, Windsurf, GitLab Duo
**API Key Providers (48+):** OpenAI, Anthropic, Gemini (Google AI Studio), DeepSeek, Groq, xAI (Grok), Mistral, Perplexity, Together AI, Fireworks AI, Cerebras, Cohere, NVIDIA NIM, Nebius AI, SiliconFlow, Hyperbolic, HuggingFace, OpenRouter, Vertex AI, Cloudflare Workers AI, Scaleway AI, AI/ML API, Pollinations AI, Puter AI, LongCat AI, Alibaba, Alibaba (China), Kimi, Kimi Coding (API Key), Minimax, Minimax (China), Blackbox AI, Synthetic, Kilo Gateway, Z.AI, GLM Coding, Deepgram, AssemblyAI, ElevenLabs, Cartesia, PlayHT, Inworld, NanoBanana, SD WebUI, ComfyUI, Ollama Cloud, Perplexity Search, Serper Search, Brave Search, Exa Search, Tavily Search, OpenCode Zen, OpenCode Go, Alibaba Coding Plan
diff --git a/docs/i18n/sk/llm.txt b/docs/i18n/sk/llm.txt
index e19bf8803f..8919c9d5a4 100644
--- a/docs/i18n/sk/llm.txt
+++ b/docs/i18n/sk/llm.txt
@@ -364,9 +364,9 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **pool**, **notion**, *
### Provider Categories
-**Free Providers (4):** Qoder AI, Qwen Code (deprecated), Kiro AI, Windsurf
+**Free Providers (3):** Qoder AI, Kiro AI, Windsurf
-**OAuth Providers (14):** Claude Code, Antigravity, OpenAI Codex, GitHub Copilot, Cursor IDE, Kimi Coding, Kilo Code, Cline, Qwen, Kiro, Qoder, Gemini, Windsurf, GitLab Duo
+**OAuth Providers (13):** Claude Code, Antigravity, OpenAI Codex, GitHub Copilot, Cursor IDE, Kimi Coding, Kilo Code, Cline, Kiro, Qoder, Gemini, Windsurf, GitLab Duo
**API Key Providers (48+):** OpenAI, Anthropic, Gemini (Google AI Studio), DeepSeek, Groq, xAI (Grok), Mistral, Perplexity, Together AI, Fireworks AI, Cerebras, Cohere, NVIDIA NIM, Nebius AI, SiliconFlow, Hyperbolic, HuggingFace, OpenRouter, Vertex AI, Cloudflare Workers AI, Scaleway AI, AI/ML API, Pollinations AI, Puter AI, LongCat AI, Alibaba, Alibaba (China), Kimi, Kimi Coding (API Key), Minimax, Minimax (China), Blackbox AI, Synthetic, Kilo Gateway, Z.AI, GLM Coding, Deepgram, AssemblyAI, ElevenLabs, Cartesia, PlayHT, Inworld, NanoBanana, SD WebUI, ComfyUI, Ollama Cloud, Perplexity Search, Serper Search, Brave Search, Exa Search, Tavily Search, OpenCode Zen, OpenCode Go, Alibaba Coding Plan
diff --git a/docs/i18n/sv/llm.txt b/docs/i18n/sv/llm.txt
index 2d7cc63ed2..27384831ba 100644
--- a/docs/i18n/sv/llm.txt
+++ b/docs/i18n/sv/llm.txt
@@ -364,9 +364,9 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **pool**, **notion**, *
### Provider Categories
-**Free Providers (4):** Qoder AI, Qwen Code (deprecated), Kiro AI, Windsurf
+**Free Providers (3):** Qoder AI, Kiro AI, Windsurf
-**OAuth Providers (14):** Claude Code, Antigravity, OpenAI Codex, GitHub Copilot, Cursor IDE, Kimi Coding, Kilo Code, Cline, Qwen, Kiro, Qoder, Gemini, Windsurf, GitLab Duo
+**OAuth Providers (13):** Claude Code, Antigravity, OpenAI Codex, GitHub Copilot, Cursor IDE, Kimi Coding, Kilo Code, Cline, Kiro, Qoder, Gemini, Windsurf, GitLab Duo
**API Key Providers (48+):** OpenAI, Anthropic, Gemini (Google AI Studio), DeepSeek, Groq, xAI (Grok), Mistral, Perplexity, Together AI, Fireworks AI, Cerebras, Cohere, NVIDIA NIM, Nebius AI, SiliconFlow, Hyperbolic, HuggingFace, OpenRouter, Vertex AI, Cloudflare Workers AI, Scaleway AI, AI/ML API, Pollinations AI, Puter AI, LongCat AI, Alibaba, Alibaba (China), Kimi, Kimi Coding (API Key), Minimax, Minimax (China), Blackbox AI, Synthetic, Kilo Gateway, Z.AI, GLM Coding, Deepgram, AssemblyAI, ElevenLabs, Cartesia, PlayHT, Inworld, NanoBanana, SD WebUI, ComfyUI, Ollama Cloud, Perplexity Search, Serper Search, Brave Search, Exa Search, Tavily Search, OpenCode Zen, OpenCode Go, Alibaba Coding Plan
diff --git a/docs/i18n/sw/llm.txt b/docs/i18n/sw/llm.txt
index fc08f7b142..64e7fb624c 100644
--- a/docs/i18n/sw/llm.txt
+++ b/docs/i18n/sw/llm.txt
@@ -364,9 +364,9 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **pool**, **notion**, *
### Provider Categories
-**Free Providers (4):** Qoder AI, Qwen Code (deprecated), Kiro AI, Windsurf
+**Free Providers (3):** Qoder AI, Kiro AI, Windsurf
-**OAuth Providers (14):** Claude Code, Antigravity, OpenAI Codex, GitHub Copilot, Cursor IDE, Kimi Coding, Kilo Code, Cline, Qwen, Kiro, Qoder, Gemini, Windsurf, GitLab Duo
+**OAuth Providers (13):** Claude Code, Antigravity, OpenAI Codex, GitHub Copilot, Cursor IDE, Kimi Coding, Kilo Code, Cline, Kiro, Qoder, Gemini, Windsurf, GitLab Duo
**API Key Providers (48+):** OpenAI, Anthropic, Gemini (Google AI Studio), DeepSeek, Groq, xAI (Grok), Mistral, Perplexity, Together AI, Fireworks AI, Cerebras, Cohere, NVIDIA NIM, Nebius AI, SiliconFlow, Hyperbolic, HuggingFace, OpenRouter, Vertex AI, Cloudflare Workers AI, Scaleway AI, AI/ML API, Pollinations AI, Puter AI, LongCat AI, Alibaba, Alibaba (China), Kimi, Kimi Coding (API Key), Minimax, Minimax (China), Blackbox AI, Synthetic, Kilo Gateway, Z.AI, GLM Coding, Deepgram, AssemblyAI, ElevenLabs, Cartesia, PlayHT, Inworld, NanoBanana, SD WebUI, ComfyUI, Ollama Cloud, Perplexity Search, Serper Search, Brave Search, Exa Search, Tavily Search, OpenCode Zen, OpenCode Go, Alibaba Coding Plan
diff --git a/docs/i18n/ta/llm.txt b/docs/i18n/ta/llm.txt
index 1015b8850a..3e6a3693b8 100644
--- a/docs/i18n/ta/llm.txt
+++ b/docs/i18n/ta/llm.txt
@@ -364,9 +364,9 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **pool**, **notion**, *
### Provider Categories
-**Free Providers (4):** Qoder AI, Qwen Code (deprecated), Kiro AI, Windsurf
+**Free Providers (3):** Qoder AI, Kiro AI, Windsurf
-**OAuth Providers (14):** Claude Code, Antigravity, OpenAI Codex, GitHub Copilot, Cursor IDE, Kimi Coding, Kilo Code, Cline, Qwen, Kiro, Qoder, Gemini, Windsurf, GitLab Duo
+**OAuth Providers (13):** Claude Code, Antigravity, OpenAI Codex, GitHub Copilot, Cursor IDE, Kimi Coding, Kilo Code, Cline, Kiro, Qoder, Gemini, Windsurf, GitLab Duo
**API Key Providers (48+):** OpenAI, Anthropic, Gemini (Google AI Studio), DeepSeek, Groq, xAI (Grok), Mistral, Perplexity, Together AI, Fireworks AI, Cerebras, Cohere, NVIDIA NIM, Nebius AI, SiliconFlow, Hyperbolic, HuggingFace, OpenRouter, Vertex AI, Cloudflare Workers AI, Scaleway AI, AI/ML API, Pollinations AI, Puter AI, LongCat AI, Alibaba, Alibaba (China), Kimi, Kimi Coding (API Key), Minimax, Minimax (China), Blackbox AI, Synthetic, Kilo Gateway, Z.AI, GLM Coding, Deepgram, AssemblyAI, ElevenLabs, Cartesia, PlayHT, Inworld, NanoBanana, SD WebUI, ComfyUI, Ollama Cloud, Perplexity Search, Serper Search, Brave Search, Exa Search, Tavily Search, OpenCode Zen, OpenCode Go, Alibaba Coding Plan
diff --git a/docs/i18n/te/llm.txt b/docs/i18n/te/llm.txt
index 375192f15a..d6dd146c9d 100644
--- a/docs/i18n/te/llm.txt
+++ b/docs/i18n/te/llm.txt
@@ -364,9 +364,9 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **pool**, **notion**, *
### Provider Categories
-**Free Providers (4):** Qoder AI, Qwen Code (deprecated), Kiro AI, Windsurf
+**Free Providers (3):** Qoder AI, Kiro AI, Windsurf
-**OAuth Providers (14):** Claude Code, Antigravity, OpenAI Codex, GitHub Copilot, Cursor IDE, Kimi Coding, Kilo Code, Cline, Qwen, Kiro, Qoder, Gemini, Windsurf, GitLab Duo
+**OAuth Providers (13):** Claude Code, Antigravity, OpenAI Codex, GitHub Copilot, Cursor IDE, Kimi Coding, Kilo Code, Cline, Kiro, Qoder, Gemini, Windsurf, GitLab Duo
**API Key Providers (48+):** OpenAI, Anthropic, Gemini (Google AI Studio), DeepSeek, Groq, xAI (Grok), Mistral, Perplexity, Together AI, Fireworks AI, Cerebras, Cohere, NVIDIA NIM, Nebius AI, SiliconFlow, Hyperbolic, HuggingFace, OpenRouter, Vertex AI, Cloudflare Workers AI, Scaleway AI, AI/ML API, Pollinations AI, Puter AI, LongCat AI, Alibaba, Alibaba (China), Kimi, Kimi Coding (API Key), Minimax, Minimax (China), Blackbox AI, Synthetic, Kilo Gateway, Z.AI, GLM Coding, Deepgram, AssemblyAI, ElevenLabs, Cartesia, PlayHT, Inworld, NanoBanana, SD WebUI, ComfyUI, Ollama Cloud, Perplexity Search, Serper Search, Brave Search, Exa Search, Tavily Search, OpenCode Zen, OpenCode Go, Alibaba Coding Plan
diff --git a/docs/i18n/th/llm.txt b/docs/i18n/th/llm.txt
index 64e8b57d81..67678e49e2 100644
--- a/docs/i18n/th/llm.txt
+++ b/docs/i18n/th/llm.txt
@@ -364,9 +364,9 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **pool**, **notion**, *
### Provider Categories
-**Free Providers (4):** Qoder AI, Qwen Code (deprecated), Kiro AI, Windsurf
+**Free Providers (3):** Qoder AI, Kiro AI, Windsurf
-**OAuth Providers (14):** Claude Code, Antigravity, OpenAI Codex, GitHub Copilot, Cursor IDE, Kimi Coding, Kilo Code, Cline, Qwen, Kiro, Qoder, Gemini, Windsurf, GitLab Duo
+**OAuth Providers (13):** Claude Code, Antigravity, OpenAI Codex, GitHub Copilot, Cursor IDE, Kimi Coding, Kilo Code, Cline, Kiro, Qoder, Gemini, Windsurf, GitLab Duo
**API Key Providers (48+):** OpenAI, Anthropic, Gemini (Google AI Studio), DeepSeek, Groq, xAI (Grok), Mistral, Perplexity, Together AI, Fireworks AI, Cerebras, Cohere, NVIDIA NIM, Nebius AI, SiliconFlow, Hyperbolic, HuggingFace, OpenRouter, Vertex AI, Cloudflare Workers AI, Scaleway AI, AI/ML API, Pollinations AI, Puter AI, LongCat AI, Alibaba, Alibaba (China), Kimi, Kimi Coding (API Key), Minimax, Minimax (China), Blackbox AI, Synthetic, Kilo Gateway, Z.AI, GLM Coding, Deepgram, AssemblyAI, ElevenLabs, Cartesia, PlayHT, Inworld, NanoBanana, SD WebUI, ComfyUI, Ollama Cloud, Perplexity Search, Serper Search, Brave Search, Exa Search, Tavily Search, OpenCode Zen, OpenCode Go, Alibaba Coding Plan
diff --git a/docs/i18n/tr/llm.txt b/docs/i18n/tr/llm.txt
index 74ef1375db..2afb833500 100644
--- a/docs/i18n/tr/llm.txt
+++ b/docs/i18n/tr/llm.txt
@@ -364,9 +364,9 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **pool**, **notion**, *
### Provider Categories
-**Free Providers (4):** Qoder AI, Qwen Code (deprecated), Kiro AI, Windsurf
+**Free Providers (3):** Qoder AI, Kiro AI, Windsurf
-**OAuth Providers (14):** Claude Code, Antigravity, OpenAI Codex, GitHub Copilot, Cursor IDE, Kimi Coding, Kilo Code, Cline, Qwen, Kiro, Qoder, Gemini, Windsurf, GitLab Duo
+**OAuth Providers (13):** Claude Code, Antigravity, OpenAI Codex, GitHub Copilot, Cursor IDE, Kimi Coding, Kilo Code, Cline, Kiro, Qoder, Gemini, Windsurf, GitLab Duo
**API Key Providers (48+):** OpenAI, Anthropic, Gemini (Google AI Studio), DeepSeek, Groq, xAI (Grok), Mistral, Perplexity, Together AI, Fireworks AI, Cerebras, Cohere, NVIDIA NIM, Nebius AI, SiliconFlow, Hyperbolic, HuggingFace, OpenRouter, Vertex AI, Cloudflare Workers AI, Scaleway AI, AI/ML API, Pollinations AI, Puter AI, LongCat AI, Alibaba, Alibaba (China), Kimi, Kimi Coding (API Key), Minimax, Minimax (China), Blackbox AI, Synthetic, Kilo Gateway, Z.AI, GLM Coding, Deepgram, AssemblyAI, ElevenLabs, Cartesia, PlayHT, Inworld, NanoBanana, SD WebUI, ComfyUI, Ollama Cloud, Perplexity Search, Serper Search, Brave Search, Exa Search, Tavily Search, OpenCode Zen, OpenCode Go, Alibaba Coding Plan
diff --git a/docs/i18n/uk-UA/llm.txt b/docs/i18n/uk-UA/llm.txt
index 54e0cafbc0..004e5fdbc0 100644
--- a/docs/i18n/uk-UA/llm.txt
+++ b/docs/i18n/uk-UA/llm.txt
@@ -364,9 +364,9 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **pool**, **notion**, *
### Provider Categories
-**Free Providers (4):** Qoder AI, Qwen Code (deprecated), Kiro AI, Windsurf
+**Free Providers (3):** Qoder AI, Kiro AI, Windsurf
-**OAuth Providers (14):** Claude Code, Antigravity, OpenAI Codex, GitHub Copilot, Cursor IDE, Kimi Coding, Kilo Code, Cline, Qwen, Kiro, Qoder, Gemini, Windsurf, GitLab Duo
+**OAuth Providers (13):** Claude Code, Antigravity, OpenAI Codex, GitHub Copilot, Cursor IDE, Kimi Coding, Kilo Code, Cline, Kiro, Qoder, Gemini, Windsurf, GitLab Duo
**API Key Providers (48+):** OpenAI, Anthropic, Gemini (Google AI Studio), DeepSeek, Groq, xAI (Grok), Mistral, Perplexity, Together AI, Fireworks AI, Cerebras, Cohere, NVIDIA NIM, Nebius AI, SiliconFlow, Hyperbolic, HuggingFace, OpenRouter, Vertex AI, Cloudflare Workers AI, Scaleway AI, AI/ML API, Pollinations AI, Puter AI, LongCat AI, Alibaba, Alibaba (China), Kimi, Kimi Coding (API Key), Minimax, Minimax (China), Blackbox AI, Synthetic, Kilo Gateway, Z.AI, GLM Coding, Deepgram, AssemblyAI, ElevenLabs, Cartesia, PlayHT, Inworld, NanoBanana, SD WebUI, ComfyUI, Ollama Cloud, Perplexity Search, Serper Search, Brave Search, Exa Search, Tavily Search, OpenCode Zen, OpenCode Go, Alibaba Coding Plan
diff --git a/docs/i18n/ur/llm.txt b/docs/i18n/ur/llm.txt
index 105c5cd699..a940a92b07 100644
--- a/docs/i18n/ur/llm.txt
+++ b/docs/i18n/ur/llm.txt
@@ -364,9 +364,9 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **pool**, **notion**, *
### Provider Categories
-**Free Providers (4):** Qoder AI, Qwen Code (deprecated), Kiro AI, Windsurf
+**Free Providers (3):** Qoder AI, Kiro AI, Windsurf
-**OAuth Providers (14):** Claude Code, Antigravity, OpenAI Codex, GitHub Copilot, Cursor IDE, Kimi Coding, Kilo Code, Cline, Qwen, Kiro, Qoder, Gemini, Windsurf, GitLab Duo
+**OAuth Providers (13):** Claude Code, Antigravity, OpenAI Codex, GitHub Copilot, Cursor IDE, Kimi Coding, Kilo Code, Cline, Kiro, Qoder, Gemini, Windsurf, GitLab Duo
**API Key Providers (48+):** OpenAI, Anthropic, Gemini (Google AI Studio), DeepSeek, Groq, xAI (Grok), Mistral, Perplexity, Together AI, Fireworks AI, Cerebras, Cohere, NVIDIA NIM, Nebius AI, SiliconFlow, Hyperbolic, HuggingFace, OpenRouter, Vertex AI, Cloudflare Workers AI, Scaleway AI, AI/ML API, Pollinations AI, Puter AI, LongCat AI, Alibaba, Alibaba (China), Kimi, Kimi Coding (API Key), Minimax, Minimax (China), Blackbox AI, Synthetic, Kilo Gateway, Z.AI, GLM Coding, Deepgram, AssemblyAI, ElevenLabs, Cartesia, PlayHT, Inworld, NanoBanana, SD WebUI, ComfyUI, Ollama Cloud, Perplexity Search, Serper Search, Brave Search, Exa Search, Tavily Search, OpenCode Zen, OpenCode Go, Alibaba Coding Plan
diff --git a/docs/i18n/vi/llm.txt b/docs/i18n/vi/llm.txt
index 24c03f7a2d..253c184061 100644
--- a/docs/i18n/vi/llm.txt
+++ b/docs/i18n/vi/llm.txt
@@ -364,9 +364,9 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **pool**, **notion**, *
### Provider Categories
-**Free Providers (4):** Qoder AI, Qwen Code (deprecated), Kiro AI, Windsurf
+**Free Providers (3):** Qoder AI, Kiro AI, Windsurf
-**OAuth Providers (14):** Claude Code, Antigravity, OpenAI Codex, GitHub Copilot, Cursor IDE, Kimi Coding, Kilo Code, Cline, Qwen, Kiro, Qoder, Gemini, Windsurf, GitLab Duo
+**OAuth Providers (13):** Claude Code, Antigravity, OpenAI Codex, GitHub Copilot, Cursor IDE, Kimi Coding, Kilo Code, Cline, Kiro, Qoder, Gemini, Windsurf, GitLab Duo
**API Key Providers (48+):** OpenAI, Anthropic, Gemini (Google AI Studio), DeepSeek, Groq, xAI (Grok), Mistral, Perplexity, Together AI, Fireworks AI, Cerebras, Cohere, NVIDIA NIM, Nebius AI, SiliconFlow, Hyperbolic, HuggingFace, OpenRouter, Vertex AI, Cloudflare Workers AI, Scaleway AI, AI/ML API, Pollinations AI, Puter AI, LongCat AI, Alibaba, Alibaba (China), Kimi, Kimi Coding (API Key), Minimax, Minimax (China), Blackbox AI, Synthetic, Kilo Gateway, Z.AI, GLM Coding, Deepgram, AssemblyAI, ElevenLabs, Cartesia, PlayHT, Inworld, NanoBanana, SD WebUI, ComfyUI, Ollama Cloud, Perplexity Search, Serper Search, Brave Search, Exa Search, Tavily Search, OpenCode Zen, OpenCode Go, Alibaba Coding Plan
diff --git a/docs/i18n/zh-CN/llm.txt b/docs/i18n/zh-CN/llm.txt
index 4a21c4fe0d..143a9fd6ec 100644
--- a/docs/i18n/zh-CN/llm.txt
+++ b/docs/i18n/zh-CN/llm.txt
@@ -364,9 +364,9 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **pool**, **notion**, *
### Provider Categories
-**Free Providers (4):** Qoder AI, Qwen Code (deprecated), Kiro AI, Windsurf
+**Free Providers (3):** Qoder AI, Kiro AI, Windsurf
-**OAuth Providers (14):** Claude Code, Antigravity, OpenAI Codex, GitHub Copilot, Cursor IDE, Kimi Coding, Kilo Code, Cline, Qwen, Kiro, Qoder, Gemini, Windsurf, GitLab Duo
+**OAuth Providers (13):** Claude Code, Antigravity, OpenAI Codex, GitHub Copilot, Cursor IDE, Kimi Coding, Kilo Code, Cline, Kiro, Qoder, Gemini, Windsurf, GitLab Duo
**API Key Providers (48+):** OpenAI, Anthropic, Gemini (Google AI Studio), DeepSeek, Groq, xAI (Grok), Mistral, Perplexity, Together AI, Fireworks AI, Cerebras, Cohere, NVIDIA NIM, Nebius AI, SiliconFlow, Hyperbolic, HuggingFace, OpenRouter, Vertex AI, Cloudflare Workers AI, Scaleway AI, AI/ML API, Pollinations AI, Puter AI, LongCat AI, Alibaba, Alibaba (China), Kimi, Kimi Coding (API Key), Minimax, Minimax (China), Blackbox AI, Synthetic, Kilo Gateway, Z.AI, GLM Coding, Deepgram, AssemblyAI, ElevenLabs, Cartesia, PlayHT, Inworld, NanoBanana, SD WebUI, ComfyUI, Ollama Cloud, Perplexity Search, Serper Search, Brave Search, Exa Search, Tavily Search, OpenCode Zen, OpenCode Go, Alibaba Coding Plan
diff --git a/docs/i18n/zh-TW/llm.txt b/docs/i18n/zh-TW/llm.txt
index 0c39f612a5..449bd4a5d8 100644
--- a/docs/i18n/zh-TW/llm.txt
+++ b/docs/i18n/zh-TW/llm.txt
@@ -364,9 +364,9 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **pool**, **notion**, *
### Provider Categories
-**Free Providers (4):** Qoder AI, Qwen Code (deprecated), Kiro AI, Windsurf
+**Free Providers (3):** Qoder AI, Kiro AI, Windsurf
-**OAuth Providers (14):** Claude Code, Antigravity, OpenAI Codex, GitHub Copilot, Cursor IDE, Kimi Coding, Kilo Code, Cline, Qwen, Kiro, Qoder, Gemini, Windsurf, GitLab Duo
+**OAuth Providers (13):** Claude Code, Antigravity, OpenAI Codex, GitHub Copilot, Cursor IDE, Kimi Coding, Kilo Code, Cline, Kiro, Qoder, Gemini, Windsurf, GitLab Duo
**API Key Providers (48+):** OpenAI, Anthropic, Gemini (Google AI Studio), DeepSeek, Groq, xAI (Grok), Mistral, Perplexity, Together AI, Fireworks AI, Cerebras, Cohere, NVIDIA NIM, Nebius AI, SiliconFlow, Hyperbolic, HuggingFace, OpenRouter, Vertex AI, Cloudflare Workers AI, Scaleway AI, AI/ML API, Pollinations AI, Puter AI, LongCat AI, Alibaba, Alibaba (China), Kimi, Kimi Coding (API Key), Minimax, Minimax (China), Blackbox AI, Synthetic, Kilo Gateway, Z.AI, GLM Coding, Deepgram, AssemblyAI, ElevenLabs, Cartesia, PlayHT, Inworld, NanoBanana, SD WebUI, ComfyUI, Ollama Cloud, Perplexity Search, Serper Search, Brave Search, Exa Search, Tavily Search, OpenCode Zen, OpenCode Go, Alibaba Coding Plan
diff --git a/docs/reference/CLI-TOOLS.md b/docs/reference/CLI-TOOLS.md
index dad82a929e..81cbd0c4d7 100644
--- a/docs/reference/CLI-TOOLS.md
+++ b/docs/reference/CLI-TOOLS.md
@@ -12,7 +12,7 @@ OmniRoute integrates with three categories of CLI tools spread across three dedi
| Page | Route | Concept | Count |
| -------------- | ----------------------- | ------------------------------------------------------------------------- | ------------ |
-| **CLI Code's** | `/dashboard/cli-code` | Coding tools you point at OmniRoute (Client → CLI → OmniRoute → Provider) | 20 |
+| **CLI Code's** | `/dashboard/cli-code` | Coding tools you point at OmniRoute (Client → CLI → OmniRoute → Provider) | 21 |
| **CLI Agents** | `/dashboard/cli-agents` | Autonomous agents you point at OmniRoute (same flow, broader scope) | 6 |
| **ACP Agents** | `/dashboard/acp-agents` | CLIs that OmniRoute spawns as backend via stdio/ACP (reverse flow) | see registry |
@@ -60,7 +60,7 @@ omniroute setup-goose omniroute setup-qwen omniroute setup-aider
Each accepts `--remote --api-key ` (configure a local tool against a
remote OmniRoute), `--dry-run` (preview without writing), and `--port`. Tools
-without model auto-discovery (Cline, Kilo, Roo, Goose, Qwen, Aider, Gemini) take
+without model auto-discovery (Cline, Kilo, Roo, Goose, Aider, Gemini) take
`--model ` (and `--yes` for non-interactive runs). The launchers
`omniroute launch` (Claude Code) and `omniroute launch-codex` (Codex) spawn the CLI
with the right env injected and write no config at all.
@@ -102,7 +102,6 @@ All tools that appear in `/dashboard/cli-code`. Those with `baseUrlSupport: none
| kilo | Kilo Code | Kilo-Org | full | custom | false |
| roo | Roo Code | Roo (OSS) | full | guide | false |
| continue | Continue | continue.dev | full | guide | false |
-| qwen | Qwen Code | Alibaba | full | guide | true |
| aider | Aider | OSS (P. Gauthier) | full | guide | true |
| forge | ForgeCode | Antinomy HQ | full | custom | true |
| jcode | jcode | 1jehuang (OSS) | full | custom | false |
@@ -116,6 +115,7 @@ All tools that appear in `/dashboard/cli-code`. Those with `baseUrlSupport: none
| pi | Pi (pi-coding-agent) | M. Zechner (OSS) | full | custom | false |
| grok-build | Grok Build | xAI | full | custom | false |
| crush | Crush | OSS (Charm) | full | custom | false |
+| qwen | Qwen Code | Alibaba | full | guide | true |
| cursor | Cursor | Anysphere | none | guide | false |
| antigravity | Antigravity | Google | none | mitm | false |
| hermes | Hermes | Nous Research | none | guide | false |
@@ -123,7 +123,6 @@ All tools that appear in `/dashboard/cli-code`. Those with `baseUrlSupport: none
| custom | Custom CLI | — | full | custom-builder | false |
Tools with `baseUrlSupport: "partial"` show a badge "⚠ Base URL parcial" in the dashboard card.
-
---
## 2. CLI Agents Catalog (8 tools)
@@ -147,7 +146,6 @@ Autonomous agents that appear in `/dashboard/cli-agents`:
This page (renamed from `/dashboard/agents`) shows CLIs that OmniRoute can **spawn** as backend execution engines via stdio/ACP protocol. The catalog is maintained separately in `src/lib/acp/registry.ts` and is **not** the same as `CLI_TOOLS`.
-
---
## 4. MITM Backlog (not shown in dashboard)
@@ -212,6 +210,7 @@ New tools with `configType: "custom"` have dedicated settings API routes:
| `POST /api/cli-tools/smelt-settings` | Smelt |
| `POST /api/cli-tools/pi-settings` | Pi coding agent |
| `POST /api/cli-tools/grok-build-settings` | Grok Build (~/.grok/config.toml, `[model.omniroute]`) |
+| `POST /api/cli-tools/qwen-settings` | Qwen Code (`~/.qwen/settings.json` + dedicated `.env` key) |
All routes use `sanitizeErrorMessage()` for error responses (Hard Rule #12).
@@ -302,7 +301,7 @@ npm install -g cline
# KiloCode
npm install -g kilocode
-# Qwen Code (Alibaba)
+# Qwen Code
npm install -g @qwen-code/qwen-code
# Aider
@@ -532,55 +531,6 @@ under `/dashboard/cli-tools → Kiro`.
---
-#### Qwen Code (Alibaba)
-
-Qwen Code supports OpenAI-compatible API endpoints via environment variables or `settings.json`.
-
-> Qwen OAuth free tier was discontinued on 2026-04-15. Use OmniRoute with
-> `bailian-coding-plan` / `alibaba` / `alibaba-cn` / `openrouter` / `anthropic` /
-> `gemini` providers instead.
-
-**Option 1: Environment variables (`~/.qwen/.env`)**
-
-```bash
-mkdir -p ~/.qwen && cat > ~/.qwen/.env << EOF
-OPENAI_API_KEY="sk-your-omniroute-key"
-OPENAI_BASE_URL="http://localhost:20128/v1"
-OPENAI_MODEL="auto"
-EOF
-```
-
-**Option 2: `settings.json` with `security.auth`**
-
-```json
-// ~/.qwen/settings.json
-{
- "security": {
- "auth": {
- "selectedType": "openai",
- "apiKey": "sk-your-omniroute-key",
- "baseUrl": "http://localhost:20128/v1"
- }
- },
- "model": {
- "name": "claude-sonnet-4-6"
- }
-}
-```
-
-**Option 3: Inline CLI flags**
-
-```bash
-OPENAI_BASE_URL="http://localhost:20128/v1" \
-OPENAI_API_KEY="sk-your-omniroute-key" \
-OPENAI_MODEL="auto" \
-qwen
-```
-
-> For a **remote server** replace `localhost:20128` with the server IP or domain.
-
----
-
## 10. Internal OmniRoute CLI
The `omniroute` binary provides commands for server lifecycle, setup, diagnostics, and provider management. Entry point: `bin/omniroute.mjs`.
diff --git a/docs/reference/ENVIRONMENT.md b/docs/reference/ENVIRONMENT.md
index a4b1cae70a..4f30b75187 100644
--- a/docs/reference/ENVIRONMENT.md
+++ b/docs/reference/ENVIRONMENT.md
@@ -471,7 +471,6 @@ Built-in credentials for **localhost development**. For remote deployments, regi
| `CODEX_OAUTH_CLIENT_ID` | Codex / OpenAI | Public client. |
| `GEMINI_OAUTH_CLIENT_ID` | Gemini (Google) | Requires matching `_SECRET`. |
| `GEMINI_OAUTH_CLIENT_SECRET` | Gemini (Google) | — |
-| `QWEN_OAUTH_CLIENT_ID` | Qwen (Alibaba) | Public client. |
| `KIMI_CODING_OAUTH_CLIENT_ID` | Kimi Coding (Moonshot) | Public client. |
| `ANTIGRAVITY_OAUTH_CLIENT_ID` | Antigravity (Google) | Requires matching `_SECRET`. |
| `ANTIGRAVITY_OAUTH_CLIENT_SECRET` | Antigravity (Google) | — |
@@ -530,7 +529,6 @@ process.env[`${PROVIDER_ID}_USER_AGENT`]
| `KIRO_OAUTH_CLIENT_ID` | `kiro-cli` | Override the Kiro social device-code `clientId` (public id) |
| `KIRO_VERIFY_FULL_CRC` | `false` | Opt-in: full per-frame message CRC validation on the Kiro event stream (debug corrupted streams) |
| `QODER_USER_AGENT` | `Qoder-Cli` | When Qoder CLI updates |
-| `QWEN_USER_AGENT` | `QwenCode/0.19.3 (linux; x64)` | When Qwen Code updates |
| `CURSOR_USER_AGENT` | `Cursor/3.3` | When Cursor updates |
> [!TIP]
@@ -556,7 +554,6 @@ When enabled, OmniRoute reorders HTTP headers and JSON body fields to match the
| `CLI_COMPAT_KIMI_CODING` | `=1` | Mimics Kimi Coding request signature |
| `CLI_COMPAT_KILOCODE` | `=1` | Mimics Kilo Code request signature |
| `CLI_COMPAT_CLINE` | `=1` | Mimics Cline request signature |
-| `CLI_COMPAT_QWEN` | `=1` | Mimics Qwen Code request signature |
### Global
diff --git a/docs/reference/FREE_TIERS.md b/docs/reference/FREE_TIERS.md
index bac9c0f725..f57d5b825f 100644
--- a/docs/reference/FREE_TIERS.md
+++ b/docs/reference/FREE_TIERS.md
@@ -83,7 +83,7 @@ A 50-agent web-research pass (official docs + last-7-days news, adversarially ve
| `muse-spark-web` | Meta ToS explicitly prohibits automated access without prior permission, reverse engineering without written permission, and circumventing technologi… |
| `nlpcloud` | ToS explicitly prohibits "setting up a proxy or other device that allows others to access the Service through it" and grants only a non-transferable,… |
| `opencode` | ToS (Anomaly Innovations, Inc.) explicitly restricts use to "your own internal use, and not on behalf of or for the benefit of any third party" — ope… |
-| `qwen-web` | The free OAuth tier is discontinued; no ToS permits a self-hosted proxy using session tokens against chat.qwen.ai. Even before shutdown, automated/pr… |
+| `qwen-web` | No ToS permits a self-hosted proxy using session tokens against chat.qwen.ai; automated/programmatic access remains high-risk. |
| `t3-web` | ToS explicitly restricts accounts to personal use only, prohibits credential sharing with third parties, and bans automated/bot/scraping access — a s… |
### ✅ Generally permissive — caution / ambiguous / ok (the rest)
@@ -308,7 +308,7 @@ A 50-agent web-research pass (official docs + last-7-days news, adversarially ve
- **`publicai`** — The shipped freeNote ("Free community inference tier") is broadly accurate but understates the specificity: the 20 RPM rate limit is now documented. No major tightening found; the service remains fre…
- **`puter`** — Partially matches: the "500+ models" count is still accurate. However "users pay via Puter account" understates the reality — free accounts receive an undocumented starting credit that can be exhaust…
- **`qoder`** — Our catalog ships freeNote "(none)", but Qoder does have a free tier: a Community Edition with unlimited basic-model completions (daily-capped, unspecified limit) plus a one-time 14-day/300-credit Pr…
-- **`qwen-web`** — The shipped freeNote ("Free — Qwen models via chat.qwen.ai with login token") is now stale. The login-token/OAuth free API path was terminated on 2026-04-15. The qwen-web executor will receive 401 er…
+- **`qwen-web`** — Session-token access against chat.qwen.ai is not a dependable free-provider path and may be rejected upstream.
- **`sambanova`** — Our shipped note only described the one-time $5 credit (30-day validity). The current reality includes a permanent recurring free tier with documented rate limits (20 RPM, 20 RPD, 200k TPD) that pers…
- **`sensenova`** — Our shipped freeNote says "Free SenseTime models" which is vague but directionally correct — free access does exist. However, reality is more nuanced: free access is a time-limited public beta (Token…
- **`serper-search`** — The shipped freeNote says "(none)" which is partially accurate — there is no recurring free plan — but Serper does offer 2,500 one-time trial credits on signup. The catalog note could be more precise…
diff --git a/docs/reference/PROVIDER_REFERENCE.md b/docs/reference/PROVIDER_REFERENCE.md
index e51d8e1bdc..ba05c4497c 100644
--- a/docs/reference/PROVIDER_REFERENCE.md
+++ b/docs/reference/PROVIDER_REFERENCE.md
@@ -54,7 +54,6 @@ Use the dashboard at `/dashboard/providers` to enable, configure, and test each
| `kimi-coding` | `kmc` | Kimi Code CLI | OAuth | — | Sign in with the same Kimi account used by Kimi Code CLI. OmniRoute uses the CLI OAuth flow and Kimi Coding Plan endpoints. |
| `kiro` | `kr` | Kiro AI | OAuth | — | Free tier: 50 credits/month (~25K–100K tokens). ⚠️ Kiro ToS prohibits third-party proxy/harness use. |
| `qoder` | `if` | Qoder | OAuth | — | — |
-| `qwen` | `qw` | Qwen Code | OAuth | — | ⚠️ **DEPRECATED.** Qwen OAuth free tier was discontinued on 2026-04-15. Use 'bailian-coding-plan', 'alibaba', 'alibaba-cn', or 'openrouter' provider with API key instead. |
| `trae` | `tr` | Trae | OAuth | [link](https://trae.ai) | Trae is an AI-native IDE by ByteDance (SOLO remote agent). Authorize via trae.ai in the popup, or sign in at solo.trae.ai and paste the Cloud-IDE-JWT (sent as 'Authorization: Cloud-IDE-JWT ', ~14-day lifetime) as the access token; web_id/biz_user_id/user_unique_id/scope/tenant/region propagate via providerSpecificData. No headless refresh for pasted tokens — re-paste on expiry. |
| `windsurf` | `ws` | Windsurf (Devin CLI) | OAuth | [link](https://windsurf.com) | In the Windsurf / VS Code IDE, open the command palette and run `Windsurf: Provide Auth Token` (or click the Jupyter "Get Windsurf Authentication Token" button), then copy the shown token and paste it here. Note: opening windsurf.com/show-auth-token directly only renders a "Redirecting" page — the IDE must initiate the flow (it adds a `?state=...` param) for the token to appear. |
| `xai-oauth` | `xao` | xAI OAuth (Grok) | OAuth | [link](https://x.ai) | Sign in with xAI to use api.x.ai models such as Grok 4.5. This is separate from Grok Build JWT sessions, which use cli-chat-proxy.grok.com and grok-build model aliases. |
diff --git a/docs/security/STEALTH_GUIDE.md b/docs/security/STEALTH_GUIDE.md
index 9d60d7be59..43043ce99b 100644
--- a/docs/security/STEALTH_GUIDE.md
+++ b/docs/security/STEALTH_GUIDE.md
@@ -147,7 +147,7 @@ Touch points:
## CLI Fingerprint Registry — `open-sse/config/cliFingerprints.ts`
-Per-provider table that pins **exact** header ordering and JSON body field ordering captured from mitmproxy traces of the official CLIs. Currently registered: `codex`, `claude`, plus runtime-derived profiles in `providerHeaderProfiles.ts` for `antigravity`, `qwen`, `github`.
+Per-provider table that pins **exact** header ordering and JSON body field ordering captured from mitmproxy traces of the official CLIs. Currently registered: `codex`, `claude`, plus runtime-derived profiles in `providerHeaderProfiles.ts` for `antigravity` and `github`.
```ts
interface CliFingerprint {
@@ -220,7 +220,6 @@ All MITM endpoints require management auth (`requireCliToolsAuth`). The sudo pas
| `ANTIGRAVITY_USER_AGENT` | `antigravity/2.0.1 linux/arm64 google-api-nodejs-client/10.3.0` |
| `KIRO_USER_AGENT` | `AWS-SDK-JS/3.0.0 kiro-ide/1.0.0` |
| `QODER_USER_AGENT` | `Qoder-Cli` |
-| `QWEN_USER_AGENT` | `QwenCode/0.19.3 (linux; x64)` |
| `CURSOR_USER_AGENT` | `Cursor/3.4` |
Consumed by `open-sse/executors/base.ts::buildHeaders()` via dynamic lookup. **Bump these when providers release new CLI versions** — stale UA strings start getting rejected as outdated clients.
@@ -238,7 +237,6 @@ Consumed by `open-sse/executors/base.ts::buildHeaders()` via dynamic lookup. **B
| `CLI_COMPAT_KIMI_CODING=1` | Kimi Coding |
| `CLI_COMPAT_KILOCODE=1` | KiloCode |
| `CLI_COMPAT_CLINE=1` | Cline |
-| `CLI_COMPAT_QWEN=1` | Qwen Code |
| `CLI_COMPAT_ALL=1` | Enable all of the above |
The provider IP is **always preserved** — the toggle only reshapes the request wire image, it does not switch IP egress.
diff --git a/llm.txt b/llm.txt
index 9e012ae34d..f2e4ffd9de 100644
--- a/llm.txt
+++ b/llm.txt
@@ -360,9 +360,9 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **pool**, **notion**, *
### Provider Categories
-**Free Providers (4):** Qoder AI, Qwen Code (deprecated), Kiro AI, Windsurf
+**Free Providers (3):** Qoder AI, Kiro AI, Windsurf
-**OAuth Providers (14):** Claude Code, Antigravity, OpenAI Codex, GitHub Copilot, Cursor IDE, Kimi Coding, Kilo Code, Cline, Qwen, Kiro, Qoder, Gemini, Windsurf, GitLab Duo
+**OAuth Providers (13):** Claude Code, Antigravity, OpenAI Codex, GitHub Copilot, Cursor IDE, Kimi Coding, Kilo Code, Cline, Kiro, Qoder, Gemini, Windsurf, GitLab Duo
**API Key Providers (48+):** OpenAI, Anthropic, Gemini (Google AI Studio), DeepSeek, Groq, xAI (Grok), Mistral, Perplexity, Together AI, Fireworks AI, Cerebras, Cohere, NVIDIA NIM, Nebius AI, SiliconFlow, Hyperbolic, HuggingFace, OpenRouter, Vertex AI, Cloudflare Workers AI, Scaleway AI, AI/ML API, Pollinations AI, Puter AI, LongCat AI, Alibaba, Alibaba (China), Kimi, Kimi Coding (API Key), Minimax, Minimax (China), Blackbox AI, Synthetic, Kilo Gateway, Z.AI, GLM Coding, Deepgram, AssemblyAI, ElevenLabs, Cartesia, PlayHT, Inworld, NanoBanana, SD WebUI, ComfyUI, Ollama Cloud, Perplexity Search, Serper Search, Brave Search, Exa Search, Tavily Search, OpenCode Zen, OpenCode Go, Alibaba Coding Plan
diff --git a/open-sse/config/cliFingerprints.ts b/open-sse/config/cliFingerprints.ts
index 7009a89505..da65fce35b 100644
--- a/open-sse/config/cliFingerprints.ts
+++ b/open-sse/config/cliFingerprints.ts
@@ -13,7 +13,6 @@ import { isClaudeCodeCompatible } from "../services/provider.ts";
import {
getAntigravityUserAgent,
GITHUB_COPILOT_CHAT_USER_AGENT,
- getQwenOauthHeaders,
} from "./providerHeaderProfiles.ts";
import { normalizeCliCompatProviderId } from "@/shared/utils/cliCompat";
@@ -198,44 +197,6 @@ export const CLI_FINGERPRINTS: Record = {
],
userAgent: getAntigravityUserAgent,
},
- qwen: {
- headerOrder: [
- "Host",
- "Content-Type",
- "Authorization",
- "User-Agent",
- "X-Dashscope-AuthType",
- "X-Dashscope-CacheControl",
- "X-Dashscope-UserAgent",
- "X-Stainless-Arch",
- "X-Stainless-Lang",
- "X-Stainless-Os",
- "X-Stainless-Package-Version",
- "X-Stainless-Retry-Count",
- "X-Stainless-Runtime",
- "X-Stainless-Runtime-Version",
- "Connection",
- "Accept",
- "Accept-Language",
- "Sec-Fetch-Mode",
- "Accept-Encoding",
- ],
- bodyFieldOrder: [
- "model",
- "messages",
- "temperature",
- "top_p",
- "max_tokens",
- "stream",
- "tools",
- "tool_choice",
- "response_format",
- "n",
- "stop",
- ],
- userAgent: getQwenOauthHeaders()["User-Agent"],
- extraHeaders: getQwenOauthHeaders(),
- },
};
/**
diff --git a/open-sse/config/constants.ts b/open-sse/config/constants.ts
index 381dd9fe9b..894560805d 100644
--- a/open-sse/config/constants.ts
+++ b/open-sse/config/constants.ts
@@ -76,10 +76,6 @@ export const OAUTH_ENDPOINTS = {
token: "https://api.anthropic.com/v1/oauth/token",
auth: "https://api.anthropic.com/v1/oauth/authorize",
},
- qwen: {
- token: "https://chat.qwen.ai/api/v1/oauth2/token", // From CLIProxyAPI
- auth: "https://chat.qwen.ai/api/v1/oauth2/device/code", // From CLIProxyAPI
- },
qoder: {
token: process.env.QODER_OAUTH_TOKEN_URL || "",
auth: process.env.QODER_OAUTH_AUTHORIZE_URL || "",
diff --git a/open-sse/config/providerHeaderProfiles.ts b/open-sse/config/providerHeaderProfiles.ts
index 59dc53aa8f..47d33a84ad 100644
--- a/open-sse/config/providerHeaderProfiles.ts
+++ b/open-sse/config/providerHeaderProfiles.ts
@@ -13,11 +13,6 @@ export const GITHUB_COPILOT_USER_AGENT_LIBRARY = "electron-fetch";
export const QWEN_CLI_VERSION = "0.19.3";
export const QWEN_STAINLESS_LANG = "js";
-export const QWEN_STAINLESS_PACKAGE_VERSION = "5.11.0";
-export const QWEN_STAINLESS_RETRY_COUNT = "1";
-export const QWEN_STAINLESS_RUNTIME = "node";
-export const QWEN_ACCEPT_LANGUAGE = "*";
-export const QWEN_SEC_FETCH_MODE = "cors";
export const QODER_DEFAULT_USER_AGENT = "Qoder-Cli";
@@ -85,8 +80,8 @@ export function normalizeStainlessArch(arch: string = getRuntimeArch()): string
}
export function getQwenCliUserAgent(version = QWEN_CLI_VERSION): string {
- // Qwen Code builds this from the runtime process values. Keep it runtime-derived so
- // packaged deployments use their own platform/architecture instead of a maintainer's host.
+ // Qoder's DashScope-compatible backend expects Qwen Code's runtime-derived wire identity.
+ // Keep it runtime-derived so packaged deployments use their own platform/architecture.
return `QwenCode/${version} (${getRuntimePlatform()}; ${getRuntimeArch()})`;
}
@@ -111,26 +106,6 @@ export function getGitHubCopilotRefreshHeaders(authorization: string): Record {
- const userAgent = getQwenCliUserAgent();
- return {
- "User-Agent": userAgent,
- "X-Dashscope-AuthType": "qwen-oauth",
- "X-Dashscope-CacheControl": "enable",
- "X-Dashscope-UserAgent": userAgent,
- "X-Stainless-Arch": normalizeStainlessArch(),
- "X-Stainless-Lang": QWEN_STAINLESS_LANG,
- "X-Stainless-Os": normalizeStainlessPlatform(),
- "X-Stainless-Package-Version": QWEN_STAINLESS_PACKAGE_VERSION,
- "X-Stainless-Retry-Count": QWEN_STAINLESS_RETRY_COUNT,
- "X-Stainless-Runtime": QWEN_STAINLESS_RUNTIME,
- "X-Stainless-Runtime-Version": getRuntimeVersion(),
- Connection: "keep-alive",
- "Accept-Language": QWEN_ACCEPT_LANGUAGE,
- "Sec-Fetch-Mode": QWEN_SEC_FETCH_MODE,
- };
-}
-
export function getQoderDefaultHeaders(): Record {
return {
"User-Agent": QODER_DEFAULT_USER_AGENT,
diff --git a/open-sse/config/providers/index.ts b/open-sse/config/providers/index.ts
index 7bbc71969b..ebd6f7ed13 100644
--- a/open-sse/config/providers/index.ts
+++ b/open-sse/config/providers/index.ts
@@ -83,7 +83,6 @@ import { snowflakeProvider } from "./registry/snowflake/index.ts";
import { huggingfaceProvider } from "./registry/huggingface/index.ts";
import { chipotleProvider } from "./registry/chipotle/index.ts";
import { freeaiapikeyProvider } from "./registry/freeaiapikey/index.ts";
-import { qwenProvider } from "./registry/qwen/index.ts";
import { qwen_webProvider } from "./registry/qwen/web/index.ts";
import { zai_webProvider } from "./registry/zai-web/index.ts";
import { modalProvider } from "./registry/modal/index.ts";
@@ -284,7 +283,6 @@ export const REGISTRY: Record = {
huggingface: huggingfaceProvider,
chipotle: chipotleProvider,
freeaiapikey: freeaiapikeyProvider,
- qwen: qwenProvider,
"qwen-web": qwen_webProvider,
"zai-web": zai_webProvider,
modal: modalProvider,
diff --git a/open-sse/config/providers/registry/qwen/index.ts b/open-sse/config/providers/registry/qwen/index.ts
deleted file mode 100644
index e7aa78f860..0000000000
--- a/open-sse/config/providers/registry/qwen/index.ts
+++ /dev/null
@@ -1,25 +0,0 @@
-import type { RegistryEntry } from "../../shared.ts";
-import { getQwenOauthHeaders, resolvePublicCred } from "../../shared.ts";
-
-export const qwenProvider: RegistryEntry = {
- id: "qwen",
- alias: "qw",
- format: "openai",
- executor: "default",
- baseUrl: "https://chat.qwen.ai/api/v1/services/aigc/text-generation/generation",
- authType: "oauth",
- authHeader: "bearer",
- headers: getQwenOauthHeaders(),
- oauth: {
- clientIdEnv: "QWEN_OAUTH_CLIENT_ID",
- clientIdDefault: resolvePublicCred("qwen_id"),
- tokenUrl: "https://chat.qwen.ai/api/v1/oauth2/token",
- authUrl: "https://chat.qwen.ai/api/v1/oauth2/device/code",
- },
- models: [
- { id: "qwen3-coder-plus", name: "Qwen3 Coder Plus" },
- { id: "qwen3-coder-flash", name: "Qwen3 Coder Flash" },
- { id: "vision-model", name: "Qwen3 Vision Model" },
- { id: "coder-model", name: "Qwen3.6 (Coder Model)" },
- ],
-};
diff --git a/open-sse/config/providers/registry/qwen/web/index.ts b/open-sse/config/providers/registry/qwen/web/index.ts
index 5d5bff7800..8b86c9806c 100644
--- a/open-sse/config/providers/registry/qwen/web/index.ts
+++ b/open-sse/config/providers/registry/qwen/web/index.ts
@@ -2,8 +2,7 @@ import type { RegistryEntry } from "../../../shared.ts";
export const qwen_webProvider: RegistryEntry = {
id: "qwen-web",
- // Distinct alias: the primary "qwen" provider keeps the short "qw" alias;
- // this web/cookie variant is addressed by its own id.
+ // The web/cookie variant is addressed by its own id.
alias: "qwen-web",
format: "openai",
executor: "qwen-web",
diff --git a/open-sse/config/providers/shared.ts b/open-sse/config/providers/shared.ts
index fd32da1ad6..5d14e5a454 100644
--- a/open-sse/config/providers/shared.ts
+++ b/open-sse/config/providers/shared.ts
@@ -33,7 +33,6 @@ import {
getGitHubCopilotChatHeaders,
getKiroServiceHeaders,
getQoderDefaultHeaders,
- getQwenOauthHeaders,
getRuntimePlatform,
getRuntimeArch,
} from "../providerHeaderProfiles.ts";
@@ -691,7 +690,6 @@ export {
getGitHubCopilotChatHeaders,
getKiroServiceHeaders,
getQoderDefaultHeaders,
- getQwenOauthHeaders,
getRuntimePlatform,
getRuntimeArch,
resolvePublicCred,
diff --git a/open-sse/executors/default.ts b/open-sse/executors/default.ts
index bc5685f521..7f45e03083 100644
--- a/open-sse/executors/default.ts
+++ b/open-sse/executors/default.ts
@@ -28,7 +28,6 @@ import {
getTargetFormat,
isClaudeCodeCompatible,
} from "../services/provider.ts";
-import { sanitizeQwenThinkingToolChoice } from "../services/qwenThinking.ts";
import { getSapResourceGroup } from "../config/sap.ts";
import {
normalizeBailianMessagesUrl,
@@ -257,10 +256,6 @@ export class DefaultExecutor extends BaseExecutor {
return `${this.config.baseUrl}?beta=true`;
case "gemini":
return `${this.config.baseUrl}/${model}:${stream ? "streamGenerateContent?alt=sse" : "generateContent"}`;
- case "qwen": {
- const resourceUrl = credentials?.providerSpecificData?.resourceUrl;
- return `https://${resourceUrl || "portal.qwen.ai"}/v1/chat/completions`;
- }
default: {
// Honor a user-supplied custom base URL (providerSpecificData.baseUrl) for
// OpenAI-format providers (e.g. the built-in "openai" provider pointed at a
@@ -445,16 +440,6 @@ export class DefaultExecutor extends BaseExecutor {
headers["Accept"] = stream ? "text/event-stream" : "application/json";
- // Qwen header cleanup: Remove X-Dashscope-* headers if using an API key (DashScope compatible mode).
- // If using OAuth (Qwen Code), we MUST keep them for portal.qwen.ai to accept the request.
- if (this.provider === "qwen" && effectiveKey) {
- for (const key of Object.keys(headers)) {
- if (key.toLowerCase().startsWith("x-dashscope-")) {
- delete headers[key];
- }
- }
- }
-
const isCompatibleProvider =
this.provider?.startsWith?.("openai-compatible-") ||
this.provider?.startsWith?.("anthropic-compatible-");
@@ -621,21 +606,11 @@ export class DefaultExecutor extends BaseExecutor {
withDefaults = withoutStreamOptions;
}
} else if (stream && targetFormat === "openai" && requestFormat !== "openai-responses") {
- // Port of decolua/9router#663 (closes upstream #557): Qwen rejects with
- // 400 "'stream_options' only set this when you set stream: true" when the
- // outgoing body carries `stream: false` (Claude Code / Claude-Code-
- // compatible callers force the executor-level stream flag on via
- // `upstreamStream = stream || isClaudeCodeCompatible`, but the body keeps
- // the caller's original `stream: false`). Same upstream also rejects the
- // injection when `thinking` / `enable_thinking` is set. Skip injection in
- // those cases instead of unconditionally adding `stream_options`.
+ // Do not inject stream_options when the outgoing body explicitly disables streaming.
const defaultsRecord = withDefaults as Record;
const bodyDisablesStreamOptions =
defaultsRecord.stream !== undefined && defaultsRecord.stream !== true;
- const qwenBlocksStreamOptions =
- this.provider === "qwen" &&
- (Boolean(defaultsRecord.thinking) || Boolean(defaultsRecord.enable_thinking));
- if (bodyDisablesStreamOptions || qwenBlocksStreamOptions) {
+ if (bodyDisablesStreamOptions) {
if (Object.prototype.hasOwnProperty.call(defaultsRecord, "stream_options")) {
const withoutStreamOptions = { ...defaultsRecord };
delete withoutStreamOptions.stream_options;
@@ -695,15 +670,10 @@ export class DefaultExecutor extends BaseExecutor {
}
}
- if (this.provider === "qwen" && typeof withDefaults === "object" && withDefaults !== null) {
- return sanitizeQwenThinkingToolChoice(
- withDefaults as Record,
- "QwenExecutor"
- );
- }
-
- // Strip params unsupported by the target provider/model before sending upstream.
- // Rules live in ../translator/paramSupport.ts (9router#7ae9fff6; fixes #1748).
+ // Config-driven strip of params unsupported by the target provider/model
+ // (e.g. claude-opus-4 deprecated `temperature` → Anthropic 400). Port from
+ // 9router#7ae9fff6 (fixes upstream #1748). Rules live in
+ // ../translator/paramSupport.ts so adding one means editing one table.
if (typeof withDefaults === "object" && withDefaults !== null) {
const bodyRecord = withDefaults as Record;
const outboundModel = typeof bodyRecord.model === "string" ? bodyRecord.model : model;
diff --git a/open-sse/executors/index.ts b/open-sse/executors/index.ts
index ada346d03a..be70ba0bf5 100644
--- a/open-sse/executors/index.ts
+++ b/open-sse/executors/index.ts
@@ -161,7 +161,6 @@ const executors = {
"doubao-web": new DoubaoWebExecutor(),
db: new DoubaoWebExecutor(), // Alias
"qwen-web": new QwenWebExecutor(),
- qw: new QwenWebExecutor(), // Alias
"zai-web": new ZaiWebExecutor(),
zw: new ZaiWebExecutor(), // Alias
theoldllm: new TheOldLlmExecutor(),
diff --git a/open-sse/handlers/chatCore.ts b/open-sse/handlers/chatCore.ts
index a21a406bba..4b0d4d5106 100644
--- a/open-sse/handlers/chatCore.ts
+++ b/open-sse/handlers/chatCore.ts
@@ -256,12 +256,6 @@ import {
} from "./chatCore/serviceTier.ts";
import { cacheReasoningFromAssistantMessage } from "../services/reasoningCache.ts";
import { sanitizeOpenAITool } from "../services/toolSchemaSanitizer.ts";
-import {
- setDetectedToolLimit,
- parseToolLimitFromError,
- shouldDetectLimit,
-} from "../services/toolLimitDetector.ts";
-
import { isCompactResponsesEndpoint } from "../executors/codex.ts";
import { buildCodexQuotaPersistence } from "./chatCore/codexQuota.ts";
import { invalidateCodexQuotaCache } from "../services/codexQuotaFetcher.ts";
@@ -1744,9 +1738,7 @@ export async function handleChatCore({
finalCompressionBody?.messages ||
body?.contents ||
body?.request?.contents ||
- (body?.input && typeof body.input === "object" && !Array.isArray(body.input)
- ? body.input
- : []);
+ (body?.input && typeof body.input === "object" && !Array.isArray(body.input) ? body.input : []);
const finalEstimatedInputTokens =
estimateTokens(finalMessages) +
(Array.isArray(body?.tools) ? estimateTokens(body.tools) : 0) +
@@ -2479,7 +2471,7 @@ export async function handleChatCore({
const execute = async () => {
// Upstream body preparation extracted to chatCore/upstreamBody.ts (#3501 — first internal
// sub-slice of executeProviderRequest); produces the body sent upstream (payload rules +
- // tool-limit truncation + qwen oauth user backfill + prompt_cache_key injection).
+ // tool-limit truncation + prompt_cache_key injection).
let bodyToSend = await prepareUpstreamBody({
translatedBody,
modelToCall,
@@ -2500,13 +2492,7 @@ export async function handleChatCore({
const rawResult = await (async () => {
let attempts = 0;
const isModelScopeForRequest = isModelScope();
- const maxAttempts = isModelScopeForRequest
- ? 3
- : provider === "qwen"
- ? 3
- : provider === "codex"
- ? 3
- : 1;
+ const maxAttempts = isModelScopeForRequest ? 3 : provider === "codex" ? 3 : 1;
// ── Codex 429 account-rotation state ─────────────────────────────────
// Track excluded connection IDs for codex failover across attempts.
@@ -2611,26 +2597,6 @@ export async function handleChatCore({
recordKeyHealthStatus(401, execCreds);
}
- // Qwen 429 strict quota backoff (wait 1.5s, 3s and retry)
- if (
- provider === "qwen" &&
- res.response.status === 429 &&
- attempts < maxAttempts - 1
- ) {
- const bodyPeek = await res.response
- .clone()
- .text()
- .catch(() => "");
- if (bodyPeek.toLowerCase().includes("exceeded your current quota")) {
- const delay = 1500 * (attempts + 1);
- log?.warn?.("QWEN_RETRY", `Quota 429 hit. Retrying in ${delay}ms...`);
- releaseAccountSemaphore();
- await new Promise((r) => setTimeout(r, delay));
- attempts++;
- continue;
- }
- }
-
if (isModelScope() && res.response.status === 429 && attempts < maxAttempts - 1) {
const bodyPeek = await res.response
.clone()
@@ -3196,39 +3162,12 @@ export async function handleChatCore({
upstreamErrorType
);
}
- // We need to peek at the error text if it's 400 for Qwen
let upstreamErrorParsed = false;
let parsedStatusCode = providerResponse.status;
let parsedMessage = "";
let parsedRetryAfterMs: number | null = null;
let upstreamErrorBody: unknown = null;
- if (provider === "qwen" && providerResponse.status === HTTP_STATUS.BAD_REQUEST) {
- const errorDetails = await parseUpstreamError(providerResponse, provider);
- parsedStatusCode = errorDetails.statusCode;
- parsedMessage = errorDetails.message;
- parsedRetryAfterMs = errorDetails.retryAfterMs;
- upstreamErrorBody = errorDetails.responseBody;
- upstreamErrorParsed = true;
- }
-
- const errorMessageForToolDetection =
- typeof upstreamErrorBody === "string"
- ? upstreamErrorBody
- : JSON.stringify(upstreamErrorBody ?? {});
- if (shouldDetectLimit(errorMessageForToolDetection, parsedStatusCode)) {
- const detectedLimit = parseToolLimitFromError(errorMessageForToolDetection);
- if (detectedLimit) {
- setDetectedToolLimit(provider, detectedLimit);
- log?.info?.("TOOL_LIMIT", `Detected tool limit ${detectedLimit} for ${provider}`);
- }
- }
-
- const isQwenExpiredError =
- provider === "qwen" &&
- parsedStatusCode === HTTP_STATUS.BAD_REQUEST &&
- parsedMessage?.toLowerCase().includes("session has expired");
-
// Track whether stream_options was present and stripped — if so, 401/403 after
// that may be from the modification rather than a genuine auth failure, so we
// skip the credential refresh attempt in that case.
@@ -3238,11 +3177,10 @@ export async function handleChatCore({
delete translatedBody.stream_options;
}
- // Handle 401/403 (and Qwen explicit expiration) - try token refresh using executor
+ // Handle 401/403 - try token refresh using executor
if (
(providerResponse.status === HTTP_STATUS.UNAUTHORIZED ||
- providerResponse.status === HTTP_STATUS.FORBIDDEN ||
- isQwenExpiredError) &&
+ providerResponse.status === HTTP_STATUS.FORBIDDEN) &&
!hadStreamOptions // Skip refresh if failure may be from stream_options removal, not auth
) {
// Fix A: wrap refreshCredentials in runWithOnPersist so the persist callback
diff --git a/open-sse/handlers/chatCore/upstreamBody.ts b/open-sse/handlers/chatCore/upstreamBody.ts
index 85b0624a6b..57e3a358b4 100644
--- a/open-sse/handlers/chatCore/upstreamBody.ts
+++ b/open-sse/handlers/chatCore/upstreamBody.ts
@@ -4,8 +4,8 @@
*
* Extracted from handleChatCore's execute() closure: prepares the body actually sent upstream for a
* given target model. Pins the model id, applies the configured payload rules, truncates the tool
- * list to the provider's effective limit, backfills a default `user` for Qwen OAuth requests, and
- * injects an OpenAI `prompt_cache_key` for caching-capable providers. Pure with respect to handler
+ * list to the provider's effective limit and injects an OpenAI `prompt_cache_key` for
+ * caching-capable providers. Pure with respect to handler
* state (returns a fresh body, only logs as a side effect); behaviour is byte-identical to the
* previous inline block. Split into small private steps so each stays under the complexity cap.
*/
@@ -26,7 +26,11 @@ import { sanitizeRequestForResolvedTarget } from "../../services/targetRequestSa
type LoggerLike = { debug?: (...args: unknown[]) => void } | null | undefined;
type Body = Record;
type CredentialsLike =
- | { apiKey?: unknown; accessToken?: unknown; providerSpecificData?: Record | null }
+ | {
+ apiKey?: unknown;
+ accessToken?: unknown;
+ providerSpecificData?: Record | null;
+ }
| null
| undefined;
@@ -83,27 +87,6 @@ function truncateToolList(
return bodyToSend;
}
-// Qwen OAuth rejects requests without a non-empty `user` field. Some minimal OpenAI-compatible
-// clients omit it, so we backfill a stable default only for OAuth mode (API key mode is unaffected).
-function backfillQwenOAuthUser(
- bodyToSend: Body,
- provider: string | null | undefined,
- credentials: CredentialsLike,
- log?: LoggerLike
-): Body {
- const hasValidQwenUser = typeof bodyToSend.user === "string" && bodyToSend.user.trim().length > 0;
- const isQwenOAuthRequest =
- provider === "qwen" &&
- !credentials?.apiKey &&
- typeof credentials?.accessToken === "string" &&
- credentials.accessToken.trim().length > 0;
- if (isQwenOAuthRequest && !hasValidQwenUser) {
- bodyToSend = { ...bodyToSend, user: "omniroute-qwen-oauth" };
- log?.debug?.("QWEN", "Injected fallback user for OAuth request");
- }
- return bodyToSend;
-}
-
// Inject prompt_cache_key only for providers that support it.
async function injectPromptCacheKey(
bodyToSend: Body,
@@ -175,9 +158,13 @@ export async function prepareUpstreamBody(opts: {
log,
});
bodyToSend = truncateToolList(bodyToSend, provider, bypassDefaultToolLimit ?? false, log);
- bodyToSend = backfillQwenOAuthUser(bodyToSend, provider, credentials, log);
const connectionCacheOverride = resolveConnectionCacheOverride(credentials?.providerSpecificData);
- bodyToSend = await injectPromptCacheKey(bodyToSend, provider, targetFormat, connectionCacheOverride);
+ bodyToSend = await injectPromptCacheKey(
+ bodyToSend,
+ provider,
+ targetFormat,
+ connectionCacheOverride
+ );
return bodyToSend;
}
diff --git a/open-sse/index.ts b/open-sse/index.ts
index 2c49410688..245a9567c9 100644
--- a/open-sse/index.ts
+++ b/open-sse/index.ts
@@ -60,7 +60,6 @@ export {
refreshAccessToken,
refreshClaudeOAuthToken,
refreshGoogleToken,
- refreshQwenToken,
refreshCodexToken,
refreshQoderToken,
refreshGitHubToken,
diff --git a/open-sse/services/__tests__/tierResolver.test.ts b/open-sse/services/__tests__/tierResolver.test.ts
index 458a567627..2a1fdd2e2a 100644
--- a/open-sse/services/__tests__/tierResolver.test.ts
+++ b/open-sse/services/__tests__/tierResolver.test.ts
@@ -49,12 +49,6 @@ describe("TierResolver", () => {
assert.equal(result.hasFreeTier, true);
});
- it("classifies Qwen as free", () => {
- const result = classifyTier("qwen", "qwen3-coder-plus");
- assert.equal(result.tier, PROVIDER_TIER.FREE);
- assert.equal(result.hasFreeTier, true);
- });
-
it("classifies Cloudflare AI as free", () => {
const result = classifyTier("cloudflare-ai", "llama-3.3-70b");
assert.equal(result.tier, PROVIDER_TIER.FREE);
@@ -188,15 +182,14 @@ describe("TierResolver", () => {
{ provider: "anthropic", model: "claude-opus-4-7" },
{ provider: "groq", model: "llama-3.3-70b" },
{ provider: "qoder", model: "kimi-k2-thinking" },
- { provider: "qwen", model: "qwen3-coder-plus" },
{ provider: "unknown", model: "unknown-model" },
];
const results = classifyTiers(targets);
- assert.equal(results.length, 10);
+ assert.equal(results.length, 9);
assert.equal(results[0].tier, PROVIDER_TIER.FREE); // kiro
assert.equal(results[1].tier, PROVIDER_TIER.PREMIUM); // openai gpt-4o ($2.50/M)
assert.equal(results[2].tier, PROVIDER_TIER.CHEAP); // deepseek
- assert.equal(results[9].tier, PROVIDER_TIER.PREMIUM); // unknown
+ assert.equal(results[8].tier, PROVIDER_TIER.PREMIUM); // unknown
});
it("uses cache for repeated models", () => {
@@ -230,7 +223,6 @@ describe("TierResolver", () => {
"pollinations",
"longcat",
"cloudflare-ai",
- "qwen",
"nvidia-nim",
"cerebras",
"groq",
diff --git a/open-sse/services/refreshSerializer.ts b/open-sse/services/refreshSerializer.ts
index ff08f8982f..f71d5c4190 100644
--- a/open-sse/services/refreshSerializer.ts
+++ b/open-sse/services/refreshSerializer.ts
@@ -27,7 +27,6 @@ const ROTATION_LOCK_GROUP: Record = {
"gitlab-duo": "gitlab-duo",
kiro: "kiro",
"kimi-coding": "kimi-coding",
- qwen: "qwen",
};
// Protective settle gap (ms) between two consecutive sibling refreshes when the
diff --git a/open-sse/services/tierConfig.ts b/open-sse/services/tierConfig.ts
index 9bd1939aa6..2119029e13 100644
--- a/open-sse/services/tierConfig.ts
+++ b/open-sse/services/tierConfig.ts
@@ -51,7 +51,6 @@ export const LEGACY_FREE_PROVIDERS: readonly string[] = [
"pollinations",
"longcat",
"cloudflare-ai",
- "qwen",
"nvidia-nim",
"cerebras",
"groq",
diff --git a/open-sse/services/tierDefaults.json b/open-sse/services/tierDefaults.json
index 49be7b08d3..5e1e4a23cf 100644
--- a/open-sse/services/tierDefaults.json
+++ b/open-sse/services/tierDefaults.json
@@ -18,7 +18,6 @@
"pollinations",
"longcat",
"cloudflare-ai",
- "qwen",
"nvidia-nim",
"cerebras",
"groq"
diff --git a/open-sse/services/tokenRefresh.ts b/open-sse/services/tokenRefresh.ts
index 7d51aca80b..33b53a0d96 100755
--- a/open-sse/services/tokenRefresh.ts
+++ b/open-sse/services/tokenRefresh.ts
@@ -23,7 +23,6 @@ import { refreshKimiCodingToken } from "./tokenRefresh/providers/kimiCoding.ts";
import { refreshGitLabDuoToken } from "./tokenRefresh/providers/gitlabDuo.ts";
import { refreshClaudeOAuthToken } from "./tokenRefresh/providers/claudeOAuth.ts";
import { refreshGoogleToken } from "./tokenRefresh/providers/google.ts";
-import { refreshQwenToken } from "./tokenRefresh/providers/qwen.ts";
import { refreshCodexToken } from "./tokenRefresh/providers/codex.ts";
import { refreshKiroToken } from "./tokenRefresh/providers/kiro.ts";
import { refreshQoderToken } from "./tokenRefresh/providers/qoder.ts";
@@ -38,7 +37,6 @@ export {
refreshGitLabDuoToken,
refreshClaudeOAuthToken,
refreshGoogleToken,
- refreshQwenToken,
refreshCodexToken,
refreshKiroToken,
refreshQoderToken,
@@ -77,7 +75,6 @@ export const REFRESH_LEAD_MS: Record = {
"gitlab-duo": 5 * 60 * 1000, // GitLab token family revocation on misuse
kiro: 5 * 60 * 1000, // AWS SSO OIDC issues one-time-use refresh tokens
"kimi-coding": 5 * 60 * 1000, // Moonshot rotates per-refresh
- qwen: 5 * 60 * 1000, // Alibaba device-code path also rotates
// Non-rotating providers — longer lead is safe.
iflow: 24 * 60 * 60 * 1000, // 24 hours
// Google OAuth refresh_tokens are permanent (non-rotating) — longer lead
@@ -391,9 +388,6 @@ async function _getAccessTokenInternal(provider, credentials, log, proxyConfig:
case "codex":
return await refreshCodexToken(credentials.refreshToken, log, proxyConfig);
- case "qwen":
- return await refreshQwenToken(credentials.refreshToken, log, proxyConfig);
-
case "qoder":
return await refreshQoderToken(credentials.refreshToken, log, proxyConfig);
@@ -457,7 +451,6 @@ export function supportsTokenRefresh(provider) {
"agy",
"claude",
"codex",
- "qwen",
"qoder",
"github",
"kiro",
@@ -767,7 +760,6 @@ export function formatProviderCredentials(provider, credentials, log) {
};
case "codex":
- case "qwen":
case "qoder":
case "openai":
case "openrouter":
diff --git a/open-sse/services/tokenRefresh/providers/qwen.ts b/open-sse/services/tokenRefresh/providers/qwen.ts
deleted file mode 100644
index 6c0df5fde0..0000000000
--- a/open-sse/services/tokenRefresh/providers/qwen.ts
+++ /dev/null
@@ -1,81 +0,0 @@
-// @ts-nocheck
-// Extracted from open-sse/services/tokenRefresh.ts — see ../shared.ts for
-// provenance notes (ported idea from KooshaPari's PR #7338, redone on tip).
-import { PROVIDERS, OAUTH_ENDPOINTS } from "../../../config/constants.ts";
-import { runWithProxyContext } from "../../../utils/proxyFetch.ts";
-import { buildFormParams } from "../shared.ts";
-
-export async function refreshQwenToken(refreshToken, log, proxyConfig: unknown = null) {
- const endpoint = OAUTH_ENDPOINTS.qwen.token;
-
- try {
- const response = await runWithProxyContext(proxyConfig, () =>
- fetch(endpoint, {
- method: "POST",
- headers: {
- "Content-Type": "application/x-www-form-urlencoded",
- Accept: "application/json",
- },
- body: buildFormParams({
- grant_type: "refresh_token",
- refresh_token: refreshToken,
- client_id: PROVIDERS.qwen.clientId,
- }),
- })
- );
-
- if (response.status === 200) {
- const tokens = await response.json();
-
- log?.info?.("TOKEN_REFRESH", "Successfully refreshed Qwen token", {
- hasNewAccessToken: !!tokens.access_token,
- hasNewRefreshToken: !!tokens.refresh_token,
- expiresIn: tokens.expires_in,
- });
-
- return {
- accessToken: tokens.access_token,
- refreshToken: tokens.refresh_token || refreshToken,
- expiresIn: tokens.expires_in,
- providerSpecificData: tokens.resource_url
- ? { resourceUrl: tokens.resource_url }
- : undefined,
- };
- } else {
- const errorText = await response.text().catch(() => "");
-
- // Detect unrecoverable invalid_request (expired/revoked refresh token or bad client_id)
- let errorCode = null;
- try {
- const parsed = JSON.parse(errorText);
- errorCode = parsed?.error;
- } catch {
- // not JSON, ignore
- }
-
- if (errorCode === "invalid_request" || errorCode === "invalid_grant") {
- log?.error?.(
- "TOKEN_REFRESH",
- "Qwen refresh token is invalid or expired. Re-authentication required.",
- {
- status: response.status,
- errorCode,
- }
- );
- return { error: "unrecoverable_refresh_error", code: errorCode };
- }
-
- log?.warn?.("TOKEN_REFRESH", `Error with Qwen endpoint`, {
- status: response.status,
- error: errorText,
- });
- }
- } catch (error) {
- log?.warn?.("TOKEN_REFRESH", `Network error trying Qwen endpoint`, {
- error: error.message,
- });
- }
-
- log?.error?.("TOKEN_REFRESH", "Failed to refresh Qwen token");
- return null;
-}
diff --git a/open-sse/services/usage.ts b/open-sse/services/usage.ts
index 162f40b50e..c0aa9128ae 100644
--- a/open-sse/services/usage.ts
+++ b/open-sse/services/usage.ts
@@ -519,7 +519,6 @@ export const USAGE_FETCHER_PROVIDERS = [
"amazon-q",
"kimi-coding",
"kimi-coding-apikey",
- "qwen",
"qoder",
"glm",
"glm-cn",
@@ -584,8 +583,6 @@ export async function getUsageForProvider(
case "kimi-coding":
case "kimi-coding-apikey":
return await getKimiUsage(accessToken, apiKey, providerSpecificData);
- case "qwen":
- return await getQwenUsage(accessToken, providerSpecificData);
case "qoder":
// Qoder PATs live in `apiKey` (decrypted) or `providerSpecificData.qoderPat`,
// never in `accessToken`.
@@ -885,24 +882,6 @@ async function getVertexUsage(connectionId: string, provider: string) {
}
}
-/**
- * Qwen Usage
- */
-async function getQwenUsage(accessToken?: string, providerSpecificData?: JsonRecord) {
- void accessToken;
- try {
- const resourceUrl = providerSpecificData?.resourceUrl;
- if (!resourceUrl) {
- return { message: "Qwen connected. No resource URL available." };
- }
-
- // Qwen may have usage endpoint at resource URL
- return { message: "Qwen connected. Usage tracked per request." };
- } catch (error) {
- return { message: "Unable to fetch Qwen usage." };
- }
-}
-
/**
* Qoder Usage
*
diff --git a/open-sse/utils/cacheControlPolicy.ts b/open-sse/utils/cacheControlPolicy.ts
index 9f28f8f14a..8e22f8a0fd 100644
--- a/open-sse/utils/cacheControlPolicy.ts
+++ b/open-sse/utils/cacheControlPolicy.ts
@@ -7,7 +7,7 @@
* Client-side caching (e.g., Claude Code) should be preserved when:
* 1. Client is Claude Code or similar caching-aware client
* 2. Request will hit a deterministic target (single model or deterministic combo strategy)
- * 3. Provider supports prompt caching (Anthropic, Alibaba Qwen, etc.)
+ * 3. Provider supports prompt caching (Anthropic, etc.)
*/
import type { RoutingStrategyValue } from "../../src/shared/constants/routingStrategies";
@@ -76,7 +76,6 @@ const CACHING_PROVIDERS = new Set([
"claude",
"anthropic",
"zai",
- "qwen",
"deepseek",
// Kimi Code's OpenAI protocol requires prompt_cache_key for Coding Plan
// cache affinity. The OAuth card and hidden API-key compatibility ID share
diff --git a/open-sse/utils/publicCreds.ts b/open-sse/utils/publicCreds.ts
index 799901ca48..1cdeccbb1a 100644
--- a/open-sse/utils/publicCreds.ts
+++ b/open-sse/utils/publicCreds.ts
@@ -165,11 +165,6 @@ const EMBEDDED_DEFAULTS = {
14, 29, 30, 54, 55, 34, 26, 21, 8, 104, 53, 47, 85, 95, 15, 83, 110, 29, 105, 14, 53, 30, 94,
26, 29, 20, 26, 11,
],
- // Qwen Code CLI — qwen oauth client (public, device flow)
- qwen_id: [
- 9, 93, 93, 89, 70, 92, 66, 71, 7, 26, 68, 20, 86, 88, 13, 81, 79, 67, 9, 91, 12, 93, 15, 16, 88,
- 69, 23, 4, 20, 21, 64, 84,
- ],
// Kimi coding CLI — moonshot oauth client (public)
kimi_id: [
94, 90, 11, 92, 20, 89, 66, 69, 72, 73, 65, 76, 86, 65, 93, 7, 75, 20, 28, 86, 90, 94, 95, 95,
diff --git a/scripts/check/check-route-guard-membership.ts b/scripts/check/check-route-guard-membership.ts
index cb5ca34c2d..14e5968278 100644
--- a/scripts/check/check-route-guard-membership.ts
+++ b/scripts/check/check-route-guard-membership.ts
@@ -52,6 +52,7 @@ export const SPAWN_CAPABLE_ROUTE_ROOTS: ReadonlyArray = [
"src/app/api/skills/collect", // Skill Collector CLI detection: GET .../detect spawns a child process per CLI_TOOL_IDS entry via getCliRuntimeStatus() (Hard Rules #15 + #17, PR #6294 review)
"src/app/api/cli-tools/forge-settings", // GET calls getCliRuntimeStatus() to detect the `forge` CLI install (Hard Rules #15 + #17, #7263)
"src/app/api/cli-tools/jcode-settings", // GET calls getCliRuntimeStatus() to detect the `jcode` CLI install (Hard Rules #15 + #17, #7263)
+ "src/app/api/cli-tools/qwen-settings", // GET calls getCliRuntimeStatus("qwen") and writes local ~/.qwen config files (Hard Rules #15 + #17)
];
// Frozen pre-existing exceptions: spawn-capable routes NOT yet classified
diff --git a/src/app/(dashboard)/dashboard/cli-code/components/DefaultToolCard.tsx b/src/app/(dashboard)/dashboard/cli-code/components/DefaultToolCard.tsx
index cd74884a0c..dc99541690 100644
--- a/src/app/(dashboard)/dashboard/cli-code/components/DefaultToolCard.tsx
+++ b/src/app/(dashboard)/dashboard/cli-code/components/DefaultToolCard.tsx
@@ -48,6 +48,7 @@ export default function DefaultToolCard({
);
const isMultiModelTool = tool.modelSelectionMode === "multiple";
const usesOpenCodePreview = tool.previewConfigMode === "opencode";
+ const usesQwenCodePreview = tool.previewConfigMode === "qwen";
const selectedKeyObj = apiKeys?.find((k) => k.id === selectedApiKeyId);
const resolveApiKeyValue = useCallback(
@@ -187,15 +188,18 @@ export default function DefaultToolCard({
}, [isExpanded, runtimeStatus, t, toolId]);
const replaceVars = useCallback(
- (text) => {
+ (text, modelOverride = "") => {
const keyToUse = resolveApiKeyValue();
return text
.replace(/\{\{baseUrl\}\}/g, baseUrlWithV1)
.replace(/\{\{apiKey\}\}/g, keyToUse)
- .replace(/\{\{model\}\}/g, getSelectedModelLabels()[0] || t("modelPlaceholder"));
+ .replace(
+ /\{\{model\}\}/g,
+ modelOverride || getSelectedModelLabels()[0] || t("modelPlaceholder")
+ );
},
- [baseUrl, getSelectedModelLabels, resolveApiKeyValue, t]
+ [baseUrlWithV1, getSelectedModelLabels, resolveApiKeyValue, t]
);
const handleCopy = async (text, field) => {
@@ -211,6 +215,9 @@ export default function DefaultToolCard({
const getRenderedCodeBlock = useCallback(() => {
if (!tool.codeBlock?.code) return "";
+ if (usesQwenCodePreview) {
+ return replaceVars(tool.codeBlock.code, getSelectedModels()[0]);
+ }
if (!usesOpenCodePreview) return replaceVars(tool.codeBlock.code);
const keyToUse = resolveApiKeyValue();
@@ -226,13 +233,14 @@ export default function DefaultToolCard({
2
);
}, [
- baseUrl,
+ baseUrlWithV1,
getSelectedModels,
getSelectedModelLabelMap,
replaceVars,
resolveApiKeyValue,
tool.codeBlock?.code,
usesOpenCodePreview,
+ usesQwenCodePreview,
]);
const handleSelectModel = (model) => {
@@ -265,7 +273,11 @@ export default function DefaultToolCard({
// (#523) Prefer keyId lookup so the backend writes the real key to disk.
const selectedKeyId = selectedApiKeyId?.trim() || null;
- const res = await fetch(`/api/cli-tools/guide-settings/${toolId}`, {
+ const saveEndpoint =
+ toolId === "qwen"
+ ? "/api/cli-tools/qwen-settings"
+ : `/api/cli-tools/guide-settings/${toolId}`;
+ const res = await fetch(saveEndpoint, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
diff --git a/src/app/(dashboard)/dashboard/combos/page.tsx b/src/app/(dashboard)/dashboard/combos/page.tsx
index ea29735d05..733ce313d1 100644
--- a/src/app/(dashboard)/dashboard/combos/page.tsx
+++ b/src/app/(dashboard)/dashboard/combos/page.tsx
@@ -407,7 +407,7 @@ const COMBO_TEMPLATE_FALLBACK = {
balancedDesc: "Least-used routing to spread demand over time.",
freeStackTitle: "Free Stack ($0)",
freeStackDesc:
- "Round-robin across free providers: Kiro, Qoder, Qwen, Antigravity CLI. Zero cost, never stops.",
+ "Round-robin across free providers: Kiro, Qoder, Antigravity CLI. Zero cost, never stops.",
paidPremiumTitle: "Paid Premium",
paidPremiumDesc:
"Round-robin across paid subscriptions: Cursor, Antigravity. Top-tier models, distributed load.",
diff --git a/src/app/(dashboard)/dashboard/providers/components/onboarding/providerOnboardingCatalog.ts b/src/app/(dashboard)/dashboard/providers/components/onboarding/providerOnboardingCatalog.ts
index bd91a29861..81684d6a29 100644
--- a/src/app/(dashboard)/dashboard/providers/components/onboarding/providerOnboardingCatalog.ts
+++ b/src/app/(dashboard)/dashboard/providers/components/onboarding/providerOnboardingCatalog.ts
@@ -40,7 +40,6 @@ export const SUPPORTED_WIZARD_OAUTH_PROVIDER_IDS = new Set([
"codex",
"antigravity",
"agy",
- "qwen",
"kimi-coding",
"github",
"gitlab-duo",
diff --git a/src/app/api/cli-tools/all-statuses/route.ts b/src/app/api/cli-tools/all-statuses/route.ts
index 06532820f9..3b78a3f00d 100644
--- a/src/app/api/cli-tools/all-statuses/route.ts
+++ b/src/app/api/cli-tools/all-statuses/route.ts
@@ -11,6 +11,7 @@ import { CLI_TOOLS } from "@/shared/constants/cliTools";
import { getCliRuntimeStatus, getCliPrimaryConfigPath } from "@/shared/services/cliRuntime";
import { getAllCliToolLastConfigured } from "@/lib/db/cliToolState";
import { checkToolConfigStatus } from "@/lib/cliTools/checkToolConfigStatus";
+import { findOmniRouteQwenCodeModel } from "@/shared/services/qwenCodeConfig";
import { getCached, setCached } from "@/lib/cliTools/batchStatusCache";
import type { ToolBatchStatus, ToolBatchStatusMap } from "@/shared/types/cliBatchStatus";
@@ -43,13 +44,8 @@ async function extractEndpointFromConfig(
return (env?.ANTHROPIC_BASE_URL as string | undefined) ?? null;
}
case "qwen": {
- const mp = config.modelProviders as Record[] | undefined;
- if (!Array.isArray(mp)) return null;
- for (const provider of mp) {
- const baseUrl = (provider as Record).apiBase as string | undefined;
- if (baseUrl) return baseUrl;
- }
- return null;
+ const managed = findOmniRouteQwenCodeModel(config);
+ return typeof managed?.baseUrl === "string" ? managed.baseUrl : null;
}
case "cline":
return (config.openAiBaseUrl as string | undefined) ?? null;
diff --git a/src/app/api/cli-tools/guide-settings/[toolId]/route.ts b/src/app/api/cli-tools/guide-settings/[toolId]/route.ts
index 79078d0e10..002ee40113 100644
--- a/src/app/api/cli-tools/guide-settings/[toolId]/route.ts
+++ b/src/app/api/cli-tools/guide-settings/[toolId]/route.ts
@@ -66,8 +66,6 @@ export async function POST(request, { params }) {
// (#524) OpenCode config was never saved because only 'continue' was handled here.
// OpenCode reads ~/.config/opencode/opencode.json — write the OmniRoute settings there.
return await saveOpenCodeConfig({ baseUrl, apiKey, model, models, modelLabels });
- case "qwen":
- return await saveQwenConfig({ baseUrl, apiKey, model });
case "hermes":
return await saveHermesConfig({ baseUrl, apiKey, model });
// hermes-agent now uses the dedicated /api/cli-tools/hermes-agent-settings endpoint
@@ -205,60 +203,6 @@ async function saveOpenCodeConfig({ baseUrl, apiKey, model, models, modelLabels
});
}
-/**
- * Save Qwen Code config to ~/.qwen/settings.json
- *
- * Uses security.auth format (not modelProviders) since Qwen Code
- * prioritizes security.auth.selectedType over modelProviders entries.
- * Per official docs: security.auth takes highest precedence.
- */
-async function saveQwenConfig({ baseUrl, apiKey, model }) {
- const home = os.homedir();
- const configPath = path.join(home, ".qwen", "settings.json");
-
- await fs.mkdir(path.dirname(configPath), { recursive: true });
-
- const normalizedBaseUrl = String(baseUrl || "")
- .trim()
- .replace(/\/+$/, "");
- const resolvedApiKey = apiKey || "sk_omniroute";
- const resolvedModel = model || "qwen/qwen3-coder-plus";
-
- // Read existing config to preserve other settings (permissions, mcpServers, etc.)
- let existingConfig: Record = {};
- try {
- const raw = await fs.readFile(configPath, "utf-8");
- existingConfig = JSON.parse(raw);
- } catch {
- // File doesn't exist or invalid JSON
- }
-
- // Set security.auth for openai auth type with direct credentials
- // This takes priority over modelProviders entries (per Qwen docs)
- existingConfig.security = {
- ...existingConfig.security,
- auth: {
- selectedType: "openai",
- apiKey: resolvedApiKey,
- baseUrl: normalizedBaseUrl,
- },
- };
-
- // Set model to the selected model
- existingConfig.model = {
- ...existingConfig.model,
- name: resolvedModel,
- };
-
- await fs.writeFile(configPath, JSON.stringify(existingConfig, null, 2), "utf-8");
-
- return NextResponse.json({
- success: true,
- message: `Qwen Code config saved to ${configPath}`,
- configPath,
- });
-}
-
/**
* Save Hermes config to ~/.hermes/config.yaml
*
diff --git a/src/app/api/cli-tools/qwen-settings/route.ts b/src/app/api/cli-tools/qwen-settings/route.ts
index 11ca68b298..da8240b8f9 100644
--- a/src/app/api/cli-tools/qwen-settings/route.ts
+++ b/src/app/api/cli-tools/qwen-settings/route.ts
@@ -1,119 +1,112 @@
"use server";
+import fs from "node:fs/promises";
+import path from "node:path";
+import pino from "pino";
import { NextResponse } from "next/server";
-import fs from "fs/promises";
-import path from "path";
-import os from "os";
+
+import { sanitizeErrorMessage } from "@omniroute/open-sse/utils/error";
+
import { requireCliToolsAuth } from "@/lib/api/requireCliToolsAuth";
+import { getApiKeyById } from "@/lib/db/apiKeys";
+import { deleteCliToolLastConfigured, saveCliToolLastConfigured } from "@/lib/db/cliToolState";
+import { createMultiBackup } from "@/shared/services/backupService";
+import {
+ hasOmniRouteQwenCodeConfig,
+ mergeQwenCodeEnv,
+ mergeQwenCodeSettings,
+ removeQwenCodeEnv,
+ removeQwenCodeSettings,
+} from "@/shared/services/qwenCodeConfig";
import {
ensureCliConfigWriteAllowed,
- getCliPrimaryConfigPath,
+ getCliConfigPaths,
getCliRuntimeStatus,
} from "@/shared/services/cliRuntime";
-import { createBackup } from "@/shared/services/backupService";
-import { saveCliToolLastConfigured, deleteCliToolLastConfigured } from "@/lib/db/cliToolState";
-import { cliModelConfigSchema } from "@/shared/validation/schemas";
import { isValidationFailure, validateBody } from "@/shared/validation/helpers";
-import { getApiKeyById } from "@/lib/localDb";
+import { cliModelConfigSchema } from "@/shared/validation/schemas";
-const getQwenSettingsPath = () => getCliPrimaryConfigPath("qwen");
-const getQwenDir = () => path.dirname(getQwenSettingsPath());
-const getQwenEnvPath = () => path.join(getQwenDir(), ".env");
+const logger = pino({ name: "qwen-code-settings-api" });
-// Read current settings.json
-const readSettings = async () => {
+const getPaths = (): { settings: string; env: string } => {
+ const paths = getCliConfigPaths("qwen");
+ if (!paths?.settings || !paths.env) throw new Error("Qwen Code config paths are unavailable");
+ return { settings: paths.settings, env: paths.env };
+};
+
+const readTextIfPresent = async (filePath: string): Promise => {
try {
- const settingsPath = getQwenSettingsPath();
- const content = await fs.readFile(settingsPath, "utf-8");
- return JSON.parse(content);
- } catch (error: any) {
- if (error.code === "ENOENT") return null;
+ return await fs.readFile(filePath, "utf8");
+ } catch (error) {
+ if ((error as NodeJS.ErrnoException).code === "ENOENT") return "";
throw error;
}
};
-// Read current .env file
-const readEnv = async () => {
+const fileExists = async (filePath: string): Promise => {
try {
- const envPath = getQwenEnvPath();
- return await fs.readFile(envPath, "utf-8");
- } catch (error: any) {
- if (error.code === "ENOENT") return "";
+ await fs.access(filePath);
+ return true;
+ } catch {
+ return false;
+ }
+};
+
+const readSettings = async (filePath: string): Promise> => {
+ const text = await readTextIfPresent(filePath);
+ if (!text.trim()) return {};
+
+ const parsed: unknown = JSON.parse(text);
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
+ throw new Error("Qwen Code settings.json must contain a JSON object");
+ }
+ return parsed as Record;
+};
+
+const writeAtomic = async (filePath: string, content: string, mode?: number): Promise => {
+ const tempPath = `${filePath}.${process.pid}.${Date.now()}.tmp`;
+ try {
+ await fs.writeFile(tempPath, content, { encoding: "utf8", mode });
+ if (mode !== undefined) await fs.chmod(tempPath, mode);
+ await fs.rename(tempPath, filePath);
+ } catch (error) {
+ await fs.unlink(tempPath).catch(() => {});
throw error;
}
};
-// Check if settings has OmniRoute config
-const hasOmniRouteConfig = (settings: any) => {
- if (!settings || !settings.modelProviders) return false;
- const openai = settings.modelProviders.openai;
- if (!Array.isArray(openai)) return false;
- return openai.some((p: any) => {
- if (p.name?.includes("OmniRoute") || p.id === "omniroute") return true;
- if (!p.baseUrl) return false;
- try {
- const urlObj = new URL(p.baseUrl);
- const host = urlObj.hostname;
- const isDashScope =
- host === "dashscope.aliyuncs.com" || host.endsWith(".dashscope.aliyuncs.com");
- const isOpenAI = host === "api.openai.com" || host.endsWith(".openai.com");
- return !isDashScope && !isOpenAI;
- } catch {
- return true; // invalid URLs are treated as custom endpoints
- }
- });
-};
-
-// GET - Check Qwen CLI and read current settings
-export async function GET(request: Request) {
+export async function GET(request: Request): Promise {
const authError = await requireCliToolsAuth(request);
if (authError) return authError;
try {
- const runtime = await getCliRuntimeStatus("qwen");
-
- if (!runtime.installed || !runtime.runnable) {
- return NextResponse.json({
- installed: runtime.installed,
- runnable: runtime.runnable,
- command: runtime.command,
- commandPath: runtime.commandPath,
- runtimeMode: runtime.runtimeMode,
- reason: runtime.reason,
- settings: null,
- message:
- runtime.installed && !runtime.runnable
- ? "Qwen Code CLI is installed but not runnable"
- : "Qwen Code CLI is not installed",
- });
- }
-
- const settings = await readSettings();
+ const configPaths = getPaths();
+ const [runtime, settings] = await Promise.all([
+ getCliRuntimeStatus("qwen"),
+ readSettings(configPaths.settings),
+ ]);
return NextResponse.json({
- installed: runtime.installed,
- runnable: runtime.runnable,
- command: runtime.command,
- commandPath: runtime.commandPath,
- runtimeMode: runtime.runtimeMode,
- reason: runtime.reason,
+ ...runtime,
settings,
- hasOmniRoute: hasOmniRouteConfig(settings),
- settingsPath: getQwenSettingsPath(),
- envPath: getQwenEnvPath(),
+ hasOmniRoute: hasOmniRouteQwenCodeConfig(settings),
+ settingsPath: configPaths.settings,
+ envPath: configPaths.env,
});
} catch (error) {
- console.log("Error checking qwen settings:", error);
- return NextResponse.json({ error: "Failed to check qwen settings" }, { status: 500 });
+ logger.error({ err: error }, "Failed to read Qwen Code settings");
+ return NextResponse.json(
+ { error: sanitizeErrorMessage(error instanceof Error ? error.message : String(error)) },
+ { status: 500 }
+ );
}
}
-// POST - Write OmniRoute config to settings.json + .env
-export async function POST(request: Request) {
+export async function POST(request: Request): Promise {
const authError = await requireCliToolsAuth(request);
if (authError) return authError;
- let rawBody;
+ let rawBody: unknown;
try {
rawBody = await request.json();
} catch {
@@ -128,228 +121,91 @@ export async function POST(request: Request) {
);
}
+ const validation = validateBody(cliModelConfigSchema, rawBody);
+ if (isValidationFailure(validation)) {
+ return NextResponse.json({ error: validation.error }, { status: 400 });
+ }
+
+ const writeGuard = ensureCliConfigWriteAllowed();
+ if (writeGuard) return NextResponse.json({ error: writeGuard }, { status: 403 });
+
try {
- const writeGuard = ensureCliConfigWriteAllowed();
- if (writeGuard) {
- return NextResponse.json({ error: writeGuard }, { status: 403 });
- }
-
- // Extract keyId BEFORE validation — Zod strips unknown fields
- const keyId = typeof rawBody?.keyId === "string" ? rawBody.keyId.trim() : null;
-
- const validation = validateBody(cliModelConfigSchema, rawBody);
- if (isValidationFailure(validation)) {
- return NextResponse.json({ error: validation.error }, { status: 400 });
- }
- let { baseUrl, apiKey, model } = validation.data;
-
- // Resolve real key from DB by ID
+ const configPaths = getPaths();
+ const bodyRecord = rawBody as Record;
+ const keyId = typeof bodyRecord.keyId === "string" ? bodyRecord.keyId.trim() : "";
+ let apiKey = validation.data.apiKey || "";
if (keyId) {
- try {
- const keyRecord = await getApiKeyById(keyId);
- if (keyRecord?.key) apiKey = keyRecord.key as string;
- } catch {
- /* non-critical */
- }
+ const keyRecord = await getApiKeyById(keyId);
+ if (keyRecord?.key) apiKey = keyRecord.key;
}
+ if (!apiKey) apiKey = "sk_omniroute";
- const resolvedApiKey = apiKey || "sk_omniroute";
- const resolvedModel = model || "coder-model";
- const normalizedBaseUrl = String(baseUrl || "")
- .trim()
- .replace(/\/+$/, "");
- const qwenDir = getQwenDir();
- const settingsPath = getQwenSettingsPath();
- const envPath = getQwenEnvPath();
-
- // Ensure directory exists
- await fs.mkdir(qwenDir, { recursive: true });
-
- // Backup current settings before modifying
- await createBackup("qwen", settingsPath);
-
- // --- Write API keys to ~/.qwen/.env ---
- let envContent = await readEnv();
- const envLines = envContent.split("\n").filter((line) => {
- // Remove old OmniRoute-related keys we're about to write
- return (
- !line.startsWith("OPENAI_API_KEY=") &&
- !line.startsWith("ANTHROPIC_API_KEY=") &&
- !line.startsWith("GEMINI_API_KEY=")
- );
+ const [existingSettings, existingEnv] = await Promise.all([
+ readSettings(configPaths.settings),
+ readTextIfPresent(configPaths.env),
+ ]);
+ const nextSettings = mergeQwenCodeSettings(existingSettings, {
+ baseUrl: validation.data.baseUrl,
+ model: validation.data.model,
});
+ const nextEnv = mergeQwenCodeEnv(existingEnv, apiKey);
- envLines.push(`OPENAI_API_KEY=${resolvedApiKey}`);
- envLines.push(`ANTHROPIC_API_KEY=${resolvedApiKey}`);
- envLines.push(`GEMINI_API_KEY=${resolvedApiKey}`);
+ await fs.mkdir(path.dirname(configPaths.settings), { recursive: true, mode: 0o700 });
+ await createMultiBackup("qwen", [configPaths.settings, configPaths.env]);
+ await writeAtomic(configPaths.settings, `${JSON.stringify(nextSettings, null, 2)}\n`);
+ await writeAtomic(configPaths.env, nextEnv, 0o600);
- await fs.writeFile(envPath, envLines.join("\n").trim() + "\n", "utf-8");
-
- // --- Write modelProviders to settings.json ---
- let existingConfig: Record = {};
- try {
- const raw = await fs.readFile(settingsPath, "utf-8");
- existingConfig = JSON.parse(raw);
- } catch {
- // File doesn't exist or invalid JSON
- }
-
- if (!existingConfig.modelProviders) existingConfig.modelProviders = {};
-
- // openai provider — primary, supports all models via OmniRoute
- const openaiEntry = {
- id: resolvedModel,
- name: `${resolvedModel} (OmniRoute)`,
- envKey: "OPENAI_API_KEY",
- baseUrl: normalizedBaseUrl,
- generationConfig: {
- contextWindowSize: 200000,
- },
- };
-
- if (!existingConfig.modelProviders.openai) existingConfig.modelProviders.openai = [];
- const openaiProviders = existingConfig.modelProviders.openai;
- const openaiIdx = openaiProviders.findIndex(
- (p: any) => p && (p.baseUrl === normalizedBaseUrl || p.id === "omniroute")
- );
- if (openaiIdx >= 0) {
- openaiProviders[openaiIdx] = openaiEntry;
- } else {
- openaiProviders.push(openaiEntry);
- }
-
- // anthropic provider — for Claude models via OmniRoute
- const anthropicEntry = {
- id: "claude-sonnet-4-6",
- name: "Claude Sonnet 4.6 (OmniRoute)",
- envKey: "ANTHROPIC_API_KEY",
- baseUrl: normalizedBaseUrl,
- generationConfig: {
- contextWindowSize: 200000,
- },
- };
-
- if (!existingConfig.modelProviders.anthropic) existingConfig.modelProviders.anthropic = [];
- const anthropicProviders = existingConfig.modelProviders.anthropic;
- const anthropicIdx = anthropicProviders.findIndex(
- (p: any) => p && p.baseUrl === normalizedBaseUrl
- );
- if (anthropicIdx >= 0) {
- anthropicProviders[anthropicIdx] = anthropicEntry;
- } else {
- anthropicProviders.push(anthropicEntry);
- }
-
- // gemini provider — for Gemini models via OmniRoute
- const geminiEntry = {
- id: "gemini-3-flash",
- name: "Gemini 3 Flash (OmniRoute)",
- envKey: "GEMINI_API_KEY",
- baseUrl: normalizedBaseUrl,
- };
-
- if (!existingConfig.modelProviders.gemini) existingConfig.modelProviders.gemini = [];
- const geminiProviders = existingConfig.modelProviders.gemini;
- const geminiIdx = geminiProviders.findIndex((p: any) => p && p.baseUrl === normalizedBaseUrl);
- if (geminiIdx >= 0) {
- geminiProviders[geminiIdx] = geminiEntry;
- } else {
- geminiProviders.push(geminiEntry);
- }
-
- await fs.writeFile(settingsPath, JSON.stringify(existingConfig, null, 2), "utf-8");
-
- // Persist last-configured timestamp
try {
saveCliToolLastConfigured("qwen");
- } catch {
- /* non-critical */
+ } catch (error) {
+ logger.warn({ err: error }, "Failed to save Qwen Code configuration timestamp");
}
return NextResponse.json({
success: true,
- message: "Qwen Code config saved successfully!",
- settingsPath,
- envPath,
+ message: "Qwen Code now routes through OmniRoute",
+ settingsPath: configPaths.settings,
+ envPath: configPaths.env,
});
} catch (error) {
- console.log("Error updating qwen settings:", error);
- return NextResponse.json({ error: "Failed to update qwen settings" }, { status: 500 });
+ logger.error({ err: error }, "Failed to update Qwen Code settings");
+ return NextResponse.json(
+ { error: sanitizeErrorMessage(error instanceof Error ? error.message : String(error)) },
+ { status: 500 }
+ );
}
}
-// DELETE - Remove OmniRoute config from settings.json and .env
-export async function DELETE(request: Request) {
+export async function DELETE(request: Request): Promise {
const authError = await requireCliToolsAuth(request);
if (authError) return authError;
+ const writeGuard = ensureCliConfigWriteAllowed();
+ if (writeGuard) return NextResponse.json({ error: writeGuard }, { status: 403 });
+
try {
- const writeGuard = ensureCliConfigWriteAllowed();
- if (writeGuard) {
- return NextResponse.json({ error: writeGuard }, { status: 403 });
- }
+ const configPaths = getPaths();
+ const [settingsExists, envExists, existingSettings, existingEnv] = await Promise.all([
+ fileExists(configPaths.settings),
+ fileExists(configPaths.env),
+ readSettings(configPaths.settings),
+ readTextIfPresent(configPaths.env),
+ ]);
+ const nextSettings = removeQwenCodeSettings(existingSettings);
+ const nextEnv = removeQwenCodeEnv(existingEnv);
- const settingsPath = getQwenSettingsPath();
- const envPath = getQwenEnvPath();
-
- // Backup current settings before resetting
- await createBackup("qwen", settingsPath);
-
- // --- Clean settings.json ---
- let existingConfig: Record = {};
- try {
- const raw = await fs.readFile(settingsPath, "utf-8");
- existingConfig = JSON.parse(raw);
- } catch (error: any) {
- if (error.code === "ENOENT") {
- return NextResponse.json({
- success: true,
- message: "No settings file to reset",
- });
+ if (settingsExists || envExists) {
+ await createMultiBackup("qwen", [configPaths.settings, configPaths.env]);
+ if (settingsExists) {
+ await writeAtomic(configPaths.settings, `${JSON.stringify(nextSettings, null, 2)}\n`);
}
- throw error;
+ if (envExists) await writeAtomic(configPaths.env, nextEnv, 0o600);
}
- // Remove OmniRoute entries from each provider type
- const providerTypes = ["openai", "anthropic", "gemini"];
- for (const type of providerTypes) {
- if (Array.isArray(existingConfig.modelProviders?.[type])) {
- existingConfig.modelProviders[type] = existingConfig.modelProviders[type].filter(
- (p: any) => !p.name?.includes("OmniRoute") && p.id !== "omniroute"
- );
- // Remove empty provider arrays
- if (existingConfig.modelProviders[type].length === 0) {
- delete existingConfig.modelProviders[type];
- }
- }
- }
-
- // Clean up empty modelProviders
- if (existingConfig.modelProviders && Object.keys(existingConfig.modelProviders).length === 0) {
- delete existingConfig.modelProviders;
- }
-
- await fs.writeFile(settingsPath, JSON.stringify(existingConfig, null, 2), "utf-8");
-
- // --- Clean .env ---
- const RESET_ENV_KEYS = ["OPENAI_API_KEY", "ANTHROPIC_API_KEY", "GEMINI_API_KEY"];
-
- try {
- let envContent = await fs.readFile(envPath, "utf-8");
- const envLines = envContent
- .split("\n")
- .filter((line) => !RESET_ENV_KEYS.some((key) => line.startsWith(`${key}=`)));
-
- await fs.writeFile(envPath, envLines.join("\n").trim() + "\n", "utf-8");
- } catch {
- // .env doesn't exist — nothing to clean
- }
-
- // Clear last-configured timestamp
try {
deleteCliToolLastConfigured("qwen");
- } catch {
- /* non-critical */
+ } catch (error) {
+ logger.warn({ err: error }, "Failed to clear Qwen Code configuration timestamp");
}
return NextResponse.json({
@@ -357,7 +213,10 @@ export async function DELETE(request: Request) {
message: "OmniRoute settings removed from Qwen Code",
});
} catch (error) {
- console.log("Error resetting qwen settings:", error);
- return NextResponse.json({ error: "Failed to reset qwen settings" }, { status: 500 });
+ logger.error({ err: error }, "Failed to reset Qwen Code settings");
+ return NextResponse.json(
+ { error: sanitizeErrorMessage(error instanceof Error ? error.message : String(error)) },
+ { status: 500 }
+ );
}
}
diff --git a/src/app/api/cli-tools/status/route.ts b/src/app/api/cli-tools/status/route.ts
index 737c0c75ac..3a2372f3fc 100644
--- a/src/app/api/cli-tools/status/route.ts
+++ b/src/app/api/cli-tools/status/route.ts
@@ -60,8 +60,8 @@ export async function GET(request: Request) {
"openclaw",
"cline",
"kilo",
- "qwen",
"hermes",
+ "qwen",
];
await Promise.all(
diff --git a/src/app/api/providers/[id]/models/discovery/providerModelsConfig.ts b/src/app/api/providers/[id]/models/discovery/providerModelsConfig.ts
index 926a1a0fd8..10af061e00 100644
--- a/src/app/api/providers/[id]/models/discovery/providerModelsConfig.ts
+++ b/src/app/api/providers/[id]/models/discovery/providerModelsConfig.ts
@@ -139,16 +139,8 @@ export const PROVIDER_MODELS_CONFIG: Record =
authPrefix: "Bearer ",
parseResponse: (data) => normalizeOpenAiLikeModelsResponse(data, "huggingface"),
},
- qwen: {
- url: "https://dashscope-intl.aliyuncs.com/compatible-mode/v1/models",
- method: "GET",
- headers: { "Content-Type": "application/json" },
- authHeader: "Authorization",
- authPrefix: "Bearer ",
- parseResponse: (data) => data.data || [],
- },
// #3931: qwen-web (cookie provider) was missing here, so its discovery page
- // showed nothing (the OAuth fallback above only fires for provider==="qwen").
+ // showed nothing.
// `chat.qwen.ai/api/v2/models/` is public (no auth header configured/sent);
// shape `{ data: { data: [{ id, name, owned_by }] } }`, flatter `{ data: [] }` fallback.
"qwen-web": {
@@ -226,8 +218,7 @@ export const PROVIDER_MODELS_CONFIG: Record =
if (token) out["x-api-key"] = token;
return out;
},
- parseResponse: (data: any) =>
- Array.isArray(data) ? data : (data?.data || data?.models || []),
+ parseResponse: (data: any) => (Array.isArray(data) ? data : data?.data || data?.models || []),
},
openai: {
url: "https://api.openai.com/v1/models",
diff --git a/src/app/api/providers/[id]/models/route.ts b/src/app/api/providers/[id]/models/route.ts
index 10eed675e7..49a7d724dc 100755
--- a/src/app/api/providers/[id]/models/route.ts
+++ b/src/app/api/providers/[id]/models/route.ts
@@ -1870,25 +1870,6 @@ export async function GET(
provider in PROVIDER_MODELS_CONFIG
? PROVIDER_MODELS_CONFIG[provider as keyof typeof PROVIDER_MODELS_CONFIG]
: deriveConfigFromRegistryModelsUrl(provider);
- // Static model providers (no remote /models API)
- // Qwen OAuth Fallback: The Dashscope /models API rejects OAuth tokens with 401
- if (provider === "qwen" && connection.authType === "oauth") {
- const qwenModels = getModelsByProviderId("qwen");
- return buildResponse({
- provider,
- connectionId,
- models: qwenModels.map((m: any) => ({
- id: m.id,
- name: m.name || m.id,
- owned_by: "qwen",
- })),
- source: "local_catalog",
- // #5460/#5465 — Qwen OAuth has no OAuth-compatible remote /models list;
- // the static catalog is intentional, so model-sync should import it.
- intentional: true,
- });
- }
-
if (provider === "codex") {
// Auto-merge live/GitHub/local (future-proof discovery), then apply explicit
// denylist filters (e.g. drop GPT-5.4 family). Do not gate remote-only IDs.
diff --git a/src/app/api/providers/[id]/sync-models/degradedLocalCatalog.ts b/src/app/api/providers/[id]/sync-models/degradedLocalCatalog.ts
index beff0041e9..6e521e81ce 100644
--- a/src/app/api/providers/[id]/sync-models/degradedLocalCatalog.ts
+++ b/src/app/api/providers/[id]/sync-models/degradedLocalCatalog.ts
@@ -2,7 +2,7 @@
* #5460 (Reka) + #5465 (t3.chat) — Distinguish a genuinely degraded
* `local_catalog` models response (remote model discovery failed → the sync
* route surfaces a 502) from a provider whose local catalog is its INTENDED and
- * only discovery source (reka, qwen-oauth, embedding/rerank providers like
+ * only discovery source (reka, embedding/rerank providers like
* voyage-ai/jina-ai, web-cookie providers like t3-web).
*
* The models route tags the latter with `intentional: true`. Before this guard,
diff --git a/src/app/api/providers/[id]/test/oauthTestConfig.ts b/src/app/api/providers/[id]/test/oauthTestConfig.ts
index 0387cdf895..5792265cd2 100644
--- a/src/app/api/providers/[id]/test/oauthTestConfig.ts
+++ b/src/app/api/providers/[id]/test/oauthTestConfig.ts
@@ -75,12 +75,6 @@ export const OAUTH_TEST_CONFIG = {
authPrefix: "Bearer ",
refreshable: true,
},
- qwen: {
- // DashScope (previously portal.qwen.ai) /v1/models might return 404 or auth issues.
- // Use checkExpiry instead — actual connectivity is validated via real requests.
- checkExpiry: true,
- refreshable: true,
- },
cursor: {
checkExpiry: true,
},
@@ -121,7 +115,7 @@ export const OAUTH_TEST_CONFIG = {
// below. Grok Build's cli-chat-proxy endpoint doesn't expose a lightweight
// userinfo probe, and it enforces cli-specific headers (see
// GrokCliExecutor.buildHeaders) that this shared prober doesn't send — so
- // mirror qwen/cline/kilocode's checkExpiry pattern instead of a live probe.
+ // mirror cline/kilocode's checkExpiry pattern instead of a live probe.
// Real connectivity is still validated on every chat/completions request.
checkExpiry: true,
refreshable: true,
@@ -130,8 +124,7 @@ export const OAUTH_TEST_CONFIG = {
// GHE Copilot: probe the enterprise user-info endpoint derived from gheUrl
// (stored in providerSpecificData).
getUrl: (connection: any) => {
- const gheUrl =
- connection?.providerSpecificData?.gheUrl || connection?.gheUrl || "";
+ const gheUrl = connection?.providerSpecificData?.gheUrl || connection?.gheUrl || "";
const base = gheUrl.replace(/\/+$/, "");
return `${base}/api/v3/user`;
},
diff --git a/src/app/api/skills/collect/detect/route.ts b/src/app/api/skills/collect/detect/route.ts
index 5f755c6ec7..6dffdecd64 100644
--- a/src/app/api/skills/collect/detect/route.ts
+++ b/src/app/api/skills/collect/detect/route.ts
@@ -31,8 +31,8 @@ const CODING_TOOL_KEYWORDS: Record = {
openclaw: ["openclaw"],
droid: ["droid", "factory-ai"],
continue: ["continue"],
+ qwen: ["qwen", "qwen-code"],
antigravity: ["antigravity"],
- qwen: ["qwen", "alibaba"],
windsurf: ["windsurf"],
devin: ["devin", "cognition"],
};
diff --git a/src/app/api/skills/collect/install/route.ts b/src/app/api/skills/collect/install/route.ts
index cecca07196..da220da4f9 100644
--- a/src/app/api/skills/collect/install/route.ts
+++ b/src/app/api/skills/collect/install/route.ts
@@ -39,11 +39,11 @@ const CODING_TOOL_PATHS: Record = {
windsurf: "~/.windsurf/skills/{category}",
devin: "~/.devin/skills/{category}",
antigravity: "~/.antigravity/skills/{category}",
- qwen: "~/.qwen/skills/{category}",
kilocode: "~/.kilocode/skills/{category}",
openclaw: "~/.openclaw/skills/{category}",
droid: "~/.droid/skills/{category}",
continue: "~/.continue/skills/{category}",
+ qwen: "~/.qwen/skills/{category}",
};
function inferCategory(skillName: string, description: string): string {
diff --git a/src/app/api/v1/models/catalogProviderMaps.ts b/src/app/api/v1/models/catalogProviderMaps.ts
index c9dd6c2c99..6de91e6458 100644
--- a/src/app/api/v1/models/catalogProviderMaps.ts
+++ b/src/app/api/v1/models/catalogProviderMaps.ts
@@ -15,7 +15,6 @@ export const FALLBACK_ALIAS_TO_PROVIDER = {
kc: "kilocode",
kmc: "kimi-coding",
kr: "kiro",
- qw: "qwen",
};
export function buildAliasMaps() {
diff --git a/src/domain/types.ts b/src/domain/types.ts
index 4c71bf0006..0fad2d2678 100644
--- a/src/domain/types.ts
+++ b/src/domain/types.ts
@@ -8,7 +8,7 @@
*/
/**
- * @typedef {'openai'|'claude'|'gemini'|'codex'|'qwen'|'deepseek'|'cohere'|'groq'|'blackbox'|'mistral'|'openrouter'} ProviderId
+ * @typedef {'openai'|'claude'|'gemini'|'codex'|'deepseek'|'cohere'|'groq'|'blackbox'|'mistral'|'openrouter'} ProviderId
*/
/**
diff --git a/src/i18n/messages/ar.json b/src/i18n/messages/ar.json
index 977d4af63b..468eddf5b3 100644
--- a/src/i18n/messages/ar.json
+++ b/src/i18n/messages/ar.json
@@ -2036,7 +2036,6 @@
"antigravity": "يُستخدم عندما يجب اعتراض حركة مرور Antigravity/Kiro عبر MITM وتوجيهها إلى OmniRoute.",
"copilot": "استخدمه عندما تريد UX بأسلوب دردشة Copilot أثناء فرض مفاتيح OmniRoute وقواعد التوجيه.",
"amp": "استخدمه عندما تريد توجيه Amp CLI عبر OmniRoute بأسماء مستعارة مختصرة ثابتة وإعداد متوافق مع OpenAI.",
- "qwen": "استخدمه عندما تريد Qwen Code CLI مع إعدادات مزوّد يديرها OmniRoute ووصول إلى نماذج متعددة المزوّدين.",
"hermes": "استخدمه عندما تحتاج إلى مساعد AI خفيف الوزن ومعتمد على الأجهزة الطرفية للقيام بالمهام السريعة.",
"hermes-agent": "استخدمه عندما تحتاج إلى Hermes Agent من Nousresearch مع توجيه نماذج الوضع الافتراضي والتفويض والرؤية والنماذج المساعدة عبر OmniRoute.",
"custom": "يستخدم لتطبيقات الأدوات المخصصة أو التكوينات العامة المتوافقة مع OpenAI."
@@ -2055,7 +2054,6 @@
"kiro": "Amazon Kiro — بيئة تطوير متكاملة مدعومة بالذكاء الاصطناعي",
"windsurf": "محرر الشيفرة Windsurf المدعوم بالذكاء الاصطناعي",
"copilot": "مساعد GitHub Copilot بالذكاء الاصطناعي",
- "qwen": "واجهة Alibaba Qwen Code لسطر الأوامر",
"amp": "مساعد Sourcegraph Amp للبرمجة عبر سطر الأوامر",
"hermes": "Hermes AI مساعد المحطة",
"hermes-agent": "Hermes Agent من Nousresearch — ذكاء اصطناعي متقدم للطرفية يدعم نماذج متعددة والتفويض والرؤية والضغط وغيرها",
diff --git a/src/i18n/messages/az.json b/src/i18n/messages/az.json
index bf745be75a..38200e8915 100644
--- a/src/i18n/messages/az.json
+++ b/src/i18n/messages/az.json
@@ -2036,7 +2036,6 @@
"antigravity": "Use when Antigravity/Kiro traffic must be intercepted through MITM and routed to OmniRoute.",
"copilot": "Use when you want Copilot chat style UX while enforcing OmniRoute keys and routing rules.",
"amp": "Use when you want Amp shorthand workflows but still need OmniRoute alias and routing rules enforcement.",
- "qwen": "Use when you need Alibaba Qwen Code CLI for coding tasks.",
"hermes": "Use when you need a lightweight terminal-native AI assistant for quick tasks.",
"hermes-agent": "__MISSING__:Use when you need Hermes Agent (by Nousresearch) with default, delegation, vision and auxiliary models routed through OmniRoute.",
"custom": "Use for custom tool implementations or generic OpenAI-compatible configurations."
@@ -2055,7 +2054,6 @@
"kiro": "Amazon Kiro — AI-powered IDE",
"windsurf": "Windsurf AI Code Editor",
"copilot": "GitHub Copilot AI Assistant",
- "qwen": "Alibaba Qwen Code CLI",
"amp": "Sourcegraph Amp coding assistant CLI",
"hermes": "Hermes AI Terminal Assistant",
"hermes-agent": "__MISSING__:Hermes Agent (by Nousresearch) - Advanced terminal AI with multi-model support (delegation, vision, compression, etc.)",
diff --git a/src/i18n/messages/bg.json b/src/i18n/messages/bg.json
index a23e77cab5..4fd2dfb05a 100644
--- a/src/i18n/messages/bg.json
+++ b/src/i18n/messages/bg.json
@@ -2036,7 +2036,6 @@
"antigravity": "Използвайте, когато трафикът на Antigravity/Kiro трябва да бъде прихванат чрез MITM и насочен към OmniRoute.",
"copilot": "Използвайте, когато искате UX в стил на чат Copilot, като същевременно налагате OmniRoute ключове и правила за маршрутизиране.",
"amp": "Use when you want Amp CLI routed through OmniRoute with stable shorthand aliases and OpenAI-compatible setup.",
- "qwen": "Use when you want Qwen Code CLI with OmniRoute-managed provider settings and multi-provider model access.",
"hermes": "Използвайте, когато се нуждаете от лек асистент с изкуствен интелект за бързи задачи.",
"hermes-agent": "__MISSING__:Use when you need Hermes Agent (by Nousresearch) with default, delegation, vision and auxiliary models routed through OmniRoute.",
"custom": "Използвайте за внедряване на персонализирани инструменти или общи конфигурации, съвместими с OpenAI."
@@ -2055,7 +2054,6 @@
"kiro": "Amazon Kiro — AI-powered IDE",
"windsurf": "Windsurf AI Code Editor",
"copilot": "GitHub Copilot AI Assistant",
- "qwen": "Alibaba Qwen Code CLI",
"amp": "Sourcegraph Amp coding assistant CLI",
"hermes": "Hermes AI Terminal Assistant",
"hermes-agent": "__MISSING__:Hermes Agent (by Nousresearch) - Advanced terminal AI with multi-model support (delegation, vision, compression, etc.)",
diff --git a/src/i18n/messages/bn.json b/src/i18n/messages/bn.json
index 71dc008f76..e7bd785f93 100644
--- a/src/i18n/messages/bn.json
+++ b/src/i18n/messages/bn.json
@@ -2036,7 +2036,6 @@
"antigravity": "Use when Antigravity/Kiro traffic must be intercepted through MITM and routed to OmniRoute.",
"copilot": "Use when you want Copilot chat style UX while enforcing OmniRoute keys and routing rules.",
"amp": "Use when you want Amp shorthand workflows but still need OmniRoute alias and routing rules enforcement.",
- "qwen": "Use when you need Alibaba Qwen Code CLI for coding tasks.",
"hermes": "Use when you need a lightweight terminal-native AI assistant for quick tasks.",
"hermes-agent": "__MISSING__:Use when you need Hermes Agent (by Nousresearch) with default, delegation, vision and auxiliary models routed through OmniRoute.",
"custom": "Use for custom tool implementations or generic OpenAI-compatible configurations."
@@ -2055,7 +2054,6 @@
"kiro": "Amazon Kiro — AI-powered IDE",
"windsurf": "Windsurf AI Code Editor",
"copilot": "GitHub Copilot AI Assistant",
- "qwen": "Alibaba Qwen Code CLI",
"amp": "Sourcegraph Amp coding assistant CLI",
"hermes": "Hermes AI Terminal Assistant",
"hermes-agent": "__MISSING__:Hermes Agent (by Nousresearch) - Advanced terminal AI with multi-model support (delegation, vision, compression, etc.)",
diff --git a/src/i18n/messages/cs.json b/src/i18n/messages/cs.json
index b1ef30c400..7bbd8191b9 100644
--- a/src/i18n/messages/cs.json
+++ b/src/i18n/messages/cs.json
@@ -2036,7 +2036,6 @@
"antigravity": "Použijte, pokud musí být provoz Antigravity/Kiro zachycen prostřednictvím MITM a směrován do OmniRoute.",
"copilot": "Použijte, pokud chcete uživatelské rozhraní ve stylu Copilot chat a zároveň vynutit klíče a pravidla směrování OmniRoute.",
"amp": "Use when you want Amp CLI routed through OmniRoute with stable shorthand aliases and OpenAI-compatible setup.",
- "qwen": "Use when you want Qwen Code CLI with OmniRoute-managed provider settings and multi-provider model access.",
"hermes": "Použijte, když potřebujete lehkého terminálového nativního AI asistenta pro rychlé úkoly.",
"hermes-agent": "__MISSING__:Use when you need Hermes Agent (by Nousresearch) with default, delegation, vision and auxiliary models routed through OmniRoute.",
"custom": "Použijte pro vlastní implementace nástrojů nebo obecné konfigurace kompatibilní s OpenAI."
@@ -2055,7 +2054,6 @@
"kiro": "Amazon Kiro – AI poháněné IDE",
"windsurf": "Windsurf AI Code Editor",
"copilot": "GitHub Copilot AI Assistant",
- "qwen": "Alibaba Qwen Code CLI",
"amp": "Sourcegraph Amp coding assistant CLI",
"hermes": "Hermes AI Terminal Assistant",
"hermes-agent": "__MISSING__:Hermes Agent (by Nousresearch) - Advanced terminal AI with multi-model support (delegation, vision, compression, etc.)",
diff --git a/src/i18n/messages/da.json b/src/i18n/messages/da.json
index 5752d5d785..151b9d1623 100644
--- a/src/i18n/messages/da.json
+++ b/src/i18n/messages/da.json
@@ -2036,7 +2036,6 @@
"antigravity": "Bruges, når Antigravity/Kiro-trafik skal opsnappes gennem MITM og dirigeres til OmniRoute.",
"copilot": "Brug, når du ønsker Copilot-chatstil UX, mens du håndhæver OmniRoute-nøgler og routingregler.",
"amp": "Use when you want Amp CLI routed through OmniRoute with stable shorthand aliases and OpenAI-compatible setup.",
- "qwen": "Use when you want Qwen Code CLI with OmniRoute-managed provider settings and multi-provider model access.",
"hermes": "Brug, når du har brug for en letvægts terminal-native AI-assistent til hurtige opgaver.",
"hermes-agent": "__MISSING__:Use when you need Hermes Agent (by Nousresearch) with default, delegation, vision and auxiliary models routed through OmniRoute.",
"custom": "Brug til brugerdefinerede værktøjsimplementeringer eller generiske OpenAI-kompatible konfigurationer."
@@ -2055,7 +2054,6 @@
"kiro": "Amazon Kiro — AI-powered IDE",
"windsurf": "Windsurf AI Code Editor",
"copilot": "GitHub Copilot AI Assistant",
- "qwen": "Alibaba Qwen Code CLI",
"amp": "Sourcegraph Amp coding assistant CLI",
"hermes": "Hermes AI terminalassistent",
"hermes-agent": "__MISSING__:Hermes Agent (by Nousresearch) - Advanced terminal AI with multi-model support (delegation, vision, compression, etc.)",
diff --git a/src/i18n/messages/de.json b/src/i18n/messages/de.json
index 830f5d282c..c3182d4c8e 100644
--- a/src/i18n/messages/de.json
+++ b/src/i18n/messages/de.json
@@ -2041,7 +2041,6 @@
"antigravity": "Wird verwendet, wenn Antigravity/Kiro-Verkehr über MITM abgefangen und an OmniRoute weitergeleitet werden muss.",
"copilot": "Verwenden Sie diese Option, wenn Sie eine UX im Copilot-Chat-Stil wünschen und gleichzeitig OmniRoute-Schlüssel und Routing-Regeln durchsetzen möchten.",
"amp": "Use when you want Amp CLI routed through OmniRoute with stable shorthand aliases and OpenAI-compatible setup.",
- "qwen": "Use when you want Qwen Code CLI with OmniRoute-managed provider settings and multi-provider model access.",
"hermes": "Verwenden Sie es, wenn Sie einen leichten terminal-nativen KI-Assistenten für schnelle Aufgaben benötigen.",
"hermes-agent": "__MISSING__:Use when you need Hermes Agent (by Nousresearch) with default, delegation, vision and auxiliary models routed through OmniRoute.",
"custom": "Verwendung für benutzerdefinierte Tool-Implementierungen oder generische OpenAI-kompatible Konfigurationen."
@@ -2060,7 +2059,6 @@
"kiro": "Amazon Kiro — AI-powered IDE",
"windsurf": "Windsurf AI Code Editor",
"copilot": "GitHub Copilot AI Assistant",
- "qwen": "Alibaba Qwen Code CLI",
"amp": "Sourcegraph Amp coding assistant CLI",
"hermes": "Hermes AI Terminal Assistant",
"hermes-agent": "__MISSING__:Hermes Agent (by Nousresearch) - Advanced terminal AI with multi-model support (delegation, vision, compression, etc.)",
diff --git a/src/i18n/messages/en.json b/src/i18n/messages/en.json
index 4c76bf0205..8945ef57e6 100644
--- a/src/i18n/messages/en.json
+++ b/src/i18n/messages/en.json
@@ -2160,7 +2160,6 @@
"antigravity": "Use when Antigravity/Kiro traffic must be intercepted through MITM and routed to OmniRoute.",
"copilot": "Use when you want Copilot chat style UX while enforcing OmniRoute keys and routing rules.",
"amp": "Use when you want Amp shorthand workflows but still need OmniRoute alias and routing rules enforcement.",
- "qwen": "Use when you need Alibaba Qwen Code CLI for coding tasks.",
"hermes": "Use when you need a lightweight terminal-native AI assistant for quick tasks.",
"hermes-agent": "Use when you need Hermes Agent (by Nousresearch) with default, delegation, vision and auxiliary models routed through OmniRoute.",
"custom": "Use for custom tool implementations or generic OpenAI-compatible configurations."
@@ -2179,7 +2178,6 @@
"kiro": "Amazon Kiro — AI-powered IDE",
"windsurf": "Windsurf AI Code Editor",
"copilot": "GitHub Copilot AI Assistant",
- "qwen": "Alibaba Qwen Code CLI",
"amp": "Sourcegraph Amp coding assistant CLI",
"hermes": "Hermes AI Terminal Assistant",
"hermes-agent": "Hermes Agent (by Nousresearch) - Advanced terminal AI with multi-model support (delegation, vision, compression, etc.)",
@@ -4628,7 +4626,7 @@
"blackboxWebCookiePlaceholder": "Blackbox Web Cookie Placeholder",
"t3ChatWebCookieHint": "Open t3.chat → DevTools → Application → Local Storage → https://t3.chat, copy 'convex-session-id'. Then open DevTools → Network, copy the full Cookie header from any chat request. Paste both values in the fields below.",
"t3ChatWebCookiePlaceholder": "convex-session-id=abc123...",
- "grokWebCookieHint": "grok.com's cf_clearance cookie is pinned to the IP, User-Agent, and TLS fingerprint of the browser where you copied it — pasting it from a different machine/IP causes a 403. Paste sso and sso-rw here, then open Advanced Settings and fill Custom User-Agent with the EXACT User-Agent string of that same browser, and use the same IP/proxy for this connection.",
+ "grokWebCookieHint": "Grok Web Cookie Hint",
"blockClaudeExtraUsageDescription": "Hide extra Claude usage rows reported by some providers when they duplicate primary token accounting.",
"blockClaudeExtraUsageLabel": "Block duplicate Claude usage rows",
"disableCoolingDescription": "Skip the transient cooldown so this connection stays eligible even after recoverable errors (terminal states like banned/expired still apply).",
@@ -4706,7 +4704,6 @@
"tierFast": "Fast",
"antigravityProjectIdLabel": "Google Cloud Project ID",
"antigravityProjectIdPlaceholder": "my-gcp-project-id",
- "grokWebCookieHint": "Grok Web Cookie Hint",
"grokWebCookiePlaceholder": "Grok Web Cookie Placeholder",
"herokuBaseUrlHint": "Heroku Base Url Hint",
"hideEmail": "Hide Email",
diff --git a/src/i18n/messages/es.json b/src/i18n/messages/es.json
index 86357c72dc..875f022eaa 100644
--- a/src/i18n/messages/es.json
+++ b/src/i18n/messages/es.json
@@ -2036,7 +2036,6 @@
"antigravity": "Úselo cuando el tráfico de Antigravity/Kiro debe interceptarse a través de MITM y enrutarse a OmniRoute.",
"copilot": "Úselo cuando desee una experiencia de usuario estilo chat Copilot mientras aplica las claves de OmniRoute y las reglas de enrutamiento.",
"amp": "Use when you want Amp CLI routed through OmniRoute with stable shorthand aliases and OpenAI-compatible setup.",
- "qwen": "Use when you want Qwen Code CLI with OmniRoute-managed provider settings and multi-provider model access.",
"hermes": "Úselo cuando necesite un asistente de IA nativo de terminal liviano para tareas rápidas.",
"hermes-agent": "__MISSING__:Use when you need Hermes Agent (by Nousresearch) with default, delegation, vision and auxiliary models routed through OmniRoute.",
"custom": "Úselo para implementaciones de herramientas personalizadas o configuraciones genéricas compatibles con OpenAI."
@@ -2055,7 +2054,6 @@
"kiro": "Amazon Kiro — AI-powered IDE",
"windsurf": "Windsurf AI Code Editor",
"copilot": "GitHub Copilot AI Assistant",
- "qwen": "Alibaba Qwen Code CLI",
"amp": "Sourcegraph Amp coding assistant CLI",
"hermes": "Asistente de terminal de IA de Hermes",
"hermes-agent": "__MISSING__:Hermes Agent (by Nousresearch) - Advanced terminal AI with multi-model support (delegation, vision, compression, etc.)",
diff --git a/src/i18n/messages/fa.json b/src/i18n/messages/fa.json
index 66df92c8ab..3695624c12 100644
--- a/src/i18n/messages/fa.json
+++ b/src/i18n/messages/fa.json
@@ -2036,7 +2036,6 @@
"antigravity": "Use when Antigravity/Kiro traffic must be intercepted through MITM and routed to OmniRoute.",
"copilot": "Use when you want Copilot chat style UX while enforcing OmniRoute keys and routing rules.",
"amp": "Use when you want Amp shorthand workflows but still need OmniRoute alias and routing rules enforcement.",
- "qwen": "Use when you need Alibaba Qwen Code CLI for coding tasks.",
"hermes": "Use when you need a lightweight terminal-native AI assistant for quick tasks.",
"hermes-agent": "__MISSING__:Use when you need Hermes Agent (by Nousresearch) with default, delegation, vision and auxiliary models routed through OmniRoute.",
"custom": "Use for custom tool implementations or generic OpenAI-compatible configurations."
@@ -2055,7 +2054,6 @@
"kiro": "Amazon Kiro — AI-powered IDE",
"windsurf": "Windsurf AI Code Editor",
"copilot": "GitHub Copilot AI Assistant",
- "qwen": "Alibaba Qwen Code CLI",
"amp": "Sourcegraph Amp coding assistant CLI",
"hermes": "Hermes AI Terminal Assistant",
"hermes-agent": "__MISSING__:Hermes Agent (by Nousresearch) - Advanced terminal AI with multi-model support (delegation, vision, compression, etc.)",
diff --git a/src/i18n/messages/fi.json b/src/i18n/messages/fi.json
index e11c30e42b..f7e6ed2881 100644
--- a/src/i18n/messages/fi.json
+++ b/src/i18n/messages/fi.json
@@ -2036,7 +2036,6 @@
"antigravity": "Käytä, kun Antigravity/Kiro-liikenne on siepattava MITM:n kautta ja ohjattava OmniRouteen.",
"copilot": "Käytä, kun haluat Copilot-chat-tyylisen UX:n ja pakota OmniRoute-avaimia ja reitityssääntöjä.",
"amp": "Use when you want Amp CLI routed through OmniRoute with stable shorthand aliases and OpenAI-compatible setup.",
- "qwen": "Use when you want Qwen Code CLI with OmniRoute-managed provider settings and multi-provider model access.",
"hermes": "Käytä, kun tarvitset kevyen pääte-alkuperäisen AI-avustajan nopeisiin tehtäviin.",
"hermes-agent": "__MISSING__:Use when you need Hermes Agent (by Nousresearch) with default, delegation, vision and auxiliary models routed through OmniRoute.",
"custom": "Käytä mukautettuihin työkalutoteutuksiin tai yleisiin OpenAI-yhteensopiviin kokoonpanoihin."
@@ -2055,7 +2054,6 @@
"kiro": "Amazon Kiro — AI-powered IDE",
"windsurf": "Windsurf AI Code Editor",
"copilot": "GitHub Copilot AI Assistant",
- "qwen": "Alibaba Qwen Code CLI",
"amp": "Sourcegraph Amp coding assistant CLI",
"hermes": "Hermes AI Terminal Assistant",
"hermes-agent": "__MISSING__:Hermes Agent (by Nousresearch) - Advanced terminal AI with multi-model support (delegation, vision, compression, etc.)",
diff --git a/src/i18n/messages/fr.json b/src/i18n/messages/fr.json
index 36691cf722..61c98171d5 100644
--- a/src/i18n/messages/fr.json
+++ b/src/i18n/messages/fr.json
@@ -2036,7 +2036,6 @@
"antigravity": "À utiliser lorsque le trafic Antigravity/Kiro doit être intercepté via MITM et acheminé vers OmniRoute.",
"copilot": "À utiliser lorsque vous souhaitez une UX de style chat Copilot tout en appliquant les clés OmniRoute et les règles de routage.",
"amp": "Use when you want Amp CLI routed through OmniRoute with stable shorthand aliases and OpenAI-compatible setup.",
- "qwen": "Use when you want Qwen Code CLI with OmniRoute-managed provider settings and multi-provider model access.",
"hermes": "À utiliser lorsque vous avez besoin d’un assistant IA léger natif du terminal pour des tâches rapides.",
"hermes-agent": "__MISSING__:Use when you need Hermes Agent (by Nousresearch) with default, delegation, vision and auxiliary models routed through OmniRoute.",
"custom": "À utiliser pour les implémentations d'outils personnalisés ou les configurations génériques compatibles OpenAI."
@@ -2055,7 +2054,6 @@
"kiro": "Amazon Kiro — AI-powered IDE",
"windsurf": "Windsurf AI Code Editor",
"copilot": "GitHub Copilot AI Assistant",
- "qwen": "Alibaba Qwen Code CLI",
"amp": "Sourcegraph Amp coding assistant CLI",
"hermes": "Assistant de terminal Hermes AI",
"hermes-agent": "__MISSING__:Hermes Agent (by Nousresearch) - Advanced terminal AI with multi-model support (delegation, vision, compression, etc.)",
diff --git a/src/i18n/messages/gu.json b/src/i18n/messages/gu.json
index cfd829e744..7361910ba2 100644
--- a/src/i18n/messages/gu.json
+++ b/src/i18n/messages/gu.json
@@ -2036,7 +2036,6 @@
"antigravity": "Use when Antigravity/Kiro traffic must be intercepted through MITM and routed to OmniRoute.",
"copilot": "Use when you want Copilot chat style UX while enforcing OmniRoute keys and routing rules.",
"amp": "Use when you want Amp shorthand workflows but still need OmniRoute alias and routing rules enforcement.",
- "qwen": "Use when you need Alibaba Qwen Code CLI for coding tasks.",
"hermes": "Use when you need a lightweight terminal-native AI assistant for quick tasks.",
"hermes-agent": "__MISSING__:Use when you need Hermes Agent (by Nousresearch) with default, delegation, vision and auxiliary models routed through OmniRoute.",
"custom": "Use for custom tool implementations or generic OpenAI-compatible configurations."
@@ -2055,7 +2054,6 @@
"kiro": "Amazon Kiro — AI-powered IDE",
"windsurf": "Windsurf AI Code Editor",
"copilot": "GitHub Copilot AI Assistant",
- "qwen": "Alibaba Qwen Code CLI",
"amp": "Sourcegraph Amp coding assistant CLI",
"hermes": "Hermes AI Terminal Assistant",
"hermes-agent": "__MISSING__:Hermes Agent (by Nousresearch) - Advanced terminal AI with multi-model support (delegation, vision, compression, etc.)",
diff --git a/src/i18n/messages/he.json b/src/i18n/messages/he.json
index e2eaff0637..c23dfab1c1 100644
--- a/src/i18n/messages/he.json
+++ b/src/i18n/messages/he.json
@@ -2036,7 +2036,6 @@
"antigravity": "השתמש כאשר יש ליירט תעבורת Antigravity/Kiro דרך MITM ולנתב אל OmniRoute.",
"copilot": "השתמש כאשר אתה רוצה UX בסגנון צ'אט Copilot תוך אכיפת מפתחות וכללי ניתוב OmniRoute.",
"amp": "Use when you want Amp CLI routed through OmniRoute with stable shorthand aliases and OpenAI-compatible setup.",
- "qwen": "Use when you want Qwen Code CLI with OmniRoute-managed provider settings and multi-provider model access.",
"hermes": "השתמש כאשר אתה צריך עוזר AI קל משקל יליד מסוף למשימות מהירות.",
"hermes-agent": "__MISSING__:Use when you need Hermes Agent (by Nousresearch) with default, delegation, vision and auxiliary models routed through OmniRoute.",
"custom": "השתמש עבור הטמעת כלים מותאמים אישית או תצורות כלליות תואמות OpenAI."
@@ -2055,7 +2054,6 @@
"kiro": "Amazon Kiro — AI-powered IDE",
"windsurf": "Windsurf AI Code Editor",
"copilot": "GitHub Copilot AI Assistant",
- "qwen": "Alibaba Qwen Code CLI",
"amp": "Sourcegraph Amp coding assistant CLI",
"hermes": "עוזר מסוף של הרמס AI",
"hermes-agent": "__MISSING__:Hermes Agent (by Nousresearch) - Advanced terminal AI with multi-model support (delegation, vision, compression, etc.)",
diff --git a/src/i18n/messages/hi.json b/src/i18n/messages/hi.json
index 9c3133ef62..dc7638cf0b 100644
--- a/src/i18n/messages/hi.json
+++ b/src/i18n/messages/hi.json
@@ -2036,7 +2036,6 @@
"antigravity": "इसका उपयोग तब करें जब एंटीग्रेविटी/किरो ट्रैफिक को एमआईटीएम के माध्यम से रोका जाना चाहिए और ओमनीरूट पर भेजा जाना चाहिए।",
"copilot": "जब आप ओम्निरूट कुंजी और रूटिंग नियमों को लागू करते समय कोपायलट चैट शैली यूएक्स चाहते हैं तो इसका उपयोग करें।",
"amp": "Use when you want Amp CLI routed through OmniRoute with stable shorthand aliases and OpenAI-compatible setup.",
- "qwen": "Use when you want Qwen Code CLI with OmniRoute-managed provider settings and multi-provider model access.",
"hermes": "जब आपको त्वरित कार्यों के लिए हल्के टर्मिनल-नेटिव एआई सहायक की आवश्यकता हो तो इसका उपयोग करें।",
"hermes-agent": "__MISSING__:Use when you need Hermes Agent (by Nousresearch) with default, delegation, vision and auxiliary models routed through OmniRoute.",
"custom": "कस्टम टूल कार्यान्वयन या सामान्य OpenAI-संगत कॉन्फ़िगरेशन के लिए उपयोग करें।"
@@ -2055,7 +2054,6 @@
"kiro": "Amazon Kiro — AI-powered IDE",
"windsurf": "Windsurf AI Code Editor",
"copilot": "GitHub Copilot AI Assistant",
- "qwen": "Alibaba Qwen Code CLI",
"amp": "Sourcegraph Amp coding assistant CLI",
"hermes": "हर्मीस एआई टर्मिनल सहायक",
"hermes-agent": "__MISSING__:Hermes Agent (by Nousresearch) - Advanced terminal AI with multi-model support (delegation, vision, compression, etc.)",
diff --git a/src/i18n/messages/hu.json b/src/i18n/messages/hu.json
index af8f00c7c4..11dc8ef560 100644
--- a/src/i18n/messages/hu.json
+++ b/src/i18n/messages/hu.json
@@ -2036,7 +2036,6 @@
"antigravity": "Akkor használja, ha az Antigravity/Kiro forgalmat MITM-en keresztül kell elfogni, és az OmniRoute-hoz kell irányítani.",
"copilot": "Használja, ha másodpilóta csevegési stílusú UX-et szeretne, miközben betartja az OmniRoute kulcsokat és útválasztási szabályokat.",
"amp": "Use when you want Amp CLI routed through OmniRoute with stable shorthand aliases and OpenAI-compatible setup.",
- "qwen": "Use when you want Qwen Code CLI with OmniRoute-managed provider settings and multi-provider model access.",
"hermes": "Használja, ha könnyű, terminál-natív AI-asszisztensre van szüksége a gyors feladatokhoz.",
"hermes-agent": "__MISSING__:Use when you need Hermes Agent (by Nousresearch) with default, delegation, vision and auxiliary models routed through OmniRoute.",
"custom": "Egyéni eszközmegvalósításokhoz vagy általános OpenAI-kompatibilis konfigurációkhoz használható."
@@ -2055,7 +2054,6 @@
"kiro": "Amazon Kiro — AI-powered IDE",
"windsurf": "Windsurf AI Code Editor",
"copilot": "GitHub Copilot AI Assistant",
- "qwen": "Alibaba Qwen Code CLI",
"amp": "Sourcegraph Amp coding assistant CLI",
"hermes": "Hermes AI terminál asszisztens",
"hermes-agent": "__MISSING__:Hermes Agent (by Nousresearch) - Advanced terminal AI with multi-model support (delegation, vision, compression, etc.)",
diff --git a/src/i18n/messages/id.json b/src/i18n/messages/id.json
index 010a46bc1e..40eece99cb 100644
--- a/src/i18n/messages/id.json
+++ b/src/i18n/messages/id.json
@@ -2036,7 +2036,6 @@
"antigravity": "Gunakan ketika lalu lintas Antigravitasi/Kiro harus dicegat melalui MITM dan dialihkan ke OmniRoute.",
"copilot": "Gunakan saat Anda menginginkan UX gaya obrolan kopilot sambil menerapkan kunci OmniRoute dan aturan perutean.",
"amp": "Use when you want Amp CLI routed through OmniRoute with stable shorthand aliases and OpenAI-compatible setup.",
- "qwen": "Use when you want Qwen Code CLI with OmniRoute-managed provider settings and multi-provider model access.",
"hermes": "Gunakan saat Anda memerlukan asisten AI asli terminal yang ringan untuk tugas cepat.",
"hermes-agent": "__MISSING__:Use when you need Hermes Agent (by Nousresearch) with default, delegation, vision and auxiliary models routed through OmniRoute.",
"custom": "Gunakan untuk implementasi alat khusus atau konfigurasi umum yang kompatibel dengan OpenAI."
@@ -2055,7 +2054,6 @@
"kiro": "Amazon Kiro — AI-powered IDE",
"windsurf": "Windsurf AI Code Editor",
"copilot": "GitHub Copilot AI Assistant",
- "qwen": "Alibaba Qwen Code CLI",
"amp": "Sourcegraph Amp coding assistant CLI",
"hermes": "Asisten Terminal AI Hermes",
"hermes-agent": "__MISSING__:Hermes Agent (by Nousresearch) - Advanced terminal AI with multi-model support (delegation, vision, compression, etc.)",
diff --git a/src/i18n/messages/in.json b/src/i18n/messages/in.json
index 64ce248c95..ae15094d6f 100644
--- a/src/i18n/messages/in.json
+++ b/src/i18n/messages/in.json
@@ -2036,7 +2036,6 @@
"antigravity": "Use when Antigravity/Kiro traffic must be intercepted through MITM and routed to OmniRoute.",
"copilot": "Use when you want Copilot chat style UX while enforcing OmniRoute keys and routing rules.",
"amp": "Use when you want Amp shorthand workflows but still need OmniRoute alias and routing rules enforcement.",
- "qwen": "Use when you need Alibaba Qwen Code CLI for coding tasks.",
"hermes": "Use when you need a lightweight terminal-native AI assistant for quick tasks.",
"hermes-agent": "__MISSING__:Use when you need Hermes Agent (by Nousresearch) with default, delegation, vision and auxiliary models routed through OmniRoute.",
"custom": "Use for custom tool implementations or generic OpenAI-compatible configurations."
@@ -2055,7 +2054,6 @@
"kiro": "Amazon Kiro — AI-powered IDE",
"windsurf": "Windsurf AI Code Editor",
"copilot": "GitHub Copilot AI Assistant",
- "qwen": "Alibaba Qwen Code CLI",
"amp": "Sourcegraph Amp coding assistant CLI",
"hermes": "Hermes AI Terminal Assistant",
"hermes-agent": "__MISSING__:Hermes Agent (by Nousresearch) - Advanced terminal AI with multi-model support (delegation, vision, compression, etc.)",
diff --git a/src/i18n/messages/it.json b/src/i18n/messages/it.json
index 19ade6f753..a3223e4740 100644
--- a/src/i18n/messages/it.json
+++ b/src/i18n/messages/it.json
@@ -2146,7 +2146,6 @@
"antigravity": "Da utilizzare quando il traffico Antigravity/Kiro deve essere intercettato tramite MITM e instradato a OmniRoute.",
"copilot": "Utilizzalo quando desideri un'esperienza utente in stile chat Copilot applicando al tempo stesso le chiavi OmniRoute e le regole di routing.",
"amp": "Use when you want Amp CLI routed through OmniRoute with stable shorthand aliases and OpenAI-compatible setup.",
- "qwen": "Use when you want Qwen Code CLI with OmniRoute-managed provider settings and multi-provider model access.",
"hermes": "Utilizzalo quando hai bisogno di un leggero assistente AI nativo del terminale per attività rapide.",
"hermes-agent": "__MISSING__:Use when you need Hermes Agent (by Nousresearch) with default, delegation, vision and auxiliary models routed through OmniRoute.",
"custom": "Utilizzare per implementazioni di strumenti personalizzati o configurazioni generiche compatibili con OpenAI."
@@ -2165,7 +2164,6 @@
"kiro": "Amazon Kiro — AI-powered IDE",
"windsurf": "Windsurf AI Code Editor",
"copilot": "GitHub Copilot AI Assistant",
- "qwen": "Alibaba Qwen Code CLI",
"amp": "Sourcegraph Amp coding assistant CLI",
"hermes": "Assistente terminale Hermes AI",
"hermes-agent": "__MISSING__:Hermes Agent (by Nousresearch) - Advanced terminal AI with multi-model support (delegation, vision, compression, etc.)",
diff --git a/src/i18n/messages/ja.json b/src/i18n/messages/ja.json
index 3437d63758..e7e1da22de 100644
--- a/src/i18n/messages/ja.json
+++ b/src/i18n/messages/ja.json
@@ -2138,7 +2138,6 @@
"antigravity": "Antigravity/Kiro トラフィックを MITM 経由でインターセプトし、OmniRoute にルーティングする必要がある場合に使用します。",
"copilot": "OmniRoute キーとルーティング ルールを適用しながら、Copilot チャット スタイルの UX が必要な場合に使用します。",
"amp": "Use when you want Amp CLI routed through OmniRoute with stable shorthand aliases and OpenAI-compatible setup.",
- "qwen": "Use when you want Qwen Code CLI with OmniRoute-managed provider settings and multi-provider model access.",
"hermes": "素早いタスクのために軽量のターミナル ネイティブ AI アシスタントが必要な場合に使用します。",
"hermes-agent": "__MISSING__:Use when you need Hermes Agent (by Nousresearch) with default, delegation, vision and auxiliary models routed through OmniRoute.",
"custom": "カスタム ツールの実装または汎用の OpenAI 互換構成に使用します。"
@@ -2157,7 +2156,6 @@
"kiro": "Amazon Kiro — AI-powered IDE",
"windsurf": "Windsurf AI Code Editor",
"copilot": "GitHub Copilot AI Assistant",
- "qwen": "Alibaba Qwen Code CLI",
"amp": "Sourcegraph Amp coding assistant CLI",
"hermes": "エルメス AI ターミナルアシスタント",
"hermes-agent": "__MISSING__:Hermes Agent (by Nousresearch) - Advanced terminal AI with multi-model support (delegation, vision, compression, etc.)",
diff --git a/src/i18n/messages/ko.json b/src/i18n/messages/ko.json
index 6f3e10f711..b755575afb 100644
--- a/src/i18n/messages/ko.json
+++ b/src/i18n/messages/ko.json
@@ -2138,7 +2138,6 @@
"antigravity": "Antigravity/Kiro 트래픽이 MITM을 통해 가로채어 OmniRoute로 라우팅되어야 하는 경우에 사용합니다.",
"copilot": "OmniRoute 키와 라우팅 규칙을 적용하면서 Copilot 채팅 스타일 UX를 원할 때 사용하세요.",
"amp": "Use when you want Amp CLI routed through OmniRoute with stable shorthand aliases and OpenAI-compatible setup.",
- "qwen": "Use when you want Qwen Code CLI with OmniRoute-managed provider settings and multi-provider model access.",
"hermes": "빠른 작업을 위해 경량 터미널 기반 AI 도우미가 필요할 때 사용하세요.",
"hermes-agent": "__MISSING__:Use when you need Hermes Agent (by Nousresearch) with default, delegation, vision and auxiliary models routed through OmniRoute.",
"custom": "사용자 정의 도구 구현 또는 일반 OpenAI 호환 구성에 사용합니다."
@@ -2157,7 +2156,6 @@
"kiro": "Amazon Kiro — AI-powered IDE",
"windsurf": "Windsurf AI Code Editor",
"copilot": "GitHub Copilot AI Assistant",
- "qwen": "Alibaba Qwen Code CLI",
"amp": "Sourcegraph Amp coding assistant CLI",
"hermes": "헤르메스 AI 터미널 어시스턴트",
"hermes-agent": "__MISSING__:Hermes Agent (by Nousresearch) - Advanced terminal AI with multi-model support (delegation, vision, compression, etc.)",
diff --git a/src/i18n/messages/mr.json b/src/i18n/messages/mr.json
index 58d95ded81..0a5e697f8e 100644
--- a/src/i18n/messages/mr.json
+++ b/src/i18n/messages/mr.json
@@ -2138,7 +2138,6 @@
"antigravity": "Use when Antigravity/Kiro traffic must be intercepted through MITM and routed to OmniRoute.",
"copilot": "Use when you want Copilot chat style UX while enforcing OmniRoute keys and routing rules.",
"amp": "Use when you want Amp shorthand workflows but still need OmniRoute alias and routing rules enforcement.",
- "qwen": "Use when you need Alibaba Qwen Code CLI for coding tasks.",
"hermes": "Use when you need a lightweight terminal-native AI assistant for quick tasks.",
"hermes-agent": "__MISSING__:Use when you need Hermes Agent (by Nousresearch) with default, delegation, vision and auxiliary models routed through OmniRoute.",
"custom": "Use for custom tool implementations or generic OpenAI-compatible configurations."
@@ -2157,7 +2156,6 @@
"kiro": "Amazon Kiro — AI-powered IDE",
"windsurf": "Windsurf AI Code Editor",
"copilot": "GitHub Copilot AI Assistant",
- "qwen": "Alibaba Qwen Code CLI",
"amp": "Sourcegraph Amp coding assistant CLI",
"hermes": "Hermes AI Terminal Assistant",
"hermes-agent": "__MISSING__:Hermes Agent (by Nousresearch) - Advanced terminal AI with multi-model support (delegation, vision, compression, etc.)",
diff --git a/src/i18n/messages/ms.json b/src/i18n/messages/ms.json
index b99f603c7c..89d3e65202 100644
--- a/src/i18n/messages/ms.json
+++ b/src/i18n/messages/ms.json
@@ -2138,7 +2138,6 @@
"antigravity": "Gunakan apabila trafik Antigraviti/Kiro mesti dipintas melalui MITM dan dihalakan ke OmniRoute.",
"copilot": "Gunakan apabila anda mahu Copilot gaya sembang UX sambil menguatkuasakan kekunci OmniRoute dan peraturan penghalaan.",
"amp": "Use when you want Amp CLI routed through OmniRoute with stable shorthand aliases and OpenAI-compatible setup.",
- "qwen": "Use when you want Qwen Code CLI with OmniRoute-managed provider settings and multi-provider model access.",
"hermes": "Gunakan apabila anda memerlukan pembantu AI asli terminal yang ringan untuk tugasan pantas.",
"hermes-agent": "__MISSING__:Use when you need Hermes Agent (by Nousresearch) with default, delegation, vision and auxiliary models routed through OmniRoute.",
"custom": "Gunakan untuk pelaksanaan alat tersuai atau konfigurasi serasi OpenAI generik."
@@ -2157,7 +2156,6 @@
"kiro": "Amazon Kiro — AI-powered IDE",
"windsurf": "Windsurf AI Code Editor",
"copilot": "GitHub Copilot AI Assistant",
- "qwen": "Alibaba Qwen Code CLI",
"amp": "Sourcegraph Amp coding assistant CLI",
"hermes": "Pembantu Terminal AI Hermes",
"hermes-agent": "__MISSING__:Hermes Agent (by Nousresearch) - Advanced terminal AI with multi-model support (delegation, vision, compression, etc.)",
diff --git a/src/i18n/messages/nl.json b/src/i18n/messages/nl.json
index 5673f67845..6a28dee783 100644
--- a/src/i18n/messages/nl.json
+++ b/src/i18n/messages/nl.json
@@ -2138,7 +2138,6 @@
"antigravity": "Gebruik wanneer Antigravity/Kiro-verkeer moet worden onderschept via MITM en naar OmniRoute moet worden gerouteerd.",
"copilot": "Gebruik wanneer u UX in Copilot-chatstijl wilt terwijl u OmniRoute-sleutels en routeringsregels afdwingt.",
"amp": "Use when you want Amp CLI routed through OmniRoute with stable shorthand aliases and OpenAI-compatible setup.",
- "qwen": "Use when you want Qwen Code CLI with OmniRoute-managed provider settings and multi-provider model access.",
"hermes": "Gebruik deze wanneer u een lichtgewicht terminal-native AI-assistent nodig heeft voor snelle taken.",
"hermes-agent": "__MISSING__:Use when you need Hermes Agent (by Nousresearch) with default, delegation, vision and auxiliary models routed through OmniRoute.",
"custom": "Gebruik voor aangepaste toolimplementaties of generieke OpenAI-compatibele configuraties."
@@ -2157,7 +2156,6 @@
"kiro": "Amazon Kiro — AI-powered IDE",
"windsurf": "Windsurf AI Code Editor",
"copilot": "GitHub Copilot AI Assistant",
- "qwen": "Alibaba Qwen Code CLI",
"amp": "Sourcegraph Amp coding assistant CLI",
"hermes": "Hermes AI Terminalassistent",
"hermes-agent": "__MISSING__:Hermes Agent (by Nousresearch) - Advanced terminal AI with multi-model support (delegation, vision, compression, etc.)",
diff --git a/src/i18n/messages/no.json b/src/i18n/messages/no.json
index 7abd261bb6..850e95602a 100644
--- a/src/i18n/messages/no.json
+++ b/src/i18n/messages/no.json
@@ -2138,7 +2138,6 @@
"antigravity": "Brukes når Antigravity/Kiro-trafikk må avskjæres gjennom MITM og rutes til OmniRoute.",
"copilot": "Bruk når du vil ha Copilot chat-stil UX mens du håndhever OmniRoute-nøkler og rutingsregler.",
"amp": "Use when you want Amp CLI routed through OmniRoute with stable shorthand aliases and OpenAI-compatible setup.",
- "qwen": "Use when you want Qwen Code CLI with OmniRoute-managed provider settings and multi-provider model access.",
"hermes": "Bruk når du trenger en lett terminal-native AI-assistent for raske oppgaver.",
"hermes-agent": "__MISSING__:Use when you need Hermes Agent (by Nousresearch) with default, delegation, vision and auxiliary models routed through OmniRoute.",
"custom": "Bruk for tilpassede verktøyimplementeringer eller generiske OpenAI-kompatible konfigurasjoner."
@@ -2157,7 +2156,6 @@
"kiro": "Amazon Kiro — AI-powered IDE",
"windsurf": "Windsurf AI Code Editor",
"copilot": "GitHub Copilot AI Assistant",
- "qwen": "Alibaba Qwen Code CLI",
"amp": "Sourcegraph Amp coding assistant CLI",
"hermes": "Hermes AI terminalassistent",
"hermes-agent": "__MISSING__:Hermes Agent (by Nousresearch) - Advanced terminal AI with multi-model support (delegation, vision, compression, etc.)",
diff --git a/src/i18n/messages/phi.json b/src/i18n/messages/phi.json
index 528e1cdf47..f5c5cdf783 100644
--- a/src/i18n/messages/phi.json
+++ b/src/i18n/messages/phi.json
@@ -2138,7 +2138,6 @@
"antigravity": "Gamitin kapag ang trapiko ng Antigravity/Kiro ay dapat ma-intercept sa MITM at iruta sa OmniRoute.",
"copilot": "Gamitin kapag gusto mong Copilot chat style UX habang ipinapatupad ang mga OmniRoute key at mga panuntunan sa pagruruta.",
"amp": "Use when you want Amp CLI routed through OmniRoute with stable shorthand aliases and OpenAI-compatible setup.",
- "qwen": "Use when you want Qwen Code CLI with OmniRoute-managed provider settings and multi-provider model access.",
"hermes": "Gamitin kapag kailangan mo ng magaan na terminal-native AI assistant para sa mga mabilisang gawain.",
"hermes-agent": "__MISSING__:Use when you need Hermes Agent (by Nousresearch) with default, delegation, vision and auxiliary models routed through OmniRoute.",
"custom": "Gamitin para sa mga custom na pagpapatupad ng tool o generic na mga configuration na tugma sa OpenAI."
@@ -2157,7 +2156,6 @@
"kiro": "Amazon Kiro — AI-powered IDE",
"windsurf": "Windsurf AI Code Editor",
"copilot": "GitHub Copilot AI Assistant",
- "qwen": "Alibaba Qwen Code CLI",
"amp": "Sourcegraph Amp coding assistant CLI",
"hermes": "Hermes AI Terminal Assistant",
"hermes-agent": "__MISSING__:Hermes Agent (by Nousresearch) - Advanced terminal AI with multi-model support (delegation, vision, compression, etc.)",
diff --git a/src/i18n/messages/pl.json b/src/i18n/messages/pl.json
index e8f627b763..d837cf72aa 100644
--- a/src/i18n/messages/pl.json
+++ b/src/i18n/messages/pl.json
@@ -2160,7 +2160,6 @@
"antigravity": "Do stosowania, gdy ruch z Antigravity/Kiro musi być przechwytywany przez MITM i kierowany do OmniRoute.",
"copilot": "Do stosowania przy chęci korzystania z interfejsu w stylu Copilot chat z jednoczesnym wymuszaniem kluczy i reguł routingu OmniRoute.",
"amp": "Do stosowania przy chęci korzystania ze skróconych procesów roboczych Amp z zachowaniem wymuszania aliasów i reguł routingu OmniRoute.",
- "qwen": "Do stosowania, gdy potrzebne jest Alibaba Qwen Code CLI do zadań związanych z kodowaniem.",
"hermes": "Do stosowania, gdy potrzebny jest lekki asystent AI działający w terminalu do szybkich zadań.",
"hermes-agent": "Do stosowania, gdy potrzebny jest Hermes Agent (od Nousresearch) z models domyślnymi, delegowanymi, wizyjnymi i pomocniczymi kierowanymi przez OmniRoute.",
"custom": "Do stosowania przy niestandardowych implementacjach narzędzi lub ogólnych konfiguracjach zgodnych z OpenAI."
@@ -2179,7 +2178,6 @@
"kiro": "Amazon Kiro — IDE oparte na AI",
"windsurf": "Edytor kodu Windsurf AI",
"copilot": "Asystent AI GitHub Copilot",
- "qwen": "Alibaba Qwen Code CLI",
"amp": "CLI asystenta kodowania Sourcegraph Amp",
"hermes": "Asystent terminalowy AI Hermes",
"hermes-agent": "Hermes Agent (od Nousresearch) - zaawansowana terminalowa AI ze wsparciem dla wielu models (delegowanie, wizja, kompresja itp.)",
diff --git a/src/i18n/messages/pt-BR.json b/src/i18n/messages/pt-BR.json
index 681bdcbdff..4f3885fe17 100644
--- a/src/i18n/messages/pt-BR.json
+++ b/src/i18n/messages/pt-BR.json
@@ -2150,7 +2150,6 @@
"antigravity": "Use quando o tráfego Antigravity/Kiro deve ser interceptado através do MITM e roteado para OmniRoute.",
"copilot": "Use quando desejar UX no estilo de bate-papo do Copilot enquanto impõe chaves OmniRoute e regras de roteamento.",
"amp": "Use when you want Amp CLI routed through OmniRoute with stable shorthand aliases and OpenAI-compatible setup.",
- "qwen": "Use quando precisar do Alibaba Qwen Code CLI para tarefas de programação.",
"hermes": "Use quando precisar de um assistente leve de IA nativo do terminal para tarefas rápidas.",
"hermes-agent": "Use quando precisar do Hermes Agent (pela Nousresearch) com modelos padrão, delegação, visão e auxiliares roteados pelo OmniRoute.",
"custom": "Use quando seu CLI ou SDK não estiver hardcoded no OmniRoute, mas ainda aceitar base URL, chave de API e model string compatíveis com OpenAI."
@@ -2169,7 +2168,6 @@
"kiro": "Amazon Kiro — IDE com IA",
"windsurf": "Windsurf — Editor de Código com IA",
"copilot": "GitHub Copilot — Assistente de IA",
- "qwen": "Alibaba Qwen Code CLI",
"amp": "CLI do assistente de codificação Sourcegraph Amp",
"hermes": "Assistente de Terminal Hermes AI",
"hermes-agent": "Hermes Agent (by Nousresearch) - IA de terminal avançada com suporte multi-modelo (delegação, visão, compressão, etc.)",
@@ -4626,7 +4624,7 @@
"blackboxWebCookiePlaceholder": "Cole o valor de __Secure-authjs.session-token",
"t3ChatWebCookieHint": "Abra t3.chat → DevTools → Aplicativo → Armazenamento local → https://t3.chat, copie 'convex-session-id'. Em seguida, abra DevTools → Rede, copie o cabeçalho completo do cookie de qualquer solicitação de chat. Cole os dois valores nos campos abaixo.",
"t3ChatWebCookiePlaceholder": "id-de-sessão-convexa=abc123...",
- "grokWebCookieHint": "O cookie cf_clearance do grok.com é vinculado ao IP, ao User-Agent e à impressão digital TLS do navegador de onde você o copiou — colá-lo de outra máquina/IP causa um erro 403. Cole sso e sso-rw aqui e depois abra Configurações Avançadas e preencha o Custom User-Agent com o valor EXATO do User-Agent desse mesmo navegador, usando o mesmo IP/proxy para esta conexão.",
+ "grokWebCookieHint": "Cole o cookie sso do grok.com. Um valor completo `sso=...` também funciona.",
"blockClaudeExtraUsageDescription": "Quando habilitado, o OmniRoute marca esta conta do Claude Code como indisponível assim que a API de usage reporta `extra_usage.queued`, para que o fallback troque para outra conta antes de continuar com cobranças extras pay-as-you-go.",
"blockClaudeExtraUsageLabel": "Bloquear Claude Extra Usage",
"disableCoolingDescription": "__MISSING__:Skip the transient cooldown so this connection stays eligible even after recoverable errors (terminal states like banned/expired still apply).",
@@ -4704,7 +4702,6 @@
"tierFast": "Rápido",
"antigravityProjectIdLabel": "ID do projeto do Google Cloud",
"antigravityProjectIdPlaceholder": "meu-gcp-project-id",
- "grokWebCookieHint": "Cole o cookie sso do grok.com. Um valor completo `sso=...` também funciona.",
"grokWebCookiePlaceholder": "Cole o valor do cookie sso do grok.com",
"herokuBaseUrlHint": "Obrigatório: cole a URL base do Heroku Inference. O app adicionará /v1/chat/completions.",
"hideEmail": "Ocultar e-mail",
diff --git a/src/i18n/messages/pt.json b/src/i18n/messages/pt.json
index ccbf7bedc4..8e7ed4867d 100644
--- a/src/i18n/messages/pt.json
+++ b/src/i18n/messages/pt.json
@@ -2138,7 +2138,6 @@
"antigravity": "Use quando o tráfego Antigravity/Kiro deve ser interceptado através do MITM e roteado para OmniRoute.",
"copilot": "Use quando desejar UX no estilo de bate-papo do Copilot enquanto impõe chaves OmniRoute e regras de roteamento.",
"amp": "Use when you want Amp CLI routed through OmniRoute with stable shorthand aliases and OpenAI-compatible setup.",
- "qwen": "Use when you want Qwen Code CLI with OmniRoute-managed provider settings and multi-provider model access.",
"hermes": "Use quando precisar de um assistente leve de IA nativo do terminal para tarefas rápidas.",
"hermes-agent": "__MISSING__:Use when you need Hermes Agent (by Nousresearch) with default, delegation, vision and auxiliary models routed through OmniRoute.",
"custom": "Use para implementações de ferramentas personalizadas ou configurações genéricas compatíveis com OpenAI."
@@ -2157,7 +2156,6 @@
"kiro": "Amazon Kiro — AI-powered IDE",
"windsurf": "Windsurf AI Code Editor",
"copilot": "GitHub Copilot AI Assistant",
- "qwen": "Alibaba Qwen Code CLI",
"amp": "Sourcegraph Amp coding assistant CLI",
"hermes": "Assistente de Terminal Hermes AI",
"hermes-agent": "__MISSING__:Hermes Agent (by Nousresearch) - Advanced terminal AI with multi-model support (delegation, vision, compression, etc.)",
diff --git a/src/i18n/messages/ro.json b/src/i18n/messages/ro.json
index 7bf4085880..677ce754ee 100644
--- a/src/i18n/messages/ro.json
+++ b/src/i18n/messages/ro.json
@@ -2138,7 +2138,6 @@
"antigravity": "Utilizați atunci când traficul Antigravity/Kiro trebuie interceptat prin MITM și direcționat către OmniRoute.",
"copilot": "Utilizați atunci când doriți UX în stilul de chat Copilot, în timp ce aplicați cheile și regulile de rutare OmniRoute.",
"amp": "Use when you want Amp CLI routed through OmniRoute with stable shorthand aliases and OpenAI-compatible setup.",
- "qwen": "Use when you want Qwen Code CLI with OmniRoute-managed provider settings and multi-provider model access.",
"hermes": "Utilizați atunci când aveți nevoie de un asistent AI nativ pentru terminale ușor pentru sarcini rapide.",
"hermes-agent": "__MISSING__:Use when you need Hermes Agent (by Nousresearch) with default, delegation, vision and auxiliary models routed through OmniRoute.",
"custom": "Utilizați pentru implementări de instrumente personalizate sau configurații generice compatibile cu OpenAI."
@@ -2157,7 +2156,6 @@
"kiro": "Amazon Kiro — AI-powered IDE",
"windsurf": "Windsurf AI Code Editor",
"copilot": "GitHub Copilot AI Assistant",
- "qwen": "Alibaba Qwen Code CLI",
"amp": "Sourcegraph Amp coding assistant CLI",
"hermes": "Hermes AI Terminal Assistant",
"hermes-agent": "__MISSING__:Hermes Agent (by Nousresearch) - Advanced terminal AI with multi-model support (delegation, vision, compression, etc.)",
diff --git a/src/i18n/messages/ru.json b/src/i18n/messages/ru.json
index e99fede1b9..7af8bf8c8b 100644
--- a/src/i18n/messages/ru.json
+++ b/src/i18n/messages/ru.json
@@ -2138,7 +2138,6 @@
"antigravity": "Используйте, когда трафик Антигравитации/Киро необходимо перехватить через MITM и направить в OmniRoute.",
"copilot": "Используйте его, если вам нужен пользовательский интерфейс в стиле чата Copilot, одновременно обеспечивая соблюдение ключей OmniRoute и правил маршрутизации.",
"amp": "Use when you want Amp CLI routed through OmniRoute with stable shorthand aliases and OpenAI-compatible setup.",
- "qwen": "Use when you want Qwen Code CLI with OmniRoute-managed provider settings and multi-provider model access.",
"hermes": "Используйте, когда вам нужен легкий помощник с искусственным интеллектом, встроенный в терминал, для быстрых задач.",
"hermes-agent": "Используйте, когда вам нужен Hermes Agent (от Nousresearch) с моделями по умолчанию, делегирования, видения и вспомогательными, маршрутизированными через OmniRoute.",
"custom": "Используйте для пользовательских реализаций инструментов или общих конфигураций, совместимых с OpenAI."
@@ -2157,7 +2156,6 @@
"kiro": "Amazon Kiro - IDE с ИИ",
"windsurf": "Редактор кода Windsurf с ИИ",
"copilot": "Ассистент GitHub Copilot с ИИ",
- "qwen": "Alibaba Qwen Code CLI",
"amp": "Sourcegraph Amp coding assistant CLI",
"hermes": "Помощник по терминалу Hermes AI",
"hermes-agent": "Агент Hermes (от Nousresearch) - Продвинутый терминальный ИИ с поддержкой нескольких моделей (делегирование, зрение, сжатие и т.д.)",
diff --git a/src/i18n/messages/sk.json b/src/i18n/messages/sk.json
index 9da4850ac4..2cdebbf63d 100644
--- a/src/i18n/messages/sk.json
+++ b/src/i18n/messages/sk.json
@@ -2138,7 +2138,6 @@
"antigravity": "Použite, keď musí byť premávka Antigravity/Kiro zachytená cez MITM a nasmerovaná na OmniRoute.",
"copilot": "Použite, keď chcete UX v štýle chatu Copilot pri presadzovaní kľúčov OmniRoute a pravidiel smerovania.",
"amp": "Use when you want Amp CLI routed through OmniRoute with stable shorthand aliases and OpenAI-compatible setup.",
- "qwen": "Use when you want Qwen Code CLI with OmniRoute-managed provider settings and multi-provider model access.",
"hermes": "Použite, keď potrebujete ľahkého terminálového natívneho asistenta AI na rýchle úlohy.",
"hermes-agent": "__MISSING__:Use when you need Hermes Agent (by Nousresearch) with default, delegation, vision and auxiliary models routed through OmniRoute.",
"custom": "Používa sa na implementácie vlastných nástrojov alebo všeobecné konfigurácie kompatibilné s OpenAI."
@@ -2157,7 +2156,6 @@
"kiro": "Amazon Kiro — AI-powered IDE",
"windsurf": "Windsurf AI Code Editor",
"copilot": "GitHub Copilot AI Assistant",
- "qwen": "Alibaba Qwen Code CLI",
"amp": "Sourcegraph Amp coding assistant CLI",
"hermes": "Hermes AI Terminal Assistant",
"hermes-agent": "__MISSING__:Hermes Agent (by Nousresearch) - Advanced terminal AI with multi-model support (delegation, vision, compression, etc.)",
diff --git a/src/i18n/messages/sv.json b/src/i18n/messages/sv.json
index acf8b1f987..53db317e67 100644
--- a/src/i18n/messages/sv.json
+++ b/src/i18n/messages/sv.json
@@ -2138,7 +2138,6 @@
"antigravity": "Använd när Antigravity/Kiro-trafik måste avlyssnas genom MITM och dirigeras till OmniRoute.",
"copilot": "Använd när du vill ha Copilot chattstil UX samtidigt som du upprätthåller OmniRoute-nycklar och routingregler.",
"amp": "Use when you want Amp CLI routed through OmniRoute with stable shorthand aliases and OpenAI-compatible setup.",
- "qwen": "Use when you want Qwen Code CLI with OmniRoute-managed provider settings and multi-provider model access.",
"hermes": "Använd när du behöver en lätt terminalbaserad AI-assistent för snabba uppgifter.",
"hermes-agent": "__MISSING__:Use when you need Hermes Agent (by Nousresearch) with default, delegation, vision and auxiliary models routed through OmniRoute.",
"custom": "Använd för anpassade verktygsimplementeringar eller generiska OpenAI-kompatibla konfigurationer."
@@ -2157,7 +2156,6 @@
"kiro": "Amazon Kiro — AI-powered IDE",
"windsurf": "Windsurf AI Code Editor",
"copilot": "GitHub Copilot AI Assistant",
- "qwen": "Alibaba Qwen Code CLI",
"amp": "Sourcegraph Amp coding assistant CLI",
"hermes": "Hermes AI terminalassistent",
"hermes-agent": "__MISSING__:Hermes Agent (by Nousresearch) - Advanced terminal AI with multi-model support (delegation, vision, compression, etc.)",
diff --git a/src/i18n/messages/sw.json b/src/i18n/messages/sw.json
index 8aa69e8778..dd436184ab 100644
--- a/src/i18n/messages/sw.json
+++ b/src/i18n/messages/sw.json
@@ -2036,7 +2036,6 @@
"antigravity": "Use when Antigravity/Kiro traffic must be intercepted through MITM and routed to OmniRoute.",
"copilot": "Use when you want Copilot chat style UX while enforcing OmniRoute keys and routing rules.",
"amp": "Use when you want Amp shorthand workflows but still need OmniRoute alias and routing rules enforcement.",
- "qwen": "Use when you need Alibaba Qwen Code CLI for coding tasks.",
"hermes": "Use when you need a lightweight terminal-native AI assistant for quick tasks.",
"hermes-agent": "__MISSING__:Use when you need Hermes Agent (by Nousresearch) with default, delegation, vision and auxiliary models routed through OmniRoute.",
"custom": "Use for custom tool implementations or generic OpenAI-compatible configurations."
@@ -2055,7 +2054,6 @@
"kiro": "Amazon Kiro — AI-powered IDE",
"windsurf": "Windsurf AI Code Editor",
"copilot": "GitHub Copilot AI Assistant",
- "qwen": "Alibaba Qwen Code CLI",
"amp": "Sourcegraph Amp coding assistant CLI",
"hermes": "Hermes AI Terminal Assistant",
"hermes-agent": "__MISSING__:Hermes Agent (by Nousresearch) - Advanced terminal AI with multi-model support (delegation, vision, compression, etc.)",
diff --git a/src/i18n/messages/ta.json b/src/i18n/messages/ta.json
index 4454249088..27169432d5 100644
--- a/src/i18n/messages/ta.json
+++ b/src/i18n/messages/ta.json
@@ -2036,7 +2036,6 @@
"antigravity": "Use when Antigravity/Kiro traffic must be intercepted through MITM and routed to OmniRoute.",
"copilot": "Use when you want Copilot chat style UX while enforcing OmniRoute keys and routing rules.",
"amp": "Use when you want Amp shorthand workflows but still need OmniRoute alias and routing rules enforcement.",
- "qwen": "Use when you need Alibaba Qwen Code CLI for coding tasks.",
"hermes": "Use when you need a lightweight terminal-native AI assistant for quick tasks.",
"hermes-agent": "__MISSING__:Use when you need Hermes Agent (by Nousresearch) with default, delegation, vision and auxiliary models routed through OmniRoute.",
"custom": "Use for custom tool implementations or generic OpenAI-compatible configurations."
@@ -2055,7 +2054,6 @@
"kiro": "Amazon Kiro — AI-powered IDE",
"windsurf": "Windsurf AI Code Editor",
"copilot": "GitHub Copilot AI Assistant",
- "qwen": "Alibaba Qwen Code CLI",
"amp": "Sourcegraph Amp coding assistant CLI",
"hermes": "Hermes AI Terminal Assistant",
"hermes-agent": "__MISSING__:Hermes Agent (by Nousresearch) - Advanced terminal AI with multi-model support (delegation, vision, compression, etc.)",
diff --git a/src/i18n/messages/te.json b/src/i18n/messages/te.json
index 4a5c40b857..06afe68594 100644
--- a/src/i18n/messages/te.json
+++ b/src/i18n/messages/te.json
@@ -2036,7 +2036,6 @@
"antigravity": "Use when Antigravity/Kiro traffic must be intercepted through MITM and routed to OmniRoute.",
"copilot": "Use when you want Copilot chat style UX while enforcing OmniRoute keys and routing rules.",
"amp": "Use when you want Amp shorthand workflows but still need OmniRoute alias and routing rules enforcement.",
- "qwen": "Use when you need Alibaba Qwen Code CLI for coding tasks.",
"hermes": "Use when you need a lightweight terminal-native AI assistant for quick tasks.",
"hermes-agent": "__MISSING__:Use when you need Hermes Agent (by Nousresearch) with default, delegation, vision and auxiliary models routed through OmniRoute.",
"custom": "Use for custom tool implementations or generic OpenAI-compatible configurations."
@@ -2055,7 +2054,6 @@
"kiro": "Amazon Kiro — AI-powered IDE",
"windsurf": "Windsurf AI Code Editor",
"copilot": "GitHub Copilot AI Assistant",
- "qwen": "Alibaba Qwen Code CLI",
"amp": "Sourcegraph Amp coding assistant CLI",
"hermes": "Hermes AI Terminal Assistant",
"hermes-agent": "__MISSING__:Hermes Agent (by Nousresearch) - Advanced terminal AI with multi-model support (delegation, vision, compression, etc.)",
diff --git a/src/i18n/messages/th.json b/src/i18n/messages/th.json
index 6f8d562daa..43aeb6eeef 100644
--- a/src/i18n/messages/th.json
+++ b/src/i18n/messages/th.json
@@ -2036,7 +2036,6 @@
"antigravity": "ใช้เมื่อต้องสกัดกั้นการรับส่งข้อมูล Antigravity/Kiro ผ่าน MITM และกำหนดเส้นทางไปยัง OmniRoute",
"copilot": "ใช้เมื่อคุณต้องการ UX รูปแบบการแชทของ Copilot ในขณะที่บังคับใช้คีย์ OmniRoute และกฎการกำหนดเส้นทาง",
"amp": "Use when you want Amp CLI routed through OmniRoute with stable shorthand aliases and OpenAI-compatible setup.",
- "qwen": "Use when you want Qwen Code CLI with OmniRoute-managed provider settings and multi-provider model access.",
"hermes": "ใช้เมื่อคุณต้องการผู้ช่วย AI แบบเนทิฟเทอร์มินัลน้ำหนักเบาสำหรับงานที่รวดเร็ว",
"hermes-agent": "__MISSING__:Use when you need Hermes Agent (by Nousresearch) with default, delegation, vision and auxiliary models routed through OmniRoute.",
"custom": "ใช้สำหรับการใช้เครื่องมือแบบกำหนดเองหรือการกำหนดค่าทั่วไปที่เข้ากันได้กับ OpenAI"
@@ -2055,7 +2054,6 @@
"kiro": "Amazon Kiro — AI-powered IDE",
"windsurf": "Windsurf AI Code Editor",
"copilot": "GitHub Copilot AI Assistant",
- "qwen": "Alibaba Qwen Code CLI",
"amp": "Sourcegraph Amp coding assistant CLI",
"hermes": "ผู้ช่วยเทอร์มินัล Hermes AI",
"hermes-agent": "__MISSING__:Hermes Agent (by Nousresearch) - Advanced terminal AI with multi-model support (delegation, vision, compression, etc.)",
diff --git a/src/i18n/messages/tr.json b/src/i18n/messages/tr.json
index 7e6e39894b..9b12c2308c 100644
--- a/src/i18n/messages/tr.json
+++ b/src/i18n/messages/tr.json
@@ -2036,7 +2036,6 @@
"antigravity": "Antigravity/Kiro trafiğinin MITM üzerinden yakalanıp OmniRoute'a yönlendirilmesi gerektiğinde kullanın.",
"copilot": "OmniRoute anahtarları ve yönlendirme kuralları uygulanırken Copilot sohbet tarzı bir UX istediğinizde kullanın.",
"amp": "Use when you want Amp CLI routed through OmniRoute with stable shorthand aliases and OpenAI-compatible setup.",
- "qwen": "Use when you want Qwen Code CLI with OmniRoute-managed provider settings and multi-provider model access.",
"hermes": "Hızlı görevler için hafif, terminalde yerleşik bir yapay zeka asistanına ihtiyaç duyduğunuzda kullanın.",
"hermes-agent": "__MISSING__:Use when you need Hermes Agent (by Nousresearch) with default, delegation, vision and auxiliary models routed through OmniRoute.",
"custom": "Özel araç uygulamaları veya genel OpenAI uyumlu yapılandırmalar için kullanın."
@@ -2055,7 +2054,6 @@
"kiro": "Amazon Kiro - AI destekli IDE",
"windsurf": "Windsurf AI Code Editor",
"copilot": "GitHub Copilot AI Assistant",
- "qwen": "Alibaba Qwen Code CLI",
"amp": "Sourcegraph Amp coding assistant CLI",
"hermes": "Hermes AI Terminal Asistanı",
"hermes-agent": "__MISSING__:Hermes Agent (by Nousresearch) - Advanced terminal AI with multi-model support (delegation, vision, compression, etc.)",
diff --git a/src/i18n/messages/uk-UA.json b/src/i18n/messages/uk-UA.json
index 15067d282d..d43c3da21f 100644
--- a/src/i18n/messages/uk-UA.json
+++ b/src/i18n/messages/uk-UA.json
@@ -2036,7 +2036,6 @@
"antigravity": "Використовуйте, коли трафік Antigravity/Kiro потрібно перехопити через MITM і направити на OmniRoute.",
"copilot": "Використовуйте, коли вам потрібен UX у стилі чату Copilot із застосуванням ключів OmniRoute і правил маршрутизації.",
"amp": "Use when you want Amp CLI routed through OmniRoute with stable shorthand aliases and OpenAI-compatible setup.",
- "qwen": "Use when you want Qwen Code CLI with OmniRoute-managed provider settings and multi-provider model access.",
"hermes": "Використовуйте, коли вам потрібен легкий власний помічник зі штучним інтелектом для швидких завдань.",
"hermes-agent": "Використовуйте, коли потрібен Hermes Agent (від Nousresearch) із моделями за замовчуванням, делегування, vision та допоміжними, маршрутизованими через OmniRoute.",
"custom": "Використовуйте для реалізації спеціальних інструментів або загальних конфігурацій, сумісних з OpenAI."
@@ -2055,7 +2054,6 @@
"kiro": "Amazon Kiro — AI-powered IDE",
"windsurf": "Windsurf AI Code Editor",
"copilot": "GitHub Copilot AI Assistant",
- "qwen": "Alibaba Qwen Code CLI",
"amp": "Sourcegraph Amp coding assistant CLI",
"hermes": "Hermes AI Terminal Assistant",
"hermes-agent": "Hermes Agent (від Nousresearch) — просунутий термінальний AI з підтримкою мультимоделей (делегування, vision, стиснення тощо)",
diff --git a/src/i18n/messages/ur.json b/src/i18n/messages/ur.json
index d7d0420c38..3877b99e47 100644
--- a/src/i18n/messages/ur.json
+++ b/src/i18n/messages/ur.json
@@ -2036,7 +2036,6 @@
"antigravity": "Use when Antigravity/Kiro traffic must be intercepted through MITM and routed to OmniRoute.",
"copilot": "Use when you want Copilot chat style UX while enforcing OmniRoute keys and routing rules.",
"amp": "Use when you want Amp shorthand workflows but still need OmniRoute alias and routing rules enforcement.",
- "qwen": "Use when you need Alibaba Qwen Code CLI for coding tasks.",
"hermes": "Use when you need a lightweight terminal-native AI assistant for quick tasks.",
"hermes-agent": "__MISSING__:Use when you need Hermes Agent (by Nousresearch) with default, delegation, vision and auxiliary models routed through OmniRoute.",
"custom": "Use for custom tool implementations or generic OpenAI-compatible configurations."
@@ -2055,7 +2054,6 @@
"kiro": "Amazon Kiro — AI-powered IDE",
"windsurf": "Windsurf AI Code Editor",
"copilot": "GitHub Copilot AI Assistant",
- "qwen": "Alibaba Qwen Code CLI",
"amp": "Sourcegraph Amp coding assistant CLI",
"hermes": "Hermes AI Terminal Assistant",
"hermes-agent": "__MISSING__:Hermes Agent (by Nousresearch) - Advanced terminal AI with multi-model support (delegation, vision, compression, etc.)",
diff --git a/src/i18n/messages/vi.json b/src/i18n/messages/vi.json
index 731dfc932d..0969bab3a2 100644
--- a/src/i18n/messages/vi.json
+++ b/src/i18n/messages/vi.json
@@ -2183,7 +2183,6 @@
"antigravity": "Sử dụng khi lưu lượng truy cập Antigravity/Kiro cần được chặn bắt qua MITM và định tuyến đến OmniRoute.",
"copilot": "Sử dụng khi bạn muốn trải nghiệm trò chuyện kiểu Copilot đồng thời áp dụng các khóa OmniRoute và quy tắc định tuyến.",
"amp": "Sử dụng khi bạn muốn các quy trình làm việc viết tắt của Amp nhưng vẫn cần áp dụng bí danh và quy tắc định tuyến của OmniRoute.",
- "qwen": "Sử dụng khi bạn cần Alibaba Qwen Code CLI cho các tác vụ lập trình.",
"hermes": "Sử dụng khi bạn cần một trợ lý AI gọn nhẹ hoạt động trực tiếp trong terminal cho các tác vụ nhanh.",
"hermes-agent": "Sử dụng khi bạn cần Hermes Agent (do Nousresearch phát triển) với các mô hình mặc định, ủy quyền, xử lý hình ảnh và phụ trợ được định tuyến qua OmniRoute.",
"custom": "Sử dụng để triển khai công cụ tùy chỉnh hoặc cấu hình chung tương thích với OpenAI."
@@ -2202,7 +2201,6 @@
"kiro": "Amazon Kiro — IDE tích hợp AI",
"windsurf": "Trình chỉnh sửa mã AI Windsurf",
"copilot": "Trợ lý AI GitHub Copilot",
- "qwen": "Alibaba Qwen Code CLI",
"amp": "CLI trợ lý lập trình Sourcegraph Amp",
"hermes": "Trợ lý AI terminal Hermes",
"hermes-agent": "Hermes Agent (bởi Nousresearch) - AI nâng cao cho terminal, hỗ trợ đa mô hình (ủy quyền, thị giác, nén, v.v.)",
diff --git a/src/i18n/messages/zh-CN.json b/src/i18n/messages/zh-CN.json
index 9937b35435..fc9015ba13 100644
--- a/src/i18n/messages/zh-CN.json
+++ b/src/i18n/messages/zh-CN.json
@@ -2121,7 +2121,6 @@
"antigravity": "当必须通过 MITM 拦截 Antigravity/Kiro 流量并将其路由到 OmniRoute 时使用。",
"copilot": "当您想要 Copilot 聊天风格的 UX 同时强制执行 OmniRoute 键和路由规则时使用。",
"amp": "当您想要 Amp 简写工作流,但仍需要 OmniRoute 别名和路由规则支持时使用。",
- "qwen": "当您需要使用阿里云 Qwen Code CLI 进行编码任务时使用。",
"hermes": "当您需要轻量级终端原生 AI 助手来处理快速任务时使用。",
"hermes-agent": "需要使用 Hermes Agent (by Nousresearch) 时使用,默认、委派、视觉与辅助模型均通过 OmniRoute 路由。",
"custom": "用于自定义工具实现或通用 OpenAI 兼容配置。"
@@ -2140,7 +2139,6 @@
"kiro": "Amazon Kiro - AI 驱动 IDE",
"windsurf": "Windsurf AI Code Editor",
"copilot": "GitHub Copilot AI Assistant",
- "qwen": "Alibaba Qwen Code CLI",
"amp": "Sourcegraph Amp 编程助手 CLI",
"hermes": "Hermes AI 终端助手",
"hermes-agent": "Hermes Agent (by Nousresearch) — 支持多模型(委派、视觉、压缩等)的高级终端 AI。",
diff --git a/src/i18n/messages/zh-TW.json b/src/i18n/messages/zh-TW.json
index cc261b9189..e2fe1c31c3 100644
--- a/src/i18n/messages/zh-TW.json
+++ b/src/i18n/messages/zh-TW.json
@@ -2144,7 +2144,6 @@
"antigravity": "當必須通過 MITM 攔截 Antigravity/Kiro 流量並將其路由到 OmniRoute 時使用。",
"copilot": "當您想要 Copilot 聊天風格的 UX 同時強制執行 OmniRoute 鍵和路由規則時使用。",
"amp": "當您想要 Amp 簡寫工作流,但仍需要 OmniRoute 別名和路由規則支援時使用。",
- "qwen": "當您需要使用阿里雲 Qwen Code CLI 進行編碼任務時使用。",
"hermes": "當您需要輕量級終端原生 AI 助手來處理快速任務時使用。",
"hermes-agent": "需要使用 Hermes Agent (by Nousresearch) 時使用,預設、委派、視覺與輔助模型均通過 OmniRoute 路由。",
"custom": "用於自定義工具實現或通用 OpenAI 相容配置。"
@@ -2163,7 +2162,6 @@
"kiro": "Amazon Kiro - AI 驅動 IDE",
"windsurf": "Windsurf AI Code Editor",
"copilot": "GitHub Copilot AI Assistant",
- "qwen": "Alibaba Qwen Code CLI",
"amp": "Sourcegraph Amp 程式設計助手 CLI",
"hermes": "Hermes AI 終端助手",
"hermes-agent": "Hermes Agent (by Nousresearch) — 支援多模型(委派、視覺、壓縮等)的高階終端 AI。",
diff --git a/src/lib/acp/registry.ts b/src/lib/acp/registry.ts
index df3a2134cd..6945762409 100644
--- a/src/lib/acp/registry.ts
+++ b/src/lib/acp/registry.ts
@@ -5,7 +5,7 @@
* and running version commands. Used to offer ACP transport as an alternative
* to the HTTP proxy method.
*
- * Supports 14 built-in agents + user-defined custom agents from settings.
+ * Supports built-in agents + user-defined custom agents from settings.
*
* Reference: https://github.com/iOfficeAI/AionUi (auto-detects CLI agents)
*/
@@ -115,12 +115,12 @@ const AGENT_DEFINITIONS: Omit[] = [
protocol: "stdio",
},
{
- id: "qwen-code",
+ id: "qwen",
name: "Qwen Code",
binary: "qwen",
versionCommand: "qwen --version",
- providerAlias: "qwen",
- spawnArgs: [],
+ providerAlias: "qwen-code",
+ spawnArgs: ["--acp"],
protocol: "stdio",
},
{
diff --git a/src/lib/arenaEloSync.ts b/src/lib/arenaEloSync.ts
index 1e24148716..75584296b0 100644
--- a/src/lib/arenaEloSync.ts
+++ b/src/lib/arenaEloSync.ts
@@ -150,8 +150,8 @@ const MODEL_ALIAS_MAP: Record = {
"gpt-5.5": ["openai/gpt-5.5", "gpt-5"],
"gemini-3-flash": ["google/gemini-3-flash", "gemini-flash"],
"deepseek-r1": ["deepseek/deepseek-r1", "if/deepseek-r1"],
- "kimi-k2-thinking": ["moonshot/kimi-k2", "qw/kimi-k2"],
- "qwen3-coder-plus": ["qw/qwen3-coder-plus", "alibaba/qwen3-coder"],
+ "kimi-k2-thinking": ["moonshot/kimi-k2"],
+ "qwen3-coder-plus": ["alibaba/qwen3-coder"],
"llama-4": ["meta/llama-4", "llama4"],
};
diff --git a/src/lib/cliTools/checkToolConfigStatus.ts b/src/lib/cliTools/checkToolConfigStatus.ts
index 482c1a45c6..75fcc5237a 100644
--- a/src/lib/cliTools/checkToolConfigStatus.ts
+++ b/src/lib/cliTools/checkToolConfigStatus.ts
@@ -2,6 +2,7 @@
import fs from "fs/promises";
import { getCliPrimaryConfigPath } from "@/shared/services/cliRuntime";
+import { hasOmniRouteQwenCodeConfig } from "@/shared/services/qwenCodeConfig";
import { getRuntimePorts } from "@/lib/runtime/ports";
const { apiPort } = getRuntimePorts();
@@ -67,17 +68,8 @@ export async function checkToolConfigStatus(
return (config?.env as Record)?.ANTHROPIC_BASE_URL
? "configured"
: "not_configured";
- case "qwen": {
- // Check modelProviders for OmniRoute entries
- const mp = config?.modelProviders;
- if (!mp) return "not_configured";
- const qwenConfigStr = JSON.stringify(mp).toLowerCase();
- return qwenConfigStr.includes("omniroute") ||
- qwenConfigStr.includes(`localhost:${apiPort}`) ||
- qwenConfigStr.includes(`127.0.0.1:${apiPort}`)
- ? "configured"
- : "not_configured";
- }
+ case "qwen":
+ return hasOmniRouteQwenCodeConfig(config) ? "configured" : "not_configured";
case "droid":
case "openclaw":
case "cline":
diff --git a/src/lib/copilot/engine.ts b/src/lib/copilot/engine.ts
index 17d7122702..2f2366cfe6 100644
--- a/src/lib/copilot/engine.ts
+++ b/src/lib/copilot/engine.ts
@@ -86,7 +86,7 @@ Use \`createCombo\` tool or \`runOmniRouteCli\` to create them.`;
return `## Providers (212+)
OmniRoute supports 212+ providers across categories:
-- **Free**: Qoder AI, Qwen Code, Kiro AI
+- **Free**: Qoder AI, Kiro AI
- **OAuth** (14): Claude Code, Antigravity, Codex, GitHub Copilot, Cursor, Kimi Coding, Windsurf, etc.
- **API Key** (120+): OpenAI, Anthropic, Gemini, DeepSeek, Groq, xAI, Mistral, etc.
- **Self-Hosted** (8+): LM Studio, vLLM, Ollama, Triton, etc.
diff --git a/src/lib/copilot/systemPrompt.ts b/src/lib/copilot/systemPrompt.ts
index 99a8c4f18f..442e779394 100644
--- a/src/lib/copilot/systemPrompt.ts
+++ b/src/lib/copilot/systemPrompt.ts
@@ -80,7 +80,7 @@ Client → API Route (/v1/chat/completions)
### 3.1 Providers (212+)
Registered in src/shared/constants/providers.ts. Categories:
-- **Free** (3): Qoder AI, Qwen Code, Kiro AI
+- **Free** (2): Qoder AI, Kiro AI
- **OAuth** (14): Claude Code, Antigravity, Codex, GitHub Copilot, Cursor, etc.
- **API Key** (120+): OpenAI, Anthropic, Gemini, DeepSeek, Groq, xAI, etc.
- **Self-Hosted** (8+): LM Studio, vLLM, Ollama, etc.
diff --git a/src/lib/db/migrations/130_remove_unregistered_qwen_data.sql b/src/lib/db/migrations/130_remove_unregistered_qwen_data.sql
new file mode 100644
index 0000000000..f32f694b6f
--- /dev/null
+++ b/src/lib/db/migrations/130_remove_unregistered_qwen_data.sql
@@ -0,0 +1,8 @@
+-- Remove saved state for a provider ID that is no longer registered.
+
+DELETE FROM provider_connections
+WHERE provider = 'qwen';
+
+DELETE FROM key_value
+WHERE namespace IN ('cliToolLastConfig', 'cliToolInitialConfig')
+ AND key = 'qwen';
diff --git a/src/lib/oauth/constants/oauth.ts b/src/lib/oauth/constants/oauth.ts
index 4db9ef2f60..da06437827 100644
--- a/src/lib/oauth/constants/oauth.ts
+++ b/src/lib/oauth/constants/oauth.ts
@@ -71,17 +71,6 @@ export const CODEX_CONFIG = {
},
};
-// Qwen OAuth Configuration (Device Code Flow with PKCE)
-export const QWEN_CONFIG = {
- clientId: resolvePublicCred("qwen_id", "QWEN_OAUTH_CLIENT_ID"),
- // Host is chat.qwen.ai — the bare qwen.ai host 404s on these paths (verified: the qwen-code
- // device flow lives at chat.qwen.ai, returning a valid device_code; qwen.ai returns Not Found).
- deviceCodeUrl: "https://chat.qwen.ai/api/v1/oauth2/device/code",
- tokenUrl: "https://chat.qwen.ai/api/v1/oauth2/token",
- scope: "openid profile email model.completion",
- codeChallengeMethod: "S256",
-};
-
// Qoder OAuth Configuration (Authorization Code)
const QODER_OAUTH_AUTHORIZE_URL = process.env.QODER_OAUTH_AUTHORIZE_URL || "";
const QODER_OAUTH_TOKEN_URL = process.env.QODER_OAUTH_TOKEN_URL || "";
@@ -504,7 +493,6 @@ export const PROVIDERS = {
CLAUDE: "claude",
CODEX: "codex",
GEMINI: "gemini",
- QWEN: "qwen",
QODER: "qoder",
ANTIGRAVITY: "antigravity",
AGY: "agy",
diff --git a/src/lib/oauth/providers/index.ts b/src/lib/oauth/providers/index.ts
index 801f78a424..2ec8a86994 100644
--- a/src/lib/oauth/providers/index.ts
+++ b/src/lib/oauth/providers/index.ts
@@ -15,7 +15,6 @@ import { codex } from "./codex";
import { antigravity } from "./antigravity";
import { agy } from "./agy";
import { qoder } from "./qoder";
-import { qwen } from "./qwen";
import { kimiCoding } from "./kimi-coding";
import { github } from "./github";
import { gheCopilot } from "./ghe-copilot";
@@ -38,7 +37,6 @@ export const PROVIDERS = {
antigravity,
agy,
qoder,
- qwen,
"kimi-coding": kimiCoding,
github,
"ghe-copilot": gheCopilot,
diff --git a/src/lib/oauth/providers/qwen.ts b/src/lib/oauth/providers/qwen.ts
deleted file mode 100644
index 8a3503bffe..0000000000
--- a/src/lib/oauth/providers/qwen.ts
+++ /dev/null
@@ -1,82 +0,0 @@
-import { QWEN_CONFIG } from "../constants/oauth";
-import { decodeJwt } from "jose";
-
-export const qwen = {
- config: QWEN_CONFIG,
- flowType: "device_code",
- requestDeviceCode: async (config, codeChallenge) => {
- const response = await fetch(config.deviceCodeUrl, {
- method: "POST",
- headers: {
- "Content-Type": "application/x-www-form-urlencoded",
- Accept: "application/json",
- },
- body: new URLSearchParams({
- client_id: config.clientId,
- scope: config.scope,
- code_challenge: codeChallenge,
- code_challenge_method: config.codeChallengeMethod,
- }),
- });
-
- if (!response.ok) {
- const error = await response.text();
- throw new Error(`Device code request failed: ${error}`);
- }
-
- return await response.json();
- },
- pollToken: async (config, deviceCode, codeVerifier) => {
- const response = await fetch(config.tokenUrl, {
- method: "POST",
- headers: {
- "Content-Type": "application/x-www-form-urlencoded",
- Accept: "application/json",
- },
- body: new URLSearchParams({
- grant_type: "urn:ietf:params:oauth:grant-type:device_code",
- client_id: config.clientId,
- device_code: deviceCode,
- code_verifier: codeVerifier,
- }),
- });
-
- return {
- ok: response.ok,
- data: await response.json(),
- };
- },
- mapTokens: (tokens) => {
- let email = null;
- let displayName = null;
- if (tokens.id_token) {
- try {
- const decoded = decodeJwt(tokens.id_token);
- email = decoded.email || decoded.preferred_username || null;
- displayName = decoded.name || email;
- } catch (e) {
- // Ignore
- }
- }
-
- if (!email && tokens.access_token) {
- try {
- const decodedToken = decodeJwt(tokens.access_token);
- email = decodedToken.email || decodedToken.preferred_username || decodedToken.sub || null;
- displayName = decodedToken.name || email;
- } catch (e) {
- // Ignore
- }
- }
-
- return {
- accessToken: tokens.access_token,
- refreshToken: tokens.refresh_token,
- expiresIn: tokens.expires_in,
- idToken: tokens.id_token,
- email,
- displayName,
- providerSpecificData: { resourceUrl: tokens.resource_url },
- };
- },
-};
diff --git a/src/lib/oauth/utils/cliProxyAuthImport.ts b/src/lib/oauth/utils/cliProxyAuthImport.ts
index 6ebc2eaddd..9d41692498 100644
--- a/src/lib/oauth/utils/cliProxyAuthImport.ts
+++ b/src/lib/oauth/utils/cliProxyAuthImport.ts
@@ -24,7 +24,6 @@ export const CLIPROXY_TYPE_TO_PROVIDER: Record = {
claude: "claude",
codex: "codex",
antigravity: "antigravity",
- qwen: "qwen",
kimi: "kimi",
};
diff --git a/src/lib/usage/fetcher.ts b/src/lib/usage/fetcher.ts
index 8a427d381b..cdac5e8195 100644
--- a/src/lib/usage/fetcher.ts
+++ b/src/lib/usage/fetcher.ts
@@ -45,8 +45,6 @@ export async function getUsageForProvider(connection) {
return await getClaudeUsage(accessToken);
case "codex":
return await getCodexUsage(accessToken, providerSpecificData);
- case "qwen":
- return await getQwenUsage(accessToken, providerSpecificData);
case "qoder":
return await getQoderUsage(accessToken);
case "kiro":
@@ -402,23 +400,6 @@ async function getCodexUsage(accessToken, providerSpecificData: Record = [
"/api/cli-tools/grok-build-settings", // GET calls getCliRuntimeStatus("grok-build"), which spawns a child process to locate + healthcheck the `grok` binary — same transitive-spawn surface that classified /api/skills/collect/ (Hard Rules #15 + #17). Writing ~/.grok/config.toml is inherently a local-machine operation, so loopback-only costs no real capability.
"/api/cli-tools/forge-settings", // spawns via getCliRuntimeStatus() to detect the `forge` CLI install (Hard Rules #15 + #17, #7263)
"/api/cli-tools/jcode-settings", // spawns via getCliRuntimeStatus() to detect the `jcode` CLI install (Hard Rules #15 + #17, #7263)
+ "/api/cli-tools/qwen-settings", // GET probes the local `qwen` binary; writes target ~/.qwen config files (Hard Rules #15 + #17)
"/api/services/", // T-10: embedded service lifecycle (spawn child processes)
"/dashboard/providers/services/", // T-07: reverse proxy to embedded service UIs
"/api/copilot/", // unauthenticated LLM driver — CLI-only by default; admins can opt-in to remote access via manage-scope bypass
diff --git a/src/shared/components/OAuthModal.tsx b/src/shared/components/OAuthModal.tsx
index 1b6558a1b6..44a6e364ef 100644
--- a/src/shared/components/OAuthModal.tsx
+++ b/src/shared/components/OAuthModal.tsx
@@ -290,11 +290,10 @@ export default function OAuthModal({
try {
setError(null);
- // Device code flow (GitHub, Qwen, Kiro, Kimi Coding, KiloCode, GHE Copilot)
+ // Device code flow (GitHub, Kiro, Kimi Coding, KiloCode, GHE Copilot)
if (
provider === "github" ||
provider === "ghe-copilot" ||
- provider === "qwen" ||
provider === "kiro" ||
provider === "amazon-q" ||
provider === "kimi-coding" ||
diff --git a/src/shared/constants/cliCompatProviders.ts b/src/shared/constants/cliCompatProviders.ts
index 8dc7c25195..619dbef12f 100644
--- a/src/shared/constants/cliCompatProviders.ts
+++ b/src/shared/constants/cliCompatProviders.ts
@@ -7,7 +7,6 @@ export const IMPLEMENTED_CLI_FINGERPRINT_PROVIDER_IDS = [
"codex",
"github",
"antigravity",
- "qwen",
] as const;
export const CLI_COMPAT_DISPLAY_PROVIDER_IDS = [
@@ -15,7 +14,6 @@ export const CLI_COMPAT_DISPLAY_PROVIDER_IDS = [
"codex",
"copilot",
"antigravity",
- "qwen",
] as const;
/**
@@ -81,8 +79,4 @@ export const CLI_COMPAT_PROVIDER_DISPLAY: Record<
name: "Antigravity",
description: "Google Antigravity IDE compatibility",
},
- qwen: {
- name: "Qwen Code / Qoder",
- description: "Qwen Code and Qoder CLI compatibility",
- },
};
diff --git a/src/shared/constants/cliTools.ts b/src/shared/constants/cliTools.ts
index 644c8999d2..d8933f3e70 100644
--- a/src/shared/constants/cliTools.ts
+++ b/src/shared/constants/cliTools.ts
@@ -404,8 +404,7 @@ export const CLI_TOOLS: Record = {
name: "Qwen Code",
icon: "psychology",
color: "#10B981",
- description:
- "Alibaba Qwen Code CLI — supports OpenAI, Anthropic & Gemini providers via OmniRoute",
+ description: "Qwen Code CLI — current V4 OpenAI-compatible model provider via OmniRoute",
docsUrl: "https://qwenlm.github.io/qwen-code-docs/en/users/configuration/model-providers/",
configType: "guide",
category: "code",
@@ -413,91 +412,15 @@ export const CLI_TOOLS: Record = {
acpSpawnable: true,
baseUrlSupport: "full",
defaultCommand: "qwen",
+ previewConfigMode: "qwen",
notes: [
{
type: "info",
- text: "Qwen Code supports multiple provider types (openai, anthropic, gemini) via modelProviders in settings.json. OmniRoute works as an OpenAI-compatible endpoint.",
+ text: "OmniRoute is registered under modelProviders.openai using Qwen Code's current bare-array V4 format.",
},
{
type: "info",
- text: "Any model available in OmniRoute can be used — not just Qwen models. Select from Qwen, Claude, Gemini, GPT, and more.",
- },
- {
- type: "warning",
- text: "Config path: Linux/macOS ~/.qwen/settings.json • Windows %USERPROFILE%\\.qwen\\settings.json",
- },
- {
- type: "error",
- text: "Qwen OAuth free tier was discontinued on 2026-04-15. Use OmniRoute with bailian-coding-plan/alibaba/alibaba-cn/openrouter/anthropic/gemini providers instead.",
- },
- ],
- modelAliases: [
- "coder-model",
- "qwen3-coder-plus",
- "qwen3-coder-flash",
- "vision-model",
- "claude-sonnet-4-6",
- "claude-opus-4-6-thinking",
- "gemini-3-flash",
- "gemini-3.1-pro-high",
- ],
- defaultModels: [
- {
- id: "coder-model",
- name: "Coder Model (Qwen 3.6 Plus)",
- alias: "coder-model",
- envKey: "OPENAI_MODEL",
- defaultValue: "coder-model",
- isTopLevel: true,
- },
- {
- id: "qwen3-coder-plus",
- name: "Qwen 3 Coder Plus",
- alias: "qwen3-coder-plus",
- envKey: "OPENAI_MODEL",
- defaultValue: "qwen3-coder-plus",
- },
- {
- id: "qwen3-coder-flash",
- name: "Qwen 3 Coder Flash",
- alias: "qwen3-coder-flash",
- envKey: "OPENAI_MODEL",
- defaultValue: "qwen3-coder-flash",
- },
- {
- id: "vision-model",
- name: "Vision Model (Multimodal)",
- alias: "vision-model",
- envKey: "OPENAI_MODEL",
- defaultValue: "vision-model",
- },
- {
- id: "claude-sonnet-4-6",
- name: "Claude Sonnet 4.6",
- alias: "claude-sonnet-4-6",
- envKey: "OPENAI_MODEL",
- defaultValue: "claude-sonnet-4-6",
- },
- {
- id: "claude-opus-4-6-thinking",
- name: "Claude Opus 4.6 Thinking",
- alias: "claude-opus-4-6-thinking",
- envKey: "OPENAI_MODEL",
- defaultValue: "claude-opus-4-6-thinking",
- },
- {
- id: "gemini-3.1-pro-high",
- name: "Gemini 3.1 Pro High",
- alias: "gemini-3.1-pro-high",
- envKey: "OPENAI_MODEL",
- defaultValue: "gemini-3.1-pro-high",
- },
- {
- id: "gemini-3-flash",
- name: "Gemini 3 Flash",
- alias: "gemini-3-flash",
- envKey: "OPENAI_MODEL",
- defaultValue: "gemini-3-flash",
+ text: "The API key is stored only as OMNIROUTE_API_KEY in ~/.qwen/.env, leaving your existing provider credentials untouched.",
},
],
guideSteps: [
@@ -508,23 +431,24 @@ export const CLI_TOOLS: Record = {
{
step: 5,
title: "Save Config",
- desc: "Click Save Config below to write your settings.json automatically.",
+ desc: "Write the modelProviders entry and dedicated .env key without replacing other Qwen Code settings.",
},
],
codeBlock: {
language: "json",
- code: `# ~/.qwen/settings.json — OmniRoute via security.auth
-{
- "security": {
- "auth": {
- "selectedType": "openai",
- "apiKey": "{{apiKey}}",
- "baseUrl": "{{baseUrl}}"
- }
+ code: `{
+ "modelProviders": {
+ "openai": [
+ {
+ "id": "{{model}}",
+ "name": "{{model}} (OmniRoute)",
+ "envKey": "OMNIROUTE_API_KEY",
+ "baseUrl": "{{baseUrl}}"
+ }
+ ]
},
- "model": {
- "name": "{{model}}"
- }
+ "security": { "auth": { "selectedType": "openai" } },
+ "model": { "name": "{{model}}", "baseUrl": "{{baseUrl}}" }
}`,
},
},
diff --git a/src/shared/constants/pricing/oauth-subscriptions.ts b/src/shared/constants/pricing/oauth-subscriptions.ts
index 43bf8385fb..6856f78402 100644
--- a/src/shared/constants/pricing/oauth-subscriptions.ts
+++ b/src/shared/constants/pricing/oauth-subscriptions.ts
@@ -180,46 +180,6 @@ export const DEFAULT_PRICING_OAUTH = {
cache_creation: 1.0,
},
},
- qw: {
- "qwen3-coder-plus": {
- input: 1.0,
- output: 4.0,
- cached: 0.5,
- reasoning: 6.0,
- cache_creation: 1.0,
- },
- // Next-generation Qwen Coder tier (added Mar 2026)
- "qwen3-coder-next": {
- input: 2.0,
- output: 8.0,
- cached: 1.0,
- reasoning: 12.0,
- cache_creation: 2.0,
- },
- "qwen3-coder-flash": {
- input: 0.5,
- output: 2.0,
- cached: 0.25,
- reasoning: 3.0,
- cache_creation: 0.5,
- },
- "vision-model": {
- input: 1.5,
- output: 6.0,
- cached: 0.75,
- reasoning: 9.0,
- cache_creation: 1.5,
- },
- // Qwen3.5/3.6 Coder Model — ported from upstream 9router PR #156 (zx07).
- // Priced identically to the vision tier per upstream defaults.
- "coder-model": {
- input: 1.5,
- output: 6.0,
- cached: 0.75,
- reasoning: 9.0,
- cache_creation: 1.5,
- },
- },
if: {
"qwen3-coder-plus": {
input: 1.0,
diff --git a/src/shared/constants/providers/oauth.ts b/src/shared/constants/providers/oauth.ts
index ce23f1dc5d..404c0df636 100644
--- a/src/shared/constants/providers/oauth.ts
+++ b/src/shared/constants/providers/oauth.ts
@@ -48,18 +48,6 @@ export const OAUTH_PROVIDERS = {
riskNoticeVariant: "oauth",
hasFree: true,
},
- qwen: {
- id: "qwen",
- alias: "qw",
- name: "Qwen Code",
- icon: "psychology",
- color: "#10B981",
- subscriptionRisk: true,
- riskNoticeVariant: "deprecated",
- deprecated: true,
- deprecationReason:
- "Qwen OAuth free tier was discontinued on 2026-04-15. Use 'bailian-coding-plan', 'alibaba', 'alibaba-cn', or 'openrouter' provider with API key instead.",
- },
agy: {
id: "agy",
alias: "agy",
diff --git a/src/shared/constants/providers/web-cookie.ts b/src/shared/constants/providers/web-cookie.ts
index 8a6cb8a49e..eae2018382 100644
--- a/src/shared/constants/providers/web-cookie.ts
+++ b/src/shared/constants/providers/web-cookie.ts
@@ -312,7 +312,7 @@ export const WEB_COOKIE_PROVIDERS = {
},
"qwen-web": {
id: "qwen-web",
- // Primary "qwen" provider keeps the short alias; web variant uses its own id.
+ // The web variant uses its own id; the retired `qw` alias is not reassigned.
alias: "qwen-web",
name: "Qwen Web (Free)",
icon: "auto_awesome",
diff --git a/src/shared/constants/spawnCapablePrefixes.ts b/src/shared/constants/spawnCapablePrefixes.ts
index 87c2186a1d..62ada570aa 100644
--- a/src/shared/constants/spawnCapablePrefixes.ts
+++ b/src/shared/constants/spawnCapablePrefixes.ts
@@ -25,6 +25,7 @@
*/
export const SPAWN_CAPABLE_PREFIXES: ReadonlyArray = [
"/api/cli-tools/runtime/",
+ "/api/cli-tools/qwen-settings", // GET probes the Qwen Code binary; the route also mutates local ~/.qwen files
"/api/services/", // T-10: can run npm install + spawn node processes
"/api/tools/agent-bridge/", // start/stop MITM server + DNS edits (Hard Rules #15 + #17)
"/api/tools/traffic-inspector/", // http-proxy listener + system proxy (Hard Rules #15 + #17)
diff --git a/src/shared/services/cliRuntime.ts b/src/shared/services/cliRuntime.ts
index 509bfd18cd..8538ec8878 100644
--- a/src/shared/services/cliRuntime.ts
+++ b/src/shared/services/cliRuntime.ts
@@ -578,6 +578,7 @@ export const getKnownToolPaths = (toolId: string): string[] => {
["qodercli.cmd", "qodercli"],
["qodercli.exe", "qodercli"],
],
+ qwen: [["qwen.cmd", "qwen"]],
devin: [
["devin.exe", "devin"],
["devin.cmd", "devin"],
diff --git a/src/shared/services/qwenCodeConfig.ts b/src/shared/services/qwenCodeConfig.ts
new file mode 100644
index 0000000000..5504e7b817
--- /dev/null
+++ b/src/shared/services/qwenCodeConfig.ts
@@ -0,0 +1,252 @@
+type JsonRecord = Record;
+
+export const QWEN_CODE_ENV_KEY = "OMNIROUTE_API_KEY";
+
+const LEGACY_ENV_KEYS = new Set(["OPENAI_API_KEY", "ANTHROPIC_API_KEY", "GEMINI_API_KEY"]);
+
+const isRecord = (value: unknown): value is JsonRecord =>
+ typeof value === "object" && value !== null && !Array.isArray(value);
+
+const cloneRecord = (value: unknown): JsonRecord => (isRecord(value) ? { ...value } : {});
+
+export const normalizeQwenCodeBaseUrl = (value: unknown): string => {
+ const baseUrl = String(value || "")
+ .trim()
+ .replace(/\/+$/, "");
+ if (!baseUrl) return "";
+ return baseUrl.endsWith("/v1") ? baseUrl : `${baseUrl}/v1`;
+};
+
+/**
+ * Identifies only entries owned by OmniRoute. Generic custom endpoints are not
+ * considered managed: users can keep any other OpenAI-compatible provider.
+ */
+export const isManagedQwenCodeModel = (value: unknown): boolean => {
+ if (!isRecord(value)) return false;
+ if (value.envKey === QWEN_CODE_ENV_KEY || value.id === "omniroute") return true;
+
+ return (
+ typeof value.name === "string" &&
+ value.name.endsWith(" (OmniRoute)") &&
+ typeof value.envKey === "string" &&
+ LEGACY_ENV_KEYS.has(value.envKey)
+ );
+};
+
+const unwrapProviderModels = (value: unknown): unknown => {
+ if (Array.isArray(value)) return [...value];
+ if (isRecord(value) && Array.isArray(value.models)) return [...value.models];
+ return value;
+};
+
+const getProviderMap = (value: unknown): JsonRecord => {
+ if (Array.isArray(value)) {
+ const migrated: Record = {};
+ for (const entry of value) {
+ if (!isRecord(entry) || typeof entry.id !== "string") continue;
+ const providerId = typeof entry.authType === "string" ? entry.authType : "openai";
+ const model = { ...entry };
+ delete model.authType;
+ (migrated[providerId] ||= []).push(model);
+ }
+ return migrated;
+ }
+ if (!isRecord(value)) return {};
+ return Object.fromEntries(
+ Object.entries(value).map(([providerId, models]) => [providerId, unwrapProviderModels(models)])
+ );
+};
+
+const filterManagedModels = (value: unknown): { value: unknown; removed: JsonRecord[] } => {
+ if (!Array.isArray(value)) return { value, removed: [] };
+
+ const removed = value.filter(isManagedQwenCodeModel).filter(isRecord);
+ return {
+ value: value.filter((entry) => !isManagedQwenCodeModel(entry)),
+ removed,
+ };
+};
+
+const removeEmptyObject = (parent: JsonRecord, key: string): void => {
+ if (isRecord(parent[key]) && Object.keys(parent[key]).length === 0) delete parent[key];
+};
+
+export type QwenCodeConfigInput = {
+ baseUrl: string;
+ model: string;
+ modelName?: string;
+};
+
+export const buildQwenCodeModel = ({
+ baseUrl,
+ model,
+ modelName,
+}: QwenCodeConfigInput): JsonRecord => {
+ const normalizedBaseUrl = normalizeQwenCodeBaseUrl(baseUrl);
+ const normalizedModel = String(model || "").trim();
+ if (!normalizedBaseUrl || !normalizedModel) {
+ throw new Error("Qwen Code requires a base URL and model");
+ }
+
+ return {
+ id: normalizedModel,
+ name: `${String(modelName || normalizedModel).trim()} (OmniRoute)`,
+ envKey: QWEN_CODE_ENV_KEY,
+ baseUrl: normalizedBaseUrl,
+ };
+};
+
+/**
+ * Merge one OmniRoute model into Qwen Code's current V4 settings contract.
+ * `modelProviders.openai` is a bare ModelConfig[] array. Other settings and
+ * user-owned provider entries are preserved.
+ */
+export const mergeQwenCodeSettings = (
+ existing: unknown,
+ input: QwenCodeConfigInput
+): JsonRecord => {
+ const next = cloneRecord(existing);
+ const modelEntry = buildQwenCodeModel(input);
+ const modelProviders = getProviderMap(next.modelProviders);
+
+ for (const [providerId, value] of Object.entries(modelProviders)) {
+ const filtered = filterManagedModels(value);
+ modelProviders[providerId] = filtered.value;
+ if (Array.isArray(filtered.value) && filtered.value.length === 0 && providerId !== "openai") {
+ delete modelProviders[providerId];
+ }
+ }
+
+ const openaiModels = Array.isArray(modelProviders.openai) ? modelProviders.openai : [];
+ modelProviders.openai = [...openaiModels, modelEntry];
+ next.modelProviders = modelProviders;
+
+ const security = cloneRecord(next.security);
+ const auth = cloneRecord(security.auth);
+ const previousAuthBaseUrl = normalizeQwenCodeBaseUrl(auth.baseUrl);
+ const replacingLegacyAuth =
+ auth.selectedType === "openai" && previousAuthBaseUrl === modelEntry.baseUrl;
+
+ auth.selectedType = "openai";
+ if (replacingLegacyAuth) {
+ delete auth.apiKey;
+ delete auth.baseUrl;
+ }
+ security.auth = auth;
+ next.security = security;
+
+ next.model = {
+ ...cloneRecord(next.model),
+ name: modelEntry.id,
+ baseUrl: modelEntry.baseUrl,
+ };
+
+ if (next.selectedProvider === "omniroute") delete next.selectedProvider;
+
+ return next;
+};
+
+export const findOmniRouteQwenCodeModel = (settings: unknown): JsonRecord | undefined => {
+ if (!isRecord(settings)) return undefined;
+ const providers = settings.modelProviders;
+ if (Array.isArray(providers))
+ return providers.find(isManagedQwenCodeModel) as JsonRecord | undefined;
+ if (!isRecord(providers)) return undefined;
+
+ for (const value of Object.values(providers)) {
+ const models = unwrapProviderModels(value);
+ if (!Array.isArray(models)) continue;
+ const managed = models.find(isManagedQwenCodeModel);
+ if (isRecord(managed)) return managed;
+ }
+ return undefined;
+};
+
+export const hasOmniRouteQwenCodeConfig = (settings: unknown): boolean =>
+ findOmniRouteQwenCodeModel(settings) !== undefined;
+
+/** Remove only OmniRoute-owned Qwen Code entries and selection state. */
+export const removeQwenCodeSettings = (existing: unknown): JsonRecord => {
+ const next = cloneRecord(existing);
+ const originalProviders = next.modelProviders;
+ const removed: JsonRecord[] = [];
+
+ if (Array.isArray(originalProviders)) {
+ const filtered = filterManagedModels(originalProviders);
+ next.modelProviders = filtered.value;
+ removed.push(...filtered.removed);
+ if ((filtered.value as unknown[]).length === 0) delete next.modelProviders;
+ } else if (isRecord(originalProviders)) {
+ const providers = { ...originalProviders };
+ for (const [providerId, rawValue] of Object.entries(providers)) {
+ const unwrapped = unwrapProviderModels(rawValue);
+ const filtered = filterManagedModels(unwrapped);
+ removed.push(...filtered.removed);
+ if (Array.isArray(filtered.value) && filtered.value.length === 0) {
+ delete providers[providerId];
+ } else {
+ providers[providerId] = filtered.value;
+ }
+ }
+ if (Object.keys(providers).length === 0) delete next.modelProviders;
+ else next.modelProviders = providers;
+ }
+
+ const model = cloneRecord(next.model);
+ const selectedMatchesRemoved = removed.some(
+ (entry) =>
+ entry.id === model.name &&
+ (!model.baseUrl ||
+ normalizeQwenCodeBaseUrl(entry.baseUrl) === normalizeQwenCodeBaseUrl(model.baseUrl))
+ );
+
+ if (selectedMatchesRemoved) {
+ delete model.name;
+ delete model.baseUrl;
+ if (Object.keys(model).length === 0) delete next.model;
+ else next.model = model;
+ }
+
+ const security = cloneRecord(next.security);
+ const auth = cloneRecord(security.auth);
+ const remainingOpenai = isRecord(next.modelProviders) ? next.modelProviders.openai : undefined;
+ const authBaseUrl = normalizeQwenCodeBaseUrl(auth.baseUrl);
+ const authMatchesRemoved = removed.some(
+ (entry) => normalizeQwenCodeBaseUrl(entry.baseUrl) === authBaseUrl
+ );
+ if (authMatchesRemoved) {
+ delete auth.apiKey;
+ delete auth.baseUrl;
+ if (auth.selectedType === "openai" && !Array.isArray(remainingOpenai)) {
+ delete auth.selectedType;
+ }
+ if (Object.keys(auth).length === 0) delete security.auth;
+ else security.auth = auth;
+ if (Object.keys(security).length === 0) delete next.security;
+ else next.security = security;
+ }
+
+ if (next.selectedProvider === "omniroute") delete next.selectedProvider;
+ removeEmptyObject(next, "modelProviders");
+ return next;
+};
+
+const OWNED_ENV_LINE = /^\s*(?:export\s+)?OMNIROUTE_API_KEY\s*=/;
+
+export const mergeQwenCodeEnv = (existing: unknown, apiKey: unknown): string => {
+ const lines = String(existing || "")
+ .split(/\r?\n/)
+ .filter((line) => !OWNED_ENV_LINE.test(line));
+
+ while (lines.length > 0 && lines.at(-1) === "") lines.pop();
+ lines.push(`${QWEN_CODE_ENV_KEY}=${JSON.stringify(String(apiKey || "sk_omniroute"))}`);
+ return `${lines.join("\n")}\n`;
+};
+
+export const removeQwenCodeEnv = (existing: unknown): string => {
+ const lines = String(existing || "")
+ .split(/\r?\n/)
+ .filter((line) => !OWNED_ENV_LINE.test(line));
+ while (lines.length > 0 && lines.at(-1) === "") lines.pop();
+ return lines.length > 0 ? `${lines.join("\n")}\n` : "";
+};
diff --git a/src/shared/utils/requestTimeout.ts b/src/shared/utils/requestTimeout.ts
index ae12ac39af..876560ba5f 100644
--- a/src/shared/utils/requestTimeout.ts
+++ b/src/shared/utils/requestTimeout.ts
@@ -50,7 +50,6 @@ export const PROVIDER_TIMEOUTS: Record = {
claude: 90000, // Claude can be slower for long outputs
gemini: 60000,
codex: 120000, // Coding tasks often take longer
- qwen: 45000,
deepseek: 60000,
cohere: 45000,
groq: 30000, // Groq is fast
diff --git a/src/sse/services/tokenRefresh.ts b/src/sse/services/tokenRefresh.ts
index 05585ae301..99f5c53379 100755
--- a/src/sse/services/tokenRefresh.ts
+++ b/src/sse/services/tokenRefresh.ts
@@ -11,7 +11,6 @@ import {
refreshAccessToken as _refreshAccessToken,
refreshClaudeOAuthToken as _refreshClaudeOAuthToken,
refreshGoogleToken as _refreshGoogleToken,
- refreshQwenToken as _refreshQwenToken,
refreshCodexToken as _refreshCodexToken,
refreshQoderToken as _refreshQoderToken,
refreshGitHubToken as _refreshGitHubToken,
@@ -68,11 +67,6 @@ export const refreshGoogleToken = async (
return _refreshGoogleToken(refreshToken, clientId, clientSecret, log, proxy);
};
-export const refreshQwenToken = async (refreshToken: string, credentials?: any) => {
- const proxy = await resolveProxyForCredentials("qwen", credentials);
- return _refreshQwenToken(refreshToken, log, proxy);
-};
-
export const refreshCodexToken = async (refreshToken: string, credentials?: any) => {
const proxy = await resolveProxyForCredentials("codex", credentials);
return _refreshCodexToken(refreshToken, log, proxy);
diff --git a/stryker.conf.json b/stryker.conf.json
index 900186627e..ca14531da8 100644
--- a/stryker.conf.json
+++ b/stryker.conf.json
@@ -240,6 +240,7 @@
"tests/unit/route-explainability.test.ts",
"tests/unit/route-guard-forge-jcode-settings-local-only.test.ts",
"tests/unit/route-guard-grok-build-settings-local-only.test.ts",
+ "tests/unit/route-guard-qwen-settings-local-only.test.ts",
"tests/unit/route-guard-middleware-local-only.test.ts",
"tests/unit/route-guard-plugins-local-only.test.ts",
"tests/unit/route-guard-private-lan.test.ts",
diff --git a/tests/snapshots/provider/translate-path.json b/tests/snapshots/provider/translate-path.json
index b4436f0596..fac56957d5 100644
--- a/tests/snapshots/provider/translate-path.json
+++ b/tests/snapshots/provider/translate-path.json
@@ -3903,71 +3903,6 @@
"stream": "https://api.qoder.com/v1/chat/completions"
}
},
- "qwen": {
- "format": "openai",
- "headers": {
- "apiKey": {
- "Accept": "text/event-stream",
- "Accept-Language": "*",
- "Authorization": "Bearer ",
- "Connection": "keep-alive",
- "Content-Type": "application/json",
- "Sec-Fetch-Mode": "cors",
- "User-Agent": "QwenCode/0.19.3 (; )",
- "X-Dashscope-AuthType": "qwen-oauth",
- "X-Dashscope-CacheControl": "enable",
- "X-Dashscope-UserAgent": "QwenCode/0.19.3 (; )",
- "X-Stainless-Arch": "",
- "X-Stainless-Lang": "js",
- "X-Stainless-Os": "",
- "X-Stainless-Package-Version": "5.11.0",
- "X-Stainless-Retry-Count": "1",
- "X-Stainless-Runtime": "node",
- "X-Stainless-Runtime-Version": ""
- },
- "nonStream": {
- "Accept-Language": "*",
- "Authorization": "Bearer ",
- "Connection": "keep-alive",
- "Content-Type": "application/json",
- "Sec-Fetch-Mode": "cors",
- "User-Agent": "QwenCode/0.19.3 (; )",
- "X-Dashscope-AuthType": "qwen-oauth",
- "X-Dashscope-CacheControl": "enable",
- "X-Dashscope-UserAgent": "QwenCode/0.19.3 (; )",
- "X-Stainless-Arch": "",
- "X-Stainless-Lang": "js",
- "X-Stainless-Os": "",
- "X-Stainless-Package-Version": "5.11.0",
- "X-Stainless-Retry-Count": "1",
- "X-Stainless-Runtime": "node",
- "X-Stainless-Runtime-Version": ""
- },
- "oauth": {
- "Accept": "text/event-stream",
- "Accept-Language": "*",
- "Authorization": "Bearer ",
- "Connection": "keep-alive",
- "Content-Type": "application/json",
- "Sec-Fetch-Mode": "cors",
- "User-Agent": "QwenCode/0.19.3 (; )",
- "X-Dashscope-AuthType": "qwen-oauth",
- "X-Dashscope-CacheControl": "enable",
- "X-Dashscope-UserAgent": "QwenCode/0.19.3 (; )",
- "X-Stainless-Arch": "",
- "X-Stainless-Lang": "js",
- "X-Stainless-Os": "",
- "X-Stainless-Package-Version": "5.11.0",
- "X-Stainless-Retry-Count": "1",
- "X-Stainless-Runtime": "node",
- "X-Stainless-Runtime-Version": ""
- }
- },
- "url": {
- "nonStream": "https://chat.qwen.ai/api/v1/services/aigc/text-generation/generation",
- "stream": "https://chat.qwen.ai/api/v1/services/aigc/text-generation/generation"
- }
- },
"qwen-web": {
"format": "openai",
"headers": {
diff --git a/tests/unit/acp-registry.test.ts b/tests/unit/acp-registry.test.ts
index 6e5d2f66f1..deb14d709b 100644
--- a/tests/unit/acp-registry.test.ts
+++ b/tests/unit/acp-registry.test.ts
@@ -3,6 +3,7 @@ import assert from "node:assert/strict";
const { resolveVersionProbe, shouldUseShellForVersionProbe } =
await import("../../src/lib/acp/registry.ts");
+const { getAgentById } = await import("../../src/lib/acp/registry.ts");
test("resolveVersionProbe parses quoted binary paths without shell semantics", () => {
const probe = resolveVersionProbe(
@@ -34,9 +35,17 @@ test("resolveVersionProbe rejects shell metacharacters in version commands", ()
test("shouldUseShellForVersionProbe preserves Windows npm wrapper detection", () => {
assert.equal(shouldUseShellForVersionProbe("codex", "win32"), true);
assert.equal(
- shouldUseShellForVersionProbe("C:\\Users\\dev\\AppData\\Roaming\\npm\\qwen.cmd", "win32"),
+ shouldUseShellForVersionProbe("C:\\Users\\dev\\AppData\\Roaming\\npm\\codex.cmd", "win32"),
true
);
assert.equal(shouldUseShellForVersionProbe("C:\\Tools\\claude.exe", "win32"), false);
assert.equal(shouldUseShellForVersionProbe("codex", "linux"), false);
});
+
+test("Qwen Code is registered with its upstream ACP mode", () => {
+ const qwen = getAgentById("qwen");
+ assert.ok(qwen);
+ assert.deepEqual(qwen.spawnArgs, ["--acp"]);
+ assert.equal(qwen.providerAlias, "qwen-code");
+ assert.equal(qwen.protocol, "stdio");
+});
diff --git a/tests/unit/authz/spawn-capable-prefixes-client-safe.test.ts b/tests/unit/authz/spawn-capable-prefixes-client-safe.test.ts
index 7c64ca0f6c..2522c659a1 100644
--- a/tests/unit/authz/spawn-capable-prefixes-client-safe.test.ts
+++ b/tests/unit/authz/spawn-capable-prefixes-client-safe.test.ts
@@ -86,5 +86,5 @@ test("SPAWN_CAPABLE_PREFIXES is defined in the server-free constants leaf with t
`SPAWN_CAPABLE_PREFIXES lost the spawn-capable prefix "${prefix}" during extraction`
);
}
- assert.equal(SPAWN_CAPABLE_PREFIXES.length, 9);
+ assert.equal(SPAWN_CAPABLE_PREFIXES.length, 10);
});
diff --git a/tests/unit/cache-control-claude-providers.test.ts b/tests/unit/cache-control-claude-providers.test.ts
index d868c796b3..ed50593e33 100644
--- a/tests/unit/cache-control-claude-providers.test.ts
+++ b/tests/unit/cache-control-claude-providers.test.ts
@@ -11,7 +11,6 @@ describe("Cache Control Policy - Claude Protocol Providers", () => {
assert.equal(providerSupportsCaching("claude", "claude"), true);
assert.equal(providerSupportsCaching("anthropic", "claude"), true);
assert.equal(providerSupportsCaching("zai", "claude"), true);
- assert.equal(providerSupportsCaching("qwen", "openai"), true);
assert.equal(providerSupportsCaching("deepseek", "openai"), true);
// Claude-protocol providers NOT in CACHING_PROVIDERS set
diff --git a/tests/unit/cache-control-policy.test.ts b/tests/unit/cache-control-policy.test.ts
index 0f67de43b5..558e768f28 100644
--- a/tests/unit/cache-control-policy.test.ts
+++ b/tests/unit/cache-control-policy.test.ts
@@ -37,7 +37,6 @@ describe("Cache Control Policy", () => {
assert.equal(providerSupportsCaching("claude"), true);
assert.equal(providerSupportsCaching("anthropic"), true);
assert.equal(providerSupportsCaching("zai"), true);
- assert.equal(providerSupportsCaching("qwen"), true);
assert.equal(providerSupportsCaching("deepseek"), true);
// #3088 — Xiaomi MiMo supports prompt caching; cache_control breakpoints
// sent by Claude Code (via cc-switch) must be preserved, not stripped.
diff --git a/tests/unit/catalog-updates-v3x.test.ts b/tests/unit/catalog-updates-v3x.test.ts
index 0f3bbc80ee..9e3c3558fe 100644
--- a/tests/unit/catalog-updates-v3x.test.ts
+++ b/tests/unit/catalog-updates-v3x.test.ts
@@ -59,7 +59,11 @@ test("Fable 5 catalog exposes claude-fable-5 in cc — but NOT via Kiro (fabrica
assert.ok(ccPricing["claude-fable-5"], "cc pricing must include claude-fable-5");
const kiroIds = new Set(getModelsByProviderId("kiro").map((m) => m.id));
- assert.equal(kiroIds.has("claude-fable-5"), false, "kiro must NOT expose claude-fable-5 (fabricated)");
+ assert.equal(
+ kiroIds.has("claude-fable-5"),
+ false,
+ "kiro must NOT expose claude-fable-5 (fabricated)"
+ );
});
test("Sonnet 5 catalog exposes claude-sonnet-5 across cc/kiro/anthropic/blackbox with Sonnet-tier pricing", () => {
@@ -91,9 +95,21 @@ test("Kiro catalog does NOT expose Claude Opus (fabricated — Kiro upstream has
// 400 "Invalid model. Please select a different model". #6170 removed them.
const ids = new Set(getModelsByProviderId("kiro").map((model) => model.id));
- assert.equal(ids.has("claude-opus-4.8"), false, "kiro must NOT expose claude-opus-4.8 (fabricated)");
- assert.equal(ids.has("claude-opus-4.7"), false, "kiro must NOT expose claude-opus-4.7 (fabricated)");
- assert.equal(ids.has("claude-opus-4.6"), false, "kiro must NOT expose claude-opus-4.6 (fabricated)");
+ assert.equal(
+ ids.has("claude-opus-4.8"),
+ false,
+ "kiro must NOT expose claude-opus-4.8 (fabricated)"
+ );
+ assert.equal(
+ ids.has("claude-opus-4.7"),
+ false,
+ "kiro must NOT expose claude-opus-4.7 (fabricated)"
+ );
+ assert.equal(
+ ids.has("claude-opus-4.6"),
+ false,
+ "kiro must NOT expose claude-opus-4.6 (fabricated)"
+ );
});
test("Every Kiro registry model resolves a non-zero pricing row (no $0.00 usage)", async () => {
@@ -164,33 +180,3 @@ test("Every Codex registry model resolves a non-zero pricing row (alias: cx)", a
);
}
});
-
-test("Every Qwen registry model resolves a non-zero pricing row (alias: qw)", async () => {
- const { getPricingForModel } = await import("../../src/shared/constants/pricing.ts");
- const models = getModelsByProviderId("qwen");
- assert.ok(models.length > 0, "qwen must expose models");
-
- for (const model of models) {
- // Qwen pricing lives under the "qw" alias (its DEFAULT_PRICING key).
- const pricing = getPricingForModel("qw", model.id) as {
- input?: number;
- output?: number;
- } | null;
- assert.ok(pricing, `qw pricing must include qwen model "${model.id}"`);
- assert.equal(
- typeof pricing?.input === "number" && typeof pricing?.output === "number",
- true,
- `qw pricing for "${model.id}" must have numeric input/output`
- );
- }
-
- // Regression guard: the "coder-model" id (Qwen3.5/3.6 Coder Model, ported from
- // upstream 9router PR #156) must be priced like the other Qwen coder tier.
- const coderModel = getPricingForModel("qw", "coder-model") as {
- input: number;
- output: number;
- } | null;
- assert.ok(coderModel, "qw pricing must include coder-model");
- assert.equal(typeof coderModel?.input, "number");
- assert.equal(typeof coderModel?.output, "number");
-});
diff --git a/tests/unit/chatcore-translation-paths.test.ts b/tests/unit/chatcore-translation-paths.test.ts
index d59df9815c..eb12a8c8f0 100644
--- a/tests/unit/chatcore-translation-paths.test.ts
+++ b/tests/unit/chatcore-translation-paths.test.ts
@@ -1788,110 +1788,6 @@ test("chatCore redirects background utility tasks to a cheaper mapped model", as
assert.equal(call.body.model, "gpt-5-mini");
});
-test("chatCore retries Qwen quota 429 responses before succeeding", async () => {
- const originalSetTimeout = globalThis.setTimeout;
- try {
- (globalThis as any).setTimeout = (callback: any, ms: any, ...args: any[]) => {
- // Only make Qwen retry delays (≤5s) synchronous; let longer timeouts (e.g. body read) use real setTimeout
- if (typeof ms === "number" && ms > 5000) {
- return originalSetTimeout(callback, ms, ...args);
- }
- callback(...args);
- return 0 as any;
- };
-
- const { calls, result } = await invokeChatCore({
- provider: "qwen",
- model: "qwen3-coder",
- body: {
- model: "qwen3-coder",
- stream: false,
- messages: [{ role: "user", content: "retry the quota hit" }],
- },
- responseFactory(_captured, seenCalls) {
- if (seenCalls.length === 1) {
- return new Response(
- JSON.stringify({ error: { message: "You exceeded your current quota for Qwen." } }),
- {
- status: 429,
- headers: { "Content-Type": "application/json" },
- }
- );
- }
- return buildOpenAIResponse(false, "qwen recovered");
- },
- });
-
- const payload = (await result.response.json()) as any;
- assert.equal(result.success, true);
- assert.equal(calls.length, 2);
- assert.equal(payload.choices[0].message.content, "qwen recovered");
- } finally {
- globalThis.setTimeout = originalSetTimeout;
- }
-});
-
-test("chatCore injects fallback user for Qwen OAuth requests without user", async () => {
- const { call, result } = await invokeChatCore({
- provider: "qwen",
- model: "qwen3-coder",
- credentials: {
- accessToken: "qwen-oauth-token",
- providerSpecificData: { resourceUrl: "portal.qwen.ai" },
- },
- body: {
- model: "qwen3-coder",
- stream: false,
- messages: [{ role: "user", content: "check qwen user fallback" }],
- },
- responseFormat: "openai",
- });
-
- assert.equal(result.success, true);
- assert.equal(call.body.user, "omniroute-qwen-oauth");
-});
-
-test("chatCore keeps explicit user for Qwen OAuth requests", async () => {
- const { call, result } = await invokeChatCore({
- provider: "qwen",
- model: "qwen3-coder",
- credentials: {
- accessToken: "qwen-oauth-token",
- providerSpecificData: { resourceUrl: "portal.qwen.ai" },
- },
- body: {
- model: "qwen3-coder",
- stream: false,
- user: "explicit-user",
- messages: [{ role: "user", content: "keep my user" }],
- },
- responseFormat: "openai",
- });
-
- assert.equal(result.success, true);
- assert.equal(call.body.user, "explicit-user");
-});
-
-test("chatCore does not inject fallback user for Qwen API key requests", async () => {
- const { call, result } = await invokeChatCore({
- provider: "qwen",
- model: "qwen3-coder",
- credentials: {
- apiKey: "qwen-api-key",
- providerSpecificData: { resourceUrl: "dashscope.aliyuncs.com/compatible-mode/v1" },
- },
- body: {
- model: "qwen3-coder",
- stream: false,
- messages: [{ role: "user", content: "api key mode should stay untouched" }],
- },
- responseFormat: "openai",
- });
-
- assert.equal(result.success, true);
- assert.equal("user" in call.body, false);
-});
-
test("chatCore preserves Codex dual-window scope cooldowns on 429 responses", async () => {
const connection = await providersDb.createProviderConnection({
provider: "codex",
diff --git a/tests/unit/chatcore-upstream-body.test.ts b/tests/unit/chatcore-upstream-body.test.ts
index 7d39ee1c4d..1348300cba 100644
--- a/tests/unit/chatcore-upstream-body.test.ts
+++ b/tests/unit/chatcore-upstream-body.test.ts
@@ -1,8 +1,8 @@
// tests/unit/chatcore-upstream-body.test.ts
// Characterization of prepareUpstreamBody — the first internal sub-slice of executeProviderRequest
// (chatCore god-file decomposition, #3501). Uses a fresh temp DB (no payload rules / no detected
-// tool limits → defaults). Locks: target-model pinning, the Qwen OAuth user backfill (and its
-// guards), and the prompt_cache_key gating (excluded providers + non-OPENAI format never inject).
+// tool limits → defaults). Locks: target-model pinning and the prompt_cache_key gating
+// (excluded providers + non-OPENAI format never inject).
import { test, before, after } from "node:test";
import assert from "node:assert/strict";
import fs from "node:fs";
@@ -232,39 +232,6 @@ test("preserves the full tool list when within the grok-cli limit", async () =>
assert.equal(out.tools.length, 150);
});
-test("backfills the Qwen OAuth user when missing", async () => {
- const out = await prepareUpstreamBody({
- translatedBody: { model: "qwen-max", messages: [] },
- modelToCall: "qwen-max",
- provider: "qwen",
- targetFormat: "claude",
- credentials: { accessToken: "tok-123" },
- });
- assert.equal(out.user, "omniroute-qwen-oauth");
-});
-
-test("does not backfill the Qwen user when an apiKey is present (API-key mode)", async () => {
- const out = await prepareUpstreamBody({
- translatedBody: { model: "qwen-max", messages: [] },
- modelToCall: "qwen-max",
- provider: "qwen",
- targetFormat: "claude",
- credentials: { apiKey: "k", accessToken: "tok-123" },
- });
- assert.equal(out.user, undefined);
-});
-
-test("does not backfill the Qwen user when one is already set", async () => {
- const out = await prepareUpstreamBody({
- translatedBody: { model: "qwen-max", messages: [], user: "real-user" },
- modelToCall: "qwen-max",
- provider: "qwen",
- targetFormat: "claude",
- credentials: { accessToken: "tok-123" },
- });
- assert.equal(out.user, "real-user");
-});
-
test("never injects prompt_cache_key for an excluded provider (codex)", async () => {
const out = await prepareUpstreamBody({
translatedBody: { model: "gpt-5-codex", messages: [{ role: "user", content: "hi" }] },
diff --git a/tests/unit/check-public-creds.test.ts b/tests/unit/check-public-creds.test.ts
index 2e3acbbcce..8d25f7dec3 100644
--- a/tests/unit/check-public-creds.test.ts
+++ b/tests/unit/check-public-creds.test.ts
@@ -46,7 +46,7 @@ test('does NOT flag empty-string fallback (process.env || "")', () => {
});
test("does NOT flag an *Env key — it carries the env-var NAME, not the secret", () => {
- const src = `clientIdEnv: "QWEN_OAUTH_CLIENT_ID",`;
+ const src = `clientIdEnv: "EXAMPLE_OAUTH_CLIENT_ID",`;
assert.deepEqual(findLiteralCreds(src, new Set(), "x.ts"), []);
});
@@ -114,8 +114,8 @@ test("every frozen literal is actually present in a scanned file (no dead allowl
});
test("with an empty allowlist the real scanned files surface zero violations (all migrated to resolvePublicCred)", () => {
- // All five public client_ids (9 call-sites) were migrated to resolvePublicCred() in
- // #3493, so neither anchor file has literal credentials anymore.
+ // Public client_ids were migrated to resolvePublicCred() in #3493, so neither
+ // anchor file has literal credentials anymore.
const reg = fs.readFileSync(
path.join(repoRoot, "open-sse/config/providerRegistry.ts"),
"utf8"
diff --git a/tests/unit/check-tool-config-status.test.ts b/tests/unit/check-tool-config-status.test.ts
index ae8db8a583..598bdd5354 100644
--- a/tests/unit/check-tool-config-status.test.ts
+++ b/tests/unit/check-tool-config-status.test.ts
@@ -96,25 +96,6 @@ test("codex: returns 'not_configured' when TOML does not mention OmniRoute", asy
assert.equal(result, "not_configured");
});
-// ── Qwen tests ────────────────────────────────────────────────────────────────
-
-test("qwen: returns 'configured' when modelProviders has OmniRoute URL", async () => {
- const configPath = await writeTempFile(
- "qwen.json",
- JSON.stringify({
- modelProviders: [{ apiBase: "http://localhost:20128/v1", name: "omniroute" }],
- })
- );
- const result = await checkToolConfigStatus("qwen", configPath);
- assert.equal(result, "configured");
-});
-
-test("qwen: returns 'not_configured' when modelProviders is missing", async () => {
- const configPath = await writeTempFile("qwen.json", JSON.stringify({}));
- const result = await checkToolConfigStatus("qwen", configPath);
- assert.equal(result, "not_configured");
-});
-
// ── Hermes tests ──────────────────────────────────────────────────────────────
test("hermes: returns 'configured' when config contains OmniRoute", async () => {
@@ -176,6 +157,44 @@ test("kilo: returns 'not_configured' when no OmniRoute markers present", async (
assert.equal(result, "not_configured");
});
+// ── Qwen Code ────────────────────────────────────────────────────────────────
+
+test("qwen: returns 'configured' only for an OmniRoute-managed model entry", async () => {
+ const configPath = await writeTempFile(
+ "settings.json",
+ JSON.stringify({
+ modelProviders: {
+ openai: [
+ {
+ id: "model-id",
+ envKey: "OMNIROUTE_API_KEY",
+ baseUrl: "http://localhost:20128/v1",
+ },
+ ],
+ },
+ })
+ );
+ assert.equal(await checkToolConfigStatus("qwen", configPath), "configured");
+});
+
+test("qwen: does not misclassify an unrelated custom OpenAI endpoint", async () => {
+ const configPath = await writeTempFile(
+ "settings.json",
+ JSON.stringify({
+ modelProviders: {
+ openai: [
+ {
+ id: "custom-model",
+ envKey: "CUSTOM_API_KEY",
+ baseUrl: "https://custom.example/v1",
+ },
+ ],
+ },
+ })
+ );
+ assert.equal(await checkToolConfigStatus("qwen", configPath), "not_configured");
+});
+
// ── Edge cases ────────────────────────────────────────────────────────────────
test("error path: non-existent file returns 'not_configured' (no throw)", async () => {
diff --git a/tests/unit/claude-oauth-provider.test.ts b/tests/unit/claude-oauth-provider.test.ts
index 8879bf20e1..dfcf6212d5 100644
--- a/tests/unit/claude-oauth-provider.test.ts
+++ b/tests/unit/claude-oauth-provider.test.ts
@@ -8,7 +8,6 @@ import assert from "node:assert/strict";
Object.assign(process.env, {
CLAUDE_OAUTH_CLIENT_ID: "9d1c250a-e61b-44d9-88ed-5944d1962f5e",
CODEX_OAUTH_CLIENT_ID: "app_EMoamEEZ73f0CkXaXp7hrann",
- QWEN_OAUTH_CLIENT_ID: "f0304373b74a44d2b584a3fb70ca9e56",
KIMI_CODING_OAUTH_CLIENT_ID: "17e5f671-d194-4dfb-9706-5516cb48c098",
GITHUB_OAUTH_CLIENT_ID: "Iv1.b507a08c87ecfe98",
});
diff --git a/tests/unit/cli-catalog-acpspawnable.test.ts b/tests/unit/cli-catalog-acpspawnable.test.ts
index 06e79f76ce..43f839bde0 100644
--- a/tests/unit/cli-catalog-acpspawnable.test.ts
+++ b/tests/unit/cli-catalog-acpspawnable.test.ts
@@ -16,11 +16,11 @@ const ACP_SPAWNABLE_IDS = [
"aider",
"opencode",
"cline",
- "qwen",
"forge",
"interpreter",
"cursor-cli",
"warp",
+ "qwen",
];
for (const id of ACP_SPAWNABLE_IDS) {
diff --git a/tests/unit/cli-catalog-counts.test.ts b/tests/unit/cli-catalog-counts.test.ts
index c6f203d928..e6ab441719 100644
--- a/tests/unit/cli-catalog-counts.test.ts
+++ b/tests/unit/cli-catalog-counts.test.ts
@@ -66,7 +66,7 @@ test("All agent entries have baseUrlSupport 'full' or 'partial' (no agent is 'no
}
});
-test("The 21 visible code entries match D15 list exactly (+ crush + codewhale + grok-build)", () => {
+test("The 21 visible code entries include Qwen Code's rebuilt integration", () => {
const d15List = new Set([
"claude",
"codex",
@@ -74,7 +74,6 @@ test("The 21 visible code entries match D15 list exactly (+ crush + codewhale +
"kilo",
"roo",
"continue",
- "qwen",
"aider",
"forge",
"jcode",
@@ -89,6 +88,7 @@ test("The 21 visible code entries match D15 list exactly (+ crush + codewhale +
"custom",
"crush",
"grok-build",
+ "qwen",
]);
const visibleIds = new Set(codeVisible.map((t) => t.id));
for (const id of d15List) {
diff --git a/tests/unit/cli-catalog-schema.test.ts b/tests/unit/cli-catalog-schema.test.ts
index 2a263e7d22..48425d759e 100644
--- a/tests/unit/cli-catalog-schema.test.ts
+++ b/tests/unit/cli-catalog-schema.test.ts
@@ -8,9 +8,8 @@ import assert from "node:assert/strict";
import { z } from "zod";
const { CLI_TOOLS } = await import("../../src/shared/constants/cliTools.ts");
-const { CliCatalogEntrySchema, CliCatalogSchema } = await import(
- "../../src/shared/schemas/cliCatalog.ts"
-);
+const { CliCatalogEntrySchema, CliCatalogSchema } =
+ await import("../../src/shared/schemas/cliCatalog.ts");
test("Every CLI_TOOLS entry passes CliCatalogEntrySchema.parse() without error", () => {
for (const [key, tool] of Object.entries(CLI_TOOLS)) {
@@ -62,7 +61,7 @@ test("CliCatalogEntrySchema throws ZodError for invalid baseUrlSupport value", (
});
test("CliCatalogEntrySchema throws ZodError when required string fields are empty", () => {
- const base = { ...CLI_TOOLS["qwen"] };
+ const base = { ...CLI_TOOLS["opencode"] };
const invalid = { ...base, vendor: "" };
assert.throws(
() => CliCatalogEntrySchema.parse(invalid),
diff --git a/tests/unit/cli-tools-schema.test.ts b/tests/unit/cli-tools-schema.test.ts
index 85841732cb..92d3944882 100644
--- a/tests/unit/cli-tools-schema.test.ts
+++ b/tests/unit/cli-tools-schema.test.ts
@@ -1,7 +1,7 @@
import test from "node:test";
import assert from "node:assert/strict";
-test("CLI_TOOLS registry contains all expected tools (plan 14 — 33 total + crush + codewhale + omp + letta + grok-build)", async () => {
+test("CLI_TOOLS registry contains all expected tools including rebuilt Qwen Code", async () => {
const { CLI_TOOLS } = await import("../../src/shared/constants/cliTools.ts");
// windsurf and amp removed per plan 14 D17 (MITM backlog plan 11)
// New entries added: roo, jcode, deepseek-tui, smelt, pi, aider, forge,
@@ -26,7 +26,6 @@ test("CLI_TOOLS registry contains all expected tools (plan 14 — 33 total + cru
"hermes",
"hermes-agent",
"kiro",
- "qwen",
"custom",
"aider",
"forge",
@@ -45,11 +44,13 @@ test("CLI_TOOLS registry contains all expected tools (plan 14 — 33 total + cru
"agent-deck",
"crush",
"grok-build",
+ "qwen",
];
for (const id of expected) {
assert.ok(id in CLI_TOOLS, `Missing tool: ${id}`);
}
assert.equal(Object.keys(CLI_TOOLS).length, expected.length);
+ assert.equal(CLI_TOOLS.qwen.previewConfigMode, "qwen");
// Confirm removed entries are gone
assert.equal((CLI_TOOLS as Record)["windsurf"], undefined);
assert.equal((CLI_TOOLS as Record)["amp"], undefined);
diff --git a/tests/unit/cli/setup-qwen.test.ts b/tests/unit/cli/setup-qwen.test.ts
index c56cfa1a11..e8086961de 100644
--- a/tests/unit/cli/setup-qwen.test.ts
+++ b/tests/unit/cli/setup-qwen.test.ts
@@ -1,28 +1,85 @@
-import { test } from "node:test";
+import test from "node:test";
import assert from "node:assert/strict";
-import { resolveQwenTarget, buildQwenSettings } from "../../../bin/cli/commands/setup-qwen.mjs";
+import fs from "node:fs/promises";
+import os from "node:os";
+import path from "node:path";
-test("resolveQwenTarget ensures /v1", () => {
- assert.equal(resolveQwenTarget({ remote: "http://vps:20128" }).baseUrl, "http://vps:20128/v1");
+import { resolveQwenTarget, runSetupQwenCommand } from "../../../bin/cli/commands/setup-qwen.mjs";
+
+test("resolveQwenTarget normalizes a remote endpoint to the OpenAI /v1 base", () => {
+ assert.deepEqual(resolveQwenTarget({ remote: "https://omni.example/", apiKey: "sk-explicit" }), {
+ baseUrl: "https://omni.example/v1",
+ apiKey: "sk-explicit",
+ });
});
-test("resolveQwenTarget: explicit --api-key wins", () => {
- assert.equal(resolveQwenTarget({ remote: "http://x:20128", apiKey: "sk-x" }).apiKey, "sk-x");
-});
-test("buildQwenSettings adds an openai modelProvider (baseUrl /v1, envKey), sets model", () => {
- const s = buildQwenSettings({}, { baseUrl: "http://vps:20128/v1", model: "glm/glm-5.2" });
- const p = s.modelProviders.find((x) => x.id === "omniroute");
- assert.equal(p.authType, "openai");
- assert.equal(p.baseUrl, "http://vps:20128/v1");
- assert.equal(p.envKey, "OMNIROUTE_API_KEY");
- assert.equal(s.model, "glm/glm-5.2");
- assert.equal(s.selectedProvider, "omniroute");
-});
-test("buildQwenSettings de-dupes the omniroute provider + preserves others", () => {
- const s = buildQwenSettings(
- { modelProviders: [{ id: "other" }, { id: "omniroute", baseUrl: "old" }], theme: "dark" },
- { baseUrl: "http://x/v1", model: "m" }
+
+test("setup-qwen writes current V4 settings and only its dedicated env key", async () => {
+ const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "omniroute-setup-qwen-"));
+ const settingsPath = path.join(tempDir, "settings.json");
+ const envPath = path.join(tempDir, ".env");
+ await fs.writeFile(
+ settingsPath,
+ JSON.stringify({
+ ui: { theme: "dark" },
+ modelProviders: {
+ openai: [
+ {
+ id: "personal",
+ envKey: "OPENAI_API_KEY",
+ baseUrl: "https://api.openai.com/v1",
+ },
+ ],
+ },
+ })
);
- assert.equal(s.modelProviders.filter((p) => p.id === "omniroute").length, 1);
- assert.ok(s.modelProviders.some((p) => p.id === "other"));
- assert.equal(s.theme, "dark");
+ await fs.writeFile(envPath, "OPENAI_API_KEY=keep-me\n");
+
+ try {
+ const code = await runSetupQwenCommand({
+ remote: "http://router:20128",
+ apiKey: "sk-qwen-dedicated",
+ model: "qwen/qwen3.8-max-preview",
+ configPath: settingsPath,
+ envPath,
+ yes: true,
+ });
+ assert.equal(code, 0);
+
+ const settings = JSON.parse(await fs.readFile(settingsPath, "utf8"));
+ assert.equal(settings.ui.theme, "dark");
+ assert.equal(Array.isArray(settings.modelProviders.openai), true);
+ assert.equal(settings.modelProviders.openai.length, 2);
+ assert.deepEqual(settings.modelProviders.openai[1], {
+ id: "qwen/qwen3.8-max-preview",
+ name: "qwen/qwen3.8-max-preview (OmniRoute)",
+ envKey: "OMNIROUTE_API_KEY",
+ baseUrl: "http://router:20128/v1",
+ });
+ assert.equal(JSON.stringify(settings).includes("sk-qwen-dedicated"), false);
+
+ const env = await fs.readFile(envPath, "utf8");
+ assert.match(env, /^OPENAI_API_KEY=keep-me$/m);
+ assert.match(env, /^OMNIROUTE_API_KEY="sk-qwen-dedicated"$/m);
+ } finally {
+ await fs.rm(tempDir, { recursive: true, force: true });
+ }
+});
+
+test("setup-qwen does not overwrite an invalid settings file", async () => {
+ const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "omniroute-setup-qwen-bad-"));
+ const settingsPath = path.join(tempDir, "settings.json");
+ await fs.writeFile(settingsPath, "{ invalid JSON");
+
+ try {
+ const code = await runSetupQwenCommand({
+ remote: "http://router:20128",
+ model: "model-id",
+ configPath: settingsPath,
+ yes: true,
+ });
+ assert.equal(code, 1);
+ assert.equal(await fs.readFile(settingsPath, "utf8"), "{ invalid JSON");
+ } finally {
+ await fs.rm(tempDir, { recursive: true, force: true });
+ }
});
diff --git a/tests/unit/cliproxy-auth-import-1934.test.ts b/tests/unit/cliproxy-auth-import-1934.test.ts
index 9bd32c845a..2f664c9330 100644
--- a/tests/unit/cliproxy-auth-import-1934.test.ts
+++ b/tests/unit/cliproxy-auth-import-1934.test.ts
@@ -55,10 +55,10 @@ test("parseCliProxyAuthRecord returns null for unknown type or missing access to
});
test("every CLIPROXY_TYPE_TO_PROVIDER target is a real OAuth provider id", () => {
- // codex/antigravity/claude/qwen/kimi are all OmniRoute providers
+ // codex/antigravity/claude/kimi are all OmniRoute providers
for (const provider of Object.values(CLIPROXY_TYPE_TO_PROVIDER)) {
assert.ok(
- ["claude", "codex", "antigravity", "qwen", "kimi"].includes(provider),
+ ["claude", "codex", "antigravity", "kimi"].includes(provider),
`unexpected provider mapping: ${provider}`
);
}
diff --git a/tests/unit/codex-failover.test.ts b/tests/unit/codex-failover.test.ts
index 0546ab9980..9de478411e 100644
--- a/tests/unit/codex-failover.test.ts
+++ b/tests/unit/codex-failover.test.ts
@@ -60,11 +60,9 @@ test("extractSessionAffinityKey returns null for empty input", () => {
// ── Test 2: maxAttempts logic — codex gets 3, other providers get 1 ──────────
test("codex maxAttempts is 3 (validates retry budget for account rotation)", () => {
// This mirrors the logic: provider === "codex" ? 3 : 1
- const getMaxAttempts = (provider: string) =>
- provider === "qwen" ? 3 : provider === "codex" ? 3 : 1;
+ const getMaxAttempts = (provider: string) => (provider === "codex" ? 3 : 1);
assert.equal(getMaxAttempts("codex"), 3, "Codex should have 3 max attempts");
- assert.equal(getMaxAttempts("qwen"), 3, "Qwen should have 3 max attempts (unchanged)");
assert.equal(getMaxAttempts("openai"), 1, "OpenAI should have 1 max attempt (unchanged)");
assert.equal(getMaxAttempts("anthropic"), 1, "Anthropic should have 1 max attempt (unchanged)");
assert.equal(getMaxAttempts("gemini"), 1, "Gemini should have 1 max attempt (unchanged)");
diff --git a/tests/unit/compression/cachingAware.test.ts b/tests/unit/compression/cachingAware.test.ts
index cf854f3522..4e69580f5b 100644
--- a/tests/unit/compression/cachingAware.test.ts
+++ b/tests/unit/compression/cachingAware.test.ts
@@ -83,7 +83,7 @@ describe("detectCachingContext", () => {
{
tools: [{ name: "lookup", cache_control: { type: "ephemeral" } }],
},
- { provider: "qwen", targetFormat: "claude" }
+ { provider: "bailian-coding-plan", targetFormat: "claude" }
);
assert.equal(ctx.hasCacheControl, true);
diff --git a/tests/unit/executor-default-base.test.ts b/tests/unit/executor-default-base.test.ts
index 7382e932dd..f73f66be58 100644
--- a/tests/unit/executor-default-base.test.ts
+++ b/tests/unit/executor-default-base.test.ts
@@ -74,10 +74,9 @@ test("BaseExecutor: legacy openai-compatible providers honor providerSpecificDat
assert.equal(url, "https://proxy.example/v1/responses");
});
-test("DefaultExecutor.buildUrl handles Gemini, Claude and Qwen variants", () => {
+test("DefaultExecutor.buildUrl handles Gemini and Claude variants", () => {
const gemini = new DefaultExecutor("gemini");
const claude = new DefaultExecutor("claude");
- const qwen = new DefaultExecutor("qwen");
assert.equal(
gemini.buildUrl("gemini-2.5-flash", false),
@@ -88,13 +87,6 @@ test("DefaultExecutor.buildUrl handles Gemini, Claude and Qwen variants", () =>
"https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:streamGenerateContent?alt=sse"
);
assert.equal(claude.buildUrl("claude-sonnet-4", true), `${PROVIDERS.claude.baseUrl}?beta=true`);
- assert.equal(qwen.buildUrl("qwen3-coder", true), "https://portal.qwen.ai/v1/chat/completions");
- assert.equal(
- qwen.buildUrl("qwen3-coder", true, 0, {
- providerSpecificData: { resourceUrl: "custom.qwen.ai" },
- }),
- "https://custom.qwen.ai/v1/chat/completions"
- );
});
test("DefaultExecutor.buildUrl uses full chat endpoints for hosted OpenAI-compatible providers", () => {
@@ -530,22 +522,6 @@ test("DefaultExecutor.buildHeaders handles Snowflake PATs and GigaChat access to
assert.equal(gigachatHeaders.Authorization, "Bearer gigachat-token");
});
-test("DefaultExecutor.buildHeaders strips DashScope headers for Qwen API keys and preserves them for OAuth", () => {
- const executor = new DefaultExecutor("qwen");
-
- const apiKeyHeaders = executor.buildHeaders({ apiKey: "dash-key" }, true);
- const oauthHeaders = executor.buildHeaders({ accessToken: "oauth-token" }, true);
-
- assert.equal(apiKeyHeaders.Authorization, "Bearer dash-key");
- assert.equal(
- Object.keys(apiKeyHeaders).some((key) => key.toLowerCase().startsWith("x-dashscope-")),
- false
- );
- assert.equal(oauthHeaders.Authorization, "Bearer oauth-token");
- assert.equal(oauthHeaders["X-Dashscope-AuthType"], "qwen-oauth");
- assert.equal(oauthHeaders["X-Dashscope-CacheControl"], "enable");
-});
-
test("DefaultExecutor.buildHeaders rotates extra API keys and builds Claude Code compatible headers", () => {
const openai = new DefaultExecutor("openai");
const cc = new DefaultExecutor("anthropic-compatible-cc-test");
@@ -1004,19 +980,6 @@ test("DefaultExecutor.transformRequest strips stream_options from Anthropic-comp
assert.equal((ccResult as any).stream_options, undefined);
});
-test("DefaultExecutor.transformRequest neutralizes incompatible tool_choice for Qwen thinking", () => {
- const executor = new DefaultExecutor("qwen");
- const body = {
- messages: [{ role: "user", content: "hi" }],
- thinking: { type: "enabled" },
- tool_choice: { type: "function", function: { name: "pwd" } },
- };
- const result = executor.transformRequest("qwen3-coder-plus", body, true, {});
-
- assert.notEqual(result, body);
- assert.equal((result as any).tool_choice, "auto");
-});
-
// Port of decolua/9router#1343: openai-compatible-* providers (DeepSeek / Ollama /
// local OpenAI-compatible models) often lack native Structured Output, so a
// `json_schema` response_format is downgraded to `json_object` with the schema
diff --git a/tests/unit/free-model-catalog.test.ts b/tests/unit/free-model-catalog.test.ts
index 96e11e3f3d..07b4da72bd 100644
--- a/tests/unit/free-model-catalog.test.ts
+++ b/tests/unit/free-model-catalog.test.ts
@@ -85,7 +85,7 @@ test("2026-06-17 refresh: discontinued providers dropped, new free providers add
for (const dead of ["chutes", "phind", "kluster", "gitlawb", "aimlapi", "theoldllm"]) {
assert.ok(!providers.has(dead), `${dead} should be removed (discontinued)`);
}
- // qwen-web is KEPT on purpose: only its OAuth API tier died — OmniRoute uses the cookie/web path.
+ // qwen-web is kept because it uses its own cookie/web path.
assert.ok(providers.has("qwen-web"), "qwen-web must stay (cookie path still free)");
// discovered in the refresh — must be present
for (const fresh of ["kilo-gateway", "opencode-zen", "glm-cn"]) {
diff --git a/tests/unit/guide-settings-route.test.ts b/tests/unit/guide-settings-route.test.ts
index b6648d2d79..8537cbc56c 100644
--- a/tests/unit/guide-settings-route.test.ts
+++ b/tests/unit/guide-settings-route.test.ts
@@ -9,9 +9,7 @@ import * as yaml from "js-yaml";
const guideSettingsRoute =
await import("../../src/app/api/cli-tools/guide-settings/[toolId]/route.ts");
-const DUMMY_HOME = path.join(os.tmpdir(), "omniroute-qwen-test-" + Date.now());
-const QWEN_CONFIG_PATH = path.join(DUMMY_HOME, ".qwen", "settings.json");
-const QWEN_ENV_PATH = path.join(DUMMY_HOME, ".qwen", ".env");
+const DUMMY_HOME = path.join(os.tmpdir(), "omniroute-guide-settings-test-" + Date.now());
const OPENCODE_CONFIG_PATH = path.join(DUMMY_HOME, ".config", "opencode", "opencode.json");
// cliRuntime.ts hermes entry maps to .config/hermes/config.json (not .hermes/config.yaml)
const HERMES_CONFIG_PATH = path.join(DUMMY_HOME, ".config", "hermes", "config.json");
@@ -31,18 +29,14 @@ async function createAuthCookie() {
return `auth_token=${token}`;
}
-type QwenProviderEntry = {
- id?: string;
- baseUrl?: string;
- envKey?: string;
- generationConfig?: {
- contextWindowSize?: number;
- };
+type HermesConfig = {
+ model?: { default?: string; provider?: string; base_url?: string };
+ providers?: { omniroute?: { base_url?: string; api_key?: string } };
};
-async function buildRequest(body: any) {
+async function buildRequest(toolId: string, body: unknown) {
const cookie = await createAuthCookie();
- return new Request("http://localhost/api/cli-tools/guide-settings/qwen", {
+ return new Request(`http://localhost/api/cli-tools/guide-settings/${toolId}`, {
method: "POST",
headers: { "Content-Type": "application/json", cookie },
body: JSON.stringify(body),
@@ -57,7 +51,6 @@ test.beforeEach(async () => {
process.env.XDG_CONFIG_HOME = path.join(DUMMY_HOME, ".config");
process.env.APPDATA = path.join(DUMMY_HOME, ".config");
process.env.API_KEY_SECRET = "test-secret";
- await fs.mkdir(path.dirname(QWEN_CONFIG_PATH), { recursive: true }).catch(() => {});
});
test.afterEach(async () => {
@@ -70,28 +63,8 @@ test.afterEach(async () => {
else process.env.JWT_SECRET = originalJwtSecret;
});
-test("guide-settings POST creates new qwen settings.json if it doesn't exist", async () => {
- const req = await buildRequest({
- baseUrl: "http://my-omni",
- apiKey: "sk-123",
- model: "qwen/qwen3-coder-plus",
- });
- const response = (await guideSettingsRoute.POST(req, { params: { toolId: "qwen" } })) as Response;
- const data = (await response.json()) as any;
-
- assert.equal(response.status, 200, "Response should be OK");
- assert.equal(data.success, true);
-
- const content = JSON.parse(await fs.readFile(QWEN_CONFIG_PATH, "utf-8"));
- // Uses security.auth format (not modelProviders)
- assert.equal(content.security?.auth?.selectedType, "openai");
- assert.ok(content.security?.auth?.apiKey.startsWith("sk-"));
- assert.equal(content.security?.auth?.baseUrl, "http://my-omni");
- assert.equal(content.model?.name, "qwen/qwen3-coder-plus");
-});
-
test("guide-settings POST creates new hermes config.yaml if it doesn't exist", async () => {
- const req = await buildRequest({
+ const req = await buildRequest("hermes", {
baseUrl: "http://my-omni",
apiKey: "sk-hermes",
model: "gpt-5.4-mini",
@@ -99,12 +72,12 @@ test("guide-settings POST creates new hermes config.yaml if it doesn't exist", a
const response = (await guideSettingsRoute.POST(req, {
params: { toolId: "hermes" },
})) as Response;
- const data = (await response.json()) as any;
+ const data = (await response.json()) as { success?: boolean };
assert.equal(response.status, 200, "Response should be OK");
assert.equal(data.success, true);
- const content = yaml.load(await fs.readFile(HERMES_CONFIG_PATH, "utf-8")) as any;
+ const content = yaml.load(await fs.readFile(HERMES_CONFIG_PATH, "utf-8")) as HermesConfig;
assert.equal(content.model?.default, "gpt-5.4-mini");
assert.equal(content.model?.provider, "omniroute");
assert.equal(content.model?.base_url, "http://my-omni/v1");
@@ -112,34 +85,6 @@ test("guide-settings POST creates new hermes config.yaml if it doesn't exist", a
assert.ok(String(content.providers?.omniroute?.api_key || "").startsWith("sk-"));
});
-test("guide-settings POST merges into existing qwen settings.json", async () => {
- await fs.mkdir(path.dirname(QWEN_CONFIG_PATH), { recursive: true });
- await fs.writeFile(
- QWEN_CONFIG_PATH,
- JSON.stringify({
- permissions: { allow: ["Bash(*)"] },
- }),
- "utf-8"
- );
-
- const req = await buildRequest({
- baseUrl: "http://my-omni",
- apiKey: "sk-456",
- model: "claude-sonnet-4-6",
- });
- const response = await guideSettingsRoute.POST(req, { params: { toolId: "qwen" } });
- assert.equal(response.status, 200);
-
- const content = JSON.parse(await fs.readFile(QWEN_CONFIG_PATH, "utf-8"));
- // security.auth format
- assert.equal(content.security?.auth?.selectedType, "openai");
- assert.ok(content.security?.auth?.apiKey.startsWith("sk-"));
- assert.equal(content.security?.auth?.baseUrl, "http://my-omni");
- assert.equal(content.model?.name, "claude-sonnet-4-6");
- // Preserves other settings
- assert.deepEqual(content.permissions?.allow, ["Bash(*)"]);
-});
-
test("guide-settings POST writes OpenCode config with current schema and multi-model selection", async () => {
await fs.mkdir(path.dirname(OPENCODE_CONFIG_PATH), { recursive: true });
await fs.writeFile(
diff --git a/tests/unit/oauth-device-code-endpoints.test.ts b/tests/unit/oauth-device-code-endpoints.test.ts
index 325a1b259d..13d15e5c89 100644
--- a/tests/unit/oauth-device-code-endpoints.test.ts
+++ b/tests/unit/oauth-device-code-endpoints.test.ts
@@ -4,11 +4,8 @@ import { readFileSync } from "node:fs";
import { fileURLToPath } from "node:url";
import { dirname, resolve } from "node:path";
-// Regression guards for the two device-code providers that returned "ошибка сервера OmniRoute":
+// Regression guard for the device-code provider that returned "ошибка сервера OmniRoute":
//
-// qwen — QWEN_CONFIG pointed at the bare host qwen.ai, whose device/token paths 404.
-// The working qwen-code device flow lives at chat.qwen.ai (verified live: 200 +
-// a valid device_code). Guard: the config uses chat.qwen.ai, never bare qwen.ai.
// codebuddy-cn— the Tencent state endpoint reads `platform` from the QUERY string, not the JSON
// body; body-only returned 400 "platform is empty" (verified live). The fix passes
// it as a query param. Guard: requestDeviceCode builds the URL with ?platform=.
@@ -18,15 +15,11 @@ import { dirname, resolve } from "node:path";
const here = dirname(fileURLToPath(import.meta.url));
const read = (p: string) => readFileSync(resolve(here, "../..", p), "utf8");
-test("qwen device-code config uses chat.qwen.ai (not the 404ing bare qwen.ai)", () => {
- const oauth = read("src/lib/oauth/constants/oauth.ts");
- const qwenBlock = oauth.slice(oauth.indexOf("QWEN_CONFIG"), oauth.indexOf("QWEN_CONFIG") + 700);
- assert.match(qwenBlock, /chat\.qwen\.ai\/api\/v1\/oauth2\/device\/code/, "deviceCodeUrl host");
- assert.match(qwenBlock, /chat\.qwen\.ai\/api\/v1\/oauth2\/token/, "tokenUrl host");
- assert.doesNotMatch(qwenBlock, /"https:\/\/qwen\.ai\//, "must not use the bare qwen.ai host");
-});
-
test("codebuddy-cn device-code sends platform as a query param (not body-only)", () => {
const cb = read("src/lib/oauth/providers/codebuddy-cn.ts");
- assert.match(cb, /\?platform=\$\{encodeURIComponent\(config\.platform\)\}/, "platform query param");
+ assert.match(
+ cb,
+ /\?platform=\$\{encodeURIComponent\(config\.platform\)\}/,
+ "platform query param"
+ );
});
diff --git a/tests/unit/oauth-providers-config.test.ts b/tests/unit/oauth-providers-config.test.ts
index 2208c22b77..9f83711529 100644
--- a/tests/unit/oauth-providers-config.test.ts
+++ b/tests/unit/oauth-providers-config.test.ts
@@ -9,7 +9,6 @@ Object.assign(process.env, {
CLAUDE_OAUTH_CLIENT_ID: "9d1c250a-e61b-44d9-88ed-5944d1962f5e",
CODEX_OAUTH_CLIENT_ID: "app_EMoamEEZ73f0CkXaXp7hrann",
GITLAB_DUO_OAUTH_CLIENT_ID: "gitlab-duo-client-id",
- QWEN_OAUTH_CLIENT_ID: "f0304373b74a44d2b584a3fb70ca9e56",
KIMI_CODING_OAUTH_CLIENT_ID: "17e5f671-d194-4dfb-9706-5516cb48c098",
KIMI_CODING_DEVICE_ID: "test-kimi-device-id",
GITHUB_OAUTH_CLIENT_ID: "Iv1.b507a08c87ecfe98",
@@ -41,7 +40,6 @@ const {
OAUTH_TIMEOUT,
PROVIDERS: OAUTH_PROVIDER_IDS,
QODER_CONFIG,
- QWEN_CONFIG,
TRAE_CONFIG,
WINDSURF_CONFIG,
XAI_OAUTH_CONFIG,
@@ -57,7 +55,6 @@ const EXPECTED_PROVIDER_KEYS = [
"antigravity",
"agy",
"qoder",
- "qwen",
"kimi-coding",
"github",
"ghe-copilot",
@@ -91,7 +88,6 @@ const EXPECTED_CONFIG_BY_PROVIDER = {
antigravity: ANTIGRAVITY_CONFIG,
agy: AGY_CONFIG,
qoder: QODER_CONFIG,
- qwen: QWEN_CONFIG,
"kimi-coding": KIMI_CODING_CONFIG,
github: GITHUB_CONFIG,
"ghe-copilot": GHE_COPILOT_CONFIG,
@@ -129,7 +125,6 @@ const REQUIRED_FIELDS_BY_PROVIDER = {
antigravity: ["authorizeUrl", "tokenUrl", "userInfoUrl", "scopes", "clientId"],
agy: ["authorizeUrl", "tokenUrl", "userInfoUrl", "scopes", "clientId"],
qoder: ["extraParams"],
- qwen: ["deviceCodeUrl", "tokenUrl", "scope", "clientId"],
"kimi-coding": ["deviceCodeUrl", "tokenUrl", "clientId"],
github: ["deviceCodeUrl", "tokenUrl", "userInfoUrl", "copilotTokenUrl", "clientId"],
// GHE Copilot derives its URLs at runtime from the per-connection gheUrl — only static fields.
@@ -315,20 +310,6 @@ test("all provider endpoint URLs use HTTPS when a URL is configured", () => {
}
});
-test("Qwen OAuth uses chat.qwen.ai (not bare qwen.ai) for device/token URLs — #7517 supersedes #683/#572", () => {
- // Upstream PR #683 / decolua issue #572 originally pointed these at the bare `qwen.ai` host.
- // #7517 (2026-07-18) live-verified that host 404s on both paths ("ошибка сервера OmniRoute" in
- // the companion extension) and that the working qwen-code device flow lives at `chat.qwen.ai`
- // (verified: 200 + a valid device_code/user_code). See tests/unit/oauth-device-code-endpoints.test.ts
- // for the companion regression guard.
- const deviceUrl = new URL(QWEN_CONFIG.deviceCodeUrl);
- const tokenUrl = new URL(QWEN_CONFIG.tokenUrl);
- assert.equal(deviceUrl.hostname, "chat.qwen.ai", "deviceCodeUrl must use chat.qwen.ai");
- assert.equal(tokenUrl.hostname, "chat.qwen.ai", "tokenUrl must use chat.qwen.ai");
- assert.equal(deviceUrl.pathname, "/api/v1/oauth2/device/code");
- assert.equal(tokenUrl.pathname, "/api/v1/oauth2/token");
-});
-
test("browser-based providers expose buildAuthUrl and return provider-specific auth URLs", () => {
const redirectUri = "http://localhost:43121/callback";
const state = "state-123";
@@ -460,7 +441,7 @@ test("Google OAuth callbacks stay on localhost when no custom credentials are co
});
test("device and import-token providers expose the flow-specific fields expected by their configs", () => {
- const deviceProviders = ["qwen", "kimi-coding", "github", "kiro", "amazon-q", "kilocode"];
+ const deviceProviders = ["kimi-coding", "github", "kiro", "amazon-q", "kilocode"];
for (const providerId of deviceProviders) {
const provider = PROVIDERS[providerId];
@@ -672,27 +653,8 @@ test("Qoder enabled mode exchanges tokens and loads profile metadata through moc
}
});
-test("Qwen and Kimi Coding execute mocked device-code flows and token mapping", async () => {
- const qwenIdToken = createJwt({
- email: "qwen@example.com",
- name: "Qwen User",
- });
-
+test("Kimi Coding executes mocked device-code flow and token mapping", async () => {
useFetchSequence([
- jsonResponse({
- device_code: "qwen-device",
- user_code: "QWEN123",
- verification_uri: "https://chat.qwen.ai/activate",
- expires_in: 300,
- interval: 5,
- }),
- jsonResponse({
- access_token: createJwt({ sub: "qwen-subject" }),
- refresh_token: "qwen-refresh",
- expires_in: 3600,
- id_token: qwenIdToken,
- resource_url: "https://chat.qwen.ai/resource",
- }),
(url, init) => {
const params = init.body;
assert.equal(String(url), KIMI_CODING_CONFIG.deviceCodeUrl);
@@ -701,10 +663,7 @@ test("Qwen and Kimi Coding execute mocked device-code flows and token mapping",
assert.equal(init.headers["X-Msh-Device-Id"], "test-kimi-device-id");
assert.equal(init.headers["X-Msh-Os-Version"], os.release());
if (os.type() === "Windows_NT") {
- assert.equal(
- init.headers["X-Msh-Device-Model"],
- `Windows ${os.release()} ${os.arch()}`
- );
+ assert.equal(init.headers["X-Msh-Device-Model"], `Windows ${os.release()} ${os.arch()}`);
}
return jsonResponse({
@@ -735,10 +694,6 @@ test("Qwen and Kimi Coding execute mocked device-code flows and token mapping",
},
]);
- const qwenDevice = await PROVIDERS.qwen.requestDeviceCode(QWEN_CONFIG, "challenge-123");
- const qwenPoll = await PROVIDERS.qwen.pollToken(QWEN_CONFIG, qwenDevice.device_code, "verifier");
- const qwenMapped = PROVIDERS.qwen.mapTokens(qwenPoll.data);
-
const kimiDevice = await PROVIDERS["kimi-coding"].requestDeviceCode(KIMI_CODING_CONFIG);
const kimiPoll = await PROVIDERS["kimi-coding"].pollToken(
KIMI_CODING_CONFIG,
@@ -746,9 +701,6 @@ test("Qwen and Kimi Coding execute mocked device-code flows and token mapping",
);
const kimiMapped = PROVIDERS["kimi-coding"].mapTokens(kimiPoll.data);
- assert.equal(qwenMapped.email, "qwen@example.com");
- assert.equal(qwenMapped.displayName, "Qwen User");
- assert.equal(qwenMapped.providerSpecificData.resourceUrl, "https://chat.qwen.ai/resource");
assert.equal(kimiMapped.accessToken, "kimi-access");
assert.equal(kimiMapped.tokenType, "Bearer");
assert.equal(
diff --git a/tests/unit/oauth-providers-error-handling.test.ts b/tests/unit/oauth-providers-error-handling.test.ts
index 2ec4adf0b3..fdc9644e8b 100644
--- a/tests/unit/oauth-providers-error-handling.test.ts
+++ b/tests/unit/oauth-providers-error-handling.test.ts
@@ -173,18 +173,6 @@ test("P2: refreshGoogleToken parses invalid_grant as unrecoverable", async () =>
assert.match(fnMatch[0], /unrecoverable_refresh_error/, "must return unrecoverable sentinel");
});
-// ─── P2: Qwen invalid_grant ───────────────────────────────────────────────────
-
-test("P2: refreshQwenToken handles invalid_grant in addition to invalid_request", async () => {
- // refreshQwenToken lives in its own co-located provider module since the
- // tokenRefresh.ts provider-extraction (#7338, redone on tip).
- const src = await read("open-sse/services/tokenRefresh/providers/qwen.ts");
- const fnMatch = src.match(/export\s+async\s+function\s+refreshQwenToken\([\s\S]+?\n\}/);
- assert.ok(fnMatch, "refreshQwenToken function body not found");
- assert.match(fnMatch[0], /invalid_grant/, "must detect invalid_grant");
- assert.match(fnMatch[0], /unrecoverable_refresh_error/, "must return unrecoverable sentinel");
-});
-
// ─── P2: Kiro AWS InvalidGrantException ──────────────────────────────────────
test("P2: refreshKiroToken parses AWS InvalidGrantException", async () => {
diff --git a/tests/unit/provider-alias-uniqueness.test.ts b/tests/unit/provider-alias-uniqueness.test.ts
index 25628a84cb..e51f7cc24f 100644
--- a/tests/unit/provider-alias-uniqueness.test.ts
+++ b/tests/unit/provider-alias-uniqueness.test.ts
@@ -4,14 +4,13 @@
* Before this guard, three aliases collided in the registry and the LAST entry in
* iteration order silently won, emitting a startup warning and shadowing a real
* provider:
- * - "qw" → qwen-web (shadowed qwen)
* - "kimi" → kimi-web (shadowed the kimi provider that gained a dedicated executor)
* - "hc" → hackclub (shadowed huggingchat)
*
* The decision: the primary provider keeps the short alias; the web/secondary
* variant takes its own id as alias. This test pins both the global uniqueness
* invariant (so future additions can't silently re-collide) and the specific
- * resolutions for the six affected providers, across BOTH alias sources
+ * resolutions for the affected providers, across BOTH alias sources
* (open-sse registry + src/shared providers map).
*/
import test from "node:test";
@@ -43,9 +42,8 @@ test("no two provider IDs share the same alias in the open-sse registry", () =>
);
});
-test("primary providers keep the short alias; web/secondary variants use their own id", () => {
+test("primary providers keep the short alias; web variants use their own id", () => {
// open-sse registry (source of the startup warning + chat routing)
- assert.equal(PROVIDER_ID_TO_ALIAS.qwen, "qw");
assert.equal(PROVIDER_ID_TO_ALIAS["qwen-web"], "qwen-web");
assert.equal(PROVIDER_ID_TO_ALIAS.kimi, "kimi");
assert.equal(PROVIDER_ID_TO_ALIAS["kimi-web"], "kimi-web");
@@ -55,7 +53,6 @@ test("primary providers keep the short alias; web/secondary variants use their o
test("src/shared providers map resolves the same aliases unambiguously", () => {
// alias → id
- assert.equal(resolveProviderId("qw"), "qwen");
assert.equal(resolveProviderId("kimi"), "kimi");
assert.equal(resolveProviderId("hc"), "hackclub");
// id used as alias for the secondary variants
@@ -63,7 +60,6 @@ test("src/shared providers map resolves the same aliases unambiguously", () => {
assert.equal(resolveProviderId("kimi-web"), "kimi-web");
assert.equal(resolveProviderId("huggingchat"), "huggingchat");
// id → alias
- assert.equal(getProviderAlias("qwen"), "qw");
assert.equal(getProviderAlias("kimi"), "kimi");
assert.equal(getProviderAlias("hackclub"), "hc");
});
diff --git a/tests/unit/provider-header-profiles.test.ts b/tests/unit/provider-header-profiles.test.ts
index dfb1c035d4..f5c53c2407 100644
--- a/tests/unit/provider-header-profiles.test.ts
+++ b/tests/unit/provider-header-profiles.test.ts
@@ -17,7 +17,6 @@ import {
getGitHubCopilotRefreshHeaders,
getKiroServiceHeaders,
getQoderDashscopeCompatHeaders,
- getQwenOauthHeaders,
} from "../../open-sse/config/providerHeaderProfiles.ts";
test("provider header profiles expose current GitHub chat and internal headers", () => {
@@ -37,27 +36,20 @@ test("provider header profiles expose current GitHub chat and internal headers",
assert.equal(internalHeaders["X-GitHub-Api-Version"], GITHUB_COPILOT_API_VERSION);
});
-test("provider header profiles expose dedicated refresh, qwen, qoder and kiro variants", () => {
+test("provider header profiles expose dedicated refresh, qoder and kiro variants", () => {
const refreshHeaders = getGitHubCopilotRefreshHeaders("token gh-access");
assert.equal(refreshHeaders.Authorization, "token gh-access");
assert.equal(refreshHeaders["User-Agent"], GITHUB_COPILOT_REFRESH_USER_AGENT);
assert.equal(refreshHeaders["Editor-Version"], GITHUB_COPILOT_EDITOR_VERSION);
assert.equal(refreshHeaders["Editor-Plugin-Version"], GITHUB_COPILOT_REFRESH_PLUGIN_VERSION);
- const qwenHeaders = getQwenOauthHeaders();
- assert.equal(qwenHeaders["User-Agent"], getQwenCliUserAgent());
- assert.equal(
- qwenHeaders["User-Agent"],
- `QwenCode/${QWEN_CLI_VERSION} (${process.platform}; ${process.arch})`
- );
- assert.notEqual(qwenHeaders["User-Agent"], "QwenCode/0.15.11 (linux; x64)");
- assert.equal(qwenHeaders["X-Dashscope-UserAgent"], getQwenCliUserAgent());
- assert.equal(qwenHeaders["X-Stainless-Package-Version"], "5.11.0");
- assert.equal(qwenHeaders["X-Stainless-Runtime-Version"], process.version);
-
const qoderHeaders = getQoderDashscopeCompatHeaders();
assert.equal(qoderHeaders["user-agent"], getQwenCliUserAgent());
assert.equal(qoderHeaders["x-dashscope-useragent"], getQwenCliUserAgent());
+ assert.equal(
+ qoderHeaders["user-agent"],
+ `QwenCode/${QWEN_CLI_VERSION} (${process.platform}; ${process.arch})`
+ );
const kiroHeaders = getKiroServiceHeaders("application/json");
assert.equal(kiroHeaders.Accept, "application/json");
@@ -76,9 +68,8 @@ test("provider header profiles tolerate browser-like process shims", async () =>
try {
assert.equal(getQwenCliUserAgent(), `QwenCode/${QWEN_CLI_VERSION} (unknown; unknown)`);
- const qwenHeaders = getQwenOauthHeaders();
- assert.equal(qwenHeaders["User-Agent"], `QwenCode/${QWEN_CLI_VERSION} (unknown; unknown)`);
- assert.equal(qwenHeaders["X-Stainless-Runtime-Version"], "unknown");
+ const qoderHeaders = getQoderDashscopeCompatHeaders();
+ assert.equal(qoderHeaders["user-agent"], `QwenCode/${QWEN_CLI_VERSION} (unknown; unknown)`);
} finally {
Object.defineProperty(process, "platform", { value: originalPlatform, configurable: true });
Object.defineProperty(process, "arch", { value: originalArch, configurable: true });
diff --git a/tests/unit/provider-limits-rotating-expired-guard.test.ts b/tests/unit/provider-limits-rotating-expired-guard.test.ts
index 2168bd4710..daca704d08 100644
--- a/tests/unit/provider-limits-rotating-expired-guard.test.ts
+++ b/tests/unit/provider-limits-rotating-expired-guard.test.ts
@@ -9,9 +9,8 @@ const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-rotating-
process.env.DATA_DIR = TEST_DATA_DIR;
process.env.API_KEY_SECRET = process.env.API_KEY_SECRET || "rotating-expired-guard-secret";
-const { quotaPathShouldMarkExpired, shouldAttemptRotatingRefresh } = await import(
- "../../src/lib/usage/providerLimits.ts"
-);
+const { quotaPathShouldMarkExpired, shouldAttemptRotatingRefresh } =
+ await import("../../src/lib/usage/providerLimits.ts");
test.after(() => {
fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true });
@@ -23,7 +22,7 @@ test.after(() => {
// flagging it expired hid freshly-added Codex accounts from the quota page even
// though a providers-page refresh turned them green.
test("rotating providers are NEVER flagged expired from the quota path", () => {
- for (const provider of ["codex", "openai", "claude", "kiro", "qwen", "gitlab-duo"]) {
+ for (const provider of ["codex", "openai", "claude", "kiro", "gitlab-duo"]) {
assert.equal(
quotaPathShouldMarkExpired(provider, "Token expired, please re-authenticate", "active"),
false,
@@ -54,9 +53,13 @@ test("an already-expired connection is left untouched (no redundant write)", ()
// expired token (cascade-safe via serializeRefresh), so its live quota shows;
// the bulk scheduler (allowRotatingRefresh falsy) must keep #3019 and never do it.
test("bulk path never refreshes rotating providers (preserves #3019)", () => {
- for (const provider of ["codex", "openai", "claude", "kiro", "qwen", "gitlab-duo"]) {
+ for (const provider of ["codex", "openai", "claude", "kiro", "gitlab-duo"]) {
assert.equal(shouldAttemptRotatingRefresh(provider, undefined), false, `${provider} bulk`);
- assert.equal(shouldAttemptRotatingRefresh(provider, false), false, `${provider} explicit false`);
+ assert.equal(
+ shouldAttemptRotatingRefresh(provider, false),
+ false,
+ `${provider} explicit false`
+ );
}
});
diff --git a/tests/unit/provider-models-route.test.ts b/tests/unit/provider-models-route.test.ts
index fe95a79988..f82f4abd8f 100644
--- a/tests/unit/provider-models-route.test.ts
+++ b/tests/unit/provider-models-route.test.ts
@@ -1002,21 +1002,6 @@ test("provider models route falls back through all Antigravity discovery endpoin
assert.ok(body.models.some((model) => model.id === "gemini-3-pro-preview"));
});
-test("provider models route returns the local catalog for OAuth-backed Qwen connections", async () => {
- const connection = await seedConnection("qwen", {
- authType: "oauth",
- accessToken: "qwen-access",
- apiKey: null,
- });
-
- const response = await callRoute(connection.id);
- const body = (await response.json()) as any;
-
- assert.equal(response.status, 200);
- assert.equal(body.source, "local_catalog");
- assert.ok(Array.isArray(body.models));
-});
-
test("provider models route filters hidden models from the static Claude catalog when requested", async () => {
const connection = await seedConnection("claude", {
authType: "oauth",
diff --git a/tests/unit/proxy-connection-test.test.ts b/tests/unit/proxy-connection-test.test.ts
index b38858b1ab..a73e19a77c 100644
--- a/tests/unit/proxy-connection-test.test.ts
+++ b/tests/unit/proxy-connection-test.test.ts
@@ -289,7 +289,6 @@ test("OAuth test config covers all expected providers", () => {
"github",
"gitlab-duo",
"qoder",
- "qwen",
"cursor",
"kimi-coding",
"kilocode",
@@ -307,7 +306,6 @@ test("OAuth test config covers all expected providers", () => {
"github",
"gitlab-duo",
"qoder",
- "qwen",
"cursor",
"kimi-coding",
"kilocode",
@@ -366,7 +364,6 @@ test("Refreshable OAuth providers are correctly identified", () => {
"antigravity",
"gitlab-duo",
"qoder",
- "qwen",
"kimi-coding",
"cline",
"kiro",
@@ -376,6 +373,6 @@ test("Refreshable OAuth providers are correctly identified", () => {
// Verify these two sets are mutually exclusive and cover all providers
const allProviders = [...refreshable, ...nonRefreshable];
- assert.equal(allProviders.length, 13);
- assert.equal(new Set(allProviders).size, 13);
+ assert.equal(allProviders.length, 12);
+ assert.equal(new Set(allProviders).size, 12);
});
diff --git a/tests/unit/public-client-ids-3493.test.ts b/tests/unit/public-client-ids-3493.test.ts
index 5141f6abb6..c8e7f26d17 100644
--- a/tests/unit/public-client-ids-3493.test.ts
+++ b/tests/unit/public-client-ids-3493.test.ts
@@ -3,7 +3,7 @@ import assert from "node:assert/strict";
import { resolvePublicCred } from "../../open-sse/utils/publicCreds.ts";
-// #3493 — five public OAuth client_ids were migrated from string literals to
+// #3493 — public OAuth client_ids were migrated from string literals to
// resolvePublicCred() (Hard Rule #11). These assertions guard that the embedded
// masked-byte defaults still decode to the exact public client_ids, so the OAuth
// flows are byte-for-byte unchanged, and that env overrides still win.
@@ -11,7 +11,6 @@ import { resolvePublicCred } from "../../open-sse/utils/publicCreds.ts";
const EXPECTED_CLIENT_IDS: Record = {
claude_id: "9d1c250a-e61b-44d9-88ed-5944d1962f5e",
codex_id: "app_EMoamEEZ73f0CkXaXp7hrann",
- qwen_id: "f0304373b74a44d2b584a3fb70ca9e56",
kimi_id: "17e5f671-d194-4dfb-9706-5516cb48c098",
github_copilot_id: "Iv1.b507a08c87ecfe98",
};
@@ -41,9 +40,7 @@ test("#3493 env override takes priority over the embedded default", () => {
});
test("#3493 the migrated OAuth/registry configs resolve to the expected client_ids", async () => {
- const { CLAUDE_CONFIG, CODEX_CONFIG, QWEN_CONFIG } = await import(
- "../../src/lib/oauth/constants/oauth.ts"
- );
+ const { CLAUDE_CONFIG, CODEX_CONFIG } = await import("../../src/lib/oauth/constants/oauth.ts");
// Only assert when env doesn't override (CI/dev may set these); skip the assert
// for any that are env-overridden so the test stays deterministic.
if (!process.env.CLAUDE_OAUTH_CLIENT_ID) {
@@ -52,7 +49,4 @@ test("#3493 the migrated OAuth/registry configs resolve to the expected client_i
if (!process.env.CODEX_OAUTH_CLIENT_ID) {
assert.equal(CODEX_CONFIG.clientId, EXPECTED_CLIENT_IDS.codex_id);
}
- if (!process.env.QWEN_OAUTH_CLIENT_ID) {
- assert.equal(QWEN_CONFIG.clientId, EXPECTED_CLIENT_IDS.qwen_id);
- }
});
diff --git a/tests/unit/qoder-oauth-config.test.ts b/tests/unit/qoder-oauth-config.test.ts
index 194d8a08fc..16d275a049 100644
--- a/tests/unit/qoder-oauth-config.test.ts
+++ b/tests/unit/qoder-oauth-config.test.ts
@@ -6,7 +6,6 @@ import assert from "node:assert/strict";
Object.assign(process.env, {
CLAUDE_OAUTH_CLIENT_ID: "9d1c250a-e61b-44d9-88ed-5944d1962f5e",
CODEX_OAUTH_CLIENT_ID: "app_EMoamEEZ73f0CkXaXp7hrann",
- QWEN_OAUTH_CLIENT_ID: "f0304373b74a44d2b584a3fb70ca9e56",
KIMI_CODING_OAUTH_CLIENT_ID: "17e5f671-d194-4dfb-9706-5516cb48c098",
GITHUB_OAUTH_CLIENT_ID: "Iv1.b507a08c87ecfe98",
});
diff --git a/tests/unit/qwen-api-key-auto-create.test.ts b/tests/unit/qwen-api-key-auto-create.test.ts
deleted file mode 100644
index fa4f8b4816..0000000000
--- a/tests/unit/qwen-api-key-auto-create.test.ts
+++ /dev/null
@@ -1,153 +0,0 @@
-import test from "node:test";
-import assert from "node:assert/strict";
-import fs from "node:fs/promises";
-import os from "os";
-import path from "path";
-import { SignJWT } from "jose";
-import { getOrCreateApiKey, resolveApiKey } from "../../src/shared/services/apiKeyResolver";
-import { validateApiKey } from "../../src/lib/db/apiKeys";
-import { getDbInstance } from "../../src/lib/db/core";
-
-const DUMMY_HOME = path.join(os.tmpdir(), "omniroute-qwen-key-test-" + Date.now());
-const originalJwtSecret = process.env.JWT_SECRET;
-
-async function createAuthCookie() {
- process.env.JWT_SECRET = "test-cli-tools-secret";
- const secret = new TextEncoder().encode(process.env.JWT_SECRET);
- const token = await new SignJWT({ sub: "test-user" })
- .setProtectedHeader({ alg: "HS256" })
- .setIssuedAt()
- .setExpirationTime("1h")
- .sign(secret);
- return `auth_token=${token}`;
-}
-
-const originalHomedir = os.homedir;
-
-test.beforeEach(async () => {
- process.env.DATA_DIR = DUMMY_HOME;
- process.env.API_KEY_SECRET = "test-secret";
- os.homedir = () => DUMMY_HOME;
- await fs.mkdir(DUMMY_HOME, { recursive: true }).catch(() => {});
- // Initialize DB
- getDbInstance();
-});
-
-test.afterEach(async () => {
- await fs.rm(DUMMY_HOME, { recursive: true, force: true }).catch(() => {});
- os.homedir = originalHomedir;
- if (originalJwtSecret === undefined) delete process.env.JWT_SECRET;
- else process.env.JWT_SECRET = originalJwtSecret;
- if (process.env.DATA_DIR?.includes("omniroute-qwen-key-test")) {
- delete process.env.DATA_DIR;
- }
-});
-
-test("getOrCreateApiKey() creates DB-backed key when no keyId provided", async () => {
- const apiKey = await getOrCreateApiKey(null);
-
- // Key should NOT be the placeholder "sk_omniroute"
- assert.notEqual(apiKey, "sk_omniroute", "Should not return placeholder");
- assert.ok(apiKey.startsWith("sk-"), "Key should start with sk- prefix");
-
- // Key should be valid in DB
- const valid = await validateApiKey(apiKey);
- assert.equal(valid, true, "Auto-created key should validate successfully");
-});
-
-test("getOrCreateApiKey() returns existing key when keyId is provided", async () => {
- // First create a key with a specific keyId
- const firstKey = await getOrCreateApiKey(null);
- assert.ok(firstKey.startsWith("sk-"));
-
- // Create another key and get its ID
- const secondKey = await getOrCreateApiKey(null);
-
- // When we pass the same keyId, we should get the same key back
- // (This tests the keyId resolution path)
- const resolvedKey = await resolveApiKey(null, firstKey);
- assert.equal(resolvedKey, firstKey, "Should return same key when resolved");
-});
-
-test("Qwen guide-settings POST creates valid DB-backed key (no keyId)", async () => {
- const guideSettingsRoute =
- await import("../../src/app/api/cli-tools/guide-settings/[toolId]/route.ts");
-
- const cookie = await createAuthCookie();
- const req = new Request("http://localhost/api/cli-tools/guide-settings/qwen", {
- method: "POST",
- headers: { "Content-Type": "application/json", cookie },
- body: JSON.stringify({
- baseUrl: "http://localhost:20128/v1",
- model: "qwen3-coder-flash",
- // No keyId provided - should auto-create
- }),
- });
-
- const response = await guideSettingsRoute.POST(req, { params: { toolId: "qwen" } });
- assert.equal(response.status, 200, "Response should be OK");
-
- // Verify settings.json was written with security.auth format and a valid DB-backed key
- const configPath = path.join(DUMMY_HOME, ".qwen", "settings.json");
- const content = JSON.parse(await fs.readFile(configPath, "utf-8"));
-
- assert.equal(content.security?.auth?.selectedType, "openai", "Should use openai auth type");
- assert.ok(content.security?.auth?.apiKey, "Should have an API key");
- assert.equal(
- content.security?.auth?.baseUrl,
- "http://localhost:20128/v1",
- "Should have base URL"
- );
- assert.equal(content.model?.name, "qwen3-coder-flash", "Should have model name");
-
- const apiKey = content.security.auth.apiKey;
- assert.notEqual(apiKey, "sk_omniroute", "Should not use placeholder");
- assert.ok(apiKey.startsWith("sk-"), "Key should start with sk- prefix");
-
- // Verify the key is valid in DB
- const valid = await validateApiKey(apiKey);
- assert.equal(valid, true, "Auto-created key should validate in DB");
-});
-
-test("Qwen guide-settings POST with keyId uses existing key", async () => {
- const guideSettingsRoute =
- await import("../../src/app/api/cli-tools/guide-settings/[toolId]/route.ts");
-
- // Pre-create a key via getOrCreateApiKey
- const existingKey = await getOrCreateApiKey(null);
- const keyIdMatch = existingKey.match(/^sk-[^-]+-([^-]+)-/);
- assert.ok(keyIdMatch, "Key should have ID portion");
-
- // Get key metadata to find the ID
- const db = getDbInstance();
- const stmt = db.prepare("SELECT id FROM api_keys WHERE `key` = ?");
- const row = stmt.get(existingKey) as { id: string } | undefined;
- assert.ok(row, "Key should exist in DB");
-
- const cookie = await createAuthCookie();
- const req = new Request("http://localhost/api/cli-tools/guide-settings/qwen", {
- method: "POST",
- headers: { "Content-Type": "application/json", cookie },
- body: JSON.stringify({
- baseUrl: "http://localhost:20128/v1",
- model: "qwen3-coder-plus",
- keyId: row.id,
- }),
- });
-
- const response = await guideSettingsRoute.POST(req, { params: { toolId: "qwen" } });
- assert.equal(response.status, 200, "Response should be OK");
-
- // Verify settings.json uses security.auth format with the existing key
- const configPath = path.join(DUMMY_HOME, ".qwen", "settings.json");
- const content = JSON.parse(await fs.readFile(configPath, "utf-8"));
-
- assert.equal(content.security?.auth?.selectedType, "openai");
- assert.equal(
- content.security?.auth?.apiKey,
- existingKey,
- "Should use existing key when keyId provided"
- );
- assert.equal(content.security?.auth?.baseUrl, "http://localhost:20128/v1");
- assert.equal(content.model?.name, "qwen3-coder-plus");
-});
diff --git a/tests/unit/qwen-code-config.test.ts b/tests/unit/qwen-code-config.test.ts
new file mode 100644
index 0000000000..3e59b4e95d
--- /dev/null
+++ b/tests/unit/qwen-code-config.test.ts
@@ -0,0 +1,333 @@
+import test from "node:test";
+import assert from "node:assert/strict";
+
+import {
+ hasOmniRouteQwenCodeConfig,
+ mergeQwenCodeEnv,
+ mergeQwenCodeSettings,
+ removeQwenCodeEnv,
+ removeQwenCodeSettings,
+} from "../../src/shared/services/qwenCodeConfig.ts";
+
+test("writes the upstream V4 bare-array modelProviders contract without a secret", () => {
+ const settings = mergeQwenCodeSettings(
+ {},
+ { baseUrl: "http://localhost:20128", model: "qwen/qwen3.8-max-preview" }
+ );
+
+ assert.deepEqual(settings.modelProviders, {
+ openai: [
+ {
+ id: "qwen/qwen3.8-max-preview",
+ name: "qwen/qwen3.8-max-preview (OmniRoute)",
+ envKey: "OMNIROUTE_API_KEY",
+ baseUrl: "http://localhost:20128/v1",
+ },
+ ],
+ });
+ assert.deepEqual(settings.security, { auth: { selectedType: "openai" } });
+ assert.deepEqual(settings.model, {
+ name: "qwen/qwen3.8-max-preview",
+ baseUrl: "http://localhost:20128/v1",
+ });
+ assert.equal(JSON.stringify(settings).includes("sk-secret"), false);
+});
+
+test("preserves user settings and unrelated provider entries", () => {
+ const existing = {
+ $version: 4,
+ mcpServers: { filesystem: { command: "npx" } },
+ security: { folderTrust: { enabled: true } },
+ modelProviders: {
+ openai: [
+ {
+ id: "gpt-4o",
+ name: "Personal OpenAI",
+ envKey: "OPENAI_API_KEY",
+ baseUrl: "https://api.openai.com/v1",
+ },
+ ],
+ idealab: [
+ {
+ id: "ideal-model",
+ envKey: "IDEALAB_API_KEY",
+ baseUrl: "https://idealab.example/v1",
+ },
+ ],
+ },
+ providerProtocol: { idealab: "openai" },
+ };
+
+ const settings = mergeQwenCodeSettings(existing, {
+ baseUrl: "https://omni.example/v1/",
+ model: "cx/gpt-5.6-sol",
+ modelName: "GPT-5.6 Sol",
+ });
+
+ assert.equal(settings.$version, 4);
+ assert.deepEqual(settings.mcpServers, existing.mcpServers);
+ assert.deepEqual(settings.providerProtocol, existing.providerProtocol);
+ assert.deepEqual(
+ (settings.modelProviders as Record).idealab,
+ existing.modelProviders.idealab
+ );
+ assert.equal(
+ ((settings.modelProviders as Record).openai as unknown[]).length,
+ 2
+ );
+});
+
+test("replaces only the managed entry and unwraps the reverted V5 provider shape", () => {
+ const settings = mergeQwenCodeSettings(
+ {
+ $version: 4,
+ modelProviders: {
+ openai: {
+ protocol: "openai",
+ models: [
+ {
+ id: "old-model",
+ name: "old-model (OmniRoute)",
+ envKey: "OPENAI_API_KEY",
+ baseUrl: "http://old-host/v1",
+ },
+ {
+ id: "personal-model",
+ envKey: "PERSONAL_API_KEY",
+ baseUrl: "https://personal.example/v1",
+ },
+ ],
+ },
+ },
+ },
+ { baseUrl: "http://new-host", model: "new-model" }
+ );
+
+ assert.deepEqual((settings.modelProviders as Record).openai, [
+ {
+ id: "personal-model",
+ envKey: "PERSONAL_API_KEY",
+ baseUrl: "https://personal.example/v1",
+ },
+ {
+ id: "new-model",
+ name: "new-model (OmniRoute)",
+ envKey: "OMNIROUTE_API_KEY",
+ baseUrl: "http://new-host/v1",
+ },
+ ]);
+});
+
+test("migrates the removed integration's root-array shape without dropping other entries", () => {
+ const settings = mergeQwenCodeSettings(
+ {
+ modelProviders: [
+ {
+ id: "omniroute",
+ name: "OmniRoute",
+ authType: "openai",
+ envKey: "OMNIROUTE_API_KEY",
+ baseUrl: "http://old/v1",
+ },
+ {
+ id: "personal-model",
+ name: "Personal model",
+ authType: "openai",
+ envKey: "PERSONAL_API_KEY",
+ baseUrl: "https://personal.example/v1",
+ },
+ ],
+ selectedProvider: "omniroute",
+ },
+ { baseUrl: "http://new", model: "new-model" }
+ );
+
+ assert.deepEqual((settings.modelProviders as Record).openai, [
+ {
+ id: "personal-model",
+ name: "Personal model",
+ envKey: "PERSONAL_API_KEY",
+ baseUrl: "https://personal.example/v1",
+ },
+ {
+ id: "new-model",
+ name: "new-model (OmniRoute)",
+ envKey: "OMNIROUTE_API_KEY",
+ baseUrl: "http://new/v1",
+ },
+ ]);
+ assert.equal(settings.selectedProvider, undefined);
+});
+
+test("migrates matching legacy security.auth credentials out of settings.json", () => {
+ const settings = mergeQwenCodeSettings(
+ {
+ security: {
+ auth: {
+ selectedType: "openai",
+ apiKey: "sk-legacy-secret",
+ baseUrl: "http://omni-host/v1",
+ other: true,
+ },
+ },
+ model: { name: "old-model", temperature: 0.2 },
+ },
+ { baseUrl: "http://omni-host", model: "new-model" }
+ );
+
+ assert.deepEqual(settings.security, {
+ auth: { selectedType: "openai", other: true },
+ });
+ assert.deepEqual(settings.model, {
+ name: "new-model",
+ baseUrl: "http://omni-host/v1",
+ temperature: 0.2,
+ });
+ assert.equal(JSON.stringify(settings).includes("sk-legacy-secret"), false);
+});
+
+test("detection is precise and does not claim arbitrary custom endpoints", () => {
+ assert.equal(
+ hasOmniRouteQwenCodeConfig({
+ modelProviders: {
+ openai: [
+ {
+ id: "custom-model",
+ name: "My custom provider",
+ envKey: "CUSTOM_API_KEY",
+ baseUrl: "https://custom.example/v1",
+ },
+ ],
+ },
+ }),
+ false
+ );
+
+ assert.equal(
+ hasOmniRouteQwenCodeConfig({
+ modelProviders: {
+ openai: [
+ {
+ id: "custom-model",
+ name: "custom-model (OmniRoute)",
+ envKey: "OMNIROUTE_API_KEY",
+ baseUrl: "https://omni.example/v1",
+ },
+ ],
+ },
+ }),
+ true
+ );
+});
+
+test("env merge owns only OMNIROUTE_API_KEY and preserves all user keys", () => {
+ const original = [
+ "# user credentials",
+ "OPENAI_API_KEY=sk-openai",
+ "ANTHROPIC_API_KEY=sk-anthropic",
+ "GEMINI_API_KEY=sk-gemini",
+ "export OMNIROUTE_API_KEY=old-value",
+ "OMNIROUTE_API_KEY_BACKUP=keep-me",
+ "",
+ ].join("\n");
+
+ const merged = mergeQwenCodeEnv(original, 'sk-new "quoted" value');
+ assert.match(merged, /^OPENAI_API_KEY=sk-openai$/m);
+ assert.match(merged, /^ANTHROPIC_API_KEY=sk-anthropic$/m);
+ assert.match(merged, /^GEMINI_API_KEY=sk-gemini$/m);
+ assert.match(merged, /^OMNIROUTE_API_KEY_BACKUP=keep-me$/m);
+ assert.match(merged, /^OMNIROUTE_API_KEY="sk-new \\"quoted\\" value"$/m);
+ assert.equal((merged.match(/^OMNIROUTE_API_KEY=/gm) || []).length, 1);
+
+ const removed = removeQwenCodeEnv(merged);
+ assert.doesNotMatch(removed, /^OMNIROUTE_API_KEY=/m);
+ assert.match(removed, /^OMNIROUTE_API_KEY_BACKUP=keep-me$/m);
+ assert.match(removed, /^OPENAI_API_KEY=sk-openai$/m);
+});
+
+test("reset removes only managed models and their active selection", () => {
+ const existing = mergeQwenCodeSettings(
+ {
+ ui: { theme: "dark" },
+ modelProviders: {
+ openai: [
+ {
+ id: "personal-model",
+ envKey: "PERSONAL_API_KEY",
+ baseUrl: "https://personal.example/v1",
+ },
+ ],
+ },
+ },
+ { baseUrl: "http://omni-host", model: "managed-model" }
+ );
+
+ const reset = removeQwenCodeSettings(existing);
+ assert.deepEqual(reset.ui, { theme: "dark" });
+ assert.deepEqual((reset.modelProviders as Record).openai, [
+ {
+ id: "personal-model",
+ envKey: "PERSONAL_API_KEY",
+ baseUrl: "https://personal.example/v1",
+ },
+ ]);
+ assert.equal(reset.model, undefined);
+ assert.deepEqual(reset.security, { auth: { selectedType: "openai" } });
+});
+
+test("reset also removes matching deprecated security.auth credentials", () => {
+ const reset = removeQwenCodeSettings({
+ security: {
+ auth: {
+ selectedType: "openai",
+ apiKey: "sk-old-secret",
+ baseUrl: "http://omni-host/v1",
+ },
+ },
+ model: { name: "managed", baseUrl: "http://omni-host/v1" },
+ modelProviders: {
+ openai: [
+ {
+ id: "managed",
+ name: "managed (OmniRoute)",
+ envKey: "OMNIROUTE_API_KEY",
+ baseUrl: "http://omni-host/v1",
+ },
+ ],
+ },
+ });
+
+ assert.equal(reset.security, undefined);
+ assert.equal(reset.model, undefined);
+ assert.equal(JSON.stringify(reset).includes("sk-old-secret"), false);
+});
+
+test("reset removes matching deprecated auth when the selected model is unrelated", () => {
+ const reset = removeQwenCodeSettings({
+ security: {
+ auth: {
+ selectedType: "openai",
+ apiKey: "sk-old-secret",
+ baseUrl: "http://omni-host/v1",
+ },
+ },
+ model: { name: "some-other-model", baseUrl: "https://other.example/v1" },
+ modelProviders: {
+ openai: [
+ {
+ id: "managed",
+ name: "managed (OmniRoute)",
+ envKey: "OMNIROUTE_API_KEY",
+ baseUrl: "http://omni-host/v1",
+ },
+ ],
+ },
+ });
+
+ assert.equal(reset.security, undefined);
+ assert.deepEqual(reset.model, {
+ name: "some-other-model",
+ baseUrl: "https://other.example/v1",
+ });
+ assert.equal(JSON.stringify(reset).includes("sk-old-secret"), false);
+});
diff --git a/tests/unit/qwen-settings-route.test.ts b/tests/unit/qwen-settings-route.test.ts
new file mode 100644
index 0000000000..0b1f2ab619
--- /dev/null
+++ b/tests/unit/qwen-settings-route.test.ts
@@ -0,0 +1,137 @@
+import test from "node:test";
+import assert from "node:assert/strict";
+import fs from "node:fs/promises";
+import os from "node:os";
+import path from "node:path";
+import { SignJWT } from "jose";
+
+const TEST_HOME = path.join(os.tmpdir(), `omniroute-qwen-route-${process.pid}-${Date.now()}`);
+const SETTINGS_PATH = path.join(TEST_HOME, ".qwen", "settings.json");
+const ENV_PATH = path.join(TEST_HOME, ".qwen", ".env");
+const originalHome = os.homedir;
+const originalJwtSecret = process.env.JWT_SECRET;
+const originalWriteFlag = process.env.CLI_ALLOW_CONFIG_WRITES;
+
+os.homedir = () => TEST_HOME;
+process.env.CLI_ALLOW_CONFIG_WRITES = "true";
+
+const route = await import("../../src/app/api/cli-tools/qwen-settings/route.ts");
+
+const authCookie = async (): Promise => {
+ process.env.JWT_SECRET = "qwen-settings-route-test-secret";
+ const token = await new SignJWT({ sub: "qwen-route-test" })
+ .setProtectedHeader({ alg: "HS256" })
+ .setIssuedAt()
+ .setExpirationTime("1h")
+ .sign(new TextEncoder().encode(process.env.JWT_SECRET));
+ return `auth_token=${token}`;
+};
+
+const request = async (method: string, body?: unknown): Promise =>
+ new Request("http://localhost/api/cli-tools/qwen-settings", {
+ method,
+ headers: {
+ cookie: await authCookie(),
+ ...(body === undefined ? {} : { "Content-Type": "application/json" }),
+ },
+ ...(body === undefined ? {} : { body: JSON.stringify(body) }),
+ });
+
+test.beforeEach(async () => {
+ await fs.rm(TEST_HOME, { recursive: true, force: true });
+ await fs.mkdir(path.dirname(SETTINGS_PATH), { recursive: true });
+});
+
+test.after(async () => {
+ os.homedir = originalHome;
+ await fs.rm(TEST_HOME, { recursive: true, force: true });
+ if (originalJwtSecret === undefined) delete process.env.JWT_SECRET;
+ else process.env.JWT_SECRET = originalJwtSecret;
+ if (originalWriteFlag === undefined) delete process.env.CLI_ALLOW_CONFIG_WRITES;
+ else process.env.CLI_ALLOW_CONFIG_WRITES = originalWriteFlag;
+});
+
+test("POST writes the shared V4 contract and preserves unrelated credentials", async () => {
+ await fs.writeFile(
+ SETTINGS_PATH,
+ JSON.stringify({
+ ui: { theme: "dark" },
+ modelProviders: {
+ openai: [
+ {
+ id: "personal-model",
+ envKey: "OPENAI_API_KEY",
+ baseUrl: "https://api.openai.com/v1",
+ },
+ ],
+ },
+ })
+ );
+ await fs.writeFile(ENV_PATH, "OPENAI_API_KEY=keep-openai\nGEMINI_API_KEY=keep-gemini\n");
+
+ const response = await route.POST(
+ await request("POST", {
+ baseUrl: "http://localhost:20128",
+ apiKey: "sk-route-secret",
+ model: "qwen/qwen3.8-max-preview",
+ })
+ );
+ assert.equal(response.status, 200);
+
+ const settings = JSON.parse(await fs.readFile(SETTINGS_PATH, "utf8"));
+ assert.equal(settings.ui.theme, "dark");
+ assert.equal(settings.modelProviders.openai.length, 2);
+ assert.deepEqual(settings.modelProviders.openai[1], {
+ id: "qwen/qwen3.8-max-preview",
+ name: "qwen/qwen3.8-max-preview (OmniRoute)",
+ envKey: "OMNIROUTE_API_KEY",
+ baseUrl: "http://localhost:20128/v1",
+ });
+ assert.equal(JSON.stringify(settings).includes("sk-route-secret"), false);
+
+ const env = await fs.readFile(ENV_PATH, "utf8");
+ assert.match(env, /^OPENAI_API_KEY=keep-openai$/m);
+ assert.match(env, /^GEMINI_API_KEY=keep-gemini$/m);
+ assert.match(env, /^OMNIROUTE_API_KEY="sk-route-secret"$/m);
+});
+
+test("DELETE removes only OmniRoute-owned settings and env lines", async () => {
+ const configured = {
+ ui: { theme: "dark" },
+ security: { auth: { selectedType: "openai" } },
+ model: { name: "managed", baseUrl: "http://localhost:20128/v1" },
+ modelProviders: {
+ openai: [
+ {
+ id: "personal",
+ envKey: "OPENAI_API_KEY",
+ baseUrl: "https://api.openai.com/v1",
+ },
+ {
+ id: "managed",
+ name: "managed (OmniRoute)",
+ envKey: "OMNIROUTE_API_KEY",
+ baseUrl: "http://localhost:20128/v1",
+ },
+ ],
+ },
+ };
+ await fs.writeFile(SETTINGS_PATH, JSON.stringify(configured));
+ await fs.writeFile(ENV_PATH, "OPENAI_API_KEY=keep\nOMNIROUTE_API_KEY=remove\n");
+
+ const response = await route.DELETE(await request("DELETE"));
+ assert.equal(response.status, 200);
+
+ const settings = JSON.parse(await fs.readFile(SETTINGS_PATH, "utf8"));
+ assert.deepEqual(settings.ui, { theme: "dark" });
+ assert.equal(settings.model, undefined);
+ assert.deepEqual(settings.modelProviders.openai, [configured.modelProviders.openai[0]]);
+ assert.equal(await fs.readFile(ENV_PATH, "utf8"), "OPENAI_API_KEY=keep\n");
+});
+
+test("DELETE is a no-op when Qwen Code has no config files", async () => {
+ const response = await route.DELETE(await request("DELETE"));
+ assert.equal(response.status, 200);
+ await assert.rejects(fs.access(SETTINGS_PATH));
+ await assert.rejects(fs.access(ENV_PATH));
+});
diff --git a/tests/unit/qwen-strip-stream-options-claude-code-port663.test.ts b/tests/unit/qwen-strip-stream-options-claude-code-port663.test.ts
deleted file mode 100644
index f4effdacba..0000000000
--- a/tests/unit/qwen-strip-stream-options-claude-code-port663.test.ts
+++ /dev/null
@@ -1,100 +0,0 @@
-/**
- * Port of upstream decolua/9router#663 (closes upstream #557).
- *
- * Scenario: Claude Code (or any caller) hits a Qwen model with an OpenAI body
- * that carries `stream: false`. OmniRoute, however, sets the executor-level
- * `stream` flag to `true` for Claude-Code-compatible providers via
- * `upstreamStream = stream || isClaudeCodeCompatible`
- * (`open-sse/handlers/chatCore.ts`). DefaultExecutor.transformRequest then runs
- * its `if (stream && targetFormat === "openai")` branch and injects
- * `stream_options: { include_usage: true }` onto a body that still carries
- * `stream: false`. Qwen upstream rejects with:
- * 400 "'stream_options' only set this when you set stream: true"
- *
- * Fix mirrors upstream: when the OUTGOING body explicitly says `stream: false`,
- * do NOT inject `stream_options` regardless of the executor-level `stream` arg.
- * Same defensive treatment when the body carries `thinking` /
- * `enable_thinking`, since the upstream PR also exempts those.
- */
-import test from "node:test";
-import assert from "node:assert/strict";
-import { DefaultExecutor } from "../../open-sse/executors/default.ts";
-
-test("port#663 qwen: body.stream===false → no stream_options even when executor stream=true", () => {
- const executor = new DefaultExecutor("qwen");
- const body = {
- model: "qwen3-coder-plus",
- messages: [{ role: "user", content: "hi" }],
- stream: false,
- };
- const result = executor.transformRequest(
- "qwen3-coder-plus",
- body,
- /* stream */ true,
- {}
- ) as Record;
- assert.equal(
- result.stream_options,
- undefined,
- "stream_options must not be injected when body.stream === false"
- );
-});
-
-test("port#663 qwen: body.thinking truthy → no stream_options injection", () => {
- const executor = new DefaultExecutor("qwen");
- const body = {
- model: "qwen3-coder-plus",
- messages: [{ role: "user", content: "hi" }],
- thinking: { type: "enabled" },
- };
- const result = executor.transformRequest(
- "qwen3-coder-plus",
- body,
- true,
- {}
- ) as Record;
- assert.equal(
- result.stream_options,
- undefined,
- "stream_options must not be injected when thinking mode is requested"
- );
-});
-
-test("port#663 qwen: body.enable_thinking truthy → no stream_options injection", () => {
- const executor = new DefaultExecutor("qwen");
- const body = {
- model: "qwen3-coder-plus",
- messages: [{ role: "user", content: "hi" }],
- enable_thinking: true,
- };
- const result = executor.transformRequest(
- "qwen3-coder-plus",
- body,
- true,
- {}
- ) as Record;
- assert.equal(
- result.stream_options,
- undefined,
- "stream_options must not be injected when enable_thinking is true"
- );
-});
-
-test("port#663 qwen: normal streaming request still injects stream_options.include_usage", () => {
- const executor = new DefaultExecutor("qwen");
- const body = {
- model: "qwen3-coder-plus",
- messages: [{ role: "user", content: "hi" }],
- };
- const result = executor.transformRequest(
- "qwen3-coder-plus",
- body,
- true,
- {}
- ) as Record;
- assert.deepEqual(
- result.stream_options,
- { include_usage: true },
- "regular qwen streaming requests must keep the include_usage injection"
- );
-});
diff --git a/tests/unit/qwen-web-models-discovery-3931.test.ts b/tests/unit/qwen-web-models-discovery-3931.test.ts
index 3610311767..0a714b65e5 100644
--- a/tests/unit/qwen-web-models-discovery-3931.test.ts
+++ b/tests/unit/qwen-web-models-discovery-3931.test.ts
@@ -2,9 +2,7 @@
* TDD regression for #3931 (Problem #3, diagnosed by @thezukiru in discussion
* #3895): the `qwen-web` cookie provider had no entry in PROVIDER_MODELS_CONFIG
* (`src/app/api/providers/[id]/models/route.ts`), so the model-discovery page
- * returned nothing for it. The OAuth fallback at the top of the handler only
- * fires for `provider === "qwen" && authType === "oauth"`, so qwen-web fell
- * through to the no-config branch.
+ * returned nothing for it, so qwen-web fell through to the no-config branch.
*
* (Problem #1 — the validator bare-token false-positive — was already fixed in
* the merged PR #3958; Problem #2 — empty stream from WAF bot-detection on the
diff --git a/tests/unit/route-guard-qwen-settings-local-only.test.ts b/tests/unit/route-guard-qwen-settings-local-only.test.ts
new file mode 100644
index 0000000000..16466e75d7
--- /dev/null
+++ b/tests/unit/route-guard-qwen-settings-local-only.test.ts
@@ -0,0 +1,21 @@
+import test from "node:test";
+import assert from "node:assert/strict";
+
+import {
+ isLocalOnlyBypassableByManageScope,
+ isLocalOnlyPath,
+} from "../../src/server/authz/routeGuard.ts";
+import { SPAWN_CAPABLE_ROUTE_ROOTS } from "../../scripts/check/check-route-guard-membership.ts";
+
+test("Qwen Code settings are local-only because GET probes a binary and writes touch ~/.qwen", () => {
+ assert.equal(isLocalOnlyPath("/api/cli-tools/qwen-settings"), true);
+ assert.equal(isLocalOnlyPath("/api/cli-tools/qwen-settings/"), true);
+});
+
+test("Qwen Code settings cannot be opened through the manage-scope bypass", () => {
+ assert.equal(isLocalOnlyBypassableByManageScope("/api/cli-tools/qwen-settings"), false);
+});
+
+test("the spawn-capable route audit includes Qwen Code settings", () => {
+ assert.ok(SPAWN_CAPABLE_ROUTE_ROOTS.includes("src/app/api/cli-tools/qwen-settings"));
+});
diff --git a/tests/unit/sync-models-degraded-local-catalog-5460-5465.test.ts b/tests/unit/sync-models-degraded-local-catalog-5460-5465.test.ts
index fdec9bb538..a897e93c07 100644
--- a/tests/unit/sync-models-degraded-local-catalog-5460-5465.test.ts
+++ b/tests/unit/sync-models-degraded-local-catalog-5460-5465.test.ts
@@ -3,7 +3,7 @@ import assert from "node:assert/strict";
// #5460 (Reka) + #5465 (t3.chat): the model-sync route used to 502 on ANY
// `local_catalog` source, so providers whose local catalog is their ONLY
-// discovery source (reka, qwen-oauth, embedding/rerank + web-cookie providers)
+// discovery source (reka, embedding/rerank + web-cookie providers)
// failed Import/Sync every time. The route now imports those (flagged
// `intentional: true` by the models route) and only 502s a genuinely degraded
// remote-fetch fallback.
diff --git a/tests/unit/t28-model-catalog-updates.test.ts b/tests/unit/t28-model-catalog-updates.test.ts
index 3455bafcca..848749e144 100644
--- a/tests/unit/t28-model-catalog-updates.test.ts
+++ b/tests/unit/t28-model-catalog-updates.test.ts
@@ -51,11 +51,9 @@ test("T28: github registry exposes Gemini 3.1 Pro Preview and keeps legacy alias
assert.equal(legacy.model, "gemini-3.1-pro-preview");
});
-test("T28: qwen registry uses native chat.qwen.ai base URL", () => {
- assert.equal(
- REGISTRY.qwen.baseUrl,
- "https://chat.qwen.ai/api/v1/services/aigc/text-generation/generation"
- );
+test("T28: qwen OAuth registry entry is retired; qwen-web keeps the native chat.qwen.ai URL", () => {
+ assert.equal(REGISTRY.qwen, undefined);
+ assert.equal(REGISTRY["qwen-web"].baseUrl, "https://chat.qwen.ai/api/v2/chat/completions");
});
test("T28: lmarena registry seeds Direct-chat Text/search; image models in IMAGE_PROVIDERS", async () => {
diff --git a/tests/unit/token-refresh-route-service.test.ts b/tests/unit/token-refresh-route-service.test.ts
index b8d50ad509..0a71e72c81 100644
--- a/tests/unit/token-refresh-route-service.test.ts
+++ b/tests/unit/token-refresh-route-service.test.ts
@@ -187,13 +187,6 @@ test("token refresh wrapper delegates provider-specific refresh helpers and form
refresh_token: "google-refresh-next",
expires_in: 3600,
});
- case OAUTH_ENDPOINTS.qwen.token:
- return jsonResponse({
- access_token: "qwen-access",
- refresh_token: "qwen-refresh-next",
- expires_in: 900,
- resource_url: "https://resource.qwen.local",
- });
case OAUTH_ENDPOINTS.openai.token:
return jsonResponse({
access_token: "codex-access",
@@ -227,7 +220,6 @@ test("token refresh wrapper delegates provider-specific refresh helpers and form
"override-client-id",
"override-client-secret"
);
- const qwen = await tokenRefresh.refreshQwenToken("refresh-qwen");
const codex = await tokenRefresh.refreshCodexToken("refresh-codex");
const qoder = await tokenRefresh.refreshQoderToken("refresh-qoder");
const github = await tokenRefresh.refreshGitHubToken("refresh-github");
@@ -244,10 +236,7 @@ test("token refresh wrapper delegates provider-specific refresh helpers and form
refreshToken: "refresh-github",
});
const allTokens = await tokenRefresh.getAllAccessTokens({
- connections: [
- { provider: "github", refreshToken: "refresh-github-all", isActive: true },
- { provider: "qwen", refreshToken: "refresh-qwen-all", isActive: false },
- ],
+ connections: [{ provider: "github", refreshToken: "refresh-github-all", isActive: true }],
});
assert.equal(tokenRefresh.TOKEN_EXPIRY_BUFFER_MS, 5 * 60 * 1000);
@@ -258,7 +247,6 @@ test("token refresh wrapper delegates provider-specific refresh helpers and form
});
assert.equal(claude.accessToken, "claude-access");
assert.equal(google.accessToken, "google-access");
- assert.equal(qwen.providerSpecificData.resourceUrl, "https://resource.qwen.local");
assert.equal(codex.accessToken, "codex-access");
assert.equal(qoder, null);
assert.equal(github.refreshToken, "github-refresh-next");
@@ -274,7 +262,7 @@ test("token refresh wrapper delegates provider-specific refresh helpers and form
}
);
- assert.equal(calls.length >= 8, true);
+ assert.equal(calls.length >= 7, true);
delete PROVIDERS["custom-oauth-local-608"];
});
diff --git a/tests/unit/token-refresh-service.test.ts b/tests/unit/token-refresh-service.test.ts
index f952ae4f06..eb75adb0d3 100644
--- a/tests/unit/token-refresh-service.test.ts
+++ b/tests/unit/token-refresh-service.test.ts
@@ -13,7 +13,6 @@ const {
refreshKimiCodingToken,
refreshClaudeOAuthToken,
refreshGoogleToken,
- refreshQwenToken,
refreshCodexToken,
refreshKiroToken,
refreshQoderToken,
@@ -379,44 +378,6 @@ test("refreshGoogleToken exchanges refresh tokens against the shared google endp
);
});
-test("refreshQwenToken maps resource_url into providerSpecificData", async () => {
- const log = createLog();
-
- await withMockedFetch(
- async () =>
- jsonResponse({
- access_token: "qwen-access",
- refresh_token: "qwen-refresh-next",
- expires_in: 1500,
- resource_url: "https://chat.qwen.ai/workspace/resource",
- }),
- async () => {
- const result = await refreshQwenToken("qwen-refresh", log);
- assert.deepEqual(result, {
- accessToken: "qwen-access",
- refreshToken: "qwen-refresh-next",
- expiresIn: 1500,
- providerSpecificData: {
- resourceUrl: "https://chat.qwen.ai/workspace/resource",
- },
- });
- }
- );
-});
-
-test("refreshQwenToken surfaces invalid_request as unrecoverable", async () => {
- const log = createLog();
-
- await withMockedFetch(
- async () => textResponse(JSON.stringify({ error: "invalid_request" }), 400),
- async () => {
- const result = await refreshQwenToken("qwen-refresh", log);
- // Normalized to unrecoverable_refresh_error sentinel (Fix 4)
- assert.deepEqual(result, { error: "unrecoverable_refresh_error", code: "invalid_request" });
- }
- );
-});
-
test("refreshCodexToken recognizes refresh_token_reused responses", async () => {
const log = createLog();
diff --git a/tests/unit/usage-providers.test.ts b/tests/unit/usage-providers.test.ts
index 52ffabb534..17bece1108 100644
--- a/tests/unit/usage-providers.test.ts
+++ b/tests/unit/usage-providers.test.ts
@@ -96,17 +96,6 @@ test("getUsageForProvider handles kimi-coding provider", async () => {
assert.ok(result);
});
-test("getUsageForProvider handles qwen provider", async () => {
- globalThis.fetch = async () => new Response(JSON.stringify({}), { status: 200 });
- const result = await usageModule.getUsageForProvider({
- id: "test-qw",
- provider: "qwen",
- accessToken: "tok",
- apiKey: "key",
- });
- assert.ok(result);
-});
-
test("getUsageForProvider handles qoder provider", async () => {
globalThis.fetch = async () => new Response(JSON.stringify({}), { status: 200 });
const result = await usageModule.getUsageForProvider({
diff --git a/tests/unit/usage-service-hardening.test.ts b/tests/unit/usage-service-hardening.test.ts
index 150b99b283..dd9a537dee 100644
--- a/tests/unit/usage-service-hardening.test.ts
+++ b/tests/unit/usage-service-hardening.test.ts
@@ -798,7 +798,7 @@ test("usage service covers Codex, Kiro and Kimi usage parsing and error branches
assert.match(kimiInvalidJson.message, /Invalid JSON response/i);
});
-test("usage service covers Codex auth failures, Kiro hard failures, Kimi no-quota fallbacks and Qwen catch branch", async () => {
+test("usage service covers Codex auth failures, Kiro hard failures and Kimi no-quota fallbacks", async () => {
globalThis.fetch = async (url) => {
if (String(url).includes("/backend-api/wham/usage")) {
return new Response("nope", { status: 401 });
@@ -865,34 +865,9 @@ test("usage service covers Codex auth failures, Kiro hard failures, Kimi no-quot
accessToken: "kimi-offline",
});
assert.match(kimiOffline.message, /Unable to fetch usage: kimi offline/i);
-
- const qwenCatch: any = await usageService.getUsageForProvider({
- provider: "qwen",
- accessToken: "qwen-catch",
- providerSpecificData: {
- get resourceUrl() {
- throw new Error("resource lookup failed");
- },
- },
- });
- assert.equal(qwenCatch.message, "Unable to fetch Qwen usage.");
});
-test("usage service covers Qwen, Qoder, GLM, Z.AI and GLMT branches", async () => {
- const qwenMissingUrl: any = await usageService.getUsageForProvider({
- provider: "qwen",
- accessToken: "qwen-token",
- providerSpecificData: {},
- });
- assert.match(qwenMissingUrl.message, /No resource URL/i);
-
- const qwen: any = await usageService.getUsageForProvider({
- provider: "qwen",
- accessToken: "qwen-token",
- providerSpecificData: { resourceUrl: "https://example.com/resource" },
- });
- assert.match(qwen.message, /Usage tracked per request/i);
-
+test("usage service covers Qoder, GLM, Z.AI and GLMT branches", async () => {
// Qoder now reads its PAT from `apiKey` (not `accessToken`); with no PAT the
// usage fetcher returns a friendly prompt instead of hitting the network.
const qoder: any = await usageService.getUsageForProvider({
diff --git a/tests/unit/web-cookie-providers-new.test.ts b/tests/unit/web-cookie-providers-new.test.ts
index 4d90fe359d..be1042c0e7 100644
--- a/tests/unit/web-cookie-providers-new.test.ts
+++ b/tests/unit/web-cookie-providers-new.test.ts
@@ -190,7 +190,6 @@ test("Doubao Web sets correct provider", () => {
test("Qwen Web executor is registered", () => {
assert.ok(hasSpecializedExecutor("qwen-web"));
- assert.ok(hasSpecializedExecutor("qw"));
const executor = getExecutor("qwen-web");
assert.ok(executor instanceof QwenWebExecutor);
});