torus-ai 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/AGENT.md ADDED
@@ -0,0 +1,47 @@
1
+ # AGENT.md — Torus (Layer 0: identity + map)
2
+
3
+ You are operating inside **Torus**, a minimal Agent SDK whose architecture
4
+ *is* the ICM folder structure. It is inspired by the Claude Agent SDK (agent loop,
5
+ tools, in-process MCP, subagents, permissions) but follows the Interpretable Context
6
+ Methodology: **folder structure as agent architecture, markdown contracts as code.**
7
+
8
+ ## Where am I?
9
+
10
+ ```
11
+ torus-ai/
12
+ ├── AGENT.md # Layer 0 — this file: identity + map
13
+ ├── CONTEXT.md # Layer 1 — routing: which module/stage handles what
14
+ ├── src/ # the runtime (the engine that reads the folders)
15
+ │ ├── types.ts # plain-data wire types (the cross-layer interface)
16
+ │ ├── loop.ts # ★ the core agentic loop
17
+ │ ├── tools.ts # tool() + createSdkMcpServer() + ToolRegistry (namespacing)
18
+ │ ├── builtins.ts # read_file / write_file / list_dir
19
+ │ ├── permissions.ts # allowlist + canUseTool gate (deny → allow → callback)
20
+ │ ├── subagents.ts # parse markdown stage contracts (Layer 2)
21
+ │ ├── context.ts # layered context loader (Layers 0–4, scoped)
22
+ │ ├── pipeline.ts # sequential stage runner with review gates
23
+ │ ├── index.ts # public API: query(), runPipeline(), tool(), ...
24
+ │ └── providers/ # MockProvider (offline) + AnthropicProvider (real)
25
+ └── examples/
26
+ └── blog-pipeline/ # an actual ICM workspace this SDK runs (the "product")
27
+ ```
28
+
29
+ ## ICM ↔ Agent-SDK concept map
30
+
31
+ | ICM layer / idea | This SDK |
32
+ |-----------------------------|-----------------------------------------------------|
33
+ | Layer 0 `AGENT.md` | workspace identity, loaded into every stage system |
34
+ | Layer 1 `CONTEXT.md` | routing doc, loaded into every stage system |
35
+ | Layer 2 `stages/NN/CONTEXT.md` | a **subagent**: Inputs / Process / Outputs / Tools |
36
+ | Layer 3 references | constraints (voice, conventions) — `_config/`, `references/` |
37
+ | Layer 4 `output/` | the handoff: stage NN's output is NN+1's input |
38
+ | Review gate | `reviewGate` callback between stages |
39
+ | "One stage, one job" | one contract → one `runLoop` pass → one artifact |
40
+ | Scoped Inputs | `loadStageContext` loads only the named files |
41
+
42
+ ## Operating rules
43
+
44
+ - Each stage loads **only** the files its contract names (ICM principle 3).
45
+ - A stage's `## Tools` list is the source of truth for what tools it may call.
46
+ - The runtime persists each stage's deliverable to `output/`; mechanical work
47
+ stays out of the model's job (ICM principle: "configure the factory, not the product").
package/CONTEXT.md ADDED
@@ -0,0 +1,26 @@
1
+ # CONTEXT.md — routing (Layer 1)
2
+
3
+ Which part of the system handles what. Start here, then jump to the named file.
4
+
5
+ ## "I want to understand the engine"
6
+ - The loop itself → `src/loop.ts` (gather → call model → run tools → repeat)
7
+ - How tools are defined and namespaced → `src/tools.ts`
8
+ - How permissions gate a tool call → `src/permissions.ts`
9
+ - How a model backend plugs in → `src/types.ts` (`ModelProvider`) + `src/providers/`
10
+
11
+ ## "I want to understand the ICM wiring"
12
+ - How a markdown stage contract is parsed → `src/subagents.ts`
13
+ - How layered context is assembled per stage → `src/context.ts`
14
+ - How stages run in order with review gates → `src/pipeline.ts`
15
+
16
+ ## "I want to use the SDK"
17
+ - Single agent run → `query()` in `src/index.ts`
18
+ - Custom tool / in-process MCP → `tool()` + `createSdkMcpServer()` in `src/tools.ts`
19
+ - Run a folder pipeline → `runPipeline()` in `src/pipeline.ts`
20
+ - Working example → `examples/blog-pipeline/` (run with `npm run demo`)
21
+
22
+ ## "I want to add a stage"
23
+ 1. Create `examples/<ws>/stages/NN_verb/CONTEXT.md` using the contract shape
24
+ (`## Inputs` / `## Process` / `## Outputs`, optional `## Tools`).
25
+ 2. Name exact input files and tag each Layer 3 (reference) or Layer 4 (working).
26
+ 3. Re-run the pipeline; the runner discovers numbered stage folders automatically.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 aenfr
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,143 @@
1
+ # Torus
2
+
3
+ > npm: `torus-ai` · repo: [aenfr/torus-ai](https://github.com/aenfr/torus-ai)
4
+
5
+ [![CI](https://github.com/aenfr/torus-ai/actions/workflows/ci.yml/badge.svg)](https://github.com/aenfr/torus-ai/actions/workflows/ci.yml)
6
+
7
+ A minimal **Agent SDK** whose architecture *is* the [ICM](./AGENT.md) folder
8
+ structure. Inspired by the Claude Agent SDK — same core ideas (agent loop, tools,
9
+ in-process MCP, subagents, permissions, streaming) — but agents and pipelines are
10
+ defined as **markdown contracts in folders**, not framework code.
11
+
12
+ > Built on the Interpretable Context Methodology (ICM): *folder structure as agent
13
+ > architecture, plain text as the interface, layered context loading.*
14
+
15
+ ## Quick start
16
+
17
+ Requires **Node ≥ 22.6** (runs TypeScript natively — no build step).
18
+
19
+ ```bash
20
+ node examples/blog-pipeline/run.ts # or: npm run demo
21
+ ```
22
+
23
+ This runs a 3-stage pipeline (`research → draft → polish`) with the offline
24
+ `MockProvider` — no API key needed. Each stage writes an artifact to its `output/`
25
+ folder; open them to inspect the handoff.
26
+
27
+ ## What's inside
28
+
29
+ | Concept (Claude Agent SDK) | Here |
30
+ |---|---|
31
+ | The agentic loop | [`src/loop.ts`](./src/loop.ts) — gather → call model → run tools → repeat |
32
+ | `tool()` / `createSdkMcpServer()` | [`src/tools.ts`](./src/tools.ts) — in-process MCP, `mcp__<server>__<tool>` namespacing |
33
+ | Built-in tools | [`src/builtins.ts`](./src/builtins.ts) — `read_file` / `write_file` / `list_dir` |
34
+ | Permissions / `canUseTool` | [`src/permissions.ts`](./src/permissions.ts) — allowlist + wildcard + callback gate |
35
+ | Subagents | [`src/subagents.ts`](./src/subagents.ts) — markdown stage contracts (Layer 2) |
36
+ | Context management | [`src/context.ts`](./src/context.ts) — layered, scoped loading (Layers 0–4) |
37
+ | `query()` streaming | [`src/index.ts`](./src/index.ts) — single-shot run yielding events |
38
+ | Pipeline orchestration | [`src/pipeline.ts`](./src/pipeline.ts) — sequential stages + review gates |
39
+ | Model backends | [`src/providers/`](./src/providers/) — `MockProvider`, `AnthropicProvider` |
40
+
41
+ ## Three ways to use it
42
+
43
+ **1. Single agent run** (`query`) — mirrors the Claude Agent SDK streaming shape:
44
+
45
+ ```ts
46
+ import { query, MockProvider, tool, createSdkMcpServer } from "./src/index.ts";
47
+
48
+ const time = tool("now", "Current ISO time", { type: "object", properties: {} },
49
+ () => ({ content: new Date().toISOString() }));
50
+ const clock = createSdkMcpServer({ name: "clock", tools: [time] });
51
+
52
+ for await (const ev of query("What time is it?", {
53
+ provider: new MockProvider(),
54
+ mcpServers: [clock],
55
+ permissions: { allowedTools: ["mcp__clock__*"] },
56
+ })) {
57
+ if (ev.type === "result") console.log(ev.finalText);
58
+ }
59
+ ```
60
+
61
+ **2. Folder pipeline** (`runPipeline`) — the ICM workflow. Drop numbered stage
62
+ folders with `CONTEXT.md` contracts under `stages/`, then run. See
63
+ [`examples/blog-pipeline`](./examples/blog-pipeline).
64
+
65
+ **3. Real model** — swap the provider and install the optional dep:
66
+
67
+ ```bash
68
+ npm i @anthropic-ai/sdk # Claude
69
+ export ANTHROPIC_API_KEY=sk-ant-...
70
+ # or
71
+ npm i @google/genai # Gemini
72
+ export GOOGLE_API_KEY=...
73
+ ```
74
+ ```ts
75
+ import { AnthropicProvider, GeminiProvider } from "torus-ai";
76
+ const claude = new AnthropicProvider({ model: "claude-sonnet-4-6" });
77
+ const gemini = new GeminiProvider({ model: "gemini-2.5-flash" });
78
+ ```
79
+
80
+ ## Providers & cost routing
81
+
82
+ Two pluggable providers implement the same `ModelProvider` interface, so they
83
+ drop into `query()`, `runPipeline()`, or `runLoop()` interchangeably:
84
+
85
+ | Provider | Package | Env | Default |
86
+ |---|---|---|---|
87
+ | `AnthropicProvider` | `@anthropic-ai/sdk` | `ANTHROPIC_API_KEY` | `claude-sonnet-4-6` |
88
+ | `GeminiProvider` | `@google/genai` | `GOOGLE_API_KEY` | `gemini-2.5-flash` |
89
+
90
+ Both support **intelligent cost routing** — set `route: true` and each request is
91
+ classified (fast keyword/length heuristics first, then a structured-output "judge"
92
+ call on the *cheap* model) and sent to the cheap or expensive model accordingly.
93
+ The classifier never throws: on any failure it falls back to the expensive model.
94
+
95
+ ```ts
96
+ const provider = new GeminiProvider({ route: true });
97
+ // cheap: gemini-2.5-flash-lite expensive: gemini-2.5-pro
98
+ // (AnthropicProvider({ route: true }) → claude-haiku-4-5 vs claude-sonnet-4-6)
99
+
100
+ import { getRoutingStats } from "torus-ai";
101
+ console.log(getRoutingStats()); // { cheap, expensive, cheapPct, expensivePct, total }
102
+ ```
103
+
104
+ Model constants (`CHEAP_MODEL`, `EXPENSIVE_MODEL`, `GEMINI_CHEAP_MODEL`,
105
+ `GEMINI_EXPENSIVE_MODEL`) and the low-level `selectModel` / `selectGeminiModel`
106
+ are exported if you want to route outside the providers.
107
+
108
+ ## The stage contract (Layer 2)
109
+
110
+ Each `stages/NN_verb/CONTEXT.md` is both the agent's instructions and human docs:
111
+
112
+ ```markdown
113
+ # Stage 02 — draft
114
+
115
+ ## Inputs
116
+ - Layer 4 (working): ../01_research/output/research-output.md
117
+ - Layer 3 (reference): ../../_config/voice.md
118
+
119
+ ## Process
120
+ Turn the research brief into a ~300-word first draft in the house voice.
121
+
122
+ ## Outputs
123
+ - draft.md -> output/
124
+
125
+ ## Tools # optional — the stage's tool allowlist (source of truth)
126
+ - mcp__research__lookup
127
+ ```
128
+
129
+ The runner reads `## Inputs` to scope context (loads *only* those files), `## Tools`
130
+ to gate what the loop may call, and `## Outputs` to name the artifact it persists.
131
+
132
+ ## Design notes
133
+
134
+ - **The contract is the control point.** A stage loads only the files it names
135
+ (ICM principle 3) and may call only the tools it lists.
136
+ - **`output/` is the handoff.** Stage `NN`'s output is stage `NN+1`'s input — the
137
+ coordination "logic" is one folder feeding the next.
138
+ - **Observable by default.** No logging layer — open the folders. Re-run one stage
139
+ by itself; its `## Inputs` table *is* its dependency declaration.
140
+ - **Provider-agnostic.** The loop only needs `ModelProvider.generate()`. Mock for
141
+ tests/offline, Anthropic for real, anything else you implement.
142
+
143
+ MIT-licensed, like the ICM protocol it's built on.
@@ -0,0 +1,373 @@
1
+ type Role = "user" | "assistant";
2
+ interface TextBlock {
3
+ type: "text";
4
+ text: string;
5
+ }
6
+ interface ToolUseBlock {
7
+ type: "tool_use";
8
+ id: string;
9
+ name: string;
10
+ input: Record<string, unknown>;
11
+ }
12
+ interface ToolResultBlock {
13
+ type: "tool_result";
14
+ toolUseId: string;
15
+ content: string;
16
+ isError?: boolean;
17
+ }
18
+ type ContentBlock = TextBlock | ToolUseBlock | ToolResultBlock;
19
+ interface Message {
20
+ role: Role;
21
+ content: ContentBlock[];
22
+ }
23
+ type StopReason = "end_turn" | "tool_use" | "max_turns";
24
+ interface ModelResponse {
25
+ content: ContentBlock[];
26
+ stopReason: StopReason;
27
+ }
28
+ type JSONSchema = Record<string, unknown>;
29
+ interface ToolSchema {
30
+ name: string;
31
+ description: string;
32
+ inputSchema: JSONSchema;
33
+ }
34
+ interface ModelRequest {
35
+ system: string;
36
+ messages: Message[];
37
+ tools: ToolSchema[];
38
+ }
39
+ /** The one capability the SDK needs from any model backend. Swap freely. */
40
+ interface ModelProvider {
41
+ readonly name: string;
42
+ generate(req: ModelRequest): Promise<ModelResponse>;
43
+ }
44
+ interface ToolResultPayload {
45
+ content: string;
46
+ isError?: boolean;
47
+ }
48
+ interface ToolContext {
49
+ workspaceDir: string;
50
+ stageDir?: string;
51
+ signal?: AbortSignal;
52
+ }
53
+ interface ToolDefinition {
54
+ name: string;
55
+ description: string;
56
+ inputSchema: JSONSchema;
57
+ handler: (input: any, ctx: ToolContext) => Promise<ToolResultPayload> | ToolResultPayload;
58
+ }
59
+ /** An in-process MCP server: tools that run in this same process, no subprocess. */
60
+ interface SdkMcpServer {
61
+ kind: "sdk-mcp";
62
+ name: string;
63
+ version: string;
64
+ tools: ToolDefinition[];
65
+ }
66
+ type PermissionDecision = {
67
+ behavior: "allow";
68
+ updatedInput?: Record<string, unknown>;
69
+ } | {
70
+ behavior: "deny";
71
+ message: string;
72
+ };
73
+ type CanUseTool = (toolName: string, input: Record<string, unknown>) => Promise<PermissionDecision> | PermissionDecision;
74
+ type AgentEvent = {
75
+ type: "assistant_text";
76
+ text: string;
77
+ stage?: string;
78
+ } | {
79
+ type: "tool_use";
80
+ name: string;
81
+ input: Record<string, unknown>;
82
+ stage?: string;
83
+ } | {
84
+ type: "tool_result";
85
+ name: string;
86
+ content: string;
87
+ isError: boolean;
88
+ stage?: string;
89
+ } | {
90
+ type: "permission_denied";
91
+ name: string;
92
+ message: string;
93
+ stage?: string;
94
+ } | {
95
+ type: "stage_start";
96
+ stage: string;
97
+ } | {
98
+ type: "stage_output";
99
+ stage: string;
100
+ artifact: string;
101
+ path: string;
102
+ } | {
103
+ type: "context_loaded";
104
+ stage?: string;
105
+ tokensEstimated: number;
106
+ files: string[];
107
+ } | {
108
+ type: "result";
109
+ finalText: string;
110
+ turns: number;
111
+ stage?: string;
112
+ };
113
+
114
+ /**
115
+ * Define a custom tool. Mirrors `tool()` from the Claude Agent SDK.
116
+ * tool("get_temp", "Get temperature", { type:"object", ... }, async (input, ctx) => ...)
117
+ */
118
+ declare function tool(name: string, description: string, inputSchema: JSONSchema, handler: (input: any, ctx: ToolContext) => Promise<ToolResultPayload> | ToolResultPayload): ToolDefinition;
119
+ /**
120
+ * Bundle tools into an in-process MCP server. Mirrors `createSdkMcpServer()`.
121
+ * Tools become namespaced `mcp__<name>__<tool>` when registered.
122
+ */
123
+ declare function createSdkMcpServer(opts: {
124
+ name: string;
125
+ version?: string;
126
+ tools: ToolDefinition[];
127
+ }): SdkMcpServer;
128
+ interface RegisteredTool {
129
+ fullName: string;
130
+ def: ToolDefinition;
131
+ }
132
+ /** Holds the model-facing tool catalog and executes calls by namespaced name. */
133
+ declare class ToolRegistry {
134
+ private map;
135
+ /** Built-ins register under their bare name (no namespace). */
136
+ addBuiltins(defs: ToolDefinition[]): this;
137
+ /** SDK MCP server tools register as mcp__<server>__<tool>. */
138
+ addServer(server: SdkMcpServer): this;
139
+ has(fullName: string): boolean;
140
+ list(): RegisteredTool[];
141
+ /** Tool schemas to hand the model, optionally filtered to a stage's allowlist. */
142
+ schemas(filter?: (fullName: string) => boolean): ToolSchema[];
143
+ execute(fullName: string, input: Record<string, unknown>, ctx: ToolContext): Promise<ToolResultPayload>;
144
+ }
145
+
146
+ /** Match a tool name against patterns supporting a trailing "*" wildcard. */
147
+ declare function matchesAllow(name: string, patterns: string[]): boolean;
148
+ interface PermissionConfig {
149
+ /** Allowlist (wildcards ok). If omitted, all tools allowed unless canUseTool vetoes. */
150
+ allowedTools?: string[];
151
+ /** Explicit denials, evaluated first. */
152
+ disallowedTools?: string[];
153
+ /** Final custom gate — can allow non-allowlisted tools, veto allowlisted ones, or rewrite input. */
154
+ canUseTool?: CanUseTool;
155
+ }
156
+ /**
157
+ * Evaluation order (mirrors the Agent SDK):
158
+ * 1. disallowedTools → deny
159
+ * 2. allowedTools → allow (if no canUseTool)
160
+ * 3. canUseTool → final say
161
+ * 4. default → allow when no allowlist, deny when allowlist set and unmatched
162
+ */
163
+ declare class PermissionEngine {
164
+ private cfg;
165
+ constructor(cfg?: PermissionConfig);
166
+ check(name: string, input: Record<string, unknown>): Promise<PermissionDecision>;
167
+ }
168
+
169
+ interface LoopOptions {
170
+ provider: ModelProvider;
171
+ registry: ToolRegistry;
172
+ permissions: PermissionEngine;
173
+ system: string;
174
+ messages: Message[];
175
+ toolContext: ToolContext;
176
+ toolFilter?: (fullName: string) => boolean;
177
+ maxTurns?: number;
178
+ stage?: string;
179
+ }
180
+ interface LoopResult {
181
+ finalText: string;
182
+ turns: number;
183
+ messages: Message[];
184
+ }
185
+ declare function runLoop(opts: LoopOptions): AsyncGenerator<AgentEvent, LoopResult>;
186
+
187
+ interface StageInput {
188
+ layer: 3 | 4;
189
+ path: string;
190
+ note?: string;
191
+ }
192
+ interface StageContract {
193
+ name: string;
194
+ order: number;
195
+ stageDir: string;
196
+ contractPath: string;
197
+ inputs: StageInput[];
198
+ process: string;
199
+ outputs: string[];
200
+ tools: string[];
201
+ }
202
+ declare function parseContract(name: string, stageDir: string, contractPath: string, body: string): StageContract;
203
+ /** Discover and parse every numbered stage folder, in execution order. */
204
+ declare function loadStages(workspaceDir: string): Promise<StageContract[]>;
205
+
206
+ interface PipelineOptions {
207
+ workspaceDir: string;
208
+ provider: ModelProvider;
209
+ mcpServers?: SdkMcpServer[];
210
+ /** Global permission overlay. A stage's own "## Tools" list is the primary allowlist. */
211
+ permissions?: Pick<PermissionConfig, "canUseTool" | "disallowedTools">;
212
+ /** Called after each stage writes output. Return false to halt the pipeline. */
213
+ reviewGate?: (stage: StageContract, outputs: {
214
+ artifact: string;
215
+ path: string;
216
+ text: string;
217
+ }[]) => Promise<boolean> | boolean;
218
+ maxTurnsPerStage?: number;
219
+ contextBudgetTokens?: number;
220
+ }
221
+ declare function runPipeline(opts: PipelineOptions): AsyncGenerator<AgentEvent, void>;
222
+
223
+ declare const readFileTool: ToolDefinition;
224
+ declare const writeFileTool: ToolDefinition;
225
+ declare const listDirTool: ToolDefinition;
226
+ declare const builtinTools: ToolDefinition[];
227
+
228
+ interface LoadedContext {
229
+ system: string;
230
+ files: string[];
231
+ tokensEstimated: number;
232
+ }
233
+ /**
234
+ * Build a stage's system prompt from the ICM layer hierarchy:
235
+ * Layer 0 AGENT.md (identity + map)
236
+ * Layer 1 CONTEXT.md (routing)
237
+ * Layer 2 stage CONTEXT.md (this stage's contract)
238
+ * Layer 3 scoped references (constraints — only files the contract names)
239
+ * Layer 4 scoped working (prior stage output — only files the contract names)
240
+ */
241
+ declare function loadStageContext(workspaceDir: string, contract: StageContract): Promise<LoadedContext>;
242
+
243
+ interface MockOptions {
244
+ /** Label stamped into outputs so mock-generated content is unmistakable. */
245
+ label?: string;
246
+ }
247
+ /**
248
+ * A deterministic, offline provider that exercises the full agent loop with no API
249
+ * key. Strategy: if tools are offered and none have been used yet, call the first
250
+ * tool once; otherwise synthesize a final answer from the system context + any tool
251
+ * results. It is intentionally dumb — its job is to prove the harness wiring, not to
252
+ * write good prose. Swap in AnthropicProvider for real output.
253
+ */
254
+ declare class MockProvider implements ModelProvider {
255
+ readonly name = "mock";
256
+ private opts;
257
+ constructor(opts?: MockOptions);
258
+ generate(req: ModelRequest): Promise<ModelResponse>;
259
+ private sampleInput;
260
+ private synthesize;
261
+ }
262
+
263
+ interface AnthropicOptions {
264
+ model?: string;
265
+ apiKey?: string;
266
+ maxTokens?: number;
267
+ /**
268
+ * When true, the model is chosen per-request by the cost router (cheap vs
269
+ * expensive) based on query complexity, instead of using a fixed `model`.
270
+ */
271
+ route?: boolean;
272
+ }
273
+ /**
274
+ * Real provider backed by the Anthropic Messages API. Requires the optional
275
+ * `@anthropic-ai/sdk` dependency and an ANTHROPIC_API_KEY. The SDK is imported
276
+ * lazily so the package (and the mock demo) work without it installed.
277
+ */
278
+ declare class AnthropicProvider implements ModelProvider {
279
+ readonly name = "anthropic";
280
+ private client;
281
+ private model;
282
+ private maxTokens;
283
+ private apiKey?;
284
+ private route;
285
+ constructor(opts?: AnthropicOptions);
286
+ private ensureClient;
287
+ generate(req: ModelRequest): Promise<ModelResponse>;
288
+ }
289
+
290
+ interface GeminiOptions {
291
+ model?: string;
292
+ apiKey?: string;
293
+ /**
294
+ * When true, the model is chosen per-request by the cost router (cheap vs
295
+ * expensive Gemini) based on query complexity, instead of a fixed `model`.
296
+ */
297
+ route?: boolean;
298
+ }
299
+ /**
300
+ * Provider backed by the Google Gemini API (@google/genai). Requires the
301
+ * optional `@google/genai` dependency and a GOOGLE_API_KEY (or GEMINI_API_KEY).
302
+ * The SDK is imported lazily so the package works without it installed.
303
+ */
304
+ declare class GeminiProvider implements ModelProvider {
305
+ readonly name = "gemini";
306
+ private client;
307
+ private model;
308
+ private apiKey?;
309
+ private route;
310
+ constructor(opts?: GeminiOptions);
311
+ private ensureClient;
312
+ generate(req: ModelRequest): Promise<ModelResponse>;
313
+ }
314
+
315
+ declare const CHEAP_MODEL = "claude-haiku-4-5";
316
+ declare const EXPENSIVE_MODEL = "claude-sonnet-4-6";
317
+ declare const GEMINI_CHEAP_MODEL = "gemini-2.5-flash-lite";
318
+ declare const GEMINI_EXPENSIVE_MODEL = "gemini-2.5-pro";
319
+ type Complexity = "SIMPLE" | "COMPLEX";
320
+ interface RouterOptions {
321
+ /** Reuse an existing provider SDK client (avoids a second client init). */
322
+ client?: any;
323
+ /** API key for a lazily-created client (defaults to the provider's env var). */
324
+ apiKey?: string;
325
+ /** Model used as the complexity judge. Defaults to the provider's cheap model. */
326
+ judgeModel?: string;
327
+ }
328
+ /**
329
+ * Cheap, deterministic pre-classification. Returns a verdict only when it's
330
+ * confident; otherwise null (defer to the judge).
331
+ */
332
+ declare function fastHeuristic(prompt: string): Complexity | null;
333
+ /** Grade complexity with Claude (structured output). May throw. */
334
+ declare function judgeComplexity(prompt: string, opts?: RouterOptions): Promise<Complexity>;
335
+ /** Grade complexity with Gemini (JSON structured output). May throw. */
336
+ declare function judgeComplexityGemini(prompt: string, opts?: RouterOptions): Promise<Complexity>;
337
+ /** Heuristics first, Claude judge second. May throw. */
338
+ declare function classifyComplexity(prompt: string, opts?: RouterOptions): Promise<Complexity>;
339
+ /** Heuristics first, Gemini judge second. May throw. */
340
+ declare function classifyComplexityGemini(prompt: string, opts?: RouterOptions): Promise<Complexity>;
341
+ /** Pick a Claude model for a prompt. Never throws (falls back to expensive). */
342
+ declare function selectModel(prompt: string, opts?: RouterOptions): Promise<string>;
343
+ /** Pick a Gemini model for a prompt. Never throws (falls back to expensive). */
344
+ declare function selectGeminiModel(prompt: string, opts?: RouterOptions): Promise<string>;
345
+ interface RoutingStats {
346
+ cheap: number;
347
+ expensive: number;
348
+ total: number;
349
+ cheapPct: number;
350
+ expensivePct: number;
351
+ }
352
+ declare function getRoutingStats(): RoutingStats;
353
+ /** Extract the most recent user turn's text — what the router classifies on. */
354
+ declare function latestUserText(messages: Message[]): string;
355
+
356
+ interface QueryOptions {
357
+ provider: ModelProvider;
358
+ system?: string;
359
+ mcpServers?: SdkMcpServer[];
360
+ includeBuiltins?: boolean;
361
+ permissions?: PermissionConfig;
362
+ workspaceDir?: string;
363
+ maxTurns?: number;
364
+ }
365
+ /**
366
+ * Single-shot agent run (no pipeline). Mirrors the Claude Agent SDK's streaming
367
+ * `query()`: yields events as they happen and a final `result` event.
368
+ *
369
+ * for await (const ev of query("Summarize X", { provider, mcpServers: [srv] })) { ... }
370
+ */
371
+ declare function query(prompt: string, options: QueryOptions): AsyncGenerator<AgentEvent>;
372
+
373
+ export { type AgentEvent, type AnthropicOptions, AnthropicProvider, CHEAP_MODEL, type CanUseTool, type Complexity, type ContentBlock, EXPENSIVE_MODEL, GEMINI_CHEAP_MODEL, GEMINI_EXPENSIVE_MODEL, type GeminiOptions, GeminiProvider, type JSONSchema, type LoadedContext, type LoopOptions, type LoopResult, type Message, type MockOptions, MockProvider, type ModelProvider, type ModelRequest, type ModelResponse, type PermissionConfig, type PermissionDecision, PermissionEngine, type PipelineOptions, type QueryOptions, type RegisteredTool, type Role, type RouterOptions, type RoutingStats, type SdkMcpServer, type StageContract, type StageInput, type StopReason, type TextBlock, type ToolContext, type ToolDefinition, ToolRegistry, type ToolResultBlock, type ToolResultPayload, type ToolSchema, type ToolUseBlock, builtinTools, classifyComplexity, classifyComplexityGemini, createSdkMcpServer, fastHeuristic, getRoutingStats, judgeComplexity, judgeComplexityGemini, latestUserText, listDirTool, loadStageContext, loadStages, matchesAllow, parseContract, query, readFileTool, runLoop, runPipeline, selectGeminiModel, selectModel, tool, writeFileTool };