Skip to content

Commit 5fee4ab

Browse files
authored
Order newest version first, format provider suffixes, trim comments
Sort each family newest-version-first (so Opus 4.8 sits above 4.7 with Fable at the top of the picker), render OpenAI/Cloudflare model names with the acronym uppercased, version attached, and any codename suffix title-cased (gpt-5.6-sol -> "GPT-5.6 Sol"), and trim the code comments to a minimum. Generated-By: PostHog Code Task-Id: ea8239ab-8fc1-46b7-8dc6-d3661caf8800
1 parent 5846634 commit 5fee4ab

3 files changed

Lines changed: 25 additions & 39 deletions

File tree

packages/agent/src/adapters/base-acp-agent.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -163,9 +163,6 @@ export abstract class BaseAcpAgent implements Agent {
163163
// silently dropping them.
164164
...(model.allowed ? {} : { _meta: restrictedModelMeta() }),
165165
}))
166-
// Group by family, then oldest-to-newest within each family, so the
167-
// picker is deterministic and reads logically instead of interleaving
168-
// families by raw version number.
169166
.sort((a, b) => compareModelsForPicker(a.value, b.value));
170167

171168
// Models the Claude adapter can drive: Anthropic ids, plus Cloudflare `@cf/` ids the gateway

packages/agent/src/gateway-models.test.ts

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ describe("formatGatewayModelName", () => {
4848
).toBe("GPT-5.5");
4949
});
5050

