fix(models): Modell-Liste kommt jetzt von Anthropic — und feste Versionen greifen wirklich

Zwei Fehler. Der zweite war der schlimmere.

1) Die Liste war handgepflegt und damit garantiert veraltet.
   Sie kannte "Opus (aktuell: Opus 5)" — waehrend es laengst Opus 5.5,
   Sonnet 5.5 und Fable 5.1 gab. Der Knopf "↻ Aktualisieren" las nur eine
   lokale Datei neu und aktualisierte damit gar nichts.

   Jetzt kommt die Liste aus Anthropics eigenem Modell-Katalog
   (downloads.claude.ai/model-catalog/v1/catalog.json, Surface 'cc') —
   dieselbe Quelle, aus der sich Claude Code selbst bedient. Oeffentlich,
   ohne API-Key (die Models-API braucht einen; mit dem Max-Abo gibt es keinen).
   6h-Cache plus Datei-Cache, der auch offline und bei abgelaufenem TTL
   einspringt: eine etwas alte echte Liste ist besser als keine.

   Die Tier-Aliase (opus/sonnet/haiku/fable) bleiben vorne und bleiben die
   Empfehlung — sie folgen automatisch der neuesten Version. Darunter die
   exakten Versionen, aeltere ausdruecklich als solche markiert.

2) Eine feste Version liess sich faktisch nicht waehlen.
   extractModel() hatte eine MODEL_MAP und fiel fuer alles Unbekannte STILL
   auf "opus" zurueck. Wer "claude-opus-5-5" setzte, bekam ohne jede Meldung
   das Tier-Default. Schlimmer noch: claude-opus-5 und claude-fable-5 standen
   selbst in der Map und wurden auf ihr Tier eingeschmolzen — eine bewusst
   gewaehlte aeltere Version wurde also durch die neueste ersetzt.

   Volle Modellnamen gehen jetzt unveraendert an die CLI. Die versteht sie
   ("--model: an alias for the latest model or a model's full name") und
   meldet sich selbst klar, wenn sie einen Namen nicht kennt — ehrlicher als
   ein stilles Downgrade. Nur echter Muell faellt noch auf "opus", und das
   wird geloggt.

