diff options
| author | Adam Malczewski <[email protected]> | 2026-06-02 13:02:08 +0900 |
|---|---|---|
| committer | Adam Malczewski <[email protected]> | 2026-06-02 13:02:08 +0900 |
| commit | 2503eba38b7885ab92a9c0e4f082323d1b3a8679 (patch) | |
| tree | cc9ccc2184510d93c601a98ed08a05fdeb146a6f /packages/frontend/src/lib/components | |
| parent | 3f629a8469fe483243671e1ca15582a111e96541 (diff) | |
| download | dispatch-2503eba38b7885ab92a9c0e4f082323d1b3a8679.tar.gz dispatch-2503eba38b7885ab92a9c0e4f082323d1b3a8679.zip | |
feat(agents): per-model reasoning effort level
Add a per-model/key reasoning effort setting to agent definitions,
surfaced and editable in the Agent Settings page and displayed at a
glance in the model selector views.
- core: single source of truth for effort levels (REASONING_EFFORTS,
DEFAULT_REASONING_EFFORT='high', labels, isReasoningEffort guard);
add 'xhigh' level; AgentModelEntry.effort; xhigh budget=24000 for
classic-thinking Claude; default floor 'high'. Persist/parse effort
in the agent TOML loader.
- api: thread effort through the fallback chain with per-model -> per-tab
-> default precedence; validate /chat + agentModels effort from the
canonical list.
- frontend: effort <select> per model row in AgentBuilder; effort badges
in ModelSelector (agent + subagent chains); Thinking dropdown sourced
from canonical list; per-tab default raised to 'high'.
- tests: +15 (loader round-trip, agent xhigh budget, canonical list +
guard, api precedence, route validation).
Diffstat (limited to 'packages/frontend/src/lib/components')
3 files changed, 49 insertions, 14 deletions
diff --git a/packages/frontend/src/lib/components/AgentBuilder.svelte b/packages/frontend/src/lib/components/AgentBuilder.svelte index d8ae530..bbdb83c 100644 --- a/packages/frontend/src/lib/components/AgentBuilder.svelte +++ b/packages/frontend/src/lib/components/AgentBuilder.svelte @@ -4,6 +4,11 @@ const modelCache = new Map(); </script> <script lang="ts"> + import { + DEFAULT_REASONING_EFFORT, + REASONING_EFFORTS, + REASONING_EFFORT_LABELS, + } from "@dispatch/core/src/types/index.js"; import { config } from "../config.js"; import { router } from "../router.svelte.js"; import type { KeyInfo } from "../types.js"; @@ -13,6 +18,7 @@ const modelCache = new Map(); interface AgentModelEntry { key_id: string; model_id: string; + effort?: string; } interface AgentDefinition { @@ -173,6 +179,10 @@ const modelCache = new Map(); formModels = formModels.filter((_, idx) => idx !== i); } + function setEffortEntry(i: number, effort: string) { + formModels = formModels.map((m, idx) => (idx === i ? { ...m, effort } : m)); + } + async function openKeyModal(i: number) { modelModalIndex = i; modelModalType = "key"; @@ -546,6 +556,16 @@ const modelCache = new Map(); > {entry.model_id || "Select Model"} </button> + <select + class="select select-bordered select-sm shrink-0 w-28" + title="Reasoning effort for this model" + value={entry.effort ?? DEFAULT_REASONING_EFFORT} + onchange={(e) => setEffortEntry(i, e.currentTarget.value)} + > + {#each REASONING_EFFORTS as effort} + <option value={effort}>{REASONING_EFFORT_LABELS[effort]}</option> + {/each} + </select> <button type="button" class="btn btn-sm btn-ghost text-error" diff --git a/packages/frontend/src/lib/components/ModelSelector.svelte b/packages/frontend/src/lib/components/ModelSelector.svelte index a752f5d..c328511 100644 --- a/packages/frontend/src/lib/components/ModelSelector.svelte +++ b/packages/frontend/src/lib/components/ModelSelector.svelte @@ -3,6 +3,12 @@ const modelCache = new Map<string, string[]>(); </script> <script lang="ts"> + import { + DEFAULT_REASONING_EFFORT, + isReasoningEffort, + REASONING_EFFORTS, + REASONING_EFFORT_LABELS, + } from "@dispatch/core/src/types/index.js"; import type { KeyInfo } from "../types.js"; import { config } from "../config.js"; import { router } from "../router.svelte.js"; @@ -14,7 +20,7 @@ const modelCache = new Map<string, string[]>(); description: string; skills: string[]; tools: string[]; - models: Array<{ key_id: string; model_id: string }>; + models: Array<{ key_id: string; model_id: string; effort?: string }>; cwd?: string; is_subagent?: boolean; } @@ -30,6 +36,15 @@ const modelCache = new Map<string, string[]>(); }; } + /** + * Human-readable effort label for a (possibly-unset / arbitrary) effort + * string. Falls back to the default level's label when unset/invalid so the + * displayed badge always reflects what will actually be used. + */ + function effortLabel(effort: string | undefined): string { + return REASONING_EFFORT_LABELS[isReasoningEffort(effort) ? effort : DEFAULT_REASONING_EFFORT]; + } + const { keys = [], activeKeyId = null, @@ -37,7 +52,7 @@ const modelCache = new Map<string, string[]>(); reasoningEffort = "max", activeAgentSlug = null, activeTabParentId = null as string | null, - activeAgentModels = null as Array<{ key_id: string; model_id: string }> | null, + activeAgentModels = null as Array<{ key_id: string; model_id: string; effort?: string }> | null, workingDirectory = null, onKeyChange, onModelChange, @@ -51,7 +66,7 @@ const modelCache = new Map<string, string[]>(); reasoningEffort?: string; activeAgentSlug?: string | null; activeTabParentId?: string | null; - activeAgentModels?: Array<{ key_id: string; model_id: string }> | null; + activeAgentModels?: Array<{ key_id: string; model_id: string; effort?: string }> | null; workingDirectory?: string | null; onKeyChange: (keyId: string) => void; onModelChange: (keyId: string, modelId: string) => void; @@ -264,11 +279,9 @@ const modelCache = new Map<string, string[]>(); value={reasoningEffort} onchange={(e) => onReasoningChange(e.currentTarget.value)} > - <option value="none">Off</option> - <option value="low">Low</option> - <option value="medium">Medium</option> - <option value="high">High</option> - <option value="max">Max</option> + {#each REASONING_EFFORTS as effort} + <option value={effort}>{REASONING_EFFORT_LABELS[effort]}</option> + {/each} </select> </div> {/if} @@ -312,8 +325,9 @@ const modelCache = new Map<string, string[]>(); </div> <div class="mt-1 flex flex-col gap-0.5"> {#each subModels as m, i} - <div class="text-xs font-mono truncate {i === displayIdx ? 'opacity-100 font-semibold' : 'opacity-50'}"> - {i + 1}. {m.key_id} / {m.model_id} + <div class="text-xs font-mono truncate flex items-center gap-1 {i === displayIdx ? 'opacity-100 font-semibold' : 'opacity-50'}"> + <span class="truncate">{i + 1}. {m.key_id} / {m.model_id}</span> + <span class="badge badge-xs badge-ghost shrink-0">{effortLabel(m.effort)}</span> </div> {/each} </div> @@ -373,8 +387,9 @@ const modelCache = new Map<string, string[]>(); {@const displayIdx = sliderDragging !== null ? sliderDragging : (currentIdx >= 0 ? currentIdx : 0)} {@const displayModel = agent.models[displayIdx]} <div class="mt-2 pt-2 border-t border-primary-content/20"> - <div class="text-xs font-semibold mb-1 truncate"> - {displayModel ? `${displayModel.key_id} / ${displayModel.model_id}` : `${activeKeyId} / ${activeModelId}`} + <div class="text-xs font-semibold mb-1 truncate flex items-center gap-1"> + <span class="truncate">{displayModel ? `${displayModel.key_id} / ${displayModel.model_id}` : `${activeKeyId} / ${activeModelId}`}</span> + <span class="badge badge-xs shrink-0">{effortLabel(displayModel?.effort)}</span> </div> <input type="range" diff --git a/packages/frontend/src/lib/components/SidebarPanel.svelte b/packages/frontend/src/lib/components/SidebarPanel.svelte index 491b1bd..3372396 100644 --- a/packages/frontend/src/lib/components/SidebarPanel.svelte +++ b/packages/frontend/src/lib/components/SidebarPanel.svelte @@ -34,7 +34,7 @@ const { reasoningEffort = "max", activeAgentSlug = null as string | null, activeTabParentId = null as string | null, - activeAgentModels = null as Array<{ key_id: string; model_id: string }> | null, + activeAgentModels = null as Array<{ key_id: string; model_id: string; effort?: string }> | null, workingDirectory = null as string | null, onKeyChange, onModelChange, @@ -54,7 +54,7 @@ const { reasoningEffort?: string; activeAgentSlug?: string | null; activeTabParentId?: string | null; - activeAgentModels?: Array<{ key_id: string; model_id: string }> | null; + activeAgentModels?: Array<{ key_id: string; model_id: string; effort?: string }> | null; workingDirectory?: string | null; onKeyChange: (keyId: string) => void; onModelChange: (keyId: string, modelId: string) => void; |
