Skip to content
Draft
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
12 changes: 7 additions & 5 deletions src/providers/slug-codec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,14 +139,16 @@ export function resolveSlugSelection(
// `a/b` is a bare NATIVE id that happens to contain a slash. Treating every slash-bearing
// selection as provider-qualified made `a/b` resolve against provider "a", so the same
// collision reported ambiguous through the dash spelling and unambiguous through the slash
// spelling — the exact asymmetry this resolver exists to remove.
const qualified = selection.startsWith(`${provider}/`)
? selection
: routedSlug(provider, selection);
// spelling — the exact asymmetry this resolver exists to remove. Prefer a known native exact
// match before interpreting the same text as a provider-qualified selection.
const ids = [...knownIds];
const qualified = ids.includes(selection) || !selection.startsWith(`${provider}/`)
? routedSlug(provider, selection)
: selection;
Comment on lines +145 to +147

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Resolve against each provider's complete model set

When customModels contains both orcarouter/auto and auto, this preference does not reach the production removal flow: handleCustomRemove calls resolveSlugSelection separately with each singleton [model.modelId], so this condition treats the first row as a native exact match and the second row as a routed suffix, causing both indices to match and ocx models remove orcarouter/auto --yes to fail as ambiguous. Resolve once against all IDs for the provider (or preselect the native exact row) and add a focused CLI regression covering the two-row case.

AGENTS.md reference: AGENTS.md:L284-L286

Useful? React with 👍 / 👎.

const selectionKey = slugEquivalenceKey(qualified);
const matched: string[] = [];
let exact: string | undefined;
for (const id of knownIds) {
for (const id of ids) {
if (slugEquivalenceKey(routedSlug(provider, id)) !== selectionKey) continue;
matched.push(id);
if (id === selection || `${provider}/${id}` === selection) exact = id;
Expand Down
19 changes: 18 additions & 1 deletion tests/slug-codec.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -355,6 +355,24 @@ describe("#2491 one selection resolver reports what it actually matched", () =>
expect(match.matched.sort()).toEqual(["a-b", "a/b"]);
});

test("a self-namespaced native id is preferred over a routed suffix", () => {
const match = resolveSlugSelection(
"orcarouter",
"orcarouter/auto",
["orcarouter/auto", "auto"],
);
expect(match.matched).toEqual(["orcarouter/auto"]);
expect(match.exact).toBe("orcarouter/auto");
expect(match.ambiguous).toBe(false);
});

test("a self-namespaced native id resolves when it is the only known id", () => {
const match = resolveSlugSelection("orcarouter", "orcarouter/auto", ["orcarouter/auto"]);
expect(match.matched).toEqual(["orcarouter/auto"]);
expect(match.exact).toBe("orcarouter/auto");
expect(match.ambiguous).toBe(false);
});

test("an id absent from an incomplete roster still reports no match rather than guessing", () => {
// Live discovery can omit a published id; the resolver must not invent one.
const match = resolveSlugSelection("p", "missing", ["a-b"]);
Expand All @@ -370,4 +388,3 @@ describe("#2491 one selection resolver reports what it actually matched", () =>
expect(match.exact).toBeUndefined();
});
});

Loading