summaryrefslogtreecommitdiffhomepage
path: root/packages/core/src
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/core/src
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/core/src')
-rw-r--r--packages/core/src/agent/agent.tsbin57720 -> 57822 bytes
-rw-r--r--packages/core/src/agents/loader.ts8
-rw-r--r--packages/core/src/types/index.ts44
3 files changed, 50 insertions, 2 deletions
diff --git a/packages/core/src/agent/agent.ts b/packages/core/src/agent/agent.ts
index c6f1322..4bfa7eb 100644
--- a/packages/core/src/agent/agent.ts
+++ b/packages/core/src/agent/agent.ts
Binary files differ
diff --git a/packages/core/src/agents/loader.ts b/packages/core/src/agents/loader.ts
index f4a6c5a..9971803 100644
--- a/packages/core/src/agents/loader.ts
+++ b/packages/core/src/agents/loader.ts
@@ -2,7 +2,7 @@ import * as fs from "node:fs";
import * as os from "node:os";
import * as path from "node:path";
import { parse as parseTOML, stringify as stringifyTOML } from "smol-toml";
-import type { AgentDefinition, AgentModelEntry } from "../types/index.js";
+import { type AgentDefinition, type AgentModelEntry, isReasoningEffort } from "../types/index.js";
// ─── Helpers ─────────────────────────────────────────────────────
@@ -192,6 +192,7 @@ export function saveAgent(agent: AgentDefinition): void {
tomlContent.models = agent.models.map((m) => ({
key_id: m.key_id,
model_id: m.model_id,
+ ...(m.effort ? { effort: m.effort } : {}),
}));
}
@@ -246,9 +247,14 @@ function loadAgentsFromDir(dir: string, scope: string): AgentDefinition[] {
if (Array.isArray(parsed.models)) {
for (const m of parsed.models) {
if (m && typeof m === "object" && "key_id" in m && "model_id" in m) {
+ const rawEffort = (m as Record<string, unknown>).effort;
models.push({
key_id: String((m as Record<string, unknown>).key_id),
model_id: String((m as Record<string, unknown>).model_id),
+ // Only carry `effort` when it's a recognised level; an
+ // unset or invalid value falls back to the per-tab /
+ // default effort at the call site.
+ ...(isReasoningEffort(rawEffort) ? { effort: rawEffort } : {}),
});
}
}
diff --git a/packages/core/src/types/index.ts b/packages/core/src/types/index.ts
index ced3dc2..bab17f1 100644
--- a/packages/core/src/types/index.ts
+++ b/packages/core/src/types/index.ts
@@ -308,7 +308,43 @@ export interface ToolDefinition {
// ─── Agent Configuration ─────────────────────────────────────────
-export type ReasoningEffort = "none" | "low" | "medium" | "high" | "max";
+/**
+ * Canonical, ordered list of reasoning-effort levels — the SINGLE SOURCE OF
+ * TRUTH for effort values across the whole codebase (core LLM call site, API
+ * validation, agent TOML persistence, and the frontend UI). Ordered from least
+ * to most effort.
+ *
+ * `none` disables reasoning. `low`/`medium`/`high`/`xhigh` are forwarded
+ * verbatim to providers that accept them (OpenAI-compatible `reasoning_effort`,
+ * Anthropic adaptive `effort`) — `xhigh` is accepted by newer OpenAI reasoning
+ * models. `max` is Dispatch's own top tier, mapped per-provider at the call
+ * site (e.g. classic-thinking Claude budget tokens).
+ */
+export const REASONING_EFFORTS = ["none", "low", "medium", "high", "xhigh", "max"] as const;
+
+export type ReasoningEffort = (typeof REASONING_EFFORTS)[number];
+
+/**
+ * Default effort applied when nothing more specific is configured (no per-model
+ * effort and no per-tab selection). Resolution order is
+ * per-model → per-tab → this default.
+ */
+export const DEFAULT_REASONING_EFFORT: ReasoningEffort = "high";
+
+/** Human-readable labels for each effort level (UI display). */
+export const REASONING_EFFORT_LABELS: Record<ReasoningEffort, string> = {
+ none: "Off",
+ low: "Low",
+ medium: "Medium",
+ high: "High",
+ xhigh: "X-High",
+ max: "Max",
+};
+
+/** Runtime type guard for narrowing an arbitrary value to a `ReasoningEffort`. */
+export function isReasoningEffort(value: unknown): value is ReasoningEffort {
+ return typeof value === "string" && (REASONING_EFFORTS as readonly string[]).includes(value);
+}
export interface AgentConfig {
model: string;
@@ -416,6 +452,12 @@ export interface QueueCallbacks {
export interface AgentModelEntry {
key_id: string;
model_id: string;
+ /**
+ * Per-model/key reasoning effort. When set, overrides the per-tab effort
+ * selector for generations that use this entry (resolution order:
+ * per-model → per-tab → DEFAULT_REASONING_EFFORT). Omitted when unset.
+ */
+ effort?: ReasoningEffort;
}
export interface AgentDefinition {