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.
|
- `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.
|
- 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.
|
- 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(...)`).
|
- 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 { prompt } = require("./input");
|
||||||
const { clearScreen } = require("./display");
|
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 = [
|
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"
|
"openrouter", "glm", "kimi", "minimax", "openai", "anthropic", "gemini"
|
||||||
];
|
];
|
||||||
|
|
||||||
// Alias to display name mapping
|
// Alias to display name mapping
|
||||||
const PROVIDER_ALIAS_NAMES = {
|
const PROVIDER_ALIAS_NAMES = {
|
||||||
cc: "Claude Code",
|
cc: "Claude Code",
|
||||||
ag: "Antigravity",
|
ag: "Antigravity",
|
||||||
cx: "OpenAI Codex",
|
cx: "OpenAI Codex",
|
||||||
if: "iFlow AI",
|
if: "iFlow AI",
|
||||||
qw: "Qwen Code",
|
qw: "Qwen Code",
|
||||||
gc: "Gemini CLI",
|
gc: "Gemini CLI",
|
||||||
gh: "GitHub Copilot",
|
gh: "GitHub Copilot",
|
||||||
kr: "Kiro AI",
|
kr: "Kiro AI",
|
||||||
|
oc: "OpenCode Free",
|
||||||
|
opencode: "OpenCode Free",
|
||||||
openrouter: "OpenRouter",
|
openrouter: "OpenRouter",
|
||||||
glm: "GLM Coding",
|
glm: "GLM Coding",
|
||||||
kimi: "Kimi Coding",
|
kimi: "Kimi Coding",
|
||||||
@@ -27,30 +29,78 @@ const PROVIDER_ALIAS_NAMES = {
|
|||||||
gemini: "Gemini"
|
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}>}
|
* @returns {Promise<{combos: Array, groups: Object}>}
|
||||||
*/
|
*/
|
||||||
async function getAvailableModelsGrouped() {
|
async function getAvailableModelsGrouped() {
|
||||||
const result = await api.getAvailableModels();
|
const [modelsResult, providersResult] = await Promise.all([
|
||||||
if (!result.success) return { combos: [], groups: {} };
|
api.getAvailableModels(),
|
||||||
|
api.getProviders()
|
||||||
const models = result.data?.data || [];
|
]);
|
||||||
|
|
||||||
|
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 combos = [];
|
||||||
const groups = {};
|
const groups = {};
|
||||||
|
|
||||||
models.forEach(m => {
|
models.forEach(m => {
|
||||||
if (m.owned_by === "combo") {
|
if (m.owned_by === "combo") {
|
||||||
combos.push(m.id);
|
combos.push(m.id);
|
||||||
} else {
|
} else {
|
||||||
const provider = m.owned_by;
|
const provider = m.owned_by;
|
||||||
|
// Only keep connected providers or noAuth providers
|
||||||
|
if (!activeAliases.has(provider)) return;
|
||||||
if (!groups[provider]) {
|
if (!groups[provider]) {
|
||||||
groups[provider] = [];
|
groups[provider] = [];
|
||||||
}
|
}
|
||||||
groups[provider].push(m.id);
|
groups[provider].push(m.id);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return { combos, groups };
|
return { combos, groups };
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,6 +118,19 @@ async function selectModelFromList(title, currentValue = "", options = {}) {
|
|||||||
|
|
||||||
const totalModels = combos.length + Object.values(groups).flat().length;
|
const totalModels = combos.length + Object.values(groups).flat().length;
|
||||||
if (totalModels === 0) {
|
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;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user