Files
patchy631--ai-engineering-hub/open-agent-builder/lib/api/llm-keys.ts
T
2026-07-13 12:37:47 +08:00

170 lines
4.6 KiB
TypeScript

/**
* LLM API Key Management
*
* Provides API keys for LLM providers with fallback logic:
* 1. Check user-specific keys from database
* 2. Fall back to environment variables if no user key exists
*/
import { ConvexClient } from "convex/browser";
import { api } from "@/convex/_generated/api";
// Initialize Convex client for server-side use
const getConvexClient = () => {
const convexUrl = process.env.NEXT_PUBLIC_CONVEX_URL;
if (!convexUrl) {
throw new Error("NEXT_PUBLIC_CONVEX_URL is not configured");
}
return new ConvexClient(convexUrl);
};
/**
* Get the API key for a specific LLM provider
* Checks user keys first, then falls back to environment variables
*
* @param provider - The LLM provider ('anthropic', 'openai', 'groq')
* @param userId - Optional user ID to check for user-specific keys
* @returns The API key or null if not found
*/
export async function getLLMApiKey(
provider: 'anthropic' | 'openai' | 'groq',
userId?: string
): Promise<string | null> {
// First, try to get user-specific key if userId is provided
if (userId) {
try {
const client = getConvexClient();
const userKey = await client.query(api.userLLMKeys.getActiveKey, {
userId,
provider,
});
if (userKey?.apiKey) {
// Update usage stats
await client.mutation(api.userLLMKeys.updateKeyUsage, {
userId,
provider,
}).catch(console.error); // Don't fail if usage update fails
return userKey.apiKey;
}
} catch (error) {
console.error(`Failed to get user key for ${provider}:`, error);
// Continue to environment variable fallback
}
}
// Fall back to environment variables
const envKeyMap = {
anthropic: 'ANTHROPIC_API_KEY',
openai: 'OPENAI_API_KEY',
groq: 'GROQ_API_KEY',
};
const envKey = envKeyMap[provider];
const apiKey = process.env[envKey];
if (apiKey) {
return apiKey;
}
return null;
}
/**
* Check if a provider has an API key configured (either user or env)
*/
export async function isProviderConfigured(
provider: 'anthropic' | 'openai' | 'groq',
userId?: string
): Promise<boolean> {
const apiKey = await getLLMApiKey(provider, userId);
return !!apiKey;
}
/**
* Get all configured providers for a user
*/
export async function getConfiguredProviders(userId?: string): Promise<string[]> {
const providers: ('anthropic' | 'openai' | 'groq')[] = ['anthropic', 'openai', 'groq'];
const configured: string[] = [];
for (const provider of providers) {
if (await isProviderConfigured(provider, userId)) {
configured.push(provider);
}
}
return configured;
}
/**
* Initialize LLM client with appropriate API key
* This is a helper function that can be used by the execute routes
*/
export async function initializeLLMClient(
provider: 'anthropic' | 'openai' | 'groq',
userId?: string
): Promise<{ apiKey: string; provider: string }> {
const apiKey = await getLLMApiKey(provider, userId);
if (!apiKey) {
throw new Error(
`No API key found for ${provider}. Please configure your API key in Settings or set the ${
provider === 'anthropic' ? 'ANTHROPIC_API_KEY' :
provider === 'openai' ? 'OPENAI_API_KEY' :
'GROQ_API_KEY'
} environment variable.`
);
}
return { apiKey, provider };
}
/**
* Get configuration status for all providers
* Useful for the settings panel to show which providers are configured
*/
export async function getProvidersStatus(userId?: string): Promise<{
anthropic: { configured: boolean; source: 'user' | 'env' | null };
openai: { configured: boolean; source: 'user' | 'env' | null };
groq: { configured: boolean; source: 'user' | 'env' | null };
}> {
const status: any = {};
for (const provider of ['anthropic', 'openai', 'groq'] as const) {
// Check user key first
if (userId) {
try {
const client = getConvexClient();
const userKey = await client.query(api.userLLMKeys.getActiveKey, {
userId,
provider,
});
if (userKey) {
status[provider] = { configured: true, source: 'user' };
continue;
}
} catch (error) {
// Continue to env check
}
}
// Check environment variable
const envKeyMap = {
anthropic: 'ANTHROPIC_API_KEY',
openai: 'OPENAI_API_KEY',
groq: 'GROQ_API_KEY',
};
const envKey = envKeyMap[provider];
if (process.env[envKey]) {
status[provider] = { configured: true, source: 'env' };
} else {
status[provider] = { configured: false, source: null };
}
}
return status;
}