4.1 KiB
Fetch Models Design
Date: 2026-03-27 Status: Approved
Summary
Add an "Aktualisieren" button to the Model setting in the settings tab. When clicked, it fetches the 3 newest Claude models from the Anthropic Models API and updates the dropdown. Falls back to the hardcoded MODELS list if the request fails or returns no models.
ClaudeClient changes
Add a new method fetchModels(apiKey: string): Promise<{id: string, name: string}[]> to ClaudeClient.
- URL:
"https://api.anthropic.com/v1/models"— inline string or a separate private constant; do not use or modifybaseUrl(which points to/v1/messages) - Call
requestUrlwiththrow: false(same pattern asstreamChat/chat) andthis.headers(apiKey) - Response shape:
{ data: [{ id: string, created: number, display_name: string, ... }] } - Throw on
response.status >= 400with the response text - If
datais empty, throw an error ("No models returned") — do not return an empty array - Sort
datadescending bycreated, take top 3 - Return
{ id, name: id }for each — useidas the display name (notdisplay_name). Note: fetched entries will show raw IDs (e.g.claude-opus-4-6) while hardcodedMODELSshow human-friendly names (e.g."Claude Opus 4.6 (Stärkste)"). This is intentional — keeps the implementation simple and avoids relying on API-provided display strings.
SettingsTab changes
Import addition: Add Notice, ButtonComponent, DropdownComponent to the import { ... } from "obsidian" line.
Convert the existing "Modell" Setting to capture both the DropdownComponent and ButtonComponent references by chaining addDropdown() and addButton() on the same Setting instance:
let modelDrop: DropdownComponent;
let refreshBtn: ButtonComponent;
new Setting(containerEl)
.setName("Modell")
.setDesc("Welches Claude-Modell verwenden?")
.addDropdown((drop) => {
modelDrop = drop;
for (const m of MODELS) drop.addOption(m.id, m.name);
drop.setValue(this.plugin.settings.model).onChange(async (value) => {
this.plugin.settings.model = value;
await this.plugin.saveSettings();
});
})
.addButton((btn) => {
refreshBtn = btn;
btn.setButtonText("Aktualisieren").onClick(async () => { /* see click flow */ });
});
Click flow:
- Capture current value:
const prev = modelDrop.getValue() refreshBtn.setDisabled(true)andrefreshBtn.setButtonText("...")- In a try/catch/finally:
- try: Call
this.plugin.claude.fetchModels(this.plugin.settings.apiKey) - On success: clear dropdown with
modelDrop.selectEl.empty(), repopulate viamodelDrop.addOption(id, name)for each fetched model, then set value toprevif it exists among the fetched ids, otherwise the first fetched id; save viathis.plugin.settings.model = modelDrop.getValue(); await this.plugin.saveSettings() - catch:
new Notice("Modelle konnten nicht geladen werden: " + err.message)— dropdown is not modified on error (hardcoded options remain) - finally:
refreshBtn.setDisabled(false)andrefreshBtn.setButtonText("Aktualisieren")
- try: Call
Fallback: The hardcoded MODELS array in SettingsTab.ts is unchanged and remains the initial population of the dropdown on every settings open.
Data flow
[Aktualisieren button click]
→ capture prev = modelDrop.getValue()
→ disable button, show "..."
→ this.plugin.claude.fetchModels(apiKey) [throw: false, separate URL]
→ throw if status >= 400 or data empty
→ sort by created desc, take 3
→ return [{id, name: id}]
→ clear selectEl, repopulate, restore selection
→ save model to settings
→ finally: restore button
Error handling
| Scenario | Behaviour |
|---|---|
| No API key (401) | Notice shown; dropdown unchanged |
| Network failure | Notice shown; dropdown unchanged |
Empty data array |
Treated as error; Notice shown; dropdown unchanged |
| Fewer than 3 models returned | Take all returned (no error) |
Out of scope
- Persisting fetched models across restarts
- Auto-fetching on settings open or plugin startup
- Configurable count of models to show
- Updating
DEFAULT_SETTINGS.modelafter a fetch