Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions apps/server/src/provider/ModelManifest.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,26 @@ describe("classifyModels", () => {
],
);
});
it("applies catalog badges to discovered models", () => {
const manifest: ModelManifestData = {
version: 1,
currentModels: { codex: ["gpt-test"] },
providers: {
codex: {
profiles: {},
models: [{ slug: "gpt-test", name: "GPT Test", status: "current", badge: "new" }],
},
},
};
const models = [model({ slug: "openai.gpt-test" }), model({ slug: "gpt-other" })];
assert.deepStrictEqual(
classifyModels(models, manifest, CODEX).map((entry) => [entry.slug, entry.badge]),
[
["openai.gpt-test", "new"],
["gpt-other", undefined],
],
);
});
});

describe("applyManifestDefault", () => {
Expand Down
26 changes: 19 additions & 7 deletions apps/server/src/provider/ModelManifest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -209,18 +209,27 @@ export const encodeManifestCache = Schema.encodeEffect(
),
);

function findCatalogModel(
manifest: ModelManifestData,
driverKind: ProviderDriverKind,
slug: string,
) {
const family = driverKind === "codex" ? codexModelFamily(slug) : slug;
const catalog = manifest.providers?.[driverKind]?.models;
return (
catalog?.find((model) => model.slug === slug) ?? catalog?.find((model) => model.slug === family)
);
}

/** True when the manifest classifies `slug` as legacy for `driverKind`. */
function isLegacyModel(
manifest: ModelManifestData,
driverKind: ProviderDriverKind,
slug: string,
): boolean {
const family = driverKind === "codex" ? codexModelFamily(slug) : slug;
const catalog = manifest.providers?.[driverKind]?.models;
const catalogModel =
catalog?.find((model) => model.slug === slug) ??
catalog?.find((model) => model.slug === family);
const catalogModel = findCatalogModel(manifest, driverKind, slug);
if (catalogModel) return catalogModel.status === "legacy";
const family = driverKind === "codex" ? codexModelFamily(slug) : slug;
const currentModels = manifest.currentModels[driverKind];
if (!currentModels) return false;
return !currentModels.includes(slug) && !currentModels.includes(family);
Expand Down Expand Up @@ -298,8 +307,11 @@ export function classifyModels(
manifest: ModelManifestData,
driverKind: ProviderDriverKind,
): ReadonlyArray<ServerProviderModel> {
return models.map((model) => {
if (model.isCustom) return model;
return models.map((entry) => {
if (entry.isCustom) return entry;
// Discovered models carry no badge, so the manifest can mark a launch.
const badge = findCatalogModel(manifest, driverKind, entry.slug)?.badge;
const model = badge && entry.badge !== badge ? { ...entry, badge } : entry;
if (isLegacyModel(manifest, driverKind, model.slug)) {
return model.isLegacy ? model : { ...model, isLegacy: true };
}
Expand Down
10 changes: 8 additions & 2 deletions apps/server/src/provider/model-manifest.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"version": 1,
"updatedAt": "2026-09-29T01:03:44Z",
"updatedAt": "2026-09-29T19:00:00Z",
"compatibility": [
{
"driver": "codex",
Expand Down Expand Up @@ -60,16 +60,22 @@
],
"currentModels": {
"codex": [
"gpt-6.1-sol",
"gpt-6-astra",
"gpt-6-luna",
"gpt-6-sol",
"gpt-daybreak-blue-latest",
"gpt-daybreak-red-latest"
],
"claudeAgent": ["claude-fable-5-1", "claude-opus-5-5", "claude-sonnet-5-5", "claude-sonnet-5"],
"antigravity": ["gemini-3.8-flash-high", "gemini-3.8-flash-medium", "gemini-3.8-flash-low"]
},
"providers": {
"codex": {
"profiles": {},
"models": [
{ "slug": "gpt-6.1-sol", "name": "GPT-6.1-Sol", "status": "current", "badge": "new" }
]
},
"claudeAgent": {
"defaults": {
"chat": "claude-fable-5-1"
Expand Down
3 changes: 2 additions & 1 deletion docs/internals/model-manifest.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ gets its model list from its app server.
releases that predate catalog discovery; it does not add models to their catalogs.
Catalog-aware releases use `providers.claudeAgent.models[].status` instead.
Codex uses `currentModels.codex` as a legacy-classification overlay for discovered
models.
models. A `providers.codex.models` entry overrides that status and can add a
`new` badge, but never adds a model the app server does not list.

Model data is schema-validated configuration. Tests should cover resolver, cache,
and adapter semantics with synthetic model names, so adding a model never requires
Expand Down
Loading