fix(cli): filter model selector by active connections and noAuth providers

Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
decolua
2026-09-26 11:14:04 +07:00
parent dc198dff1f
commit f462837536
2 changed files with 74 additions and 10 deletions

View File

@@ -88,4 +88,5 @@ Pre-translate hooks that compress `tool_result` content in-place to cut tokens.
- `custom-server.js` wraps the Next standalone server to derive client IP from the TCP socket and strip attacker-controlled `X-Forwarded-For` — trusting forwarding headers only from a loopback reverse proxy. Preserve this when touching request/IP/rate-limit code.
- Security-sensitive env: `JWT_SECRET` (session cookie), `INITIAL_PASSWORD` (default `123456` — must override), `API_KEY_SECRET`, `MACHINE_ID_SALT`. Full env contract in `.env.example` and ARCHITECTURE.md's env matrix.
- Binary/protobuf upstreams (kiro EventStream, cursor protobuf, commandcode NDJSON) don't round-trip through OpenAI — they're handled inside their own executor, not the translator.
- **Security-first on PRs**: Security is the top priority when reviewing or creating PRs. Audit authentication, credential/token storage & leaks, header manipulation (`X-Forwarded-For`), and SSRF risks before functional logic. Always include explicit security warnings/notes when reporting PR reviews or changes to the user.
- Versioning: root and `cli/` are versioned independently; changes are logged in `CHANGELOG.md`. Commit style is Conventional Commits (`fix(translator): …`, `feat(...)`).

View File

@@ -2,22 +2,24 @@ const api = require("../api/client");
const { prompt } = require("./input");
const { clearScreen } = require("./display");
// Provider alias order: OAuth first, then API Key (matches ModelSelectModal)
// Provider alias order: OAuth first, then Free, then API Key
const PROVIDER_ALIAS_ORDER = [
"cc", "ag", "cx", "if", "qw", "gc", "gh", "kr",
"cc", "ag", "cx", "if", "qw", "gc", "gh", "kr", "oc",
"openrouter", "glm", "kimi", "minimax", "openai", "anthropic", "gemini"
];
// Alias to display name mapping
const PROVIDER_ALIAS_NAMES = {
cc: "Claude Code",
ag: "Antigravity",
ag: "Antigravity",
cx: "OpenAI Codex",
if: "iFlow AI",
qw: "Qwen Code",
gc: "Gemini CLI",
gh: "GitHub Copilot",
kr: "Kiro AI",
oc: "OpenCode Free",
opencode: "OpenCode Free",
openrouter: "OpenRouter",
glm: "GLM Coding",
kimi: "Kimi Coding",
@@ -27,30 +29,78 @@ const PROVIDER_ALIAS_NAMES = {
gemini: "Gemini"
};
const PROVIDER_ID_TO_ALIAS = {
claude: "cc",
codex: "cx",
"gemini-cli": "gc",
github: "gh",
antigravity: "ag",
iflow: "if",
qwen: "qw",
kiro: "kr",
cursor: "cu",
cline: "cline",
clinepass: "clinepass",
qoder: "qd",
"qoder-cn": "qd",
gitlab: "gitlab",
"codebuddy-cn": "cb",
"codebuddy-intl": "cbai",
kimchi: "kimchi",
"grok-cli": "grok-cli",
trae: "trae",
windsurf: "windsurf",
zed: "zed",
opencode: "oc",
"opencode-go": "ocg",
"opencode-zen": "ocz",
};
// Providers usable without stored credentials
const NO_AUTH_PROVIDERS = new Set(["opencode", "oc"]);
/**
* Get all available models grouped by provider + combos
* Get all available models grouped by provider + combos (filtered by active connections)
* @returns {Promise<{combos: Array, groups: Object}>}
*/
async function getAvailableModelsGrouped() {
const result = await api.getAvailableModels();
if (!result.success) return { combos: [], groups: {} };
const models = result.data?.data || [];
const [modelsResult, providersResult] = await Promise.all([
api.getAvailableModels(),
api.getProviders()
]);
if (!modelsResult.success) return { combos: [], groups: {} };
const connections = providersResult.success ? (providersResult.data?.connections || []) : [];
const activeAliases = new Set(NO_AUTH_PROVIDERS);
connections.forEach(conn => {
if (conn.isActive === false) return;
const p = conn.provider;
if (!p) return;
activeAliases.add(p);
const alias = conn.providerSpecificData?.prefix || PROVIDER_ID_TO_ALIAS[p] || p;
activeAliases.add(alias);
});
const models = modelsResult.data?.data || [];
const combos = [];
const groups = {};
models.forEach(m => {
if (m.owned_by === "combo") {
combos.push(m.id);
} else {
const provider = m.owned_by;
// Only keep connected providers or noAuth providers
if (!activeAliases.has(provider)) return;
if (!groups[provider]) {
groups[provider] = [];
}
groups[provider].push(m.id);
}
});
return { combos, groups };
}
@@ -68,6 +118,19 @@ async function selectModelFromList(title, currentValue = "", options = {}) {
const totalModels = combos.length + Object.values(groups).flat().length;
if (totalModels === 0) {
clearScreen();
console.log(`\n🎯 ${title}`);
console.log("=".repeat(50));
console.log("\n No connected providers found.");
console.log(" Please connect a provider in Providers menu first.\n");
console.log(" m. ✍️ Enter custom model ID");
console.log(" 0. Cancel\n");
const act = await prompt("Select option (m/0): ");
const trimmed = act.trim();
if (trimmed.toLowerCase() === "m") {
const custom = await prompt("Enter custom model ID: ");
return custom.trim() || null;
}
return null;
}