diff options
Diffstat (limited to 'src/features/chat/model-select.test.ts')
| -rw-r--r-- | src/features/chat/model-select.test.ts | 126 |
1 files changed, 126 insertions, 0 deletions
diff --git a/src/features/chat/model-select.test.ts b/src/features/chat/model-select.test.ts index 6d3081d..523f1d8 100644 --- a/src/features/chat/model-select.test.ts +++ b/src/features/chat/model-select.test.ts @@ -1,9 +1,19 @@ import { describe, expect, it } from "vitest"; import { + isClaudeSubscriptionModel, + isNamedAgentSelection, isVisionModel, joinModelName, + migrateLegacyModelName, + modelKeyLabel, modelKeys, modelsForKey, + NAMED_AGENTS_PROVIDER, + namedAgentNames, + namedAgentSelectionLabel, + namedAgentsProviderLabel, + openCodeNumber, + providerKeysWithNamedAgents, splitModelName, } from "./model-select"; @@ -47,11 +57,64 @@ describe("modelKeys", () => { ).toEqual(["openai", "anthropic", "google"]); }); + it("sorts numbered OpenCode keys numerically (#2 before #10)", () => { + expect( + modelKeys(["opencode-10/models", "opencode-2/models", "opencode-1/models", "openai/gpt-4"]), + ).toEqual(["opencode-1", "opencode-2", "opencode-10", "openai"]); + }); + it("is empty for no models", () => { expect(modelKeys([])).toEqual([]); }); }); +describe("openCodeNumber", () => { + it("parses a numbered OpenCode key", () => { + expect(openCodeNumber("opencode-1")).toBe(1); + expect(openCodeNumber("opencode-10")).toBe(10); + }); + + it("returns null for non-openCode / malformed keys", () => { + expect(openCodeNumber("umans")).toBeNull(); + expect(openCodeNumber("opencode")).toBeNull(); + expect(openCodeNumber("opencode-0")).toBeNull(); + expect(openCodeNumber("opencode-01")).toBeNull(); + }); +}); + +describe("modelKeyLabel", () => { + it("labels a numbered OpenCode key as OpenCode #N", () => { + expect(modelKeyLabel("opencode-2")).toBe("OpenCode #2"); + expect(modelKeyLabel("opencode-10")).toBe("OpenCode #10"); + }); + + it("leaves non-OpenCode keys unchanged", () => { + expect(modelKeyLabel("umans")).toBe("umans"); + }); +}); + +describe("migrateLegacyModelName", () => { + it("rewrites opencode/<model> to opencode-1/<model>", () => { + expect(migrateLegacyModelName("opencode/deepseek-v4-flash")).toBe( + "opencode-1/deepseek-v4-flash", + ); + expect(migrateLegacyModelName("opencode/nested/model")).toBe("opencode-1/nested/model"); + }); + + it("leaves already-numbered and other-provider names unchanged", () => { + expect(migrateLegacyModelName("opencode-1/deepseek-v4-flash")).toBe( + "opencode-1/deepseek-v4-flash", + ); + expect(migrateLegacyModelName("umans/umans-glm-5.2")).toBe("umans/umans-glm-5.2"); + expect(migrateLegacyModelName("opencode-2/x")).toBe("opencode-2/x"); + }); + + it("is idempotent", () => { + const once = migrateLegacyModelName("opencode/deepseek-v4-flash"); + expect(migrateLegacyModelName(once)).toBe(once); + }); +}); + describe("modelsForKey", () => { it("returns the model suffixes under a key, in order", () => { const models = ["openai/gpt-4", "anthropic/claude-3", "openai/gpt-4o"]; @@ -87,3 +150,66 @@ describe("isVisionModel", () => { expect(isVisionModel({}, "kimi/k2")).toBe(false); }); }); + +describe("named-agent provider helpers", () => { + it("providerKeysWithNamedAgents puts the synthetic provider first, then raw keys", () => { + expect(providerKeysWithNamedAgents(["openai/gpt-4", "anthropic/claude"])).toEqual([ + NAMED_AGENTS_PROVIDER, + "openai", + "anthropic", + ]); + // Always present even with zero models. + expect(providerKeysWithNamedAgents([])).toEqual([NAMED_AGENTS_PROVIDER]); + }); + + it("namedAgentsProviderLabel + NAMED_AGENTS_PROVIDER are stable", () => { + expect(namedAgentsProviderLabel()).toBe("Named Agents"); + expect(NAMED_AGENTS_PROVIDER).toBe("__named_agents__"); + }); + + it("namedAgentNames returns the display names", () => { + expect(namedAgentNames([{ id: "a", name: "Coding", model: "m" }])).toEqual(["Coding"]); + }); + + it("isNamedAgentSelection is true only for a snapshot whose model equals the selection", () => { + expect( + isNamedAgentSelection("opencode-1/claude", { + namedAgentId: "a", + namedAgentName: "C", + model: "opencode-1/claude", + }), + ).toBe(true); + expect( + isNamedAgentSelection("other/model", { + namedAgentId: "a", + namedAgentName: "C", + model: "opencode-1/claude", + }), + ).toBe(false); + expect(isNamedAgentSelection("opencode-1/claude", null)).toBe(false); + }); + + it("namedAgentSelectionLabel renders 'Named Agents -> <name>'", () => { + expect(namedAgentSelectionLabel("Coding")).toBe("Named Agents -> Coding"); + }); +}); + +describe("isClaudeSubscriptionModel", () => { + it("is true for claude/<model> (the Claude Code subscription credential)", () => { + expect(isClaudeSubscriptionModel("claude/claude-opus-4-6")).toBe(true); + expect(isClaudeSubscriptionModel("claude/claude-sonnet-4-6")).toBe(true); + }); + + it("is false for Claude models served through other credentials", () => { + expect(isClaudeSubscriptionModel("opencode-1/claude-sonnet-4.5")).toBe(false); + expect(isClaudeSubscriptionModel("openrouter/anthropic/claude-opus-4")).toBe(false); + expect(isClaudeSubscriptionModel("openai/gpt-4o")).toBe(false); + }); + + it("is false for a bare key, a lookalike key, or undefined", () => { + expect(isClaudeSubscriptionModel("claude")).toBe(false); + expect(isClaudeSubscriptionModel("claude/")).toBe(false); + expect(isClaudeSubscriptionModel("claude-2/claude-opus-4-6")).toBe(false); + expect(isClaudeSubscriptionModel(undefined)).toBe(false); + }); +}); |
