fix(cli): filter model selector by active connections and noAuth providers
Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
@@ -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(...)`).
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user