Skip to content

Commit 7ee4d97

Browse files
committed
fix(models): preserve input provider order and build catalog index once
1 parent 6805029 commit 7ee4d97

2 files changed

Lines changed: 80 additions & 32 deletions

File tree

apps/sim/providers/models.test.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,29 @@ describe('orderModelIdsByReleaseDate', () => {
5656
}
5757
})
5858

59+
it('preserves the cross-provider grouping order given in the input', () => {
60+
// Pick the first model of two different providers and feed the second provider
61+
// first; the helper must keep that provider's group ahead of the other.
62+
const byProvider = new Map<number, string[]>()
63+
for (const id of Object.keys(getBaseModelProviders())) {
64+
const providerIndex = PROVIDER_INDEX_BY_MODEL.get(id.toLowerCase()) as number
65+
const bucket = byProvider.get(providerIndex) ?? []
66+
bucket.push(id)
67+
byProvider.set(providerIndex, bucket)
68+
}
69+
const providerIndexes = [...byProvider.keys()]
70+
expect(providerIndexes.length).toBeGreaterThanOrEqual(2)
71+
const [firstProvider, secondProvider] = providerIndexes
72+
const fromFirst = byProvider.get(firstProvider) as string[]
73+
const fromSecond = byProvider.get(secondProvider) as string[]
74+
75+
// Input order intentionally leads with the second provider.
76+
const input = [fromSecond[0], fromFirst[0]]
77+
const ordered = orderModelIdsByReleaseDate(input)
78+
expect(PROVIDER_INDEX_BY_MODEL.get(ordered[0].toLowerCase())).toBe(secondProvider)
79+
expect(PROVIDER_INDEX_BY_MODEL.get(ordered[1].toLowerCase())).toBe(firstProvider)
80+
})
81+
5982
it('places unknown model IDs last, preserving their input order', () => {
6083
const known = Object.keys(getBaseModelProviders())[0]
6184
const ordered = orderModelIdsByReleaseDate(['mystery-a', known, 'mystery-b'])

apps/sim/providers/models.ts

Lines changed: 57 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -3047,46 +3047,71 @@ export function getProviderModels(providerId: string): string[] {
30473047
return PROVIDER_DEFINITIONS[providerId]?.models.map((m) => m.id) || []
30483048
}
30493049

3050+
interface ModelCatalogEntry {
3051+
providerId: string
3052+
declIndex: number
3053+
releaseTime: number
3054+
}
3055+
3056+
/**
3057+
* Lowercased model ID → catalog position metadata, built once from the static
3058+
* provider catalog. Dynamic providers contribute nothing here because their model
3059+
* lists are populated at runtime (not at module load), and only catalog models are
3060+
* ever reordered by release date.
3061+
*/
3062+
const MODEL_CATALOG_INDEX: Map<string, ModelCatalogEntry> = new Map(
3063+
Object.entries(PROVIDER_DEFINITIONS).flatMap(([providerId, provider]) =>
3064+
provider.models.map((model, declIndex): [string, ModelCatalogEntry] => {
3065+
const parsed = model.releaseDate ? Date.parse(model.releaseDate) : Number.NaN
3066+
return [
3067+
model.id.toLowerCase(),
3068+
{
3069+
providerId,
3070+
declIndex,
3071+
releaseTime: Number.isNaN(parsed) ? Number.NEGATIVE_INFINITY : parsed,
3072+
},
3073+
]
3074+
})
3075+
)
3076+
)
3077+
30503078
/**
3051-
* Reorders catalog model IDs so that, within each provider, newer models (by
3052-
* release date) come first, while preserving the existing provider grouping order.
3079+
* Reorders model IDs so that, within each provider, newer models (by release date)
3080+
* come first — while preserving the caller's existing provider grouping order. The
3081+
* relative order of providers is taken from the order they first appear in `modelIds`,
3082+
* so the cross-provider layout the user already sees is never reshuffled.
30533083
*
30543084
* Models without a known release date keep their declaration order and sort after
30553085
* dated models within the same provider. IDs not found in the catalog (e.g.
30563086
* dynamically-discovered provider models) are left in their original order at the end.
30573087
*/
30583088
export function orderModelIdsByReleaseDate(modelIds: string[]): string[] {
3059-
const catalogIndex = new Map<
3060-
string,
3061-
{ providerIndex: number; declIndex: number; releaseTime: number }
3062-
>()
3063-
Object.values(PROVIDER_DEFINITIONS).forEach((provider, providerIndex) => {
3064-
provider.models.forEach((model, declIndex) => {
3065-
const parsed = model.releaseDate ? Date.parse(model.releaseDate) : Number.NaN
3066-
catalogIndex.set(model.id.toLowerCase(), {
3067-
providerIndex,
3068-
declIndex,
3069-
releaseTime: Number.isNaN(parsed) ? Number.NEGATIVE_INFINITY : parsed,
3070-
})
3071-
})
3072-
})
3073-
3074-
return modelIds
3075-
.map((id, inputIndex) => ({ id, inputIndex, meta: catalogIndex.get(id.toLowerCase()) }))
3076-
.sort((a, b) => {
3077-
if (!a.meta || !b.meta) {
3078-
if (!a.meta && !b.meta) return a.inputIndex - b.inputIndex
3079-
return a.meta ? -1 : 1
3080-
}
3081-
if (a.meta.providerIndex !== b.meta.providerIndex) {
3082-
return a.meta.providerIndex - b.meta.providerIndex
3083-
}
3084-
if (a.meta.releaseTime !== b.meta.releaseTime) {
3085-
return b.meta.releaseTime - a.meta.releaseTime
3086-
}
3087-
return a.meta.declIndex - b.meta.declIndex
3089+
const groups = new Map<string, string[]>()
3090+
const unknown: string[] = []
3091+
3092+
for (const id of modelIds) {
3093+
const meta = MODEL_CATALOG_INDEX.get(id.toLowerCase())
3094+
if (!meta) {
3095+
unknown.push(id)
3096+
continue
3097+
}
3098+
const bucket = groups.get(meta.providerId)
3099+
if (bucket) bucket.push(id)
3100+
else groups.set(meta.providerId, [id])
3101+
}
3102+
3103+
const ordered: string[] = []
3104+
for (const bucket of groups.values()) {
3105+
bucket.sort((a, b) => {
3106+
const ma = MODEL_CATALOG_INDEX.get(a.toLowerCase())!
3107+
const mb = MODEL_CATALOG_INDEX.get(b.toLowerCase())!
3108+
if (ma.releaseTime !== mb.releaseTime) return mb.releaseTime - ma.releaseTime
3109+
return ma.declIndex - mb.declIndex
30883110
})
3089-
.map((entry) => entry.id)
3111+
ordered.push(...bucket)
3112+
}
3113+
ordered.push(...unknown)
3114+
return ordered
30903115
}
30913116

30923117
export const DYNAMIC_MODEL_PROVIDERS = [

0 commit comments

Comments
 (0)