summaryrefslogtreecommitdiffhomepage
path: root/src/features/chat/model-select.test.ts
diff options
context:
space:
mode:
Diffstat (limited to 'src/features/chat/model-select.test.ts')
-rw-r--r--src/features/chat/model-select.test.ts126
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);
+ });
+});