Nachgewiesen gegen den LIVE-Katalog und die echte CLI im Proxy-Container:
20 Pruefungen gruen, u.a. dass alle 12 gelieferten exakten Versionen den Proxy
unveraendert passieren. `claude --print --model claude-opus-5-5` antwortet.
Stefans aktive Einstellung ist der Alias "opus" — am laufenden Betrieb aendert
sich dadurch nichts.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-10-03 10:47:34 +02:00
co-authored by Claude Opus 5
parent 4c0d8ce3fe
commit 09d06011d5
4 changed files with 218 additions and 29 deletions
+50 -9
View File
@@ -611,19 +611,27 @@
<code>/shared/config/runtime.json</code> als <code>brainModel</code> persistiert.
Bei Aenderung: <strong>aria-brain restarten</strong> (Reparatur-Section oben), damit's greift.
<br><br>
ARIA laeuft ueber dein Claude-Max-Abo (CLI) — waehlbar ist der <strong>Tier</strong>
(Opus/Sonnet/Haiku), nicht eine feste Version. Die CLI nimmt automatisch das
jeweils aktuelle Modell des Tiers.
ARIA laeuft ueber dein Claude-Max-Abo (CLI). Du hast zwei Möglichkeiten:
<br>
<strong>Automatisch</strong> (Opus/Sonnet/Haiku/Fable) — folgt immer der neuesten
Version dieses Tiers, ohne dass du etwas pflegen musst. Für den Alltag die
richtige Wahl.
<br>
<strong>Feste Version</strong> (z.B. „Opus 5.5") — genau dieses Modell, auch wenn
später ein neueres kommt.
<br><br>
Die Auswahlliste kommt aus <code>/shared/config/models.json</code> — dort kannst du
Tiers/Beschreibungen anpassen (kein Neustart noetig, danach „↻ Aktualisieren").
Die Liste kommt aus Anthropics eigenem <strong>Modell-Katalog</strong>
(<code>downloads.claude.ai</code>) — dieselbe Quelle, aus der sich Claude Code
selbst bedient. „↻ Aktualisieren" holt sie frisch; sonst gilt ein 6-Stunden-Cache.
Die automatischen Tiers kommen weiterhin aus
<code>/shared/config/models.json</code>.
</div>
<div style="display:flex;align-items:center;gap:8px;margin-bottom:8px;">
<span style="font-size:12px;color:#8888AA;white-space:nowrap;">Aktives Model:</span>
<select id="setting-model-select" onchange="onModelSelectChange()" style="flex:1;background:#1E1E2E;border:1px solid #333;border-radius:4px;padding:6px 8px;color:#E0E0F0;font-family:inherit;font-size:12px;">
<option value="">(lade Liste…)</option>
</select>
<button class="btn secondary" onclick="loadModelList()" title="Liste vom Proxy neu holen" style="padding:4px 8px;font-size:10px;">↻ Aktualisieren</button>
<button class="btn secondary" onclick="loadModelList(true)" title="Modell-Liste frisch von Anthropic holen (Modell-Katalog)" style="padding:4px 8px;font-size:10px;">↻ Aktualisieren</button>
<button class="btn" onclick="saveModel()" style="padding:4px 8px;font-size:10px;">Setzen</button>
</div>
<div id="model-desc" style="font-size:10px;color:#6a6a88;margin-bottom:6px;min-height:12px;"></div>
@@ -7899,17 +7907,46 @@
// Kuratierte Tier-Liste vom Proxy holen (via Diagnostic-Server) und ins
// Dropdown fuellen. Danach get_model, damit das aktive Model markiert wird.
async function loadModelList() {
// force=true (Knopf „↻ Aktualisieren") holt die Liste frisch von Anthropic
// statt aus dem Proxy-Cache.
async function loadModelList(force) {
const sel = document.getElementById('setting-model-select');
const st = document.getElementById('model-status');
if (!sel) return;
if (force && st) { st.textContent = 'Hole Modell-Liste von Anthropic …'; st.style.color = '#8888AA'; }
try {
const r = await fetch('/api/models-list');
const r = await fetch('/api/models-list' + (force ? '?refresh=1' : ''));
const d = await r.json();
if (!d.ok) throw new Error(d.error || 'Fehler');
_modelListCache = d.models || [];
sel.innerHTML = '';
for (const m of _modelListCache) {
// Nach Herkunft gruppieren, damit die Wahl klar ist:
// Tier — folgt automatisch der neuesten Version (Empfehlung)
// main — die aktuell von Anthropic vorgeschlagenen Versionen
// overflow— aeltere Versionen, nur wenn man sie bewusst will
const GROUPS = [
['tier', 'Automatisch (folgt der neuesten Version)'],
['main', 'Aktuelle Versionen'],
['overflow', 'Ältere Versionen'],
];
for (const [section, label] of GROUPS) {
const inGroup = _modelListCache.filter(m => (m.section || 'tier') === section);
if (!inGroup.length) continue;
const g = document.createElement('optgroup');
g.label = label;
for (const m of inGroup) {
const opt = document.createElement('option');
opt.value = m.id;
opt.textContent = m.displayName || m.id;
g.appendChild(opt);
}
sel.appendChild(g);
}
// Unbekannte Sektionen nicht verschlucken (aeltere Proxy-Version
// liefert gar kein section-Feld -> die landen oben unter 'tier').
const known = new Set(GROUPS.map(g => g[0]));
const rest = _modelListCache.filter(m => !known.has(m.section || 'tier'));
for (const m of rest) {
const opt = document.createElement('option');
opt.value = m.id;
opt.textContent = m.displayName || m.id;
@@ -7920,6 +7957,10 @@
opt.value = ''; opt.textContent = '(keine Modelle vom Proxy)';
sel.appendChild(opt);
}
if (st && d.catalogOk === false) {
st.textContent = 'Anthropic-Katalog nicht erreichbar — nur die automatischen Tiers verfügbar.';
st.style.color = '#FFD60A';
}
onModelSelectChange();
// Aktuelles Model vom Brain holen → markiert die richtige Option
loadModel();
+8 -3
View File
@@ -2204,22 +2204,27 @@ const server = http.createServer((req, res) => {
res.end(JSON.stringify({ ok: false, error: err.message }));
}
return;
} else if (req.url === "/api/models-list" && req.method === "GET") {
} else if (req.url.startsWith("/api/models-list") && req.method === "GET") {
// Kuratierte Model-Liste vom Proxy (/v1/models) — Tier-Auswahl fuers
// Sprachmodell-Dropdown. ARIA laeuft ueber das Max-Abo/CLI, waehlbar ist
// der Tier (opus/sonnet/haiku), keine feste Version.
(async () => {
try {
const r = await fetch(`${PROXY_URL}/v1/models`);
// ?refresh=1 weiterreichen: der Proxy umgeht damit seinen Katalog-Cache
// und holt die Liste frisch von Anthropic („↻ Aktualisieren").
const refresh = /[?&]refresh=1\b/.test(req.url) ? "?refresh=1" : "";
const r = await fetch(`${PROXY_URL}/v1/models${refresh}`);
const d = await r.json();
const models = (d.data || []).map(m => ({
id: m.id,
tier: m.tier || m.id,
displayName: m.display_name || m.id,
description: m.description || "",
// section: tier | main | overflow — die UI gruppiert danach.
section: m.section || "tier",
}));
res.writeHead(200, { "Content-Type": "application/json" });
res.end(JSON.stringify({ ok: true, models }));
res.end(JSON.stringify({ ok: true, models, catalogOk: d.catalog_ok !== false }));
} catch (err) {
res.writeHead(502, { "Content-Type": "application/json" });
res.end(JSON.stringify({ ok: false, error: String(err && err.message || err) }));