Skip to content
Open
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: 8 additions & 4 deletions pm-stack/aggregator/src/provider-display.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
//
// Rule 0 (#1823, Drew 2026-11-07): a leading underscore is a
// config/source-level ownership marker (fleet-built providers carry
// it: _claude, _antigravity, ...). Display surfaces strip it — the
// it: _claude, _antigravity, ...). Display surfaces strip it unless
// that would collide with another provider in the rendered set — the
// canonical id keeps it everywhere else.
//
// Rule (dashboard only, markup only): a provider whose name ends in
Expand Down Expand Up @@ -35,13 +36,16 @@ function providerHead(name: string): string {
* same set.
*/
export function providerDisplayNames(names: Iterable<string>): Map<string, string> {
const list = [...names].map((name) => {
const originals = [...names];
const originalSet = new Set(originals);
const list = originals.map((name) => {
const head = providerHead(name);
return head.startsWith("_") ? head.slice(1) + name.slice(head.length) : name;
if (!head.startsWith("_")) return name;
const stripped = head.slice(1) + name.slice(head.length);
return originalSet.has(stripped) ? name : stripped;
});
const heads = new Set(list.map(providerHead));
const out = new Map<string, string>();
const originals = [...names];
for (let i = 0; i < list.length; i++) {
const name = list[i];
const original = originals[i];
Expand Down
29 changes: 29 additions & 0 deletions pm-stack/aggregator/test/provider-display.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,35 @@ describe("providerDisplayNames (#6125)", () => {
expect([...names.keys()].sort()).toEqual(["_antigravity.claude-gpt", "_antigravity.gemini", "_claude", "meta"]);
});

test("reads one-shot iterables exactly once and preserves their input keys", () => {
const inputs = ["_claude", "meta", "_antigravity.gemini"];
function* providers() {
yield* inputs;
}

const expected: [string, string][] = [
["_claude", "claude"],
["meta", "meta"],
["_antigravity.gemini", "antigravity.gemini"],
];
expect([...providerDisplayNames(providers())]).toEqual(expected);

const keys = new Map(inputs.map((name) => [name, true] as const)).keys();
expect([...providerDisplayNames(keys)]).toEqual(expected);
});

test("keeps the ownership marker when stripping it would collide with a plain provider", () => {
const names = providerDisplayNames(["_claude", "claude"]);
expect(names.get("_claude")).toBe("_claude");
expect(names.get("claude")).toBe("claude");
});

test("keeps dotted renamed providers distinguishable from their plain sibling", () => {
const names = providerDisplayNames(["_antigravity.gemini", "antigravity.gemini"]);
expect(names.get("_antigravity.gemini")).toBe("_antigravity.gemini");
expect(names.get("antigravity.gemini")).toBe("antigravity.gemini");
});

test("non-direct names are untouched", () => {
const names = providerDisplayNames(["mistral", "openai.main", "cline-pass", "direct"]);
expect(names.get("mistral")).toBe("mistral");
Expand Down