mirror of
https://github.com/n8n-io/n8n.git
synced 2026-09-19 09:51:59 +08:00
fix(core): Remove (latest) tags from models in agent models catalog (no-changelog) (#32023)
This commit is contained in:
@@ -70,4 +70,58 @@ describe('fetchProviderCatalog', () => {
|
||||
expect(catalog.azure).toBeUndefined();
|
||||
expect(catalog['azure-cognitive-services']).toBeUndefined();
|
||||
});
|
||||
|
||||
it('strips the "(latest)" suffix from model names and drops duplicate pinned snapshots', async () => {
|
||||
const fetchMock = vi.fn().mockResolvedValue({
|
||||
ok: true,
|
||||
json: async () =>
|
||||
await Promise.resolve({
|
||||
anthropic: {
|
||||
id: 'anthropic',
|
||||
name: 'Anthropic',
|
||||
models: {
|
||||
'claude-opus-4-5': {
|
||||
id: 'claude-opus-4-5',
|
||||
name: 'Claude Opus 4.5 (latest)',
|
||||
},
|
||||
'claude-opus-4-5-20251101': {
|
||||
id: 'claude-opus-4-5-20251101',
|
||||
name: 'Claude Opus 4.5',
|
||||
},
|
||||
'claude-opus-4-8': {
|
||||
id: 'claude-opus-4-8',
|
||||
name: 'Claude Opus 4.8',
|
||||
},
|
||||
'claude-3-5-haiku-latest': {
|
||||
id: 'claude-3-5-haiku-latest',
|
||||
name: 'Claude Haiku 3.5 (latest)',
|
||||
},
|
||||
},
|
||||
},
|
||||
google: {
|
||||
id: 'google',
|
||||
name: 'Google',
|
||||
models: {
|
||||
'gemini-flash-latest': {
|
||||
id: 'gemini-flash-latest',
|
||||
name: 'Gemini Flash Latest',
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
});
|
||||
global.fetch = fetchMock as typeof fetch;
|
||||
|
||||
const catalog = await fetchProviderCatalog();
|
||||
|
||||
// Alias keeps its id but loses the "(latest)" tag
|
||||
expect(catalog.anthropic.models['claude-opus-4-5'].name).toBe('Claude Opus 4.5');
|
||||
expect(catalog.anthropic.models['claude-3-5-haiku-latest'].name).toBe('Claude Haiku 3.5');
|
||||
// Pinned snapshot with the same name as an alias is dropped
|
||||
expect(catalog.anthropic.models['claude-opus-4-5-20251101']).toBeUndefined();
|
||||
// Models without a same-named alias are untouched
|
||||
expect(catalog.anthropic.models['claude-opus-4-8'].name).toBe('Claude Opus 4.8');
|
||||
// Only the parenthesized suffix is stripped, not other "latest" naming
|
||||
expect(catalog.google.models['gemini-flash-latest'].name).toBe('Gemini Flash Latest');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -82,6 +82,33 @@ function toAgentProviderId(modelsDevProviderId: string): string {
|
||||
return MODELS_DEV_PROVIDER_ALIASES[modelsDevProviderId] ?? modelsDevProviderId;
|
||||
}
|
||||
|
||||
const LATEST_NAME_SUFFIX = /\s*\(latest\)$/i;
|
||||
|
||||
/**
|
||||
* models.dev names versionless alias models with a " (latest)" suffix
|
||||
* (e.g. `claude-opus-4-5` → "Claude Opus 4.5 (latest)"), which quickly goes
|
||||
* stale as newer models ship. Strip the suffix from every model name, and when
|
||||
* a pinned snapshot shares the resulting name with an alias (e.g.
|
||||
* `claude-opus-4-5-20251101` "Claude Opus 4.5"), drop the snapshot so the
|
||||
* alias is the single entry for that model.
|
||||
*/
|
||||
function normalizeLatestModelNames(models: Record<string, ModelInfo>): void {
|
||||
const aliasNames = new Set<string>();
|
||||
for (const model of Object.values(models)) {
|
||||
if (LATEST_NAME_SUFFIX.test(model.name)) {
|
||||
aliasNames.add(model.name.replace(LATEST_NAME_SUFFIX, ''));
|
||||
}
|
||||
}
|
||||
|
||||
for (const [modelId, model] of Object.entries(models)) {
|
||||
if (LATEST_NAME_SUFFIX.test(model.name)) {
|
||||
model.name = model.name.replace(LATEST_NAME_SUFFIX, '');
|
||||
} else if (aliasNames.has(model.name)) {
|
||||
delete models[modelId];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the provider/model catalog from models.dev.
|
||||
*
|
||||
@@ -146,6 +173,10 @@ export async function fetchProviderCatalog(): Promise<ProviderCatalog> {
|
||||
};
|
||||
}
|
||||
|
||||
for (const provider of Object.values(catalog)) {
|
||||
normalizeLatestModelNames(provider.models);
|
||||
}
|
||||
|
||||
return catalog;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user