summaryrefslogtreecommitdiffhomepage
path: root/packages/frontend/src/lib/components
diff options
context:
space:
mode:
authorAdam Malczewski <[email protected]>2026-06-02 13:02:08 +0900
committerAdam Malczewski <[email protected]>2026-06-02 13:02:08 +0900
commit2503eba38b7885ab92a9c0e4f082323d1b3a8679 (patch)
treecc9ccc2184510d93c601a98ed08a05fdeb146a6f /packages/frontend/src/lib/components
parent3f629a8469fe483243671e1ca15582a111e96541 (diff)
downloaddispatch-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')
-rw-r--r--packages/frontend/src/lib/components/AgentBuilder.svelte20
-rw-r--r--packages/frontend/src/lib/components/ModelSelector.svelte39
-rw-r--r--packages/frontend/src/lib/components/SidebarPanel.svelte4
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;