51-
it("strips the openai/ prefix and uppercases GPT", () => {
51+
it("strips the openai/ prefix, uppercases GPT, and title-cases the suffix", () => {
5252
expect(
5353
formatGatewayModelName({
5454
id: "openai/gpt-5.6-sol",
@@ -58,7 +58,7 @@ describe("formatGatewayModelName", () => {
5858
supports_vision: true,
5959
allowed: true,
6060
}),
61-
).toBe("GPT-5.6-sol");
61+
).toBe("GPT-5.6 Sol");
6262
});
6363

6464
it("formats Cloudflare models as the final path segment with GLM uppercased", () => {
@@ -119,28 +119,24 @@ describe("getClaudeModelRecency", () => {
119119
});
120120

121121
describe("compareModelsForPicker", () => {
122-
it("groups models by family, then orders each family oldest to newest", () => {
122+
it("groups models by family, most capable first, newest version first", () => {
123123
// Models as the gateway might return them — arbitrary order.
124124
const gatewayOrder = [
125125
"claude-fable-5",
126-
"claude-opus-4-8",
126+
"claude-opus-4-7",
127127
"claude-mystery",
128128
"claude-sonnet-5",
129129
"claude-haiku-4-5",
130130
"claude-sonnet-4-6",
131-
"claude-opus-4-7",
131+
"claude-opus-4-8",
132132
];
133133
const displayed = [...gatewayOrder].sort(compareModelsForPicker);
134-
// Families are ordered most-capable first and each stays contiguous (both
135-
// Sonnets together, both Opuses together) instead of interleaving by raw
136-
// version number. Within a family, versions run oldest-to-newest.
137-
// Unknown/non-Anthropic models trail the known families.
138134
expect(displayed).toEqual([
139135
"claude-fable-5",
140-
"claude-opus-4-7",
141136
"claude-opus-4-8",
142-
"claude-sonnet-4-6",
137+
"claude-opus-4-7",
143138
"claude-sonnet-5",
139+
"claude-sonnet-4-6",
144140
"claude-haiku-4-5",
145141
"claude-mystery",
146142
]);

packages/agent/src/gateway-models.ts

Lines changed: 18 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -258,16 +258,8 @@ export function getProviderName(ownedBy: string): string {
258258
return PROVIDER_NAMES[ownedBy] ?? ownedBy;
259259
}
260260

261-
// Sort key for ordering models oldest-to-newest in pickers. The model menu
262-
// opens upward (side="top"), so the last item sits closest to the trigger —
263-
// sorting ascending by this key puts the newest model right under the user's
264-
// cursor. The key is the version embedded in the model id, e.g.
265-
// "claude-sonnet-4-6" -> 4006, "claude-opus-4-8" -> 4008, "claude-fable-5" ->
266-
// 5000; a higher number means a newer model. An id with no recognisable
267-
// version (a brand-new or unexpected release) ranks as newest so it still
268-
// surfaces at the end rather than at an arbitrary gateway-determined position.
269-
// Only the first version group is read, so a trailing date suffix (e.g.
270-
// "-20251001") is ignored; the minor component is assumed to be < 1000.
261+
// Version embedded in the model id, e.g. "claude-opus-4-8" -> 4008. Ids with no
262+
// recognisable version rank newest. A trailing date suffix is ignored.
271263
export function getClaudeModelRecency(modelId: string): number {
272264
const match = modelId.toLowerCase().match(/-(\d+)(?:[-.](\d+))?/);
273265
if (!match) return Number.MAX_SAFE_INTEGER;
@@ -276,43 +268,44 @@ export function getClaudeModelRecency(modelId: string): number {
276268
return major * 1000 + minor;
277269
}
278270

279-
// Anthropic model families ordered by tier, most capable first.
271+
// Families ordered most-capable first; unknown families sort last.
280272
const MODEL_FAMILY_ORDER = ["fable", "opus", "sonnet", "haiku"];
281273

282274
function getModelFamilyRank(modelId: string): number {
283275
const id = modelId.toLowerCase();
284276
const index = MODEL_FAMILY_ORDER.findIndex((family) => id.includes(family));
285-
// Non-Anthropic-family models (e.g. Cloudflare `@cf/...`) group after the
286-
// known families.
287277
return index === -1 ? MODEL_FAMILY_ORDER.length : index;
288278
}
289279

290-
// Comparator for the model picker. Groups models by family (tier) first, then
291-
// orders each family's versions oldest-to-newest. Sorting by version alone
292-
// interleaves families (e.g. a Sonnet 5 lands between Opus versions), which
293-
// reads as an arbitrary order; grouping keeps every family contiguous.
280+
// Group by family, then newest version first within each family.
294281
export function compareModelsForPicker(a: string, b: string): number {
295282
const familyDiff = getModelFamilyRank(a) - getModelFamilyRank(b);
296283
if (familyDiff !== 0) return familyDiff;
297-
return getClaudeModelRecency(a) - getClaudeModelRecency(b);
284+
return getClaudeModelRecency(b) - getClaudeModelRecency(a);
298285
}
299286

300287
const PROVIDER_PREFIXES = ["anthropic/", "openai/", "google-vertex/"];
301288

302-
// Uppercase the leading acronym in a model id so provider prefixes read as
303-
// their brand (e.g. "gpt-5.5" -> "GPT-5.5", "glm-5.2" -> "GLM-5.2").
304-
function uppercaseLeadingAcronym(name: string): string {
305-
return name.replace(/^[a-z]+/, (prefix) => prefix.toUpperCase());
289+
// Uppercase the acronym, keep the version attached, title-case any suffix:
290+
// "gpt-5.6-sol" -> "GPT-5.6 Sol", "glm-5.2" -> "GLM-5.2".
291+
function formatProviderModelName(modelId: string): string {
292+
const [acronym, version, ...suffix] = modelId.split("-");
293+
const head = version
294+
? `${acronym.toUpperCase()}-${version}`
295+
: acronym.toUpperCase();
296+
const tail = suffix.map(
297+
(word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase(),
298+
);
299+
return [head, ...tail].join(" ");
306300
}
307301

308302
export function formatGatewayModelName(model: GatewayModel): string {
309303
if (isCloudflareModel(model)) {
310-
const name = (model.id.split("/").pop() ?? model.id).toLowerCase();
311-
return uppercaseLeadingAcronym(name);
304+
return formatProviderModelName(model.id.split("/").pop() ?? model.id);
312305
}
313306

314307
if (isOpenAIModel(model)) {
315-
return uppercaseLeadingAcronym(stripProviderPrefix(model.id).toLowerCase());
308+
return formatProviderModelName(stripProviderPrefix(model.id));
316309
}
317310

318311
return formatModelId(model.id);

0 commit comments

Comments
 (0)