diff options
Diffstat (limited to 'src/features/chat/reasoning-effort.ts')
| -rw-r--r-- | src/features/chat/reasoning-effort.ts | 66 |
1 files changed, 66 insertions, 0 deletions
diff --git a/src/features/chat/reasoning-effort.ts b/src/features/chat/reasoning-effort.ts new file mode 100644 index 0000000..1eb77b6 --- /dev/null +++ b/src/features/chat/reasoning-effort.ts @@ -0,0 +1,66 @@ +import type { ReasoningEffort } from "@dispatch/transport-contract"; + +/** + * Pure helpers for the reasoning-effort selector (the thinking-depth knob). + * + * The canonical ladder + resolution chain are SERVER-owned (`[email protected]` + * `ReasoningEffort`; per-turn override → persisted conversation value → default + * `"high"`). These helpers only shape the persisted value for display: a `null` + * from `GET /conversations/:id/reasoning-effort` means "never set ⇒ the default + * applies", so the selector shows `high (default)` — never "off". Zero DOM, + * zero Svelte. + */ + +/** The canonical ladder, in ascending thinking-depth order (`[email protected]`). */ +export const REASONING_EFFORT_LEVELS: readonly ReasoningEffort[] = [ + "low", + "medium", + "high", + "xhigh", + "max", +]; + +/** The server's fallback when nothing is set (the resolution chain's tail). */ +export const DEFAULT_REASONING_EFFORT: ReasoningEffort = "high"; + +/** Narrow an untrusted string (e.g. a `<select>` value) to the ladder. */ +export function isReasoningEffort(value: string): value is ReasoningEffort { + return (REASONING_EFFORT_LEVELS as readonly string[]).includes(value); +} + +/** + * The level the selector should show as selected: the persisted value, or the + * server default when never set (`null` = "default applies", not "off"). + */ +export function effectiveEffort(persisted: ReasoningEffort | null): ReasoningEffort { + return persisted ?? DEFAULT_REASONING_EFFORT; +} + +/** One `<option>` of the selector. */ +export interface EffortOption { + readonly value: ReasoningEffort; + readonly label: string; +} + +/** + * The selector's options: every ladder level, with the server default marked + * `(default)` so a never-set conversation reads "high (default)". + */ +export function effortOptions(): readonly EffortOption[] { + return REASONING_EFFORT_LEVELS.map((level) => ({ + value: level, + label: level === DEFAULT_REASONING_EFFORT ? `${level} (default)` : level, + })); +} + +// ── Injected port (consumer-defines-port; the composition root adapts the +// store's `PUT /conversations/:id/reasoning-effort` to this shape). ──────── + +/** Outcome of `PUT /conversations/:id/reasoning-effort`. */ +export type ReasoningEffortSaveResult = + | { readonly ok: true; readonly reasoningEffort: ReasoningEffort } + | { readonly ok: false; readonly error: string }; + +export type SaveReasoningEffort = ( + level: ReasoningEffort, +) => Promise<ReasoningEffortSaveResult | null>; |
