The model picker flagged "latest" whenever a model's catalog name contained "(latest)". That string goes stale the moment a newer model ships — e.g. Opus 4.8 and Sonnet 5 are out, but "Claude Opus 4.5 (latest)" / "Claude Sonnet 4.5 (latest)" kept the tag, so superseded models read as latest. The same staleness hit every provider whose catalog aliases lagged (openai, mistral, openrouter, …). Compute "latest" as the newest model per (provider, family) by release_date instead. Provider-agnostic, driven by real dates: within each family the model with the max release_date wins; models missing family/release_date are never flagged (no false positives). Deprecated models are already filtered upstream. Result for anthropic: Opus 4.8, Sonnet 5, Haiku 4.5, Fable 5 — not the old 4.5s. The "(latest)" suffix is still stripped from display names.
Unified · Split
| @@ -44,13 +44,32 @@ export const { use: useModels, provider: ModelsProvider } = createSimpleContext( · 43 unmodified lines | ||
| 44 | 44 | return map |
| 45 | 45 | }) |
| 46 | 46 | |
| 47 | − const list = createMemo(() => | |
| 48 | − available().map((m) => ({ | |
| 49 | − ...m, | |
| 50 | − name: m.name.replace("(latest)", "").trim(), | |
| 51 | − latest: m.name.includes("(latest)"), | |
| 52 | − })), | |
| 53 | − ) | |
| 47 | + // "latest" = the newest model per (provider, family), decided by release_date | |
| 48 | + // rather than a catalog "(latest)" name string. That string goes stale the | |
| 49 | + // moment a newer model ships — e.g. Opus 4.8 lands but 4.5 keeps the tag — so | |
| 50 | + // it mislabelled superseded models as latest across every provider. | |
| 51 | + const list = createMemo(() => { | |
| 52 | + const items = available() | |
| 53 | + const newestByFamily = new Map<string, string>() | |
| 54 | + for (const m of items) { | |
| 55 | + const family = m.family?.trim() | |
| 56 | + const released = m.release_date?.trim() | |
| 57 | + if (!family || !released) continue | |
| 58 | + const key = `${m.provider.id}:${family}` | |
| 59 | + const current = newestByFamily.get(key) | |
| 60 | + if (!current || released > current) newestByFamily.set(key, released) | |
| 61 | + } | |
| 62 | + return items.map((m) => { | |
| 63 | + const family = m.family?.trim() | |
| 64 | + const released = m.release_date?.trim() | |
| 65 | + const latest = !!family && !!released && newestByFamily.get(`${m.provider.id}:${family}`) === released | |
| 66 | + return { | |
| 67 | + ...m, | |
| 68 | + name: m.name.replace("(latest)", "").trim(), | |
| 69 | + latest, | |
| 70 | + } | |
| 71 | + }) | |
| 72 | + }) | |
| 54 | 73 | |
| 55 | 74 | const find = (key: ModelKey) => list().find((m) => m.id === key.modelID && m.provider.id === key.providerID) |
| 56 | 75 | |