mirror of
https://github.com/leonvanzyl/autocoder.git
synced 2026-03-16 18:33:08 +00:00
feat: add Azure Anthropic (Claude) provider support
- Add "Azure Anthropic (Claude)" to API_PROVIDERS in registry.py with ANTHROPIC_API_KEY auth (required for Claude CLI to route through custom base URL instead of default Anthropic endpoint) - Add Azure env var template to .env.example - Show Base URL input field for Azure provider in Settings UI with "Configured" state and Azure-specific placeholder - Widen Settings modal for better readability with long URLs - Add Azure endpoint detection and "Azure Mode" log label - Rename misleading "GLM Mode" fallback label to "Alternative API" Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -30,11 +30,18 @@
|
||||
# ANTHROPIC_DEFAULT_HAIKU_MODEL=claude-3-5-haiku@20241022
|
||||
|
||||
# ===================
|
||||
# Alternative API Providers (GLM, Ollama, Kimi, Custom)
|
||||
# Alternative API Providers (Azure, GLM, Ollama, Kimi, Custom)
|
||||
# ===================
|
||||
# Configure via Settings UI (recommended) or set env vars below.
|
||||
# When both are set, env vars take precedence.
|
||||
#
|
||||
# Azure Anthropic (Claude):
|
||||
# ANTHROPIC_BASE_URL=https://your-resource.services.ai.azure.com/anthropic
|
||||
# ANTHROPIC_API_KEY=your-azure-api-key
|
||||
# ANTHROPIC_DEFAULT_OPUS_MODEL=claude-opus-4-6
|
||||
# ANTHROPIC_DEFAULT_SONNET_MODEL=claude-sonnet-4-5
|
||||
# ANTHROPIC_DEFAULT_HAIKU_MODEL=claude-haiku-4-5
|
||||
#
|
||||
# GLM (Zhipu AI):
|
||||
# ANTHROPIC_BASE_URL=https://api.z.ai/api/anthropic
|
||||
# ANTHROPIC_AUTH_TOKEN=your-glm-api-key
|
||||
|
||||
@@ -463,6 +463,7 @@ def create_client(
|
||||
is_vertex = sdk_env.get("CLAUDE_CODE_USE_VERTEX") == "1"
|
||||
is_alternative_api = bool(base_url) or is_vertex
|
||||
is_ollama = "localhost:11434" in base_url or "127.0.0.1:11434" in base_url
|
||||
is_azure = "services.ai.azure.com" in base_url
|
||||
model = convert_model_for_vertex(model)
|
||||
if sdk_env:
|
||||
print(f" - API overrides: {', '.join(sdk_env.keys())}")
|
||||
@@ -472,8 +473,10 @@ def create_client(
|
||||
print(f" - Vertex AI Mode: Using GCP project '{project_id}' with model '{model}' in region '{region}'")
|
||||
elif is_ollama:
|
||||
print(" - Ollama Mode: Using local models")
|
||||
elif is_azure:
|
||||
print(f" - Azure Mode: Using {base_url}")
|
||||
elif "ANTHROPIC_BASE_URL" in sdk_env:
|
||||
print(f" - GLM Mode: Using {sdk_env['ANTHROPIC_BASE_URL']}")
|
||||
print(f" - Alternative API: Using {sdk_env['ANTHROPIC_BASE_URL']}")
|
||||
|
||||
# Create a wrapper for bash_security_hook that passes project_dir via context
|
||||
async def bash_hook_with_context(input_data, tool_use_id=None, context=None):
|
||||
|
||||
12
registry.py
12
registry.py
@@ -676,6 +676,18 @@ API_PROVIDERS: dict[str, dict[str, Any]] = {
|
||||
],
|
||||
"default_model": "glm-4.7",
|
||||
},
|
||||
"azure": {
|
||||
"name": "Azure Anthropic (Claude)",
|
||||
"base_url": "",
|
||||
"requires_auth": True,
|
||||
"auth_env_var": "ANTHROPIC_API_KEY",
|
||||
"models": [
|
||||
{"id": "claude-opus-4-6", "name": "Claude Opus"},
|
||||
{"id": "claude-sonnet-4-5", "name": "Claude Sonnet"},
|
||||
{"id": "claude-haiku-4-5", "name": "Claude Haiku"},
|
||||
],
|
||||
"default_model": "claude-opus-4-6",
|
||||
},
|
||||
"ollama": {
|
||||
"name": "Ollama (Local)",
|
||||
"base_url": "http://localhost:11434",
|
||||
|
||||
@@ -83,8 +83,10 @@ export function SettingsModal({ isOpen, onClose }: SettingsModalProps) {
|
||||
}
|
||||
|
||||
const handleSaveCustomBaseUrl = () => {
|
||||
if (customBaseUrlInput.trim() && !updateSettings.isPending) {
|
||||
updateSettings.mutate({ api_base_url: customBaseUrlInput.trim() })
|
||||
const effectiveBaseUrl = customBaseUrlInput || settings?.api_base_url || ''
|
||||
if (effectiveBaseUrl.trim() && !updateSettings.isPending) {
|
||||
updateSettings.mutate({ api_base_url: effectiveBaseUrl.trim() })
|
||||
setCustomBaseUrlInput('')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,12 +104,12 @@ export function SettingsModal({ isOpen, onClose }: SettingsModalProps) {
|
||||
const currentProviderInfo: ProviderInfo | undefined = providers.find(p => p.id === currentProvider)
|
||||
const isAlternativeProvider = currentProvider !== 'claude'
|
||||
const showAuthField = isAlternativeProvider && currentProviderInfo?.requires_auth
|
||||
const showBaseUrlField = currentProvider === 'custom'
|
||||
const showBaseUrlField = currentProvider === 'custom' || currentProvider === 'azure'
|
||||
const showCustomModelInput = currentProvider === 'custom' || currentProvider === 'ollama'
|
||||
|
||||
return (
|
||||
<Dialog open={isOpen} onOpenChange={(open) => !open && onClose()}>
|
||||
<DialogContent aria-describedby={undefined} className="sm:max-w-sm max-h-[85vh] overflow-y-auto">
|
||||
<DialogContent aria-describedby={undefined} className="sm:max-w-lg max-h-[90vh] overflow-y-auto">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-2">
|
||||
Settings
|
||||
@@ -289,22 +291,38 @@ export function SettingsModal({ isOpen, onClose }: SettingsModalProps) {
|
||||
{showBaseUrlField && (
|
||||
<div className="space-y-2 pt-1">
|
||||
<Label className="text-sm">Base URL</Label>
|
||||
<div className="flex gap-2">
|
||||
<input
|
||||
type="text"
|
||||
value={customBaseUrlInput || settings.api_base_url || ''}
|
||||
onChange={(e) => setCustomBaseUrlInput(e.target.value)}
|
||||
placeholder="https://api.example.com/v1"
|
||||
className="flex-1 py-1.5 px-3 text-sm border rounded-md bg-background"
|
||||
/>
|
||||
<Button
|
||||
size="sm"
|
||||
onClick={handleSaveCustomBaseUrl}
|
||||
disabled={!customBaseUrlInput.trim() || isSaving}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
</div>
|
||||
{settings.api_base_url && !customBaseUrlInput && (
|
||||
<div className="flex items-center gap-2 text-sm text-muted-foreground">
|
||||
<ShieldCheck size={14} className="text-green-500" />
|
||||
<span className="truncate">{settings.api_base_url}</span>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="h-auto py-0.5 px-2 text-xs shrink-0"
|
||||
onClick={() => setCustomBaseUrlInput(settings.api_base_url || ' ')}
|
||||
>
|
||||
Change
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
{(!settings.api_base_url || customBaseUrlInput) && (
|
||||
<div className="flex gap-2">
|
||||
<input
|
||||
type="text"
|
||||
value={customBaseUrlInput.trim()}
|
||||
onChange={(e) => setCustomBaseUrlInput(e.target.value)}
|
||||
placeholder={currentProvider === 'azure' ? 'https://your-resource.services.ai.azure.com/anthropic' : 'https://api.example.com/v1'}
|
||||
className="flex-1 py-1.5 px-3 text-sm border rounded-md bg-background"
|
||||
/>
|
||||
<Button
|
||||
size="sm"
|
||||
onClick={handleSaveCustomBaseUrl}
|
||||
disabled={!customBaseUrlInput.trim() || isSaving}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user