Fix provider limits to only use configured providers and support OpenRouter model formats
This commit is contained in:
147
chat/server.js
147
chat/server.js
@@ -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') {
|
||||
|
||||
Reference in New Issue
Block a user