diff options
| author | Adam Malczewski <[email protected]> | 2026-08-20 19:36:12 +0900 |
|---|---|---|
| committer | Adam Malczewski <[email protected]> | 2026-08-20 19:36:12 +0900 |
| commit | fb7eb3105e1fbbdefdad9348f9132815b5bbe52d (patch) | |
| tree | deb95e97a917424ea67470b168b91349a41d1c08 /src/features/chat/model-select.ts | |
| parent | 63588c3c69b32d180dc78a2cc187cc8aa22ad7e0 (diff) | |
| download | dispatch-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.ts | 70 |
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}`; +} |
