feat: Anthropic SDK backend — API key alternative to Claude CLI OAuth
Adds `anthropic_api` model type so users can authenticate with a direct
Anthropic API key instead of (or alongside) the CLI OAuth session.
- model_registry.py: `anthropic_api` type; `save/get/remove_anthropic_api_key()`
mirroring the Google account pattern; `save_cloud_model()` now picks type
based on credential type (cli → claude_cli, api_key → anthropic_api);
`_resolve_model()` merges api_key from the credential entry
- llm_client.py: `_anthropic_api()` backend (AsyncAnthropic SDK); dispatch
and fallback wiring; usage tracking
- routers/local_llm.py: Anthropic API key management routes
(POST /settings/local/anthropic-key, /anthropic-key/{id}/remove);
`anthropic_api` badge and edit-form credential selector
- static/local_llm.html: Anthropic Cloud Provider block now shows API key
management (add/remove); Add Model → Anthropic tab has credential selector
(CLI vs API key)
- requirements.txt: enable anthropic>=0.40.0
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -229,7 +229,9 @@ Configure which AI models are available and which handles each task type.
|
||||
|
||||
Do this before adding models — models need a provider account or local host to attach to.
|
||||
|
||||
**Anthropic (Claude):** Nothing to configure. Claude uses your existing CLI OAuth session. If Claude isn't working, run `claude auth login` in a terminal.
|
||||
**Anthropic (Claude):** Two options:
|
||||
- **CLI (OAuth):** Nothing to configure — uses your existing `claude auth login` session. If Claude isn't working, run `claude auth login` in a terminal.
|
||||
- **Direct API key:** Scroll to **Cloud Providers → Anthropic** → click **+ Add API key**. Enter a label and your `sk-ant-…` key from [console.anthropic.com/keys](https://console.anthropic.com/keys). When you add a model using an API key credential, it routes through the Anthropic SDK instead of the CLI.
|
||||
|
||||
**Google (Gemini):** Add one entry per API key you want to use:
|
||||
1. Scroll to **Cloud Providers → Google** → click **+ Add Google account**
|
||||
@@ -258,7 +260,7 @@ Scroll to **Add Model**. Select the provider tab, fill in the details, click **A
|
||||
|---|---|
|
||||
| **Local** | Select a host (from Step 1) → enter model name, or use **Fetch from host** to pick from a live list |
|
||||
| **Google** | Select a Gemini model from the catalog → select a Google account (from Step 1) |
|
||||
| **Anthropic** | Select a Claude model from the catalog → uses your CLI session automatically |
|
||||
| **Anthropic** | Select a credential (CLI OAuth or an API key added in Step 1) → select a Claude model from the catalog |
|
||||
|
||||
The label and context window size auto-fill from the catalog — edit them if you want. Tags are optional.
|
||||
|
||||
|
||||
@@ -319,16 +319,44 @@
|
||||
<div class="provider-icon pi-anthropic">A</div>
|
||||
<div>
|
||||
<div class="provider-title">Anthropic</div>
|
||||
<div class="provider-subtitle">Claude via CLI (OAuth) — no API key needed</div>
|
||||
<div class="provider-subtitle">Claude via CLI (OAuth) or direct API key</div>
|
||||
</div>
|
||||
</div>
|
||||
<p class="section-note" style="margin-bottom:0">
|
||||
Claude models are accessed through the Claude CLI using your existing OAuth login.
|
||||
Run <code style="font-family:monospace;color:#94a3b8">claude auth login</code> to authenticate.
|
||||
</p>
|
||||
<div id="claude-auth-status" class="auth-status" style="margin-top:0.6rem">
|
||||
<span class="dot"></span><span id="claude-auth-msg">Checking…</span>
|
||||
|
||||
<div style="margin-bottom:1rem">
|
||||
<p class="section-note" style="margin-bottom:0.3rem">
|
||||
<strong>CLI (OAuth):</strong> Uses your existing Claude CLI login — no API key needed.
|
||||
Run <code style="font-family:monospace;color:#94a3b8">claude auth login</code> to authenticate.
|
||||
</p>
|
||||
<div id="claude-auth-status" class="auth-status">
|
||||
<span class="dot"></span><span id="claude-auth-msg">Checking…</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="section-note" style="margin-bottom:0.4rem"><strong>API Keys:</strong></p>
|
||||
{{ anthropic_key_rows }}
|
||||
<details style="margin-top:0.5rem">
|
||||
<summary>+ Add API key</summary>
|
||||
<div>
|
||||
<form method="POST" action="/settings/local/anthropic-key">
|
||||
<input type="hidden" name="key_id" value="">
|
||||
<div class="field-row">
|
||||
<div class="field">
|
||||
<label>Label <span style="color:#475569;font-weight:400">(e.g. Personal, Work)</span></label>
|
||||
<input type="text" name="label" placeholder="Personal"
|
||||
autocomplete="off" data-form-type="other">
|
||||
</div>
|
||||
<div class="field" style="flex:2">
|
||||
<label>API Key</label>
|
||||
<input type="password" name="api_key" placeholder="sk-ant-…"
|
||||
autocomplete="new-password" data-1p-ignore data-lpignore="true"
|
||||
data-form-type="other" required>
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-secondary btn-sm">Save</button>
|
||||
</form>
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
|
||||
<div class="provider-block" style="border-top:1px solid #2d3148; padding-top:1.25rem">
|
||||
@@ -474,16 +502,22 @@
|
||||
|
||||
<!-- ANTHROPIC fields -->
|
||||
<div id="pf-anthropic" style="display:none">
|
||||
<div class="field">
|
||||
<label>Credential</label>
|
||||
<select id="add-anthropic-cred">
|
||||
<!-- populated by JS from ANTHROPIC_API_KEYS -->
|
||||
</select>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Claude model</label>
|
||||
<select id="add-claude-model"></select>
|
||||
</div>
|
||||
<p class="section-note" style="margin-top:-0.25rem">Uses Claude CLI (OAuth)</p>
|
||||
</div>
|
||||
|
||||
<!-- Hidden: cloud model name (set by JS from catalog pickers) -->
|
||||
<input type="hidden" id="cloud-model-name" name="cloud_model_name" value="">
|
||||
<input type="hidden" name="credential_id" value="cli">
|
||||
<!-- credential_id is set by JS when Anthropic tab is active -->
|
||||
<input type="hidden" id="add-credential-id" name="credential_id" value="cli">
|
||||
|
||||
<!-- Shared fields -->
|
||||
<div class="field-row" style="margin-top:0.75rem">
|
||||
@@ -559,6 +593,7 @@
|
||||
const ROLE_CONFIG_DATA = {{ role_config_data_js }};
|
||||
const TOOL_CATEGORIES = {{ tool_categories_js }};
|
||||
const GOOGLE_ACCOUNTS = {{ google_accounts_js }};
|
||||
const ANTHROPIC_API_KEYS = {{ anthropic_keys_js }};
|
||||
const GOOGLE_CATALOG = {{ google_catalog_js }};
|
||||
const ANTHROPIC_CATALOG = {{ anthropic_catalog_js }};
|
||||
const HAS_HOSTS = {{ has_hosts }};
|
||||
@@ -742,6 +777,10 @@
|
||||
el.style.display = key === p ? '' : 'none';
|
||||
}
|
||||
fetchBtn.style.display = p === 'local' ? '' : 'none';
|
||||
// Sync credential_id when switching to/from Anthropic
|
||||
if (p === 'anthropic') {
|
||||
credIdInput.value = anthropicCredSel.value || 'cli';
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -758,13 +797,27 @@
|
||||
});
|
||||
}
|
||||
|
||||
const geminiSel = document.getElementById('add-gemini-model');
|
||||
const claudeSel = document.getElementById('add-claude-model');
|
||||
const gAcctSel = document.getElementById('add-google-account');
|
||||
const geminiSel = document.getElementById('add-gemini-model');
|
||||
const claudeSel = document.getElementById('add-claude-model');
|
||||
const gAcctSel = document.getElementById('add-google-account');
|
||||
const anthropicCredSel = document.getElementById('add-anthropic-cred');
|
||||
const credIdInput = document.getElementById('add-credential-id');
|
||||
|
||||
populateSelect(geminiSel, GOOGLE_CATALOG, 'id', 'label');
|
||||
populateSelect(claudeSel, ANTHROPIC_CATALOG, 'id', 'label');
|
||||
|
||||
// Populate Anthropic credential selector (CLI + any configured API keys)
|
||||
anthropicCredSel.innerHTML = '<option value="cli">Claude CLI (OAuth)</option>';
|
||||
ANTHROPIC_API_KEYS.forEach(k => {
|
||||
const opt = document.createElement('option');
|
||||
opt.value = k.id;
|
||||
opt.textContent = (k.label || 'API Key') + (k.hint ? ` (${k.hint})` : '');
|
||||
anthropicCredSel.appendChild(opt);
|
||||
});
|
||||
anthropicCredSel.addEventListener('change', () => {
|
||||
credIdInput.value = anthropicCredSel.value || 'cli';
|
||||
});
|
||||
|
||||
if (GOOGLE_ACCOUNTS.length) {
|
||||
gAcctSel.innerHTML = '<option value="">— select account —</option>';
|
||||
GOOGLE_ACCOUNTS.forEach(a => {
|
||||
|
||||
Reference in New Issue
Block a user