-
Notifications
You must be signed in to change notification settings - Fork 461
feat: add Anthropic API base URL configuration support #382
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,15 +5,17 @@ | |
| import type { Request, Response } from 'express'; | ||
| import { setApiKey, persistApiKeyToEnv, getErrorMessage, logError } from '../common.js'; | ||
| import { createLogger } from '@automaker/utils'; | ||
| import type { SettingsService } from '../../../services/settings-service.js'; | ||
|
|
||
| const logger = createLogger('Setup'); | ||
|
|
||
| export function createStoreApiKeyHandler() { | ||
| export function createStoreApiKeyHandler(settingsService: SettingsService) { | ||
| return async (req: Request, res: Response): Promise<void> => { | ||
| try { | ||
| const { provider, apiKey } = req.body as { | ||
| const { provider, apiKey, baseUrl } = req.body as { | ||
| provider: string; | ||
| apiKey: string; | ||
| baseUrl?: string; | ||
| }; | ||
|
|
||
| if (!provider || !apiKey) { | ||
|
|
@@ -29,6 +31,28 @@ export function createStoreApiKeyHandler() { | |
| process.env.ANTHROPIC_API_KEY = apiKey; | ||
| await persistApiKeyToEnv('ANTHROPIC_API_KEY', apiKey); | ||
| logger.info('[Setup] Stored API key as ANTHROPIC_API_KEY'); | ||
|
|
||
| // Handle custom base URL if provided | ||
| if (baseUrl && baseUrl.trim()) { | ||
| process.env.ANTHROPIC_BASE_URL = baseUrl.trim(); | ||
| await persistApiKeyToEnv('ANTHROPIC_BASE_URL', baseUrl.trim()); | ||
| logger.info('[Setup] Stored custom base URL as ANTHROPIC_BASE_URL'); | ||
| } else if (process.env.ANTHROPIC_BASE_URL) { | ||
| // Clear existing base URL if not provided | ||
| delete process.env.ANTHROPIC_BASE_URL; | ||
| // Note: we don't remove from .env file as that's more complex | ||
| logger.info('[Setup] Cleared ANTHROPIC_BASE_URL from environment'); | ||
| } | ||
|
|
||
| // Persist base URL to settings service | ||
| if (baseUrl !== undefined) { | ||
| await settingsService.updateCredentials({ | ||
| baseUrls: { | ||
| anthropic: baseUrl.trim() || undefined, | ||
| }, | ||
| }); | ||
| logger.info('[Setup] Persisted base URL to settings service'); | ||
| } | ||
|
Comment on lines
+35
to
+55
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: # First, find and examine the file mentioned in the review
find . -type f -name "store-api-key.ts" -path "*/routes/setup/*"Repository: AutoMaker-Org/automaker Length of output: 121 🏁 Script executed: # Check the actual implementation of the code in question
cat -n apps/server/src/routes/setup/routes/store-api-key.ts | head -100Repository: AutoMaker-Org/automaker Length of output: 3113 🏁 Script executed: # Find persistApiKeyToEnv implementation
rg -n "persistApiKeyToEnv" -A 10 -B 2Repository: AutoMaker-Org/automaker Length of output: 4326 🏁 Script executed: # Check how environment variables are loaded at startup
rg -n "ANTHROPIC_BASE_URL" -A 5 -B 5Repository: AutoMaker-Org/automaker Length of output: 3373 🏁 Script executed: # Check secureFs.writeEnvKey implementation to see if it can delete keys
rg -n "writeEnvKey" -A 15 -B 5Repository: AutoMaker-Org/automaker Length of output: 4236 🏁 Script executed: # Check how settings service initializes and uses baseUrl
rg -n "class SettingsService|updateCredentials|getCredentials" apps/server/src/services/settings-service.ts -A 10 -B 2Repository: AutoMaker-Org/automaker Length of output: 3273 🏁 Script executed: # Check how ANTHROPIC_BASE_URL is used when initializing the client
rg -n "ANTHROPIC_BASE_URL" apps/server/src/providers/claude-provider.ts -A 5 -B 5Repository: AutoMaker-Org/automaker Length of output: 369 🏁 Script executed: # Check the initialization order in the main app file
find . -type f -name "main.ts" -o -name "index.ts" -o -name "app.ts" | xargs grep -l "SettingsService\|setupRoutes" 2>/dev/null | head -3Repository: AutoMaker-Org/automaker Length of output: 198 🏁 Script executed: # Check claude-provider to see where ANTHROPIC_BASE_URL is used and how it's initialized
cat -n apps/server/src/providers/claude-provider.ts | head -150Repository: AutoMaker-Org/automaker Length of output: 5968 🏁 Script executed: # Check how settings service baseUrls are loaded and used
rg -n "baseUrl|baseUrls" apps/server/src/providers/claude-provider.ts -A 3 -B 3Repository: AutoMaker-Org/automaker Length of output: 49 🏁 Script executed: # Look at app initialization to understand the order of precedence
rg -n "SettingsService\|setupRoutes\|createClaudeProvider" apps/server/src/index.ts -A 5 -B 5Repository: AutoMaker-Org/automaker Length of output: 49 🏁 Script executed: # Look for where settings service baseUrls are applied to process.env
rg -n "baseUrl|baseUrls" apps/server/src -A 2 -B 2 | head -100Repository: AutoMaker-Org/automaker Length of output: 4110 🏁 Script executed: # Check if there's any initialization code that applies settings service values to process.env
rg -n "updateCredentials|getCredentials" apps/server/src/index.ts -A 10 -B 5Repository: AutoMaker-Org/automaker Length of output: 49 🏁 Script executed: # Check the updateEnvContent function to understand what it does
cat -n libs/platform/src/secure-fs.ts | grep -A 30 "function updateEnvContent"Repository: AutoMaker-Org/automaker Length of output: 1194 🏁 Script executed: # Check if there's any startup code that loads settings service baseUrl into process.env
rg -n "process.env.ANTHROPIC_BASE_URL\s*=" apps/server/src -B 5 -A 5Repository: AutoMaker-Org/automaker Length of output: 1268 🏁 Script executed: # Search for any initialization that reads baseUrls from settings service
rg -n "getCredentials|baseUrl" apps/server/src/index.ts -A 5 -B 5Repository: AutoMaker-Org/automaker Length of output: 49 🏁 Script executed: # Look for any middleware or initialization that syncs settings service to process.env
rg -n "settingsService|baseUrl" apps/server/src/middleware -A 3 -B 3Repository: AutoMaker-Org/automaker Length of output: 49 🏁 Script executed: # Check the main server file for initialization logic
find . -type f -path "*/apps/server/src/index.ts" -o -path "*/apps/server/src/main.ts" | xargs catRepository: AutoMaker-Org/automaker Length of output: 22471 Call When clearing the base URL (lines 40-45), the code removes it from The simplest fix is to persist an empty value to .env when clearing, which the existing This way, the .env file will be updated to an empty value, preventing the old baseUrl from being restored on restart. 🤖 Prompt for AI Agents |
||
| } else { | ||
| res.status(400).json({ | ||
| success: false, | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -10,6 +10,33 @@ import { getApiKey } from '../common.js'; | |
|
|
||
| const logger = createLogger('Setup'); | ||
|
|
||
| // Allowed environment variables to pass to the SDK | ||
| // Must be passed explicitly - SDK doesn't inherit from process.env | ||
| const ALLOWED_ENV_VARS = [ | ||
| 'ANTHROPIC_API_KEY', | ||
| 'ANTHROPIC_BASE_URL', | ||
| 'PATH', | ||
| 'HOME', | ||
| 'SHELL', | ||
| 'TERM', | ||
| 'USER', | ||
| 'LANG', | ||
| 'LC_ALL', | ||
| ]; | ||
|
|
||
| /** | ||
| * Build environment for the SDK with only explicitly allowed variables | ||
| */ | ||
| function buildEnv(): Record<string, string | undefined> { | ||
| const env: Record<string, string | undefined> = {}; | ||
| for (const key of ALLOWED_ENV_VARS) { | ||
| if (process.env[key]) { | ||
| env[key] = process.env[key]; | ||
| } | ||
| } | ||
| return env; | ||
| } | ||
|
|
||
| // Known error patterns that indicate auth failure | ||
| const AUTH_ERROR_PATTERNS = [ | ||
| 'OAuth token revoked', | ||
|
|
@@ -91,13 +118,16 @@ export function createVerifyClaudeAuthHandler() { | |
|
|
||
| // Save original env values | ||
| const originalAnthropicKey = process.env.ANTHROPIC_API_KEY; | ||
| const originalBaseUrl = process.env.ANTHROPIC_BASE_URL; | ||
|
|
||
| try { | ||
| // Configure environment based on auth method | ||
| if (authMethod === 'cli') { | ||
| // For CLI verification, remove any API key so it uses CLI credentials only | ||
| delete process.env.ANTHROPIC_API_KEY; | ||
| logger.info('[Setup] Cleared API key environment for CLI verification'); | ||
| // CLI auth only works with standard Anthropic API, not custom endpoints | ||
| delete process.env.ANTHROPIC_BASE_URL; | ||
| logger.info('[Setup] Cleared API key and base URL for CLI verification'); | ||
| } else if (authMethod === 'api_key') { | ||
| // For API key verification, use provided key, stored key, or env var (in order of priority) | ||
| if (apiKey) { | ||
|
|
@@ -128,6 +158,7 @@ export function createVerifyClaudeAuthHandler() { | |
| maxTurns: 1, | ||
| allowedTools: [], | ||
| abortController, | ||
| env: buildEnv(), | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🌐 Web query:
💡 Result: Short answer: In @anthropic-ai/claude-agent-sdk v0.1.76 the "env" option is a dictionary of environment-variable name → string value that the SDK passes to the child/CLI/MCP processes it launches (used to provide API keys, credentials, feature flags, etc.). Example usage: env: { "ANTHROPIC_API_KEY": "sk-...", "DEBUG": "true" }. You can also combine this with extra_args for CLI flags. [1][2][3] Sources:
🏁 Script executed: # Find and examine the actual file context
cat -n apps/server/src/routes/setup/routes/verify-claude-auth.ts | sed -n '150,175p'Repository: AutoMaker-Org/automaker Length of output: 1098 🏁 Script executed: # Search for buildEnv() function definition
fd -t f "\.ts$" -x grep -l "buildEnv" {} \;Repository: AutoMaker-Org/automaker Length of output: 173 🏁 Script executed: # Check for SDK version specification
cat -n package.json | head -50Repository: AutoMaker-Org/automaker Length of output: 3748 🏁 Script executed: # Find and examine buildEnv() definition
rg "export\s+function\s+buildEnv|function\s+buildEnv|const\s+buildEnv" -A 10Repository: AutoMaker-Org/automaker Length of output: 1788 🏁 Script executed: # Search for query function definition and its parameters
rg "function\s+query|const\s+query|export.*query" -B 2 -A 15 apps/server/src/routes/setup/routes/verify-claude-auth.tsRepository: AutoMaker-Org/automaker Length of output: 49 🏁 Script executed: # Check imports in the file to understand where query and buildEnv come from
head -30 apps/server/src/routes/setup/routes/verify-claude-auth.tsRepository: AutoMaker-Org/automaker Length of output: 881 🏁 Script executed: # Search for query function type definition
rg "query.*options" -A 5 apps/server/src/routes/setup/routes/verify-claude-auth.ts | head -20Repository: AutoMaker-Org/automaker Length of output: 49 🏁 Script executed: # Check if there are TypeScript type definitions available
find . -name "*.d.ts" -type f | xargs grep -l "query" 2>/dev/null | head -5Repository: AutoMaker-Org/automaker Length of output: 49 🏁 Script executed: # Look at how buildEnv is used - check if undefined values could cause issues
rg "for.*const.*key.*of.*ALLOWED_ENV_VARS" -A 5 apps/server/src/routes/setup/routes/verify-claude-auth.tsRepository: AutoMaker-Org/automaker Length of output: 193 🏁 Script executed: # Check if the env object actually contains any undefined values at runtime
# by looking at how the allowed vars are accessed
cat -n apps/server/src/routes/setup/routes/verify-claude-auth.ts | sed -n '25,60p'Repository: AutoMaker-Org/automaker Length of output: 1198 SDK support confirmed; tighten the return type annotation. The 🤖 Prompt for AI Agents |
||
| }, | ||
| }); | ||
|
|
||
|
|
@@ -285,6 +316,13 @@ export function createVerifyClaudeAuthHandler() { | |
| // If we cleared it and there was no original, keep it cleared | ||
| delete process.env.ANTHROPIC_API_KEY; | ||
| } | ||
| // Restore base URL | ||
| if (originalBaseUrl !== undefined) { | ||
| process.env.ANTHROPIC_BASE_URL = originalBaseUrl; | ||
| } else if (authMethod === 'cli') { | ||
| // If we cleared it and there was no original, keep it cleared | ||
| delete process.env.ANTHROPIC_BASE_URL; | ||
| } | ||
| } | ||
|
|
||
| logger.info('[Setup] Verification result:', { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -55,6 +55,7 @@ export function ClaudeSetupStep({ onNext, onBack, onSkip }: ClaudeSetupStepProps | |
| const { setApiKeys, apiKeys } = useAppStore(); | ||
|
|
||
| const [apiKey, setApiKey] = useState(''); | ||
| const [baseUrl, setBaseUrl] = useState(''); | ||
|
|
||
| // CLI Verification state | ||
| const [cliVerificationStatus, setCliVerificationStatus] = useState<VerificationStatus>('idle'); | ||
|
|
@@ -111,7 +112,7 @@ export function ClaudeSetupStep({ onNext, onBack, onSkip }: ClaudeSetupStepProps | |
| apiKeyValid: true, | ||
| }); | ||
| setApiKeys({ ...apiKeys, anthropic: apiKey }); | ||
| toast.success('API key saved successfully!'); | ||
| toast.success('Anthropic configuration saved successfully!'); | ||
| }, | ||
| }); | ||
|
|
||
|
|
@@ -220,6 +221,7 @@ export function ClaudeSetupStep({ onNext, onBack, onSkip }: ClaudeSetupStepProps | |
| if (result.success) { | ||
| // Clear local state | ||
| setApiKey(''); | ||
| setBaseUrl(''); | ||
| setApiKeys({ ...apiKeys, anthropic: '' }); | ||
| setApiKeyVerificationStatus('idle'); | ||
| setApiKeyVerificationError(null); | ||
|
|
@@ -228,12 +230,13 @@ export function ClaudeSetupStep({ onNext, onBack, onSkip }: ClaudeSetupStepProps | |
| method: 'none', | ||
| hasCredentialsFile: claudeAuthStatus?.hasCredentialsFile || false, | ||
| }); | ||
| toast.success('API key deleted successfully'); | ||
| toast.success('Anthropic configuration deleted successfully'); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The toast message now correctly refers to 'Anthropic configuration'. However, the backend |
||
| } else { | ||
| toast.error(result.error || 'Failed to delete API key'); | ||
| toast.error(result.error || 'Failed to delete configuration'); | ||
| } | ||
| } catch (error) { | ||
| const errorMessage = error instanceof Error ? error.message : 'Failed to delete API key'; | ||
| const errorMessage = | ||
| error instanceof Error ? error.message : 'Failed to delete configuration'; | ||
| toast.error(errorMessage); | ||
| } finally { | ||
| setIsDeletingApiKey(false); | ||
|
|
@@ -531,7 +534,7 @@ export function ClaudeSetupStep({ onNext, onBack, onSkip }: ClaudeSetupStepProps | |
| </div> | ||
| </AccordionTrigger> | ||
| <AccordionContent className="pt-4 space-y-4"> | ||
| {/* API Key Input */} | ||
| {/* API Key Configuration */} | ||
| <div className="space-y-4 p-4 rounded-lg bg-muted/30 border border-border"> | ||
| <div className="space-y-2"> | ||
| <Label htmlFor="anthropic-key" className="text-foreground"> | ||
|
|
@@ -560,9 +563,28 @@ export function ClaudeSetupStep({ onNext, onBack, onSkip }: ClaudeSetupStepProps | |
| </p> | ||
| </div> | ||
|
|
||
| <div className="space-y-2"> | ||
| <Label htmlFor="anthropic-base-url" className="text-foreground"> | ||
| API Base URL | ||
| </Label> | ||
| <Input | ||
| id="anthropic-base-url" | ||
| type="url" | ||
| placeholder="https://api.anthropic.com (leave empty for default)" | ||
| value={baseUrl} | ||
| onChange={(e) => setBaseUrl(e.target.value)} | ||
| className="bg-input border-border text-foreground" | ||
| data-testid="anthropic-base-url-input" | ||
| /> | ||
| <p className="text-xs text-muted-foreground"> | ||
| Optional: Set a custom API base URL for enterprise deployments or custom | ||
| endpoints | ||
| </p> | ||
| </div> | ||
|
|
||
| <div className="flex gap-2"> | ||
| <Button | ||
| onClick={() => saveApiKeyToken(apiKey)} | ||
| onClick={() => saveApiKeyToken(apiKey, baseUrl)} | ||
| disabled={isSavingApiKey || !apiKey.trim()} | ||
| className="flex-1 bg-brand-500 hover:bg-brand-600 text-white" | ||
| data-testid="save-anthropic-key-button" | ||
|
|
@@ -573,7 +595,7 @@ export function ClaudeSetupStep({ onNext, onBack, onSkip }: ClaudeSetupStepProps | |
| Saving... | ||
| </> | ||
| ) : ( | ||
| 'Save API Key' | ||
| 'Save Configuration' | ||
| )} | ||
| </Button> | ||
| {hasApiKey && ( | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Clearing the
ANTHROPIC_BASE_URLfromprocess.envis not sufficient as it won't persist across server restarts. The value will be reloaded from the.envfile, causing the old base URL to be used again. This is likely not the intended behavior.A function
removeApiKeyFromEnvalready exists inapps/server/src/routes/setup/routes/delete-api-key.tswhich can remove a key from the.envfile. It would be best to move this function tocommon.tsand use it here to properly remove the key from the.envfile.Here's how you could modify this block after making
removeApiKeyFromEnvavailable:This ensures that clearing the base URL is a persistent change.