zidane 4.1.8 → 4.1.9

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.
Files changed (59) hide show
  1. package/dist/{index-bgh-k8Mv.d.ts → agent-CMIhYhDz.d.ts} +2032 -1993
  2. package/dist/agent-CMIhYhDz.d.ts.map +1 -0
  3. package/dist/chat.d.ts +7 -6
  4. package/dist/chat.d.ts.map +1 -1
  5. package/dist/chat.js +2 -2
  6. package/dist/contexts.d.ts +1 -1
  7. package/dist/{index-BB4kuRh3.d.ts → index-CXVvqTQj.d.ts} +1 -1
  8. package/dist/{index-BB4kuRh3.d.ts.map → index-CXVvqTQj.d.ts.map} +1 -1
  9. package/dist/{index-Ds5YpvfZ.d.ts → index-D6Dd6Kc0.d.ts} +3 -3
  10. package/dist/{index-Ds5YpvfZ.d.ts.map → index-D6Dd6Kc0.d.ts.map} +1 -1
  11. package/dist/{index-DRoG_udt.d.ts → index-DAaKyadO.d.ts} +2 -2
  12. package/dist/{index-DRoG_udt.d.ts.map → index-DAaKyadO.d.ts.map} +1 -1
  13. package/dist/index.d.ts +4 -4
  14. package/dist/index.js +6 -6
  15. package/dist/{interpolate-CukJwP2G.js → interpolate-BydkV1eT.js} +3 -1
  16. package/dist/interpolate-BydkV1eT.js.map +1 -0
  17. package/dist/{mcp-8wClKY-3.js → mcp-Dw-fRPVk.js} +61 -65
  18. package/dist/mcp-Dw-fRPVk.js.map +1 -0
  19. package/dist/mcp.d.ts +1 -1
  20. package/dist/mcp.js +1 -1
  21. package/dist/{presets-BzkJDW1K.js → presets-4zCJzCYw.js} +2 -2
  22. package/dist/{presets-BzkJDW1K.js.map → presets-4zCJzCYw.js.map} +1 -1
  23. package/dist/presets.d.ts +1 -1
  24. package/dist/presets.js +1 -1
  25. package/dist/providers.d.ts +1 -1
  26. package/dist/session/sqlite.d.ts +13 -2
  27. package/dist/session/sqlite.d.ts.map +1 -1
  28. package/dist/session/sqlite.js +70 -27
  29. package/dist/session/sqlite.js.map +1 -1
  30. package/dist/{session-Cn68UASv.js → session-B1RN0uoi.js} +42 -30
  31. package/dist/{session-Cn68UASv.js.map → session-B1RN0uoi.js.map} +1 -1
  32. package/dist/session.d.ts +1 -1
  33. package/dist/session.js +1 -1
  34. package/dist/skills.d.ts +2 -2
  35. package/dist/skills.js +1 -1
  36. package/dist/{stats-BT9l57RS.js → stats-DZIsGqzu.js} +15 -5
  37. package/dist/stats-DZIsGqzu.js.map +1 -0
  38. package/dist/{theme-BlXO6yHe.d.ts → theme-Caf4AvTO.d.ts} +147 -13
  39. package/dist/theme-Caf4AvTO.d.ts.map +1 -0
  40. package/dist/{theme-context-MungM3SY.js → theme-context-DQM2lx4U.js} +212 -72
  41. package/dist/theme-context-DQM2lx4U.js.map +1 -0
  42. package/dist/{tools-C8kDot0H.js → tools-BdQENveS.js} +409 -312
  43. package/dist/tools-BdQENveS.js.map +1 -0
  44. package/dist/tools.d.ts +2 -2
  45. package/dist/tools.js +1 -1
  46. package/dist/tui.d.ts +64 -7
  47. package/dist/tui.d.ts.map +1 -1
  48. package/dist/tui.js +481 -143
  49. package/dist/tui.js.map +1 -1
  50. package/dist/types.d.ts +3 -3
  51. package/dist/types.js +1 -1
  52. package/package.json +6 -1
  53. package/dist/index-bgh-k8Mv.d.ts.map +0 -1
  54. package/dist/interpolate-CukJwP2G.js.map +0 -1
  55. package/dist/mcp-8wClKY-3.js.map +0 -1
  56. package/dist/stats-BT9l57RS.js.map +0 -1
  57. package/dist/theme-BlXO6yHe.d.ts.map +0 -1
  58. package/dist/theme-context-MungM3SY.js.map +0 -1
  59. package/dist/tools-C8kDot0H.js.map +0 -1
@@ -1,4 +1,4 @@
1
- import { c as ExecutionContext, l as ExecutionHandle } from "./index-BB4kuRh3.js";
1
+ import { c as ExecutionContext, l as ExecutionHandle } from "./index-CXVvqTQj.js";
2
2
  import { Hookable } from "hookable";
3
3
  import { Client } from "@modelcontextprotocol/sdk/client/index.js";
4
4
  //#region src/errors.d.ts
@@ -117,2257 +117,2296 @@ declare function matchesContextExceeded(message: unknown): boolean;
117
117
  */
118
118
  declare function toTypedError(classification: ClassifiedError, provider: string, cause: unknown): AgentContextExceededError | AgentProviderError | AgentAbortedError;
119
119
  //#endregion
120
- //#region src/mcp/index.d.ts
121
- interface McpConnection {
122
- tools: Record<string, ToolDef>;
123
- close: () => Promise<void>;
124
- }
125
- /**
126
- * Normalize MCP server configs from any common shape to `McpServerConfig[]`.
127
- *
128
- * Accepts:
129
- * - `McpServerConfig[]` — zidane native (pass-through).
130
- * - `McpServerConfig` — a single config object (wrapped to a 1-element array).
131
- * - `Record<string, RawShape>` — name-keyed map (common in host-SDK configs), where the key is the server name.
132
- * - Mixed shapes with `type` vs `transport`, `httpUrl`/`sseUrl` vs `url`.
133
- *
134
- * Returns `[]` when `input` is nullish. Throws a descriptive error when the transport
135
- * cannot be inferred from a given entry, or when the input shape is unsupported.
136
- */
137
- declare function normalizeMcpServers(input: unknown): McpServerConfig[];
138
- /**
139
- * Lossy flattener — converts MCP `CallToolResult.content` blocks to a single
140
- * string. Text blocks are extracted; non-text blocks are JSON-stringified.
141
- *
142
- * Use this only at UI / log boundaries that require a string. The agent
143
- * loop itself routes through {@link normalizeMcpBlocks} so image blocks
144
- * survive into provider-native tool_result content (Anthropic blocks,
145
- * OpenAI companion-user-message).
146
- */
147
- declare function resultToString(content: unknown[]): string;
148
- /**
149
- * Normalize MCP `CallToolResult.content` to zidane's {@link ToolResultContent[]} shape.
150
- *
151
- * Handles the four MCP content block types:
152
- * - `text` → preserved as `{type:'text', text}`
153
- * - `image` → preserved as `{type:'image', mediaType, data}` (MCP uses `mimeType`)
154
- * - `resource` with embedded text → flattened to a text block
155
- * - `resource` with embedded blob whose `mimeType` is `image/*` → flattened to an image block
156
- * - Any unrecognized block → JSON-stringified fallback text block (lossy but safe)
157
- *
158
- * Returns `null` when the input is not an array — callers should fall back to an empty
159
- * result in that case.
160
- */
161
- declare function normalizeMcpBlocks(content: unknown): ToolResultContent[] | null;
162
- /**
163
- * Connect to MCP servers and discover their tools.
164
- *
165
- * Each tool is namespaced as `mcp_{serverName}_{toolName}` to avoid
166
- * collisions with agent tools or tools from other servers.
167
- *
168
- * @param configs - Array of MCP server configurations
169
- * @param _clientFactory - Internal: override client construction for testing
170
- * @param hooks - Optional agent hooks for firing mcp:connect, mcp:error, mcp:close events
171
- */
172
- declare function connectMcpServers(configs: McpServerConfig[], _clientFactory?: () => Client, hooks?: Hookable<AgentHooks>): Promise<McpConnection>;
173
- //#endregion
174
- //#region src/session/file-map.d.ts
175
- /**
176
- * Host-provided file-map adapter. Three methods exchanging `Record<string, string>`
177
- * payloads — the whole persistence surface the wrapper needs.
178
- */
179
- interface FileMapAdapter {
180
- /** Load the current file map. Returns an empty `files` record when nothing is persisted. */
181
- get: () => Promise<{
182
- files: Record<string, string>;
183
- }>;
184
- /** Replace the persisted file map. Full-rewrite semantics. */
185
- save: (files: Record<string, string>) => Promise<void>;
186
- /** Delete all persisted state. */
187
- delete: () => Promise<void>;
188
- }
189
- interface FileMapStoreOptions {
190
- /** Filename for the JSONL turns log. Default: `turns.jsonl`. */
191
- turnsFile?: string;
192
- /** Filename for the metadata JSON. Default: `meta.json`. */
193
- metaFile?: string;
194
- }
120
+ //#region src/types.d.ts
195
121
  /**
196
- * Create a single-session `SessionStore` backed by a file-map adapter.
122
+ * Thinking / extended-reasoning configuration.
197
123
  *
198
- * @example
199
- * ```ts
200
- * const session = await createSession({
201
- * store: createFileMapStore(hostSessionStore),
202
- * })
203
- * ```
124
+ * - `'off'` — no thinking.
125
+ * - `'minimal' | 'low' | 'medium' | 'high'` — explicit token budget. Maps to
126
+ * provider-specific reasoning controls (Anthropic `thinking.type='enabled'`
127
+ * with a budget; OpenAI `reasoning_effort`).
128
+ * - `'adaptive'` — let the model decide per-turn whether and how much to think.
129
+ * Anthropic-only (`thinking.type='adaptive'`). Other providers fall back to
130
+ * no reasoning when this value is supplied.
204
131
  */
