Remove separate chain concept - fallback is now determined by OpenCode models order
- Removed opencodeChain variable entirely - Removed chain form/list from admin UI - Fallback now uses the order of models in the OpenCode models list - Updated buildOpencodeAttemptChain to iterate through opencodeModels - Removed chain-related API endpoints - Simplified to just two lists: opencodeModels and publicModels
This commit is contained in:
194
chat/server.js
194
chat/server.js
@@ -1524,10 +1524,9 @@ const MODELS_DEV_CACHE_TTL = 3600000; // 1 hour
|
||||
const adminSessions = new Map();
|
||||
let adminModels = [];
|
||||
let adminModelIndex = new Map();
|
||||
// Clean model structure
|
||||
let opencodeModels = []; // Models from OpenCode [{id, name, label, icon, tier, supportsMedia, multiplier}]
|
||||
let opencodeChain = []; // OpenCode fallback chain [{provider, model}]
|
||||
let publicModels = []; // Public-facing models (completely separate) [{id, name, label, icon, tier, supportsMedia, multiplier}]
|
||||
// Simple model structure - just two lists
|
||||
let opencodeModels = []; // Models from OpenCode - order determines fallback chain
|
||||
let publicModels = []; // Public-facing models (completely separate)
|
||||
let openrouterSettings = {
|
||||
primaryModel: OPENROUTER_MODEL_PRIMARY,
|
||||
backupModel1: OPENROUTER_MODEL_BACKUP_1,
|
||||
@@ -5161,11 +5160,12 @@ async function ensureOpencodeConfig(session) {
|
||||
// Find which providers are used in models
|
||||
const usedProviders = new Set();
|
||||
|
||||
// Check opencode chain first
|
||||
if (opencodeChain.length > 0) {
|
||||
for (const p of opencodeChain) {
|
||||
if (p.provider) {
|
||||
usedProviders.add(p.provider.toLowerCase());
|
||||
// Check opencode models for provider prefixes in names (e.g., "openrouter/model-name")
|
||||
for (const model of opencodeModels) {
|
||||
if (model.name && model.name.includes('/')) {
|
||||
const providerFromName = model.name.split('/')[0].toLowerCase();
|
||||
if (providerFromName && providerFromName !== 'opencode') {
|
||||
usedProviders.add(providerFromName);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5205,8 +5205,7 @@ async function ensureOpencodeConfig(session) {
|
||||
|
||||
log('Detected providers from models', {
|
||||
usedProviders: Array.from(usedProviders),
|
||||
count: usedProviders.size,
|
||||
source: opencodeChain.length > 0 ? 'opencodeChain' : 'legacy'
|
||||
count: usedProviders.size
|
||||
});
|
||||
|
||||
// Provider configurations with their base URLs
|
||||
@@ -5760,17 +5759,14 @@ async function loadAdminModelStore() {
|
||||
const raw = await fs.readFile(ADMIN_MODELS_FILE, 'utf8').catch(() => '{}');
|
||||
const parsed = JSON.parse(raw || '{}');
|
||||
|
||||
// Load clean structure
|
||||
// Load simple structure
|
||||
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
|
||||
// New clean structure
|
||||
opencodeModels = Array.isArray(parsed.opencodeModels) ? parsed.opencodeModels : [];
|
||||
opencodeChain = Array.isArray(parsed.opencodeChain) ? parsed.opencodeChain : [];
|
||||
publicModels = Array.isArray(parsed.publicModels) ? parsed.publicModels : [];
|
||||
adminModels = Array.isArray(parsed.adminModels) ? parsed.adminModels : [];
|
||||
} else if (Array.isArray(parsed)) {
|
||||
// Old array structure - migrate
|
||||
adminModels = parsed;
|
||||
// Migrate old admin models to opencode models
|
||||
opencodeModels = parsed.map((m) => ({
|
||||
id: m.id || randomUUID(),
|
||||
name: m.name,
|
||||
@@ -5781,26 +5777,8 @@ async function loadAdminModelStore() {
|
||||
multiplier: getTierMultiplier(m.tier),
|
||||
})).filter((m) => !!m.name);
|
||||
publicModels = [];
|
||||
// Create chain from providers
|
||||
const allProviders = new Map();
|
||||
parsed.forEach((m) => {
|
||||
const providers = Array.isArray(m.providers) && m.providers.length
|
||||
? m.providers
|
||||
: [{ provider: 'opencode', model: m.name }];
|
||||
providers.forEach((p) => {
|
||||
const key = `${p.provider}:${p.model || m.name}`;
|
||||
if (!allProviders.has(key)) {
|
||||
allProviders.set(key, {
|
||||
provider: normalizeProviderName(p.provider || 'opencode'),
|
||||
model: (p.model || m.name || '').trim() || m.name,
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
opencodeChain = Array.from(allProviders.values());
|
||||
} else {
|
||||
opencodeModels = [];
|
||||
opencodeChain = [];
|
||||
publicModels = [];
|
||||
adminModels = [];
|
||||
}
|
||||
@@ -5827,22 +5805,14 @@ async function loadAdminModelStore() {
|
||||
multiplier: m.multiplier || getTierMultiplier(m.tier),
|
||||
})).filter((m) => !!m.name);
|
||||
|
||||
// Validate chain
|
||||
opencodeChain = opencodeChain.map((p) => ({
|
||||
provider: normalizeProviderName(p.provider || 'opencode'),
|
||||
model: (p.model || '').trim(),
|
||||
})).filter((p) => !!p.model);
|
||||
|
||||
refreshAdminModelIndex();
|
||||
log('Loaded admin model store', {
|
||||
opencodeModels: opencodeModels.length,
|
||||
opencodeChain: opencodeChain.length,
|
||||
publicModels: publicModels.length,
|
||||
});
|
||||
} catch (error) {
|
||||
log('Failed to load admin models, starting empty', { error: String(error) });
|
||||
opencodeModels = [];
|
||||
opencodeChain = [];
|
||||
publicModels = [];
|
||||
adminModels = [];
|
||||
refreshAdminModelIndex();
|
||||
@@ -5852,13 +5822,12 @@ async function loadAdminModelStore() {
|
||||
async function persistAdminModels() {
|
||||
await ensureStateFile();
|
||||
await ensureAssetsDir();
|
||||
// Save clean structure
|
||||
// Save simple structure
|
||||
const payload = JSON.stringify({
|
||||
opencodeModels,
|
||||
opencodeChain,
|
||||
publicModels,
|
||||
adminModels, // Keep legacy for backwards compatibility
|
||||
version: 4, // Clean structure
|
||||
adminModels,
|
||||
version: 5,
|
||||
}, null, 2);
|
||||
await safeWriteFile(ADMIN_MODELS_FILE, payload);
|
||||
refreshAdminModelIndex();
|
||||
@@ -6052,20 +6021,25 @@ function collectProviderSeeds() {
|
||||
const normalized = normalizeProviderName(p);
|
||||
if (validProviders.has(normalized)) seeds.add(normalized);
|
||||
});
|
||||
// Use opencode chain if available, otherwise fall back to legacy adminModels
|
||||
if (opencodeChain.length > 0) {
|
||||
opencodeChain.forEach((p) => {
|
||||
// Collect providers from opencode models (checking for provider prefixes in model names)
|
||||
opencodeModels.forEach((m) => {
|
||||
if (m.name && m.name.includes('/')) {
|
||||
const providerName = extractProviderName(m.name);
|
||||
if (validProviders.has(providerName)) seeds.add(providerName);
|
||||
}
|
||||
});
|
||||
|
||||
// Also check legacy adminModels
|
||||
adminModels.forEach((m) => {
|
||||
if (m.name && m.name.includes('/')) {
|
||||
const providerName = extractProviderName(m.name);
|
||||
if (validProviders.has(providerName)) seeds.add(providerName);
|
||||
}
|
||||
(m.providers || []).forEach((p) => {
|
||||
const providerName = extractProviderName(p);
|
||||
if (validProviders.has(providerName)) seeds.add(providerName);
|
||||
});
|
||||
} else {
|
||||
adminModels.forEach((m) => {
|
||||
(m.providers || []).forEach((p) => {
|
||||
const providerName = extractProviderName(p);
|
||||
if (validProviders.has(providerName)) seeds.add(providerName);
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
(planSettings.planningChain || []).forEach((entry) => {
|
||||
const normalized = normalizeProviderName(entry.provider);
|
||||
if (validProviders.has(normalized)) seeds.add(normalized);
|
||||
@@ -6109,14 +6083,17 @@ async function discoverProviderModels() {
|
||||
|
||||
collectProviderSeeds().forEach((p) => add(p));
|
||||
|
||||
// Use opencode chain if available, otherwise fall back to legacy adminModels
|
||||
if (opencodeChain.length > 0) {
|
||||
opencodeChain.forEach((p) => add(extractProviderName(p), p.model));
|
||||
} else {
|
||||
adminModels.forEach((m) => {
|
||||
(m.providers || []).forEach((p) => add(extractProviderName(p), p.model || m.name));
|
||||
});
|
||||
}
|
||||
// Collect models from opencodeModels (checking for provider prefixes)
|
||||
opencodeModels.forEach((m) => {
|
||||
if (m.name && m.name.includes('/')) {
|
||||
add(extractProviderName(m.name), m.name);
|
||||
}
|
||||
});
|
||||
|
||||
// Also check legacy adminModels
|
||||
adminModels.forEach((m) => {
|
||||
(m.providers || []).forEach((p) => add(extractProviderName(p), p.model || m.name));
|
||||
});
|
||||
|
||||
(planSettings.planningChain || []).forEach((entry) => {
|
||||
add(entry.provider, entry.model);
|
||||
@@ -10274,14 +10251,15 @@ function buildOpencodeAttemptChain(cli, preferredModel) {
|
||||
const chain = [];
|
||||
const seen = new Set();
|
||||
|
||||
// Build chain from opencodeChain with user's preferred model
|
||||
opencodeChain.forEach((p, idx) => {
|
||||
const modelToUse = idx === 0 && preferredModel ? preferredModel : p.model;
|
||||
const key = `${p.provider}:${modelToUse}`;
|
||||
// Build chain from opencodeModels order
|
||||
// The order in the list determines fallback priority
|
||||
opencodeModels.forEach((m, idx) => {
|
||||
const modelToUse = idx === 0 && preferredModel ? preferredModel : m.name;
|
||||
const key = `${modelToUse}`;
|
||||
if (seen.has(key)) return;
|
||||
seen.add(key);
|
||||
chain.push({
|
||||
provider: p.provider,
|
||||
provider: 'opencode',
|
||||
model: modelToUse,
|
||||
primary: idx === 0,
|
||||
cli: normalizeCli(cli || 'opencode'),
|
||||
@@ -10289,7 +10267,7 @@ function buildOpencodeAttemptChain(cli, preferredModel) {
|
||||
});
|
||||
});
|
||||
|
||||
// If no chain configured, fall back to default
|
||||
// If no models configured, fall back to default
|
||||
if (chain.length === 0) {
|
||||
chain.push({
|
||||
provider: 'opencode',
|
||||
@@ -10305,7 +10283,7 @@ function buildOpencodeAttemptChain(cli, preferredModel) {
|
||||
cli,
|
||||
preferredModel: (typeof preferredModel === 'string' && preferredModel.trim()) ? preferredModel : '(none)',
|
||||
chainLength: chain.length,
|
||||
models: chain.map(c => `${c.provider}:${c.model}`).slice(0, 5)
|
||||
models: chain.map(c => c.model).slice(0, 5)
|
||||
});
|
||||
|
||||
return chain;
|
||||
@@ -11339,14 +11317,14 @@ async function processMessage(sessionId, message) {
|
||||
function getConfiguredModels(cliParam = 'opencode') {
|
||||
const cli = normalizeCli(cliParam || 'opencode');
|
||||
// Return opencode models for backwards compatibility
|
||||
const mapped = opencodeModels.map((m) => ({
|
||||
const mapped = opencodeModels.map((m, idx) => ({
|
||||
id: m.id,
|
||||
name: m.name,
|
||||
label: m.label || m.name,
|
||||
icon: m.icon || '',
|
||||
cli: cli,
|
||||
providers: opencodeChain,
|
||||
primaryProvider: opencodeChain[0]?.provider || 'opencode',
|
||||
providers: [],
|
||||
primaryProvider: 'opencode',
|
||||
tier: m.tier || 'free',
|
||||
multiplier: getTierMultiplier(m.tier || 'free'),
|
||||
supportsMedia: m.supportsMedia ?? false,
|
||||
@@ -15857,7 +15835,6 @@ async function handleAdminModelsList(req, res) {
|
||||
// Return clean structure
|
||||
sendJson(res, 200, {
|
||||
opencodeModels,
|
||||
opencodeChain,
|
||||
publicModels,
|
||||
// Legacy support
|
||||
models: getConfiguredModels('opencode'),
|
||||
@@ -15900,7 +15877,6 @@ async function handleAdminModelUpsert(req, res) {
|
||||
await persistAdminModels();
|
||||
sendJson(res, 200, {
|
||||
opencodeModels,
|
||||
opencodeChain,
|
||||
publicModels,
|
||||
});
|
||||
} else if (body.type === 'public') {
|
||||
@@ -15912,7 +15888,6 @@ async function handleAdminModelUpsert(req, res) {
|
||||
await persistAdminModels();
|
||||
sendJson(res, 200, {
|
||||
opencodeModels,
|
||||
opencodeChain,
|
||||
publicModels,
|
||||
});
|
||||
} else {
|
||||
@@ -15977,7 +15952,6 @@ async function handleAdminModelsReorder(req, res) {
|
||||
sendJson(res, 200, {
|
||||
ok: true,
|
||||
opencodeModels,
|
||||
opencodeChain,
|
||||
publicModels,
|
||||
});
|
||||
} catch (error) {
|
||||
@@ -16010,72 +15984,10 @@ async function handleAdminModelDelete(req, res, id) {
|
||||
sendJson(res, 200, {
|
||||
ok: true,
|
||||
opencodeModels,
|
||||
opencodeChain,
|
||||
publicModels,
|
||||
});
|
||||
}
|
||||
|
||||
// Chain handlers
|
||||
async function handleAdminChainPost(req, res) {
|
||||
const session = requireAdminAuth(req, res);
|
||||
if (!session) return;
|
||||
try {
|
||||
const body = await parseJsonBody(req);
|
||||
if (!Array.isArray(body.chain)) {
|
||||
return sendJson(res, 400, { error: 'chain must be an array' });
|
||||
}
|
||||
|
||||
opencodeChain = body.chain.map((p) => ({
|
||||
provider: normalizeProviderName(p.provider || 'opencode'),
|
||||
model: (p.model || '').trim(),
|
||||
})).filter((p) => !!p.model);
|
||||
|
||||
await persistAdminModels();
|
||||
sendJson(res, 200, {
|
||||
opencodeChain,
|
||||
opencodeModels,
|
||||
publicModels,
|
||||
});
|
||||
} catch (error) {
|
||||
sendJson(res, 400, { error: error.message || 'Unable to update chain' });
|
||||
}
|
||||
}
|
||||
|
||||
async function handleAdminChainReorder(req, res) {
|
||||
const session = requireAdminAuth(req, res);
|
||||
if (!session) return;
|
||||
try {
|
||||
const body = await parseJsonBody(req);
|
||||
if (!Array.isArray(body.chain)) {
|
||||
return sendJson(res, 400, { error: 'chain array is required' });
|
||||
}
|
||||
|
||||
// Validate all entries
|
||||
const currentIds = new Set(opencodeChain.map((_, i) => i));
|
||||
const newIds = body.chain.map((_, i) => i);
|
||||
const allExist = newIds.every((_, i) => currentIds.has(i));
|
||||
|
||||
if (!allExist || body.chain.length !== opencodeChain.length) {
|
||||
return sendJson(res, 400, { error: 'Invalid chain order' });
|
||||
}
|
||||
|
||||
// Reorder
|
||||
opencodeChain = body.chain.map((p) => ({
|
||||
provider: normalizeProviderName(p.provider || 'opencode'),
|
||||
model: (p.model || '').trim(),
|
||||
})).filter((p) => !!p.model);
|
||||
|
||||
await persistAdminModels();
|
||||
sendJson(res, 200, {
|
||||
opencodeChain,
|
||||
opencodeModels,
|
||||
publicModels,
|
||||
});
|
||||
} catch (error) {
|
||||
sendJson(res, 400, { error: error.message || 'Unable to reorder chain' });
|
||||
}
|
||||
}
|
||||
|
||||
async function handleAdminOpenRouterSettingsGet(req, res) {
|
||||
const session = requireAdminAuth(req, res);
|
||||
if (!session) return;
|
||||
@@ -18976,8 +18888,6 @@ async function routeInternal(req, res, url, pathname) {
|
||||
if (req.method === 'GET' && pathname === '/api/admin/models') return handleAdminModelsList(req, res);
|
||||
if (req.method === 'POST' && pathname === '/api/admin/models') return handleAdminModelUpsert(req, res);
|
||||
if (req.method === 'POST' && pathname === '/api/admin/models/reorder') return handleAdminModelsReorder(req, res);
|
||||
if (req.method === 'POST' && pathname === '/api/admin/chain') return handleAdminChainPost(req, res);
|
||||
if (req.method === 'POST' && pathname === '/api/admin/chain/reorder') return handleAdminChainReorder(req, res);
|
||||
if (req.method === 'GET' && pathname === '/api/admin/openrouter-settings') return handleAdminOpenRouterSettingsGet(req, res);
|
||||
if (req.method === 'POST' && pathname === '/api/admin/openrouter-settings') return handleAdminOpenRouterSettingsPost(req, res);
|
||||
if (req.method === 'GET' && pathname === '/api/admin/mistral-settings') return handleAdminMistralSettingsGet(req, res);
|
||||
|
||||
Reference in New Issue
Block a user