mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-07-26 09:52:11 +03:00
[Part 1/3]refactor(qwen): replace legacy Qwen Code and remove OAuth provider (#7866)
* refactor(cli): remove legacy Qwen Code integration * refactor(qwen): remove deprecated Qwen OAuth provider * feat(cli): rebuild Qwen Code integration for upstream V4 * fix(qwen): clear stale CLI auth on reset * test(qwen): align retired provider coverage * fix(db): renumber qwen-cleanup migration 129 -> 130 release/v3.8.49 tip took slot 129 via #7843 (usage_history_codex_strong_identity, itself renumbered from 128 during the #7838/#7840 base-red cleanup) after this branch forked; renumber remove_unregistered_qwen_data to 130. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> --------- Co-authored-by: Diego Rodrigues de Sa e Souza <8016841+diegosouzapw@users.noreply.github.com>
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -292,7 +292,6 @@ All **18** strategies — mix & match per combo step:
|
||||
<td align="center" width="76"><a href="https://github.com/Kilo-Org/kilocode"><img src="./public/providers/kilocode.svg" width="40" alt="Kilo Code"/><br/><sub><b>Kilo Code</b></sub><br/><sub> </sub></a></td>
|
||||
<td align="center" width="76"><img src="https://cdn.jsdelivr.net/npm/@lobehub/icons-static-png@1.91.0/dark/roocode.png#gh-dark-mode-only" width="40" alt="Roo Code"/><img src="https://cdn.jsdelivr.net/npm/@lobehub/icons-static-svg@1.91.0/icons/roocode.svg#gh-light-mode-only" width="40" alt="Roo Code"/><br/><sub><b>Roo Code</b></sub><br/><sub> </sub></td>
|
||||
<td align="center" width="76"><img src="./public/providers/continue.svg" width="40" alt="Continue"/><br/><sub><b>Continue</b></sub><br/><sub> </sub></td>
|
||||
<td align="center" width="76"><img src="./public/providers/qwen.svg" width="40" alt="Qwen Code"/><br/><sub><b>Qwen Code</b></sub><br/><sub> </sub></td>
|
||||
<td align="center" width="76"><img src="./public/providers/cli-generic.svg" width="40" alt="Aider"/><br/><sub><b>Aider</b></sub><br/><sub> </sub></td>
|
||||
<td align="center" width="76"><img src="./public/providers/cli-generic.svg" width="40" alt="ForgeCode"/><br/><sub><b>ForgeCode</b></sub><br/><sub> </sub></td>
|
||||
</tr>
|
||||
|
||||
@@ -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` |
|
||||
|
||||
@@ -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" },
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 */
|
||||
// An active context is optional for local setup.
|
||||
}
|
||||
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 */
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
return s;
|
||||
if (!root) root = context?.baseUrl || "";
|
||||
if (!root) {
|
||||
const port = Number(opts.port ?? process.env.PORT ?? 20128) || 20128;
|
||||
root = `http://localhost:${port}`;
|
||||
}
|
||||
|
||||
function readJson(path) {
|
||||
const apiKey =
|
||||
opts.apiKey ??
|
||||
opts["api-key"] ??
|
||||
context?.accessToken ??
|
||||
context?.apiKey ??
|
||||
process.env.OMNIROUTE_API_KEY ??
|
||||
"sk_omniroute";
|
||||
|
||||
return { baseUrl: normalizeQwenCodeBaseUrl(root), apiKey };
|
||||
}
|
||||
|
||||
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 parsed;
|
||||
};
|
||||
|
||||
const readText = (filePath) => (existsSync(filePath) ? readFileSync(filePath, "utf8") : "");
|
||||
|
||||
const writeAtomic = (filePath, content, mode) => {
|
||||
const tempPath = `${filePath}.${process.pid}.${Date.now()}.tmp`;
|
||||
try {
|
||||
if (existsSync(path)) return JSON.parse(readFileSync(path, "utf8"));
|
||||
writeFileSync(tempPath, content, { encoding: "utf8", mode });
|
||||
if (mode !== undefined) chmodSync(tempPath, mode);
|
||||
renameSync(tempPath, filePath);
|
||||
} catch (error) {
|
||||
try {
|
||||
unlinkSync(tempPath);
|
||||
} catch {
|
||||
/* corrupt/missing */
|
||||
// The temporary file may not have been created.
|
||||
}
|
||||
return {};
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
async function fetchModelIds(baseUrl, apiKey) {
|
||||
const fetchModelIds = async (baseUrl, apiKey) => {
|
||||
try {
|
||||
const headers = { "Content-Type": "application/json" };
|
||||
if (apiKey) headers["Authorization"] = `Bearer ${apiKey}`;
|
||||
const res = await fetch(`${baseUrl.replace(/\/v1$/, "")}/v1/models`, {
|
||||
headers,
|
||||
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 ? " …" : ""}`);
|
||||
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 = await prompt.ask("Model id for Qwen");
|
||||
model = String(await prompt.ask("Model id for Qwen Code")).trim();
|
||||
} finally {
|
||||
prompt.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!model) {
|
||||
printError("A model is required. Pass --model <id>.");
|
||||
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}`);
|
||||
}
|
||||
printInfo(
|
||||
"\nProvide the key (settings reference OMNIROUTE_API_KEY): export OMNIROUTE_API_KEY=..."
|
||||
);
|
||||
printInfo('Then run: qwen (or headless: qwen -p "reply OK")');
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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 <port>", "Local OmniRoute port (ignored when --remote is set)", "20128")
|
||||
.option("--remote <url>", "Remote OmniRoute URL, e.g. http://192.168.0.15:20128")
|
||||
.option("--api-key <key>", "OmniRoute API key (defaults to OMNIROUTE_API_KEY env var)")
|
||||
.option("--model <id>", "Model id for Qwen (required unless picked interactively)")
|
||||
.option("--config-path <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 <url>", "Remote OmniRoute URL")
|
||||
.option("--api-key <key>", "OmniRoute API key")
|
||||
.option("--model <id>", "Model id for Qwen Code")
|
||||
.option("--config-path <path>", "Qwen Code settings.json path")
|
||||
.option("--env-path <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;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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 (<cap): the combo/upstream-proxy context caches — getCombosCached/clearCombosCache/getUpstreamProxyConfigCached/clearUpstreamProxyConfigCache plus their consts COMBOS_CACHE_TTL/PROXY_CONFIG_CACHE_TTL AND the two module-level cache Maps (_proxyConfigCache + the _combosPromise/_combosCacheTs/_combosCacheVersionSnapshot combos cache) moved WITH their accessor functions so each Map stays a single instance in one module (state cohesion — no double-state). clearCombosCache/clearUpstreamProxyConfigCache re-exported from chatCore.ts (combo-cache-invalidation + chatcore-translation-paths tests). (2) executorHelpers.ts (<cap): resolveAccountSemaphoreKey/resolveAccountSemaphoreAccountKey/resolveAccountSemaphoreMaxConcurrency + buildClaudePromptCacheLogMeta; the private toFiniteNumberOrNull helper (its only remaining caller was resolveAccountSemaphoreMaxConcurrency) moved with them rather than creating a barrel import. Orphaned imports dropped from chatCore.ts (buildAccountSemaphoreKey, getUpstreamProxyConfig). No re-export needed for the executor helpers (none were ever public). Pure move, no logic change.",
|
||||
"_rebaseline_2026_06_18_qg9_chatcore_split_prA": "QG v2 Fase 9 T5 C2-C3-C5: chatCore.ts 5445->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 <cap, pure byte-identical moves (no runtime change): passthroughHelpers.ts (C2: shouldUseNativeCodexPassthrough/redactPassthroughThinkingSignatures/isClaudeCodeSemanticPassthroughRequest), responseHeaders.ts (C3: STREAMING_RESPONSE_HEADER_DENYLIST/buildStreamingResponseHeaders/materializeDeduplicatedExecutionResult/stripStaleForwardingHeaders), telemetryHelpers.ts (C5: forwardDashboardEventToLiveWs/maybeSyncClaudeExtraUsageState). chatCore.ts imports all 8 still-referenced symbols back from the leaves and re-exports the 5 previously-public ones (shouldUseNativeCodexPassthrough/redactPassthroughThinkingSignatures/isClaudeCodeSemanticPassthroughRequest/buildStreamingResponseHeaders/stripStaleForwardingHeaders) so existing test importers keep resolving. No barrel imports in the new leaves.",
|
||||
"_rebaseline_2026_06_18_4176_free_models": "PR #4176 own growth: AddApiKeyModal.tsx 845->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, <cap); the duplicated Toggle is ~6 lines and the per-modal state wiring is intrinsic, so the remaining growth is cohesive UI, not an extractable block.",
|
||||
"_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 (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 <maxWaitMs> 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 (<cap, normalizeShadowRoutingConfig/resolveShadowTargets/drainShadowResponse/withTimeout/cloneRequestBodyForShadowRouting/scheduleShadowRouting). The shared isRecord guard moved to the new combo/comboData.ts leaf (<cap) so submodules can use it without importing the barrel. resolveShadowTargets/scheduleShadowRouting re-exported from combo.ts for compatibility (handleComboChat/handleRoundRobinCombo callers unchanged). Pure move, no logic change.",
|
||||
"_rebaseline_2026_06_18_qg9_combo_split_d5": "QG v2 Fase 9 T5 D5: combo.ts 4589->4430 — target sorters extracted byte-identically to the new open-sse/services/combo/targetSorters.ts (<cap, normalizeModelEntry/selectWeightedTarget/orderTargetsForWeightedFallback/sortModelsByCost/sortTargetsByCost/sortModelsByUsage/sortTargetsByUsage/getP2CTargetScore/orderTargetsByPowerOfTwoChoices). None were ever public, so combo.ts imports back only the six still called by code that stays (no re-export). getComboStepTarget/getComboStepWeight imports dropped from combo.ts (only normalizeModelEntry used them). Pure move, no logic change.",
|
||||
@@ -306,7 +306,6 @@
|
||||
"_rebaseline_2026_06_27_5193_antigravity_test": "#5193 own test growth: oauth-providers-config.test.ts 870->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,
|
||||
|
||||
@@ -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 | ✅ | ✅ | ✅ | ❌ |
|
||||
|
||||
@@ -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` |
|
||||
|
||||
@@ -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` |
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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: <id>`
|
||||
|
||||
**Solution**: Only 4 agents are allowed in `spawn()`:
|
||||
**Solution**: Only these agents are allowed in `spawn()`:
|
||||
|
||||
- `claude`
|
||||
- `codex`
|
||||
|
||||
@@ -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/<id>`) + 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 |
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -215,8 +215,8 @@ context, or `--remote <url> --api-key <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/<id>`) + 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)
|
||||
|
||||
@@ -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 <url> --api-key <key>` to configure a local tool against a
|
||||
|
||||
@@ -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`
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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 <url> --api-key <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 <id>` (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`.
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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…
|
||||
|
||||
@@ -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 <token>', ~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. |
|
||||
|
||||
@@ -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.
|
||||
|
||||
4
llm.txt
4
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
|
||||
|
||||
|
||||
@@ -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<string, CliFingerprint> = {
|
||||
],
|
||||
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(),
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -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 || "",
|
||||
|
||||
@@ -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<st
|
||||
};
|
||||
}
|
||||
|
||||
export function getQwenOauthHeaders(): Record<string, string> {
|
||||
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<string, string> {
|
||||
return {
|
||||
"User-Agent": QODER_DEFAULT_USER_AGENT,
|
||||
|
||||
@@ -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<string, RegistryEntry> = {
|
||||
huggingface: huggingfaceProvider,
|
||||
chipotle: chipotleProvider,
|
||||
freeaiapikey: freeaiapikeyProvider,
|
||||
qwen: qwenProvider,
|
||||
"qwen-web": qwen_webProvider,
|
||||
"zai-web": zai_webProvider,
|
||||
modal: modalProvider,
|
||||
|
||||
@@ -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)" },
|
||||
],
|
||||
};
|
||||
@@ -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",
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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<string, unknown>;
|
||||
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<string, unknown>,
|
||||
"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<string, unknown>;
|
||||
const outboundModel = typeof bodyRecord.model === "string" ? bodyRecord.model : model;
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<string, unknown>;
|
||||
type CredentialsLike =
|
||||
| { apiKey?: unknown; accessToken?: unknown; providerSpecificData?: Record<string, unknown> | null }
|
||||
| {
|
||||
apiKey?: unknown;
|
||||
accessToken?: unknown;
|
||||
providerSpecificData?: Record<string, unknown> | 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;
|
||||
}
|
||||
|
||||
@@ -60,7 +60,6 @@ export {
|
||||
refreshAccessToken,
|
||||
refreshClaudeOAuthToken,
|
||||
refreshGoogleToken,
|
||||
refreshQwenToken,
|
||||
refreshCodexToken,
|
||||
refreshQoderToken,
|
||||
refreshGitHubToken,
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -27,7 +27,6 @@ const ROTATION_LOCK_GROUP: Record<string, string> = {
|
||||
"gitlab-duo": "gitlab-duo",
|
||||
kiro: "kiro",
|
||||
"kimi-coding": "kimi-coding",
|
||||
qwen: "qwen",
|
||||
};
|
||||
|
||||
// Protective settle gap (ms) between two consecutive sibling refreshes when the
|
||||
|
||||
@@ -51,7 +51,6 @@ export const LEGACY_FREE_PROVIDERS: readonly string[] = [
|
||||
"pollinations",
|
||||
"longcat",
|
||||
"cloudflare-ai",
|
||||
"qwen",
|
||||
"nvidia-nim",
|
||||
"cerebras",
|
||||
"groq",
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
"pollinations",
|
||||
"longcat",
|
||||
"cloudflare-ai",
|
||||
"qwen",
|
||||
"nvidia-nim",
|
||||
"cerebras",
|
||||
"groq"
|
||||
|
||||
@@ -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<string, number> = {
|
||||
"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":
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
*
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -52,6 +52,7 @@ export const SPAWN_CAPABLE_ROUTE_ROOTS: ReadonlyArray<string> = [
|
||||
"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
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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.",
|
||||
|
||||
@@ -40,7 +40,6 @@ export const SUPPORTED_WIZARD_OAUTH_PROVIDER_IDS = new Set([
|
||||
"codex",
|
||||
"antigravity",
|
||||
"agy",
|
||||
"qwen",
|
||||
"kimi-coding",
|
||||
"github",
|
||||
"gitlab-duo",
|
||||
|
||||
@@ -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<string, unknown>[] | undefined;
|
||||
if (!Array.isArray(mp)) return null;
|
||||
for (const provider of mp) {
|
||||
const baseUrl = (provider as Record<string, unknown>).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;
|
||||
|
||||
@@ -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<string, any> = {};
|
||||
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
|
||||
*
|
||||
|
||||
@@ -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<string> => {
|
||||
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<boolean> => {
|
||||
try {
|
||||
const envPath = getQwenEnvPath();
|
||||
return await fs.readFile(envPath, "utf-8");
|
||||
} catch (error: any) {
|
||||
if (error.code === "ENOENT") return "";
|
||||
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;
|
||||
await fs.access(filePath);
|
||||
return true;
|
||||
} catch {
|
||||
return true; // invalid URLs are treated as custom endpoints
|
||||
return false;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// GET - Check Qwen CLI and read current settings
|
||||
export async function GET(request: Request) {
|
||||
const readSettings = async (filePath: string): Promise<Record<string, unknown>> => {
|
||||
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<string, unknown>;
|
||||
};
|
||||
|
||||
const writeAtomic = async (filePath: string, content: string, mode?: number): Promise<void> => {
|
||||
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;
|
||||
}
|
||||
};
|
||||
|
||||
export async function GET(request: Request): Promise<Response> {
|
||||
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<Response> {
|
||||
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) {
|
||||
);
|
||||
}
|
||||
|
||||
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 writeGuard = ensureCliConfigWriteAllowed();
|
||||
if (writeGuard) return NextResponse.json({ error: writeGuard }, { status: 403 });
|
||||
|
||||
try {
|
||||
const configPaths = getPaths();
|
||||
const bodyRecord = rawBody as Record<string, unknown>;
|
||||
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 */
|
||||
}
|
||||
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<string, any> = {};
|
||||
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<Response> {
|
||||
const authError = await requireCliToolsAuth(request);
|
||||
if (authError) return authError;
|
||||
|
||||
try {
|
||||
const writeGuard = ensureCliConfigWriteAllowed();
|
||||
if (writeGuard) {
|
||||
return NextResponse.json({ error: writeGuard }, { status: 403 });
|
||||
}
|
||||
|
||||
const settingsPath = getQwenSettingsPath();
|
||||
const envPath = getQwenEnvPath();
|
||||
|
||||
// Backup current settings before resetting
|
||||
await createBackup("qwen", settingsPath);
|
||||
|
||||
// --- Clean settings.json ---
|
||||
let existingConfig: Record<string, any> = {};
|
||||
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",
|
||||
});
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
|
||||
// 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"];
|
||||
if (writeGuard) return NextResponse.json({ error: writeGuard }, { status: 403 });
|
||||
|
||||
try {
|
||||
let envContent = await fs.readFile(envPath, "utf-8");
|
||||
const envLines = envContent
|
||||
.split("\n")
|
||||
.filter((line) => !RESET_ENV_KEYS.some((key) => line.startsWith(`${key}=`)));
|
||||
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);
|
||||
|
||||
await fs.writeFile(envPath, envLines.join("\n").trim() + "\n", "utf-8");
|
||||
} catch {
|
||||
// .env doesn't exist — nothing to clean
|
||||
if (settingsExists || envExists) {
|
||||
await createMultiBackup("qwen", [configPaths.settings, configPaths.env]);
|
||||
if (settingsExists) {
|
||||
await writeAtomic(configPaths.settings, `${JSON.stringify(nextSettings, null, 2)}\n`);
|
||||
}
|
||||
if (envExists) await writeAtomic(configPaths.env, nextEnv, 0o600);
|
||||
}
|
||||
|
||||
// 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 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,8 +60,8 @@ export async function GET(request: Request) {
|
||||
"openclaw",
|
||||
"cline",
|
||||
"kilo",
|
||||
"qwen",
|
||||
"hermes",
|
||||
"qwen",
|
||||
];
|
||||
|
||||
await Promise.all(
|
||||
|
||||
@@ -139,16 +139,8 @@ export const PROVIDER_MODELS_CONFIG: Record<string, ProviderModelsConfigEntry> =
|
||||
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<string, ProviderModelsConfigEntry> =
|
||||
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",
|
||||
|
||||
@@ -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.
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user