205
- declare function createFileMapStore(adapter: FileMapAdapter, options?: FileMapStoreOptions): SessionStore;
206
- //#endregion
207
- //#region src/session/memory.d.ts
208
- declare function createMemoryStore(): SessionStore;
209
- //#endregion
210
- //#region src/session/messages.d.ts
211
- declare function fromAnthropic(msg: {
212
- role: string;
213
- content: unknown;
214
- }): SessionMessage;
215
- declare function fromOpenAI(msg: {
216
- role: string;
217
- content: unknown;
218
- }): SessionMessage;
219
- declare function toAnthropic(msg: SessionMessage): {
220
- role: string;
221
- content: unknown;
222
- };
223
- declare function toOpenAI(msg: SessionMessage): {
224
- role: string;
225
- content: unknown;
226
- };
227
- declare function autoDetectAndConvert(msg: {
228
- role: string;
229
- content: unknown;
230
- }): SessionMessage;
231
- //#endregion
232
- //#region src/session/remote.d.ts
233
- interface RemoteStoreOptions {
234
- /** Base URL of the session API */
235
- url: string;
236
- /** Optional headers (e.g. for authentication) */
237
- headers?: Record<string, string>;
238
- }
239
- declare function createRemoteStore(options: RemoteStoreOptions): SessionStore;
240
- //#endregion
241
- //#region src/session/index.d.ts
242
- interface SessionRun {
243
- id: string;
244
- startedAt: number;
245
- endedAt?: number;
246
- prompt: string;
247
- status: 'running' | 'completed' | 'aborted' | 'error';
248
- turns?: number;
249
- tokensIn?: number;
250
- tokensOut?: number;
251
- error?: string;
252
- /** Per-turn usage breakdown */
253
- turnUsage?: TurnUsage[];
254
- /** Total usage across all turns */
255
- totalUsage?: TurnUsage;
256
- /** Estimated cost in USD */
257
- cost?: number;
132
+ type ThinkingLevel = 'off' | 'minimal' | 'low' | 'medium' | 'high' | 'adaptive';
133
+ interface McpServerConfig {
134
+ /** Display name (used for tool namespacing) */
135
+ name: string;
136
+ /** Transport type */
137
+ transport: 'stdio' | 'sse' | 'streamable-http';
138
+ /** For stdio: command to run */
139
+ command?: string;
140
+ /** For stdio: command arguments */
141
+ args?: string[];
258
142
  /**
259
- * The run that spawned this one, when the agent is a subagent sharing its
260
- * parent's session. Undefined on top-level `agent.run()`. Consumers can walk
261
- * `runs` by `parentRunId` to reconstruct the subagent tree.
143
+ * For stdio: environment variables to pass to the server process.
144
+ *
145
+ * Merged on top of the MCP SDK's default inherited environment — a safety
146
+ * whitelist (`PATH`, `HOME`, `LANG`, `SHELL`, `USER` on POSIX; `APPDATA`,
147
+ * `PATH`, ... on Win32). Setting this to `{}` no longer strips `PATH` from
148
+ * the child process. Set {@link McpServerConfig.strictEnv} to `true` to
149
+ * pass `env` verbatim with no inherited defaults.
262
150
  */
263
- parentRunId?: string;
151
+ env?: Record<string, string>;
264
152
  /**
265
- * Zero-based subagent depth. 0 = top-level run, 1 = direct child, …
266
- * Recorded here so hosts can query/filter by level without walking the tree.
153
+ * When true, {@link McpServerConfig.env} is passed verbatim to the spawned
154
+ * process the MCP SDK's default inherited environment (`PATH`, `HOME`, ...)
155
+ * is NOT merged in. Most consumers should leave this off; the default merge
156
+ * prevents `spawn ENOENT` when a stdio server declares an `env` without
157
+ * restating `PATH`.
267
158
  */
268
- depth?: number;
269
- }
270
- interface SessionData {
271
- id: string;
272
- agentId?: string;
273
- turns: SessionTurn[];
274
- runs: SessionRun[];
275
- status: 'idle' | 'running' | 'completed' | 'error';
276
- metadata: Record<string, unknown>;
277
- createdAt: number;
278
- updatedAt: number;
279
- }
280
- interface SessionStore {
281
- /** Optional: generate a session ID server-side (e.g. Supabase UUID). */
282
- generateSessionId?: () => string | Promise<string>;
283
- /** Optional: generate a turn ID server-side. */
284
- generateTurnId?: () => string | Promise<string>;
285
- /** Load a session by ID. Returns null if not found. */
286
- load: (sessionId: string) => Promise<SessionData | null>;
287
- /** Save a session (create or update, full document). */
288
- save: (session: SessionData) => Promise<void>;
289
- /** Delete a session. */
290
- delete: (sessionId: string) => Promise<void>;
291
- /** List session IDs, optionally filtered. */
292
- list: (filter?: {
293
- agentId?: string;
294
- limit?: number;
295
- }) => Promise<string[]>;
296
- /** Append new turns to a session (incremental, avoids full re-save). */
297
- appendTurns: (sessionId: string, turns: SessionTurn[]) => Promise<void>;
298
- /** Return a slice of turns for a session. */
299
- getTurns: (sessionId: string, from?: number, limit?: number) => Promise<SessionTurn[]>;
300
- /** Persist an updated run record (called after completeRun / abortRun / errorRun). */
301
- updateRun: (sessionId: string, run: SessionRun) => Promise<void>;
302
- /** Update the top-level status of a session. */
303
- updateStatus: (sessionId: string, status: SessionData['status']) => Promise<void>;
304
- }
305
- interface Session {
306
- /** Session ID */
307
- readonly id: string;
308
- /** Agent ID (optional label) */
309
- readonly agentId?: string;
310
- /** Current turn history */
311
- readonly turns: SessionTurn[];
159
+ strictEnv?: boolean;
160
+ /** For sse/streamable-http: server URL */
161
+ url?: string;
162
+ /** Optional headers for HTTP transports */
163
+ headers?: Record<string, string>;
312
164
  /**
313
- * True when this session has no turns yet.
165
+ * Timeout in milliseconds for MCP server bootstrap (connect + tool discovery).
314
166
  *
315
- * Use this as a first-prompt signal when setting up a run e.g. writing initial
316
- * configuration only on fresh sessions. Equivalent to `turns.length === 0`.
167
+ * Zidane connects MCP servers lazily on the first `run()`. Without a
168
+ * bootstrap timeout, a slow or hung server can delay the first provider call
169
+ * for an arbitrarily long time even when that MCP server is never used.
170
+ *
171
+ * Default: `10000`.
317
172
  */
318
- readonly isEmpty: boolean;
319
- /** Top-level session status */
320
- readonly status: SessionData['status'];
321
- /** All runs in this session */
322
- readonly runs: SessionRun[];
323
- /** Arbitrary metadata */
324
- readonly metadata: Record<string, unknown>;
173
+ bootstrapTimeout?: number;
174
+ /** Timeout in milliseconds for MCP tool calls (default: 30000) */
175
+ toolTimeout?: number;
325
176
  /**
326
- * Start tracking a new run. `extras.parentRunId` + `extras.depth` are
327
- * populated by the spawn tool when a child agent shares its parent's
328
- * session; regular top-level `agent.run()` calls omit them.
177
+ * Allow-list of tool names to expose. Names match the upstream tool name
178
+ * (NOT the namespaced `mcp_{server}_{tool}` form). Tools not in the list are
179
+ * dropped before registration the model never sees them in its catalog and
180
+ * the wire cost of advertising them is avoided.
181
+ *
182
+ * Mutually exclusive with {@link McpServerConfig.disabledTools} — passing both
183
+ * throws at bootstrap time.
184
+ *
185
+ * Composes with {@link McpServerConfig.toolFilter}: allow-list applies first,
186
+ * then the predicate. Composes with the `mcp:tools:filter` hook: config-side
187
+ * filters apply first, then the hook can further narrow the list.
329
188
  */
330
- startRun: (runId: string, prompt?: string, extras?: {
331
- parentRunId?: string;
332
- depth?: number;
333
- }) => void;
334
- /** Mark a run as completed */
335
- completeRun: (runId: string, stats: {
336
- turns: number;
337
- tokensIn: number;
338
- tokensOut: number;
339
- turnUsage?: TurnUsage[];
340
- cost?: number;
341
- }) => void;
342
- /** Mark a run as aborted */
343
- abortRun: (runId: string) => void;
344
- /** Mark a run as errored */
345
- errorRun: (runId: string, error: string) => void;
346
- /** Append turns to in-memory history AND persist via store.appendTurns (if store present) */
347
- appendTurns: (turns: SessionTurn[]) => Promise<void>;
348
- /** Replace all turns in-memory (does not persist — use save() for that) */
349
- setTurns: (turns: SessionTurn[]) => void;
350
- /** Update the session status in memory AND via store.updateStatus (if store present) */
351
- updateStatus: (status: SessionData['status']) => Promise<void>;
352
- /** Persist an updated run record via store.updateRun (if store present) */
353
- updateRun: (run: SessionRun) => Promise<void>;
354
- /** Generate a turn ID using store.generateTurnId if available, else crypto.randomUUID() */
355
- generateTurnId: () => string | Promise<string>;
356
- /** Set metadata key */
357
- setMeta: (key: string, value: unknown) => void;
358
- /** Persist the full session document to the store */
359
- save: () => Promise<void>;
360
- /** Serialize to SessionData */
361
- toJSON: () => SessionData;
362
- }
363
- interface CreateSessionOptions {
364
- /** Session ID. If omitted and store provides generateSessionId, that is used. */
365
- id?: string;
366
- /** Agent ID label */
367
- agentId?: string;
368
- /** Initial metadata */
369
- metadata?: Record<string, unknown>;
370
- /** Storage backend (optional, enables save/load) */
371
- store?: SessionStore;
372
- _data?: SessionData;
373
- }
374
- /**
375
- * Create a new session.
376
- * Async so stores that generate IDs server-side (e.g. Supabase) can be supported.
377
- */
378
- declare function createSession(options?: CreateSessionOptions): Promise<Session>;
379
- /**
380
- * Load an existing session from a store.
381
- */
382
- declare function loadSession(store: SessionStore, sessionId: string): Promise<Session | null>;
383
- //#endregion
384
- //#region src/skills/types.d.ts
385
- /**
386
- * Types for the Agent Skills system.
387
- *
388
- * Follows the Agent Skills open standard (agentskills.io/specification).
389
- * Zidane-specific metadata conventionally uses the `zidane.` key prefix
390
- * (e.g. `metadata['zidane.paths']`) to stay spec-compliant.
391
- */
392
- interface SkillResource {
393
- /** Relative path from skill directory */
394
- path: string;
395
- /** Resource type inferred from directory */
396
- type: 'script' | 'reference' | 'asset' | 'other';
397
- }
398
- /**
399
- * Where the skill came from. Used for collision precedence (project beats user)
400
- * and for host SDKs to gate project-level skills on a trust check.
401
- */
402
- type SkillSource = 'project' | 'user' | 'inline' | 'builtin';
403
- /** Severity + code for lenient-load warnings surfaced to host UIs. */
404
- interface SkillDiagnostic {
405
- severity: 'warning' | 'error';
406
- /** Stable machine-readable code (e.g. `name-mismatch-directory`). */
407
- code: string;
408
- /** Human-readable description. */
409
- message: string;
410
- /** Optional frontmatter field name the diagnostic relates to. */
411
- field?: string;
412
- }
413
- interface SkillConfig {
414
- /** Skill name: 1-64 chars, lowercase alphanumeric + hyphens */
415
- name: string;
416
- /** What the skill does and when to use it (1-1024 chars) */
417
- description: string;
418
- /** The SKILL.md body content (after YAML frontmatter) */
419
- instructions: string;
189
+ enabledTools?: string[];
420
190
  /**
421
- * Where this skill was loaded from. Drives collision precedence and the
422
- * `trustProjectSkills` gate. Optional `parseSkillFile` stamps it; raw
423
- * fixtures that omit it are treated as `'project'` by downstream readers.
191
+ * Deny-list of tool names. Tools matching are dropped before registration.
192
+ * Same matching semantics as {@link McpServerConfig.enabledTools}.
424
193
  */
425
- source?: SkillSource;
426
- /** Absolute path to SKILL.md (undefined for inline skills) */
427
- location?: string;
428
- /** Skill directory path for resolving relative references */
429
- baseDir?: string;
430
- /** License identifier or reference */
431
- license?: string;
432
- /** Environment requirements */
433
- compatibility?: string;
194
+ disabledTools?: string[];
434
195
  /**
435
- * Flat key-value metadata bag per the spec. For Zidane-specific hints,
436
- * use the `zidane.` key prefix (e.g. `metadata['zidane.paths']`).
196
+ * Custom predicate run on each upstream tool. Return `true` to keep, `false`
197
+ * to drop. Receives the raw `listTools()` payload useful for filtering by
198
+ * description, schema shape, or other metadata that an allow/deny list can't
199
+ * express.
200
+ *
201
+ * Runs after the allow/deny filter but before the `mcp:tools:filter` hook.
437
202
  */
438
- metadata?: Record<string, string>;
439
- /** Pre-approved tool names (experimental per spec) */
440
- allowedTools?: string[];
441
- /** Bundled resource files discovered in the skill directory */
442
- resources?: SkillResource[];
203
+ toolFilter?: (tool: {
204
+ name: string;
205
+ description?: string | null;
206
+ inputSchema?: unknown;
207
+ }) => boolean;
443
208
  /**
444
- * Lenient-load warnings recorded during parsing. Host SDKs can surface these
445
- * as inline UI hints. Absent when no issues were found.
209
+ * Per-server override for {@link AgentBehavior.toolDisclosure}. When set,
210
+ * this server's tools follow this disclosure mode regardless of the
211
+ * agent-wide default. Useful when one big MCP server (200+ tools) should
212
+ * stay lazy while smaller servers stay eager.
213
+ *
214
+ * Default: inherits from `behavior.toolDisclosure`.
446
215
  */
447
- diagnostics?: SkillDiagnostic[];
216
+ disclosure?: 'eager' | 'lazy';
448
217
  }
449
- interface SkillsConfig {
218
+ type ToolExecutionMode = 'sequential' | 'parallel';
219
+ interface AgentBehavior {
220
+ /** Tool execution mode (default: 'sequential') */
221
+ toolExecution?: ToolExecutionMode;
450
222
  /**
451
- * Control which skills are active.
452
- * - `true` (default): all discovered skills are enabled
453
- * - `false` or `[]`: fully disable the skills system (no resolution, no catalog, no hooks)
454
- * - `string[]`: allowlist only skills with matching names are enabled
223
+ * Max agent loop iterations.
224
+ *
225
+ * Default: unlimited (Infinity). The loop runs until the model signals
226
+ * completion (no tool calls / `end_turn`), the abort signal fires, or this
227
+ * cap is hit. Set a finite value as a safety net for runaway loops.
455
228
  */
456
- enabled?: boolean | string[];
457
- /** Directories to scan for SKILL.md files */
458
- scan?: string[];
459
- /** Dynamic skills written to disk at agent start, then loaded normally */
460
- write?: SkillConfig[];
461
- /** Skill names to exclude from the catalog */
462
- exclude?: string[];
463
- /** Skip default scan paths (~/.agents/skills, .zidane/skills, etc.) */
464
- skipDefaultPaths?: boolean;
229
+ maxTurns?: number;
230
+ /** Max tokens per LLM response (default: 16384) */
231
+ maxTokens?: number;
232
+ /** Thinking token budget overrides the level-based default when set */
233
+ thinkingBudget?: number;
234
+ /** JSON Schema for structured output enforcement */
235
+ schema?: Record<string, unknown>;
465
236
  /**
466
- * Auto-inject `skills_use` / `skills_read` / `skills_run_script` tools
467
- * when the catalog is non-empty. Default `true`. Set `false` to opt out
468
- * (the system prompt will then instruct the model to use its file-read
469
- * tool instead).
237
+ * Enable provider prompt caching. When on (default), the provider marks the
238
+ * system prompt, tools, and the last stable message with cache breakpoints so
239
+ * the shared prefix is served from cache across turns.
240
+ *
241
+ * - Anthropic: `cache_control: { type: 'ephemeral' }` on the last `system`
242
+ * content part, the last tool, and the last message content part.
243
+ * - OpenAI-compatible / OpenRouter: same shape — honored by Anthropic-backed
244
+ * OpenRouter routes and by Gemini; ignored (no-op) by providers that cache
245
+ * automatically (OpenAI, DeepSeek, Grok, Groq, Moonshot).
246
+ *
247
+ * Usage is surfaced via `TurnUsage.cacheRead` / `TurnUsage.cacheCreation`.
248
+ *
249
+ * Default: `true`.
470
250
  */
471
- tool?: boolean;
251
+ cache?: boolean;
472
252
  /**
473
- * Cap on concurrently active skills per run. Default `undefined` (unlimited).
474
- * Attempts to activate past the cap throw from `skills_use`.
253
+ * Soft per-turn cap on total tool-output bytes. When the sum of `outputBytes`
254
+ * across a turn's tool results exceeds this value, the loop injects a
255
+ * synthetic user message instructing the model to summarize before calling
256
+ * more tools, and fires the `budget:exceeded` hook.
257
+ *
258
+ * Measured **post-`tool:transform`** so consumer truncation counts toward the
259
+ * budget. Off by default (undefined / `0` disables the check). A reasonable
260
+ * starting value for OSS-model integrations is `32768`.
475
261
  */
476
- maxActive?: number;
477
- /** Script timeout for `skills_run_script`, in milliseconds. Default `60000`. */
478
- scriptTimeoutMs?: number;
262
+ toolOutputBudget?: number;
479
263
  /**
480
- * When `false`, skills with `source: 'project'` are skipped during
481
- * resolution with a diagnostic. Default `true` (preserves existing behavior).
482
- * Useful for host SDKs handling untrusted repositories.
264
+ * Deduplicate identical re-reads of the same file in `read_file`. When the
265
+ * model re-reads a file with the same slice and the bytes haven't changed
266
+ * since the last read in this session, the tool returns a short stub
267
+ * instead of re-emitting the full content. Pairs with the read-before-edit
268
+ * guard in `edit` / `multi_edit`.
269
+ *
270
+ * Requires a session (set via `createSession()`); without one, the flag is
271
+ * a no-op since per-session state has nowhere to live.
272
+ *
273
+ * Default: `true`.
483
274
  */
484
- trustProjectSkills?: boolean;
485
- }
486
- //#endregion
487
- //#region src/skills/activation.d.ts
488
- /** How a skill was activated. Surfaced in `skills:activate` hook ctx. */
489
- type ActivationVia = 'model' | 'explicit' | 'resume';
490
- /** Reason a skill was deactivated. Surfaced in `skills:deactivate` hook ctx. */
491
- type DeactivationReason = 'run-end' | 'explicit' | 'reset';
492
- /** A skill currently active in the state machine. */
493
- interface ActiveSkill {
494
- skill: SkillConfig;
495
- activatedAt: number;
496
- activatedVia: ActivationVia;
497
- }
498
- /**
499
- * Per-agent skill activation state. Public read-surface is the `active()` list
500
- * and `isActive(name)` predicate; writes go through `activate()` / `deactivate()`.
501
- */
502
- interface SkillActivationState {
503
- /** List of currently active skills in activation order. Returns a snapshot. */
504
- active: () => readonly ActiveSkill[];
505
- /** Is the skill with this canonical name currently active? */
506
- isActive: (name: string) => boolean;
507
- /** Retrieve the `ActiveSkill` record by name, or `undefined`. */
508
- get: (name: string) => ActiveSkill | undefined;
275
+ dedupReads?: boolean;
509
276
  /**
510
- * Mark a skill as active.
511
- * - Returns `'ok'` on a fresh activation (caller should fire `skills:activate`).
512
- * - Returns `'already-active'` if the skill was already in the set (idempotent).
513
- * - Returns `'cap-reached'` if the `maxActive` cap would be exceeded. State is unchanged.
277
+ * Taper the thinking budget over the course of a run. Late turns are
278
+ * usually checkpoint / cleanup work where reasoning rarely pays for
279
+ * itself; early turns benefit most. Two forms:
280
+ *
281
+ * - **Struct** — geometric decay starting after `afterTurn`, multiplying by
282
+ * `factor` each subsequent turn, clamped to `floor`. Example
283
+ * `{ afterTurn: 5, factor: 0.5, floor: 1024 }` with a base budget of 8192:
284
+ * turns 1-5 = 8192, turn 6 = 4096, turn 7 = 2048, turn 8+ = 1024.
285
+ * - **Function** — `(runTurn, baseBudget) => number`. Arbitrary curves;
286
+ * `runTurn` is 1-indexed, run-relative (resumed sessions reset).
287
+ *
288
+ * No-op when `thinkingBudget` is unset. Honored by every provider that
289
+ * respects `thinkingBudget` (anthropic explicit-budget `enabled` path,
290
+ * adaptive `maxTokensCap`, openai-compat `max_tokens` padding).
291
+ *
292
+ * Default: `undefined` (no decay).
514
293
  */
515
- activate: (skill: SkillConfig, via: ActivationVia) => 'ok' | 'already-active' | 'cap-reached';
516
- /**
517
- * Mark a skill as inactive. Returns the removed `ActiveSkill` record or `undefined`
518
- * if it wasn't active. Callers fire `skills:deactivate` on removal.
519
- */
520
- deactivate: (name: string) => ActiveSkill | undefined;
521
- /** Remove every active skill. Returns the list of removed records. */
522
- clear: () => readonly ActiveSkill[];
523
- }
524
- interface SkillActivationStateOptions {
525
- /**
526
- * Cap on concurrent activations. `undefined` (the default) disables the cap.
527
- * When set, `activate()` returns `'cap-reached'` once the set is at size `maxActive`.
528
- */
529
- maxActive?: number;
530
- }
531
- declare function createSkillActivationState(options?: SkillActivationStateOptions): SkillActivationState;
532
- //#endregion
533
- //#region src/agent.d.ts
534
- interface AgentHooks {
535
- 'system:before': (ctx: {
536
- system: string;
537
- }) => void;
538
- 'turn:before': (ctx: {
539
- turn: number;
540
- turnId: string;
541
- options: StreamOptions;
542
- }) => void;
294
+ thinkingDecay?: {
295
+ afterTurn: number;
296
+ factor: number;
297
+ floor: number;
298
+ } | ((runTurn: number, baseBudget: number) => number);
543
299
  /**
544
- * Fires after each assistant turn (before its tool-result follow-up
545
- * dispatches; the loop iterates back to a fresh `turn:before` once the
546
- * tool results are produced).
300
+ * Per-tool soft call budget for this run. Keyed by **canonical** tool name.
301
+ * On the first call after the run-cumulative dispatched count for that tool
302
+ * reaches `max`, the framework fires `onExceed`:
547
303
  *
548
- * `toolCounts.turn` — calls **emitted** by the model in this assistant
549
- * turn, keyed by canonical tool name. Reflects what the model asked for,
550
- * regardless of downstream gate outcome. Most useful for spotting per-turn
551
- * spikes ("the model called todowrite 4 times in one turn").
304
+ * - `'steer'` (default) let the call execute, but emit a synthetic user
305
+ * message after the turn that nudges the model away from re-calling the
306
+ * tool. Reuses the existing post-turn steer pathway used by
307
+ * `toolOutputBudget`. Fires `tool-budget:exceeded` with `mode: 'steer'`.
308
+ * - `'block'` — refuse the call via `tool:gate` `block`. The model sees a
309
+ * `Blocked: <reason>` tool result. Fires `tool-budget:exceeded` with
310
+ * `mode: 'block'`.
311
+ * - **Function** — `(ctx) => { mode, message }`. The consumer supplies the
312
+ * steering / refusal text and chooses the mode dynamically.
552
313
  *
553
- * `toolCounts.run` cumulative running counter of **dispatched** calls
554
- * scoped to this `runId`, captured at fire time. Excludes calls that were
555
- * `block`ed by `tool:gate` handlers. Includes calls short-circuited via
556
- * `tool:gate` `result` substitution (the model still asked, the framework
557
- * just answered without the tool running). Resumed sessions start a fresh
558
- * run with empty counts.
314
+ * Counts include both real dispatches and dedup substitutes (Z19 hits).
315
+ * Excludes calls already blocked by an earlier gate (skill allow-list,
316
+ * consumer hook). Tool dispatched by spawned subagents has its own per-run
317
+ * counter child counts never charge the parent.
559
318
  *
560
- * Both fields are frozen snapshots; mutate-safe.
319
+ * For MCP tools, key by the namespaced wire name (`mcp_<server>_<tool>`).
320
+ *
321
+ * Atomic in parallel mode: the middleware tracks its own per-tool
322
+ * approval counter, incremented synchronously at gate-time. A
323
+ * 4-call parallel batch against `max: 2` will let the first 2 through
324
+ * and refuse the rest, even though the loop's `runToolCounts` only
325
+ * propagates between calls (not within a single batch's gate fan-out).
326
+ *
327
+ * Default: `undefined` (no budget enforcement).
561
328
  */
562
- 'turn:after': (ctx: {
563
- turn: number;
564
- turnId: string;
565
- usage: TurnUsage;
566
- message: SessionTurn;
567
- toolCounts: {
568
- turn: Readonly<Record<string, number>>;
569
- run: Readonly<Record<string, number>>;
570
- };
571
- }) => void;
572
- 'stream:text': (ctx: StreamHookContext & {
573
- delta: string;
574
- text: string;
575
- }) => void;
576
- 'stream:end': (ctx: StreamHookContext & {
577
- text: string;
578
- }) => void;
579
- 'stream:thinking': (ctx: StreamHookContext & {
580
- delta: string;
581
- thinking: string;
582
- }) => void;
583
- 'oauth:refresh': (ctx: OAuthRefreshHookContext) => void;
329
+ toolBudgets?: Record<string, {
330
+ max: number;
331
+ onExceed?: 'steer' | 'block' | ((ctx: {
332
+ tool: string;
333
+ count: number;
334
+ max: number;
335
+ }) => {
336
+ mode: 'steer' | 'block';
337
+ message: string;
338
+ });
339
+ }>;
584
340
  /**
585
- * Fires before validation, `tool:before`, and `execute`. Two ways to
586
- * intercept:
341
+ * Generic per-tool argument deduplication. Keyed by the tool's **canonical**
342
+ * name (alias-stable). Each entry is a hasher: `(input) => string | undefined`.
587
343
  *
588
- * - Set `block = true` (with a `reason`) to refuse the call. The model
589
- * sees a `Blocked: <reason>` tool result; `tool:before` / `tool:after`
590
- * do **not** fire.
591
- * - Set `result` to substitute a successful tool_result and skip
592
- * execution. The model sees the substitute as a normal tool_result;
593
- * `tool:before` does not fire, but `tool:after` and `tool:transform`
594
- * do — so byte budgets, telemetry, and post-mutation hooks see the
595
- * substitute. Useful for cache hits, dedup, idempotency guards,
596
- * plan-mode synthetic acks.
344
+ * **Hasher contract** three return values, three meanings:
597
345
  *
598
- * If multiple handlers along the chain set both `block` and `result`,
599
- * `block` wins — refusal beats substitution, so a policy gate
600
- * (skills allow-list, custom security) can always override an upstream
601
- * consumer's cache substitute. Mirrors the writable-`result` shape on
602
- * `tool:unknown` and `tool:error` so consumers learn one pattern.
346
+ * | Return | Meaning |
347
+ * |-------------------------|------------------------------------------------------------------------|
348
+ * | a non-empty string | Cache key for this call. Equal keys (most-recent-only, this session) |
349
+ * | | replay the prior recorded result without re-dispatching the tool. |
350
+ * | `undefined` | **Skip dedup for this call.** The tool runs normally; nothing recorded.|
351
+ * | `''` / non-string | Treated identically to `undefined` (defensive: no dedup, no error). |
603
352
  *
604
- * `runToolCounts` frozen pre-call snapshot of per-tool dispatched
605
- * counts in this run. Use it to self-throttle, drive observability, or
606
- * implement budget guards. Counts every call that passed gate, including
607
- * dedup substitutes (Z19); excludes `block`ed calls.
353
+ * The `undefined` opt-out is the way to say *"this specific call is not
354
+ * cacheable"* (timestamps in input, randomness baked in, debug flags). It
355
+ * is **not** the same as `JSON.stringify(input)` that would dedup against
356
+ * the verbatim input. Pick one explicitly:
608
357
  *
609
- * **Parallel mode** (`toolExecution: 'parallel'`, the default): the
610
- * snapshot is taken before any dispatches in the batch, so consumer
611
- * hooks reading `runToolCounts` see the pre-batch view. Built-in
612
- * budget / dedup middleware uses internal per-call reservation, so
613
- * `behavior.toolBudgets` enforces atomically even within a parallel
614
- * batch.
358
+ * ```ts
359
+ * // Always cache by full input every identical re-call dedups.
360
+ * dedupTools: { todowrite: input => JSON.stringify(input) }
361
+ *
362
+ * // Cache by a normalized subset; non-cacheable shapes opt out.
363
+ * dedupTools: {
364
+ * execute_sql: (input) => {
365
+ * const q = typeof input.query === 'string' ? input.query.trim().toLowerCase() : undefined
366
+ * if (!q || q.includes('now()') || q.includes('random()')) return undefined
367
+ * return q
368
+ * },
369
+ * }
370
+ * ```
371
+ *
372
+ * On a hit, the previously-recorded result is replayed as the tool_result
373
+ * without dispatching the tool. The substitution flows through `tool:gate`
374
+ * `result` (Z20), so `tool:after` and `tool:transform` still fire.
375
+ *
376
+ * Requires a session (`createSession()`); without one, the map is a silent
377
+ * no-op since per-session state has nowhere to live. Tools with side
378
+ * effects or non-deterministic outputs (network, time, randomness) MUST
379
+ * NOT be listed — there is no safety net beyond the consumer's hasher.
380
+ *
381
+ * For MCP tools, key by the namespaced wire name (`mcp_<server>_<tool>`).
382
+ * Parallel mode (`toolExecution: 'parallel'`, the default) sees calls in
383
+ * the SAME assistant turn race against each other — none can dedup against
384
+ * a sibling that started in the same batch. Sequential mode honors order
385
+ * within a turn.
386
+ *
387
+ * **Cache policy**: only the most recent `(hash, result)` per tool is
388
+ * retained. Interleaved patterns (input A, input B, input A) miss on the
389
+ * second A because B overwrote it. Sufficient for the common spam-the-
390
+ * same-call loop; consumers needing a richer cache should hook
391
+ * `tool:gate` directly.
392
+ *
393
+ * Default: `undefined` (no per-tool dedup).
615
394
  */
616
- 'tool:gate': (ctx: ToolHookContext & {
617
- block: boolean;
618
- reason: string;
619
- result?: string | ToolResultContent[];
620
- runToolCounts: Readonly<Record<string, number>>;
621
- }) => void;
622
- 'tool:before': (ctx: ToolHookContext & {
623
- coercions?: readonly string[];
624
- runToolCounts: Readonly<Record<string, number>>;
625
- }) => void;
626
- 'tool:after': (ctx: ToolHookContext & {
627
- result: string | ToolResultContent[];
628
- outputBytes: number;
629
- coercions?: readonly string[];
630
- runToolCounts: Readonly<Record<string, number>>;
631
- }) => void;
395
+ dedupTools?: Record<string, (input: Record<string, unknown>) => string | undefined>;
632
396
  /**
633
- * Fires when a tool throws during execution. Mutate `result` to substitute a
634
- * tool-output payload that gets sent back to the model in place of the
635
- * default `Tool error: <msg>` string useful for OSS-model error rewriting
636
- * (collapse stack traces, hide internal paths, prepend recovery hints).
397
+ * Require `read_file` before `edit` / `multi_edit` on the same path, and
398
+ * reject edits when the file has changed on disk since the last read in
399
+ * this session. Eliminates the silent-corruption failure mode where a
400
+ * model "remembers" stale content and applies a substring edit against
401
+ * bytes that have moved.
637
402
  *
638
- * The post-hook value flows through `tool:transform` like a normal output, so
639
- * downstream byte-budgeting and image-stripping still apply.
403
+ * Requires a session. Off by default; turn it on for stricter eval-grade
404
+ * runs where silent edit corruption would invalidate the result.
405
+ *
406
+ * Default: `false`.
640
407
  */
641
- 'tool:error': (ctx: ToolHookContext & {
642
- error: Error;
643
- result?: string | ToolResultContent[];
644
- }) => void;
645
- 'tool:transform': (ctx: ToolHookContext & {
646
- result: string | ToolResultContent[];
647
- isError: boolean;
648
- outputBytes: number;
649
- coercions?: readonly string[];
650
- }) => void;
408
+ requireReadBeforeEdit?: boolean;
651
409
  /**
652
- * Fires before the generic "Unknown tool" error when the model invokes a tool
653
- * that isn't registered (hallucinated names, dropped MCP servers, dangling
654
- * aliases). Mutate `result` to substitute a friendly response or set
655
- * `suppressError: true` to skip the companion `tool:error` emission.
410
+ * Client-side context compaction strategy. Use this for non-Anthropic
411
+ * providers (OSS via cerebras / openai-compat / openrouter) that don't
412
+ * have a server-side equivalent. Anthropic users should prefer the
413
+ * server-side `context-management-2025-06-27` beta see
414
+ * `AnthropicParams.contextManagement`.
656
415
  *
657
- * Fires for any unknown tool name including hallucinated MCP-style names
658
- * (`mcp_supabase_xxx`); branch on `name.startsWith('mcp_')` to differentiate.
416
+ * - `'off'` (default)no client-side compaction.
417
+ * - `'tail'` when total tool-output bytes in the persisted history
418
+ * exceed `compactThreshold`, replace older `tool_result` outputs with a
419
+ * short stub, keeping the newest `compactKeepTurns` turns intact. The
420
+ * compaction is applied to the wire-level message list only; the
421
+ * underlying session turns are not modified.
422
+ *
423
+ * Default: `'off'`.
659
424
  */
660
- 'tool:unknown': (ctx: ToolHookContext & {
661
- result?: string | ToolResultContent[];
662
- suppressError: boolean;
663
- }) => void;
425
+ compactStrategy?: 'off' | 'tail';
664
426
  /**
665
- * Fires when `validateToolArgs` rejects an input that could not be auto-coerced
666
- * to satisfy the tool's `inputSchema`. Observational the tool call still
667
- * surfaces a `Validation error: …` string back to the model. Useful for
668
- * counting validation failures separately from runtime tool errors.
427
+ * Soft byte threshold that triggers tail compaction when
428
+ * `compactStrategy === 'tail'`. Counts the post-`context:transform` bytes
429
+ * of `tool_result` outputs across all messages. Default: `131_072` (128
430
+ * KiB). Ignored when compaction is off.
669
431
  */
670
- 'validation:reject': (ctx: ToolHookContext & {
671
- reason: string;
672
- schema: Record<string, unknown>;
673
- }) => void;
432
+ compactThreshold?: number;
674
433
  /**
675
- * Fires when `validateToolArgs` successfully auto-coerced one or more input
676
- * fields to satisfy the tool's `inputSchema`. **Only fires when at least one
677
- * coercion happened** never on perfectly-shaped inputs. Useful for counting
678
- * model "wrongness rate" without re-running validation downstream.
679
- *
680
- * `coercions` lists the field names that were coerced. The values landed in
681
- * the input that the tool actually received; consumers wanting before/after
682
- * comparison can re-run `validateToolArgs(ctx.input, ctx.schema)`.
434
+ * Number of trailing turns to leave untouched during tail compaction. The
435
+ * most-recent `compactKeepTurns` user/assistant messages are not eligible
436
+ * for elision so the model keeps the freshest tool context. Default: `4`.
683
437
  */
684
- 'validation:coerce': (ctx: ToolHookContext & {
685
- coercions: readonly string[];
686
- schema: Record<string, unknown>;
687
- }) => void;
688
- 'context:transform': (ctx: {
689
- messages: SessionMessage[];
690
- }) => void;
438
+ compactKeepTurns?: number;
691
439
  /**
692
- * Fires per request, after `context:transform` and before the request goes
693
- * out. Mutating `ctx.system` updates the system prompt the provider sends
694
- * for this turn useful for runtime-derived sections (e.g. listing files
695
- * already read in the session, surfacing live tool budgets, injecting
696
- * skill activation reminders).
440
+ * Prefix every line of `read_file` output with its 1-indexed line number
441
+ * followed by a tab (`<N>\t<content>`) the compact `cat -n`-style
442
+ * format Claude Code emits. The `edit` tool strips the prefix from
443
+ * `old_string` / `new_string` so the model can paste back a numbered
444
+ * chunk verbatim without breaking the match.
697
445
  *
698
- * Cache breakpoints are applied inside the provider after this hook, so
699
- * mutations land in the cache key naturally — repeated turns with the
700
- * same derived system text still hit the cache.
446
+ * Set `false` to opt out useful for callers piping `read_file` into
447
+ * downstream parsers that don't recognize the prefix. Per-call
448
+ * `read_file({ lineNumbers: false })` overrides this default.
701
449
  *
702
- * `messages` is read-only here; use `context:transform` for message
703
- * surgery. `session` is `undefined` when the run is sessionless.
450
+ * Default: `true`.
704
451
  */
705
- 'system:transform': (ctx: {
706
- system: string;
707
- messages: readonly SessionMessage[];
708
- turn: number;
709
- turnId: string;
710
- session?: Session;
711
- }) => void;
712
- 'steer:inject': (ctx: {
713
- message: string;
714
- }) => void;
715
- 'spawn:before': (ctx: SpawnHookContext) => void;
716
- 'spawn:complete': (ctx: ChildRunStats) => void;
717
- 'spawn:error': (ctx: SpawnHookContext & {
718
- error: Error;
719
- }) => void;
720
- 'child:stream:text': (ctx: StreamHookContext & {
721
- delta: string;
722
- text: string;
723
- childId: string;
724
- depth: number;
725
- }) => void;
726
- 'child:stream:thinking': (ctx: StreamHookContext & {
727
- delta: string;
728
- thinking: string;
729
- childId: string;
730
- depth: number;
731
- }) => void;
732
- 'child:stream:end': (ctx: StreamHookContext & {
733
- text: string;
734
- childId: string;
735
- depth: number;
736
- }) => void;
452
+ readLineNumbers?: boolean;
737
453
  /**
738
- * Gate-style child events. Unlike the other `child:*` events, the bubble
739
- * passes the **same `ctx` reference** the subagent's loop is awaiting on:
740
- * setting `ctx.block` / `ctx.reason` / `ctx.result` on a parent listener
741
- * propagates straight back to the child, refusing or substituting the call.
454
+ * Replace older `read_file` `tool_result` blocks with a short stub when
455
+ * a successful `edit` / `multi_edit` / `write_file` later in the same
456
+ * run modified the same path. The replacement is applied to the
457
+ * wire-level message list only persisted session turns keep the
458
+ * original content.
742
459
  *
743
- * Use these to gate subagent tool calls (native + MCP) from the parent
744
- * without registering listeners on every child agent. The parent's own
745
- * `tool:gate` / `mcp:tool:gate` listeners are NOT auto-shared with
746
- * children that would also share their budgets and dedup state.
747
- */
748
- 'child:tool:gate': (ctx: ToolHookContext & {
749
- block: boolean;
750
- reason: string;
751
- result?: string | ToolResultContent[];
752
- runToolCounts: Readonly<Record<string, number>>;
753
- childId: string;
754
- depth: number;
755
- }) => void;
756
- 'child:mcp:tool:gate': (ctx: McpToolHookContext & {
757
- block: boolean;
758
- reason: string;
759
- result?: string | ToolResultContent[];
760
- childId: string;
761
- depth: number;
762
- }) => void;
763
- 'child:tool:before': (ctx: ToolHookContext & {
764
- coercions?: readonly string[];
765
- runToolCounts: Readonly<Record<string, number>>;
766
- childId: string;
767
- depth: number;
768
- }) => void;
769
- 'child:tool:after': (ctx: ToolHookContext & {
770
- result: string | ToolResultContent[];
771
- outputBytes: number;
772
- coercions?: readonly string[];
773
- runToolCounts: Readonly<Record<string, number>>;
774
- childId: string;
775
- depth: number;
776
- }) => void;
777
- 'child:tool:error': (ctx: ToolHookContext & {
778
- error: Error;
779
- childId: string;
780
- depth: number;
781
- }) => void;
782
- 'child:turn:after': (ctx: {
783
- turn: number;
784
- turnId: string;
785
- usage: TurnUsage;
786
- message: SessionTurn;
787
- toolCounts: {
788
- turn: Readonly<Record<string, number>>;
789
- run: Readonly<Record<string, number>>;
790
- };
791
- childId: string;
792
- depth: number;
793
- }) => void;
794
- 'mcp:connect': (ctx: {
795
- name: string;
796
- transport: string;
797
- tools: string[];
798
- }) => void;
799
- 'mcp:error': (ctx: {
800
- name: string;
801
- error: Error;
802
- }) => void;
803
- 'mcp:close': (ctx: {
804
- name: string;
805
- }) => void;
806
- /**
807
- * Fires at the start of a per-server bootstrap attempt, before any network I/O.
808
- * Pairs with `mcp:bootstrap:end` and is always emitted, regardless of outcome.
809
- */
810
- 'mcp:bootstrap:start': (ctx: {
811
- name: string;
812
- transport: string;
813
- }) => void;
814
- /**
815
- * Fires at the end of a per-server bootstrap attempt. `durationMs` spans from
816
- * the matching `mcp:bootstrap:start`. On `ok: false` carries the originating
817
- * error so consumers can log / trace without relying on a separate `mcp:error`.
460
+ * Eliminates the common waste pattern where the model carries the
461
+ * pre-edit file body forward across many turns "in case it needs it".
462
+ * Pairs cleanly with `compactStrategy: 'tail'`: stale reads shrink
463
+ * first, then the byte-threshold compaction fires if anything's left.
464
+ *
465
+ * Detection is conservative — only triggers when the corresponding
466
+ * tool_result confirms success (`Edited …`, `Created …`, `Updated …`).
467
+ * Failed edits and `No change needed` write_file calls do NOT
468
+ * invalidate prior reads.
469
+ *
470
+ * Default: `false`.
818
471
  */
819
- 'mcp:bootstrap:end': (ctx: {
820
- name: string;
821
- transport: string;
822
- durationMs: number;
823
- } & ({
824
- ok: true;
825
- toolCount: number;
826
- } | {
827
- ok: false;
828
- error: Error;
829
- })) => void;
472
+ elideStaleReads?: boolean;
830
473
  /**
831
- * Fires once per server after `listTools()` and after the config-side filters
832
- * (`enabledTools` / `disabledTools` / `toolFilter`) have applied, but BEFORE
833
- * tools are registered. Handlers may mutate `ctx.tools` in place splicing,
834
- * reordering, or replacing entries to further narrow what the model sees.
474
+ * Tool disclosure strategy. Controls whether the model sees every tool's
475
+ * full `inputSchema` in its tool list every turn ("eager") or whether MCP
476
+ * tools are advertised as a name+description catalog in the system prompt
477
+ * and only get full schemas after being surfaced via the `tool_search`
478
+ * native tool ("lazy" / progressive disclosure).
835
479
  *
836
- * Composes with config-side filters: config drops tools the host's static
837
- * policy excludes; this hook is the runtime escape hatch for per-user, per-
838
- * environment, or capability-driven decisions that the config can't express.
480
+ * Native tools (those passed to `createAgent({ tools })`) and skill tools
481
+ * are always eager they are core to the agent and cheap. Only MCP tools
482
+ * are eligible for lazy disclosure.
839
483
  *
840
- * Items are upstream tool descriptors (NOT yet namespaced as `mcp_<server>_<tool>`).
484
+ * When `'lazy'`, the agent:
485
+ * - Appends a `<searchable_tools>` section to the system prompt listing
486
+ * every MCP tool by `name` + `description` only (no `inputSchema`).
487
+ * - Auto-injects a `tool_search` native tool (opt out via
488
+ * {@link AgentBehavior.toolSearch}) the model uses to load schemas on
489
+ * demand. Surfaced tools persist for the rest of the run.
490
+ * - Rebuilds the wire-level tool list each turn, appending newly-unlocked
491
+ * tools at the end so the prefix-cache breakpoint advances cleanly.
492
+ *
493
+ * Trade-off: every `tool_search` invocation expands the tool list and
494
+ * invalidates the tool-list cache breakpoint for one turn. With many
495
+ * MCP servers, the savings on cold turns (fewer schemas in context) are
496
+ * substantial; with one tiny MCP server, the overhead may not pay back.
497
+ *
498
+ * Default: `'eager'`.
841
499
  */
842
- 'mcp:tools:filter': (ctx: {
843
- server: string;
844
- transport: 'stdio' | 'sse' | 'streamable-http';
845
- tools: Array<{
846
- name: string;
847
- description?: string | null;
848
- inputSchema?: unknown;
849
- }>;
850
- }) => void;
500
+ toolDisclosure?: 'eager' | 'lazy';
851
501
  /**
852
- * MCP-side counterpart of `tool:gate`. Same shape: set `block` to refuse,
853
- * set `result` to substitute a successful payload and skip the upstream
854
- * MCP `callTool`. When both are set across the handler chain, `block` wins.
502
+ * Fine-grained config for the `tool_search` tool auto-injected when
503
+ * {@link AgentBehavior.toolDisclosure} is `'lazy'`. No-op in eager mode.
855
504
  *
856
- * Fires INSIDE the MCP wrapper's `execute`, after the loop's `tool:gate`
857
- * already ran. Does **not** carry `runToolCounts` those are loop-level
858
- * and already exposed on `tool:gate` for MCP tools (which are registered
859
- * as agent tools under their namespaced name `mcp_<server>_<tool>`). Use
860
- * `tool:gate` for budget / dedup logic; reserve `mcp:tool:gate` for
861
- * MCP-specific concerns (per-server routing, transport-aware refusals).
505
+ * - `tool: false` opt out of the auto-injection entirely. Use when the
506
+ * host wants to ship a custom discovery tool. Note that the catalog
507
+ * text drops the call-to-action prose in this case so the model isn't
508
+ * pointed at a non-existent tool.
509
+ * - `limit` default cap on results returned per `tool_search` call when
510
+ * the model omits the parameter. Default: `20`.
511
+ *
512
+ * Note on host-defined `tool_search`: a tool the host registers under the
513
+ * name `tool_search` (or under any alias whose canonical is `tool_search`)
514
+ * will shadow the auto-injected one — the catalog text will point at the
515
+ * host's wire name, but driving the unlock flow requires either using
516
+ * `createToolSearchTool({ catalog, unlocked })` from `tools/tool-search`
517
+ * (which internally mutates the unlock set) or fully opting out via
518
+ * `toolSearch.tool: false` and treating discovery as a host-side concern.
519
+ * A bare host tool that doesn't touch the unlock set will not advance the
520
+ * lazy disclosure state and the hard gate will keep refusing lazy calls.
521
+ *
522
+ * Default: `undefined` (auto-inject with the default limit).
862
523
  */
863
- 'mcp:tool:gate': (ctx: McpToolHookContext & {
864
- block: boolean;
865
- reason: string;
866
- result?: string | ToolResultContent[];
867
- }) => void;
868
- 'mcp:tool:before': (ctx: McpToolHookContext) => void;
869
- 'mcp:tool:after': (ctx: McpToolHookContext & {
870
- result: string | ToolResultContent[];
871
- outputBytes: number;
872
- }) => void;
873
- 'mcp:tool:transform': (ctx: McpToolHookContext & {
874
- result: string | ToolResultContent[];
875
- outputBytes: number;
876
- }) => void;
877
- 'mcp:tool:error': (ctx: McpToolHookContext & {
878
- error: Error;
879
- }) => void;
880
- 'skills:resolve': (ctx: {
881
- skills: SkillConfig[];
882
- }) => void;
883
- 'skills:catalog': (ctx: {
884
- catalog: string;
885
- skills: SkillConfig[];
886
- }) => void;
887
- 'skills:activate': (ctx: {
888
- skill: SkillConfig;
889
- via: ActivationVia;
890
- }) => void;
891
- 'skills:deactivate': (ctx: {
892
- skill: SkillConfig;
893
- reason: DeactivationReason;
894
- }) => void;
895
- 'usage': (ctx: {
896
- turn: number;
897
- turnId: string;
898
- usage: TurnUsage;
899
- totalIn: number;
900
- totalOut: number;
901
- }) => void;
902
- 'output': (ctx: {
903
- output: Record<string, unknown>;
904
- schema: Record<string, unknown>;
905
- }) => void;
906
- /**
907
- * Fires when a turn's total tool-output bytes exceed `behavior.toolOutputBudget`.
908
- * Measured post-`tool:transform`. Loop injects a synthetic user message after
909
- * the tool-results turn instructing the model to summarize.
910
- */
911
- 'budget:exceeded': (ctx: {
912
- turn: number;
913
- turnId: string;
914
- bytes: number;
915
- budget: number;
916
- }) => void;
917
- /**
918
- * Fires when a per-tool budget configured via `behavior.toolBudgets` is
919
- * exceeded for a specific tool. `mode` reflects how the framework reacted:
920
- * `'steer'` lets the call run and queues a post-turn nudge; `'block'`
921
- * refuses the call outright with `Blocked: <message>`.
922
- *
923
- * `count` is the run-cumulative dispatched count just before this call.
924
- * Use `turnId` to correlate with `turn:after` if you need the integer turn
925
- * index. Distinct from `budget:exceeded` (byte-level) so consumers can
926
- * subscribe specifically; both can fire in the same turn.
927
- */
928
- 'tool-budget:exceeded': (ctx: {
929
- tool: string;
930
- count: number;
931
- max: number;
932
- turnId: string;
933
- mode: 'steer' | 'block';
934
- }) => void;
935
- 'agent:abort': (ctx: object) => void;
936
- /**
937
- * Run finished — fires on all exit paths (completion, maxTurns, abort).
938
- *
939
- * Since 4.0 the `AgentStats` carried here is **cumulative** across the
940
- * parent agent loop and every recursively-spawned sub-agent
941
- * (`totalIn` / `totalOut` / `cost` / `totalCacheRead` / `totalCacheCreation`).
942
- * For parent-loop-only counts use `ctx.turnUsage` (parent-only array);
943
- * for tree-wide turn counts use `flattenTurns(ctx).length`.
944
- */
945
- 'agent:done': (ctx: AgentStats) => void;
946
- 'session:start': (ctx: SessionHookContext & {
947
- runId: string;
948
- prompt: string;
949
- }) => void;
950
- 'session:end': (ctx: SessionHookContext & {
951
- runId: string;
952
- status: SessionEndStatus;
953
- turnRange: [number, number];
954
- }) => void;
955
- 'session:turns': (ctx: SessionHookContext & {
956
- turns: SessionTurn[];
957
- count: number;
958
- }) => void;
959
- 'session:meta': (ctx: SessionHookContext & {
960
- key: string;
961
- value: unknown;
962
- }) => void;
963
- 'session:save': (ctx: SessionHookContext) => void;
524
+ toolSearch?: {
525
+ tool?: false;
526
+ limit?: number;
527
+ };
964
528
  }
965
- interface AgentOptions {
966
- provider: Provider;
967
- /** Display name for the agent (used in traces/logs). */
529
+ /**
530
+ * One block of a multimodal user prompt.
531
+ *
532
+ * `agent.run({ prompt })` accepts either a plain string (treated as a single
533
+ * text part) or an array of these parts for multimodal inputs.
534
+ *
535
+ * `document` parts are routed per provider: PDF-style mime types are sent as
536
+ * native document blocks when the provider supports them; text documents are
537
+ * inlined as text with an attachment header. Providers that cannot handle an
538
+ * image or document throw early.
539
+ */
540
+ type PromptPart = PromptTextPart | PromptImagePart | PromptDocumentPart;
541
+ interface PromptTextPart {
542
+ type: 'text';
543
+ text: string;
544
+ }
545
+ interface PromptImagePart {
546
+ type: 'image';
547
+ /** IANA media type (e.g. `image/png`, `image/jpeg`) */
548
+ mediaType: string;
549
+ /** Base64-encoded payload */
550
+ data: string;
551
+ /** Optional display name */
968
552
  name?: string;
969
- /** Default system prompt injected when no system is provided at run time. */
970
- system?: string;
971
- /** Tool definitions available to the agent. Defaults to no tools. */
972
- tools?: Record<string, ToolDef>;
553
+ }
554
+ interface PromptDocumentPart {
555
+ type: 'document';
556
+ /** IANA media type (e.g. `application/pdf`, `text/plain`) */
557
+ mediaType: string;
558
+ /** Either a base64-encoded payload (`encoding: 'base64'`) or raw text (`encoding: 'text'`) */
559
+ data: string;
560
+ encoding: 'base64' | 'text';
561
+ /** Optional display name used in attachment headers */
562
+ name?: string;
563
+ }
564
+ /**
565
+ * A single block of structured tool-result content.
566
+ *
567
+ * MCP servers can return a mix of text, image, resource, and audio blocks. Tools
568
+ * return `string` for the common text-only case or `ToolResultContent[]` when they
569
+ * need to preserve non-text content (e.g. screenshots from a browser MCP).
570
+ *
571
+ * Providers that support native multi-part tool results (Anthropic, OpenAI Codex via
572
+ * pi-ai) route image blocks into their wire format verbatim; OpenAI-compat providers
573
+ * route them via a companion-user-message fallback when the underlying model/endpoint
574
+ * does not accept images inside tool-role messages.
575
+ */
576
+ type ToolResultContent = ToolResultTextContent | ToolResultImageContent;
577
+ interface ToolResultTextContent {
578
+ type: 'text';
579
+ text: string;
580
+ }
581
+ interface ToolResultImageContent {
582
+ type: 'image';
583
+ /** IANA media type (e.g. `image/png`, `image/jpeg`) */
584
+ mediaType: string;
585
+ /** Base64-encoded payload */
586
+ data: string;
587
+ }
588
+ /**
589
+ * Lossy flattener — converts `ToolResultContent[]` (or a plain string) to a single
590
+ * string. Image blocks are replaced with `[image: <media> — <n> b64 bytes]` markers.
591
+ *
592
+ * Use at UI boundaries where a string is required; providers that understand
593
+ * structured content should route the array through without flattening.
594
+ */
595
+ declare function toolResultToText(content: string | ToolResultContent[]): string;
596
+ /**
597
+ * Approximate byte length of a tool output as it goes back to the model.
598
+ *
599
+ * - Plain text: UTF-8 byte length.
600
+ * - Structured content: text blocks contribute their UTF-8 byte length; image
601
+ * blocks contribute their **base64 character length**, since that is what
602
+ * the model tokenizes (the wire-encoded payload, not the decoded image).
603
+ *
604
+ * Used by the agent loop to populate `outputBytes` on `tool:after`,
605
+ * `tool:transform`, `mcp:tool:after`, and `mcp:tool:transform` hooks so
606
+ * consumers can size-budget tool output without re-counting bytes themselves.
607
+ */
608
+ declare function toolOutputByteLength(content: string | ToolResultContent[]): number;
609
+ type SessionContentBlock = {
610
+ type: 'text';
611
+ text: string;
612
+ } | {
613
+ type: 'image';
614
+ mediaType: string;
615
+ data: string;
616
+ } | {
617
+ type: 'tool_call';
618
+ id: string;
619
+ name: string;
620
+ input: Record<string, unknown>;
621
+ } | {
622
+ type: 'tool_result';
623
+ callId: string;
973
624
  /**
974
- * Map canonical tool names to LLM-facing (aliased) names.
975
- *
976
- * Aliasing is **LLM-boundary-only**: the alias is what the provider's tool spec
977
- * carries and what the model calls the tool; the canonical name is what lives in
978
- * `session.turns` and what the agent uses to look up the tool implementation.
625
+ * Tool output either a plain string (text-only, the common case) or a structured
626
+ * array of content blocks (text + image for multimodal tools such as screenshots).
979
627
  */
980
- toolAliases?: Record<string, string>;
981
- /** Agent-level behavior defaults (overridden by run-level behavior) */
982
- behavior?: AgentBehavior;
983
- /** Execution context: where tools run. Defaults to in-process. */
984
- execution?: ExecutionContext;
985
- /** MCP servers to connect and expose as tools */
986
- mcpServers?: McpServerConfig[];
987
- /** Session for identity, turn persistence, and run tracking */
988
- session?: Session;
989
- /** Skills configuration */
990
- skills?: SkillsConfig;
628
+ output: string | ToolResultContent[];
629
+ isError?: boolean;
630
+ } | {
631
+ type: 'thinking';
632
+ text: string;
633
+ signature?: string;
991
634
  /**
992
- * Test seam replaces the default MCP connector with a custom
993
- * implementation. Bypasses the `mcpServers` normalization layer entirely
994
- * and is **not** part of the supported public API. Subject to change or
995
- * removal in any release.
996
- *
997
- * @internal
635
+ * Provider that minted `signature`. Signatures are provider-bound (Anthropic
636
+ * HMAC vs. OpenAI `encrypted_content`) and are dropped on cross-provider
637
+ * hops to avoid 400s. Unset means legacy/unknown forwarded as-is.
998
638
  */
999
- mcpConnector?: (configs: McpServerConfig[]) => Promise<McpConnection>;
639
+ signatureProducer?: 'anthropic' | 'openai';
640
+ } | {
641
+ type: 'redacted_thinking';
642
+ data: string;
643
+ } | {
1000
644
  /**
1001
- * Pre-connect MCP servers in the background as soon as `createAgent` returns,
1002
- * instead of deferring the bootstrap to the first `agent.run()`.
1003
- *
1004
- * Useful when MCP latency is the dominant cost of a cold start: callers that
1005
- * construct the agent early (e.g. at process init) can hide the bootstrap
1006
- * behind other setup work. If bootstrap fails, the error is stored and
1007
- * surfaced on the first `agent.run()` / `agent.warmup()`; the in-flight
1008
- * promise is `await`ed by both paths so the error is never silently lost.
645
+ * Opaque round-trip envelope for reasoning state minted by an OpenAI-compat
646
+ * gateway (currently OpenRouter). The gateway expects its own
647
+ * `reasoning_details` array echoed back verbatim on the next turn so the
648
+ * upstream model can resume an extended-reasoning chain across tool calls.
1009
649
  *
1010
- * No-op when `mcpServers` is empty. Default: `false`.
1011
- */
1012
- eager?: boolean;
1013
- }
1014
- interface Agent {
1015
- hooks: Hookable<AgentHooks>;
1016
- run: (options: AgentRunOptions) => Promise<AgentStats>;
1017
- abort: () => void;
1018
- steer: (message: string) => void;
1019
- followUp: (message: string) => void;
1020
- waitForIdle: () => Promise<void>;
1021
- /**
1022
- * Clear the agent's in-memory state (turns, queues, skill activations).
1023
- * Fires `skills:deactivate` with `reason: 'reset'` for each previously active
1024
- * skill. Awaiting lets host apps observe listener rejections.
650
+ * Stored opaquely because the items are provider-bound (Anthropic HMAC
651
+ * signatures, OpenAI `encrypted_content`, model-specific summary formats
652
+ * — all flowing through the gateway's normalized envelope).
1025
653
  */
1026
- reset: () => Promise<void>;
654
+ type: 'provider_reasoning';
655
+ producer: 'openrouter';
656
+ details: unknown[];
1027
657
  /**
1028
- * Destroy the execution context and clean up resources.
1029
- * Idempotentsafe to call from both a `finally` block and a signal handler.
658
+ * Model id that produced the details. Reasoning is bound to a specific
659
+ * upstream route a model switch on the next turn invalidates the
660
+ * embedded signatures, so the sender drops the block on mismatch.
1030
661
  */
1031
- destroy: () => Promise<void>;
662
+ model?: string;
663
+ };
664
+ interface SessionMessage {
665
+ role: 'user' | 'assistant';
666
+ content: SessionContentBlock[];
667
+ }
668
+ interface SessionTurn {
669
+ /** UUID — generated by the store if it provides generateTurnId, else crypto.randomUUID() */
670
+ id: string;
671
+ /** Run that produced this turn (e.g. 'run_1') */
672
+ runId?: string;
673
+ role: 'user' | 'assistant' | 'system';
674
+ content: SessionContentBlock[];
675
+ /** Token usage — only present on assistant turns */
676
+ usage?: TurnUsage;
677
+ /** Unix timestamp (Date.now()) when the turn was created */
678
+ createdAt: number;
679
+ }
680
+ /**
681
+ * Per-run hook registrations. Each entry can be a single handler or an array of handlers.
682
+ * Keys are `AgentHooks` event names (loose-typed here to avoid a circular import; agent.ts
683
+ * narrows it to the strongly-typed map).
684
+ */
685
+ type RunHookMap = Record<string, ((ctx: any) => unknown) | ((ctx: any) => unknown)[]>;
686
+ interface AgentRunOptions {
687
+ model?: string;
1032
688
  /**
1033
- * Explicitly activate a skill by name. Fires `skills:activate` with
1034
- * `via: 'explicit'`. Throws if the skill isn't in the resolved catalog or
1035
- * if the `maxActive` cap is reached. Idempotent activating an already-active
1036
- * skill is a no-op.
689
+ * User prompt. Optional when resuming a session with existing turns.
690
+ *
691
+ * Accepts either a plain string (single text part) or an array of `PromptPart`s for
692
+ * multimodal inputs (text, images, documents). See {@link PromptPart}.
1037
693
  */
1038
- activateSkill: (name: string) => Promise<void>;
694
+ prompt?: string | PromptPart[];
695
+ system?: string;
696
+ thinking?: ThinkingLevel;
697
+ /** Abort signal — when triggered, the agent stops after the current turn */
698
+ signal?: AbortSignal;
699
+ /** Behavior overrides for this run (overrides agent defaults) */
700
+ behavior?: AgentBehavior;
701
+ /** Tool overrides for this run. Pass {} for no tools. Omit to use agent tools. */
702
+ tools?: Record<string, ToolDef>;
1039
703
  /**
1040
- * Deactivate a skill by name. Fires `skills:deactivate` with `reason: 'explicit'`.
1041
- * No-op when the skill wasn't active.
704
+ * Per-run hook registrations. Each hook is attached before the run starts and
705
+ * detached in a finally block so handlers never leak across runs.
706
+ *
707
+ * Accepts either a single handler or an array (all handlers register).
1042
708
  */
1043
- deactivateSkill: (name: string) => Promise<void>;
709
+ hooks?: RunHookMap;
1044
710
  /**
1045
- * Pre-connect MCP servers without running a turn. Idempotent and concurrency-safe:
1046
- * - No MCP servers configured resolves immediately.
1047
- * - Connection already established resolves immediately.
1048
- * - Another `warmup()` / `run()` is bootstrapping → awaits the in-flight promise.
1049
- *
1050
- * Use from host code that wants to hide MCP bootstrap latency behind other
1051
- * startup work (UI init, auth, etc.). Safe to call multiple times and from
1052
- * multiple callers concurrently.
711
+ * Parent run id. Populated automatically by the `spawn` tool when the child
712
+ * shares the parent's session; recorded on the resulting `SessionRun` so the
713
+ * parent↔child run tree can be reconstructed from a persisted session.
1053
714
  */
1054
- warmup: () => Promise<void>;
1055
- readonly isRunning: boolean;
1056
- readonly turns: SessionTurn[];
1057
- readonly execution: ExecutionContext;
1058
- readonly handle: ExecutionHandle | null;
1059
- readonly session: Session | null;
1060
- /** Snapshot of currently active skills. */
1061
- readonly activeSkills: readonly ActiveSkill[];
715
+ parentRunId?: string;
1062
716
  /**
1063
- * Frozen view of the underlying `provider.meta`. Read-only to prevent
1064
- * accidental cross-agent contamination writes are rejected at runtime
1065
- * (via `Object.freeze`) and at compile time (via `Readonly`). To override
1066
- * model / capability defaults, construct a new provider.
717
+ * Zero-based subagent depth. 0 = top-level `agent.run()`, 1 = first-level
718
+ * child spawned by a parent agent, and so on. Used by the spawn tool to
719
+ * enforce `maxDepth` and to stamp `child:*` forwarded hook payloads.
1067
720
  */
1068
- readonly meta: Readonly<Record<string, unknown>>;
721
+ depth?: number;
1069
722
  }
1070
- declare function createAgent({
1071
- provider,
1072
- name: agentName,
1073
- system: agentSystem,
1074
- tools: agentTools,
1075
- toolAliases,
1076
- behavior: agentBehavior,
1077
- execution,
1078
- mcpServers,
1079
- session,
1080
- skills: agentSkills,
1081
- mcpConnector,
1082
- eager
1083
- }: AgentOptions): Agent;
1084
- //#endregion
1085
- //#region src/tools/types.d.ts
1086
723
  /**
1087
- * Runtime context passed to every tool execution.
1088
- * Provides access to the agent's provider, abort signal, execution environment, and hooks.
724
+ * Reason the provider gave for stopping the turn.
1089
725
  *
1090
- * The preset-y fields (`name`, `system`, `tools`, `toolAliases`, `mcpServers`, `skills`,
1091
- * `behavior`) mirror the agent's own options so child-spawning tools can inherit them.
726
+ * - `'stop'` — natural turn end (`end_turn` / `stop_sequence`).
727
+ * - `'tool-calls'` model emitted tool_use blocks.
728
+ * - `'length'` — `max_tokens` reached, or (Anthropic 4.6+) the response bumped
729
+ * against the model's context window mid-stream
730
+ * (`model_context_window_exceeded`). The partial response is preserved; the
731
+ * loop emits this reason so consumers can prune/retry.
732
+ * - `'content-filter'` — model refused.
733
+ * - `'pause'` — Anthropic `pause_turn`: a server-side mid-turn pause for very
734
+ * long thinking. The loop continues with a synthetic "Please continue."
735
+ * user message rather than terminating; consumers see the pause via this
736
+ * finish reason on the prior assistant turn.
737
+ * - `'error'` — provider classified the turn as failed.
738
+ * - `'other'` — unknown / unmapped.
1092
739
  */
1093
- interface ToolContext {
1094
- /** The LLM provider for this agent run */
1095
- provider: Provider;
1096
- /** Abort signal — tools should check this for early termination */
1097
- signal: AbortSignal;
1098
- /** The execution context (shell, filesystem, etc.) */
1099
- execution: ExecutionContext;
1100
- /** The active execution handle for the current agent run */
1101
- handle: ExecutionHandle;
1102
- /** Agent hooks for emitting events (e.g. spawn:complete) */
1103
- hooks: Hookable<AgentHooks>;
1104
- /** Agent display name (preset or user-supplied) */
1105
- name?: string;
1106
- /** Agent default system prompt */
1107
- system?: string;
1108
- /** Source tool map the agent was created with (pre-MCP-merge, pre-skills-injection) */
1109
- tools: Record<string, ToolDef>;
740
+ type TurnFinishReason = 'stop' | 'tool-calls' | 'length' | 'content-filter' | 'pause' | 'error' | 'other';
741
+ interface TurnUsage {
742
+ input: number;
743
+ output: number;
744
+ /** Tokens written to cache (Anthropic) */
745
+ cacheCreation?: number;
746
+ /** Tokens read from cache (Anthropic) */
747
+ cacheRead?: number;
748
+ /** Thinking/reasoning tokens used */
749
+ thinking?: number;
750
+ /** Cost in USD as reported by the provider (OpenRouter) */
751
+ cost?: number;
1110
752
  /**
1111
- * Map canonical tool names to LLM-facing (aliased) names.
1112
- *
1113
- * Aliasing is **LLM-boundary-only**:
1114
- * - The alias is what the provider's tool spec carries, what the model calls it, and
1115
- * what appears in `ToolHookContext.displayName` / `McpToolHookContext.displayName`.
1116
- * - The canonical name is what lives in `session.turns`, `ToolHookContext.name`, and
1117
- * what the agent uses to look up the tool implementation. Alias changes never
1118
- * desync persisted history.
753
+ * Why the model stopped this turn. Providers normalize native stop reasons to this union.
754
+ * Absent when the provider did not surface a reason (e.g. mock turns).
1119
755
  */
1120
- toolAliases?: Record<string, string>;
1121
- /** MCP servers configured on the agent (for child inheritance) */
1122
- mcpServers?: McpServerConfig[];
1123
- /** Skills configuration (for child inheritance) */
1124
- skills?: SkillsConfig;
1125
- /** Behavior defaults (for child inheritance) */
1126
- behavior?: AgentBehavior;
1127
- /** Turn ID that requested this tool call */
1128
- turnId: string;
1129
- /** Tool call ID from the model */
1130
- callId: string;
756
+ finishReason?: TurnFinishReason;
1131
757
  /**
1132
- * The run id this tool call is part of. Populated by the agent loop when
1133
- * invoking tools. Optional on the type so host code constructing contexts
1134
- * by hand (tests, direct tool invocations) doesn't have to synthesize one.
1135
- *
1136
- * Spawn-style tools rely on this to tag child runs with `parentRunId` so
1137
- * the subagent tree can be reconstructed from a persisted session.
758
+ * The model ID the provider ultimately used. May differ from the requested model when the
759
+ * provider remaps aliases. Absent for providers that do not echo a model ID.
1138
760
  */
1139
- runId?: string;
761
+ modelId?: string;
762
+ }
763
+ interface AgentStats {
1140
764
  /**
1141
- * The agent's session, when one was provided to `createAgent`. Tools that
1142
- * want to persist their own state (or, in the case of `spawn`, inherit the
1143
- * parent's session for child persistence) can read from here.
765
+ * Cumulative input tokens across the parent agent loop **and** every
766
+ * recursively-spawned sub-agent. Use this for billing / token-ledger
767
+ * consumption.
1144
768
  */
1145
- session?: Session;
769
+ totalIn: number;
770
+ /** Cumulative output tokens. Same semantics as {@link AgentStats.totalIn}. */
771
+ totalOut: number;
1146
772
  /**
1147
- * Subagent depth for the agent owning this tool call. 0 = top-level,
1148
- * 1 = first-level child, Used by spawn to enforce a `maxDepth` cap.
1149
- * Undefined is treated as 0 by spawn.
773
+ * Cumulative cache-read tokens across the parent agent loop and every
774
+ * recursively-spawned sub-agent. Surfaced at the top level (rather than
775
+ * only per-`TurnUsage`) because Anthropic prices cache reads at a separate
776
+ * line-item rate from regular input — billing-correct cost computation
777
+ * needs this number directly. Always `0` for providers that don't report
778
+ * cache usage.
1150
779
  */
1151
- depth?: number;
1152
- }
1153
- interface ToolDef {
1154
- spec: ToolSpec;
780
+ totalCacheRead: number;
1155
781
  /**
1156
- * Execute the tool and return its output.
1157
- *
1158
- * Return a plain string for text-only tools (the common case). Return a
1159
- * `ToolResultContent[]` when the tool produces non-text content (images, mixed
1160
- * text+image) that the provider can route through natively (Anthropic
1161
- * `tool_result.content` arrays, OpenAI Codex pi-ai) or through the
1162
- * companion-user-message fallback (OpenAI Chat Completions).
782
+ * Cumulative cache-creation tokens across the parent agent loop and every
783
+ * recursively-spawned sub-agent. Same rationale as
784
+ * {@link AgentStats.totalCacheRead} separate Anthropic billing rate.
785
+ * Always `0` for providers that don't report cache usage.
1163
786
  */
1164
- execute: (input: Record<string, unknown>, ctx: ToolContext) => Promise<string | ToolResultContent[]>;
1165
- }
1166
- type ToolMap = Map<string, ToolDef>;
1167
- //#endregion
1168
- //#region src/types.d.ts
1169
- /**
1170
- * Thinking / extended-reasoning configuration.
1171
- *
1172
- * - `'off'` — no thinking.
1173
- * - `'minimal' | 'low' | 'medium' | 'high'` — explicit token budget. Maps to
1174
- * provider-specific reasoning controls (Anthropic `thinking.type='enabled'`
1175
- * with a budget; OpenAI `reasoning_effort`).
1176
- * - `'adaptive'` — let the model decide per-turn whether and how much to think.
1177
- * Anthropic-only (`thinking.type='adaptive'`). Other providers fall back to
1178
- * no reasoning when this value is supplied.
1179
- */
1180
- type ThinkingLevel = 'off' | 'minimal' | 'low' | 'medium' | 'high' | 'adaptive';
1181
- interface McpServerConfig {
1182
- /** Display name (used for tool namespacing) */
1183
- name: string;
1184
- /** Transport type */
1185
- transport: 'stdio' | 'sse' | 'streamable-http';
1186
- /** For stdio: command to run */
1187
- command?: string;
1188
- /** For stdio: command arguments */
1189
- args?: string[];
787
+ totalCacheCreation: number;
1190
788
  /**
1191
- * For stdio: environment variables to pass to the server process.
789
+ * Number of parent agent-loop turns. Children's turn counts live under
790
+ * `children[].stats.turns` and are NOT folded in here — a single "turns"
791
+ * number for the whole tree would conflate two different measures
792
+ * (parent-loop iterations vs. tree-wide tool-call rounds).
1192
793
  *
1193
- * Merged on top of the MCP SDK's default inherited environment — a safety
1194
- * whitelist (`PATH`, `HOME`, `LANG`, `SHELL`, `USER` on POSIX; `APPDATA`,
1195
- * `PATH`, ... on Win32). Setting this to `{}` no longer strips `PATH` from
1196
- * the child process. Set {@link McpServerConfig.strictEnv} to `true` to
1197
- * pass `env` verbatim with no inherited defaults.
794
+ * Tree-wide turn count: `flattenTurns(stats).length`.
1198
795
  */
1199
- env?: Record<string, string>;
796
+ turns: number;
1200
797
  /**
1201
- * When true, {@link McpServerConfig.env} is passed verbatim to the spawned
1202
- * process the MCP SDK's default inherited environment (`PATH`, `HOME`, ...)
1203
- * is NOT merged in. Most consumers should leave this off; the default merge
1204
- * prevents `spawn ENOENT` when a stdio server declares an `env` without
1205
- * restating `PATH`.
798
+ * Wall-clock duration of the top-level `agent.run()` call, in milliseconds.
799
+ * Children run during parent tool calls so this naturally subsumes child
800
+ * wall time sequential children inflate it, parallel children compress
801
+ * into the parent's window.
1206
802
  */
1207
- strictEnv?: boolean;
1208
- /** For sse/streamable-http: server URL */
1209
- url?: string;
1210
- /** Optional headers for HTTP transports */
1211
- headers?: Record<string, string>;
803
+ elapsed: number;
1212
804
  /**
1213
- * Timeout in milliseconds for MCP server bootstrap (connect + tool discovery).
1214
- *
1215
- * Zidane connects MCP servers lazily on the first `run()`. Without a
1216
- * bootstrap timeout, a slow or hung server can delay the first provider call
1217
- * for an arbitrarily long time even when that MCP server is never used.
1218
- *
1219
- * Default: `10000`.
805
+ * Per-turn usage breakdown for the **parent loop only**. Children's per-turn
806
+ * usages live under `children[].stats.turnUsage`. Use {@link flattenTurns}
807
+ * to walk the full tree.
1220
808
  */
1221
- bootstrapTimeout?: number;
1222
- /** Timeout in milliseconds for MCP tool calls (default: 30000) */
1223
- toolTimeout?: number;
809
+ turnUsage?: TurnUsage[];
1224
810
  /**
1225
- * Allow-list of tool names to expose. Names match the upstream tool name
1226
- * (NOT the namespaced `mcp_{server}_{tool}` form). Tools not in the list are
1227
- * dropped before registration the model never sees them in its catalog and
1228
- * the wire cost of advertising them is avoided.
1229
- *
1230
- * Mutually exclusive with {@link McpServerConfig.disabledTools} passing both
1231
- * throws at bootstrap time.
811
+ * Cumulative cost in USD parent loop plus every recursively-spawned
812
+ * sub-agent. Sums per-turn `TurnUsage.cost` reported by the provider.
813
+ * Absent when neither parent nor any descendant reported a non-zero cost.
814
+ */
815
+ cost?: number;
816
+ /** Stats from child agents spawned during this run, in completion order. Recursive. */
817
+ children?: ChildRunStats[];
818
+ /** Structured output from schema enforcement (only present when behavior.schema is set) */
819
+ output?: Record<string, unknown>;
820
+ /**
821
+ * Milliseconds from the start of `agent.run()` to the first observable signal from the
822
+ * provider (first `stream:text`, `stream:thinking`, or `tool:before` event).
1232
823
  *
1233
- * Composes with {@link McpServerConfig.toolFilter}: allow-list applies first,
1234
- * then the predicate. Composes with the `mcp:tools:filter` hook: config-side
1235
- * filters apply first, then the hook can further narrow the list.
824
+ * Absent when the run produced no observable signals (e.g. aborted before any stream event).
1236
825
  */
1237
- enabledTools?: string[];
826
+ timeTillFirstTokenMs?: number;
827
+ }
828
+ interface ChildRunStats {
829
+ id: string;
830
+ task: string;
1238
831
  /**
1239
- * Deny-list of tool names. Tools matching are dropped before registration.
1240
- * Same matching semantics as {@link McpServerConfig.enabledTools}.
832
+ * The child agent's full {@link AgentStats}. Cumulative for that child's
833
+ * own subtree (child loop + its grandchildren). Do **not** sum
834
+ * `ctx.stats.totalIn` across `spawn:complete` events to derive top-level
835
+ * totals — `agent.run()`'s return value is the canonical cumulative root.
1241
836
  */
1242
- disabledTools?: string[];
837
+ stats: AgentStats;
1243
838
  /**
1244
- * Custom predicate run on each upstream tool. Return `true` to keep, `false`
1245
- * to drop. Receives the raw `listTools()` payload useful for filtering by
1246
- * description, schema shape, or other metadata that an allow/deny list can't
1247
- * express.
1248
- *
1249
- * Runs after the allow/deny filter but before the `mcp:tools:filter` hook.
839
+ * Subagent depth when this child ran. 1 = direct child of the top-level
840
+ * agent, 2 = grandchild, etc. Useful for telemetry that wants to group
841
+ * runs by depth.
1250
842
  */
1251
- toolFilter?: (tool: {
1252
- name: string;
1253
- description?: string | null;
1254
- inputSchema?: unknown;
1255
- }) => boolean;
843
+ depth?: number;
844
+ /**
845
+ * Terminal state of the child run. `'completed'` is the default. Exposed so
846
+ * a parent reading `stats.children` can distinguish aborted/timed-out
847
+ * children without re-parsing the returned string.
848
+ */
849
+ status?: 'completed' | 'aborted' | 'timeout' | 'error';
850
+ /**
851
+ * Final structured output when the child was run with `behavior.schema`.
852
+ * Mirrors `AgentStats.output` but is surfaced here so the parent can read
853
+ * it without peeking at the nested `stats` bag.
854
+ */
855
+ output?: Record<string, unknown>;
856
+ }
857
+ /**
858
+ * Base context for tool execution hooks.
859
+ *
860
+ * `name` is the canonical tool identity — the spec name registered on the agent (or the
861
+ * `mcp_{server}_{tool}` name for MCP tools). Hooks should policy-match against `name`.
862
+ *
863
+ * `displayName` is the outward-facing name — the alias surfaced to the LLM when
864
+ * `AgentOptions.toolAliases` maps the canonical name; otherwise equal to `name`.
865
+ * UI/telemetry adapters should emit `displayName`.
866
+ *
867
+ * Canonical vs. alias matters on session resume: `session.turns` persists canonical
868
+ * names only, so renaming an alias cannot desync history.
869
+ */
870
+ interface ToolHookContext {
871
+ turnId: string;
872
+ callId: string;
873
+ /** Canonical tool name (spec name). Stable across alias-map changes. */
874
+ name: string;
875
+ /** Aliased (wire) name — equal to `name` when no alias is defined. */
876
+ displayName: string;
877
+ input: Record<string, unknown>;
878
+ }
879
+ /**
880
+ * Base context for MCP tool hooks.
881
+ *
882
+ * `tool` is the native tool name on the MCP server. `server` is the configured server
883
+ * name. The canonical zidane-namespaced identity is `mcp_{server}_{tool}`.
884
+ *
885
+ * `displayName` equals the canonical namespaced name unless the agent has aliased
886
+ * this MCP tool via `AgentOptions.toolAliases`; in which case `displayName` is the
887
+ * alias that the LLM sees.
888
+ */
889
+ interface McpToolHookContext {
890
+ turnId: string;
891
+ callId: string;
892
+ server: string;
893
+ tool: string;
894
+ /** Aliased wire name for this MCP tool, or the canonical `mcp_{server}_{tool}` name. */
895
+ displayName: string;
896
+ input: Record<string, unknown>;
897
+ }
898
+ /** Base context for session hooks */
899
+ interface SessionHookContext {
900
+ sessionId: string;
901
+ }
902
+ /** Base context for spawn hooks */
903
+ interface SpawnHookContext {
904
+ id: string;
905
+ task: string;
1256
906
  /**
1257
- * Per-server override for {@link AgentBehavior.toolDisclosure}. When set,
1258
- * this server's tools follow this disclosure mode regardless of the
1259
- * agent-wide default. Useful when one big MCP server (200+ tools) should
1260
- * stay lazy while smaller servers stay eager.
1261
- *
1262
- * Default: inherits from `behavior.toolDisclosure`.
907
+ * Subagent depth for the spawn. 1 = direct child of the top-level agent.
908
+ * Present on spawn:before/complete/error. Absent for grandchild spawns that
909
+ * bubble through `child:*` events (which carry their own `depth`).
1263
910
  */
1264
- disclosure?: 'eager' | 'lazy';
911
+ depth?: number;
1265
912
  }
1266
- type ToolExecutionMode = 'sequential' | 'parallel';
1267
- interface AgentBehavior {
1268
- /** Tool execution mode (default: 'sequential') */
1269
- toolExecution?: ToolExecutionMode;
913
+ /** Context for stream hooks */
914
+ interface StreamHookContext {
915
+ turnId: string;
916
+ }
917
+ /** Context for OAuth refresh hooks */
918
+ interface OAuthRefreshHookContext {
919
+ provider: string;
920
+ providerId: string;
921
+ source: 'params' | 'file';
922
+ previousCredentials: Record<string, unknown> & {
923
+ access: string;
924
+ refresh: string;
925
+ expires: number;
926
+ };
927
+ credentials: Record<string, unknown> & {
928
+ access: string;
929
+ refresh: string;
930
+ expires: number;
931
+ };
932
+ }
933
+ type SessionEndStatus = 'completed' | 'aborted' | 'error';
934
+ //#endregion
935
+ //#region src/providers/anthropic.d.ts
936
+ /**
937
+ * Server-side context-management config — the body of `context_management` on
938
+ * the Messages API. Typed loosely (Record-of-unknown) so we don't pin a specific
939
+ * SDK schema version: the v0.90 SDK does not yet type this field, but the wire
940
+ * format is stable behind the `context-management-2025-06-27` beta.
941
+ *
942
+ * See: https://docs.anthropic.com/en/docs/build-with-claude/context-management
943
+ */
944
+ interface AnthropicContextManagement {
945
+ edits?: Array<Record<string, unknown>>;
946
+ [key: string]: unknown;
947
+ }
948
+ interface AnthropicParams {
949
+ apiKey?: string;
950
+ access?: string;
951
+ refresh?: string;
952
+ expires?: number;
953
+ defaultModel?: string;
1270
954
  /**
1271
- * Max agent loop iterations.
1272
- *
1273
- * Default: unlimited (Infinity). The loop runs until the model signals
1274
- * completion (no tool calls / `end_turn`), the abort signal fires, or this
1275
- * cap is hit. Set a finite value as a safety net for runaway loops.
955
+ * Optional override for the Anthropic SDK base URL. Honored end-to-end — headers and
956
+ * routing pass through to the forwarded host. Useful for proxies (e.g. corporate
957
+ * gateways, internal router).
1276
958
  */
1277
- maxTurns?: number;
1278
- /** Max tokens per LLM response (default: 16384) */
1279
- maxTokens?: number;
1280
- /** Thinking token budget — overrides the level-based default when set */
1281
- thinkingBudget?: number;
1282
- /** JSON Schema for structured output enforcement */
1283
- schema?: Record<string, unknown>;
959
+ baseURL?: string;
1284
960
  /**
1285
- * Enable provider prompt caching. When on (default), the provider marks the
1286
- * system prompt, tools, and the last stable message with cache breakpoints so
1287
- * the shared prefix is served from cache across turns.
1288
- *
1289
- * - Anthropic: `cache_control: { type: 'ephemeral' }` on the last `system`
1290
- * content part, the last tool, and the last message content part.
1291
- * - OpenAI-compatible / OpenRouter: same shape — honored by Anthropic-backed
1292
- * OpenRouter routes and by Gemini; ignored (no-op) by providers that cache
1293
- * automatically (OpenAI, DeepSeek, Grok, Groq, Moonshot).
961
+ * Additional `anthropic-beta` flags to opt into. Merged with the OAuth-path
962
+ * defaults (`claude-code-20250219`, `oauth-2025-04-20`); duplicates are
963
+ * de-duped. Examples:
1294
964
  *
1295
- * Usage is surfaced via `TurnUsage.cacheRead` / `TurnUsage.cacheCreation`.
965
+ * - `'context-management-2025-06-27'` server-side context compaction
966
+ * (token-accurate; pair with {@link AnthropicParams.contextManagement}).
967
+ * - `'token-efficient-tools-2026-03-28'` — terser tool_use wire format.
968
+ * - `'interleaved-thinking-2025-05-14'` — think between tool calls within
969
+ * one turn.
970
+ * - `'redact-thinking-2026-02-12'` — replace large thinking blocks with
971
+ * stubs server-side.
972
+ * - `'prompt-caching-scope-2026-01-05'` — extended prompt-cache scope.
1296
973
  *
1297
- * Default: `true`.
974
+ * Honored on both the OAuth and API-key paths.
1298
975
  */
1299
- cache?: boolean;
976
+ extraBetas?: readonly string[];
1300
977
  /**
1301
- * Soft per-turn cap on total tool-output bytes. When the sum of `outputBytes`
1302
- * across a turn's tool results exceeds this value, the loop injects a
1303
- * synthetic user message instructing the model to summarize before calling
1304
- * more tools, and fires the `budget:exceeded` hook.
978
+ * Server-side context-management directive. Sent on the request body as
979
+ * `context_management`. Requires the `context-management-2025-06-27` beta
980
+ * add it to {@link AnthropicParams.extraBetas}.
1305
981
  *
1306
- * Measured **post-`tool:transform`** so consumer truncation counts toward the
1307
- * budget. Off by default (undefined / `0` disables the check). A reasonable
1308
- * starting value for OSS-model integrations is `32768`.
982
+ * Typed loosely so future Anthropic schema additions land without an SDK
983
+ * bump. A typical compaction edit:
984
+ *
985
+ * ```ts
986
+ * contextManagement: {
987
+ * edits: [{
988
+ * type: 'clear_tool_uses_20250919',
989
+ * trigger: { type: 'input_tokens', value: 180_000 },
990
+ * clear_at_least: { type: 'input_tokens', value: 140_000 },
991
+ * clear_tool_inputs: ['Read', 'Bash', 'Grep'],
992
+ * }],
993
+ * }
994
+ * ```
1309
995
  */
1310
- toolOutputBudget?: number;
996
+ contextManagement?: AnthropicContextManagement;
1311
997
  /**
1312
- * Deduplicate identical re-reads of the same file in `read_file`. When the
1313
- * model re-reads a file with the same slice and the bytes haven't changed
1314
- * since the last read in this session, the tool returns a short stub
1315
- * instead of re-emitting the full content. Pairs with the read-before-edit
1316
- * guard in `edit` / `multi_edit`.
1317
- *
1318
- * Requires a session (set via `createSession()`); without one, the flag is
1319
- * a no-op since per-session state has nowhere to live.
998
+ * Generic pass-through for fields on the Messages API request body that the
999
+ * SDK does not yet type. Spread into the request before the typed fields,
1000
+ * so explicit options ({@link AnthropicParams.contextManagement} and the
1001
+ * built-in fields like `model` / `tools` / `messages`) win on collision.
1320
1002
  *
1321
- * Default: `true`.
1003
+ * Forward-compat escape hatch for new Anthropic betas — when a future flag
1004
+ * ships before zidane has a dedicated typed knob, set it here without
1005
+ * waiting on a release. Most fields will still need the matching beta in
1006
+ * {@link AnthropicParams.extraBetas}.
1322
1007
  */
1323
- dedupReads?: boolean;
1008
+ extraBodyParams?: Record<string, unknown>;
1009
+ }
1010
+ declare function anthropic(anthropicParams?: AnthropicParams): Provider;
1011
+ //#endregion
1012
+ //#region src/providers/cerebras.d.ts
1013
+ interface CerebrasParams {
1014
+ apiKey?: string;
1015
+ defaultModel?: string;
1324
1016
  /**
1325
- * Taper the thinking budget over the course of a run. Late turns are
1326
- * usually checkpoint / cleanup work where reasoning rarely pays for
1327
- * itself; early turns benefit most. Two forms:
1328
- *
1329
- * - **Struct** — geometric decay starting after `afterTurn`, multiplying by
1330
- * `factor` each subsequent turn, clamped to `floor`. Example
1331
- * `{ afterTurn: 5, factor: 0.5, floor: 1024 }` with a base budget of 8192:
1332
- * turns 1-5 = 8192, turn 6 = 4096, turn 7 = 2048, turn 8+ = 1024.
1333
- * - **Function** — `(runTurn, baseBudget) => number`. Arbitrary curves;
1334
- * `runTurn` is 1-indexed, run-relative (resumed sessions reset).
1335
- *
1336
- * No-op when `thinkingBudget` is unset. Honored by every provider that
1337
- * respects `thinkingBudget` (anthropic explicit-budget `enabled` path,
1338
- * adaptive `maxTokensCap`, openai-compat `max_tokens` padding).
1339
- *
1340
- * Default: `undefined` (no decay).
1017
+ * Provider capability flags. Cerebras currently serves text-only OSS models
1018
+ * (GLM, Llama-family, Qwen-family) default: `{ vision: false, imageInToolResult: false }`.
1019
+ * Override when routing to a vision-capable deployment.
1341
1020
  */
1342
- thinkingDecay?: {
1343
- afterTurn: number;
1344
- factor: number;
1345
- floor: number;
1346
- } | ((runTurn: number, baseBudget: number) => number);
1021
+ capabilities?: ProviderCapabilities;
1022
+ }
1023
+ /**
1024
+ * Cerebras provider.
1025
+ *
1026
+ * Thin wrapper around {@link openaiCompat} with Cerebras-specific defaults
1027
+ * (base URL, default model).
1028
+ */
1029
+ declare function cerebras(params?: CerebrasParams): Provider;
1030
+ //#endregion
1031
+ //#region src/providers/openai.d.ts
1032
+ interface OpenAIParams {
1033
+ /** OpenAI Codex OAuth access token. Falls back to OPENAI_CODEX_API_KEY and .credentials.json. */
1034
+ apiKey?: string;
1035
+ /** Alias for apiKey, matching the OAuth credential field. */
1036
+ access?: string;
1037
+ refresh?: string;
1038
+ expires?: number;
1039
+ accountId?: string;
1040
+ defaultModel?: string;
1041
+ transport?: 'sse' | 'websocket' | 'auto';
1042
+ }
1043
+ declare function openai(params?: OpenAIParams): Provider;
1044
+ //#endregion
1045
+ //#region src/providers/openai-compat.d.ts
1046
+ /**
1047
+ * HTTP error thrown when an OpenAI-compatible endpoint returns a non-OK response.
1048
+ *
1049
+ * The body is best-effort JSON-parsed; `error.message` / `error.code` / `error.type`
1050
+ * are extracted for clean downstream classification.
1051
+ */
1052
+ declare class OpenAICompatHttpError extends Error {
1053
+ readonly status: number;
1054
+ readonly providerCode?: string;
1055
+ readonly bodyText: string;
1056
+ constructor(status: number, bodyText: string);
1057
+ }
1058
+ /**
1059
+ * Classify an OpenAI-compatible error into `ClassifiedError`.
1060
+ *
1061
+ * Recognizes:
1062
+ * - `AbortError` (from fetch) → `aborted`.
1063
+ * - `OpenAICompatHttpError` with a context-exceeded code or message → `context_exceeded`.
1064
+ * - Any other `OpenAICompatHttpError` → `provider_error`.
1065
+ *
1066
+ * Returns `null` for unrecognized error shapes (the loop falls back to `AgentProviderError`).
1067
+ */
1068
+ declare function classifyOpenAICompatError(err: unknown): ClassifiedError | null;
1069
+ /**
1070
+ * Map an OpenAI-compatible `finish_reason` string to the zidane `TurnFinishReason` union.
1071
+ */
1072
+ declare function mapOAIFinishReason(reason: string | null | undefined): TurnFinishReason | undefined;
1073
+ /**
1074
+ * Auth header config. `scheme` is prepended to the api key with a space, e.g.
1075
+ * `{ name: 'Authorization', scheme: 'Bearer' }` → `Authorization: Bearer <key>`.
1076
+ * Omit `scheme` for raw header values (e.g. `{ name: 'X-Api-Key' }` → `X-Api-Key: <key>`).
1077
+ *
1078
+ * Real-world examples:
1079
+ * - Default OpenAI / OpenRouter / Cerebras: `{ name: 'Authorization', scheme: 'Bearer' }`.
1080
+ * - Baseten: `{ name: 'Authorization', scheme: 'Api-Key' }`.
1081
+ * - Some gateways: `{ name: 'X-Api-Key' }`.
1082
+ */
1083
+ interface OpenAICompatAuthHeader {
1084
+ name: string;
1085
+ scheme?: string;
1086
+ }
1087
+ interface OpenAICompatParams {
1088
+ /** Bearer-style API key. */
1089
+ apiKey: string;
1090
+ /** Base URL — `/chat/completions` is appended. */
1091
+ baseURL: string;
1092
+ /** Default model id when `run({ model })` is omitted. */
1093
+ defaultModel?: string;
1094
+ /** Provider name exposed as `Provider.name`. Defaults to `'openai-compat'`. */
1095
+ name?: string;
1096
+ /** Auth header shape. Defaults to `{ name: 'Authorization', scheme: 'Bearer' }`. */
1097
+ authHeader?: OpenAICompatAuthHeader;
1098
+ /** Extra headers sent with every request (e.g. referer, user-agent). */
1099
+ extraHeaders?: Record<string, string>;
1347
1100
  /**
1348
- * Per-tool soft call budget for this run. Keyed by **canonical** tool name.
1349
- * On the first call after the run-cumulative dispatched count for that tool
1350
- * reaches `max`, the framework fires `onExceed`:
1351
- *
1352
- * - `'steer'` (default) — let the call execute, but emit a synthetic user
1353
- * message after the turn that nudges the model away from re-calling the
1354
- * tool. Reuses the existing post-turn steer pathway used by
1355
- * `toolOutputBudget`. Fires `tool-budget:exceeded` with `mode: 'steer'`.
1356
- * - `'block'` — refuse the call via `tool:gate` `block`. The model sees a
1357
- * `Blocked: <reason>` tool result. Fires `tool-budget:exceeded` with
1358
- * `mode: 'block'`.
1359
- * - **Function** — `(ctx) => { mode, message }`. The consumer supplies the
1360
- * steering / refusal text and chooses the mode dynamically.
1361
- *
1362
- * Counts include both real dispatches and dedup substitutes (Z19 hits).
1363
- * Excludes calls already blocked by an earlier gate (skill allow-list,
1364
- * consumer hook). Tool dispatched by spawned subagents has its own per-run
1365
- * counter — child counts never charge the parent.
1366
- *
1367
- * For MCP tools, key by the namespaced wire name (`mcp_<server>_<tool>`).
1368
- *
1369
- * Atomic in parallel mode: the middleware tracks its own per-tool
1370
- * approval counter, incremented synchronously at gate-time. A
1371
- * 4-call parallel batch against `max: 2` will let the first 2 through
1372
- * and refuse the rest, even though the loop's `runToolCounts` only
1373
- * propagates between calls (not within a single batch's gate fan-out).
1101
+ * Provider-level capability flags. Routed into the message shaper and the
1102
+ * agent loop so images in tool results + user messages are handled correctly
1103
+ * for the underlying model.
1374
1104
  *
1375
- * Default: `undefined` (no budget enforcement).
1105
+ * Defaults when omitted: `vision: false`, `imageInToolResult: false` a
1106
+ * conservative assumption matching most OSS text-only OpenAI-compat
1107
+ * endpoints. Override when routing to a known vision-capable endpoint
1108
+ * (e.g. OpenRouter vision models, Baseten image-capable deployments).
1376
1109
  */
1377
- toolBudgets?: Record<string, {
1378
- max: number;
1379
- onExceed?: 'steer' | 'block' | ((ctx: {
1380
- tool: string;
1381
- count: number;
1382
- max: number;
1383
- }) => {
1384
- mode: 'steer' | 'block';
1385
- message: string;
1386
- });
1387
- }>;
1110
+ capabilities?: ProviderCapabilities;
1388
1111
  /**
1389
- * Generic per-tool argument deduplication. Keyed by the tool's **canonical**
1390
- * name (alias-stable). Each entry is a hasher: `(input) => string | undefined`.
1391
- *
1392
- * **Hasher contract** — three return values, three meanings:
1393
- *
1394
- * | Return | Meaning |
1395
- * |-------------------------|------------------------------------------------------------------------|
1396
- * | a non-empty string | Cache key for this call. Equal keys (most-recent-only, this session) |
1397
- * | | replay the prior recorded result without re-dispatching the tool. |
1398
- * | `undefined` | **Skip dedup for this call.** The tool runs normally; nothing recorded.|
1399
- * | `''` / non-string | Treated identically to `undefined` (defensive: no dedup, no error). |
1112
+ * Whether this endpoint honors `cache_control: { type: 'ephemeral' }` markers
1113
+ * on message content parts and tool definitions.
1400
1114
  *
1401
- * The `undefined` opt-out is the way to say *"this specific call is not
1402
- * cacheable"* (timestamps in input, randomness baked in, debug flags). It
1403
- * is **not** the same as `JSON.stringify(input)` — that would dedup against
1404
- * the verbatim input. Pick one explicitly:
1115
+ * - `true` inject markers when the caller asks for caching. OpenRouter routes
1116
+ * to Anthropic/Gemini forward the markers; routes to OpenAI/DeepSeek/
1117
+ * Grok/Groq/Moonshot ignore them (those backends cache automatically).
1118
+ * - `false` — never inject markers. Safe default for endpoints that strictly
1119
+ * validate the request schema (OpenAI direct, most OSS inference
1120
+ * servers) and would reject unknown fields.
1405
1121
  *
1406
- * ```ts
1407
- * // Always cache by full input — every identical re-call dedups.
1408
- * dedupTools: { todowrite: input => JSON.stringify(input) }
1122
+ * Default: `false`. The `openrouter` wrapper sets this to `true`.
1123
+ */
1124
+ cacheBreakpoints?: boolean;
1125
+ /**
1126
+ * Whether this endpoint speaks OpenRouter's normalized reasoning envelope —
1127
+ * `reasoning: { effort | max_tokens | exclude }` on requests and structured
1128
+ * `reasoning_details[]` on assistant messages, round-tripped to preserve
1129
+ * extended-reasoning state across turns.
1409
1130
  *
1410
- * // Cache by a normalized subset; non-cacheable shapes opt out.
1411
- * dedupTools: {
1412
- * execute_sql: (input) => {
1413
- * const q = typeof input.query === 'string' ? input.query.trim().toLowerCase() : undefined
1414
- * if (!q || q.includes('now()') || q.includes('random()')) return undefined
1415
- * return q
1416
- * },
1417
- * }
1418
- * ```
1131
+ * - `true` — map zidane's `behavior.thinking` / `behavior.thinkingBudget` to
1132
+ * the request's `reasoning` field, capture `reasoning_details`
1133
+ * from streaming responses into `provider_reasoning` blocks, and
1134
+ * echo them back on subsequent assistant messages.
1135
+ * - `false` never set the field; drop any stored `provider_reasoning`
1136
+ * blocks before sending. Safe default for hosts that strict-
1137
+ * validate the request schema.
1419
1138
  *
1420
- * On a hit, the previously-recorded result is replayed as the tool_result
1421
- * without dispatching the tool. The substitution flows through `tool:gate`
1422
- * `result` (Z20), so `tool:after` and `tool:transform` still fire.
1139
+ * Default: `false`. The `openrouter` wrapper sets this to `true`.
1140
+ */
1141
+ supportsReasoning?: boolean;
1142
+ /**
1143
+ * Generic pass-through for fields on the Chat Completions request body that
1144
+ * zidane does not yet type. Spread into the request before the typed core
1145
+ * (model / messages / tools / max_tokens / stream / tool_choice), so
1146
+ * explicit options always win on collision.
1423
1147
  *
1424
- * Requires a session (`createSession()`); without one, the map is a silent
1425
- * no-op since per-session state has nowhere to live. Tools with side
1426
- * effects or non-deterministic outputs (network, time, randomness) MUST
1427
- * NOT be listed — there is no safety net beyond the consumer's hasher.
1148
+ * Forward-compat escape hatch for endpoints that ship one-off fields ahead
1149
+ * of zidane (e.g. OpenAI `reasoning_effort`, OpenRouter `provider` routing,
1150
+ * vendor-specific `safety_level` knobs).
1151
+ */
1152
+ extraBodyParams?: Record<string, unknown>;
1153
+ }
1154
+ /**
1155
+ * Factory for any OpenAI-compatible HTTP endpoint.
1156
+ *
1157
+ * Speaks the standard `POST /chat/completions` + `stream: true` + SSE dialect.
1158
+ * Thin wrappers (`openrouter`, `cerebras`) call this with pinned defaults.
1159
+ *
1160
+ * @example Baseten (non-standard auth scheme)
1161
+ * ```ts
1162
+ * openaiCompat({
1163
+ * name: 'baseten',
1164
+ * apiKey: process.env.BASETEN_API_KEY!,
1165
+ * baseURL: process.env.BASETEN_PROXY_URL!,
1166
+ * authHeader: { name: 'Authorization', scheme: 'Api-Key' },
1167
+ * })
1168
+ * ```
1169
+ */
1170
+ declare function openaiCompat(params: OpenAICompatParams): Provider;
1171
+ //#endregion
1172
+ //#region src/providers/openrouter.d.ts
1173
+ interface OpenRouterParams {
1174
+ apiKey?: string;
1175
+ defaultModel?: string;
1176
+ /**
1177
+ * Provider capability flags. OpenRouter itself is a router — whether vision or
1178
+ * native image-in-tool-result are supported depends on the downstream model.
1179
+ * Default: `{ vision: true, imageInToolResult: false }` — matches the default
1180
+ * `anthropic/claude-sonnet-4-6` model (vision-capable via companion user-message
1181
+ * fallback since OpenRouter exposes Claude over the Chat Completions dialect).
1428
1182
  *
1429
- * For MCP tools, key by the namespaced wire name (`mcp_<server>_<tool>`).
1430
- * Parallel mode (`toolExecution: 'parallel'`, the default) sees calls in
1431
- * the SAME assistant turn race against each other — none can dedup against
1432
- * a sibling that started in the same batch. Sequential mode honors order
1433
- * within a turn.
1183
+ * Override when routing to a known-text-only model (e.g. `meta-llama/llama-3-8b-instruct`).
1184
+ */
1185
+ capabilities?: ProviderCapabilities;
1186
+ }
1187
+ /**
1188
+ * OpenRouter provider.
1189
+ *
1190
+ * Thin wrapper around {@link openaiCompat} with OpenRouter-specific defaults
1191
+ * (base URL, default model) and required attribution headers.
1192
+ */
1193
+ declare function openrouter(params?: OpenRouterParams): Provider;
1194
+ //#endregion
1195
+ //#region src/providers/index.d.ts
1196
+ interface ToolSpec {
1197
+ name: string;
1198
+ description: string;
1199
+ inputSchema: Record<string, unknown>;
1200
+ }
1201
+ interface ToolCall {
1202
+ id: string;
1203
+ name: string;
1204
+ input: Record<string, unknown>;
1205
+ }
1206
+ interface ToolResult {
1207
+ id: string;
1208
+ /**
1209
+ * Tool output — plain string for text-only tools (the common case) or a structured
1210
+ * array of content blocks for tools that return images or mixed content (e.g. an
1211
+ * MCP browser server returning a screenshot).
1434
1212
  *
1435
- * **Cache policy**: only the most recent `(hash, result)` per tool is
1436
- * retained. Interleaved patterns (input A, input B, input A) miss on the
1437
- * second A because B overwrote it. Sufficient for the common spam-the-
1438
- * same-call loop; consumers needing a richer cache should hook
1439
- * `tool:gate` directly.
1213
+ * Use `toolResultToText(content)` when a downstream consumer only handles strings.
1214
+ */
1215
+ content: string | ToolResultContent[];
1216
+ }
1217
+ /**
1218
+ * Provider-level capability flags used by the agent loop to route tool results
1219
+ * and user messages appropriately.
1220
+ *
1221
+ * When a flag is `undefined` (omitted), the loop applies the conservative
1222
+ * text-only default — images are stripped and replaced with a text marker so
1223
+ * non-vision models do not confabulate about content they cannot see.
1224
+ */
1225
+ interface ProviderCapabilities {
1226
+ /**
1227
+ * Model accepts image input anywhere (user messages and tool results).
1440
1228
  *
1441
- * Default: `undefined` (no per-tool dedup).
1229
+ * When `false`, the loop replaces image blocks with
1230
+ * `"[image omitted — model does not support vision]"` before they reach the provider.
1231
+ * Gives the model an honest marker instead of letting JSON-stringified base64 slip
1232
+ * through and get confabulated over.
1442
1233
  */
1443
- dedupTools?: Record<string, (input: Record<string, unknown>) => string | undefined>;
1234
+ vision?: boolean;
1444
1235
  /**
1445
- * Require `read_file` before `edit` / `multi_edit` on the same path, and
1446
- * reject edits when the file has changed on disk since the last read in
1447
- * this session. Eliminates the silent-corruption failure mode where a
1448
- * model "remembers" stale content and applies a substring edit against
1449
- * bytes that have moved.
1236
+ * Provider wire format embeds images inside tool-role messages natively
1237
+ * (Anthropic `tool_result.content` arrays, OpenAI Codex pi-ai `toolResult` blocks).
1450
1238
  *
1451
- * Requires a session. Off by default; turn it on for stricter eval-grade
1452
- * runs where silent edit corruption would invalidate the result.
1239
+ * When `false`, a vision-capable provider still gets images but via a
1240
+ * companion `user` message emitted immediately after the flattened
1241
+ * `tool`/`tool_result` marker. This is the Claude Desktop / Cline pattern
1242
+ * and works on any OpenAI Chat Completions endpoint that accepts image
1243
+ * URLs in user messages.
1244
+ */
1245
+ imageInToolResult?: boolean;
1246
+ }
1247
+ interface StreamCallbacks {
1248
+ onText: (delta: string) => void;
1249
+ onThinking?: (delta: string) => void;
1250
+ onOAuthRefresh?: (ctx: OAuthRefreshHookContext) => void | Promise<void>;
1251
+ }
1252
+ interface TurnResult {
1253
+ /** Full assistant turn as a SessionMessage */
1254
+ assistantMessage: SessionMessage;
1255
+ /** Text content blocks concatenated */
1256
+ text: string;
1257
+ /** Tool calls requested by the model */
1258
+ toolCalls: ToolCall[];
1259
+ /** Whether the model wants to stop */
1260
+ done: boolean;
1261
+ usage: TurnUsage;
1262
+ }
1263
+ interface StreamOptions {
1264
+ model: string;
1265
+ system: string;
1266
+ tools: unknown[];
1267
+ messages: SessionMessage[];
1268
+ maxTokens: number;
1269
+ /** Thinking/reasoning level (optional, default: off) */
1270
+ thinking?: ThinkingLevel;
1271
+ /** Exact thinking token budget — overrides the level-based default when set */
1272
+ thinkingBudget?: number;
1273
+ /** Force tool selection behavior */
1274
+ toolChoice?: {
1275
+ type: 'auto' | 'required' | 'tool';
1276
+ name?: string;
1277
+ };
1278
+ /**
1279
+ * Enable prompt caching on this call. When `true`, providers that support it
1280
+ * insert `cache_control` breakpoints on the system prompt, last tool, and
1281
+ * last stable message so the shared prefix is cached across turns.
1453
1282
  *
1454
- * Default: `false`.
1283
+ * Default: `false` (providers opt callers in — the agent loop passes `true`).
1455
1284
  */
1456
- requireReadBeforeEdit?: boolean;
1285
+ cache?: boolean;
1286
+ /** Abort signal for cancellation */
1287
+ signal?: AbortSignal;
1288
+ }
1289
+ interface Provider {
1290
+ readonly name: string;
1291
+ readonly meta: {
1292
+ defaultModel: string; /** Provider-level capability flags. See {@link ProviderCapabilities}. */
1293
+ capabilities?: ProviderCapabilities;
1294
+ } & Record<string, unknown>;
1295
+ /** Format tool specs for this provider */
1296
+ formatTools: (tools: ToolSpec[]) => unknown[];
1297
+ /** Create a text-only user message. Multimodal content goes through `promptMessage`. */
1298
+ userMessage: (content: string) => SessionMessage;
1299
+ /** Create an assistant message (for priming) */
1300
+ assistantMessage: (content: string) => SessionMessage;
1301
+ /** Create a tool results message to send back */
1302
+ toolResultsMessage: (results: ToolResult[]) => SessionMessage;
1303
+ /** Stream a turn, calling onText for each text delta */
1304
+ stream: (options: StreamOptions, callbacks: StreamCallbacks) => Promise<TurnResult>;
1457
1305
  /**
1458
- * Client-side context compaction strategy. Use this for non-Anthropic
1459
- * providers (OSS via cerebras / openai-compat / openrouter) that don't
1460
- * have a server-side equivalent. Anthropic users should prefer the
1461
- * server-side `context-management-2025-06-27` beta — see
1462
- * `AnthropicParams.contextManagement`.
1463
- *
1464
- * - `'off'` (default) — no client-side compaction.
1465
- * - `'tail'` — when total tool-output bytes in the persisted history
1466
- * exceed `compactThreshold`, replace older `tool_result` outputs with a
1467
- * short stub, keeping the newest `compactKeepTurns` turns intact. The
1468
- * compaction is applied to the wire-level message list only; the
1469
- * underlying session turns are not modified.
1306
+ * Build a user `SessionMessage` from multimodal prompt parts.
1470
1307
  *
1471
- * Default: `'off'`.
1308
+ * Providers that cannot handle a particular part type (e.g. document) should throw.
1309
+ * The agent loop always canonicalizes the run-level prompt into parts before calling
1310
+ * this method; providers may fall back to `userMessage` for the text-only path if
1311
+ * they do not implement this.
1472
1312
  */
1473
- compactStrategy?: 'off' | 'tail';
1313
+ promptMessage?: (parts: PromptPart[]) => SessionMessage;
1474
1314
  /**
1475
- * Soft byte threshold that triggers tail compaction when
1476
- * `compactStrategy === 'tail'`. Counts the post-`context:transform` bytes
1477
- * of `tool_result` outputs across all messages. Default: `131_072` (128
1478
- * KiB). Ignored when compaction is off.
1315
+ * Classify a native provider error for downstream typed-error wrapping.
1316
+ *
1317
+ * Return `null` when the error is not recognized — the loop will wrap it in
1318
+ * `AgentProviderError` with the provider's name. Return a `ClassifiedError` to
1319
+ * route it to one of the typed error classes.
1479
1320
  */
1480
- compactThreshold?: number;
1321
+ classifyError?: (err: unknown) => ClassifiedError | null;
1322
+ }
1323
+ //#endregion
1324
+ //#region src/session/file-map.d.ts
1325
+ /**
1326
+ * Host-provided file-map adapter. Three methods exchanging `Record<string, string>`
1327
+ * payloads — the whole persistence surface the wrapper needs.
1328
+ */
1329
+ interface FileMapAdapter {
1330
+ /** Load the current file map. Returns an empty `files` record when nothing is persisted. */
1331
+ get: () => Promise<{
1332
+ files: Record<string, string>;
1333
+ }>;
1334
+ /** Replace the persisted file map. Full-rewrite semantics. */
1335
+ save: (files: Record<string, string>) => Promise<void>;
1336
+ /** Delete all persisted state. */
1337
+ delete: () => Promise<void>;
1338
+ }
1339
+ interface FileMapStoreOptions {
1340
+ /** Filename for the JSONL turns log. Default: `turns.jsonl`. */
1341
+ turnsFile?: string;
1342
+ /** Filename for the metadata JSON. Default: `meta.json`. */
1343
+ metaFile?: string;
1344
+ }
1345
+ /**
1346
+ * Create a single-session `SessionStore` backed by a file-map adapter.
1347
+ *
1348
+ * @example
1349
+ * ```ts
1350
+ * const session = await createSession({
1351
+ * store: createFileMapStore(hostSessionStore),
1352
+ * })
1353
+ * ```
1354
+ */
1355
+ declare function createFileMapStore(adapter: FileMapAdapter, options?: FileMapStoreOptions): SessionStore;
1356
+ //#endregion
1357
+ //#region src/session/memory.d.ts
1358
+ declare function createMemoryStore(): SessionStore;
1359
+ //#endregion
1360
+ //#region src/session/messages.d.ts
1361
+ declare function fromAnthropic(msg: {
1362
+ role: string;
1363
+ content: unknown;
1364
+ }): SessionMessage;
1365
+ declare function fromOpenAI(msg: {
1366
+ role: string;
1367
+ content: unknown;
1368
+ }): SessionMessage;
1369
+ declare function toAnthropic(msg: SessionMessage): {
1370
+ role: string;
1371
+ content: unknown;
1372
+ };
1373
+ declare function toOpenAI(msg: SessionMessage): {
1374
+ role: string;
1375
+ content: unknown;
1376
+ };
1377
+ declare function autoDetectAndConvert(msg: {
1378
+ role: string;
1379
+ content: unknown;
1380
+ }): SessionMessage;
1381
+ //#endregion
1382
+ //#region src/session/remote.d.ts
1383
+ interface RemoteStoreOptions {
1384
+ /** Base URL of the session API */
1385
+ url: string;
1386
+ /** Optional headers (e.g. for authentication) */
1387
+ headers?: Record<string, string>;
1388
+ }
1389
+ declare function createRemoteStore(options: RemoteStoreOptions): SessionStore;
1390
+ //#endregion
1391
+ //#region src/session/index.d.ts
1392
+ interface SessionRun {
1393
+ id: string;
1394
+ startedAt: number;
1395
+ endedAt?: number;
1396
+ prompt: string;
1397
+ status: 'running' | 'completed' | 'aborted' | 'error';
1398
+ turns?: number;
1399
+ tokensIn?: number;
1400
+ tokensOut?: number;
1401
+ error?: string;
1402
+ /** Per-turn usage breakdown */
1403
+ turnUsage?: TurnUsage[];
1404
+ /** Total usage across all turns */
1405
+ totalUsage?: TurnUsage;
1406
+ /** Estimated cost in USD */
1407
+ cost?: number;
1481
1408
  /**
1482
- * Number of trailing turns to leave untouched during tail compaction. The
1483
- * most-recent `compactKeepTurns` user/assistant messages are not eligible
1484
- * for elision so the model keeps the freshest tool context. Default: `4`.
1409
+ * The run that spawned this one, when the agent is a subagent sharing its
1410
+ * parent's session. Undefined on top-level `agent.run()`. Consumers can walk
1411
+ * `runs` by `parentRunId` to reconstruct the subagent tree.
1485
1412
  */
1486
- compactKeepTurns?: number;
1413
+ parentRunId?: string;
1487
1414
  /**
1488
- * Prefix every line of `read_file` output with its 1-indexed line number
1489
- * followed by a tab (`<N>\t<content>`) the compact `cat -n`-style
1490
- * format Claude Code emits. The `edit` tool strips the prefix from
1491
- * `old_string` / `new_string` so the model can paste back a numbered
1492
- * chunk verbatim without breaking the match.
1493
- *
1494
- * Set `false` to opt out — useful for callers piping `read_file` into
1495
- * downstream parsers that don't recognize the prefix. Per-call
1496
- * `read_file({ lineNumbers: false })` overrides this default.
1497
- *
1498
- * Default: `true`.
1415
+ * Zero-based subagent depth. 0 = top-level run, 1 = direct child, …
1416
+ * Recorded here so hosts can query/filter by level without walking the tree.
1499
1417
  */
1500
- readLineNumbers?: boolean;
1418
+ depth?: number;
1419
+ }
1420
+ interface SessionData {
1421
+ id: string;
1422
+ agentId?: string;
1423
+ turns: SessionTurn[];
1424
+ runs: SessionRun[];
1425
+ status: 'idle' | 'running' | 'completed' | 'error';
1426
+ metadata: Record<string, unknown>;
1427
+ createdAt: number;
1428
+ updatedAt: number;
1429
+ }
1430
+ interface SessionStore {
1431
+ /** Optional: generate a session ID server-side (e.g. Supabase UUID). */
1432
+ generateSessionId?: () => string | Promise<string>;
1433
+ /** Optional: generate a turn ID server-side. */
1434
+ generateTurnId?: () => string | Promise<string>;
1435
+ /** Load a session by ID. Returns null if not found. */
1436
+ load: (sessionId: string) => Promise<SessionData | null>;
1437
+ /** Save a session (create or update, full document). */
1438
+ save: (session: SessionData) => Promise<void>;
1439
+ /** Delete a session. */
1440
+ delete: (sessionId: string) => Promise<void>;
1441
+ /** List session IDs, optionally filtered. */
1442
+ list: (filter?: {
1443
+ agentId?: string;
1444
+ limit?: number;
1445
+ }) => Promise<string[]>;
1446
+ /** Append new turns to a session (incremental, avoids full re-save). */
1447
+ appendTurns: (sessionId: string, turns: SessionTurn[]) => Promise<void>;
1448
+ /** Return a slice of turns for a session. */
1449
+ getTurns: (sessionId: string, from?: number, limit?: number) => Promise<SessionTurn[]>;
1450
+ /** Persist an updated run record (called after completeRun / abortRun / errorRun). */
1451
+ updateRun: (sessionId: string, run: SessionRun) => Promise<void>;
1452
+ /** Update the top-level status of a session. */
1453
+ updateStatus: (sessionId: string, status: SessionData['status']) => Promise<void>;
1454
+ }
1455
+ interface Session {
1456
+ /** Session ID */
1457
+ readonly id: string;
1458
+ /** Agent ID (optional label) */
1459
+ readonly agentId?: string;
1460
+ /** Current turn history */
1461
+ readonly turns: SessionTurn[];
1501
1462
  /**
1502
- * Replace older `read_file` `tool_result` blocks with a short stub when
1503
- * a successful `edit` / `multi_edit` / `write_file` later in the same
1504
- * run modified the same path. The replacement is applied to the
1505
- * wire-level message list only — persisted session turns keep the
1506
- * original content.
1507
- *
1508
- * Eliminates the common waste pattern where the model carries the
1509
- * pre-edit file body forward across many turns "in case it needs it".
1510
- * Pairs cleanly with `compactStrategy: 'tail'`: stale reads shrink
1511
- * first, then the byte-threshold compaction fires if anything's left.
1512
- *
1513
- * Detection is conservative — only triggers when the corresponding
1514
- * tool_result confirms success (`Edited …`, `Created …`, `Updated …`).
1515
- * Failed edits and `No change needed` write_file calls do NOT
1516
- * invalidate prior reads.
1463
+ * True when this session has no turns yet.
1517
1464
  *
1518
- * Default: `false`.
1465
+ * Use this as a first-prompt signal when setting up a run — e.g. writing initial
1466
+ * configuration only on fresh sessions. Equivalent to `turns.length === 0`.
1519
1467
  */
1520
- elideStaleReads?: boolean;
1521
- /**
1522
- * Tool disclosure strategy. Controls whether the model sees every tool's
1523
- * full `inputSchema` in its tool list every turn ("eager") or whether MCP
1524
- * tools are advertised as a name+description catalog in the system prompt
1525
- * and only get full schemas after being surfaced via the `tool_search`
1526
- * native tool ("lazy" / progressive disclosure).
1527
- *
1528
- * Native tools (those passed to `createAgent({ tools })`) and skill tools
1529
- * are always eager they are core to the agent and cheap. Only MCP tools
1530
- * are eligible for lazy disclosure.
1531
- *
1532
- * When `'lazy'`, the agent:
1533
- * - Appends a `<searchable_tools>` section to the system prompt listing
1534
- * every MCP tool by `name` + `description` only (no `inputSchema`).
1535
- * - Auto-injects a `tool_search` native tool (opt out via
1536
- * {@link AgentBehavior.toolSearch}) the model uses to load schemas on
1537
- * demand. Surfaced tools persist for the rest of the run.
1538
- * - Rebuilds the wire-level tool list each turn, appending newly-unlocked
1539
- * tools at the end so the prefix-cache breakpoint advances cleanly.
1540
- *
1541
- * Trade-off: every `tool_search` invocation expands the tool list and
1542
- * invalidates the tool-list cache breakpoint for one turn. With many
1543
- * MCP servers, the savings on cold turns (fewer schemas in context) are
1544
- * substantial; with one tiny MCP server, the overhead may not pay back.
1545
- *
1546
- * Default: `'eager'`.
1468
+ readonly isEmpty: boolean;
1469
+ /** Top-level session status */
1470
+ readonly status: SessionData['status'];
1471
+ /** All runs in this session */
1472
+ readonly runs: SessionRun[];
1473
+ /** Arbitrary metadata */
1474
+ readonly metadata: Record<string, unknown>;
1475
+ /**
1476
+ * Start tracking a new run. `extras.parentRunId` + `extras.depth` are
1477
+ * populated by the spawn tool when a child agent shares its parent's
1478
+ * session; regular top-level `agent.run()` calls omit them.
1547
1479
  */
1548
- toolDisclosure?: 'eager' | 'lazy';
1480
+ startRun: (runId: string, prompt?: string, extras?: {
1481
+ parentRunId?: string;
1482
+ depth?: number;
1483
+ }) => void;
1484
+ /** Mark a run as completed */
1485
+ completeRun: (runId: string, stats: {
1486
+ turns: number;
1487
+ tokensIn: number;
1488
+ tokensOut: number;
1489
+ turnUsage?: TurnUsage[];
1490
+ cost?: number;
1491
+ }) => void;
1492
+ /** Mark a run as aborted */
1549
1493
  /**
1550
- * Fine-grained config for the `tool_search` tool auto-injected when
1551
- * {@link AgentBehavior.toolDisclosure} is `'lazy'`. No-op in eager mode.
1552
- *
1553
- * - `tool: false` opt out of the auto-injection entirely. Use when the
1554
- * host wants to ship a custom discovery tool. Note that the catalog
1555
- * text drops the call-to-action prose in this case so the model isn't
1556
- * pointed at a non-existent tool.
1557
- * - `limit` — default cap on results returned per `tool_search` call when
1558
- * the model omits the parameter. Default: `20`.
1559
- *
1560
- * Note on host-defined `tool_search`: a tool the host registers under the
1561
- * name `tool_search` (or under any alias whose canonical is `tool_search`)
1562
- * will shadow the auto-injected one — the catalog text will point at the
1563
- * host's wire name, but driving the unlock flow requires either using
1564
- * `createToolSearchTool({ catalog, unlocked })` from `tools/tool-search`
1565
- * (which internally mutates the unlock set) or fully opting out via
1566
- * `toolSearch.tool: false` and treating discovery as a host-side concern.
1567
- * A bare host tool that doesn't touch the unlock set will not advance the
1568
- * lazy disclosure state and the hard gate will keep refusing lazy calls.
1569
- *
1570
- * Default: `undefined` (auto-inject with the default limit).
1494
+ * Optional `stats` lets the agent backfill the run's token totals when
1495
+ * the abort happened *after* the loop accumulated meaningful usage —
1496
+ * common when the user presses esc mid-streaming. Without it, the run
1497
+ * record reads `0 in / 0 out` on reload regardless of how much was
1498
+ * spent before the abort. Same shape as `completeRun`'s stats so the
1499
+ * persisted `totalUsage` aggregate stays consistent across paths.
1571
1500
  */
1572
- toolSearch?: {
1573
- tool?: false;
1574
- limit?: number;
1575
- };
1501
+ abortRun: (runId: string, stats?: {
1502
+ turns: number;
1503
+ tokensIn: number;
1504
+ tokensOut: number;
1505
+ turnUsage?: TurnUsage[];
1506
+ cost?: number;
1507
+ }) => void;
1508
+ /** Mark a run as errored */
1509
+ /** Optional `stats` — same rationale as `abortRun.stats`. */
1510
+ errorRun: (runId: string, error: string, stats?: {
1511
+ turns: number;
1512
+ tokensIn: number;
1513
+ tokensOut: number;
1514
+ turnUsage?: TurnUsage[];
1515
+ cost?: number;
1516
+ }) => void;
1517
+ /** Append turns to in-memory history AND persist via store.appendTurns (if store present) */
1518
+ appendTurns: (turns: SessionTurn[]) => Promise<void>;
1519
+ /** Replace all turns in-memory (does not persist — use save() for that) */
1520
+ setTurns: (turns: SessionTurn[]) => void;
1521
+ /** Update the session status in memory AND via store.updateStatus (if store present) */
1522
+ updateStatus: (status: SessionData['status']) => Promise<void>;
1523
+ /** Persist an updated run record via store.updateRun (if store present) */
1524
+ updateRun: (run: SessionRun) => Promise<void>;
1525
+ /** Generate a turn ID using store.generateTurnId if available, else crypto.randomUUID() */
1526
+ generateTurnId: () => string | Promise<string>;
1527
+ /** Set metadata key */
1528
+ setMeta: (key: string, value: unknown) => void;
1529
+ /** Persist the full session document to the store */
1530
+ save: () => Promise<void>;
1531
+ /** Serialize to SessionData */
1532
+ toJSON: () => SessionData;
1533
+ }
1534
+ interface CreateSessionOptions {
1535
+ /** Session ID. If omitted and store provides generateSessionId, that is used. */
1536
+ id?: string;
1537
+ /** Agent ID label */
1538
+ agentId?: string;
1539
+ /** Initial metadata */
1540
+ metadata?: Record<string, unknown>;
1541
+ /** Storage backend (optional, enables save/load) */
1542
+ store?: SessionStore;
1543
+ _data?: SessionData;
1576
1544
  }
1577
1545
  /**
1578
- * One block of a multimodal user prompt.
1579
- *
1580
- * `agent.run({ prompt })` accepts either a plain string (treated as a single
1581
- * text part) or an array of these parts for multimodal inputs.
1582
- *
1583
- * `document` parts are routed per provider: PDF-style mime types are sent as
1584
- * native document blocks when the provider supports them; text documents are
1585
- * inlined as text with an attachment header. Providers that cannot handle an
1586
- * image or document throw early.
1546
+ * Create a new session.
1547
+ * Async so stores that generate IDs server-side (e.g. Supabase) can be supported.
1587
1548
  */
1588
- type PromptPart = PromptTextPart | PromptImagePart | PromptDocumentPart;
1589
- interface PromptTextPart {
1590
- type: 'text';
1591
- text: string;
1592
- }
1593
- interface PromptImagePart {
1594
- type: 'image';
1595
- /** IANA media type (e.g. `image/png`, `image/jpeg`) */
1596
- mediaType: string;
1597
- /** Base64-encoded payload */
1598
- data: string;
1599
- /** Optional display name */
1600
- name?: string;
1601
- }
1602
- interface PromptDocumentPart {
1603
- type: 'document';
1604
- /** IANA media type (e.g. `application/pdf`, `text/plain`) */
1605
- mediaType: string;
1606
- /** Either a base64-encoded payload (`encoding: 'base64'`) or raw text (`encoding: 'text'`) */
1607
- data: string;
1608
- encoding: 'base64' | 'text';
1609
- /** Optional display name used in attachment headers */
1610
- name?: string;
1611
- }
1549
+ declare function createSession(options?: CreateSessionOptions): Promise<Session>;
1612
1550
  /**
1613
- * A single block of structured tool-result content.
1614
- *
1615
- * MCP servers can return a mix of text, image, resource, and audio blocks. Tools
1616
- * return `string` for the common text-only case or `ToolResultContent[]` when they
1617
- * need to preserve non-text content (e.g. screenshots from a browser MCP).
1618
- *
1619
- * Providers that support native multi-part tool results (Anthropic, OpenAI Codex via
1620
- * pi-ai) route image blocks into their wire format verbatim; OpenAI-compat providers
1621
- * route them via a companion-user-message fallback when the underlying model/endpoint
1622
- * does not accept images inside tool-role messages.
1551
+ * Load an existing session from a store.
1623
1552
  */
1624
- type ToolResultContent = ToolResultTextContent | ToolResultImageContent;
1625
- interface ToolResultTextContent {
1626
- type: 'text';
1627
- text: string;
1628
- }
1629
- interface ToolResultImageContent {
1630
- type: 'image';
1631
- /** IANA media type (e.g. `image/png`, `image/jpeg`) */
1632
- mediaType: string;
1633
- /** Base64-encoded payload */
1634
- data: string;
1635
- }
1553
+ declare function loadSession(store: SessionStore, sessionId: string): Promise<Session | null>;
1554
+ //#endregion
1555
+ //#region src/skills/types.d.ts
1636
1556
  /**
1637
- * Lossy flattener converts `ToolResultContent[]` (or a plain string) to a single
1638
- * string. Image blocks are replaced with `[image: <media> — <n> b64 bytes]` markers.
1557
+ * Types for the Agent Skills system.
1639
1558
  *
1640
- * Use at UI boundaries where a string is required; providers that understand
1641
- * structured content should route the array through without flattening.
1559
+ * Follows the Agent Skills open standard (agentskills.io/specification).
1560
+ * Zidane-specific metadata conventionally uses the `zidane.` key prefix
1561
+ * (e.g. `metadata['zidane.paths']`) to stay spec-compliant.
1642
1562
  */
1643
- declare function toolResultToText(content: string | ToolResultContent[]): string;
1563
+ interface SkillResource {
1564
+ /** Relative path from skill directory */
1565
+ path: string;
1566
+ /** Resource type inferred from directory */
1567
+ type: 'script' | 'reference' | 'asset' | 'other';
1568
+ }
1644
1569
  /**
1645
- * Approximate byte length of a tool output as it goes back to the model.
1646
- *
1647
- * - Plain text: UTF-8 byte length.
1648
- * - Structured content: text blocks contribute their UTF-8 byte length; image
1649
- * blocks contribute their **base64 character length**, since that is what
1650
- * the model tokenizes (the wire-encoded payload, not the decoded image).
1651
- *
1652
- * Used by the agent loop to populate `outputBytes` on `tool:after`,
1653
- * `tool:transform`, `mcp:tool:after`, and `mcp:tool:transform` hooks so
1654
- * consumers can size-budget tool output without re-counting bytes themselves.
1570
+ * Where the skill came from. Used for collision precedence (project beats user)
1571
+ * and for host SDKs to gate project-level skills on a trust check.
1655
1572
  */
1656
- declare function toolOutputByteLength(content: string | ToolResultContent[]): number;
1657
- type SessionContentBlock = {
1658
- type: 'text';
1659
- text: string;
1660
- } | {
1661
- type: 'image';
1662
- mediaType: string;
1663
- data: string;
1664
- } | {
1665
- type: 'tool_call';
1666
- id: string;
1573
+ type SkillSource = 'project' | 'user' | 'inline' | 'builtin';
1574
+ /** Severity + code for lenient-load warnings surfaced to host UIs. */
1575
+ interface SkillDiagnostic {
1576
+ severity: 'warning' | 'error';
1577
+ /** Stable machine-readable code (e.g. `name-mismatch-directory`). */
1578
+ code: string;
1579
+ /** Human-readable description. */
1580
+ message: string;
1581
+ /** Optional frontmatter field name the diagnostic relates to. */
1582
+ field?: string;
1583
+ }
1584
+ interface SkillConfig {
1585
+ /** Skill name: 1-64 chars, lowercase alphanumeric + hyphens */
1667
1586
  name: string;
1668
- input: Record<string, unknown>;
1669
- } | {
1670
- type: 'tool_result';
1671
- callId: string;
1587
+ /** What the skill does and when to use it (1-1024 chars) */
1588
+ description: string;
1589
+ /** The SKILL.md body content (after YAML frontmatter) */
1590
+ instructions: string;
1672
1591
  /**
1673
- * Tool output either a plain string (text-only, the common case) or a structured
1674
- * array of content blocks (text + image for multimodal tools such as screenshots).
1592
+ * Where this skill was loaded from. Drives collision precedence and the
1593
+ * `trustProjectSkills` gate. Optional `parseSkillFile` stamps it; raw
1594
+ * fixtures that omit it are treated as `'project'` by downstream readers.
1675
1595
  */
1676
- output: string | ToolResultContent[];
1677
- isError?: boolean;
1678
- } | {
1679
- type: 'thinking';
1680
- text: string;
1681
- signature?: string;
1596
+ source?: SkillSource;
1597
+ /** Absolute path to SKILL.md (undefined for inline skills) */
1598
+ location?: string;
1599
+ /** Skill directory path for resolving relative references */
1600
+ baseDir?: string;
1601
+ /** License identifier or reference */
1602
+ license?: string;
1603
+ /** Environment requirements */
1604
+ compatibility?: string;
1605
+ /**
1606
+ * Flat key-value metadata bag per the spec. For Zidane-specific hints,
1607
+ * use the `zidane.` key prefix (e.g. `metadata['zidane.paths']`).
1608
+ */
1609
+ metadata?: Record<string, string>;
1610
+ /** Pre-approved tool names (experimental per spec) */
1611
+ allowedTools?: string[];
1612
+ /** Bundled resource files discovered in the skill directory */
1613
+ resources?: SkillResource[];
1614
+ /**
1615
+ * Lenient-load warnings recorded during parsing. Host SDKs can surface these
1616
+ * as inline UI hints. Absent when no issues were found.
1617
+ */
1618
+ diagnostics?: SkillDiagnostic[];
1619
+ }
1620
+ interface SkillsConfig {
1621
+ /**
1622
+ * Control which skills are active.
1623
+ * - `true` (default): all discovered skills are enabled
1624
+ * - `false` or `[]`: fully disable the skills system (no resolution, no catalog, no hooks)
1625
+ * - `string[]`: allowlist — only skills with matching names are enabled
1626
+ */
1627
+ enabled?: boolean | string[];
1628
+ /** Directories to scan for SKILL.md files */
1629
+ scan?: string[];
1630
+ /** Dynamic skills written to disk at agent start, then loaded normally */
1631
+ write?: SkillConfig[];
1632
+ /** Skill names to exclude from the catalog */
1633
+ exclude?: string[];
1634
+ /** Skip default scan paths (~/.agents/skills, .zidane/skills, etc.) */
1635
+ skipDefaultPaths?: boolean;
1682
1636
  /**
1683
- * Provider that minted `signature`. Signatures are provider-bound (Anthropic
1684
- * HMAC vs. OpenAI `encrypted_content`) and are dropped on cross-provider
1685
- * hops to avoid 400s. Unset means legacy/unknown forwarded as-is.
1637
+ * Auto-inject `skills_use` / `skills_read` / `skills_run_script` tools
1638
+ * when the catalog is non-empty. Default `true`. Set `false` to opt out
1639
+ * (the system prompt will then instruct the model to use its file-read
1640
+ * tool instead).
1686
1641
  */
1687
- signatureProducer?: 'anthropic' | 'openai';
1688
- } | {
1689
- type: 'redacted_thinking';
1690
- data: string;
1691
- } | {
1642
+ tool?: boolean;
1692
1643
  /**
1693
- * Opaque round-trip envelope for reasoning state minted by an OpenAI-compat
1694
- * gateway (currently OpenRouter). The gateway expects its own
1695
- * `reasoning_details` array echoed back verbatim on the next turn so the
1696
- * upstream model can resume an extended-reasoning chain across tool calls.
1697
- *
1698
- * Stored opaquely because the items are provider-bound (Anthropic HMAC
1699
- * signatures, OpenAI `encrypted_content`, model-specific summary formats
1700
- * — all flowing through the gateway's normalized envelope).
1644
+ * Cap on concurrently active skills per run. Default `undefined` (unlimited).
1645
+ * Attempts to activate past the cap throw from `skills_use`.
1701
1646
  */
1702
- type: 'provider_reasoning';
1703
- producer: 'openrouter';
1704
- details: unknown[];
1647
+ maxActive?: number;
1648
+ /** Script timeout for `skills_run_script`, in milliseconds. Default `60000`. */
1649
+ scriptTimeoutMs?: number;
1705
1650
  /**
1706
- * Model id that produced the details. Reasoning is bound to a specific
1707
- * upstream route a model switch on the next turn invalidates the
1708
- * embedded signatures, so the sender drops the block on mismatch.
1651
+ * When `false`, skills with `source: 'project'` are skipped during
1652
+ * resolution with a diagnostic. Default `true` (preserves existing behavior).
1653
+ * Useful for host SDKs handling untrusted repositories.
1709
1654
  */
1710
- model?: string;
1711
- };
1712
- interface SessionMessage {
1713
- role: 'user' | 'assistant';
1714
- content: SessionContentBlock[];
1715
- }
1716
- interface SessionTurn {
1717
- /** UUID — generated by the store if it provides generateTurnId, else crypto.randomUUID() */
1718
- id: string;
1719
- /** Run that produced this turn (e.g. 'run_1') */
1720
- runId?: string;
1721
- role: 'user' | 'assistant' | 'system';
1722
- content: SessionContentBlock[];
1723
- /** Token usage — only present on assistant turns */
1724
- usage?: TurnUsage;
1725
- /** Unix timestamp (Date.now()) when the turn was created */
1726
- createdAt: number;
1655
+ trustProjectSkills?: boolean;
1727
1656
  }
1657
+ //#endregion
1658
+ //#region src/tools/types.d.ts
1728
1659
  /**
1729
- * Per-run hook registrations. Each entry can be a single handler or an array of handlers.
1730
- * Keys are `AgentHooks` event names (loose-typed here to avoid a circular import; agent.ts
1731
- * narrows it to the strongly-typed map).
1660
+ * Runtime context passed to every tool execution.
1661
+ * Provides access to the agent's provider, abort signal, execution environment, and hooks.
1662
+ *
1663
+ * The preset-y fields (`name`, `system`, `tools`, `toolAliases`, `mcpServers`, `skills`,
1664
+ * `behavior`) mirror the agent's own options so child-spawning tools can inherit them.
1732
1665
  */
1733
- type RunHookMap = Record<string, ((ctx: any) => unknown) | ((ctx: any) => unknown)[]>;
1734
- interface AgentRunOptions {
1735
- model?: string;
1666
+ interface ToolContext {
1667
+ /** The LLM provider for this agent run */
1668
+ provider: Provider;
1669
+ /** Abort signal — tools should check this for early termination */
1670
+ signal: AbortSignal;
1671
+ /** The execution context (shell, filesystem, etc.) */
1672
+ execution: ExecutionContext;
1673
+ /** The active execution handle for the current agent run */
1674
+ handle: ExecutionHandle;
1675
+ /** Agent hooks for emitting events (e.g. spawn:complete) */
1676
+ hooks: Hookable<AgentHooks>;
1677
+ /** Agent display name (preset or user-supplied) */
1678
+ name?: string;
1679
+ /** Agent default system prompt */
1680
+ system?: string;
1681
+ /** Source tool map the agent was created with (pre-MCP-merge, pre-skills-injection) */
1682
+ tools: Record<string, ToolDef>;
1736
1683
  /**
1737
- * User prompt. Optional when resuming a session with existing turns.
1684
+ * Map canonical tool names to LLM-facing (aliased) names.
1738
1685
  *
1739
- * Accepts either a plain string (single text part) or an array of `PromptPart`s for
1740
- * multimodal inputs (text, images, documents). See {@link PromptPart}.
1686
+ * Aliasing is **LLM-boundary-only**:
1687
+ * - The alias is what the provider's tool spec carries, what the model calls it, and
1688
+ * what appears in `ToolHookContext.displayName` / `McpToolHookContext.displayName`.
1689
+ * - The canonical name is what lives in `session.turns`, `ToolHookContext.name`, and
1690
+ * what the agent uses to look up the tool implementation. Alias changes never
1691
+ * desync persisted history.
1741
1692
  */
1742
- prompt?: string | PromptPart[];
1743
- system?: string;
1744
- thinking?: ThinkingLevel;
1745
- /** Abort signal when triggered, the agent stops after the current turn */
1746
- signal?: AbortSignal;
1747
- /** Behavior overrides for this run (overrides agent defaults) */
1693
+ toolAliases?: Record<string, string>;
1694
+ /** MCP servers configured on the agent (for child inheritance) */
1695
+ mcpServers?: McpServerConfig[];
1696
+ /** Skills configuration (for child inheritance) */
1697
+ skills?: SkillsConfig;
1698
+ /** Behavior defaults (for child inheritance) */
1748
1699
  behavior?: AgentBehavior;
1749
- /** Tool overrides for this run. Pass {} for no tools. Omit to use agent tools. */
1750
- tools?: Record<string, ToolDef>;
1700
+ /** Turn ID that requested this tool call */
1701
+ turnId: string;
1702
+ /** Tool call ID from the model */
1703
+ callId: string;
1751
1704
  /**
1752
- * Per-run hook registrations. Each hook is attached before the run starts and
1753
- * detached in a finally block so handlers never leak across runs.
1705
+ * The run id this tool call is part of. Populated by the agent loop when
1706
+ * invoking tools. Optional on the type so host code constructing contexts
1707
+ * by hand (tests, direct tool invocations) doesn't have to synthesize one.
1754
1708
  *
1755
- * Accepts either a single handler or an array (all handlers register).
1709
+ * Spawn-style tools rely on this to tag child runs with `parentRunId` so
1710
+ * the subagent tree can be reconstructed from a persisted session.
1756
1711
  */
1757
- hooks?: RunHookMap;
1712
+ runId?: string;
1758
1713
  /**
1759
- * Parent run id. Populated automatically by the `spawn` tool when the child
1760
- * shares the parent's session; recorded on the resulting `SessionRun` so the
1761
- * parentchild run tree can be reconstructed from a persisted session.
1714
+ * The agent's session, when one was provided to `createAgent`. Tools that
1715
+ * want to persist their own state (or, in the case of `spawn`, inherit the
1716
+ * parent's session for child persistence) can read from here.
1762
1717
  */
1763
- parentRunId?: string;
1718
+ session?: Session;
1764
1719
  /**
1765
- * Zero-based subagent depth. 0 = top-level `agent.run()`, 1 = first-level
1766
- * child spawned by a parent agent, and so on. Used by the spawn tool to
1767
- * enforce `maxDepth` and to stamp `child:*` forwarded hook payloads.
1720
+ * Subagent depth for the agent owning this tool call. 0 = top-level,
1721
+ * 1 = first-level child, Used by spawn to enforce a `maxDepth` cap.
1722
+ * Undefined is treated as 0 by spawn.
1768
1723
  */
1769
1724
  depth?: number;
1770
1725
  }
1726
+ interface ToolDef {
1727
+ spec: ToolSpec;
1728
+ /**
1729
+ * Execute the tool and return its output.
1730
+ *
1731
+ * Return a plain string for text-only tools (the common case). Return a
1732
+ * `ToolResultContent[]` when the tool produces non-text content (images, mixed
1733
+ * text+image) that the provider can route through natively (Anthropic
1734
+ * `tool_result.content` arrays, OpenAI Codex pi-ai) or through the
1735
+ * companion-user-message fallback (OpenAI Chat Completions).
1736
+ */
1737
+ execute: (input: Record<string, unknown>, ctx: ToolContext) => Promise<string | ToolResultContent[]>;
1738
+ }
1739
+ type ToolMap = Map<string, ToolDef>;
1740
+ //#endregion
1741
+ //#region src/mcp/index.d.ts
1742
+ interface McpConnection {
1743
+ tools: Record<string, ToolDef>;
1744
+ close: () => Promise<void>;
1745
+ }
1771
1746
  /**
1772
- * Reason the provider gave for stopping the turn.
1747
+ * Normalize MCP server configs from any common shape to `McpServerConfig[]`.
1773
1748
  *
1774
- * - `'stop'` — natural turn end (`end_turn` / `stop_sequence`).
1775
- * - `'tool-calls'` — model emitted tool_use blocks.
1776
- * - `'length'` — `max_tokens` reached, or (Anthropic 4.6+) the response bumped
1777
- * against the model's context window mid-stream
1778
- * (`model_context_window_exceeded`). The partial response is preserved; the
1779
- * loop emits this reason so consumers can prune/retry.
1780
- * - `'content-filter'` model refused.
1781
- * - `'pause'` Anthropic `pause_turn`: a server-side mid-turn pause for very
1782
- * long thinking. The loop continues with a synthetic "Please continue."
1783
- * user message rather than terminating; consumers see the pause via this
1784
- * finish reason on the prior assistant turn.
1785
- * - `'error'` — provider classified the turn as failed.
1786
- * - `'other'` — unknown / unmapped.
1749
+ * Accepts:
1750
+ * - `McpServerConfig[]` — zidane native (pass-through).
1751
+ * - `McpServerConfig` — a single config object (wrapped to a 1-element array).
1752
+ * - `Record<string, RawShape>` — name-keyed map (common in host-SDK configs), where the key is the server name.
1753
+ * - Mixed shapes with `type` vs `transport`, `httpUrl`/`sseUrl` vs `url`.
1754
+ *
1755
+ * Returns `[]` when `input` is nullish. Throws a descriptive error when the transport
1756
+ * cannot be inferred from a given entry, or when the input shape is unsupported.
1787
1757
  */
1788
- type TurnFinishReason = 'stop' | 'tool-calls' | 'length' | 'content-filter' | 'pause' | 'error' | 'other';
1789
- interface TurnUsage {
1790
- input: number;
1791
- output: number;
1792
- /** Tokens written to cache (Anthropic) */
1793
- cacheCreation?: number;
1794
- /** Tokens read from cache (Anthropic) */
1795
- cacheRead?: number;
1796
- /** Thinking/reasoning tokens used */
1797
- thinking?: number;
1798
- /** Cost in USD as reported by the provider (OpenRouter) */
1799
- cost?: number;
1800
- /**
1801
- * Why the model stopped this turn. Providers normalize native stop reasons to this union.
1802
- * Absent when the provider did not surface a reason (e.g. mock turns).
1803
- */
1804
- finishReason?: TurnFinishReason;
1805
- /**
1806
- * The model ID the provider ultimately used. May differ from the requested model when the
1807
- * provider remaps aliases. Absent for providers that do not echo a model ID.
1808
- */
1809
- modelId?: string;
1758
+ declare function normalizeMcpServers(input: unknown): McpServerConfig[];
1759
+ /**
1760
+ * Lossy flattener — converts MCP `CallToolResult.content` blocks to a single
1761
+ * string. Text blocks are extracted; non-text blocks are JSON-stringified.
1762
+ *
1763
+ * Use this only at UI / log boundaries that require a string. The agent
1764
+ * loop itself routes through {@link normalizeMcpBlocks} so image blocks
1765
+ * survive into provider-native tool_result content (Anthropic blocks,
1766
+ * OpenAI companion-user-message).
1767
+ */
1768
+ declare function resultToString(content: unknown[]): string;
1769
+ /**
1770
+ * Normalize MCP `CallToolResult.content` to zidane's {@link ToolResultContent[]} shape.
1771
+ *
1772
+ * Handles the four MCP content block types:
1773
+ * - `text` → preserved as `{type:'text', text}`
1774
+ * - `image` → preserved as `{type:'image', mediaType, data}` (MCP uses `mimeType`)
1775
+ * - `resource` with embedded text → flattened to a text block
1776
+ * - `resource` with embedded blob whose `mimeType` is `image/*` flattened to an image block
1777
+ * - Any unrecognized block JSON-stringified fallback text block (lossy but safe)
1778
+ *
1779
+ * Returns `null` when the input is not an array — callers should fall back to an empty
1780
+ * result in that case.
1781
+ */
1782
+ declare function normalizeMcpBlocks(content: unknown): ToolResultContent[] | null;
1783
+ /**
1784
+ * Connect to MCP servers and discover their tools.
1785
+ *
1786
+ * Each tool is namespaced as `mcp_{serverName}_{toolName}` to avoid
1787
+ * collisions with agent tools or tools from other servers.
1788
+ *
1789
+ * @param configs - Array of MCP server configurations
1790
+ * @param _clientFactory - Internal: override client construction for testing
1791
+ * @param hooks - Optional agent hooks for firing mcp:connect, mcp:error, mcp:close events
1792
+ */
1793
+ declare function connectMcpServers(configs: McpServerConfig[], _clientFactory?: () => Client, hooks?: Hookable<AgentHooks>): Promise<McpConnection>;
1794
+ //#endregion
1795
+ //#region src/skills/activation.d.ts
1796
+ /** How a skill was activated. Surfaced in `skills:activate` hook ctx. */
1797
+ type ActivationVia = 'model' | 'explicit' | 'resume';
1798
+ /** Reason a skill was deactivated. Surfaced in `skills:deactivate` hook ctx. */
1799
+ type DeactivationReason = 'run-end' | 'explicit' | 'reset';
1800
+ /** A skill currently active in the state machine. */
1801
+ interface ActiveSkill {
1802
+ skill: SkillConfig;
1803
+ activatedAt: number;
1804
+ activatedVia: ActivationVia;
1810
1805
  }
1811
- interface AgentStats {
1806
+ /**
1807
+ * Per-agent skill activation state. Public read-surface is the `active()` list
1808
+ * and `isActive(name)` predicate; writes go through `activate()` / `deactivate()`.
1809
+ */
1810
+ interface SkillActivationState {
1811
+ /** List of currently active skills in activation order. Returns a snapshot. */
1812
+ active: () => readonly ActiveSkill[];
1813
+ /** Is the skill with this canonical name currently active? */
1814
+ isActive: (name: string) => boolean;
1815
+ /** Retrieve the `ActiveSkill` record by name, or `undefined`. */
1816
+ get: (name: string) => ActiveSkill | undefined;
1812
1817
  /**
1813
- * Cumulative input tokens across the parent agent loop **and** every
1814
- * recursively-spawned sub-agent. Use this for billing / token-ledger
1815
- * consumption.
1818
+ * Mark a skill as active.
1819
+ * - Returns `'ok'` on a fresh activation (caller should fire `skills:activate`).
1820
+ * - Returns `'already-active'` if the skill was already in the set (idempotent).
1821
+ * - Returns `'cap-reached'` if the `maxActive` cap would be exceeded. State is unchanged.
1816
1822
  */
1817
- totalIn: number;
1818
- /** Cumulative output tokens. Same semantics as {@link AgentStats.totalIn}. */
1819
- totalOut: number;
1823
+ activate: (skill: SkillConfig, via: ActivationVia) => 'ok' | 'already-active' | 'cap-reached';
1820
1824
  /**
1821
- * Cumulative cache-read tokens across the parent agent loop and every
1822
- * recursively-spawned sub-agent. Surfaced at the top level (rather than
1823
- * only per-`TurnUsage`) because Anthropic prices cache reads at a separate
1824
- * line-item rate from regular input — billing-correct cost computation
1825
- * needs this number directly. Always `0` for providers that don't report
1826
- * cache usage.
1825
+ * Mark a skill as inactive. Returns the removed `ActiveSkill` record or `undefined`
1826
+ * if it wasn't active. Callers fire `skills:deactivate` on removal.
1827
1827
  */
1828
- totalCacheRead: number;
1828
+ deactivate: (name: string) => ActiveSkill | undefined;
1829
+ /** Remove every active skill. Returns the list of removed records. */
1830
+ clear: () => readonly ActiveSkill[];
1831
+ }
1832
+ interface SkillActivationStateOptions {
1829
1833
  /**
1830
- * Cumulative cache-creation tokens across the parent agent loop and every
1831
- * recursively-spawned sub-agent. Same rationale as
1832
- * {@link AgentStats.totalCacheRead} — separate Anthropic billing rate.
1833
- * Always `0` for providers that don't report cache usage.
1834
+ * Cap on concurrent activations. `undefined` (the default) disables the cap.
1835
+ * When set, `activate()` returns `'cap-reached'` once the set is at size `maxActive`.
1834
1836
  */
1835
- totalCacheCreation: number;
1837
+ maxActive?: number;
1838
+ }
1839
+ declare function createSkillActivationState(options?: SkillActivationStateOptions): SkillActivationState;
1840
+ //#endregion
1841
+ //#region src/agent.d.ts
1842
+ interface AgentHooks {
1843
+ 'system:before': (ctx: {
1844
+ system: string;
1845
+ }) => void;
1846
+ 'turn:before': (ctx: {
1847
+ turn: number;
1848
+ turnId: string;
1849
+ options: StreamOptions;
1850
+ }) => void;
1836
1851
  /**
1837
- * Number of parent agent-loop turns. Children's turn counts live under
1838
- * `children[].stats.turns` and are NOT folded in here a single "turns"
1839
- * number for the whole tree would conflate two different measures
1840
- * (parent-loop iterations vs. tree-wide tool-call rounds).
1852
+ * Fires after each assistant turn (before its tool-result follow-up
1853
+ * dispatches; the loop iterates back to a fresh `turn:before` once the
1854
+ * tool results are produced).
1841
1855
  *
1842
- * Tree-wide turn count: `flattenTurns(stats).length`.
1843
- */
1844
- turns: number;
1845
- /**
1846
- * Wall-clock duration of the top-level `agent.run()` call, in milliseconds.
1847
- * Children run during parent tool calls so this naturally subsumes child
1848
- * wall time — sequential children inflate it, parallel children compress
1849
- * into the parent's window.
1850
- */
1851
- elapsed: number;
1852
- /**
1853
- * Per-turn usage breakdown for the **parent loop only**. Children's per-turn
1854
- * usages live under `children[].stats.turnUsage`. Use {@link flattenTurns}
1855
- * to walk the full tree.
1856
- */
1857
- turnUsage?: TurnUsage[];
1858
- /**
1859
- * Cumulative cost in USD — parent loop plus every recursively-spawned
1860
- * sub-agent. Sums per-turn `TurnUsage.cost` reported by the provider.
1861
- * Absent when neither parent nor any descendant reported a non-zero cost.
1862
- */
1863
- cost?: number;
1864
- /** Stats from child agents spawned during this run, in completion order. Recursive. */
1865
- children?: ChildRunStats[];
1866
- /** Structured output from schema enforcement (only present when behavior.schema is set) */
1867
- output?: Record<string, unknown>;
1868
- /**
1869
- * Milliseconds from the start of `agent.run()` to the first observable signal from the
1870
- * provider (first `stream:text`, `stream:thinking`, or `tool:before` event).
1856
+ * `toolCounts.turn` calls **emitted** by the model in this assistant
1857
+ * turn, keyed by canonical tool name. Reflects what the model asked for,
1858
+ * regardless of downstream gate outcome. Most useful for spotting per-turn
1859
+ * spikes ("the model called todowrite 4 times in one turn").
1871
1860
  *
1872
- * Absent when the run produced no observable signals (e.g. aborted before any stream event).
1873
- */
1874
- timeTillFirstTokenMs?: number;
1875
- }
1876
- interface ChildRunStats {
1877
- id: string;
1878
- task: string;
1879
- /**
1880
- * The child agent's full {@link AgentStats}. Cumulative for that child's
1881
- * own subtree (child loop + its grandchildren). Do **not** sum
1882
- * `ctx.stats.totalIn` across `spawn:complete` events to derive top-level
1883
- * totals — `agent.run()`'s return value is the canonical cumulative root.
1861
+ * `toolCounts.run` cumulative running counter of **dispatched** calls
1862
+ * scoped to this `runId`, captured at fire time. Excludes calls that were
1863
+ * `block`ed by `tool:gate` handlers. Includes calls short-circuited via
1864
+ * `tool:gate` `result` substitution (the model still asked, the framework
1865
+ * just answered without the tool running). Resumed sessions start a fresh
1866
+ * run with empty counts.
1867
+ *
1868
+ * Both fields are frozen snapshots; mutate-safe.
1884
1869
  */
1885
- stats: AgentStats;
1870
+ 'turn:after': (ctx: {
1871
+ turn: number;
1872
+ turnId: string;
1873
+ usage: TurnUsage;
1874
+ message: SessionTurn;
1875
+ toolCounts: {
1876
+ turn: Readonly<Record<string, number>>;
1877
+ run: Readonly<Record<string, number>>;
1878
+ };
1879
+ }) => void;
1886
1880
  /**
1887
- * Subagent depth when this child ran. 1 = direct child of the top-level
1888
- * agent, 2 = grandchild, etc. Useful for telemetry that wants to group
1889
- * runs by depth.
1881
+ * Fires after a tool-results user turn is pushed onto the conversation,
1882
+ * before* any byte-budget enforcement or follow-up steering. Symmetric
1883
+ * with `turn:after` but for the user-role tool_result turn that closes the
1884
+ * round-trip.
1885
+ *
1886
+ * Why it exists: persistence layers must write tool_use/tool_result turn
1887
+ * pairs together — if only the assistant turn (carrying tool_use blocks)
1888
+ * is durable, a process death between turns leaves the DB with an orphan
1889
+ * tool_use that Anthropic rejects on resume. Subscribe here to persist
1890
+ * the tool-results turn before the loop continues.
1890
1891
  */
1891
- depth?: number;
1892
+ 'tool-results:after': (ctx: {
1893
+ turn: number;
1894
+ turnId: string;
1895
+ message: SessionTurn; /** Frozen list of tool results that were packed into `message`. */
1896
+ results: readonly ToolResult[];
1897
+ }) => void;
1898
+ 'stream:text': (ctx: StreamHookContext & {
1899
+ delta: string;
1900
+ text: string;
1901
+ }) => void;
1902
+ 'stream:end': (ctx: StreamHookContext & {
1903
+ text: string;
1904
+ }) => void;
1905
+ 'stream:thinking': (ctx: StreamHookContext & {
1906
+ delta: string;
1907
+ thinking: string;
1908
+ }) => void;
1909
+ 'oauth:refresh': (ctx: OAuthRefreshHookContext) => void;
1892
1910
  /**
1893
- * Terminal state of the child run. `'completed'` is the default. Exposed so
1894
- * a parent reading `stats.children` can distinguish aborted/timed-out
1895
- * children without re-parsing the returned string.
1911
+ * Fires before validation, `tool:before`, and `execute`. Two ways to
1912
+ * intercept:
1913
+ *
1914
+ * - Set `block = true` (with a `reason`) to refuse the call. The model
1915
+ * sees a `Blocked: <reason>` tool result; `tool:before` / `tool:after`
1916
+ * do **not** fire.
1917
+ * - Set `result` to substitute a successful tool_result and skip
1918
+ * execution. The model sees the substitute as a normal tool_result;
1919
+ * `tool:before` does not fire, but `tool:after` and `tool:transform`
1920
+ * do — so byte budgets, telemetry, and post-mutation hooks see the
1921
+ * substitute. Useful for cache hits, dedup, idempotency guards,
1922
+ * plan-mode synthetic acks.
1923
+ *
1924
+ * If multiple handlers along the chain set both `block` and `result`,
1925
+ * `block` wins — refusal beats substitution, so a policy gate
1926
+ * (skills allow-list, custom security) can always override an upstream
1927
+ * consumer's cache substitute. Mirrors the writable-`result` shape on
1928
+ * `tool:unknown` and `tool:error` so consumers learn one pattern.
1929
+ *
1930
+ * `runToolCounts` — frozen pre-call snapshot of per-tool dispatched
1931
+ * counts in this run. Use it to self-throttle, drive observability, or
1932
+ * implement budget guards. Counts every call that passed gate, including
1933
+ * dedup substitutes (Z19); excludes `block`ed calls.
1934
+ *
1935
+ * **Parallel mode** (`toolExecution: 'parallel'`, the default): the
1936
+ * snapshot is taken before any dispatches in the batch, so consumer
1937
+ * hooks reading `runToolCounts` see the pre-batch view. Built-in
1938
+ * budget / dedup middleware uses internal per-call reservation, so
1939
+ * `behavior.toolBudgets` enforces atomically even within a parallel
1940
+ * batch.
1896
1941
  */
1897
- status?: 'completed' | 'aborted' | 'timeout' | 'error';
1942
+ 'tool:gate': (ctx: ToolHookContext & {
1943
+ block: boolean;
1944
+ reason: string;
1945
+ result?: string | ToolResultContent[];
1946
+ runToolCounts: Readonly<Record<string, number>>;
1947
+ }) => void;
1948
+ 'tool:before': (ctx: ToolHookContext & {
1949
+ coercions?: readonly string[];
1950
+ runToolCounts: Readonly<Record<string, number>>;
1951
+ }) => void;
1952
+ 'tool:after': (ctx: ToolHookContext & {
1953
+ result: string | ToolResultContent[];
1954
+ outputBytes: number;
1955
+ coercions?: readonly string[];
1956
+ runToolCounts: Readonly<Record<string, number>>;
1957
+ }) => void;
1898
1958
  /**
1899
- * Final structured output when the child was run with `behavior.schema`.
1900
- * Mirrors `AgentStats.output` but is surfaced here so the parent can read
1901
- * it without peeking at the nested `stats` bag.
1959
+ * Fires when a tool throws during execution. Mutate `result` to substitute a
1960
+ * tool-output payload that gets sent back to the model in place of the
1961
+ * default `Tool error: <msg>` string useful for OSS-model error rewriting
1962
+ * (collapse stack traces, hide internal paths, prepend recovery hints).
1963
+ *
1964
+ * The post-hook value flows through `tool:transform` like a normal output, so
1965
+ * downstream byte-budgeting and image-stripping still apply.
1902
1966
  */
1903
- output?: Record<string, unknown>;
1904
- }
1905
- /**
1906
- * Base context for tool execution hooks.
1907
- *
1908
- * `name` is the canonical tool identity — the spec name registered on the agent (or the
1909
- * `mcp_{server}_{tool}` name for MCP tools). Hooks should policy-match against `name`.
1910
- *
1911
- * `displayName` is the outward-facing name — the alias surfaced to the LLM when
1912
- * `AgentOptions.toolAliases` maps the canonical name; otherwise equal to `name`.
1913
- * UI/telemetry adapters should emit `displayName`.
1914
- *
1915
- * Canonical vs. alias matters on session resume: `session.turns` persists canonical
1916
- * names only, so renaming an alias cannot desync history.
1917
- */
1918
- interface ToolHookContext {
1919
- turnId: string;
1920
- callId: string;
1921
- /** Canonical tool name (spec name). Stable across alias-map changes. */
1922
- name: string;
1923
- /** Aliased (wire) name — equal to `name` when no alias is defined. */
1924
- displayName: string;
1925
- input: Record<string, unknown>;
1926
- }
1927
- /**
1928
- * Base context for MCP tool hooks.
1929
- *
1930
- * `tool` is the native tool name on the MCP server. `server` is the configured server
1931
- * name. The canonical zidane-namespaced identity is `mcp_{server}_{tool}`.
1932
- *
1933
- * `displayName` equals the canonical namespaced name unless the agent has aliased
1934
- * this MCP tool via `AgentOptions.toolAliases`; in which case `displayName` is the
1935
- * alias that the LLM sees.
1936
- */
1937
- interface McpToolHookContext {
1938
- turnId: string;
1939
- callId: string;
1940
- server: string;
1941
- tool: string;
1942
- /** Aliased wire name for this MCP tool, or the canonical `mcp_{server}_{tool}` name. */
1943
- displayName: string;
1944
- input: Record<string, unknown>;
1945
- }
1946
- /** Base context for session hooks */
1947
- interface SessionHookContext {
1948
- sessionId: string;
1949
- }
1950
- /** Base context for spawn hooks */
1951
- interface SpawnHookContext {
1952
- id: string;
1953
- task: string;
1967
+ 'tool:error': (ctx: ToolHookContext & {
1968
+ error: Error;
1969
+ result?: string | ToolResultContent[];
1970
+ }) => void;
1971
+ 'tool:transform': (ctx: ToolHookContext & {
1972
+ result: string | ToolResultContent[];
1973
+ isError: boolean;
1974
+ outputBytes: number;
1975
+ coercions?: readonly string[];
1976
+ }) => void;
1954
1977
  /**
1955
- * Subagent depth for the spawn. 1 = direct child of the top-level agent.
1956
- * Present on spawn:before/complete/error. Absent for grandchild spawns that
1957
- * bubble through `child:*` events (which carry their own `depth`).
1978
+ * Fires before the generic "Unknown tool" error when the model invokes a tool
1979
+ * that isn't registered (hallucinated names, dropped MCP servers, dangling
1980
+ * aliases). Mutate `result` to substitute a friendly response or set
1981
+ * `suppressError: true` to skip the companion `tool:error` emission.
1982
+ *
1983
+ * Fires for any unknown tool name — including hallucinated MCP-style names
1984
+ * (`mcp_supabase_xxx`); branch on `name.startsWith('mcp_')` to differentiate.
1958
1985
  */
1959
- depth?: number;
1960
- }
1961
- /** Context for stream hooks */
1962
- interface StreamHookContext {
1963
- turnId: string;
1964
- }
1965
- /** Context for OAuth refresh hooks */
1966
- interface OAuthRefreshHookContext {
1967
- provider: string;
1968
- providerId: string;
1969
- source: 'params' | 'file';
1970
- previousCredentials: Record<string, unknown> & {
1971
- access: string;
1972
- refresh: string;
1973
- expires: number;
1974
- };
1975
- credentials: Record<string, unknown> & {
1976
- access: string;
1977
- refresh: string;
1978
- expires: number;
1979
- };
1980
- }
1981
- type SessionEndStatus = 'completed' | 'aborted' | 'error';
1982
- //#endregion
1983
- //#region src/providers/anthropic.d.ts
1984
- /**
1985
- * Server-side context-management config — the body of `context_management` on
1986
- * the Messages API. Typed loosely (Record-of-unknown) so we don't pin a specific
1987
- * SDK schema version: the v0.90 SDK does not yet type this field, but the wire
1988
- * format is stable behind the `context-management-2025-06-27` beta.
1989
- *
1990
- * See: https://docs.anthropic.com/en/docs/build-with-claude/context-management
1991
- */
1992
- interface AnthropicContextManagement {
1993
- edits?: Array<Record<string, unknown>>;
1994
- [key: string]: unknown;
1995
- }
1996
- interface AnthropicParams {
1997
- apiKey?: string;
1998
- access?: string;
1999
- refresh?: string;
2000
- expires?: number;
2001
- defaultModel?: string;
1986
+ 'tool:unknown': (ctx: ToolHookContext & {
1987
+ result?: string | ToolResultContent[];
1988
+ suppressError: boolean;
1989
+ }) => void;
2002
1990
  /**
2003
- * Optional override for the Anthropic SDK base URL. Honored end-to-end — headers and
2004
- * routing pass through to the forwarded host. Useful for proxies (e.g. corporate
2005
- * gateways, internal router).
1991
+ * Fires when `validateToolArgs` rejects an input that could not be auto-coerced
1992
+ * to satisfy the tool's `inputSchema`. Observational the tool call still
1993
+ * surfaces a `Validation error: …` string back to the model. Useful for
1994
+ * counting validation failures separately from runtime tool errors.
2006
1995
  */
2007
- baseURL?: string;
1996
+ 'validation:reject': (ctx: ToolHookContext & {
1997
+ reason: string;
1998
+ schema: Record<string, unknown>;
1999
+ }) => void;
2008
2000
  /**
2009
- * Additional `anthropic-beta` flags to opt into. Merged with the OAuth-path
2010
- * defaults (`claude-code-20250219`, `oauth-2025-04-20`); duplicates are
2011
- * de-duped. Examples:
2001
+ * Fires when `validateToolArgs` successfully auto-coerced one or more input
2002
+ * fields to satisfy the tool's `inputSchema`. **Only fires when at least one
2003
+ * coercion happened** — never on perfectly-shaped inputs. Useful for counting
2004
+ * model "wrongness rate" without re-running validation downstream.
2012
2005
  *
2013
- * - `'context-management-2025-06-27'` server-side context compaction
2014
- * (token-accurate; pair with {@link AnthropicParams.contextManagement}).
2015
- * - `'token-efficient-tools-2026-03-28'` — terser tool_use wire format.
2016
- * - `'interleaved-thinking-2025-05-14'` — think between tool calls within
2017
- * one turn.
2018
- * - `'redact-thinking-2026-02-12'` — replace large thinking blocks with
2019
- * stubs server-side.
2020
- * - `'prompt-caching-scope-2026-01-05'` — extended prompt-cache scope.
2006
+ * `coercions` lists the field names that were coerced. The values landed in
2007
+ * the input that the tool actually received; consumers wanting before/after
2008
+ * comparison can re-run `validateToolArgs(ctx.input, ctx.schema)`.
2009
+ */
2010
+ 'validation:coerce': (ctx: ToolHookContext & {
2011
+ coercions: readonly string[];
2012
+ schema: Record<string, unknown>;
2013
+ }) => void;
2014
+ 'context:transform': (ctx: {
2015
+ messages: SessionMessage[];
2016
+ }) => void;
2017
+ /**
2018
+ * Fires per request, after `context:transform` and before the request goes
2019
+ * out. Mutating `ctx.system` updates the system prompt the provider sends
2020
+ * for this turn — useful for runtime-derived sections (e.g. listing files
2021
+ * already read in the session, surfacing live tool budgets, injecting
2022
+ * skill activation reminders).
2021
2023
  *
2022
- * Honored on both the OAuth and API-key paths.
2024
+ * Cache breakpoints are applied inside the provider after this hook, so
2025
+ * mutations land in the cache key naturally — repeated turns with the
2026
+ * same derived system text still hit the cache.
2027
+ *
2028
+ * `messages` is read-only here; use `context:transform` for message
2029
+ * surgery. `session` is `undefined` when the run is sessionless.
2023
2030
  */
2024
- extraBetas?: readonly string[];
2031
+ 'system:transform': (ctx: {
2032
+ system: string;
2033
+ messages: readonly SessionMessage[];
2034
+ turn: number;
2035
+ turnId: string;
2036
+ session?: Session;
2037
+ }) => void;
2038
+ 'steer:inject': (ctx: {
2039
+ message: string;
2040
+ }) => void;
2041
+ 'spawn:before': (ctx: SpawnHookContext) => void;
2042
+ 'spawn:complete': (ctx: ChildRunStats) => void;
2043
+ 'spawn:error': (ctx: SpawnHookContext & {
2044
+ error: Error;
2045
+ }) => void;
2046
+ 'child:stream:text': (ctx: StreamHookContext & {
2047
+ delta: string;
2048
+ text: string;
2049
+ childId: string;
2050
+ depth: number;
2051
+ }) => void;
2052
+ 'child:stream:thinking': (ctx: StreamHookContext & {
2053
+ delta: string;
2054
+ thinking: string;
2055
+ childId: string;
2056
+ depth: number;
2057
+ }) => void;
2058
+ 'child:stream:end': (ctx: StreamHookContext & {
2059
+ text: string;
2060
+ childId: string;
2061
+ depth: number;
2062
+ }) => void;
2025
2063
  /**
2026
- * Server-side context-management directive. Sent on the request body as
2027
- * `context_management`. Requires the `context-management-2025-06-27` beta
2028
- * add it to {@link AnthropicParams.extraBetas}.
2064
+ * Gate-style child events. Unlike the other `child:*` events, the bubble
2065
+ * passes the **same `ctx` reference** the subagent's loop is awaiting on:
2066
+ * setting `ctx.block` / `ctx.reason` / `ctx.result` on a parent listener
2067
+ * propagates straight back to the child, refusing or substituting the call.
2029
2068
  *
2030
- * Typed loosely so future Anthropic schema additions land without an SDK
2031
- * bump. A typical compaction edit:
2069
+ * Use these to gate subagent tool calls (native + MCP) from the parent
2070
+ * without registering listeners on every child agent. The parent's own
2071
+ * `tool:gate` / `mcp:tool:gate` listeners are NOT auto-shared with
2072
+ * children — that would also share their budgets and dedup state.
2073
+ */
2074
+ 'child:tool:gate': (ctx: ToolHookContext & {
2075
+ block: boolean;
2076
+ reason: string;
2077
+ result?: string | ToolResultContent[];
2078
+ runToolCounts: Readonly<Record<string, number>>;
2079
+ childId: string;
2080
+ depth: number;
2081
+ }) => void;
2082
+ 'child:mcp:tool:gate': (ctx: McpToolHookContext & {
2083
+ block: boolean;
2084
+ reason: string;
2085
+ result?: string | ToolResultContent[];
2086
+ childId: string;
2087
+ depth: number;
2088
+ }) => void;
2089
+ 'child:tool:before': (ctx: ToolHookContext & {
2090
+ coercions?: readonly string[];
2091
+ runToolCounts: Readonly<Record<string, number>>;
2092
+ childId: string;
2093
+ depth: number;
2094
+ }) => void;
2095
+ 'child:tool:after': (ctx: ToolHookContext & {
2096
+ result: string | ToolResultContent[];
2097
+ outputBytes: number;
2098
+ coercions?: readonly string[];
2099
+ runToolCounts: Readonly<Record<string, number>>;
2100
+ childId: string;
2101
+ depth: number;
2102
+ }) => void;
2103
+ 'child:tool:error': (ctx: ToolHookContext & {
2104
+ error: Error;
2105
+ childId: string;
2106
+ depth: number;
2107
+ }) => void;
2108
+ 'child:turn:after': (ctx: {
2109
+ turn: number;
2110
+ turnId: string;
2111
+ usage: TurnUsage;
2112
+ message: SessionTurn;
2113
+ toolCounts: {
2114
+ turn: Readonly<Record<string, number>>;
2115
+ run: Readonly<Record<string, number>>;
2116
+ };
2117
+ childId: string;
2118
+ depth: number;
2119
+ }) => void;
2120
+ 'mcp:connect': (ctx: {
2121
+ name: string;
2122
+ transport: string;
2123
+ tools: string[];
2124
+ }) => void;
2125
+ 'mcp:error': (ctx: {
2126
+ name: string;
2127
+ error: Error;
2128
+ }) => void;
2129
+ 'mcp:close': (ctx: {
2130
+ name: string;
2131
+ }) => void;
2132
+ /**
2133
+ * Fires at the start of a per-server bootstrap attempt, before any network I/O.
2134
+ * Pairs with `mcp:bootstrap:end` and is always emitted, regardless of outcome.
2135
+ */
2136
+ 'mcp:bootstrap:start': (ctx: {
2137
+ name: string;
2138
+ transport: string;
2139
+ }) => void;
2140
+ /**
2141
+ * Fires at the end of a per-server bootstrap attempt. `durationMs` spans from
2142
+ * the matching `mcp:bootstrap:start`. On `ok: false` carries the originating
2143
+ * error so consumers can log / trace without relying on a separate `mcp:error`.
2144
+ */
2145
+ 'mcp:bootstrap:end': (ctx: {
2146
+ name: string;
2147
+ transport: string;
2148
+ durationMs: number;
2149
+ } & ({
2150
+ ok: true;
2151
+ toolCount: number;
2152
+ } | {
2153
+ ok: false;
2154
+ error: Error;
2155
+ })) => void;
2156
+ /**
2157
+ * Fires once per server after `listTools()` and after the config-side filters
2158
+ * (`enabledTools` / `disabledTools` / `toolFilter`) have applied, but BEFORE
2159
+ * tools are registered. Handlers may mutate `ctx.tools` in place — splicing,
2160
+ * reordering, or replacing entries — to further narrow what the model sees.
2161
+ *
2162
+ * Composes with config-side filters: config drops tools the host's static
2163
+ * policy excludes; this hook is the runtime escape hatch for per-user, per-
2164
+ * environment, or capability-driven decisions that the config can't express.
2032
2165
  *
2033
- * ```ts
2034
- * contextManagement: {
2035
- * edits: [{
2036
- * type: 'clear_tool_uses_20250919',
2037
- * trigger: { type: 'input_tokens', value: 180_000 },
2038
- * clear_at_least: { type: 'input_tokens', value: 140_000 },
2039
- * clear_tool_inputs: ['Read', 'Bash', 'Grep'],
2040
- * }],
2041
- * }
2042
- * ```
2166
+ * Items are upstream tool descriptors (NOT yet namespaced as `mcp_<server>_<tool>`).
2043
2167
  */
2044
- contextManagement?: AnthropicContextManagement;
2168
+ 'mcp:tools:filter': (ctx: {
2169
+ server: string;
2170
+ transport: 'stdio' | 'sse' | 'streamable-http';
2171
+ tools: Array<{
2172
+ name: string;
2173
+ description?: string | null;
2174
+ inputSchema?: unknown;
2175
+ }>;
2176
+ }) => void;
2045
2177
  /**
2046
- * Generic pass-through for fields on the Messages API request body that the
2047
- * SDK does not yet type. Spread into the request before the typed fields,
2048
- * so explicit options ({@link AnthropicParams.contextManagement} and the
2049
- * built-in fields like `model` / `tools` / `messages`) win on collision.
2178
+ * MCP-side counterpart of `tool:gate`. Same shape: set `block` to refuse,
2179
+ * set `result` to substitute a successful payload and skip the upstream
2180
+ * MCP `callTool`. When both are set across the handler chain, `block` wins.
2050
2181
  *
2051
- * Forward-compat escape hatch for new Anthropic betas when a future flag
2052
- * ships before zidane has a dedicated typed knob, set it here without
2053
- * waiting on a release. Most fields will still need the matching beta in
2054
- * {@link AnthropicParams.extraBetas}.
2182
+ * Fires INSIDE the MCP wrapper's `execute`, after the loop's `tool:gate`
2183
+ * already ran. Does **not** carry `runToolCounts` those are loop-level
2184
+ * and already exposed on `tool:gate` for MCP tools (which are registered
2185
+ * as agent tools under their namespaced name `mcp_<server>_<tool>`). Use
2186
+ * `tool:gate` for budget / dedup logic; reserve `mcp:tool:gate` for
2187
+ * MCP-specific concerns (per-server routing, transport-aware refusals).
2055
2188
  */
2056
- extraBodyParams?: Record<string, unknown>;
2057
- }
2058
- declare function anthropic(anthropicParams?: AnthropicParams): Provider;
2059
- //#endregion
2060
- //#region src/providers/cerebras.d.ts
2061
- interface CerebrasParams {
2062
- apiKey?: string;
2063
- defaultModel?: string;
2189
+ 'mcp:tool:gate': (ctx: McpToolHookContext & {
2190
+ block: boolean;
2191
+ reason: string;
2192
+ result?: string | ToolResultContent[];
2193
+ }) => void;
2194
+ 'mcp:tool:before': (ctx: McpToolHookContext) => void;
2195
+ 'mcp:tool:after': (ctx: McpToolHookContext & {
2196
+ result: string | ToolResultContent[];
2197
+ outputBytes: number;
2198
+ }) => void;
2199
+ 'mcp:tool:transform': (ctx: McpToolHookContext & {
2200
+ result: string | ToolResultContent[];
2201
+ outputBytes: number;
2202
+ }) => void;
2203
+ 'mcp:tool:error': (ctx: McpToolHookContext & {
2204
+ error: Error;
2205
+ }) => void;
2206
+ 'skills:resolve': (ctx: {
2207
+ skills: SkillConfig[];
2208
+ }) => void;
2209
+ 'skills:catalog': (ctx: {
2210
+ catalog: string;
2211
+ skills: SkillConfig[];
2212
+ }) => void;
2213
+ 'skills:activate': (ctx: {
2214
+ skill: SkillConfig;
2215
+ via: ActivationVia;
2216
+ }) => void;
2217
+ 'skills:deactivate': (ctx: {
2218
+ skill: SkillConfig;
2219
+ reason: DeactivationReason;
2220
+ }) => void;
2221
+ 'usage': (ctx: {
2222
+ turn: number;
2223
+ turnId: string;
2224
+ usage: TurnUsage;
2225
+ totalIn: number;
2226
+ totalOut: number;
2227
+ }) => void;
2228
+ 'output': (ctx: {
2229
+ output: Record<string, unknown>;
2230
+ schema: Record<string, unknown>;
2231
+ }) => void;
2064
2232
  /**
2065
- * Provider capability flags. Cerebras currently serves text-only OSS models
2066
- * (GLM, Llama-family, Qwen-family) default: `{ vision: false, imageInToolResult: false }`.
2067
- * Override when routing to a vision-capable deployment.
2233
+ * Fires when a turn's total tool-output bytes exceed `behavior.toolOutputBudget`.
2234
+ * Measured post-`tool:transform`. Loop injects a synthetic user message after
2235
+ * the tool-results turn instructing the model to summarize.
2068
2236
  */
2069
- capabilities?: ProviderCapabilities;
2070
- }
2071
- /**
2072
- * Cerebras provider.
2073
- *
2074
- * Thin wrapper around {@link openaiCompat} with Cerebras-specific defaults
2075
- * (base URL, default model).
2076
- */
2077
- declare function cerebras(params?: CerebrasParams): Provider;
2078
- //#endregion
2079
- //#region src/providers/openai.d.ts
2080
- interface OpenAIParams {
2081
- /** OpenAI Codex OAuth access token. Falls back to OPENAI_CODEX_API_KEY and .credentials.json. */
2082
- apiKey?: string;
2083
- /** Alias for apiKey, matching the OAuth credential field. */
2084
- access?: string;
2085
- refresh?: string;
2086
- expires?: number;
2087
- accountId?: string;
2088
- defaultModel?: string;
2089
- transport?: 'sse' | 'websocket' | 'auto';
2090
- }
2091
- declare function openai(params?: OpenAIParams): Provider;
2092
- //#endregion
2093
- //#region src/providers/openai-compat.d.ts
2094
- /**
2095
- * HTTP error thrown when an OpenAI-compatible endpoint returns a non-OK response.
2096
- *
2097
- * The body is best-effort JSON-parsed; `error.message` / `error.code` / `error.type`
2098
- * are extracted for clean downstream classification.
2099
- */
2100
- declare class OpenAICompatHttpError extends Error {
2101
- readonly status: number;
2102
- readonly providerCode?: string;
2103
- readonly bodyText: string;
2104
- constructor(status: number, bodyText: string);
2105
- }
2106
- /**
2107
- * Classify an OpenAI-compatible error into `ClassifiedError`.
2108
- *
2109
- * Recognizes:
2110
- * - `AbortError` (from fetch) → `aborted`.
2111
- * - `OpenAICompatHttpError` with a context-exceeded code or message → `context_exceeded`.
2112
- * - Any other `OpenAICompatHttpError` → `provider_error`.
2113
- *
2114
- * Returns `null` for unrecognized error shapes (the loop falls back to `AgentProviderError`).
2115
- */
2116
- declare function classifyOpenAICompatError(err: unknown): ClassifiedError | null;
2117
- /**
2118
- * Map an OpenAI-compatible `finish_reason` string to the zidane `TurnFinishReason` union.
2119
- */
2120
- declare function mapOAIFinishReason(reason: string | null | undefined): TurnFinishReason | undefined;
2121
- /**
2122
- * Auth header config. `scheme` is prepended to the api key with a space, e.g.
2123
- * `{ name: 'Authorization', scheme: 'Bearer' }` → `Authorization: Bearer <key>`.
2124
- * Omit `scheme` for raw header values (e.g. `{ name: 'X-Api-Key' }` → `X-Api-Key: <key>`).
2125
- *
2126
- * Real-world examples:
2127
- * - Default OpenAI / OpenRouter / Cerebras: `{ name: 'Authorization', scheme: 'Bearer' }`.
2128
- * - Baseten: `{ name: 'Authorization', scheme: 'Api-Key' }`.
2129
- * - Some gateways: `{ name: 'X-Api-Key' }`.
2130
- */
2131
- interface OpenAICompatAuthHeader {
2132
- name: string;
2133
- scheme?: string;
2134
- }
2135
- interface OpenAICompatParams {
2136
- /** Bearer-style API key. */
2137
- apiKey: string;
2138
- /** Base URL — `/chat/completions` is appended. */
2139
- baseURL: string;
2140
- /** Default model id when `run({ model })` is omitted. */
2141
- defaultModel?: string;
2142
- /** Provider name exposed as `Provider.name`. Defaults to `'openai-compat'`. */
2143
- name?: string;
2144
- /** Auth header shape. Defaults to `{ name: 'Authorization', scheme: 'Bearer' }`. */
2145
- authHeader?: OpenAICompatAuthHeader;
2146
- /** Extra headers sent with every request (e.g. referer, user-agent). */
2147
- extraHeaders?: Record<string, string>;
2237
+ 'budget:exceeded': (ctx: {
2238
+ turn: number;
2239
+ turnId: string;
2240
+ bytes: number;
2241
+ budget: number;
2242
+ }) => void;
2148
2243
  /**
2149
- * Provider-level capability flags. Routed into the message shaper and the
2150
- * agent loop so images in tool results + user messages are handled correctly
2151
- * for the underlying model.
2244
+ * Fires when a per-tool budget configured via `behavior.toolBudgets` is
2245
+ * exceeded for a specific tool. `mode` reflects how the framework reacted:
2246
+ * `'steer'` lets the call run and queues a post-turn nudge; `'block'`
2247
+ * refuses the call outright with `Blocked: <message>`.
2152
2248
  *
2153
- * Defaults when omitted: `vision: false`, `imageInToolResult: false` a
2154
- * conservative assumption matching most OSS text-only OpenAI-compat
2155
- * endpoints. Override when routing to a known vision-capable endpoint
2156
- * (e.g. OpenRouter vision models, Baseten image-capable deployments).
2249
+ * `count` is the run-cumulative dispatched count just before this call.
2250
+ * Use `turnId` to correlate with `turn:after` if you need the integer turn
2251
+ * index. Distinct from `budget:exceeded` (byte-level) so consumers can
2252
+ * subscribe specifically; both can fire in the same turn.
2157
2253
  */
2158
- capabilities?: ProviderCapabilities;
2254
+ 'tool-budget:exceeded': (ctx: {
2255
+ tool: string;
2256
+ count: number;
2257
+ max: number;
2258
+ turnId: string;
2259
+ mode: 'steer' | 'block';
2260
+ }) => void;
2261
+ 'agent:abort': (ctx: object) => void;
2159
2262
  /**
2160
- * Whether this endpoint honors `cache_control: { type: 'ephemeral' }` markers
2161
- * on message content parts and tool definitions.
2162
- *
2163
- * - `true` — inject markers when the caller asks for caching. OpenRouter routes
2164
- * to Anthropic/Gemini forward the markers; routes to OpenAI/DeepSeek/
2165
- * Grok/Groq/Moonshot ignore them (those backends cache automatically).
2166
- * - `false` — never inject markers. Safe default for endpoints that strictly
2167
- * validate the request schema (OpenAI direct, most OSS inference
2168
- * servers) and would reject unknown fields.
2263
+ * Run finished fires on all exit paths (completion, maxTurns, abort).
2169
2264
  *
2170
- * Default: `false`. The `openrouter` wrapper sets this to `true`.
2265
+ * Since 4.0 the `AgentStats` carried here is **cumulative** across the
2266
+ * parent agent loop and every recursively-spawned sub-agent
2267
+ * (`totalIn` / `totalOut` / `cost` / `totalCacheRead` / `totalCacheCreation`).
2268
+ * For parent-loop-only counts use `ctx.turnUsage` (parent-only array);
2269
+ * for tree-wide turn counts use `flattenTurns(ctx).length`.
2171
2270
  */
2172
- cacheBreakpoints?: boolean;
2173
- /**
2174
- * Whether this endpoint speaks OpenRouter's normalized reasoning envelope —
2175
- * `reasoning: { effort | max_tokens | exclude }` on requests and structured
2176
- * `reasoning_details[]` on assistant messages, round-tripped to preserve
2177
- * extended-reasoning state across turns.
2178
- *
2179
- * - `true` — map zidane's `behavior.thinking` / `behavior.thinkingBudget` to
2180
- * the request's `reasoning` field, capture `reasoning_details`
2181
- * from streaming responses into `provider_reasoning` blocks, and
2182
- * echo them back on subsequent assistant messages.
2183
- * - `false` — never set the field; drop any stored `provider_reasoning`
2184
- * blocks before sending. Safe default for hosts that strict-
2185
- * validate the request schema.
2271
+ 'agent:done': (ctx: AgentStats) => void;
2272
+ 'session:start': (ctx: SessionHookContext & {
2273
+ runId: string;
2274
+ prompt: string;
2275
+ }) => void;
2276
+ 'session:end': (ctx: SessionHookContext & {
2277
+ runId: string;
2278
+ status: SessionEndStatus;
2279
+ turnRange: [number, number];
2280
+ }) => void;
2281
+ 'session:turns': (ctx: SessionHookContext & {
2282
+ turns: SessionTurn[];
2283
+ count: number;
2284
+ }) => void;
2285
+ 'session:meta': (ctx: SessionHookContext & {
2286
+ key: string;
2287
+ value: unknown;
2288
+ }) => void;
2289
+ 'session:save': (ctx: SessionHookContext) => void;
2290
+ }
2291
+ interface AgentOptions {
2292
+ provider: Provider;
2293
+ /** Display name for the agent (used in traces/logs). */
2294
+ name?: string;
2295
+ /** Default system prompt injected when no system is provided at run time. */
2296
+ system?: string;
2297
+ /** Tool definitions available to the agent. Defaults to no tools. */
2298
+ tools?: Record<string, ToolDef>;
2299
+ /**
2300
+ * Map canonical tool names to LLM-facing (aliased) names.
2186
2301
  *
2187
- * Default: `false`. The `openrouter` wrapper sets this to `true`.
2302
+ * Aliasing is **LLM-boundary-only**: the alias is what the provider's tool spec
2303
+ * carries and what the model calls the tool; the canonical name is what lives in
2304
+ * `session.turns` and what the agent uses to look up the tool implementation.
2188
2305
  */
2189
- supportsReasoning?: boolean;
2306
+ toolAliases?: Record<string, string>;
2307
+ /** Agent-level behavior defaults (overridden by run-level behavior) */
2308
+ behavior?: AgentBehavior;
2309
+ /** Execution context: where tools run. Defaults to in-process. */
2310
+ execution?: ExecutionContext;
2311
+ /** MCP servers to connect and expose as tools */
2312
+ mcpServers?: McpServerConfig[];
2313
+ /** Session for identity, turn persistence, and run tracking */
2314
+ session?: Session;
2315
+ /** Skills configuration */
2316
+ skills?: SkillsConfig;
2190
2317
  /**
2191
- * Generic pass-through for fields on the Chat Completions request body that
2192
- * zidane does not yet type. Spread into the request before the typed core
2193
- * (model / messages / tools / max_tokens / stream / tool_choice), so
2194
- * explicit options always win on collision.
2318
+ * Test seam replaces the default MCP connector with a custom
2319
+ * implementation. Bypasses the `mcpServers` normalization layer entirely
2320
+ * and is **not** part of the supported public API. Subject to change or
2321
+ * removal in any release.
2195
2322
  *
2196
- * Forward-compat escape hatch for endpoints that ship one-off fields ahead
2197
- * of zidane (e.g. OpenAI `reasoning_effort`, OpenRouter `provider` routing,
2198
- * vendor-specific `safety_level` knobs).
2323
+ * @internal
2199
2324
  */
2200
- extraBodyParams?: Record<string, unknown>;
2201
- }
2202
- /**
2203
- * Factory for any OpenAI-compatible HTTP endpoint.
2204
- *
2205
- * Speaks the standard `POST /chat/completions` + `stream: true` + SSE dialect.
2206
- * Thin wrappers (`openrouter`, `cerebras`) call this with pinned defaults.
2207
- *
2208
- * @example Baseten (non-standard auth scheme)
2209
- * ```ts
2210
- * openaiCompat({
2211
- * name: 'baseten',
2212
- * apiKey: process.env.BASETEN_API_KEY!,
2213
- * baseURL: process.env.BASETEN_PROXY_URL!,
2214
- * authHeader: { name: 'Authorization', scheme: 'Api-Key' },
2215
- * })
2216
- * ```
2217
- */
2218
- declare function openaiCompat(params: OpenAICompatParams): Provider;
2219
- //#endregion
2220
- //#region src/providers/openrouter.d.ts
2221
- interface OpenRouterParams {
2222
- apiKey?: string;
2223
- defaultModel?: string;
2325
+ mcpConnector?: (configs: McpServerConfig[]) => Promise<McpConnection>;
2224
2326
  /**
2225
- * Provider capability flags. OpenRouter itself is a router whether vision or
2226
- * native image-in-tool-result are supported depends on the downstream model.
2227
- * Default: `{ vision: true, imageInToolResult: false }` — matches the default
2228
- * `anthropic/claude-sonnet-4-6` model (vision-capable via companion user-message
2229
- * fallback since OpenRouter exposes Claude over the Chat Completions dialect).
2327
+ * Pre-connect MCP servers in the background as soon as `createAgent` returns,
2328
+ * instead of deferring the bootstrap to the first `agent.run()`.
2230
2329
  *
2231
- * Override when routing to a known-text-only model (e.g. `meta-llama/llama-3-8b-instruct`).
2330
+ * Useful when MCP latency is the dominant cost of a cold start: callers that
2331
+ * construct the agent early (e.g. at process init) can hide the bootstrap
2332
+ * behind other setup work. If bootstrap fails, the error is stored and
2333
+ * surfaced on the first `agent.run()` / `agent.warmup()`; the in-flight
2334
+ * promise is `await`ed by both paths so the error is never silently lost.
2335
+ *
2336
+ * No-op when `mcpServers` is empty. Default: `false`.
2232
2337
  */
2233
- capabilities?: ProviderCapabilities;
2234
- }
2235
- /**
2236
- * OpenRouter provider.
2237
- *
2238
- * Thin wrapper around {@link openaiCompat} with OpenRouter-specific defaults
2239
- * (base URL, default model) and required attribution headers.
2240
- */
2241
- declare function openrouter(params?: OpenRouterParams): Provider;
2242
- //#endregion
2243
- //#region src/providers/index.d.ts
2244
- interface ToolSpec {
2245
- name: string;
2246
- description: string;
2247
- inputSchema: Record<string, unknown>;
2248
- }
2249
- interface ToolCall {
2250
- id: string;
2251
- name: string;
2252
- input: Record<string, unknown>;
2338
+ eager?: boolean;
2253
2339
  }
2254
- interface ToolResult {
2255
- id: string;
2340
+ interface Agent {
2341
+ hooks: Hookable<AgentHooks>;
2342
+ run: (options: AgentRunOptions) => Promise<AgentStats>;
2343
+ abort: () => void;
2344
+ steer: (message: string) => void;
2345
+ followUp: (message: string) => void;
2346
+ waitForIdle: () => Promise<void>;
2256
2347
  /**
2257
- * Tool output plain string for text-only tools (the common case) or a structured
2258
- * array of content blocks for tools that return images or mixed content (e.g. an
2259
- * MCP browser server returning a screenshot).
2260
- *
2261
- * Use `toolResultToText(content)` when a downstream consumer only handles strings.
2348
+ * Clear the agent's in-memory state (turns, queues, skill activations).
2349
+ * Fires `skills:deactivate` with `reason: 'reset'` for each previously active
2350
+ * skill. Awaiting lets host apps observe listener rejections.
2262
2351
  */
2263
- content: string | ToolResultContent[];
2264
- }
2265
- /**
2266
- * Provider-level capability flags used by the agent loop to route tool results
2267
- * and user messages appropriately.
2268
- *
2269
- * When a flag is `undefined` (omitted), the loop applies the conservative
2270
- * text-only default — images are stripped and replaced with a text marker so
2271
- * non-vision models do not confabulate about content they cannot see.
2272
- */
2273
- interface ProviderCapabilities {
2352
+ reset: () => Promise<void>;
2274
2353
  /**
2275
- * Model accepts image input anywhere (user messages and tool results).
2276
- *
2277
- * When `false`, the loop replaces image blocks with
2278
- * `"[image omitted — model does not support vision]"` before they reach the provider.
2279
- * Gives the model an honest marker instead of letting JSON-stringified base64 slip
2280
- * through and get confabulated over.
2354
+ * Destroy the execution context and clean up resources.
2355
+ * Idempotent — safe to call from both a `finally` block and a signal handler.
2281
2356
  */
2282
- vision?: boolean;
2357
+ destroy: () => Promise<void>;
2283
2358
  /**
2284
- * Provider wire format embeds images inside tool-role messages natively
2285
- * (Anthropic `tool_result.content` arrays, OpenAI Codex pi-ai `toolResult` blocks).
2286
- *
2287
- * When `false`, a vision-capable provider still gets images — but via a
2288
- * companion `user` message emitted immediately after the flattened
2289
- * `tool`/`tool_result` marker. This is the Claude Desktop / Cline pattern
2290
- * and works on any OpenAI Chat Completions endpoint that accepts image
2291
- * URLs in user messages.
2359
+ * Explicitly activate a skill by name. Fires `skills:activate` with
2360
+ * `via: 'explicit'`. Throws if the skill isn't in the resolved catalog or
2361
+ * if the `maxActive` cap is reached. Idempotent — activating an already-active
2362
+ * skill is a no-op.
2292
2363
  */
2293
- imageInToolResult?: boolean;
2294
- }
2295
- interface StreamCallbacks {
2296
- onText: (delta: string) => void;
2297
- onThinking?: (delta: string) => void;
2298
- onOAuthRefresh?: (ctx: OAuthRefreshHookContext) => void | Promise<void>;
2299
- }
2300
- interface TurnResult {
2301
- /** Full assistant turn as a SessionMessage */
2302
- assistantMessage: SessionMessage;
2303
- /** Text content blocks concatenated */
2304
- text: string;
2305
- /** Tool calls requested by the model */
2306
- toolCalls: ToolCall[];
2307
- /** Whether the model wants to stop */
2308
- done: boolean;
2309
- usage: TurnUsage;
2310
- }
2311
- interface StreamOptions {
2312
- model: string;
2313
- system: string;
2314
- tools: unknown[];
2315
- messages: SessionMessage[];
2316
- maxTokens: number;
2317
- /** Thinking/reasoning level (optional, default: off) */
2318
- thinking?: ThinkingLevel;
2319
- /** Exact thinking token budget — overrides the level-based default when set */
2320
- thinkingBudget?: number;
2321
- /** Force tool selection behavior */
2322
- toolChoice?: {
2323
- type: 'auto' | 'required' | 'tool';
2324
- name?: string;
2325
- };
2364
+ activateSkill: (name: string) => Promise<void>;
2326
2365
  /**
2327
- * Enable prompt caching on this call. When `true`, providers that support it
2328
- * insert `cache_control` breakpoints on the system prompt, last tool, and
2329
- * last stable message so the shared prefix is cached across turns.
2330
- *
2331
- * Default: `false` (providers opt callers in — the agent loop passes `true`).
2366
+ * Deactivate a skill by name. Fires `skills:deactivate` with `reason: 'explicit'`.
2367
+ * No-op when the skill wasn't active.
2332
2368
  */
2333
- cache?: boolean;
2334
- /** Abort signal for cancellation */
2335
- signal?: AbortSignal;
2336
- }
2337
- interface Provider {
2338
- readonly name: string;
2339
- readonly meta: {
2340
- defaultModel: string; /** Provider-level capability flags. See {@link ProviderCapabilities}. */
2341
- capabilities?: ProviderCapabilities;
2342
- } & Record<string, unknown>;
2343
- /** Format tool specs for this provider */
2344
- formatTools: (tools: ToolSpec[]) => unknown[];
2345
- /** Create a text-only user message. Multimodal content goes through `promptMessage`. */
2346
- userMessage: (content: string) => SessionMessage;
2347
- /** Create an assistant message (for priming) */
2348
- assistantMessage: (content: string) => SessionMessage;
2349
- /** Create a tool results message to send back */
2350
- toolResultsMessage: (results: ToolResult[]) => SessionMessage;
2351
- /** Stream a turn, calling onText for each text delta */
2352
- stream: (options: StreamOptions, callbacks: StreamCallbacks) => Promise<TurnResult>;
2369
+ deactivateSkill: (name: string) => Promise<void>;
2353
2370
  /**
2354
- * Build a user `SessionMessage` from multimodal prompt parts.
2371
+ * Pre-connect MCP servers without running a turn. Idempotent and concurrency-safe:
2372
+ * - No MCP servers configured → resolves immediately.
2373
+ * - Connection already established → resolves immediately.
2374
+ * - Another `warmup()` / `run()` is bootstrapping → awaits the in-flight promise.
2355
2375
  *
2356
- * Providers that cannot handle a particular part type (e.g. document) should throw.
2357
- * The agent loop always canonicalizes the run-level prompt into parts before calling
2358
- * this method; providers may fall back to `userMessage` for the text-only path if
2359
- * they do not implement this.
2376
+ * Use from host code that wants to hide MCP bootstrap latency behind other
2377
+ * startup work (UI init, auth, etc.). Safe to call multiple times and from
2378
+ * multiple callers concurrently.
2360
2379
  */
2361
- promptMessage?: (parts: PromptPart[]) => SessionMessage;
2380
+ warmup: () => Promise<void>;
2381
+ readonly isRunning: boolean;
2382
+ readonly turns: SessionTurn[];
2383
+ readonly execution: ExecutionContext;
2384
+ readonly handle: ExecutionHandle | null;
2385
+ readonly session: Session | null;
2386
+ /** Snapshot of currently active skills. */
2387
+ readonly activeSkills: readonly ActiveSkill[];
2362
2388
  /**
2363
- * Classify a native provider error for downstream typed-error wrapping.
2364
- *
2365
- * Return `null` when the error is not recognized the loop will wrap it in
2366
- * `AgentProviderError` with the provider's name. Return a `ClassifiedError` to
2367
- * route it to one of the typed error classes.
2389
+ * Frozen view of the underlying `provider.meta`. Read-only to prevent
2390
+ * accidental cross-agent contamination — writes are rejected at runtime
2391
+ * (via `Object.freeze`) and at compile time (via `Readonly`). To override
2392
+ * model / capability defaults, construct a new provider.
2368
2393
  */
2369
- classifyError?: (err: unknown) => ClassifiedError | null;
2394
+ readonly meta: Readonly<Record<string, unknown>>;
2370
2395
  }
2396
+ declare function createAgent({
2397
+ provider,
2398
+ name: agentName,
2399
+ system: agentSystem,
2400
+ tools: agentTools,
2401
+ toolAliases,
2402
+ behavior: agentBehavior,
2403
+ execution,
2404
+ mcpServers,
2405
+ session,
2406
+ skills: agentSkills,
2407
+ mcpConnector,
2408
+ eager
2409
+ }: AgentOptions): Agent;
2371
2410
  //#endregion
2372
- export { ToolDef as $, PromptDocumentPart as A, createMemoryStore as At, SpawnHookContext as B, AgentContextExceededError as Bt, AgentBehavior as C, RemoteStoreOptions as Ct, McpServerConfig as D, fromOpenAI as Dt, ChildRunStats as E, fromAnthropic as Et, SessionContentBlock as F, connectMcpServers as Ft, ToolResultContent as G, ClassifiedErrorKind as Gt, ThinkingLevel as H, AgentToolNotAllowedError as Ht, SessionEndStatus as I, normalizeMcpBlocks as It, TurnFinishReason as J, ToolResultImageContent as K, matchesContextExceeded as Kt, SessionHookContext as L, normalizeMcpServers as Lt, PromptPart as M, FileMapStoreOptions as Mt, PromptTextPart as N, createFileMapStore as Nt, McpToolHookContext as O, toAnthropic as Ot, RunHookMap as P, McpConnection as Pt, ToolContext as Q, SessionMessage as R, resultToString as Rt, anthropic as S, loadSession as St, AgentStats as T, autoDetectAndConvert as Tt, ToolExecutionMode as U, CONTEXT_EXCEEDED_MESSAGE_PATTERNS as Ut, StreamHookContext as V, AgentProviderError as Vt, ToolHookContext as W, ClassifiedError as Wt, toolOutputByteLength as X, TurnUsage as Y, toolResultToText as Z, OpenAIParams as _, Session as _t, ToolCall as a, ActivationVia as at, cerebras as b, SessionStore as bt, TurnResult as c, SkillActivationState as ct, OpenAICompatAuthHeader as d, SkillConfig as dt, ToolMap as et, OpenAICompatHttpError as f, SkillDiagnostic as ft, openaiCompat as g, CreateSessionOptions as gt, mapOAIFinishReason as h, SkillsConfig as ht, StreamOptions as i, createAgent as it, PromptImagePart as j, FileMapAdapter as jt, OAuthRefreshHookContext as k, toOpenAI as kt, OpenRouterParams as l, SkillActivationStateOptions as lt, classifyOpenAICompatError as m, SkillSource as mt, ProviderCapabilities as n, AgentHooks as nt, ToolResult as o, ActiveSkill as ot, OpenAICompatParams as p, SkillResource as pt, ToolResultTextContent as q, toTypedError as qt, StreamCallbacks as r, AgentOptions as rt, ToolSpec as s, DeactivationReason as st, Provider as t, Agent as tt, openrouter as u, createSkillActivationState as ut, openai as v, SessionData as vt, AgentRunOptions as w, createRemoteStore as wt, AnthropicParams as x, createSession as xt, CerebrasParams as y, SessionRun as yt, SessionTurn as z, AgentAbortedError as zt };
2373
- //# sourceMappingURL=index-bgh-k8Mv.d.ts.map
2411
+ export { OpenAICompatHttpError as $, loadSession as A, ToolExecutionMode as At, FileMapStoreOptions as B, AgentContextExceededError as Bt, SkillsConfig as C, SessionEndStatus as Ct, SessionRun as D, SpawnHookContext as Dt, SessionData as E, SessionTurn as Et, fromOpenAI as F, TurnFinishReason as Ft, StreamOptions as G, ClassifiedErrorKind as Gt, Provider as H, AgentToolNotAllowedError as Ht, toAnthropic as I, TurnUsage as It, ToolSpec as J, ToolCall as K, matchesContextExceeded as Kt, toOpenAI as L, toolOutputByteLength as Lt, createRemoteStore as M, ToolResultContent as Mt, autoDetectAndConvert as N, ToolResultImageContent as Nt, SessionStore as O, StreamHookContext as Ot, fromAnthropic as P, ToolResultTextContent as Pt, OpenAICompatAuthHeader as Q, createMemoryStore as R, toolResultToText as Rt, SkillSource as S, SessionContentBlock as St, Session as T, SessionMessage as Tt, ProviderCapabilities as U, CONTEXT_EXCEEDED_MESSAGE_PATTERNS as Ut, createFileMapStore as V, AgentProviderError as Vt, StreamCallbacks as W, ClassifiedError as Wt, OpenRouterParams as X, TurnResult as Y, openrouter as Z, ToolDef as _, PromptDocumentPart as _t, ActivationVia as a, openai as at, SkillDiagnostic as b, PromptTextPart as bt, SkillActivationState as c, AnthropicParams as ct, McpConnection as d, AgentRunOptions as dt, OpenAICompatParams as et, connectMcpServers as f, AgentStats as ft, ToolContext as g, OAuthRefreshHookContext as gt, resultToString as h, McpToolHookContext as ht, createAgent as i, OpenAIParams as it, RemoteStoreOptions as j, ToolHookContext as jt, createSession as k, ThinkingLevel as kt, SkillActivationStateOptions as l, anthropic as lt, normalizeMcpServers as m, McpServerConfig as mt, AgentHooks as n, mapOAIFinishReason as nt, ActiveSkill as o, CerebrasParams as ot, normalizeMcpBlocks as p, ChildRunStats as pt, ToolResult as q, toTypedError as qt, AgentOptions as r, openaiCompat as rt, DeactivationReason as s, cerebras as st, Agent as t, classifyOpenAICompatError as tt, createSkillActivationState as u, AgentBehavior as ut, ToolMap as v, PromptImagePart as vt, CreateSessionOptions as w, SessionHookContext as wt, SkillResource as x, RunHookMap as xt, SkillConfig as y, PromptPart as yt, FileMapAdapter as z, AgentAbortedError as zt };
2412
+ //# sourceMappingURL=agent-CMIhYhDz.d.ts.map