zidane 2.2.3 → 3.0.1

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
@@ -14,7 +14,11 @@ Built to be embedded.
14
14
 
15
15
  - 🧠 **Multi-provider** — Anthropic, OpenAI Codex, OpenRouter, Cerebras, plus a generic `openaiCompat` factory (Baseten, Fireworks, Groq, local servers). OAuth + API-key auth, auto-refreshing tokens.
16
16
  - 🔁 **Streaming turn loop** — stream text + thinking deltas, tool calls, and tool results with hookable events at every step.
17
- - 🛠 **Tools first-class** — shell, file IO, glob, spawn, human-in-the-loop, plus any [MCP](https://modelcontextprotocol.io) server. Sequential or parallel execution, per-call gates, typed hooks.
17
+ - 🛠 **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 execution, per-call gates, typed hooks. Built-in tools ship with sensible truncation and idempotency defaults so consumers don't have to polyfill them.
18
+ - ✂️ **Token-aware tool ergonomics** — `read_file` line-paginates with a footer that documents how to read the rest, `shell` tail-truncates combined output at 8 KB, `write_file` returns `No change needed` on idempotent writes. `outputBytes` surfaced on every tool/mcp hook.
19
+ - 🧰 **Self-healing tool args** — `validateToolArgs` auto-coerces small/OSS-model mistakes (`"true"` → `true`, `"42"` → `42`, JSON-encoded arrays) before reaching `execute`. `validation:reject` fires only on irrecoverable mismatches.
20
+ - 🪤 **Hallucinated tool names** handled — `tool:unknown` fires before the default error so consumers can substitute a friendly response.
21
+ - 📉 **Per-turn output budget** — `behavior.toolOutputBudget` injects a "summarize before continuing" message when a turn's tool outputs exceed the cap. Off by default.
18
22
  - 🧩 **[Agent Skills](https://agentskills.io/specification) spec-aligned** — discover, activate, and run skills with `allowed-tools` enforcement and session-resume rehydration.
19
23
  - 💾 **Pluggable sessions** — in-memory, SQLite, remote HTTP, or a file-map adapter. Turns persist incrementally — a crash leaves valid partial history.
20
24
  - 🖼 **Multimodal** — images + documents via `PromptPart[]`; tools can return image blocks (screenshots, diagrams) routed natively on vision providers and via companion messages elsewhere.
@@ -25,8 +29,10 @@ Built to be embedded.
25
29
  - 🧵 **Sub-agent spawning** — delegate to child agents with inherited or overridden preset; child stream/tool events bubble to the parent.
26
30
  - 🧭 **Typed errors** — `AgentContextExceededError` / `AgentProviderError` / `AgentAbortedError` instead of sniffing error strings.
27
31
  - 🔌 **Execution contexts** — run tools in-process, in Docker, or in a remote sandbox (E2B / Rivet / any `SandboxProvider`).
28
- - 🪝 **Hookable everything** — ~40 typed hook events covering turn, stream, tool, MCP, session, skills, spawn, OAuth refresh, and bootstrap timing.
29
- - 🧪 **915+ tests, zero API keys** — mock providers + mock execution contexts; suite runs in under 2 seconds.
32
+ - 🪝 **Hookable everything** — typed hook events covering turn, stream, tool, MCP, session, skills, spawn, OAuth refresh, bootstrap timing, validation rejection / coercion, and budget overflow.
33
+ - 🧪 **1000+ tests, zero API keys** — mock providers + mock execution contexts; suite runs in under 2 seconds.
34
+
35
+ > Upgrading from 2.x? See [`docs/migrate-from-v2.md`](./docs/migrate-from-v2.md) for the full list of behavior changes.
30
36
 
31
37
  ## Quickstart
32
38
 
@@ -70,6 +76,7 @@ createAgent({
70
76
  maxTokens: 16384, // max tokens per LLM response
71
77
  thinkingBudget: 10240, // exact thinking token budget
72
78
  cache: true, // prompt-cache breakpoints on supported providers (default: true)
79
+ toolOutputBudget: 32768, // soft per-turn cap on tool-output bytes (off by default)
73
80
  },
74
81
  execution: createProcessContext(), // where tools run
75
82
  mcpServers: [], // MCP tool servers
@@ -213,13 +220,23 @@ The `basic` preset bundles:
213
220
 
214
221
  | Tool | Description |
215
222
  |---|---|
216
- | `shell` | Execute shell commands |
217
- | `readFile` | Read file contents |
218
- | `writeFile` | Write/create files |
223
+ | `shell` | Execute shell commands. Combined stdout+stderr tail-truncated at 8 KB by default; `maxOutputBytes: 0` disables |
224
+ | `readFile` | Read a file by line range. Default: lines 1..2000, byte cap 64 KB. Truncation footer documents how to page; binary files return a marker instead of mojibake |
225
+ | `writeFile` | Write a file. Returns `Created` / `Updated` / `No change needed: …` so the model can detect no-ops without a separate read |
226
+ | `edit` | Surgical replace of `old_string` → `new_string`. Fails clearly on non-unique matches (unless `replace_all`) and on not-found (with a nearest-match preview) |
227
+ | `multiEdit` | Atomic list of edits to one file. All-or-nothing: any failed edit prevents the write |
219
228
  | `listFiles` | List directory contents |
220
229
  | `spawn` | Spawn a sub-agent |
221
230
 
222
- Extra tools live alongside: `glob` (pattern-based file matching), `createInteractionTool` (human-in-the-loop factory), and the three `skills_use` / `skills_read` / `skills_run_script` tools that auto-inject when the skills catalog is non-empty.
231
+ Opt-in tools available via `import { glob, grep, createInteractionTool } from 'zidane'`:
232
+
233
+ | Tool | Description |
234
+ |---|---|
235
+ | `glob` | Bun.Glob-backed pattern matching (in-process); shells out in docker/sandbox |
236
+ | `grep` | ripgrep-backed regex search (with a Bun.Glob fallback). `output_mode`, `-i / -n / -A / -B / -C`, `multiline`, `head_limit`, `offset` — Claude Code Grep semantics |
237
+ | `createInteractionTool` | Human-in-the-loop factory |
238
+
239
+ The three `skills_use` / `skills_read` / `skills_run_script` tools auto-inject when the skills catalog is non-empty.
223
240
 
224
241
  Define a custom preset:
225
242
 
@@ -333,26 +350,51 @@ agent.hooks.hook('tool:gate', (ctx) => {
333
350
  }
334
351
  })
335
352
 
336
- agent.hooks.hook('tool:before', (ctx) => { /* ctx.turnId, ctx.callId, ctx.name, ctx.input */ })
337
- agent.hooks.hook('tool:after', (ctx) => { /* + ctx.result */ })
353
+ agent.hooks.hook('tool:before', (ctx) => { /* ctx.turnId, ctx.callId, ctx.name, ctx.input, ctx.coercions? */ })
354
+ agent.hooks.hook('tool:after', (ctx) => { /* + ctx.result, ctx.outputBytes, ctx.coercions? */ })
338
355
  agent.hooks.hook('tool:error', (ctx) => { /* + ctx.error */ })
339
356
  agent.hooks.hook('tool:transform', (ctx) => {
340
- // + ctx.result, ctx.isError — mutate to modify output
341
- if (ctx.result.length > 5000)
342
- ctx.result = ctx.result.slice(0, 5000) + '\n... (truncated)'
357
+ // + ctx.result, ctx.isError, ctx.outputBytes (pre-mutation), ctx.coercions? — mutate result/isError to modify.
358
+ // Built-in tools already truncate; use this hook for consumer concerns the framework can't infer,
359
+ // e.g. redacting secrets in tool output before they reach the model.
360
+ if (typeof ctx.result === 'string')
361
+ ctx.result = ctx.result.replace(/\b(API_KEY|TOKEN|PASSWORD)\s*=\s*\S+/gi, '$1=<redacted>')
362
+ })
363
+ agent.hooks.hook('tool:unknown', (ctx) => {
364
+ // Fires when the model invents a tool name (or calls one no longer registered).
365
+ // Mutate ctx.result to substitute a friendly response, set ctx.suppressError = true
366
+ // to skip the companion `tool:error`.
367
+ if (ctx.name === 'EnterPlanMode') {
368
+ ctx.result = 'EnterPlanMode is not available — use shell to draft a plan as comments.'
369
+ ctx.suppressError = true
370
+ }
371
+ })
372
+ agent.hooks.hook('validation:reject', (ctx) => {
373
+ // Fires when arg validation rejects the input even after auto-coercion attempts.
374
+ // Observational — the model still receives `Validation error: …` for the retry.
375
+ // ctx.reason, ctx.schema
376
+ })
377
+ agent.hooks.hook('validation:coerce', (ctx) => {
378
+ // Fires when validation auto-healed at least one field. Never fires on
379
+ // perfectly-typed inputs. ctx.coercions lists the field names that were changed.
380
+ // Symmetric counterpart to `validation:reject` — useful for "model wrongness rate".
343
381
  })
344
382
  ```
345
383
 
384
+ `ctx.coercions` (when present) is the same `readonly string[]` exposed via `validation:coerce`. The field is **omitted** from `tool:before` / `tool:after` / `tool:transform` ctx when no coercion happened, so it never noises up the happy path. Listeners can `if (ctx.coercions)` guard.
385
+
346
386
  MCP tool hooks mirror the same pattern with `server` and `tool` fields. Typed via `McpToolHookContext`.
347
387
 
348
388
  ```ts
349
389
  agent.hooks.hook('mcp:tool:gate', (ctx) => { /* ctx.turnId, ctx.callId, ctx.server, ctx.tool, ctx.input, ctx.block, ctx.reason */ })
350
390
  agent.hooks.hook('mcp:tool:before', (ctx) => { /* ctx.turnId, ctx.callId, ctx.server, ctx.tool, ctx.input */ })
351
- agent.hooks.hook('mcp:tool:after', (ctx) => { /* + ctx.result */ })
352
- agent.hooks.hook('mcp:tool:transform', (ctx) => { /* + ctx.result — mutate to modify */ })
391
+ agent.hooks.hook('mcp:tool:after', (ctx) => { /* + ctx.result, ctx.outputBytes */ })
392
+ agent.hooks.hook('mcp:tool:transform', (ctx) => { /* + ctx.result, ctx.outputBytes — mutate to modify */ })
353
393
  agent.hooks.hook('mcp:tool:error', (ctx) => { /* + ctx.error */ })
354
394
  ```
355
395
 
396
+ `outputBytes` measures the wire size of the tool's result. On `*:transform` it's the **pre-mutation** size (a truncation handler can size-budget); on `*:after` it's the **post-mutation** size that goes to the model. `toolOutputByteLength(content)` exported from `zidane` reproduces the formula.
397
+
356
398
  ### Context transform
357
399
 
358
400
  Prune messages before each LLM call:
@@ -364,6 +406,58 @@ agent.hooks.hook('context:transform', (ctx) => {
364
406
  })
365
407
  ```
366
408
 
409
+ ### Hook recipes
410
+
411
+ Three patterns that don't have a built-in default. Copy-paste and tune.
412
+
413
+ ```ts
414
+ // 1. Truncate MCP tool results.
415
+ // Built-in tools (shell, read_file) already tail-truncate; MCP server outputs
416
+ // don't, since their sizes vary wildly and zidane can't pick a sane default
417
+ // on their behalf. Apply the same shape to mcp:tool:transform.
418
+ agent.hooks.hook('mcp:tool:transform', (ctx) => {
419
+ if (ctx.outputBytes <= 8192 || typeof ctx.result !== 'string')
420
+ return
421
+ const tail = ctx.result.slice(-4096)
422
+ ctx.result = `…(${ctx.outputBytes - tail.length} bytes truncated from head)…\n${tail}`
423
+ })
424
+
425
+ // 2. Substitute a friendly response when the model invents a tool name.
426
+ agent.hooks.hook('tool:unknown', (ctx) => {
427
+ if (ctx.name === 'EnterPlanMode') {
428
+ ctx.result = 'EnterPlanMode is not available — use shell to draft a plan as comments.'
429
+ ctx.suppressError = true
430
+ }
431
+ })
432
+
433
+ // 3. Drop old turns once the conversation grows past a soft cap.
434
+ agent.hooks.hook('context:transform', (ctx) => {
435
+ const KEEP_RECENT = 30
436
+ if (ctx.messages.length > KEEP_RECENT) {
437
+ const trimmed = [ctx.messages[0], ...ctx.messages.slice(-KEEP_RECENT + 1)]
438
+ ctx.messages.splice(0, ctx.messages.length, ...trimmed)
439
+ }
440
+ })
441
+ ```
442
+
443
+ `mcp:tool:transform`, `tool:unknown`, and `context:transform` are the highest-leverage entries on the surface for the cases v3 doesn't auto-handle. Most production agents end up with one of each.
444
+
445
+ ### Per-turn output budget
446
+
447
+ When working with OSS models that return large tool outputs, set `behavior.toolOutputBudget` to inject a "summarize before continuing" message after any turn whose combined post-`tool:transform` tool-output bytes exceed the cap. Off by default.
448
+
449
+ ```ts
450
+ const agent = createAgent({
451
+ ...basic,
452
+ provider,
453
+ behavior: { toolOutputBudget: 32768 },
454
+ })
455
+
456
+ agent.hooks.hook('budget:exceeded', (ctx) => {
457
+ console.warn(`turn ${ctx.turn}: ${ctx.bytes} > ${ctx.budget} bytes`)
458
+ })
459
+ ```
460
+
367
461
  ## Steering and Follow-up
368
462
 
369
463
  ### Steering
@@ -751,19 +845,29 @@ stats.timeTillFirstTokenMs // ms from run() start to the first stream/tool
751
845
  All types are available from `zidane/types`:
752
846
 
753
847
  ```ts
754
- import type { Agent, SessionTurn, TurnUsage, Provider, ToolDef } from 'zidane/types'
848
+ import type { Agent, SessionTurn, TurnUsage, Provider, ToolDef, ValidationResult } from 'zidane/types'
755
849
 
756
850
  // Hook context types for typed event handlers
757
851
  import type { ToolHookContext, McpToolHookContext, SessionHookContext, StreamHookContext } from 'zidane/types'
758
852
  ```
759
853
 
854
+ Helpers (re-exported from the main entry):
855
+
856
+ ```ts
857
+ import { toolResultToText, toolOutputByteLength, validateToolArgs } from 'zidane'
858
+ ```
859
+
860
+ - `toolResultToText(content)` — flatten `string | ToolResultContent[]` to a string for logging.
861
+ - `toolOutputByteLength(content)` — same formula the loop uses for `outputBytes`.
862
+ - `validateToolArgs(input, schema)` — the validator the loop runs between `tool:gate` and `tool:before`. Useful for unit tests of consumer tool definitions.
863
+
760
864
  ## Testing
761
865
 
762
866
  ```bash
763
867
  bun test
764
868
  ```
765
869
 
766
- 915+ tests with mock provider and execution context. No API keys or Docker needed; the suite runs in under 2 seconds.
870
+ 1000+ tests with mock provider and execution context. No API keys or Docker needed; the suite runs in under 2 seconds.
767
871
 
768
872
  ## Benchmarks
769
873
 
@@ -121,7 +121,18 @@ declare function toTypedError(classification: ClassifiedError, provider: string,
121
121
  * Shared types for the agent system.
122
122
  */
123
123
 
124
- type ThinkingLevel = 'off' | 'minimal' | 'low' | 'medium' | 'high';
124
+ /**
125
+ * Thinking / extended-reasoning configuration.
126
+ *
127
+ * - `'off'` — no thinking.
128
+ * - `'minimal' | 'low' | 'medium' | 'high'` — explicit token budget. Maps to
129
+ * provider-specific reasoning controls (Anthropic `thinking.type='enabled'`
130
+ * with a budget; OpenAI `reasoning_effort`).
131
+ * - `'adaptive'` — let the model decide per-turn whether and how much to think.
132
+ * Anthropic-only (`thinking.type='adaptive'`). Other providers fall back to
133
+ * no reasoning when this value is supplied.
134
+ */
135
+ type ThinkingLevel = 'off' | 'minimal' | 'low' | 'medium' | 'high' | 'adaptive';
125
136
  interface McpServerConfig {
126
137
  /** Display name (used for tool namespacing) */
127
138
  name: string;
@@ -194,6 +205,17 @@ interface AgentBehavior {
194
205
  * Default: `true`.
195
206
  */
196
207
  cache?: boolean;
208
+ /**
209
+ * Soft per-turn cap on total tool-output bytes. When the sum of `outputBytes`
210
+ * across a turn's tool results exceeds this value, the loop injects a
211
+ * synthetic user message instructing the model to summarize before calling
212
+ * more tools, and fires the `budget:exceeded` hook.
213
+ *
214
+ * Measured **post-`tool:transform`** so consumer truncation counts toward the
215
+ * budget. Off by default (undefined / `0` disables the check). A reasonable
216
+ * starting value for OSS-model integrations is `32768`.
217
+ */
218
+ toolOutputBudget?: number;
197
219
  }
198
220
  interface ImageContent {
199
221
  type: 'image';
@@ -270,6 +292,19 @@ interface ToolResultImageContent {
270
292
  * structured content should route the array through without flattening.
271
293
  */
272
294
  declare function toolResultToText(content: string | ToolResultContent[]): string;
295
+ /**
296
+ * Approximate byte length of a tool output as it goes back to the model.
297
+ *
298
+ * - Plain text: UTF-8 byte length.
299
+ * - Structured content: text blocks contribute their UTF-8 byte length; image
300
+ * blocks contribute their **base64 character length**, since that is what
301
+ * the model tokenizes (the wire-encoded payload, not the decoded image).
302
+ *
303
+ * Used by the agent loop to populate `outputBytes` on `tool:after`,
304
+ * `tool:transform`, `mcp:tool:after`, and `mcp:tool:transform` hooks so
305
+ * consumers can size-budget tool output without re-counting bytes themselves.
306
+ */
307
+ declare function toolOutputByteLength(content: string | ToolResultContent[]): number;
273
308
  type SessionContentBlock = {
274
309
  type: 'text';
275
310
  text: string;
@@ -1423,9 +1458,13 @@ interface AgentHooks {
1423
1458
  block: boolean;
1424
1459
  reason: string;
1425
1460
  }) => void;
1426
- 'tool:before': (ctx: ToolHookContext) => void;
1461
+ 'tool:before': (ctx: ToolHookContext & {
1462
+ coercions?: readonly string[];
1463
+ }) => void;
1427
1464
  'tool:after': (ctx: ToolHookContext & {
1428
1465
  result: string | ToolResultContent[];
1466
+ outputBytes: number;
1467
+ coercions?: readonly string[];
1429
1468
  }) => void;
1430
1469
  'tool:error': (ctx: ToolHookContext & {
1431
1470
  error: Error;
@@ -1433,6 +1472,45 @@ interface AgentHooks {
1433
1472
  'tool:transform': (ctx: ToolHookContext & {
1434
1473
  result: string | ToolResultContent[];
1435
1474
  isError: boolean;
1475
+ outputBytes: number;
1476
+ coercions?: readonly string[];
1477
+ }) => void;
1478
+ /**
1479
+ * Fires before the generic "Unknown tool" error when the model invokes a tool
1480
+ * that isn't registered (hallucinated names, dropped MCP servers, dangling
1481
+ * aliases). Mutate `result` to substitute a friendly response or set
1482
+ * `suppressError: true` to skip the companion `tool:error` emission.
1483
+ *
1484
+ * Fires for any unknown tool name — including hallucinated MCP-style names
1485
+ * (`mcp_supabase_xxx`); branch on `name.startsWith('mcp_')` to differentiate.
1486
+ */
1487
+ 'tool:unknown': (ctx: ToolHookContext & {
1488
+ result?: string | ToolResultContent[];
1489
+ suppressError: boolean;
1490
+ }) => void;
1491
+ /**
1492
+ * Fires when `validateToolArgs` rejects an input that could not be auto-coerced
1493
+ * to satisfy the tool's `inputSchema`. Observational — the tool call still
1494
+ * surfaces a `Validation error: …` string back to the model. Useful for
1495
+ * counting validation failures separately from runtime tool errors.
1496
+ */
1497
+ 'validation:reject': (ctx: ToolHookContext & {
1498
+ reason: string;
1499
+ schema: Record<string, unknown>;
1500
+ }) => void;
1501
+ /**
1502
+ * Fires when `validateToolArgs` successfully auto-coerced one or more input
1503
+ * fields to satisfy the tool's `inputSchema`. **Only fires when at least one
1504
+ * coercion happened** — never on perfectly-shaped inputs. Useful for counting
1505
+ * model "wrongness rate" without re-running validation downstream.
1506
+ *
1507
+ * `coercions` lists the field names that were coerced. The values landed in
1508
+ * the input that the tool actually received; consumers wanting before/after
1509
+ * comparison can re-run `validateToolArgs(ctx.input, ctx.schema)`.
1510
+ */
1511
+ 'validation:coerce': (ctx: ToolHookContext & {
1512
+ coercions: readonly string[];
1513
+ schema: Record<string, unknown>;
1436
1514
  }) => void;
1437
1515
  'context:transform': (ctx: {
1438
1516
  messages: SessionMessage[];
@@ -1463,11 +1541,14 @@ interface AgentHooks {
1463
1541
  depth: number;
1464
1542
  }) => void;
1465
1543
  'child:tool:before': (ctx: ToolHookContext & {
1544
+ coercions?: readonly string[];
1466
1545
  childId: string;
1467
1546
  depth: number;
1468
1547
  }) => void;
1469
1548
  'child:tool:after': (ctx: ToolHookContext & {
1470
1549
  result: string | ToolResultContent[];
1550
+ outputBytes: number;
1551
+ coercions?: readonly string[];
1471
1552
  childId: string;
1472
1553
  depth: number;
1473
1554
  }) => void;
@@ -1527,9 +1608,11 @@ interface AgentHooks {
1527
1608
  'mcp:tool:before': (ctx: McpToolHookContext) => void;
1528
1609
  'mcp:tool:after': (ctx: McpToolHookContext & {
1529
1610
  result: string | ToolResultContent[];
1611
+ outputBytes: number;
1530
1612
  }) => void;
1531
1613
  'mcp:tool:transform': (ctx: McpToolHookContext & {
1532
1614
  result: string | ToolResultContent[];
1615
+ outputBytes: number;
1533
1616
  }) => void;
1534
1617
  'mcp:tool:error': (ctx: McpToolHookContext & {
1535
1618
  error: Error;
@@ -1560,6 +1643,17 @@ interface AgentHooks {
1560
1643
  output: Record<string, unknown>;
1561
1644
  schema: Record<string, unknown>;
1562
1645
  }) => void;
1646
+ /**
1647
+ * Fires when a turn's total tool-output bytes exceed `behavior.toolOutputBudget`.
1648
+ * Measured post-`tool:transform`. Loop injects a synthetic user message after
1649
+ * the tool-results turn instructing the model to summarize.
1650
+ */
1651
+ 'budget:exceeded': (ctx: {
1652
+ turn: number;
1653
+ turnId: string;
1654
+ bytes: number;
1655
+ budget: number;
1656
+ }) => void;
1563
1657
  'agent:abort': (ctx: object) => void;
1564
1658
  'agent:done': (ctx: AgentStats) => void;
1565
1659
  'session:start': (ctx: SessionHookContext & {
@@ -1675,4 +1769,4 @@ interface Agent {
1675
1769
  }
1676
1770
  declare function createAgent({ provider, name: agentName, system: agentSystem, tools: agentTools, toolAliases, behavior: agentBehavior, execution, mcpServers, session, skills: agentSkills, mcpConnector, eager }: AgentOptions): Agent;
1677
1771
 
1678
- export { type ToolHookContext as $, type Agent as A, type SessionData as B, CONTEXT_EXCEEDED_MESSAGE_PATTERNS as C, type SessionEndStatus as D, type SessionHookContext as E, type SessionMessage as F, type SessionRun as G, type SessionStore as H, type ImageContent as I, type SessionTurn as J, type SkillConfig as K, type SkillResource as L, type McpConnection as M, type SkillsConfig as N, type OAuthRefreshHookContext as O, type PromptDocumentPart as P, type SpawnHookContext as Q, type RemoteStoreOptions as R, type Session as S, type StreamCallbacks as T, type StreamHookContext as U, type StreamOptions as V, type ThinkingLevel as W, type ToolCall as X, type ToolContext as Y, type ToolDef as Z, type ToolExecutionMode as _, AgentAbortedError as a, type ToolMap as a0, type ToolResult as a1, type ToolResultContent as a2, type ToolResultImageContent as a3, type ToolResultTextContent as a4, type ToolSpec as a5, type TurnFinishReason as a6, type TurnResult as a7, type TurnUsage as a8, matchesContextExceeded as a9, loadSession as aA, mapOAIFinishReason as aB, normalizeMcpBlocks as aC, normalizeMcpServers as aD, openai as aE, openaiCompat as aF, openrouter as aG, resultToString as aH, toAnthropic as aI, toOpenAI as aJ, toTypedError as aK, toolResultToText as aa, type ActivationVia as ab, type ActiveSkill as ac, type DeactivationReason as ad, type FileMapAdapter as ae, type FileMapStoreOptions as af, type OpenAICompatAuthHeader as ag, OpenAICompatHttpError as ah, type OpenAICompatParams as ai, type SkillActivationState as aj, type SkillActivationStateOptions as ak, type SkillDiagnostic as al, type SkillSource as am, anthropic as an, autoDetectAndConvert as ao, cerebras as ap, classifyOpenAICompatError as aq, connectMcpServers as ar, createAgent as as, createFileMapStore as at, createMemoryStore as au, createRemoteStore as av, createSession as aw, createSkillActivationState as ax, fromAnthropic as ay, fromOpenAI as az, type AgentBehavior as b, AgentContextExceededError as c, type AgentHooks as d, type AgentOptions as e, AgentProviderError as f, type AgentRunOptions as g, type AgentStats as h, AgentToolNotAllowedError as i, type AnthropicParams as j, type CerebrasParams as k, type ChildRunStats as l, type ClassifiedError as m, type ClassifiedErrorKind as n, type CreateSessionOptions as o, type McpServerConfig as p, type McpToolHookContext as q, type OpenAIParams as r, type OpenRouterParams as s, type PromptImagePart as t, type PromptPart as u, type PromptTextPart as v, type Provider as w, type ProviderCapabilities as x, type RunHookMap as y, type SessionContentBlock as z };
1772
+ export { type ToolHookContext as $, type Agent as A, type SessionData as B, CONTEXT_EXCEEDED_MESSAGE_PATTERNS as C, type SessionEndStatus as D, type SessionHookContext as E, type SessionMessage as F, type SessionRun as G, type SessionStore as H, type ImageContent as I, type SessionTurn as J, type SkillConfig as K, type SkillResource as L, type McpConnection as M, type SkillsConfig as N, type OAuthRefreshHookContext as O, type PromptDocumentPart as P, type SpawnHookContext as Q, type RemoteStoreOptions as R, type Session as S, type StreamCallbacks as T, type StreamHookContext as U, type StreamOptions as V, type ThinkingLevel as W, type ToolCall as X, type ToolContext as Y, type ToolDef as Z, type ToolExecutionMode as _, AgentAbortedError as a, type ToolMap as a0, type ToolResult as a1, type ToolResultContent as a2, type ToolResultImageContent as a3, type ToolResultTextContent as a4, type ToolSpec as a5, type TurnFinishReason as a6, type TurnResult as a7, type TurnUsage as a8, matchesContextExceeded as a9, fromOpenAI as aA, loadSession as aB, mapOAIFinishReason as aC, normalizeMcpBlocks as aD, normalizeMcpServers as aE, openai as aF, openaiCompat as aG, openrouter as aH, resultToString as aI, toAnthropic as aJ, toOpenAI as aK, toTypedError as aL, toolOutputByteLength as aa, toolResultToText as ab, type ActivationVia as ac, type ActiveSkill as ad, type DeactivationReason as ae, type FileMapAdapter as af, type FileMapStoreOptions as ag, type OpenAICompatAuthHeader as ah, OpenAICompatHttpError as ai, type OpenAICompatParams as aj, type SkillActivationState as ak, type SkillActivationStateOptions as al, type SkillDiagnostic as am, type SkillSource as an, anthropic as ao, autoDetectAndConvert as ap, cerebras as aq, classifyOpenAICompatError as ar, connectMcpServers as as, createAgent as at, createFileMapStore as au, createMemoryStore as av, createRemoteStore as aw, createSession as ax, createSkillActivationState as ay, fromAnthropic as az, type AgentBehavior as b, AgentContextExceededError as c, type AgentHooks as d, type AgentOptions as e, AgentProviderError as f, type AgentRunOptions as g, type AgentStats as h, AgentToolNotAllowedError as i, type AnthropicParams as j, type CerebrasParams as k, type ChildRunStats as l, type ClassifiedError as m, type ClassifiedErrorKind as n, type CreateSessionOptions as o, type McpServerConfig as p, type McpToolHookContext as q, type OpenAIParams as r, type OpenRouterParams as s, type PromptImagePart as t, type PromptPart as u, type PromptTextPart as v, type Provider as w, type ProviderCapabilities as x, type RunHookMap as y, type SessionContentBlock as z };
@@ -1,16 +1,18 @@
1
1
  import {
2
+ edit,
2
3
  listFiles,
4
+ multiEdit,
3
5
  readFile,
4
6
  shell,
5
7
  spawn,
6
8
  writeFile
7
- } from "./chunk-O2XZLJMG.js";
9
+ } from "./chunk-QFHGWKK3.js";
8
10
 
9
11
  // src/presets/basic.ts
10
- var basicTools = { shell, readFile, writeFile, listFiles };
12
+ var basicTools = { shell, readFile, writeFile, listFiles, edit, multiEdit };
11
13
  var basic_default = definePreset({
12
14
  name: "basic",
13
- system: "You are a helpful assistant with access to shell, file reading, file writing, directory listing, and sub-agent spawning tools. Use them to accomplish tasks in the project directory.",
15
+ system: "You are a helpful assistant with access to shell, file reading, file writing, surgical and multi-edit tools, directory listing, and sub-agent spawning. Prefer `edit` / `multi_edit` for in-place changes and `write_file` for full file overwrites. Use them to accomplish tasks in the project directory.",
14
16
  tools: { ...basicTools, spawn }
15
17
  });
16
18
 
@@ -161,12 +161,22 @@ function createClient(SDK, apiKey, isOAuth, baseURL) {
161
161
  } : { apiKey, ...base }
162
162
  );
163
163
  }
164
- var THINKING_BUDGETS = {
165
- minimal: 1024,
166
- low: 4096,
167
- medium: 10240,
168
- high: 32768
164
+ var EFFORT_FOR_LEVEL = {
165
+ minimal: "low",
166
+ low: "low",
167
+ medium: "medium",
168
+ high: "high"
169
169
  };
170
+ function planAnthropicThinking(level, customBudget) {
171
+ if (level === "off")
172
+ return null;
173
+ if (level === "adaptive")
174
+ return { kind: "adaptive" };
175
+ if (customBudget !== void 0) {
176
+ return { kind: "enabled", budgetTokens: customBudget, maxTokensBump: customBudget };
177
+ }
178
+ return { kind: "adaptive", effort: EFFORT_FOR_LEVEL[level] };
179
+ }
170
180
  function mapStopReason(stopReason) {
171
181
  if (!stopReason)
172
182
  return void 0;
@@ -393,13 +403,16 @@ function anthropic(anthropicParams) {
393
403
  };
394
404
  if (options.cache !== false)
395
405
  applyAnthropicCacheBreakpoints(params);
396
- if (thinking !== "off") {
397
- const budgetTokens = options.thinkingBudget ?? THINKING_BUDGETS[thinking];
398
- params.thinking = {
399
- type: "enabled",
400
- budget_tokens: budgetTokens
401
- };
402
- params.max_tokens = budgetTokens + params.max_tokens;
406
+ const plan = planAnthropicThinking(thinking, options.thinkingBudget);
407
+ if (plan) {
408
+ if (plan.kind === "enabled") {
409
+ params.thinking = { type: "enabled", budget_tokens: plan.budgetTokens };
410
+ params.max_tokens = plan.maxTokensBump + params.max_tokens;
411
+ } else {
412
+ params.thinking = { type: "adaptive" };
413
+ if (plan.effort)
414
+ params.output_config = { effort: plan.effort };
415
+ }
403
416
  params.temperature = 1;
404
417
  }
405
418
  if (options.toolChoice) {
@@ -679,13 +692,14 @@ function openai(params) {
679
692
  messages: toPiMessages(options.messages, modelId),
680
693
  tools: options.tools
681
694
  };
695
+ const reasoningLevel = options.thinking && options.thinking !== "off" && options.thinking !== "adaptive" ? options.thinking : void 0;
682
696
  const stream = streamOpenAICodexResponses(model, context, {
683
697
  apiKey,
684
698
  maxTokens: options.maxTokens,
685
699
  signal: options.signal,
686
700
  transport: params?.transport,
687
- reasoningEffort: options.thinking && options.thinking !== "off" ? options.thinking : void 0,
688
- reasoningSummary: options.thinking && options.thinking !== "off" ? "auto" : void 0,
701
+ reasoningEffort: reasoningLevel,
702
+ reasoningSummary: reasoningLevel ? "auto" : void 0,
689
703
  onPayload: (payload) => applyPayloadOverrides(payload, options)
690
704
  });
691
705
  let finalMessage;
@@ -0,0 +1,28 @@
1
+ // src/types.ts
2
+ import { Buffer } from "buffer";
3
+ function toolResultToText(content) {
4
+ if (typeof content === "string")
5
+ return content;
6
+ return content.map((block) => {
7
+ if (block.type === "text")
8
+ return block.text;
9
+ return `[image: ${block.mediaType} \u2014 ${block.data.length} b64 bytes]`;
10
+ }).join("\n");
11
+ }
12
+ function toolOutputByteLength(content) {
13
+ if (typeof content === "string")
14
+ return Buffer.byteLength(content);
15
+ let total = 0;
16
+ for (const block of content) {
17
+ if (block.type === "text")
18
+ total += Buffer.byteLength(block.text);
19
+ else
20
+ total += block.data.length;
21
+ }
22
+ return total;
23
+ }
24
+
25
+ export {
26
+ toolResultToText,
27
+ toolOutputByteLength
28
+ };