diff --git a/app/api/api-keys/check/route.ts b/app/api/api-keys/check/route.ts index 32db9cbf..92ad89a2 100644 --- a/app/api/api-keys/check/route.ts +++ b/app/api/api-keys/check/route.ts @@ -5,7 +5,7 @@ type Provider = 'openai' | 'gemini' | 'cursor' | 'anthropic' | 'aigateway' // Map agents to their required providers const AGENT_PROVIDER_MAP: Record = { - claude: 'anthropic', + claude: 'aigateway', // Claude uses Vercel AI Gateway codex: 'aigateway', // Codex uses Vercel AI Gateway copilot: null, // Copilot uses user's GitHub token from their account cursor: 'cursor', diff --git a/components/api-keys-dialog.tsx b/components/api-keys-dialog.tsx index e616f0b1..44933f0d 100644 --- a/components/api-keys-dialog.tsx +++ b/components/api-keys-dialog.tsx @@ -137,10 +137,37 @@ export function ApiKeysDialog({ open, onOpenChange }: ApiKeysDialogProps) { } } - const handleClear = (provider: Provider) => { - // Mark as cleared locally, no DB changes - setClearedKeys((prev) => new Set(prev).add(provider)) - setApiKeys((prev) => ({ ...prev, [provider]: '' })) + const handleClear = async (provider: Provider) => { + // Delete the key from the database + setLoading(true) + try { + const response = await fetch(`/api/api-keys?provider=${provider}`, { + method: 'DELETE', + }) + + if (response.ok) { + toast.success(`${PROVIDERS.find((p) => p.id === provider)?.name} API key cleared`) + setSavedKeys((prev) => { + const newSet = new Set(prev) + newSet.delete(provider) + return newSet + }) + setClearedKeys((prev) => { + const newSet = new Set(prev) + newSet.delete(provider) + return newSet + }) + setApiKeys((prev) => ({ ...prev, [provider]: '' })) + } else { + const error = await response.json() + toast.error(error.error || 'Failed to clear API key') + } + } catch (error) { + console.error('Error clearing API key:', error) + toast.error('Failed to clear API key') + } finally { + setLoading(false) + } } const toggleShowKey = (provider: Provider) => { diff --git a/lib/sandbox/agents/claude.ts b/lib/sandbox/agents/claude.ts index 41a24ca1..b1a815d2 100644 --- a/lib/sandbox/agents/claude.ts +++ b/lib/sandbox/agents/claude.ts @@ -83,9 +83,12 @@ export async function installClaudeCLI( if (claudeInstall.success) { await logger.info('Claude CLI installed successfully') - // Authenticate Claude CLI with API key - if (process.env.ANTHROPIC_API_KEY) { - await logger.info('Authenticating Claude CLI...') + // Authenticate Claude CLI with API key (using AI Gateway) + const apiKey = process.env.AI_GATEWAY_API_KEY + const baseUrl = 'https://ai-gateway.vercel.sh' + + if (apiKey) { + await logger.info('Authenticating Claude CLI with AI Gateway...') // Create Claude config directory (use $HOME instead of ~) await runCommandInSandbox(sandbox, 'mkdir', ['-p', '$HOME/.config/claude']) @@ -101,7 +104,7 @@ export async function installClaudeCLI( if (server.type === 'local') { // Local STDIO server - command string contains both executable and args - const envPrefix = `ANTHROPIC_API_KEY="${process.env.ANTHROPIC_API_KEY}"` + const envPrefix = `ANTHROPIC_API_KEY="${apiKey}" ANTHROPIC_BASE_URL="${baseUrl}"` let addMcpCmd = `${envPrefix} claude mcp add "${serverName}" -- ${server.command}` // Add env vars if provided @@ -122,7 +125,7 @@ export async function installClaudeCLI( } } else { // Remote HTTP/SSE server - const envPrefix = `ANTHROPIC_API_KEY="${process.env.ANTHROPIC_API_KEY}"` + const envPrefix = `ANTHROPIC_API_KEY="${apiKey}" ANTHROPIC_BASE_URL="${baseUrl}"` let addMcpCmd = `${envPrefix} claude mcp add --transport http "${serverName}" "${server.baseUrl}"` if (server.oauthClientSecret) { @@ -148,7 +151,8 @@ export async function installClaudeCLI( const modelToUse = selectedModel || 'claude-sonnet-4-5' const configFileCmd = `mkdir -p $HOME/.config/claude && cat > $HOME/.config/claude/config.json << 'EOF' { - "api_key": "${process.env.ANTHROPIC_API_KEY}", + "api_key": "${apiKey}", + "api_base_url": "${baseUrl}", "default_model": "${modelToUse}" } EOF` @@ -163,7 +167,7 @@ EOF` // Verify authentication const verifyAuth = await runCommandInSandbox(sandbox, 'sh', [ '-c', - `ANTHROPIC_API_KEY=${process.env.ANTHROPIC_API_KEY} claude --version`, + `ANTHROPIC_API_KEY=${apiKey} ANTHROPIC_BASE_URL=${baseUrl} claude --version`, ]) if (verifyAuth.success) { await logger.info('Claude CLI authentication verified') @@ -171,7 +175,7 @@ EOF` await logger.info('Warning: Claude CLI authentication could not be verified') } } else { - await logger.info('Warning: ANTHROPIC_API_KEY not found, Claude CLI may not work') + await logger.info('Warning: AI_GATEWAY_API_KEY not found, Claude CLI may not work') } return { success: true } @@ -233,11 +237,11 @@ export async function executeClaudeInSandbox( } } - // Check if ANTHROPIC_API_KEY is available - if (!process.env.ANTHROPIC_API_KEY) { + // Check if AI_GATEWAY_API_KEY is available + if (!process.env.AI_GATEWAY_API_KEY) { return { success: false, - error: 'ANTHROPIC_API_KEY environment variable is required but not found', + error: 'AI_GATEWAY_API_KEY environment variable is required but not found', cliName: 'claude', changesDetected: false, } @@ -246,13 +250,13 @@ export async function executeClaudeInSandbox( // Log what we're trying to do const modelToUse = selectedModel || 'claude-sonnet-4-5' if (logger) { - await logger.info( - `Attempting to execute Claude CLI with model ${modelToUse} and instruction: ${instruction.substring(0, 100)}...`, - ) + await logger.info('Attempting to execute Claude CLI...') } // Check MCP configuration status - const envPrefix = `ANTHROPIC_API_KEY="${process.env.ANTHROPIC_API_KEY}"` + const aiGatewayKey = process.env.AI_GATEWAY_API_KEY! + const aiGatewayBaseUrl = 'https://ai-gateway.vercel.sh' + const envPrefix = `ANTHROPIC_API_KEY="${aiGatewayKey}" ANTHROPIC_BASE_URL="${aiGatewayBaseUrl}"` const mcpList = await runCommandInSandbox(sandbox, 'sh', ['-c', `${envPrefix} claude mcp list`]) await logger.info('MCP servers list retrieved') if (mcpList.error) { @@ -295,7 +299,7 @@ export async function executeClaudeInSandbox( } // Log the command we're about to execute (with redacted API key) - const redactedCommand = fullCommand.replace(process.env.ANTHROPIC_API_KEY!, '[REDACTED]') + const redactedCommand = fullCommand.replace(aiGatewayKey, '[REDACTED]') await logger.command(redactedCommand) // Set up streaming output capture if we have an agent message @@ -363,8 +367,6 @@ export async function executeClaudeInSandbox( const pattern = input.pattern || input.regex || input.search || 'pattern' statusMsg = `Grep: ${pattern}` } else { - // For debugging, log the tool name and input to console - console.log('Unknown Claude tool:', toolName, 'Input:', JSON.stringify(input)) // Skip logging generic tool uses to reduce noise statusMsg = '' } @@ -387,12 +389,8 @@ export async function executeClaudeInSandbox( // Extract session ID and mark as completed from result chunks else if (parsed.type === 'result') { - console.log('Result chunk received:', JSON.stringify(parsed).substring(0, 300)) if (parsed.session_id) { extractedSessionId = parsed.session_id - console.log('Extracted session ID:', extractedSessionId) - } else { - console.log('No session_id in result chunk') } isCompleted = true } @@ -447,8 +445,6 @@ export async function executeClaudeInSandbox( await runAndLogCommand(sandbox, 'ls', ['-la'], logger) } - console.log('Claude execution completed, returning sessionId:', extractedSessionId) - return { success: true, output: `Claude CLI executed successfully${hasChanges ? ' (Changes detected)' : ' (No changes made)'}`, diff --git a/lib/sandbox/config.ts b/lib/sandbox/config.ts index dadaf47d..c2ffcf50 100644 --- a/lib/sandbox/config.ts +++ b/lib/sandbox/config.ts @@ -12,8 +12,8 @@ export function validateEnvironmentVariables( const errors: string[] = [] // Check for required environment variables based on selected agent - if (selectedAgent === 'claude' && !apiKeys?.ANTHROPIC_API_KEY && !process.env.ANTHROPIC_API_KEY) { - errors.push('ANTHROPIC_API_KEY is required for Claude CLI. Please add your API key in your profile.') + if (selectedAgent === 'claude' && !apiKeys?.AI_GATEWAY_API_KEY && !process.env.AI_GATEWAY_API_KEY) { + errors.push('AI_GATEWAY_API_KEY is required for Claude CLI. Please add your API key in your profile.') } if (selectedAgent === 'cursor' && !apiKeys?.CURSOR_API_KEY && !process.env.CURSOR_API_KEY) {