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:
+50
-9
@@ -611,19 +611,27 @@
|
|||||||
<code>/shared/config/runtime.json</code> als <code>brainModel</code> persistiert.
|
<code>/shared/config/runtime.json</code> als <code>brainModel</code> persistiert.
|
||||||
Bei Aenderung: <strong>aria-brain restarten</strong> (Reparatur-Section oben), damit's greift.
|
Bei Aenderung: <strong>aria-brain restarten</strong> (Reparatur-Section oben), damit's greift.
|
||||||
<br><br>
|
<br><br>
|
||||||
ARIA laeuft ueber dein Claude-Max-Abo (CLI) — waehlbar ist der <strong>Tier</strong>
|
ARIA laeuft ueber dein Claude-Max-Abo (CLI). Du hast zwei Möglichkeiten:
|
||||||
(Opus/Sonnet/Haiku), nicht eine feste Version. Die CLI nimmt automatisch das
|
<br>
|
||||||
jeweils aktuelle Modell des Tiers.
|
<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>
|
<br><br>
|
||||||
Die Auswahlliste kommt aus <code>/shared/config/models.json</code> — dort kannst du
|
Die Liste kommt aus Anthropics eigenem <strong>Modell-Katalog</strong>
|
||||||
Tiers/Beschreibungen anpassen (kein Neustart noetig, danach „↻ Aktualisieren").
|
(<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>
|
||||||
<div style="display:flex;align-items:center;gap:8px;margin-bottom:8px;">
|
<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>
|
<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;">
|
<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>
|
<option value="">(lade Liste…)</option>
|
||||||
</select>
|
</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>
|
<button class="btn" onclick="saveModel()" style="padding:4px 8px;font-size:10px;">Setzen</button>
|
||||||
</div>
|
</div>
|
||||||
<div id="model-desc" style="font-size:10px;color:#6a6a88;margin-bottom:6px;min-height:12px;"></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
|
// Kuratierte Tier-Liste vom Proxy holen (via Diagnostic-Server) und ins
|
||||||
// Dropdown fuellen. Danach get_model, damit das aktive Model markiert wird.
|
// 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 sel = document.getElementById('setting-model-select');
|
||||||
const st = document.getElementById('model-status');
|
const st = document.getElementById('model-status');
|
||||||
if (!sel) return;
|
if (!sel) return;
|
||||||
|
if (force && st) { st.textContent = 'Hole Modell-Liste von Anthropic …'; st.style.color = '#8888AA'; }
|
||||||
try {
|
try {
|
||||||
const r = await fetch('/api/models-list');
|
const r = await fetch('/api/models-list' + (force ? '?refresh=1' : ''));
|
||||||
const d = await r.json();
|
const d = await r.json();
|
||||||
if (!d.ok) throw new Error(d.error || 'Fehler');
|
if (!d.ok) throw new Error(d.error || 'Fehler');
|
||||||
_modelListCache = d.models || [];
|
_modelListCache = d.models || [];
|
||||||
sel.innerHTML = '';
|
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');
|
const opt = document.createElement('option');
|
||||||
opt.value = m.id;
|
opt.value = m.id;
|
||||||
opt.textContent = m.displayName || m.id;
|
opt.textContent = m.displayName || m.id;
|
||||||
@@ -7920,6 +7957,10 @@
|
|||||||
opt.value = ''; opt.textContent = '(keine Modelle vom Proxy)';
|
opt.value = ''; opt.textContent = '(keine Modelle vom Proxy)';
|
||||||
sel.appendChild(opt);
|
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();
|
onModelSelectChange();
|
||||||
// Aktuelles Model vom Brain holen → markiert die richtige Option
|
// Aktuelles Model vom Brain holen → markiert die richtige Option
|
||||||
loadModel();
|
loadModel();
|
||||||
|
|||||||
@@ -2204,22 +2204,27 @@ const server = http.createServer((req, res) => {
|
|||||||
res.end(JSON.stringify({ ok: false, error: err.message }));
|
res.end(JSON.stringify({ ok: false, error: err.message }));
|
||||||
}
|
}
|
||||||
return;
|
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
|
// Kuratierte Model-Liste vom Proxy (/v1/models) — Tier-Auswahl fuers
|
||||||
// Sprachmodell-Dropdown. ARIA laeuft ueber das Max-Abo/CLI, waehlbar ist
|
// Sprachmodell-Dropdown. ARIA laeuft ueber das Max-Abo/CLI, waehlbar ist
|
||||||
// der Tier (opus/sonnet/haiku), keine feste Version.
|
// der Tier (opus/sonnet/haiku), keine feste Version.
|
||||||
(async () => {
|
(async () => {
|
||||||
try {
|
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 d = await r.json();
|
||||||
const models = (d.data || []).map(m => ({
|
const models = (d.data || []).map(m => ({
|
||||||
id: m.id,
|
id: m.id,
|
||||||
tier: m.tier || m.id,
|
tier: m.tier || m.id,
|
||||||
displayName: m.display_name || m.id,
|
displayName: m.display_name || m.id,
|
||||||
description: m.description || "",
|
description: m.description || "",
|
||||||
|
// section: tier | main | overflow — die UI gruppiert danach.
|
||||||
|
section: m.section || "tier",
|
||||||
}));
|
}));
|
||||||
res.writeHead(200, { "Content-Type": "application/json" });
|
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) {
|
} catch (err) {
|
||||||
res.writeHead(502, { "Content-Type": "application/json" });
|
res.writeHead(502, { "Content-Type": "application/json" });
|
||||||
res.end(JSON.stringify({ ok: false, error: String(err && err.message || err) }));
|
res.end(JSON.stringify({ ok: false, error: String(err && err.message || err) }));
|
||||||
|
|||||||
@@ -15,29 +15,47 @@
|
|||||||
* (siehe docker-compose.yml proxy-Block).
|
* (siehe docker-compose.yml proxy-Block).
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
// NUR echte Tier-Aliase. Die CLI loest sie selbst auf die aktuelle Version des
|
||||||
|
// Tiers auf.
|
||||||
|
//
|
||||||
|
// Hier standen frueher auch volle Versionen (claude-opus-5 → "opus",
|
||||||
|
// claude-fable-5 → "fable", ...). Das war falsch: wer bewusst eine BESTIMMTE
|
||||||
|
// Version waehlte, bekam still das Tier-Default — also unter Umstaenden ein
|
||||||
|
// ganz anderes Modell als ausgewaehlt. Volle Namen gehen jetzt unveraendert an
|
||||||
|
// die CLI (siehe extractModel), die versteht sie.
|
||||||
const MODEL_MAP = {
|
const MODEL_MAP = {
|
||||||
"claude-opus-4": "opus",
|
|
||||||
"claude-sonnet-4": "sonnet",
|
|
||||||
"claude-haiku-4": "haiku",
|
|
||||||
"claude-code-cli/claude-opus-4": "opus",
|
|
||||||
"claude-code-cli/claude-sonnet-4": "sonnet",
|
|
||||||
"claude-code-cli/claude-haiku-4": "haiku",
|
|
||||||
"opus": "opus",
|
"opus": "opus",
|
||||||
"sonnet": "sonnet",
|
"sonnet": "sonnet",
|
||||||
"haiku": "haiku",
|
"haiku": "haiku",
|
||||||
"fable": "fable",
|
"fable": "fable",
|
||||||
"claude-fable-5": "fable",
|
"claude-code-cli/opus": "opus",
|
||||||
|
"claude-code-cli/sonnet": "sonnet",
|
||||||
|
"claude-code-cli/haiku": "haiku",
|
||||||
"claude-code-cli/fable": "fable",
|
"claude-code-cli/fable": "fable",
|
||||||
// Volle aktuelle IDs (falls die App/Diagnostic sie mal direkt setzt)
|
|
||||||
"claude-opus-5": "opus",
|
|
||||||
"claude-sonnet-5": "sonnet",
|
|
||||||
"claude-haiku-4-5": "haiku",
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Echte Claude-Modellnamen: claude-<familie>-<zahlen/suffixe>.
|
||||||
|
// Die CLI nimmt laut `claude --help` "an alias for the latest model (e.g.
|
||||||
|
// 'fable', 'opus', or 'sonnet') or a model's full name" — exakte Versionen
|
||||||
|
// darf man also direkt durchreichen.
|
||||||
|
const FULL_MODEL_RE = /^claude-[a-z]+-[0-9][0-9a-z.-]*$/;
|
||||||
|
|
||||||
export function extractModel(model) {
|
export function extractModel(model) {
|
||||||
if (MODEL_MAP[model]) return MODEL_MAP[model];
|
if (MODEL_MAP[model]) return MODEL_MAP[model];
|
||||||
const stripped = (model || "").replace(/^claude-code-cli\//, "");
|
const stripped = (model || "").replace(/^claude-code-cli\//, "");
|
||||||
if (MODEL_MAP[stripped]) return MODEL_MAP[stripped];
|
if (MODEL_MAP[stripped]) return MODEL_MAP[stripped];
|
||||||
|
// Exakte Version durchreichen statt sie auf einen Tier einzuschmelzen.
|
||||||
|
//
|
||||||
|
// Vorher landete JEDER unbekannte Name still auf "opus". Damit war eine
|
||||||
|
// neuere Version faktisch nicht waehlbar: wer "claude-opus-5-5" setzte,
|
||||||
|
// bekam ohne jede Meldung das Tier-Default — die Einstellung sah gesetzt
|
||||||
|
// aus und tat nichts. Kennt die CLI den Namen nicht, sagt SIE das deutlich
|
||||||
|
// ("isn't described by this version's model catalog"), und das ist die
|
||||||
|
// ehrlichere Antwort als ein stilles Downgrade.
|
||||||
|
if (FULL_MODEL_RE.test(stripped)) return stripped;
|
||||||
|
if (stripped) {
|
||||||
|
console.error(`[aria-model] Unbekannter Modellname ${JSON.stringify(stripped)} — nutze 'opus'`);
|
||||||
|
}
|
||||||
return "opus";
|
return "opus";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+131
-6
@@ -552,19 +552,144 @@ function _loadModels() {
|
|||||||
return DEFAULT_MODELS;
|
return DEFAULT_MODELS;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function handleModels(_req, res) {
|
// ── Echte Modell-Liste von Anthropic ────────────────────────────────
|
||||||
|
//
|
||||||
|
// Anthropic veroeffentlicht einen signierten Modell-Katalog, aus dem auch
|
||||||
|
// Claude Code selbst seinen Modell-Auswaehler fuellt. Oeffentlich erreichbar,
|
||||||
|
// KEIN API-Key noetig (die Models-API /v1/models braucht einen; mit dem
|
||||||
|
// Max-Abo gibt es keinen).
|
||||||
|
//
|
||||||
|
// Warum ueberhaupt: die handgepflegte Liste hier veraltete garantiert. Sie
|
||||||
|
// kannte Opus 5 — waehrend im Katalog laengst Opus 5.5, Sonnet 5.5 und
|
||||||
|
// Fable 5.1 standen. Ein "↻ Aktualisieren"-Knopf, der nur eine lokale Datei
|
||||||
|
// neu liest, aktualisiert nichts.
|
||||||
|
//
|
||||||
|
// Struktur: surfaces.<surface>.model_selector_config[].models[]
|
||||||
|
// = { id, name, short_name, description, section }
|
||||||
|
// 'cc' ist die Claude-Code-Oberflaeche — genau die, durch die ARIA laeuft.
|
||||||
|
// section 'main' = die vorgeschlagenen, 'overflow' = aeltere/weitere.
|
||||||
|
const CATALOG_URL = process.env.ARIA_MODEL_CATALOG_URL
|
||||||
|
|| "https://downloads.claude.ai/model-catalog/v1/catalog.json";
|
||||||
|
const CATALOG_CACHE_FILE = "/shared/config/model_catalog_cache.json";
|
||||||
|
const CATALOG_TTL_MS = 6 * 60 * 60 * 1000; // 6h — der Katalog gilt eine Woche
|
||||||
|
const CATALOG_SURFACE = "cc";
|
||||||
|
|
||||||
|
let _catalogMem = null; // { at, models }
|
||||||
|
|
||||||
|
function _parseCatalog(doc) {
|
||||||
|
const out = [];
|
||||||
|
const surfaces = (doc && doc.surfaces && doc.surfaces[CATALOG_SURFACE]) || null;
|
||||||
|
const configs = surfaces && surfaces.model_selector_config;
|
||||||
|
if (!Array.isArray(configs)) return out;
|
||||||
|
for (const cfg of configs) {
|
||||||
|
for (const m of (cfg.models || [])) {
|
||||||
|
if (!m || typeof m.id !== "string") continue;
|
||||||
|
out.push({
|
||||||
|
id: m.id,
|
||||||
|
tier: m.short_name ? String(m.short_name).toLowerCase() : m.id,
|
||||||
|
display_name: m.name || m.id,
|
||||||
|
description: m.description || "",
|
||||||
|
section: m.section || "main",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _readCatalogCache() {
|
||||||
|
try {
|
||||||
|
const c = JSON.parse(fs.readFileSync(CATALOG_CACHE_FILE, "utf-8"));
|
||||||
|
if (Array.isArray(c.models) && c.models.length) return c;
|
||||||
|
} catch (_) { /* kein Cache */ }
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Liefert die Katalog-Modelle. `force` umgeht den TTL (Diagnostic-Knopf).
|
||||||
|
* Schlaegt der Abruf fehl, gilt der Cache — auch ein abgelaufener, denn eine
|
||||||
|
* etwas alte echte Liste ist besser als gar keine.
|
||||||
|
*/
|
||||||
|
async function _catalogModels(force) {
|
||||||
|
const now = Date.now();
|
||||||
|
if (!force && _catalogMem && (now - _catalogMem.at) < CATALOG_TTL_MS) {
|
||||||
|
return _catalogMem.models;
|
||||||
|
}
|
||||||
|
if (!force) {
|
||||||
|
const cached = _readCatalogCache();
|
||||||
|
if (cached && (now - (cached.at || 0)) < CATALOG_TTL_MS) {
|
||||||
|
_catalogMem = { at: cached.at, models: cached.models };
|
||||||
|
return cached.models;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const ctl = AbortSignal.timeout ? AbortSignal.timeout(10000) : undefined;
|
||||||
|
const r = await fetch(CATALOG_URL, { signal: ctl });
|
||||||
|
if (!r.ok) throw new Error(`HTTP ${r.status}`);
|
||||||
|
const doc = await r.json();
|
||||||
|
const models = _parseCatalog(doc);
|
||||||
|
if (!models.length) throw new Error("Katalog enthaelt keine Modelle fuer Surface " + CATALOG_SURFACE);
|
||||||
|
_catalogMem = { at: now, models };
|
||||||
|
try {
|
||||||
|
fs.mkdirSync("/shared/config", { recursive: true });
|
||||||
|
fs.writeFileSync(CATALOG_CACHE_FILE, JSON.stringify({
|
||||||
|
at: now, version: doc.version, expires_at: doc.expires_at, models,
|
||||||
|
}, null, 2));
|
||||||
|
} catch (e) {
|
||||||
|
console.error("[aria-models] Katalog-Cache schreiben fehlgeschlagen:", e && e.message);
|
||||||
|
}
|
||||||
|
console.error(`[aria-models] Katalog geladen: ${models.length} Modelle (Version ${doc.version})`);
|
||||||
|
return models;
|
||||||
|
} catch (e) {
|
||||||
|
console.error("[aria-models] Katalog-Abruf fehlgeschlagen:", e && e.message);
|
||||||
|
const cached = _readCatalogCache();
|
||||||
|
if (cached) {
|
||||||
|
console.error("[aria-models] nutze Cache vom " + new Date(cached.at || 0).toISOString());
|
||||||
|
_catalogMem = { at: cached.at, models: cached.models };
|
||||||
|
return cached.models;
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function handleModels(req, res) {
|
||||||
const created = Math.floor(Date.now() / 1000);
|
const created = Math.floor(Date.now() / 1000);
|
||||||
const models = _loadModels();
|
const force = !!(req && req.query && (req.query.refresh === "1" || req.query.refresh === "true"));
|
||||||
|
|
||||||
|
// 1) Tier-Aliase zuerst: sie zeigen IMMER auf die aktuelle Version ihres
|
||||||
|
// Tiers, ganz ohne Pflege. Fuer den Alltag die richtige Wahl.
|
||||||
|
const tiers = _loadModels().map(m => ({
|
||||||
|
id: m.id,
|
||||||
|
tier: m.tier || m.id,
|
||||||
|
display_name: m.display_name || m.id,
|
||||||
|
description: m.description || "",
|
||||||
|
section: "tier",
|
||||||
|
}));
|
||||||
|
|
||||||
|
// 2) Danach die exakten Versionen aus dem Anthropic-Katalog.
|
||||||
|
const catalog = await _catalogModels(force);
|
||||||
|
const seen = new Set(tiers.map(t => t.id));
|
||||||
|
const exact = catalog.filter(m => !seen.has(m.id)).map(m => ({
|
||||||
|
id: m.id,
|
||||||
|
tier: m.tier,
|
||||||
|
display_name: m.display_name
|
||||||
|
+ (m.section === "main" ? "" : " (ältere Version)"),
|
||||||
|
description: m.description
|
||||||
|
|| (m.section === "main" ? "" : "Aeltere Version — nur waehlen wenn gewollt."),
|
||||||
|
section: m.section,
|
||||||
|
}));
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
object: "list",
|
object: "list",
|
||||||
data: models.map(m => ({
|
catalog_ok: catalog.length > 0,
|
||||||
|
data: [...tiers, ...exact].map(m => ({
|
||||||
id: m.id,
|
id: m.id,
|
||||||
object: "model",
|
object: "model",
|
||||||
owned_by: "anthropic",
|
owned_by: "anthropic",
|
||||||
created,
|
created,
|
||||||
tier: m.tier || m.id,
|
tier: m.tier,
|
||||||
display_name: m.display_name || m.id,
|
display_name: m.display_name,
|
||||||
description: m.description || "",
|
description: m.description,
|
||||||
|
section: m.section,
|
||||||
})),
|
})),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user