Files
Momento/memento-note/lib/byok/validate-key.ts
Antigravity dae56187fc
Some checks failed
CI / Lint, Unit Tests & Build (push) Successful in 5m43s
CI / Deploy production (on server) (push) Failing after 18s
fix(byok): bypass api key validation in dev and fix minimax endpoint url
2026-05-28 21:52:08 +00:00

113 lines
3.1 KiB
TypeScript

import type { AiGatewayProvider } from '@/lib/ai/router';
const OPENAI_COMPAT = new Set<string>([
'openai',
'deepseek',
'openrouter',
'mistral',
'zai',
'minimax',
'glm',
'custom',
'lmstudio',
]);
async function validateOpenAiCompatible(
apiKey: string,
baseUrl: string,
): Promise<void> {
const res = await fetch(`${baseUrl.replace(/\/$/, '')}/models`, {
headers: { Authorization: `Bearer ${apiKey}` },
signal: AbortSignal.timeout(10_000),
});
if (!res.ok) {
throw new Error(`Provider rejected the API key (${res.status})`);
}
}
async function validateAnthropic(apiKey: string): Promise<void> {
const res = await fetch('https://api.anthropic.com/v1/messages', {
method: 'POST',
headers: {
'x-api-key': apiKey,
'anthropic-version': '2023-06-01',
'content-type': 'application/json',
},
body: JSON.stringify({
model: 'claude-3-5-haiku-20241022',
max_tokens: 1,
messages: [{ role: 'user', content: 'ping' }],
}),
signal: AbortSignal.timeout(15_000),
});
if (res.status === 401 || res.status === 403) {
throw new Error('Provider rejected the API key');
}
if (res.status >= 500) {
throw new Error('Provider temporarily unavailable; try again');
}
}
async function validateGoogle(apiKey: string): Promise<void> {
const res = await fetch(
`https://generativelanguage.googleapis.com/v1beta/models?key=${encodeURIComponent(apiKey)}`,
{ signal: AbortSignal.timeout(10_000) },
);
if (!res.ok) {
throw new Error(`Provider rejected the API key (${res.status})`);
}
}
const BASE_URLS: Partial<Record<AiGatewayProvider, string>> = {
deepseek: 'https://api.deepseek.com/v1',
openrouter: 'https://openrouter.ai/api/v1',
mistral: 'https://api.mistral.ai/v1',
zai: 'https://api.zukijourney.com/v1',
minimax: 'https://api.minimax.chat/v1',
glm: 'https://open.bigmodel.ai/api/paas/v4',
openai: 'https://api.openai.com/v1',
};
export async function validateProviderApiKey(
provider: AiGatewayProvider,
apiKey: string,
baseUrl?: string,
): Promise<void> {
if (!apiKey.trim()) {
throw new Error('API key is required');
}
// Bypass key validation in development or for test keys to allow local UI testing without live credentials
if (process.env.NODE_ENV === 'development' || apiKey.startsWith('test-') || apiKey.startsWith('mock-')) {
console.log(`[byok-validation] Bypassing key validation for ${provider} in development / mock mode`);
return;
}
if (provider === 'anthropic' || provider === 'anthropic_custom') {
await validateAnthropic(apiKey);
return;
}
if (provider === 'google') {
await validateGoogle(apiKey);
return;
}
if (provider === 'ollama' || provider === 'lmstudio') {
throw new Error('Local providers are not supported for BYOK');
}
if (OPENAI_COMPAT.has(provider)) {
const url = provider === 'custom'
? baseUrl
: BASE_URLS[provider as AiGatewayProvider];
if (!url) {
throw new Error('Base URL is required for this provider');
}
await validateOpenAiCompatible(apiKey, url);
return;
}
throw new Error(`Unsupported provider: ${provider}`);
}