summaryrefslogtreecommitdiffhomepage
path: root/src/features/chat/model-select.test.ts
diff options
context:
space:
mode:
authorAdam Malczewski <[email protected]>2026-08-20 19:36:12 +0900
committerAdam Malczewski <[email protected]>2026-08-20 19:36:12 +0900
commitfb7eb3105e1fbbdefdad9348f9132815b5bbe52d (patch)
treedeb95e97a917424ea67470b168b91349a41d1c08 /src/features/chat/model-select.test.ts
parent63588c3c69b32d180dc78a2cc187cc8aa22ad7e0 (diff)
downloaddispatch-web-fb7eb3105e1fbbdefdad9348f9132815b5bbe52d.tar.gz
dispatch-web-fb7eb3105e1fbbdefdad9348f9132815b5bbe52d.zip
feat(named-agents): sidebar view, synthetic provider, auto-save, snapshot semantics
Implement the frontend of the Named Agents feature (plan §5-§9): - New features/named-agents module: pure logic (types re-exported from @dispatch/wire + transport-contract, validation, provider/model options, snapshot helpers, network normalizers, auto-save status model) + an injected auto-save controller (name debounce, immediate provider/model save, monotonic stale-response guard) + the collapsible sidebar card view. - Store: /named-agents CRUD + boot seed; selectModel(model, snapshot?) persists the raw model WITH the namedAgent snapshot; draft promotion carries it; refreshModel restores ModelResponse.namedAgent. - Chat store setNamedAgent (sends namedAgent on chat.send); Tab.snapshot + setSnapshot (retained across rename/remove, persisted with tabs). - Model selector: synthetic Named Agents provider ALWAYS first; selecting one resolves to the raw model + records a snapshot; a retained snapshot of a renamed/removed agent still displays as unavailable (plan §7). - Composition root wires the view (viewKinds + loaded-modules manifest) + feeds the selector. Tests: logic + controller + selector + app-store CRUD (1246 pass), typecheck 0, biome clean (only pre-existing config infos pending a future migrate), build OK.
Diffstat (limited to 'src/features/chat/model-select.test.ts')
-rw-r--r--src/features/chat/model-select.test.ts49
1 files changed, 49 insertions, 0 deletions
diff --git a/src/features/chat/model-select.test.ts b/src/features/chat/model-select.test.ts
index 33c1a16..57de11d 100644
--- a/src/features/chat/model-select.test.ts
+++ b/src/features/chat/model-select.test.ts
@@ -1,12 +1,18 @@
import { describe, expect, it } from "vitest";
import {
+ isNamedAgentSelection,
isVisionModel,
joinModelName,
migrateLegacyModelName,
modelKeyLabel,
modelKeys,
modelsForKey,
+ NAMED_AGENTS_PROVIDER,
+ namedAgentNames,
+ namedAgentSelectionLabel,
+ namedAgentsProviderLabel,
openCodeNumber,
+ providerKeysWithNamedAgents,
splitModelName,
} from "./model-select";
@@ -143,3 +149,46 @@ 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");
+ });
+});