refactor(coding-agent): remove test-only parser and feature-hint seams (#1860)
* refactor(coding-agent): remove test-only parser and feature-hint seams * test(coding-agent): restore provider-overlap and invalid-suffix warning coverage via public resolver API
1- Simplified model resolution and feature hint shuffling internals.52 unmodified lines535455565757585960611 unmodified line636465666767686970717273747675767879807778798081838485868788899091929394959697989910010110210310410510610710810911082838485118 unmodified linesExpand all52 unmodified lines}
describe("feature hint deck", () => { it("shows every available hint before repeating and avoids a boundary repeat", () => { const deck = new FeatureHintDeck(() => 0); const deck = new FeatureHintDeck(); const context = { getKeybinding: (action: string) => `Custom ${action}`, isResidentSession: true }; const firstCycle = FEATURE_HINTS.map(() => deck.next(context));
expect(firstCycle.every((hint) => hint !== undefined)).toBe(true);1 unmodified line expect(deck.next(context)?.id).not.toBe(firstCycle.at(-1)?.id); });
it("uses configured shortcuts in keybinding-based hints", () => { const deck = new FeatureHintDeck(() => 0); const context = { getKeybinding: (action: string) => { if (action === "app.prompt.stash") return "Meta+S"; if (action === "app.message.followUp") return "Meta+Enter"; return "Meta+Left"; }, isResidentSession: true, }; const hints = FEATURE_HINTS.map(() => deck.next(context)); const textById = new Map(FEATURE_HINTS.map((hint) => [hint.id, hint.getText(context)]));
expect(hints.find((hint) => hint?.id === "prompt-stash")?.text).toContain("Meta+S"); expect(hints.find((hint) => hint?.id === "follow-up")?.text).toContain("Meta+Enter"); expect(hints.find((hint) => hint?.id === "agents-view")?.text).toContain("Meta+Left"); expect(textById.get("prompt-stash")).toContain("Meta+S"); expect(textById.get("follow-up")).toContain("Meta+Enter"); expect(textById.get("agents-view")).toContain("Meta+Left"); });
it("covers Prime Agent workflows with capability-focused copy", () => { const deck = new FeatureHintDeck(() => 0); const hints = FEATURE_HINTS.map(() => deck.next({ getKeybinding: () => "Meta+A", isResidentSession: true })); const textById = new Map(hints.map((hint) => [hint?.id, hint?.text]));
expect(textById.get("subagents")).toBe("Prime Agent can delegate tasks to subagents and run them in parallel."); expect(textById.get("agents-view")).toContain("Session View"); expect(textById.get("session-rewind")).toContain("/tree"); expect(textById.get("steering")).toContain("steer"); expect(textById.get("agent-messaging")).toContain("message each other"); expect(textById.get("goal")).toContain("/goal"); expect(textById.get("refine")).toContain("/refine"); expect(textById.get("persistent-ipython")).toContain("kernel variables"); expect(textById.get("context-usage")).toContain("/context"); expect(textById.get("session-fork")).toContain("/fork"); expect(textById.get("compaction")).toContain("/compact"); expect(textById.get("auto-compaction")).toContain("automatically compacts"); expect(textById.get("auto-refine")).toContain("self-improves"); expect(textById.get("background-running")).toContain("close the terminal"); });
it("keeps every hint concise", () => { const deck = new FeatureHintDeck(() => 0); const hints = FEATURE_HINTS.map(() => deck.next({ getKeybinding: () => "Ctrl+Key", isResidentSession: true }));
expect(hints.every((hint) => hint !== undefined && hint.text.length <= 80)).toBe(true); });
it("hides resident-only hints in ephemeral sessions", () => { const context = { getKeybinding: () => "Left", isResidentSession: false }; const textById = new Map(FEATURE_HINTS.map((hint) => [hint.id, hint.getText(context)]));
118 unmodified linesExpand all1223456678978 unmodified lines888990919392959697989910010110293949510410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513696979813914014114214314414599100101102103147148149150151152153154155156157158159160161104105106107108109110164165166167168169170171111112113114115116117118119120121122123124125173174175176177178179126127128129130181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217131132219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250133134135136388 unmodified linesExpand allimport type { Model } from "@earendil-works/pi-ai";import { describe, expect, test } from "vitest";import { describe, expect, test, vi } from "vitest";import { defaultModelPerProvider, findInitialModel, parseModelPattern, resolveCliModel, resolveModelScopeFromModels,} from "../src/core/model-resolver.js";
78 unmodified lines expect(result[0]?.thinkingLevel).toBe("high"); expect(result[1]?.model.provider).toBe("openai"); expect(result[1]?.model.id).toBe("gpt-4o"); });});
describe("parseModelPattern", () => { describe("simple patterns without colons", () => { test("exact match returns model with undefined thinking level", () => { const result = parseModelPattern("claude-sonnet-4-5", allModels); expect(result.model?.id).toBe("claude-sonnet-4-5"); expect(result.thinkingLevel).toBeUndefined(); expect(result.warning).toBeUndefined(); }); test("resolves a thinking level after a colon-bearing model id", () => { const result = resolveModelScopeFromModels(["openrouter/qwen/qwen3-coder:exacto:high"], allModels);
test("partial match returns best model with undefined thinking level", () => { const result = parseModelPattern("sonnet", allModels); expect(result.model?.id).toBe("claude-sonnet-4-5"); expect(result.thinkingLevel).toBeUndefined(); expect(result.warning).toBeUndefined(); });
test("preserves provider-qualified selections when model names overlap", () => { const primeInferenceModel: Model<"anthropic-messages"> = { ...mockModels[0], id: "z-ai/glm-5.2", name: "GLM 5.2", provider: "prime-inference", baseUrl: "https://api.pinference.ai/api/v1", }; const huggingFaceModel: Model<"anthropic-messages"> = { ...primeInferenceModel, id: "zai-org/GLM-5.2", provider: "huggingface", baseUrl: "https://router.huggingface.co/v1", };
const result = parseModelPattern("huggingface/zai-org/GLM-5.2", [primeInferenceModel, huggingFaceModel]);
expect(result.model).toBe(huggingFaceModel); });
test("no match returns undefined model and thinking level", () => { const result = parseModelPattern("nonexistent", allModels); expect(result.model).toBeUndefined(); expect(result.thinkingLevel).toBeUndefined(); expect(result.warning).toBeUndefined(); }); expect(result).toEqual([{ model: mockOpenRouterModels[0], thinkingLevel: "high" }]); });
describe("patterns with valid thinking levels", () => { test("sonnet:high returns sonnet with high thinking level", () => { const result = parseModelPattern("sonnet:high", allModels); expect(result.model?.id).toBe("claude-sonnet-4-5"); expect(result.thinkingLevel).toBe("high"); expect(result.warning).toBeUndefined(); }); test("keeps the model, warns, and drops an invalid thinking suffix", () => { const warn = vi.spyOn(console, "warn").mockImplementation(() => {}); try { const result = resolveModelScopeFromModels(["sonnet:random"], allModels);
test("gpt-4o:medium returns gpt-4o with medium thinking level", () => { const result = parseModelPattern("gpt-4o:medium", allModels); expect(result.model?.id).toBe("gpt-4o"); expect(result.thinkingLevel).toBe("medium"); expect(result.warning).toBeUndefined(); });
test("all valid thinking levels work", () => { for (const level of ["off", "minimal", "low", "medium", "high", "xhigh"]) { const result = parseModelPattern(`sonnet:${level}`, allModels); expect(result.model?.id).toBe("claude-sonnet-4-5"); expect(result.thinkingLevel).toBe(level); expect(result.warning).toBeUndefined(); } }); expect(result).toEqual([{ model: mockModels[0], thinkingLevel: undefined }]); expect(warn).toHaveBeenCalledWith(expect.stringContaining('Invalid thinking level "random"')); } finally { warn.mockRestore(); } });
describe("patterns with invalid thinking levels", () => { test("sonnet:random returns sonnet with undefined thinking level and warning", () => { const result = parseModelPattern("sonnet:random", allModels); expect(result.model?.id).toBe("claude-sonnet-4-5"); expect(result.thinkingLevel).toBeUndefined(); expect(result.warning).toContain("Invalid thinking level"); expect(result.warning).toContain("random"); }); test("preserves provider-qualified selections when model names overlap", () => { const primeInferenceModel: Model<"anthropic-messages"> = { ...mockModels[0]!, id: "z-ai/glm-5.2", name: "GLM 5.2", provider: "prime-inference", baseUrl: "https://api.pinference.ai/api/v1", }; const huggingFaceModel: Model<"anthropic-messages"> = { ...primeInferenceModel, id: "zai-org/GLM-5.2", provider: "huggingface", baseUrl: "https://router.huggingface.co/v1", };
test("gpt-4o:invalid returns gpt-4o with undefined thinking level and warning", () => { const result = parseModelPattern("gpt-4o:invalid", allModels); expect(result.model?.id).toBe("gpt-4o"); expect(result.thinkingLevel).toBeUndefined(); expect(result.warning).toContain("Invalid thinking level"); }); }); const result = resolveModelScopeFromModels( ["huggingface/zai-org/GLM-5.2"], [primeInferenceModel, huggingFaceModel], );
describe("OpenRouter models with colons in IDs", () => { test("qwen3-coder:exacto matches the model with undefined thinking level", () => { const result = parseModelPattern("qwen/qwen3-coder:exacto", allModels); expect(result.model?.id).toBe("qwen/qwen3-coder:exacto"); expect(result.thinkingLevel).toBeUndefined(); expect(result.warning).toBeUndefined(); });
test("openrouter/qwen/qwen3-coder:exacto matches with provider prefix", () => { const result = parseModelPattern("openrouter/qwen/qwen3-coder:exacto", allModels); expect(result.model?.id).toBe("qwen/qwen3-coder:exacto"); expect(result.model?.provider).toBe("openrouter"); expect(result.thinkingLevel).toBeUndefined(); expect(result.warning).toBeUndefined(); });
test("qwen3-coder:exacto:high matches model with high thinking level", () => { const result = parseModelPattern("qwen/qwen3-coder:exacto:high", allModels); expect(result.model?.id).toBe("qwen/qwen3-coder:exacto"); expect(result.thinkingLevel).toBe("high"); expect(result.warning).toBeUndefined(); });
test("openrouter/qwen/qwen3-coder:exacto:high matches with provider and thinking level", () => { const result = parseModelPattern("openrouter/qwen/qwen3-coder:exacto:high", allModels); expect(result.model?.id).toBe("qwen/qwen3-coder:exacto"); expect(result.model?.provider).toBe("openrouter"); expect(result.thinkingLevel).toBe("high"); expect(result.warning).toBeUndefined(); });
test("gpt-4o:extended matches the extended model with undefined thinking level", () => { const result = parseModelPattern("openai/gpt-4o:extended", allModels); expect(result.model?.id).toBe("openai/gpt-4o:extended"); expect(result.thinkingLevel).toBeUndefined(); expect(result.warning).toBeUndefined(); }); expect(result).toEqual([{ model: huggingFaceModel, thinkingLevel: undefined }]); });
describe("invalid thinking levels with OpenRouter models", () => { test("qwen3-coder:exacto:random returns model with undefined thinking level and warning", () => { const result = parseModelPattern("qwen/qwen3-coder:exacto:random", allModels); expect(result.model?.id).toBe("qwen/qwen3-coder:exacto"); expect(result.thinkingLevel).toBeUndefined(); expect(result.warning).toContain("Invalid thinking level"); expect(result.warning).toContain("random"); });
test("qwen3-coder:exacto:high:random returns model with undefined thinking level and warning", () => { const result = parseModelPattern("qwen/qwen3-coder:exacto:high:random", allModels); expect(result.model?.id).toBe("qwen/qwen3-coder:exacto"); expect(result.thinkingLevel).toBeUndefined(); expect(result.warning).toContain("Invalid thinking level"); expect(result.warning).toContain("random"); }); });
describe("edge cases", () => { test("empty pattern matches via partial matching", () => { const result = parseModelPattern("", allModels); expect(result.model).not.toBeNull(); expect(result.thinkingLevel).toBeUndefined(); });
test("pattern ending with colon treats empty suffix as invalid", () => { const result = parseModelPattern("sonnet:", allModels); expect(result.model?.id).toBe("claude-sonnet-4-5"); expect(result.warning).toContain("Invalid thinking level"); }); });});
describe("resolveCliModel", () => { test("resolves --model provider/id without --provider", () => {388 unmodified linesExpand all145 unmodified linesExpand all14614714814915015015115215315443 unmodified lines198199200201202203202205203204205206207432 unmodified linesExpand all145 unmodified linesExpand all return datedVersions[0]; }}
export interface ParsedModelResult {interface ParsedModelResult { model: Model<Api> | undefined; /** Thinking level if explicitly specified in pattern, undefined otherwise */ thinkingLevel?: ThinkingLevel; warning: string | undefined;43 unmodified lines * 2. If found, return it with "off" thinking level * 3. If not found and has colons, split on last colon: * - If suffix is valid thinking level, use it and recurse on prefix * - If suffix is invalid, warn and recurse on prefix with "off" * * @internal Exported for testing */export function parseModelPattern(function parseModelPattern( pattern: string, availableModels: Model<Api>[], options?: { allowInvalidThinkingLevelFallback?: boolean },): ParsedModelResult {432 unmodified linesExpand all107 unmodified linesExpand all1081091101111121131121131141159 unmodified lines1251261271281311321291301311321334 unmodified lines107 unmodified linesExpand allexport class FeatureHintDeck { private remaining: FeatureHint[] = []; private previousId: string | undefined;
constructor(private readonly random: () => number = Math.random) {}
next(context: FeatureHintContext): FeatureHint | undefined { if (this.remaining.length === 0) { this.refill(context); }9 unmodified lines const text = hint.getText(context); return text ? [{ id: hint.id, text }] : []; }); for (let index = hints.length - 1; index > 0; index--) { const random = Math.min(0.999999999, Math.max(0, this.random())); const target = Math.floor(random * (index + 1)); const target = Math.floor(Math.random() * (index + 1)); [hints[index], hints[target]] = [hints[target]!, hints[index]!]; } if (hints.length > 1 && hints[hints.length - 1]?.id === this.previousId) { [hints[0], hints[hints.length - 1]] = [hints[hints.length - 1]!, hints[0]!];4 unmodified lines