Fix provider limits to only use configured providers and support OpenRouter model formats

This commit is contained in:
southseact-3d
2026-02-09 18:03:08 +00:00
parent e9c5200e7c
commit f9dc1a6920

View File

@@ -5741,12 +5741,27 @@ function extractProviderName(p) {
function collectProviderSeeds() {
const seeds = new Set(DEFAULT_PROVIDER_SEEDS);
Object.keys(providerLimits.limits || {}).forEach((p) => seeds.add(normalizeProviderName(p)));
Object.keys(providerUsage || {}).forEach((p) => seeds.add(normalizeProviderName(p)));
adminModels.forEach((m) => {
(m.providers || []).forEach((p) => seeds.add(extractProviderName(p)));
const validProviders = new Set(DEFAULT_PROVIDER_SEEDS);
// Only include providers from limits/usage that are valid configured providers
// This prevents "moonshot" or "z-ai" from being treated as providers
Object.keys(providerLimits.limits || {}).forEach((p) => {
const normalized = normalizeProviderName(p);
if (validProviders.has(normalized)) seeds.add(normalized);
});
Object.keys(providerUsage || {}).forEach((p) => {
const normalized = normalizeProviderName(p);
if (validProviders.has(normalized)) seeds.add(normalized);
});
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);
});
(planSettings.planningChain || []).forEach((entry) => seeds.add(normalizeProviderName(entry.provider)));
return Array.from(seeds);
}
@@ -5792,13 +5807,38 @@ async function discoverProviderModels() {
try {
const models = await listModels(DEFAULT_PROVIDER_FALLBACK);
// Build a set of valid configured providers to avoid treating model name prefixes as providers
const validProviders = new Set(DEFAULT_PROVIDER_SEEDS);
models.forEach((m) => {
// listModels may return strings or objects; handle both.
const name = m.name || m.id || m;
let name = m.name || m.id || m;
if (!name || typeof name !== 'string') return;
// Handle OpenRouter model name formats:
// 1. "openrouter/z-ai/glm-4.7" -> provider: openrouter, model: openrouter/z-ai/glm-4.7
// 2. "z-ai/glm-4.7 (OpenRouter)" -> provider: openrouter, model: z-ai/glm-4.7
// 3. "z-ai/glm-4.7" without prefix/label -> provider: openrouter (detected from slash), model: z-ai/glm-4.7
const openRouterLabelMatch = name.match(/\s*\(openrouter\)\s*$/i);
if (openRouterLabelMatch) {
// Format: "z-ai/glm-4.7 (OpenRouter)" - strip the label and treat as openrouter
const cleanName = name.slice(0, openRouterLabelMatch.index).trim();
add('openrouter', cleanName);
return;
}
const parts = name.split('/');
if (parts.length > 1 && parts[0]) add(parts[0], name);
else add(DEFAULT_PROVIDER_FALLBACK, name);
if (parts.length > 1 && parts[0] && validProviders.has(parts[0].toLowerCase())) {
// The first part is a known configured provider (e.g., "openrouter/z-ai/glm-4.7")
add(parts[0], name);
} else if (parts.length > 1) {
// Model has a slash but first part is not a known provider (e.g., "z-ai/glm-4.7")
// This is likely an OpenRouter model without the prefix
add('openrouter', name);
} else {
// No slash in name, use default provider
add(DEFAULT_PROVIDER_FALLBACK, name);
}
});
} catch (err) {
log('provider discovery failed', { error: String(err) });
@@ -5826,6 +5866,19 @@ async function loadProviderLimits() {
} catch (error) {
log('Failed to load provider limits, using defaults', { error: String(error) });
}
// Clean up any invalid providers that may have been saved previously
// (e.g., "moonshot", "z-ai" that were incorrectly extracted from model names)
const validProviders = new Set(DEFAULT_PROVIDER_SEEDS);
if (providerLimits.limits) {
Object.keys(providerLimits.limits).forEach((key) => {
if (!validProviders.has(normalizeProviderName(key))) {
delete providerLimits.limits[key];
log('Removed invalid provider from limits', { provider: key });
}
});
}
collectProviderSeeds().forEach((p) => ensureProviderLimitDefaults(p));
if (!providerLimits.opencodeBackupModel) providerLimits.opencodeBackupModel = '';
}
@@ -14844,6 +14897,83 @@ async function handleAdminCancelMessages(req, res) {
}
}
// Test Ollama connection from admin panel
async function handleAdminOllamaTest(req, res) {
const adminSession = requireAdminAuth(req, res);
if (!adminSession) return;
try {
const startTime = Date.now();
// Log the configuration (without exposing the full API key)
const apiKeyConfigured = !!OLLAMA_API_KEY;
const apiKeyPreview = OLLAMA_API_KEY
? `${OLLAMA_API_KEY.substring(0, 4)}...${OLLAMA_API_KEY.substring(OLLAMA_API_KEY.length - 4)}`
: 'Not configured';
console.log('[ADMIN] Testing Ollama connection', {
url: OLLAMA_API_URL,
model: OLLAMA_DEFAULT_MODEL,
apiKeyConfigured,
apiKeyPreview
});
// Make a test request to Ollama
const testMessages = [
{ role: 'user', content: 'Say "Ollama test successful" and nothing else.' }
];
let result;
let error = null;
try {
result = await sendOllamaChat({ messages: testMessages, model: OLLAMA_DEFAULT_MODEL });
} catch (err) {
error = err;
console.log('[ADMIN] Ollama test failed', {
error: err.message,
status: err.status,
detail: err.detail
});
}
const duration = Date.now() - startTime;
// Prepare the response
const response = {
ok: !error,
duration,
config: {
url: OLLAMA_API_URL,
model: OLLAMA_DEFAULT_MODEL,
apiKeyConfigured,
apiKeyPreview
},
result: error ? null : {
reply: result.reply,
model: result.model
},
error: error ? {
message: error.message,
status: error.status,
detail: error.detail,
isAuthError: error.isAuthError,
isModelMissing: error.isModelMissing,
code: error.code
} : null
};
console.log('[ADMIN] Ollama test completed', { ok: response.ok, duration });
sendJson(res, 200, response);
} catch (error) {
console.error('[ADMIN] Ollama test handler error', error);
sendJson(res, 500, {
ok: false,
error: error.message || 'Internal server error during Ollama test'
});
}
}
// Get detailed resource usage breakdown by session for admin panel
async function handleAdminResources(req, res) {
const adminSession = requireAdminAuth(req, res);
@@ -16936,6 +17066,7 @@ async function routeInternal(req, res, url, pathname) {
if (req.method === 'POST' && pathname === '/api/admin/external-testing-self-test') return handleAdminExternalTestingSelfTest(req, res);
if (req.method === 'POST' && pathname === '/api/upgrade-popup-tracking') return handleUpgradePopupTracking(req, res);
if (req.method === 'POST' && pathname === '/api/admin/cancel-messages') return handleAdminCancelMessages(req, res);
if (req.method === 'POST' && pathname === '/api/admin/ollama-test') return handleAdminOllamaTest(req, res);
const adminDeleteMatch = pathname.match(/^\/api\/admin\/models\/([a-f0-9\-]+)$/i);
if (req.method === 'DELETE' && adminDeleteMatch) return handleAdminModelDelete(req, res, adminDeleteMatch[1]);
if (req.method === 'GET' && pathname === '/api/models') {