summaryrefslogtreecommitdiffhomepage
path: root/src/features/chat/model-select.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.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.ts')
-rw-r--r--src/features/chat/model-select.ts70
1 files changed, 70 insertions, 0 deletions
diff --git a/src/features/chat/model-select.ts b/src/features/chat/model-select.ts
index 5da6e7b..6f198f3 100644
--- a/src/features/chat/model-select.ts
+++ b/src/features/chat/model-select.ts
@@ -108,3 +108,73 @@ export function isVisionModel(
): boolean {
return modelInfo[fullName]?.vision === true;
}
+
+// ── Named Agents synthetic provider (plan §6) ────────────────────────────────
+//
+// The regular model picker gains a "Named Agents" provider at the TOP of the
+// provider list. Selecting it + a Named Agent name resolves to that agent's raw
+// model for the conversation AND records a snapshot so the selection survives
+// later edits to the shared preset. These helpers are structural (they take a
+// minimal NamedAgent-ish shape) so `model-select` stays decoupled from the
+// named-agents feature up the chain; the picker binds the real types.
+
+/** A minimal Named Agent shape the synthetic provider needs (structural). */
+export interface NamedAgentLike {
+ readonly id: string;
+ readonly name: string;
+ readonly model: string;
+}
+
+/** A minimal per-conversation Named Agent selection snapshot (structural). */
+export interface NamedAgentSnapshotLike {
+ readonly namedAgentId: string;
+ readonly namedAgentName: string;
+ readonly model: string;
+}
+
+/**
+ * The synthetic "Named Agents" provider id used by the regular model picker
+ * (plan §6). Deliberately a value that can never collide with a real provider
+ * (`<credentialName>`), so the provider dropdown treats it as the first option.
+ */
+export const NAMED_AGENTS_PROVIDER = "__named_agents__";
+
+/** The friendly label of the synthetic Named Agents provider. */
+export function namedAgentsProviderLabel(): string {
+ return "Named Agents";
+}
+
+/**
+ * The provider dropdown options for the picker: the synthetic "Named Agents"
+ * provider ALWAYS first, then the raw providers (via `modelKeys`). The synthetic
+ * entry stays present even with zero Named Agents (plan §6 shows a disabled
+ * "No named agents configured" model option).
+ */
+export function providerKeysWithNamedAgents(models: readonly string[]): string[] {
+ return [NAMED_AGENTS_PROVIDER, ...modelKeys(models)];
+}
+
+/** The Named Agent names offered in the model dropdown when the synthetic provider is selected. */
+export function namedAgentNames(agents: readonly NamedAgentLike[]): string[] {
+ return agents.map((a) => a.name);
+}
+
+/**
+ * Whether a full raw model string corresponds to a Named Agent snapshot —
+ * i.e. the conversation was selected via a Named Agent whose captured raw model
+ * equals `selected`. When true, the picker displays `Named Agents -> <name>`.
+ */
+export function isNamedAgentSelection(
+ selected: string,
+ snapshot: NamedAgentSnapshotLike | null,
+): boolean {
+ return snapshot !== null && snapshot.model === selected;
+}
+
+/**
+ * The snapshot label shown in the model dropdown for a Named Agent selection:
+ * "Named Agents -> <name>". Pure display.
+ */
+export function namedAgentSelectionLabel(name: string): string {
+ return `${namedAgentsProviderLabel()} -> ${name}`;
+}