zidane 3.1.1 → 3.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -15,10 +15,10 @@ Built to be embedded.
15
15
  A small, hookable core with sensible defaults so most consumers don't write a single hook. Built around three principles: **token discipline by default** (cache, dedup, compaction, byte-accounting), **self-healing on the fault paths** (auto-coerce args, hallucinated-tool fallback, error rewriting), and **provider parity** (server-side features on Anthropic, client-side equivalents everywhere else).
16
16
 
17
17
  - 🧠 **Multi-provider, multi-auth** — Anthropic, OpenAI Codex, OpenRouter, Cerebras, plus a generic `openaiCompat` factory (Baseten, Fireworks, Groq, local servers). OAuth + API key, auto-refreshing tokens. Anthropic accepts opt-in `extraBetas` and `contextManagement` for first-party features.
18
- - 🪝 **Streaming, hookable turn loop** — text/thinking deltas, tool calls, MCP, sessions, skills, spawn, OAuth, validation, budgets — all observable (and most mutatable) via typed hook events.
19
- - 🛠 **Tools first-class** — `shell`, `read_file`, `write_file`, `edit`, `multi_edit`, `glob`, `grep`, `spawn`, human-in-the-loop, plus any [MCP](https://modelcontextprotocol.io) server. Sequential or parallel, per-call gates (`tool:gate`), validation auto-coerce (`"true"` → `true`), hallucinated-tool fallback (`tool:unknown`), error rewriting (`tool:error` → `result`).
20
- - ✂️ **Token-aware ergonomics** — paginated reads with a "how to page" footer, 8 KB tail-truncated `shell`, idempotent `write_file`; `outputBytes` surfaced on every tool/MCP hook. `behavior.toolOutputBudget` injects a "summarize" nudge when a turn's outputs exceed the cap.
21
- - 🗜 **Context discipline** — auto-injected `cache_control` breakpoints (Anthropic + OpenRouter); server-side compaction via `context-management-2025-06-27` on Anthropic, `behavior.compactStrategy: 'tail'` on everyone else. Per-session `read_file` dedup + opt-in `requireReadBeforeEdit` guard kill stale-content edits.
18
+ - 🪝 **Streaming, hookable turn loop** — text/thinking deltas, tool calls, MCP, sessions, skills, spawn, OAuth, validation, budgets — all observable (and most mutatable) via typed hook events. Per-request `system:transform` hook for runtime-derived prompt sections.
19
+ - 🛠 **Tools first-class** — `shell`, `read_file`, `write_file`, `edit`, `multi_edit`, `glob`, `grep`, `spawn`, human-in-the-loop, plus any [MCP](https://modelcontextprotocol.io) server. Sequential or parallel, per-call gates (`tool:gate` with writable `block` / `result` / `runToolCounts`), validation auto-coerce (`"true"` → `true`), hallucinated-tool fallback (`tool:unknown`), error rewriting (`tool:error` → `result`).
20
+ - ✂️ **Token-aware ergonomics** — paginated reads with a "how to page" footer, 8 KB tail-truncated `shell`, idempotent `write_file`; `outputBytes` surfaced on every tool/MCP hook. `behavior.toolOutputBudget` injects a "summarize" nudge when a turn's outputs exceed the cap; `behavior.toolBudgets` caps per-tool call counts (`'steer'` or `'block'`); `behavior.thinkingDecay` tapers reasoning budget per turn.
21
+ - 🗜 **Context discipline** — auto-injected `cache_control` breakpoints (Anthropic + OpenRouter); server-side compaction via `context-management-2025-06-27` on Anthropic, `behavior.compactStrategy: 'tail'` on everyone else. Per-session `read_file` dedup + opt-in `requireReadBeforeEdit` guard kill stale-content edits; `behavior.dedupTools` generalizes the same pattern to arbitrary tools (`todowrite`, `execute_sql`, …).
22
22
  - 🎯 **Reasoning + structured output** — thinking levels (`off` / `minimal` / `low` / `medium` / `high` / `adaptive`) with optional exact budgets; force the final answer to a JSON Schema (Zod v4 interop), no brittle parsing.
23
23
  - 💾 **Sessions, skills, multimodal** — pluggable session stores (memory / SQLite / remote / file-map), incremental persistence; [Agent Skills](https://agentskills.io/specification) spec-aligned with `allowed-tools` enforcement + resume rehydration; images + documents via `PromptPart[]`, tools can return image blocks routed natively on vision providers or via companion messages elsewhere.
24
24
  - 🧵 **Sub-agents + execution contexts** — delegate to child agents with inherited or overridden preset (child events bubble to the parent); run tools in-process, Docker, or any `SandboxProvider` (E2B / Rivet / custom). Parallel MCP bootstrap with `agent.warmup()` + `eager: true` to hide cold starts.
@@ -67,10 +67,13 @@ createAgent({
67
67
  maxTurns: 50, // max loop iterations
68
68
  maxTokens: 16384, // max tokens per LLM response
69
69
  thinkingBudget: 10240, // exact thinking token budget
70
+ thinkingDecay: { afterTurn: 5, factor: 0.5, floor: 1024 }, // taper budget per run-relative turn
70
71
  cache: true, // prompt-cache breakpoints on supported providers (default: true)
71
72
  toolOutputBudget: 32768, // soft per-turn cap on tool-output bytes (off by default)
72
73
  dedupReads: true, // dedup identical re-reads of the same file in `read_file` (default: true)
74
+ dedupTools: { todowrite: i => JSON.stringify(i.todos) }, // generic per-tool argument dedup
73
75
  requireReadBeforeEdit: false, // refuse `edit` / `multi_edit` against unread or stale files (default: false)
76
+ toolBudgets: { todowrite: { max: 6, onExceed: 'steer' } }, // per-tool soft call caps
74
77
  compactStrategy: 'off', // client-side tail compaction for non-Anthropic providers — 'off' | 'tail' (default: 'off')
75
78
  compactThreshold: 131_072, // bytes threshold that triggers tail compaction (default: 128 KiB)
76
79
  compactKeepTurns: 4, // trailing turns left intact during compaction (default: 4)
@@ -162,6 +165,18 @@ Fallback: `params.apiKey` > `params.access` > `ANTHROPIC_API_KEY` env > `.creden
162
165
 
163
166
  `extraBetas` are merged with the OAuth defaults (`claude-code-20250219`, `oauth-2025-04-20`) and de-duped. `contextManagement` is sent on the request body as `context_management`; pair it with the `context-management-2025-06-27` beta. For non-Anthropic providers, see `behavior.compactStrategy: 'tail'` for the client-side fallback.
164
167
 
168
+ `extraBodyParams` is a generic forward-compat pass-through for un-typed Messages API fields. Spread into the request before the typed core, so explicit factory options always win on collision. Use it when Anthropic ships a new beta before zidane has a dedicated knob:
169
+
170
+ ```ts
171
+ anthropic({
172
+ apiKey: '...',
173
+ extraBetas: ['some-future-beta'],
174
+ extraBodyParams: { future_field: { /* ... */ } },
175
+ })
176
+ ```
177
+
178
+ `openaiCompat` accepts the same `extraBodyParams` for OpenAI-style endpoints (e.g. `reasoning_effort`, `metadata`, OpenRouter `provider` routing).
179
+
165
180
  ### OpenRouter
166
181
 
167
182
  ```ts
@@ -367,15 +382,19 @@ All tool hooks include `turnId` and `callId` for correlation. Typed via `ToolHoo
367
382
 
368
383
  ```ts
369
384
  agent.hooks.hook('tool:gate', (ctx) => {
370
- // ctx.turnId, ctx.callId, ctx.name, ctx.input
385
+ // ctx.turnId, ctx.callId, ctx.name, ctx.input, ctx.runToolCounts
371
386
  if (ctx.name === 'shell' && String(ctx.input.command).includes('rm -rf')) {
372
387
  ctx.block = true
373
388
  ctx.reason = 'dangerous command'
374
389
  }
390
+ // Substitute a successful result without running the tool — mirrors
391
+ // tool:unknown / tool:error. When both are set, `block` wins.
392
+ if (ctx.name === 'todowrite' && (ctx.runToolCounts.todowrite ?? 0) > 0)
393
+ ctx.result = 'Already recorded; no-op.'
375
394
  })
376
395
 
377
- agent.hooks.hook('tool:before', (ctx) => { /* ctx.turnId, ctx.callId, ctx.name, ctx.input, ctx.coercions? */ })
378
- agent.hooks.hook('tool:after', (ctx) => { /* + ctx.result, ctx.outputBytes, ctx.coercions? */ })
396
+ agent.hooks.hook('tool:before', (ctx) => { /* ctx.turnId, ctx.callId, ctx.name, ctx.input, ctx.runToolCounts, ctx.coercions? */ })
397
+ agent.hooks.hook('tool:after', (ctx) => { /* + ctx.result, ctx.outputBytes, ctx.runToolCounts, ctx.coercions? */ })
379
398
  agent.hooks.hook('tool:error', (ctx) => {
380
399
  // + ctx.error. Mutate ctx.result to substitute the payload sent back to the
381
400
  // model in place of the default `Tool error: <msg>` — useful for OSS-model
@@ -434,6 +453,20 @@ agent.hooks.hook('context:transform', (ctx) => {
434
453
  })
435
454
  ```
436
455
 
456
+ ### System transform
457
+
458
+ Mutate the system prompt per request — useful for runtime-derived sections (files already read in the session, live tool budgets, skill activation reminders). Fires after `context:transform`, before the request goes out. `messages` is read-only here.
459
+
460
+ ```ts
461
+ agent.hooks.hook('system:transform', (ctx) => {
462
+ // ctx.system, ctx.messages (readonly), ctx.turn, ctx.turnId, ctx.session?
463
+ if (ctx.session && ctx.turn > 1)
464
+ ctx.system += `\n\n## Reminder: keep responses concise after turn ${ctx.turn}.`
465
+ })
466
+ ```
467
+
468
+ Cache breakpoints land naturally inside the provider after this hook, so repeated turns with the same derived system text still hit the cache.
469
+
437
470
  ### Hook recipes
438
471
 
439
472
  Three patterns that don't have a built-in default. Copy-paste and tune.
@@ -484,6 +517,12 @@ const agent = createAgent({
484
517
  agent.hooks.hook('budget:exceeded', (ctx) => {
485
518
  console.warn(`turn ${ctx.turn}: ${ctx.bytes} > ${ctx.budget} bytes`)
486
519
  })
520
+
521
+ agent.hooks.hook('tool-budget:exceeded', (ctx) => {
522
+ // Per-tool counterpart, fires when `behavior.toolBudgets[ctx.tool]` trips.
523
+ // ctx.tool, ctx.count, ctx.max, ctx.turnId, ctx.mode ('steer' | 'block')
524
+ console.warn(`tool ${ctx.tool} hit cap (${ctx.count}/${ctx.max}, mode=${ctx.mode})`)
525
+ })
487
526
  ```
488
527
 
489
528
  ### Client-side context compaction (non-Anthropic)
@@ -510,6 +549,70 @@ Anthropic users should prefer the server-side `context-management-2025-06-27` be
510
549
 
511
550
  `behavior.requireReadBeforeEdit` (off by default) — `edit` and `multi_edit` reject when the file hasn't been read in the session, or when its on-disk content has drifted since the last read. Eliminates the silent-corruption case where a model edits against bytes it "remembers" but no longer reflect reality. Recommended on for stricter eval-grade runs.
512
551
 
552
+ ### Generic per-tool dedup
553
+
554
+ `behavior.dedupTools` extends the read-file pattern to arbitrary tools. Provide a hasher per tool keyed by canonical name; identical inputs replay the prior result without re-running the tool. Requires a session.
555
+
556
+ The hasher contract has **three return values, three meanings** — pick deliberately:
557
+
558
+ | Return | Meaning |
559
+ |---|---|
560
+ | non-empty string | Cache key for this call. Equal keys replay the prior result. |
561
+ | `undefined` | **Skip dedup for this call.** Tool runs normally; nothing recorded. |
562
+ | `''` or non-string | Treated as `undefined` (defensive). |
563
+
564
+ ```ts
565
+ behavior: {
566
+ dedupTools: {
567
+ // Always cache by full input — every identical re-call dedups.
568
+ todowrite: input => JSON.stringify(input),
569
+
570
+ // Cache by a normalized subset; non-cacheable shapes opt out via `undefined`.
571
+ execute_sql: (input) => {
572
+ const q = typeof input.query === 'string' ? input.query.trim().toLowerCase() : undefined
573
+ if (!q || q.includes('now()') || q.includes('random()')) return undefined
574
+ return q
575
+ },
576
+ },
577
+ }
578
+ ```
579
+
580
+ The `undefined` opt-out is **not** the same as `JSON.stringify(input)` — that would dedup against the verbatim input. Use `undefined` to mean "this specific call is not cacheable" (timestamps baked in, randomness, debug flags).
581
+
582
+ Tools with side effects or non-deterministic outputs (network, time, randomness) **must not** be listed — there is no safety net beyond the consumer's hasher. For MCP tools, key by the namespaced wire name (`mcp_<server>_<tool>`).
583
+
584
+ ### Per-tool call budgets
585
+
586
+ `behavior.toolBudgets` caps per-tool calls per run. Two reactions:
587
+
588
+ - `'steer'` — let the call run, but emit a synthetic user message after the turn nudging the model to commit and finish. Fires once per tool per run.
589
+ - `'block'` — refuse subsequent calls with `Blocked: <reason>`.
590
+
591
+ ```ts
592
+ behavior: {
593
+ toolBudgets: {
594
+ todowrite: { max: 6, onExceed: 'steer' },
595
+ execute_sql: { max: 3, onExceed: 'block' },
596
+ },
597
+ }
598
+ ```
599
+
600
+ Pass a function for custom messages: `onExceed: ctx => ({ mode: 'steer', message: '...' })`. Subscribe to `tool-budget:exceeded` for telemetry. Counts include dedup hits — by design, since both eat against agent-loop sanity.
601
+
602
+ ### Adaptive thinking budget
603
+
604
+ `behavior.thinkingDecay` tapers the thinking budget across turns. Late turns are usually checkpoint / cleanup work where reasoning rarely pays for itself.
605
+
606
+ ```ts
607
+ behavior: {
608
+ thinkingBudget: 8192,
609
+ thinkingDecay: { afterTurn: 5, factor: 0.5, floor: 1024 },
610
+ // turn 1-5 → 8192, turn 6 → 4096, turn 7 → 2048, turn 8+ → 1024
611
+ }
612
+ ```
613
+
614
+ Pass a function for arbitrary curves: `thinkingDecay: (turn, base) => base / Math.sqrt(turn)`. No-op when `thinkingBudget` is unset. Honored by every provider that respects `thinkingBudget`.
615
+
513
616
  ## Steering and Follow-up
514
617
 
515
618
  ### Steering
@@ -229,6 +229,126 @@ interface AgentBehavior {
229
229
  * Default: `true`.
230
230
  */
231
231
  dedupReads?: boolean;
232
+ /**
233
+ * Taper the thinking budget over the course of a run. Late turns are
234
+ * usually checkpoint / cleanup work where reasoning rarely pays for
235
+ * itself; early turns benefit most. Two forms:
236
+ *
237
+ * - **Struct** — geometric decay starting after `afterTurn`, multiplying by
238
+ * `factor` each subsequent turn, clamped to `floor`. Example
239
+ * `{ afterTurn: 5, factor: 0.5, floor: 1024 }` with a base budget of 8192:
240
+ * turns 1-5 = 8192, turn 6 = 4096, turn 7 = 2048, turn 8+ = 1024.
241
+ * - **Function** — `(runTurn, baseBudget) => number`. Arbitrary curves;
242
+ * `runTurn` is 1-indexed, run-relative (resumed sessions reset).
243
+ *
244
+ * No-op when `thinkingBudget` is unset. Honored by every provider that
245
+ * respects `thinkingBudget` (anthropic legacy enabled+budget path,
246
+ * adaptive `maxTokensCap`, openai-compat `max_tokens` padding).
247
+ *
248
+ * Default: `undefined` (no decay).
249
+ */
250
+ thinkingDecay?: {
251
+ afterTurn: number;
252
+ factor: number;
253
+ floor: number;
254
+ } | ((runTurn: number, baseBudget: number) => number);
255
+ /**
256
+ * Per-tool soft call budget for this run. Keyed by **canonical** tool name.
257
+ * On the first call after the run-cumulative dispatched count for that tool
258
+ * reaches `max`, the framework fires `onExceed`:
259
+ *
260
+ * - `'steer'` (default) — let the call execute, but emit a synthetic user
261
+ * message after the turn that nudges the model away from re-calling the
262
+ * tool. Reuses the existing post-turn steer pathway used by
263
+ * `toolOutputBudget`. Fires `tool-budget:exceeded` with `mode: 'steer'`.
264
+ * - `'block'` — refuse the call via `tool:gate` `block`. The model sees a
265
+ * `Blocked: <reason>` tool result. Fires `tool-budget:exceeded` with
266
+ * `mode: 'block'`.
267
+ * - **Function** — `(ctx) => { mode, message }`. The consumer supplies the
268
+ * steering / refusal text and chooses the mode dynamically.
269
+ *
270
+ * Counts include both real dispatches and dedup substitutes (Z19 hits).
271
+ * Excludes calls already blocked by an earlier gate (skill allow-list,
272
+ * consumer hook). Tool dispatched by spawned subagents has its own per-run
273
+ * counter — child counts never charge the parent.
274
+ *
275
+ * For MCP tools, key by the namespaced wire name (`mcp_<server>_<tool>`).
276
+ *
277
+ * Atomic in parallel mode: the middleware tracks its own per-tool
278
+ * approval counter, incremented synchronously at gate-time. A
279
+ * 4-call parallel batch against `max: 2` will let the first 2 through
280
+ * and refuse the rest, even though the loop's `runToolCounts` only
281
+ * propagates between calls (not within a single batch's gate fan-out).
282
+ *
283
+ * Default: `undefined` (no budget enforcement).
284
+ */
285
+ toolBudgets?: Record<string, {
286
+ max: number;
287
+ onExceed?: 'steer' | 'block' | ((ctx: {
288
+ tool: string;
289
+ count: number;
290
+ max: number;
291
+ }) => {
292
+ mode: 'steer' | 'block';
293
+ message: string;
294
+ });
295
+ }>;
296
+ /**
297
+ * Generic per-tool argument deduplication. Keyed by the tool's **canonical**
298
+ * name (alias-stable). Each entry is a hasher: `(input) => string | undefined`.
299
+ *
300
+ * **Hasher contract** — three return values, three meanings:
301
+ *
302
+ * | Return | Meaning |
303
+ * |-------------------------|------------------------------------------------------------------------|
304
+ * | a non-empty string | Cache key for this call. Equal keys (most-recent-only, this session) |
305
+ * | | replay the prior recorded result without re-dispatching the tool. |
306
+ * | `undefined` | **Skip dedup for this call.** The tool runs normally; nothing recorded.|
307
+ * | `''` / non-string | Treated identically to `undefined` (defensive: no dedup, no error). |
308
+ *
309
+ * The `undefined` opt-out is the way to say *"this specific call is not
310
+ * cacheable"* (timestamps in input, randomness baked in, debug flags). It
311
+ * is **not** the same as `JSON.stringify(input)` — that would dedup against
312
+ * the verbatim input. Pick one explicitly:
313
+ *
314
+ * ```ts
315
+ * // Always cache by full input — every identical re-call dedups.
316
+ * dedupTools: { todowrite: input => JSON.stringify(input) }
317
+ *
318
+ * // Cache by a normalized subset; non-cacheable shapes opt out.
319
+ * dedupTools: {
320
+ * execute_sql: (input) => {
321
+ * const q = typeof input.query === 'string' ? input.query.trim().toLowerCase() : undefined
322
+ * if (!q || q.includes('now()') || q.includes('random()')) return undefined
323
+ * return q
324
+ * },
325
+ * }
326
+ * ```
327
+ *
328
+ * On a hit, the previously-recorded result is replayed as the tool_result
329
+ * without dispatching the tool. The substitution flows through `tool:gate`
330
+ * `result` (Z20), so `tool:after` and `tool:transform` still fire.
331
+ *
332
+ * Requires a session (`createSession()`); without one, the map is a silent
333
+ * no-op since per-session state has nowhere to live. Tools with side
334
+ * effects or non-deterministic outputs (network, time, randomness) MUST
335
+ * NOT be listed — there is no safety net beyond the consumer's hasher.
336
+ *
337
+ * For MCP tools, key by the namespaced wire name (`mcp_<server>_<tool>`).
338
+ * Parallel mode (`toolExecution: 'parallel'`, the default) sees calls in
339
+ * the SAME assistant turn race against each other — none can dedup against
340
+ * a sibling that started in the same batch. Sequential mode honors order
341
+ * within a turn.
342
+ *
343
+ * **Cache policy**: only the most recent `(hash, result)` per tool is
344
+ * retained. Interleaved patterns (input A, input B, input A) miss on the
345
+ * second A because B overwrote it. Sufficient for the common spam-the-
346
+ * same-call loop; consumers needing a richer cache should hook
347
+ * `tool:gate` directly.
348
+ *
349
+ * Default: `undefined` (no per-tool dedup).
350
+ */
351
+ dedupTools?: Record<string, (input: Record<string, unknown>) => string | undefined>;
232
352
  /**
233
353
  * Require `read_file` before `edit` / `multi_edit` on the same path, and
234
354
  * reject edits when the file has changed on disk since the last read in
@@ -674,6 +794,18 @@ interface AnthropicParams {
674
794
  * ```
675
795
  */
676
796
  contextManagement?: AnthropicContextManagement;
797
+ /**
798
+ * Generic pass-through for fields on the Messages API request body that the
799
+ * SDK does not yet type. Spread into the request before the typed fields,
800
+ * so explicit options ({@link AnthropicParams.contextManagement} and the
801
+ * built-in fields like `model` / `tools` / `messages`) win on collision.
802
+ *
803
+ * Forward-compat escape hatch for new Anthropic betas — when a future flag
804
+ * ships before zidane has a dedicated typed knob, set it here without
805
+ * waiting on a release. Most fields will still need the matching beta in
806
+ * {@link AnthropicParams.extraBetas}.
807
+ */
808
+ extraBodyParams?: Record<string, unknown>;
677
809
  }
678
810
  declare function anthropic(anthropicParams?: AnthropicParams): Provider;
679
811
 
@@ -796,6 +928,17 @@ interface OpenAICompatParams {
796
928
  * Default: `false`. The `openrouter` wrapper sets this to `true`.
797
929
  */
798
930
  cacheBreakpoints?: boolean;
931
+ /**
932
+ * Generic pass-through for fields on the Chat Completions request body that
933
+ * zidane does not yet type. Spread into the request before the typed core
934
+ * (model / messages / tools / max_tokens / stream / tool_choice), so
935
+ * explicit options always win on collision.
936
+ *
937
+ * Forward-compat escape hatch for endpoints that ship one-off fields ahead
938
+ * of zidane (e.g. OpenAI `reasoning_effort`, OpenRouter `provider` routing,
939
+ * vendor-specific `safety_level` knobs).
940
+ */
941
+ extraBodyParams?: Record<string, unknown>;
799
942
  }
800
943
  /**
801
944
  * Factory for any OpenAI-compatible HTTP endpoint.
@@ -1557,11 +1700,34 @@ interface AgentHooks {
1557
1700
  turnId: string;
1558
1701
  options: StreamOptions;
1559
1702
  }) => void;
1703
+ /**
1704
+ * Fires after each assistant turn (before its tool-result follow-up
1705
+ * dispatches; the loop iterates back to a fresh `turn:before` once the
1706
+ * tool results are produced).
1707
+ *
1708
+ * `toolCounts.turn` — calls **emitted** by the model in this assistant
1709
+ * turn, keyed by canonical tool name. Reflects what the model asked for,
1710
+ * regardless of downstream gate outcome. Most useful for spotting per-turn
1711
+ * spikes ("the model called todowrite 4 times in one turn").
1712
+ *
1713
+ * `toolCounts.run` — cumulative running counter of **dispatched** calls
1714
+ * scoped to this `runId`, captured at fire time. Excludes calls that were
1715
+ * `block`ed by `tool:gate` handlers. Includes calls short-circuited via
1716
+ * `tool:gate` `result` substitution (the model still asked, the framework
1717
+ * just answered without the tool running). Resumed sessions start a fresh
1718
+ * run with empty counts.
1719
+ *
1720
+ * Both fields are frozen snapshots; mutate-safe.
1721
+ */
1560
1722
  'turn:after': (ctx: {
1561
1723
  turn: number;
1562
1724
  turnId: string;
1563
1725
  usage: TurnUsage;
1564
1726
  message: SessionTurn;
1727
+ toolCounts: {
1728
+ turn: Readonly<Record<string, number>>;
1729
+ run: Readonly<Record<string, number>>;
1730
+ };
1565
1731
  }) => void;
1566
1732
  'stream:text': (ctx: StreamHookContext & {
1567
1733
  delta: string;
@@ -1575,17 +1741,53 @@ interface AgentHooks {
1575
1741
  thinking: string;
1576
1742
  }) => void;
1577
1743
  'oauth:refresh': (ctx: OAuthRefreshHookContext) => void;
1744
+ /**
1745
+ * Fires before validation, `tool:before`, and `execute`. Two ways to
1746
+ * intercept:
1747
+ *
1748
+ * - Set `block = true` (with a `reason`) to refuse the call. The model
1749
+ * sees a `Blocked: <reason>` tool result; `tool:before` / `tool:after`
1750
+ * do **not** fire.
1751
+ * - Set `result` to substitute a successful tool_result and skip
1752
+ * execution. The model sees the substitute as a normal tool_result;
1753
+ * `tool:before` does not fire, but `tool:after` and `tool:transform`
1754
+ * do — so byte budgets, telemetry, and post-mutation hooks see the
1755
+ * substitute. Useful for cache hits, dedup, idempotency guards,
1756
+ * plan-mode synthetic acks.
1757
+ *
1758
+ * If multiple handlers along the chain set both `block` and `result`,
1759
+ * `block` wins — refusal beats substitution, so a policy gate
1760
+ * (skills allow-list, custom security) can always override an upstream
1761
+ * consumer's cache substitute. Mirrors the writable-`result` shape on
1762
+ * `tool:unknown` and `tool:error` so consumers learn one pattern.
1763
+ *
1764
+ * `runToolCounts` — frozen pre-call snapshot of per-tool dispatched
1765
+ * counts in this run. Use it to self-throttle, drive observability, or
1766
+ * implement budget guards. Counts every call that passed gate, including
1767
+ * dedup substitutes (Z19); excludes `block`ed calls.
1768
+ *
1769
+ * **Parallel mode** (`toolExecution: 'parallel'`, the default): the
1770
+ * snapshot is taken before any dispatches in the batch, so consumer
1771
+ * hooks reading `runToolCounts` see the pre-batch view. Built-in
1772
+ * budget / dedup middleware uses internal per-call reservation, so
1773
+ * `behavior.toolBudgets` enforces atomically even within a parallel
1774
+ * batch.
1775
+ */
1578
1776
  'tool:gate': (ctx: ToolHookContext & {
1579
1777
  block: boolean;
1580
1778
  reason: string;
1779
+ result?: string | ToolResultContent[];
1780
+ runToolCounts: Readonly<Record<string, number>>;
1581
1781
  }) => void;
1582
1782
  'tool:before': (ctx: ToolHookContext & {
1583
1783
  coercions?: readonly string[];
1784
+ runToolCounts: Readonly<Record<string, number>>;
1584
1785
  }) => void;
1585
1786
  'tool:after': (ctx: ToolHookContext & {
1586
1787
  result: string | ToolResultContent[];
1587
1788
  outputBytes: number;
1588
1789
  coercions?: readonly string[];
1790
+ runToolCounts: Readonly<Record<string, number>>;
1589
1791
  }) => void;
1590
1792
  /**
1591
1793
  * Fires when a tool throws during execution. Mutate `result` to substitute a
@@ -1646,6 +1848,27 @@ interface AgentHooks {
1646
1848
  'context:transform': (ctx: {
1647
1849
  messages: SessionMessage[];
1648
1850
  }) => void;
1851
+ /**
1852
+ * Fires per request, after `context:transform` and before the request goes
1853
+ * out. Mutating `ctx.system` updates the system prompt the provider sends
1854
+ * for this turn — useful for runtime-derived sections (e.g. listing files
1855
+ * already read in the session, surfacing live tool budgets, injecting
1856
+ * skill activation reminders).
1857
+ *
1858
+ * Cache breakpoints are applied inside the provider after this hook, so
1859
+ * mutations land in the cache key naturally — repeated turns with the
1860
+ * same derived system text still hit the cache.
1861
+ *
1862
+ * `messages` is read-only here; use `context:transform` for message
1863
+ * surgery. `session` is `undefined` when the run is sessionless.
1864
+ */
1865
+ 'system:transform': (ctx: {
1866
+ system: string;
1867
+ messages: readonly SessionMessage[];
1868
+ turn: number;
1869
+ turnId: string;
1870
+ session?: Session;
1871
+ }) => void;
1649
1872
  'steer:inject': (ctx: {
1650
1873
  message: string;
1651
1874
  }) => void;
@@ -1673,6 +1896,7 @@ interface AgentHooks {
1673
1896
  }) => void;
1674
1897
  'child:tool:before': (ctx: ToolHookContext & {
1675
1898
  coercions?: readonly string[];
1899
+ runToolCounts: Readonly<Record<string, number>>;
1676
1900
  childId: string;
1677
1901
  depth: number;
1678
1902
  }) => void;
@@ -1680,6 +1904,7 @@ interface AgentHooks {
1680
1904
  result: string | ToolResultContent[];
1681
1905
  outputBytes: number;
1682
1906
  coercions?: readonly string[];
1907
+ runToolCounts: Readonly<Record<string, number>>;
1683
1908
  childId: string;
1684
1909
  depth: number;
1685
1910
  }) => void;
@@ -1693,6 +1918,10 @@ interface AgentHooks {
1693
1918
  turnId: string;
1694
1919
  usage: TurnUsage;
1695
1920
  message: SessionTurn;
1921
+ toolCounts: {
1922
+ turn: Readonly<Record<string, number>>;
1923
+ run: Readonly<Record<string, number>>;
1924
+ };
1696
1925
  childId: string;
1697
1926
  depth: number;
1698
1927
  }) => void;
@@ -1732,9 +1961,22 @@ interface AgentHooks {
1732
1961
  ok: false;
1733
1962
  error: Error;
1734
1963
  })) => void;
1964
+ /**
1965
+ * MCP-side counterpart of `tool:gate`. Same shape: set `block` to refuse,
1966
+ * set `result` to substitute a successful payload and skip the upstream
1967
+ * MCP `callTool`. When both are set across the handler chain, `block` wins.
1968
+ *
1969
+ * Fires INSIDE the MCP wrapper's `execute`, after the loop's `tool:gate`
1970
+ * already ran. Does **not** carry `runToolCounts` — those are loop-level
1971
+ * and already exposed on `tool:gate` for MCP tools (which are registered
1972
+ * as agent tools under their namespaced name `mcp_<server>_<tool>`). Use
1973
+ * `tool:gate` for budget / dedup logic; reserve `mcp:tool:gate` for
1974
+ * MCP-specific concerns (per-server routing, transport-aware refusals).
1975
+ */
1735
1976
  'mcp:tool:gate': (ctx: McpToolHookContext & {
1736
1977
  block: boolean;
1737
1978
  reason: string;
1979
+ result?: string | ToolResultContent[];
1738
1980
  }) => void;
1739
1981
  'mcp:tool:before': (ctx: McpToolHookContext) => void;
1740
1982
  'mcp:tool:after': (ctx: McpToolHookContext & {
@@ -1785,6 +2027,24 @@ interface AgentHooks {
1785
2027
  bytes: number;
1786
2028
  budget: number;
1787
2029
  }) => void;
2030
+ /**
2031
+ * Fires when a per-tool budget configured via `behavior.toolBudgets` is
2032
+ * exceeded for a specific tool. `mode` reflects how the framework reacted:
2033
+ * `'steer'` lets the call run and queues a post-turn nudge; `'block'`
2034
+ * refuses the call outright with `Blocked: <message>`.
2035
+ *
2036
+ * `count` is the run-cumulative dispatched count just before this call.
2037
+ * Use `turnId` to correlate with `turn:after` if you need the integer turn
2038
+ * index. Distinct from `budget:exceeded` (byte-level) so consumers can
2039
+ * subscribe specifically; both can fire in the same turn.
2040
+ */
2041
+ 'tool-budget:exceeded': (ctx: {
2042
+ tool: string;
2043
+ count: number;
2044
+ max: number;
2045
+ turnId: string;
2046
+ mode: 'steer' | 'block';
2047
+ }) => void;
1788
2048
  'agent:abort': (ctx: object) => void;
1789
2049
  'agent:done': (ctx: AgentStats) => void;
1790
2050
  'session:start': (ctx: SessionHookContext & {
@@ -11,7 +11,7 @@ import {
11
11
  } from "./chunk-IUBBVF53.js";
12
12
  import {
13
13
  connectMcpServers
14
- } from "./chunk-R74LQKAM.js";
14
+ } from "./chunk-7H34OFDA.js";
15
15
  import {
16
16
  toolOutputByteLength
17
17
  } from "./chunk-JH6IAAFA.js";
@@ -197,6 +197,17 @@ function getReadState(session) {
197
197
  }
198
198
  return map;
199
199
  }
200
+ var TOOL_DEDUP_STATE = /* @__PURE__ */ new WeakMap();
201
+ function getToolDedupState(session) {
202
+ if (!session)
203
+ return void 0;
204
+ let map = TOOL_DEDUP_STATE.get(session);
205
+ if (!map) {
206
+ map = /* @__PURE__ */ new Map();
207
+ TOOL_DEDUP_STATE.set(session, map);
208
+ }
209
+ return map;
210
+ }
200
211
  function hashContent(text) {
201
212
  let h = 2166136261;
202
213
  for (let i = 0; i < text.length; i++) {
@@ -1279,6 +1290,59 @@ function rewriteMessagesToWire(messages, maps) {
1279
1290
  return messages.map((msg) => ({ ...msg, content: rewriteContentToWire(msg.content, maps) }));
1280
1291
  }
1281
1292
 
1293
+ // src/dedup-tools.ts
1294
+ function installDedupToolsGate(hooks, getDedupTools, getSession) {
1295
+ const pending = /* @__PURE__ */ new Map();
1296
+ function pendingKey(callId, name) {
1297
+ return `${callId}::${name}`;
1298
+ }
1299
+ function gateHandler(ctx) {
1300
+ if (ctx.block || ctx.result !== void 0)
1301
+ return;
1302
+ const dedupTools = getDedupTools();
1303
+ const hasher = dedupTools?.[ctx.name];
1304
+ if (!hasher)
1305
+ return;
1306
+ const session = getSession();
1307
+ const state = getToolDedupState(session);
1308
+ if (!state)
1309
+ return;
1310
+ let hash;
1311
+ try {
1312
+ hash = hasher(ctx.input);
1313
+ } catch {
1314
+ return;
1315
+ }
1316
+ if (typeof hash !== "string" || hash.length === 0)
1317
+ return;
1318
+ const prior = state.get(ctx.name);
1319
+ if (prior && prior.hash === hash) {
1320
+ ctx.result = prior.result;
1321
+ return;
1322
+ }
1323
+ pending.set(pendingKey(ctx.callId, ctx.name), hash);
1324
+ }
1325
+ function afterHandler(ctx) {
1326
+ const key = pendingKey(ctx.callId, ctx.name);
1327
+ const hash = pending.get(key);
1328
+ if (hash === void 0)
1329
+ return;
1330
+ pending.delete(key);
1331
+ const session = getSession();
1332
+ const state = getToolDedupState(session);
1333
+ if (!state)
1334
+ return;
1335
+ state.set(ctx.name, { hash, result: ctx.result });
1336
+ }
1337
+ const unregisterGate = hooks.hook("tool:gate", gateHandler);
1338
+ const unregisterAfter = hooks.hook("tool:after", afterHandler);
1339
+ return function uninstall() {
1340
+ unregisterGate();
1341
+ unregisterAfter();
1342
+ pending.clear();
1343
+ };
1344
+ }
1345
+
1282
1346
  // src/tools/validation.ts
1283
1347
  var TRUE_STRINGS = /* @__PURE__ */ new Set(["true", "True", "TRUE", "1", "yes", "Yes", "YES"]);
1284
1348
  var FALSE_STRINGS = /* @__PURE__ */ new Set(["false", "False", "FALSE", "0", "no", "No", "NO"]);
@@ -1434,6 +1498,24 @@ function formatValue(value) {
1434
1498
 
1435
1499
  // src/loop.ts
1436
1500
  var IMAGE_OMITTED_MARKER = "[image omitted \u2014 model does not support vision]";
1501
+ function applyThinkingDecay(baseBudget, decay, turn) {
1502
+ if (typeof baseBudget !== "number" || baseBudget <= 0)
1503
+ return baseBudget;
1504
+ if (!decay)
1505
+ return baseBudget;
1506
+ let raw;
1507
+ if (typeof decay === "function") {
1508
+ raw = decay(turn, baseBudget);
1509
+ } else {
1510
+ if (turn <= decay.afterTurn)
1511
+ return baseBudget;
1512
+ const k = turn - decay.afterTurn;
1513
+ raw = Math.max(decay.floor, baseBudget * decay.factor ** k);
1514
+ }
1515
+ if (Number.isNaN(raw) || raw <= 0)
1516
+ return 0;
1517
+ return Math.round(Math.min(baseBudget, raw));
1518
+ }
1437
1519
  function turnsToMessages(turns) {
1438
1520
  return turns.filter((t) => t.role !== "system").map((t) => ({ role: t.role, content: t.content }));
1439
1521
  }
@@ -1589,6 +1671,7 @@ async function executeTurn(ctx, turn) {
1589
1671
  const keep = typeof ctx.compactKeepTurns === "number" && ctx.compactKeepTurns >= 0 ? ctx.compactKeepTurns : 4;
1590
1672
  sanitizedMessages = applyTailCompaction(sanitizedMessages, threshold, keep);
1591
1673
  }
1674
+ const effectiveThinkingBudget = applyThinkingDecay(ctx.thinkingBudget, ctx.thinkingDecay, turn);
1592
1675
  const streamOptions = {
1593
1676
  model: ctx.model,
1594
1677
  system: ctx.system,
@@ -1596,13 +1679,22 @@ async function executeTurn(ctx, turn) {
1596
1679
  messages: sanitizedMessages,
1597
1680
  maxTokens: ctx.maxTokens ?? 16384,
1598
1681
  thinking: ctx.thinking,
1599
- thinkingBudget: ctx.thinkingBudget,
1682
+ thinkingBudget: effectiveThinkingBudget,
1600
1683
  cache: ctx.cache ?? true,
1601
1684
  signal: ctx.signal
1602
1685
  };
1603
1686
  const transformCtx = { messages: streamOptions.messages };
1604
1687
  await ctx.hooks.callHook("context:transform", transformCtx);
1605
1688
  streamOptions.messages = transformCtx.messages;
1689
+ const systemCtx = {
1690
+ system: streamOptions.system,
1691
+ messages: streamOptions.messages,
1692
+ turn,
1693
+ turnId,
1694
+ ...ctx.session ? { session: ctx.session } : {}
1695
+ };
1696
+ await ctx.hooks.callHook("system:transform", systemCtx);
1697
+ streamOptions.system = systemCtx.system;
1606
1698
  await ctx.hooks.callHook("turn:before", { turn, turnId, options: streamOptions });
1607
1699
  let currentText = "";
1608
1700
  let currentThinking = "";
@@ -1635,7 +1727,13 @@ async function executeTurn(ctx, turn) {
1635
1727
  createdAt: Date.now()
1636
1728
  };
1637
1729
  ctx.turns.push(errorTurn);
1638
- await ctx.hooks.callHook("turn:after", { turn, turnId, usage: errorUsage, message: errorTurn });
1730
+ await ctx.hooks.callHook("turn:after", {
1731
+ turn,
1732
+ turnId,
1733
+ usage: errorUsage,
1734
+ message: errorTurn,
1735
+ toolCounts: { turn: Object.freeze({}), run: Object.freeze({ ...ctx.runToolCounts }) }
1736
+ });
1639
1737
  throw wrapProviderError(err, ctx);
1640
1738
  }
1641
1739
  if (currentText) {
@@ -1658,7 +1756,16 @@ async function executeTurn(ctx, turn) {
1658
1756
  createdAt: Date.now()
1659
1757
  };
1660
1758
  ctx.turns.push(assistantTurn);
1661
- await ctx.hooks.callHook("turn:after", { turn, turnId, usage: result.usage, message: assistantTurn });
1759
+ const turnCounts = {};
1760
+ for (const tc of canonicalToolCalls)
1761
+ turnCounts[tc.name] = (turnCounts[tc.name] ?? 0) + 1;
1762
+ await ctx.hooks.callHook("turn:after", {
1763
+ turn,
1764
+ turnId,
1765
+ usage: result.usage,
1766
+ message: assistantTurn,
1767
+ toolCounts: { turn: Object.freeze(turnCounts), run: Object.freeze({ ...ctx.runToolCounts }) }
1768
+ });
1662
1769
  if (result.done) {
1663
1770
  if (ctx.schema && !ctx.signal.aborted) {
1664
1771
  const outputSpec = {
@@ -1771,6 +1878,7 @@ async function executeSingleTool(ctx, call, turnId) {
1771
1878
  const toolDef = ctx.tools[call.name];
1772
1879
  const callId = call.id;
1773
1880
  const displayName = toWireName(call.name, ctx.aliasMaps);
1881
+ const runToolCounts = Object.freeze({ ...ctx.runToolCounts });
1774
1882
  const gateCtx = {
1775
1883
  turnId,
1776
1884
  callId,
@@ -1778,12 +1886,27 @@ async function executeSingleTool(ctx, call, turnId) {
1778
1886
  displayName,
1779
1887
  input: call.input,
1780
1888
  block: false,
1781
- reason: "Tool execution was blocked"
1889
+ reason: "Tool execution was blocked",
1890
+ runToolCounts
1782
1891
  };
1783
1892
  await ctx.hooks.callHook("tool:gate", gateCtx);
1784
1893
  if (gateCtx.block) {
1785
1894
  return { result: { id: callId, content: `Blocked: ${gateCtx.reason}` } };
1786
1895
  }
1896
+ ctx.runToolCounts[call.name] = (ctx.runToolCounts[call.name] ?? 0) + 1;
1897
+ if (gateCtx.result !== void 0) {
1898
+ const substitute = await emitToolResult(ctx, {
1899
+ turnId,
1900
+ callId,
1901
+ name: call.name,
1902
+ displayName,
1903
+ input: gateCtx.input,
1904
+ output: gateCtx.result,
1905
+ isError: false,
1906
+ runToolCounts
1907
+ });
1908
+ return { result: { id: callId, content: substitute } };
1909
+ }
1787
1910
  let effectiveInput = gateCtx.input;
1788
1911
  if (!toolDef) {
1789
1912
  const unknownCtx = {
@@ -1841,6 +1964,7 @@ async function executeSingleTool(ctx, call, turnId) {
1841
1964
  name: call.name,
1842
1965
  displayName,
1843
1966
  input: effectiveInput,
1967
+ runToolCounts,
1844
1968
  ...coercions ? { coercions } : {}
1845
1969
  });
1846
1970
  let output;
@@ -1880,12 +2004,29 @@ async function executeSingleTool(ctx, call, turnId) {
1880
2004
  output = errorCtx.result ?? `Tool error: ${error.message}`;
1881
2005
  isError = true;
1882
2006
  }
1883
- const transformCtx = {
2007
+ const finalOutput = await emitToolResult(ctx, {
1884
2008
  turnId,
1885
2009
  callId,
1886
2010
  name: call.name,
1887
2011
  displayName,
1888
2012
  input: effectiveInput,
2013
+ output,
2014
+ isError,
2015
+ runToolCounts,
2016
+ ...coercions ? { coercions } : {}
2017
+ });
2018
+ return { result: { id: callId, content: finalOutput } };
2019
+ }
2020
+ async function emitToolResult(ctx, params) {
2021
+ const { turnId, callId, name, displayName, input, runToolCounts, coercions } = params;
2022
+ let output = params.output;
2023
+ let isError = params.isError;
2024
+ const transformCtx = {
2025
+ turnId,
2026
+ callId,
2027
+ name,
2028
+ displayName,
2029
+ input,
1889
2030
  result: output,
1890
2031
  isError,
1891
2032
  outputBytes: toolOutputByteLength(output),
@@ -1898,14 +2039,15 @@ async function executeSingleTool(ctx, call, turnId) {
1898
2039
  await ctx.hooks.callHook("tool:after", {
1899
2040
  turnId,
1900
2041
  callId,
1901
- name: call.name,
2042
+ name,
1902
2043
  displayName,
1903
- input: effectiveInput,
2044
+ input,
1904
2045
  result: output,
1905
2046
  outputBytes: toolOutputByteLength(output),
2047
+ runToolCounts,
1906
2048
  ...coercions ? { coercions } : {}
1907
2049
  });
1908
- return { result: { id: callId, content: output } };
2050
+ return output;
1909
2051
  }
1910
2052
  async function executeToolsSequential(ctx, toolCalls, turnId) {
1911
2053
  const results = [];
@@ -2014,6 +2156,81 @@ function buildPromptMessage(provider, parts) {
2014
2156
  return defaultPromptMessage(parts);
2015
2157
  }
2016
2158
 
2159
+ // src/tool-budgets.ts
2160
+ function installToolBudgetsGate(hooks, getToolBudgets, enqueueSteer) {
2161
+ const steeredOnce = /* @__PURE__ */ new Set();
2162
+ const approvedCounts = {};
2163
+ async function gateHandler(ctx) {
2164
+ if (ctx.block || ctx.result !== void 0)
2165
+ return;
2166
+ const toolBudgets = getToolBudgets();
2167
+ const budget = toolBudgets?.[ctx.name];
2168
+ if (!budget)
2169
+ return;
2170
+ const max = budget.max;
2171
+ if (typeof max !== "number" || max <= 0)
2172
+ return;
2173
+ const count = approvedCounts[ctx.name] ?? 0;
2174
+ if (count < max) {
2175
+ approvedCounts[ctx.name] = count + 1;
2176
+ return;
2177
+ }
2178
+ const onExceed = budget.onExceed ?? "steer";
2179
+ let mode;
2180
+ let message;
2181
+ if (typeof onExceed === "function") {
2182
+ try {
2183
+ const out = onExceed({ tool: ctx.name, count, max });
2184
+ mode = out.mode;
2185
+ message = out.message;
2186
+ } catch {
2187
+ mode = "steer";
2188
+ message = defaultSteerMessage(ctx.name, count, max);
2189
+ }
2190
+ } else if (onExceed === "block") {
2191
+ mode = "block";
2192
+ message = defaultBlockMessage(ctx.name, max);
2193
+ } else {
2194
+ mode = "steer";
2195
+ message = defaultSteerMessage(ctx.name, count, max);
2196
+ }
2197
+ if (mode === "block") {
2198
+ ctx.block = true;
2199
+ ctx.reason = message;
2200
+ await hooks.callHook("tool-budget:exceeded", {
2201
+ tool: ctx.name,
2202
+ count,
2203
+ max,
2204
+ turnId: ctx.turnId,
2205
+ mode: "block"
2206
+ });
2207
+ return;
2208
+ }
2209
+ if (!steeredOnce.has(ctx.name)) {
2210
+ steeredOnce.add(ctx.name);
2211
+ enqueueSteer(message);
2212
+ await hooks.callHook("tool-budget:exceeded", {
2213
+ tool: ctx.name,
2214
+ count,
2215
+ max,
2216
+ turnId: ctx.turnId,
2217
+ mode: "steer"
2218
+ });
2219
+ }
2220
+ }
2221
+ const unregister = hooks.hook("tool:gate", gateHandler);
2222
+ return function uninstall() {
2223
+ unregister();
2224
+ steeredOnce.clear();
2225
+ };
2226
+ }
2227
+ function defaultSteerMessage(tool, count, max) {
2228
+ return `[Tool budget reached: '${tool}' has been called ${count} times this run (cap: ${max}). Avoid calling it again unless strictly necessary; commit to a result and move on.]`;
2229
+ }
2230
+ function defaultBlockMessage(tool, max) {
2231
+ return `Tool '${tool}' has reached its per-run budget of ${max} calls; further invocations are refused.`;
2232
+ }
2233
+
2017
2234
  // src/agent.ts
2018
2235
  var HOOK_EVENT_NAMES = [
2019
2236
  "system:before",
@@ -2032,6 +2249,7 @@ var HOOK_EVENT_NAMES = [
2032
2249
  "validation:reject",
2033
2250
  "validation:coerce",
2034
2251
  "context:transform",
2252
+ "system:transform",
2035
2253
  "steer:inject",
2036
2254
  "spawn:before",
2037
2255
  "spawn:complete",
@@ -2060,6 +2278,7 @@ var HOOK_EVENT_NAMES = [
2060
2278
  "usage",
2061
2279
  "output",
2062
2280
  "budget:exceeded",
2281
+ "tool-budget:exceeded",
2063
2282
  "agent:abort",
2064
2283
  "agent:done",
2065
2284
  "session:start",
@@ -2083,7 +2302,12 @@ function resolveBehavior(agentBehavior, runBehavior) {
2083
2302
  toolOutputBudget: runBehavior?.toolOutputBudget ?? agentBehavior?.toolOutputBudget,
2084
2303
  compactStrategy: runBehavior?.compactStrategy ?? agentBehavior?.compactStrategy ?? "off",
2085
2304
  compactThreshold: runBehavior?.compactThreshold ?? agentBehavior?.compactThreshold,
2086
- compactKeepTurns: runBehavior?.compactKeepTurns ?? agentBehavior?.compactKeepTurns
2305
+ compactKeepTurns: runBehavior?.compactKeepTurns ?? agentBehavior?.compactKeepTurns,
2306
+ thinkingDecay: runBehavior?.thinkingDecay ?? agentBehavior?.thinkingDecay,
2307
+ dedupReads: runBehavior?.dedupReads ?? agentBehavior?.dedupReads,
2308
+ dedupTools: runBehavior?.dedupTools ?? agentBehavior?.dedupTools,
2309
+ requireReadBeforeEdit: runBehavior?.requireReadBeforeEdit ?? agentBehavior?.requireReadBeforeEdit,
2310
+ toolBudgets: runBehavior?.toolBudgets ?? agentBehavior?.toolBudgets
2087
2311
  };
2088
2312
  }
2089
2313
  function createAgent({ provider, name: agentName, system: agentSystem, tools: agentTools, toolAliases, behavior: agentBehavior, execution, mcpServers, session, skills: agentSkills, mcpConnector, eager }) {
@@ -2206,7 +2430,8 @@ function createAgent({ provider, name: agentName, system: agentSystem, tools: ag
2206
2430
  }
2207
2431
  const thinking = options.thinking ?? "off";
2208
2432
  const model = options.model ?? provider.meta.defaultModel;
2209
- const { toolExecution, maxTurns, maxTokens, thinkingBudget, schema, cache, toolOutputBudget, compactStrategy, compactThreshold, compactKeepTurns } = resolveBehavior(agentBehavior, options.behavior);
2433
+ const resolvedBehavior = resolveBehavior(agentBehavior, options.behavior);
2434
+ const { toolExecution, maxTurns, maxTokens, thinkingBudget, schema, cache, toolOutputBudget, compactStrategy, compactThreshold, compactKeepTurns, thinkingDecay, dedupTools, toolBudgets } = resolvedBehavior;
2210
2435
  let system = options.system || agentSystem || "You are a helpful assistant.";
2211
2436
  if (skillsCatalog) {
2212
2437
  system = `${system}
@@ -2306,6 +2531,16 @@ ${skillsCatalog}`;
2306
2531
  await hooks.callHook("session:end", { sessionId: session.id, runId, status, turnRange: [runTurnStart, turns.length - 1] });
2307
2532
  }
2308
2533
  const uninstallAllowedToolsGate = installAllowedToolsGate(hooks, skillActivationState);
2534
+ const uninstallToolBudgets = installToolBudgetsGate(
2535
+ hooks,
2536
+ () => toolBudgets,
2537
+ (msg) => steeringQueue.push(msg)
2538
+ );
2539
+ const uninstallDedupTools = installDedupToolsGate(
2540
+ hooks,
2541
+ () => dedupTools,
2542
+ () => session ?? void 0
2543
+ );
2309
2544
  const runStartMs = Date.now();
2310
2545
  const runDepth = typeof options.depth === "number" ? options.depth : 0;
2311
2546
  try {
@@ -2318,7 +2553,10 @@ ${skillsCatalog}`;
2318
2553
  agentToolAliases: toolAliases,
2319
2554
  agentMcpServers: mcpServers,
2320
2555
  agentSkills,
2321
- agentBehavior,
2556
+ // Forward the resolved view (agent + run merged) so per-run overrides
2557
+ // of `dedupReads` / `requireReadBeforeEdit` / etc. are visible to
2558
+ // tools via `ToolContext.behavior`.
2559
+ agentBehavior: resolvedBehavior,
2322
2560
  tools,
2323
2561
  formattedTools,
2324
2562
  aliasMaps,
@@ -2345,7 +2583,9 @@ ${skillsCatalog}`;
2345
2583
  compactStrategy,
2346
2584
  compactThreshold,
2347
2585
  compactKeepTurns,
2348
- runStartMs
2586
+ ...thinkingDecay !== void 0 ? { thinkingDecay } : {},
2587
+ runStartMs,
2588
+ runToolCounts: {}
2349
2589
  });
2350
2590
  const finalStats = {
2351
2591
  ...stats,
@@ -2387,6 +2627,8 @@ ${skillsCatalog}`;
2387
2627
  } finally {
2388
2628
  await deactivateAllSkills();
2389
2629
  uninstallAllowedToolsGate();
2630
+ uninstallDedupTools();
2631
+ uninstallToolBudgets();
2390
2632
  unregisterSpawnHook();
2391
2633
  unregisterSessionSync?.();
2392
2634
  for (const unregister of perRunUnregisters)
@@ -259,6 +259,32 @@ function buildMcpToolDef(config, client, tool, namespacedName, hooks) {
259
259
  if (gateCtx.block)
260
260
  return `Blocked: ${gateCtx.reason}`;
261
261
  const effectiveInput = gateCtx.input;
262
+ if (gateCtx.result !== void 0) {
263
+ let substitute = gateCtx.result;
264
+ const transformCtx = {
265
+ turnId,
266
+ callId,
267
+ server: config.name,
268
+ tool: tool.name,
269
+ displayName,
270
+ input: effectiveInput,
271
+ result: substitute,
272
+ outputBytes: toolOutputByteLength(substitute)
273
+ };
274
+ await hooks?.callHook("mcp:tool:transform", transformCtx);
275
+ substitute = transformCtx.result;
276
+ await hooks?.callHook("mcp:tool:after", {
277
+ turnId,
278
+ callId,
279
+ server: config.name,
280
+ tool: tool.name,
281
+ displayName,
282
+ input: effectiveInput,
283
+ result: substitute,
284
+ outputBytes: toolOutputByteLength(substitute)
285
+ });
286
+ return substitute;
287
+ }
262
288
  await hooks?.callHook("mcp:tool:before", {
263
289
  turnId,
264
290
  callId,
@@ -5,7 +5,7 @@ import {
5
5
  toAnthropic,
6
6
  toolResultsMessage,
7
7
  userMessage
8
- } from "./chunk-VF4A7HAC.js";
8
+ } from "./chunk-YQ7LY6CL.js";
9
9
  import {
10
10
  matchesContextExceeded
11
11
  } from "./chunk-LNN5UTS2.js";
@@ -437,6 +437,11 @@ function anthropic(anthropicParams) {
437
437
  const thinking = options.thinking ?? "off";
438
438
  const modelId = options.model;
439
439
  const params = {
440
+ // Forward-compat escape hatch for un-typed beta fields. Spread first so
441
+ // the typed core (model / max_tokens / system / tools / messages /
442
+ // stream) and the explicit `context_management` below override on
443
+ // collision — explicit always wins.
444
+ ...anthropicParams?.extraBodyParams ?? {},
440
445
  model: modelId,
441
446
  max_tokens: options.maxTokens,
442
447
  system,
@@ -6,7 +6,7 @@ import {
6
6
  shell,
7
7
  spawn,
8
8
  writeFile
9
- } from "./chunk-EBSFBIP3.js";
9
+ } from "./chunk-2AE3VM5O.js";
10
10
 
11
11
  // src/presets/basic.ts
12
12
  var basicTools = { shell, readFile, writeFile, listFiles, edit, multiEdit };
@@ -393,6 +393,9 @@ function openaiCompat(params) {
393
393
  }
394
394
  const maxTokens = options.thinkingBudget ? options.thinkingBudget + options.maxTokens : options.maxTokens;
395
395
  const body = {
396
+ // Spread first so the typed core below wins on collision — explicit
397
+ // always overrides generic.
398
+ ...params.extraBodyParams ?? {},
396
399
  model: modelId,
397
400
  messages,
398
401
  max_tokens: maxTokens,
package/dist/index.d.ts CHANGED
@@ -1,12 +1,12 @@
1
- import { d as AgentHooks } from './agent-Cq009tbG.js';
2
- export { ac as ActivationVia, ad as ActiveSkill, A as Agent, a as AgentAbortedError, b as AgentBehavior, c as AgentContextExceededError, e as AgentOptions, f as AgentProviderError, g as AgentRunOptions, h as AgentStats, i as AgentToolNotAllowedError, j as AnthropicParams, C as CONTEXT_EXCEEDED_MESSAGE_PATTERNS, k as CerebrasParams, m as ClassifiedError, n as ClassifiedErrorKind, o as CreateSessionOptions, ae as DeactivationReason, af as FileMapAdapter, ag as FileMapStoreOptions, I as ImageContent, M as McpConnection, p as McpServerConfig, q as McpToolHookContext, O as OAuthRefreshHookContext, ah as OpenAICompatAuthHeader, ai as OpenAICompatHttpError, aj as OpenAICompatParams, r as OpenAIParams, s as OpenRouterParams, P as PromptDocumentPart, t as PromptImagePart, u as PromptPart, v as PromptTextPart, w as Provider, x as ProviderCapabilities, R as RemoteStoreOptions, y as RunHookMap, S as Session, z as SessionContentBlock, B as SessionData, D as SessionEndStatus, E as SessionHookContext, F as SessionMessage, G as SessionRun, H as SessionStore, J as SessionTurn, ak as SkillActivationState, al as SkillActivationStateOptions, K as SkillConfig, am as SkillDiagnostic, L as SkillResource, an as SkillSource, N as SkillsConfig, Q as SpawnHookContext, T as StreamCallbacks, U as StreamHookContext, V as StreamOptions, W as ThinkingLevel, X as ToolCall, Y as ToolContext, Z as ToolDef, _ as ToolExecutionMode, $ as ToolHookContext, a0 as ToolMap, a1 as ToolResult, a2 as ToolResultContent, a3 as ToolResultImageContent, a4 as ToolResultTextContent, a5 as ToolSpec, a6 as TurnFinishReason, a7 as TurnResult, a8 as TurnUsage, ao as anthropic, ap as autoDetectAndConvert, aq as cerebras, ar as classifyOpenAICompatError, as as connectMcpServers, at as createAgent, au as createFileMapStore, av as createMemoryStore, aw as createRemoteStore, ax as createSession, ay as createSkillActivationState, az as fromAnthropic, aA as fromOpenAI, aB as loadSession, aC as mapOAIFinishReason, a9 as matchesContextExceeded, aD as normalizeMcpBlocks, aE as normalizeMcpServers, aF as openai, aG as openaiCompat, aH as openrouter, aI as resultToString, aJ as toAnthropic, aK as toOpenAI, aL as toTypedError, aa as toolOutputByteLength, ab as toolResultToText } from './agent-Cq009tbG.js';
1
+ import { d as AgentHooks } from './agent-C9q5VMGa.js';
2
+ export { ac as ActivationVia, ad as ActiveSkill, A as Agent, a as AgentAbortedError, b as AgentBehavior, c as AgentContextExceededError, e as AgentOptions, f as AgentProviderError, g as AgentRunOptions, h as AgentStats, i as AgentToolNotAllowedError, j as AnthropicParams, C as CONTEXT_EXCEEDED_MESSAGE_PATTERNS, k as CerebrasParams, m as ClassifiedError, n as ClassifiedErrorKind, o as CreateSessionOptions, ae as DeactivationReason, af as FileMapAdapter, ag as FileMapStoreOptions, I as ImageContent, M as McpConnection, p as McpServerConfig, q as McpToolHookContext, O as OAuthRefreshHookContext, ah as OpenAICompatAuthHeader, ai as OpenAICompatHttpError, aj as OpenAICompatParams, r as OpenAIParams, s as OpenRouterParams, P as PromptDocumentPart, t as PromptImagePart, u as PromptPart, v as PromptTextPart, w as Provider, x as ProviderCapabilities, R as RemoteStoreOptions, y as RunHookMap, S as Session, z as SessionContentBlock, B as SessionData, D as SessionEndStatus, E as SessionHookContext, F as SessionMessage, G as SessionRun, H as SessionStore, J as SessionTurn, ak as SkillActivationState, al as SkillActivationStateOptions, K as SkillConfig, am as SkillDiagnostic, L as SkillResource, an as SkillSource, N as SkillsConfig, Q as SpawnHookContext, T as StreamCallbacks, U as StreamHookContext, V as StreamOptions, W as ThinkingLevel, X as ToolCall, Y as ToolContext, Z as ToolDef, _ as ToolExecutionMode, $ as ToolHookContext, a0 as ToolMap, a1 as ToolResult, a2 as ToolResultContent, a3 as ToolResultImageContent, a4 as ToolResultTextContent, a5 as ToolSpec, a6 as TurnFinishReason, a7 as TurnResult, a8 as TurnUsage, ao as anthropic, ap as autoDetectAndConvert, aq as cerebras, ar as classifyOpenAICompatError, as as connectMcpServers, at as createAgent, au as createFileMapStore, av as createMemoryStore, aw as createRemoteStore, ax as createSession, ay as createSkillActivationState, az as fromAnthropic, aA as fromOpenAI, aB as loadSession, aC as mapOAIFinishReason, a9 as matchesContextExceeded, aD as normalizeMcpBlocks, aE as normalizeMcpServers, aF as openai, aG as openaiCompat, aH as openrouter, aI as resultToString, aJ as toAnthropic, aK as toOpenAI, aL as toTypedError, aa as toolOutputByteLength, ab as toolResultToText } from './agent-C9q5VMGa.js';
3
3
  export { createDockerContext, createProcessContext } from './contexts.js';
4
4
  export { S as SandboxProvider, c as createSandboxContext } from './sandbox-CLghrTLi.js';
5
5
  export { C as ContextCapabilities, a as ContextType, E as ExecResult, b as ExecutionContext, c as ExecutionHandle, S as SpawnConfig } from './types-vA1a_ZX7.js';
6
6
  export { Preset, basic, basicTools, definePreset } from './presets.js';
7
7
  export { IMPLICITLY_ALLOWED_SKILL_TOOLS, SkillValidationIssue, SkillValidationResult, SourcedScanPath, buildCatalog, defineSkill, discoverSkills, installAllowedToolsGate, interpolateShellCommands, isToolAllowedByUnion, matchesAllowedTool, parseAllowedToolPattern, parseSkillFile, resolveSkills, validateResourcePath, validateSkillForWrite, validateSkillName, writeSkillToDisk, writeSkillsToDisk } from './skills.js';
8
- export { S as SkillsReadToolOptions, a as SkillsRunScriptToolOptions, b as SkillsUseToolOptions, c as createSkillsReadTool, d as createSkillsRunScriptTool, e as createSkillsUseTool, f as edit, g as glob, h as grep, m as multiEdit } from './skills-use-Bi6Dklye.js';
9
- export { C as ChildAgent, I as InteractionToolOptions, S as SpawnToolOptions, a as SpawnToolState, V as ValidationResult, c as createInteractionTool, b as createSpawnTool, s as spawn, v as validateToolArgs } from './validation-BeQD94ft.js';
8
+ export { S as SkillsReadToolOptions, a as SkillsRunScriptToolOptions, b as SkillsUseToolOptions, c as createSkillsReadTool, d as createSkillsRunScriptTool, e as createSkillsUseTool, f as edit, g as glob, h as grep, m as multiEdit } from './skills-use-DU0unNP4.js';
9
+ export { C as ChildAgent, I as InteractionToolOptions, S as SpawnToolOptions, a as SpawnToolState, V as ValidationResult, c as createInteractionTool, b as createSpawnTool, s as spawn, v as validateToolArgs } from './validation-BKA33eqb.js';
10
10
  import { Hookable } from 'hookable';
11
11
  import '@modelcontextprotocol/sdk/client/index.js';
12
12
 
package/dist/index.js CHANGED
@@ -6,12 +6,12 @@ import {
6
6
  cerebras,
7
7
  openai,
8
8
  openrouter
9
- } from "./chunk-3DUWP7YU.js";
9
+ } from "./chunk-BRMURQA2.js";
10
10
  import {
11
11
  basicTools,
12
12
  basic_default,
13
13
  definePreset
14
- } from "./chunk-ATMVSCGJ.js";
14
+ } from "./chunk-BXO7CZHJ.js";
15
15
  import {
16
16
  createAgent,
17
17
  createInteractionTool,
@@ -25,7 +25,7 @@ import {
25
25
  multiEdit,
26
26
  spawn,
27
27
  validateToolArgs
28
- } from "./chunk-EBSFBIP3.js";
28
+ } from "./chunk-2AE3VM5O.js";
29
29
  import {
30
30
  IMPLICITLY_ALLOWED_SKILL_TOOLS,
31
31
  buildCatalog,
@@ -54,7 +54,7 @@ import {
54
54
  normalizeMcpBlocks,
55
55
  normalizeMcpServers,
56
56
  resultToString
57
- } from "./chunk-R74LQKAM.js";
57
+ } from "./chunk-7H34OFDA.js";
58
58
  import {
59
59
  toolOutputByteLength,
60
60
  toolResultToText
@@ -76,7 +76,7 @@ import {
76
76
  openaiCompat,
77
77
  toAnthropic,
78
78
  toOpenAI
79
- } from "./chunk-VF4A7HAC.js";
79
+ } from "./chunk-YQ7LY6CL.js";
80
80
  import {
81
81
  AgentAbortedError,
82
82
  AgentContextExceededError,
package/dist/mcp.d.ts CHANGED
@@ -1,4 +1,4 @@
1
1
  import 'hookable';
2
- export { M as McpConnection, p as McpServerConfig, as as connectMcpServers, aD as normalizeMcpBlocks, aE as normalizeMcpServers, aI as resultToString } from './agent-Cq009tbG.js';
2
+ export { M as McpConnection, p as McpServerConfig, as as connectMcpServers, aD as normalizeMcpBlocks, aE as normalizeMcpServers, aI as resultToString } from './agent-C9q5VMGa.js';
3
3
  import '@modelcontextprotocol/sdk/client/index.js';
4
4
  import './types-vA1a_ZX7.js';
package/dist/mcp.js CHANGED
@@ -3,7 +3,7 @@ import {
3
3
  normalizeMcpBlocks,
4
4
  normalizeMcpServers,
5
5
  resultToString
6
- } from "./chunk-R74LQKAM.js";
6
+ } from "./chunk-7H34OFDA.js";
7
7
  import "./chunk-JH6IAAFA.js";
8
8
  export {
9
9
  connectMcpServers,
package/dist/presets.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { Z as ToolDef, e as AgentOptions } from './agent-Cq009tbG.js';
1
+ import { Z as ToolDef, e as AgentOptions } from './agent-C9q5VMGa.js';
2
2
  import 'hookable';
3
3
  import './types-vA1a_ZX7.js';
4
4
  import '@modelcontextprotocol/sdk/client/index.js';
package/dist/presets.js CHANGED
@@ -2,11 +2,11 @@ import {
2
2
  basicTools,
3
3
  basic_default,
4
4
  definePreset
5
- } from "./chunk-ATMVSCGJ.js";
6
- import "./chunk-EBSFBIP3.js";
5
+ } from "./chunk-BXO7CZHJ.js";
6
+ import "./chunk-2AE3VM5O.js";
7
7
  import "./chunk-TPXPVEH6.js";
8
8
  import "./chunk-IUBBVF53.js";
9
- import "./chunk-R74LQKAM.js";
9
+ import "./chunk-7H34OFDA.js";
10
10
  import "./chunk-JH6IAAFA.js";
11
11
  import "./chunk-LNN5UTS2.js";
12
12
  export {
@@ -1,4 +1,4 @@
1
- export { j as AnthropicParams, k as CerebrasParams, ah as OpenAICompatAuthHeader, ai as OpenAICompatHttpError, aj as OpenAICompatParams, r as OpenAIParams, s as OpenRouterParams, w as Provider, x as ProviderCapabilities, T as StreamCallbacks, V as StreamOptions, X as ToolCall, a1 as ToolResult, a5 as ToolSpec, a7 as TurnResult, ao as anthropic, aq as cerebras, ar as classifyOpenAICompatError, aC as mapOAIFinishReason, aF as openai, aG as openaiCompat, aH as openrouter } from './agent-Cq009tbG.js';
1
+ export { j as AnthropicParams, k as CerebrasParams, ah as OpenAICompatAuthHeader, ai as OpenAICompatHttpError, aj as OpenAICompatParams, r as OpenAIParams, s as OpenRouterParams, w as Provider, x as ProviderCapabilities, T as StreamCallbacks, V as StreamOptions, X as ToolCall, a1 as ToolResult, a5 as ToolSpec, a7 as TurnResult, ao as anthropic, aq as cerebras, ar as classifyOpenAICompatError, aC as mapOAIFinishReason, aF as openai, aG as openaiCompat, aH as openrouter } from './agent-C9q5VMGa.js';
2
2
  import 'hookable';
3
3
  import './types-vA1a_ZX7.js';
4
4
  import '@modelcontextprotocol/sdk/client/index.js';
package/dist/providers.js CHANGED
@@ -3,13 +3,13 @@ import {
3
3
  cerebras,
4
4
  openai,
5
5
  openrouter
6
- } from "./chunk-3DUWP7YU.js";
6
+ } from "./chunk-BRMURQA2.js";
7
7
  import {
8
8
  OpenAICompatHttpError,
9
9
  classifyOpenAICompatError,
10
10
  mapOAIFinishReason,
11
11
  openaiCompat
12
- } from "./chunk-VF4A7HAC.js";
12
+ } from "./chunk-YQ7LY6CL.js";
13
13
  import "./chunk-LNN5UTS2.js";
14
14
  export {
15
15
  OpenAICompatHttpError,
@@ -1,4 +1,4 @@
1
- import { H as SessionStore } from '../agent-Cq009tbG.js';
1
+ import { H as SessionStore } from '../agent-C9q5VMGa.js';
2
2
  import 'hookable';
3
3
  import '../types-vA1a_ZX7.js';
4
4
  import '@modelcontextprotocol/sdk/client/index.js';
package/dist/session.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export { o as CreateSessionOptions, af as FileMapAdapter, ag as FileMapStoreOptions, R as RemoteStoreOptions, S as Session, z as SessionContentBlock, B as SessionData, F as SessionMessage, G as SessionRun, H as SessionStore, J as SessionTurn, ap as autoDetectAndConvert, au as createFileMapStore, av as createMemoryStore, aw as createRemoteStore, ax as createSession, az as fromAnthropic, aA as fromOpenAI, aB as loadSession, aJ as toAnthropic, aK as toOpenAI } from './agent-Cq009tbG.js';
1
+ export { o as CreateSessionOptions, af as FileMapAdapter, ag as FileMapStoreOptions, R as RemoteStoreOptions, S as Session, z as SessionContentBlock, B as SessionData, F as SessionMessage, G as SessionRun, H as SessionStore, J as SessionTurn, ap as autoDetectAndConvert, au as createFileMapStore, av as createMemoryStore, aw as createRemoteStore, ax as createSession, az as fromAnthropic, aA as fromOpenAI, aB as loadSession, aJ as toAnthropic, aK as toOpenAI } from './agent-C9q5VMGa.js';
2
2
  import 'hookable';
3
3
  import './types-vA1a_ZX7.js';
4
4
  import '@modelcontextprotocol/sdk/client/index.js';
package/dist/session.js CHANGED
@@ -11,7 +11,7 @@ import {
11
11
  fromOpenAI,
12
12
  toAnthropic,
13
13
  toOpenAI
14
- } from "./chunk-VF4A7HAC.js";
14
+ } from "./chunk-YQ7LY6CL.js";
15
15
  import "./chunk-LNN5UTS2.js";
16
16
  export {
17
17
  autoDetectAndConvert,
@@ -1,4 +1,4 @@
1
- import { Z as ToolDef, K as SkillConfig, ak as SkillActivationState, d as AgentHooks } from './agent-Cq009tbG.js';
1
+ import { Z as ToolDef, K as SkillConfig, ak as SkillActivationState, d as AgentHooks } from './agent-C9q5VMGa.js';
2
2
  import { Hookable } from 'hookable';
3
3
 
4
4
  /**
package/dist/skills.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { d as AgentHooks, ak as SkillActivationState, K as SkillConfig, an as SkillSource, am as SkillDiagnostic, N as SkillsConfig } from './agent-Cq009tbG.js';
2
- export { ac as ActivationVia, ad as ActiveSkill, ae as DeactivationReason, al as SkillActivationStateOptions, L as SkillResource, ay as createSkillActivationState } from './agent-Cq009tbG.js';
1
+ import { d as AgentHooks, ak as SkillActivationState, K as SkillConfig, an as SkillSource, am as SkillDiagnostic, N as SkillsConfig } from './agent-C9q5VMGa.js';
2
+ export { ac as ActivationVia, ad as ActiveSkill, ae as DeactivationReason, al as SkillActivationStateOptions, L as SkillResource, ay as createSkillActivationState } from './agent-C9q5VMGa.js';
3
3
  import { Hookable } from 'hookable';
4
4
  import { b as ExecutionContext, c as ExecutionHandle } from './types-vA1a_ZX7.js';
5
5
  import '@modelcontextprotocol/sdk/client/index.js';
package/dist/tools.d.ts CHANGED
@@ -1,7 +1,7 @@
1
- export { S as SkillsReadToolOptions, a as SkillsRunScriptToolOptions, b as SkillsUseToolOptions, c as createSkillsReadTool, d as createSkillsRunScriptTool, e as createSkillsUseTool, f as edit, g as glob, h as grep, m as multiEdit } from './skills-use-Bi6Dklye.js';
2
- export { C as ChildAgent, I as InteractionToolOptions, S as SpawnToolOptions, a as SpawnToolState, V as ValidationResult, c as createInteractionTool, b as createSpawnTool, s as spawn, v as validateToolArgs } from './validation-BeQD94ft.js';
3
- import { Z as ToolDef } from './agent-Cq009tbG.js';
4
- export { Y as ToolContext, a0 as ToolMap } from './agent-Cq009tbG.js';
1
+ export { S as SkillsReadToolOptions, a as SkillsRunScriptToolOptions, b as SkillsUseToolOptions, c as createSkillsReadTool, d as createSkillsRunScriptTool, e as createSkillsUseTool, f as edit, g as glob, h as grep, m as multiEdit } from './skills-use-DU0unNP4.js';
2
+ export { C as ChildAgent, I as InteractionToolOptions, S as SpawnToolOptions, a as SpawnToolState, V as ValidationResult, c as createInteractionTool, b as createSpawnTool, s as spawn, v as validateToolArgs } from './validation-BKA33eqb.js';
3
+ import { Z as ToolDef } from './agent-C9q5VMGa.js';
4
+ export { Y as ToolContext, a0 as ToolMap } from './agent-C9q5VMGa.js';
5
5
  import 'hookable';
6
6
  import './presets.js';
7
7
  import './types-vA1a_ZX7.js';
package/dist/tools.js CHANGED
@@ -14,10 +14,10 @@ import {
14
14
  spawn,
15
15
  validateToolArgs,
16
16
  writeFile
17
- } from "./chunk-EBSFBIP3.js";
17
+ } from "./chunk-2AE3VM5O.js";
18
18
  import "./chunk-TPXPVEH6.js";
19
19
  import "./chunk-IUBBVF53.js";
20
- import "./chunk-R74LQKAM.js";
20
+ import "./chunk-7H34OFDA.js";
21
21
  import "./chunk-JH6IAAFA.js";
22
22
  import "./chunk-LNN5UTS2.js";
23
23
  export {
package/dist/types.d.ts CHANGED
@@ -1,7 +1,7 @@
1
- export { A as Agent, a as AgentAbortedError, b as AgentBehavior, c as AgentContextExceededError, d as AgentHooks, e as AgentOptions, f as AgentProviderError, g as AgentRunOptions, h as AgentStats, i as AgentToolNotAllowedError, j as AnthropicParams, C as CONTEXT_EXCEEDED_MESSAGE_PATTERNS, k as CerebrasParams, l as ChildRunStats, m as ClassifiedError, n as ClassifiedErrorKind, o as CreateSessionOptions, I as ImageContent, M as McpConnection, p as McpServerConfig, q as McpToolHookContext, O as OAuthRefreshHookContext, r as OpenAIParams, s as OpenRouterParams, P as PromptDocumentPart, t as PromptImagePart, u as PromptPart, v as PromptTextPart, w as Provider, x as ProviderCapabilities, R as RemoteStoreOptions, y as RunHookMap, S as Session, z as SessionContentBlock, B as SessionData, D as SessionEndStatus, E as SessionHookContext, F as SessionMessage, G as SessionRun, H as SessionStore, J as SessionTurn, K as SkillConfig, L as SkillResource, N as SkillsConfig, Q as SpawnHookContext, T as StreamCallbacks, U as StreamHookContext, V as StreamOptions, W as ThinkingLevel, X as ToolCall, Y as ToolContext, Z as ToolDef, _ as ToolExecutionMode, $ as ToolHookContext, a0 as ToolMap, a1 as ToolResult, a2 as ToolResultContent, a3 as ToolResultImageContent, a4 as ToolResultTextContent, a5 as ToolSpec, a6 as TurnFinishReason, a7 as TurnResult, a8 as TurnUsage, a9 as matchesContextExceeded, aa as toolOutputByteLength, ab as toolResultToText } from './agent-Cq009tbG.js';
1
+ export { A as Agent, a as AgentAbortedError, b as AgentBehavior, c as AgentContextExceededError, d as AgentHooks, e as AgentOptions, f as AgentProviderError, g as AgentRunOptions, h as AgentStats, i as AgentToolNotAllowedError, j as AnthropicParams, C as CONTEXT_EXCEEDED_MESSAGE_PATTERNS, k as CerebrasParams, l as ChildRunStats, m as ClassifiedError, n as ClassifiedErrorKind, o as CreateSessionOptions, I as ImageContent, M as McpConnection, p as McpServerConfig, q as McpToolHookContext, O as OAuthRefreshHookContext, r as OpenAIParams, s as OpenRouterParams, P as PromptDocumentPart, t as PromptImagePart, u as PromptPart, v as PromptTextPart, w as Provider, x as ProviderCapabilities, R as RemoteStoreOptions, y as RunHookMap, S as Session, z as SessionContentBlock, B as SessionData, D as SessionEndStatus, E as SessionHookContext, F as SessionMessage, G as SessionRun, H as SessionStore, J as SessionTurn, K as SkillConfig, L as SkillResource, N as SkillsConfig, Q as SpawnHookContext, T as StreamCallbacks, U as StreamHookContext, V as StreamOptions, W as ThinkingLevel, X as ToolCall, Y as ToolContext, Z as ToolDef, _ as ToolExecutionMode, $ as ToolHookContext, a0 as ToolMap, a1 as ToolResult, a2 as ToolResultContent, a3 as ToolResultImageContent, a4 as ToolResultTextContent, a5 as ToolSpec, a6 as TurnFinishReason, a7 as TurnResult, a8 as TurnUsage, a9 as matchesContextExceeded, aa as toolOutputByteLength, ab as toolResultToText } from './agent-C9q5VMGa.js';
2
2
  export { C as ContextCapabilities, a as ContextType, E as ExecResult, b as ExecutionContext, c as ExecutionHandle, S as SpawnConfig } from './types-vA1a_ZX7.js';
3
3
  export { S as SandboxProvider } from './sandbox-CLghrTLi.js';
4
4
  export { Preset } from './presets.js';
5
- export { C as ChildAgent, I as InteractionToolOptions, S as SpawnToolOptions, a as SpawnToolState, V as ValidationResult } from './validation-BeQD94ft.js';
5
+ export { C as ChildAgent, I as InteractionToolOptions, S as SpawnToolOptions, a as SpawnToolState, V as ValidationResult } from './validation-BKA33eqb.js';
6
6
  import 'hookable';
7
7
  import '@modelcontextprotocol/sdk/client/index.js';
@@ -1,4 +1,4 @@
1
- import { Y as ToolContext, Z as ToolDef, h as AgentStats, l as ChildRunStats } from './agent-Cq009tbG.js';
1
+ import { Y as ToolContext, Z as ToolDef, h as AgentStats, l as ChildRunStats } from './agent-C9q5VMGa.js';
2
2
  import { Preset } from './presets.js';
3
3
 
4
4
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zidane",
3
- "version": "3.1.1",
3
+ "version": "3.1.2",
4
4
  "description": "an agent that goes straight to the goal",
5
5
  "type": "module",
6
6
  "private": false,