mirror of
https://github.com/rowboatlabs/rowboat.git
synced 2026-07-24 21:41:08 +02:00
Adds a canonical reasoning-effort ladder (low|medium|high, absent = auto/provider default) that travels: send config → turn_created.config → every model call's persisted parameters (§8.3) → provider-specific options at invoke time. Behavior-neutral: nothing sends an effort yet, and auto produces byte-identical requests to today. - shared: ReasoningEffort enum (single source, reused by models.json provider config), TurnCreated.config.reasoningEffort (additive optional on a non-strict object — old builds strip it, no schemaVersion bump), sessions:sendMessage IPC schema. - turns: CreateTurnInput/SendMessageConfig/HeadlessAgentOptions carry the value; runModelStep stamps it on each call's parameters so every step durably records what it ran with. - bridge: maps canonical effort to provider options, transport-only like prompt caching — OpenAI reasoningEffort, Anthropic thinking budgets (raising maxOutputTokens to the budget floor, never lowering an explicit value), Gemini thinkingLevel/thinkingBudget by generation, OpenRouter/rowboat reasoning.effort. Capability-gated via a cache-only models.dev lookup (never blocks a turn on the network); unknown support fails closed on strict flavors, while OpenRouter-shaped flavors map permissively since OpenRouter drops the field for non-reasoning models. Explicit persisted providerOptions win over the mapping. Ollama keeps its existing provider-level think rewrite; openai-compatible endpoints get nothing (no safe universal parameter). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
74 lines
3.7 KiB
TypeScript
74 lines
3.7 KiB
TypeScript
import { z } from "zod";
|
|
|
|
// Canonical reasoning-effort ladder, used everywhere effort appears: the
|
|
// per-provider default in models.json, the per-turn override on turn
|
|
// creation, and the persisted per-call parameters. Absence means "auto" —
|
|
// send nothing and let the provider default apply. Provider-specific
|
|
// syntax (OpenAI reasoningEffort, Anthropic thinking budgets, Gemini
|
|
// thinkingLevel, OpenRouter reasoning.effort) is mapped at invoke time.
|
|
export const ReasoningEffort = z.enum(["low", "medium", "high"]);
|
|
|
|
export const LlmProvider = z.object({
|
|
flavor: z.enum(["openai", "anthropic", "google", "openrouter", "aigateway", "ollama", "openai-compatible", "rowboat"]),
|
|
apiKey: z.string().optional(),
|
|
baseURL: z.string().optional(),
|
|
headers: z.record(z.string(), z.string()).optional(),
|
|
// Context window (in tokens) to request from local runtimes. Ollama defaults
|
|
// to a ~4k window that silently truncates Rowboat's prompts; when unset,
|
|
// local providers get a larger default (see core/models/local.ts).
|
|
contextLength: z.number().int().positive().optional(),
|
|
// Default reasoning effort for this provider. For Ollama this drives the
|
|
// `think` parameter (gpt-oss takes the levels directly; other thinking
|
|
// models map low → off, high → on; defaults to "low" — background agents
|
|
// and chat both want snappy responses on local hardware). For cloud
|
|
// providers it seeds the per-turn effort when the user hasn't chosen one.
|
|
reasoningEffort: ReasoningEffort.optional(),
|
|
});
|
|
|
|
// A provider-qualified model reference. `provider` is a provider name as
|
|
// understood by resolveProviderConfig — a BYOK flavor ("ollama", "openai",
|
|
// …) or "rowboat" for the signed-in gateway.
|
|
export const ModelRef = z.object({
|
|
provider: z.string(),
|
|
model: z.string(),
|
|
});
|
|
|
|
// Category overrides accept either a bare model id (legacy: paired with the
|
|
// active default provider) or a provider-qualified ref (hybrid mode: e.g.
|
|
// gateway assistant + local Ollama background agents).
|
|
export const ModelOverride = z.union([z.string(), ModelRef]);
|
|
|
|
export const LlmModelConfig = z.object({
|
|
provider: LlmProvider,
|
|
model: z.string(),
|
|
models: z.array(z.string()).optional(),
|
|
// The user's explicit default assistant model. When set it wins over both
|
|
// the signed-in curated default and the legacy top-level provider/model
|
|
// pair — this is what lets signed-in users default to a BYOK model.
|
|
defaultSelection: ModelRef.optional(),
|
|
// When true, background agent runs (knowledge pipeline, live notes,
|
|
// background tasks) wait until no chat turn is running before starting.
|
|
// Surfaced as a settings checkbox; recommended for local models, where a
|
|
// background run competes with the chat for the same hardware.
|
|
deferBackgroundTasks: z.boolean().optional(),
|
|
providers: z.record(z.string(), z.object({
|
|
apiKey: z.string().optional(),
|
|
baseURL: z.string().optional(),
|
|
headers: z.record(z.string(), z.string()).optional(),
|
|
contextLength: z.number().int().positive().optional(),
|
|
reasoningEffort: ReasoningEffort.optional(),
|
|
model: z.string().optional(),
|
|
models: z.array(z.string()).optional(),
|
|
knowledgeGraphModel: z.string().optional(),
|
|
meetingNotesModel: z.string().optional(),
|
|
liveNoteAgentModel: z.string().optional(),
|
|
autoPermissionDecisionModel: z.string().optional(),
|
|
})).optional(),
|
|
// Per-category model overrides. Honored in both modes: when unset,
|
|
// signed-in users get the curated gateway defaults and BYOK users get the
|
|
// assistant model. Read by helpers in core/models/defaults.ts.
|
|
knowledgeGraphModel: ModelOverride.optional(),
|
|
meetingNotesModel: ModelOverride.optional(),
|
|
liveNoteAgentModel: ModelOverride.optional(),
|
|
autoPermissionDecisionModel: ModelOverride.optional(),
|
|
});
|