Files
readest/apps/readest-app/src/services/reedy/models/ChatModel.ts
T
Huang Xin 4d96c0d54a feat(reedy): Appendix A · Phase 2.1–2.3 — agent runtime foundation (#4298)
* feat(reedy): Appendix A · Phase 2.1 — ChatModel + model registry

First piece of the deferred agent runtime, building alongside the shipped
Phase 1 MVP per the locked decision (no rip-and-replace; legacy + Reedy
MVP + agent paths coexist).

ChatModel is a thin interface over Vercel SDK's LanguageModel that
surfaces contextWindow / reservedOutput / supportsTools — the metadata
the M2.5 PromptContextBuilder and M2.6 AgentRuntime need for prompt
budgeting and tool-calling decisions.

createReedyModels(settings) returns the active (chat, embedding) pair from
the user's currently configured AIProvider, rewrapped in the Reedy
interfaces without touching the legacy AIProvider. CONTEXT_WINDOW_TABLE
hardcodes per-model metadata (Gemini 2.5 → 2M, GPT-5/Llama-4 → 128K,
local Ollama llama → 4K with tools off, etc.) with a conservative 8K
fallback for unknown ids.

No runtime wired yet — Phase 2.6 consumes this.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(reedy): Appendix A · Phases 2.2–2.3 — runtime event/error taxonomy + ToolRegistry

Phase 2.2 — pure types and factories for everything AgentRuntime.runTurn()
will yield:

  turn_start | text_delta | tool_call | tool_result(ok/err)
    | citation | memory_write | step_finish | usage | error | abort | done

ReedyError covers turn-level failures (context_overflow, turn_timeout,
model_error, provider_unavailable, stream_parse, abort, ...) with default
retryability per kind so the M2.6 streamLoop's retry-with-backoff can
decide without string-matching. ReedyToolError covers tool-execution
failures with a narrower kind union so the streamLoop can re-emit them as
{ tool_result, ok: false, error } events without losing structure.

Phase 2.3 — ToolRegistry that holds the runtime's tool catalog and adapts
each tool to the Vercel ai-SDK ToolSet streamText consumes. Every
registered ReedyTool gets wrapped with:

  - Zod input validation     → tool_invalid_args on mismatch
  - Permission gate          → tool_permission_denied (read auto-approves)
  - Per-call wall-clock      → tool_timeout (default 10s)
  - AbortSignal propagation  → tool_aborted on turn cancel
  - Per-tool serialization   → parallelSafe=false tools queue

The local AbortController per call composes the turn-level signal with
the per-tool timeout so we classify timeout-vs-abort correctly based on
which controller fired first, and listener cleanup avoids leaks.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 02:30:34 +02:00

44 lines
1.6 KiB
TypeScript

import type { LanguageModel } from 'ai';
/**
* Chat-model surface the agent runtime talks to.
*
* Appendix A · Phase 2.1 — a thin wrapper around the Vercel `ai` SDK's
* `LanguageModel` that adds the metadata the runtime needs for prompt
* budgeting and tool-calling decisions. The actual streamText/generateText
* invocation still happens against `getLanguageModel()`, so we don't
* re-implement provider transports here.
*
* Lives alongside the MVP (per the "Build Phases 2-5 alongside MVP" decision):
* the legacy `AIProvider` interface in `src/services/ai/types.ts` is unchanged
* and continues to power Phase 1B's TauriChatAdapter.
*/
export interface ChatModel {
/** Stable identifier — matches the model field in AISettings. */
readonly id: string;
/**
* Maximum input + output tokens the model accepts. Used by the M2.5
* PromptContextBuilder to decide what to shrink. Hardcoded per-model in
* registry.ts; ChatModel callers should never have to probe.
*/
readonly contextWindow: number;
/**
* Tokens reserved for completion. The PromptContextBuilder budgets to
* `contextWindow - reservedOutput - safetyMargin`. Defaults to 1024 for
* most models; reasoning models reserve more.
*/
readonly reservedOutput: number;
/**
* Whether the model supports Vercel-SDK tool calling. The runtime falls
* back to system-prompt-injection mode when false, which matches the
* MVP's legacy IDB path.
*/
readonly supportsTools: boolean;
/** Underlying Vercel SDK model; pass to streamText / generateText. */
getLanguageModel(): LanguageModel;
}