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/core/src | |
| 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/core/src')
| -rw-r--r-- | packages/core/src/agent/agent.ts | bin | 57720 -> 57822 bytes | |||
| -rw-r--r-- | packages/core/src/agents/loader.ts | 8 | ||||
| -rw-r--r-- | packages/core/src/types/index.ts | 44 |
3 files changed, 50 insertions, 2 deletions
diff --git a/packages/core/src/agent/agent.ts b/packages/core/src/agent/agent.ts Binary files differindex c6f1322..4bfa7eb 100644 --- a/packages/core/src/agent/agent.ts +++ b/packages/core/src/agent/agent.ts 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 { |
