zidane 1.1.5 → 1.3.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 +516 -27
- package/dist/agent-DxIUxou4.d.ts +319 -0
- package/dist/chunk-26LIQARN.js +109 -0
- package/dist/chunk-IC2WAUBZ.js +416 -0
- package/dist/chunk-LS57GDAV.js +365 -0
- package/dist/chunk-N523NBO2.js +45 -0
- package/dist/chunk-PNKVD2UK.js +26 -0
- package/dist/chunk-SWS5624X.js +1107 -0
- package/dist/harnesses.d.ts +7 -24
- package/dist/harnesses.js +6 -1
- package/dist/index.d.ts +49 -82
- package/dist/index.js +51 -255
- package/dist/mcp.d.ts +7 -0
- package/dist/mcp.js +11 -0
- package/dist/providers.d.ts +65 -1
- package/dist/providers.js +37 -157
- package/dist/session.d.ts +193 -0
- package/dist/session.js +27 -0
- package/dist/spawn-bEqlGUVT.d.ts +63 -0
- package/dist/tools.d.ts +28 -0
- package/dist/tools.js +20 -0
- package/dist/types-CLRMCak3.d.ts +104 -0
- package/package.json +15 -1
- package/dist/chunk-ECE5USCO.js +0 -125
- package/dist/index-ByJfS-kX.d.ts +0 -101
package/README.md
CHANGED
|
@@ -8,6 +8,8 @@ Minimal TypeScript agent loop built with [Bun](https://bun.sh).
|
|
|
8
8
|
|
|
9
9
|
Hook into every step of the agent's execution using [hookable](https://github.com/unjs/hookable).
|
|
10
10
|
|
|
11
|
+
Built to be embedded in other projects easily, extended through [providers](#providers), [harnesses](#harnesses), and [execution contexts](#execution-contexts).
|
|
12
|
+
|
|
11
13
|
## Quickstart
|
|
12
14
|
|
|
13
15
|
```bash
|
|
@@ -30,7 +32,106 @@ bun start \
|
|
|
30
32
|
--provider anthropic \ # anthropic | openrouter | cerebras
|
|
31
33
|
--harness basic \ # tool set to use
|
|
32
34
|
--system "be concise" \ # system prompt
|
|
33
|
-
--thinking off
|
|
35
|
+
--thinking off \ # off | minimal | low | medium | high
|
|
36
|
+
--context process \ # process | docker
|
|
37
|
+
--mcp '{"name":"fs","transport":"stdio","command":"npx","args":["-y","@modelcontextprotocol/server-filesystem","."]}'
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
The `--mcp` flag accepts a JSON object matching `McpServerConfig`. It can be passed multiple times.
|
|
41
|
+
|
|
42
|
+
## Execution Contexts
|
|
43
|
+
|
|
44
|
+
An execution context defines **where** the agent's tools run. All tool operations (shell, filesystem) go through it.
|
|
45
|
+
|
|
46
|
+
### In-process (default)
|
|
47
|
+
|
|
48
|
+
Runs in the same Node/Bun process. No isolation, fastest.
|
|
49
|
+
|
|
50
|
+
```ts
|
|
51
|
+
import { createAgent, createProcessContext } from 'zidane'
|
|
52
|
+
|
|
53
|
+
const agent = createAgent({
|
|
54
|
+
harness,
|
|
55
|
+
provider,
|
|
56
|
+
// execution defaults to createProcessContext()
|
|
57
|
+
})
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Docker
|
|
61
|
+
|
|
62
|
+
Full container isolation via [dockerode](https://github.com/apocas/dockerode). Configurable resource limits.
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
# CLI
|
|
66
|
+
bun start --prompt "run uname -a" --context docker
|
|
67
|
+
bun start --prompt "build the app" --context docker --image node:22 --cwd /workspace
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
```ts
|
|
71
|
+
import { createAgent, createDockerContext } from 'zidane'
|
|
72
|
+
|
|
73
|
+
const agent = createAgent({
|
|
74
|
+
harness,
|
|
75
|
+
provider,
|
|
76
|
+
execution: createDockerContext({
|
|
77
|
+
image: 'node:22',
|
|
78
|
+
cwd: '/workspace',
|
|
79
|
+
limits: { memory: 512, cpu: '1.0' },
|
|
80
|
+
}),
|
|
81
|
+
})
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Requires `dockerode` as a peer dependency: `bun add dockerode`
|
|
85
|
+
|
|
86
|
+
### Sandbox (remote)
|
|
87
|
+
|
|
88
|
+
Offloads execution to a remote sandbox API. Implement the `SandboxProvider` interface for your provider (Rivet, E2B, etc.).
|
|
89
|
+
|
|
90
|
+
```ts
|
|
91
|
+
import { createAgent, createSandboxContext } from 'zidane'
|
|
92
|
+
import type { SandboxProvider } from 'zidane'
|
|
93
|
+
|
|
94
|
+
const myProvider: SandboxProvider = {
|
|
95
|
+
name: 'my-sandbox',
|
|
96
|
+
spawn: async (config) => { /* ... */ },
|
|
97
|
+
exec: async (id, command) => { /* ... */ },
|
|
98
|
+
readFile: async (id, path) => { /* ... */ },
|
|
99
|
+
writeFile: async (id, path, content) => { /* ... */ },
|
|
100
|
+
listFiles: async (id, path) => { /* ... */ },
|
|
101
|
+
destroy: async (id) => { /* ... */ },
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const agent = createAgent({
|
|
105
|
+
harness,
|
|
106
|
+
provider,
|
|
107
|
+
execution: createSandboxContext(myProvider),
|
|
108
|
+
})
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### Execution Context Interface
|
|
112
|
+
|
|
113
|
+
All contexts implement the same interface:
|
|
114
|
+
|
|
115
|
+
```ts
|
|
116
|
+
interface ExecutionContext {
|
|
117
|
+
type: 'process' | 'docker' | 'sandbox'
|
|
118
|
+
capabilities: { shell, filesystem, network, gpu }
|
|
119
|
+
spawn(config?): Promise<ExecutionHandle>
|
|
120
|
+
exec(handle, command, options?): Promise<ExecResult>
|
|
121
|
+
readFile(handle, path): Promise<string>
|
|
122
|
+
writeFile(handle, path, content): Promise<void>
|
|
123
|
+
listFiles(handle, path): Promise<string[]>
|
|
124
|
+
destroy(handle): Promise<void>
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Access the context from a running agent:
|
|
129
|
+
|
|
130
|
+
```ts
|
|
131
|
+
agent.execution // ExecutionContext
|
|
132
|
+
agent.execution.type // 'process' | 'docker' | 'sandbox'
|
|
133
|
+
agent.handle // ExecutionHandle (after first run)
|
|
134
|
+
await agent.destroy() // clean up context resources
|
|
34
135
|
```
|
|
35
136
|
|
|
36
137
|
## Providers
|
|
@@ -69,8 +170,6 @@ CEREBRAS_API_KEY=csk-... bun start \
|
|
|
69
170
|
--prompt "hello"
|
|
70
171
|
```
|
|
71
172
|
|
|
72
|
-
Available models: `zai-glm-4.7`, `gpt-oss-120b`
|
|
73
|
-
|
|
74
173
|
## Thinking
|
|
75
174
|
|
|
76
175
|
Extended reasoning for complex tasks. Maps to Anthropic's thinking API or OpenRouter's `:thinking` variant.
|
|
@@ -97,9 +196,245 @@ Tools are grouped into **harnesses**. The `basic` harness includes:
|
|
|
97
196
|
| `read_file` | Read file contents |
|
|
98
197
|
| `write_file` | Write/create files |
|
|
99
198
|
| `list_files` | List directory contents |
|
|
199
|
+
| `spawn` | Spawn a sub-agent for a task |
|
|
100
200
|
|
|
101
201
|
All paths are sandboxed to the working directory.
|
|
102
202
|
|
|
203
|
+
Define a custom harness with `defineHarness`:
|
|
204
|
+
|
|
205
|
+
```ts
|
|
206
|
+
import { defineHarness } from 'zidane'
|
|
207
|
+
|
|
208
|
+
const harness = defineHarness({
|
|
209
|
+
name: 'researcher',
|
|
210
|
+
system: 'You are a research assistant.',
|
|
211
|
+
tools: { ...basicTools },
|
|
212
|
+
mcpServers: [
|
|
213
|
+
{ name: 'filesystem', transport: 'stdio', command: 'npx', args: ['-y', '@modelcontextprotocol/server-filesystem', '.'] },
|
|
214
|
+
],
|
|
215
|
+
})
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
## Sub-agent Spawning
|
|
219
|
+
|
|
220
|
+
The `spawn` tool lets the agent delegate tasks to child agents. Children run independently and return their result as a tool response.
|
|
221
|
+
|
|
222
|
+
### Static spawn tool
|
|
223
|
+
|
|
224
|
+
```ts
|
|
225
|
+
import { spawn, basicTools, defineHarness } from 'zidane'
|
|
226
|
+
|
|
227
|
+
const harness = defineHarness({
|
|
228
|
+
name: 'orchestrator',
|
|
229
|
+
tools: { ...basicTools, spawn },
|
|
230
|
+
})
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
Children inherit the parent's harness (and can spawn their own children).
|
|
234
|
+
|
|
235
|
+
### Configurable factory
|
|
236
|
+
|
|
237
|
+
Use `createSpawnTool` when you need custom concurrency limits, model overrides, or lifecycle callbacks.
|
|
238
|
+
|
|
239
|
+
```ts
|
|
240
|
+
import { createSpawnTool } from 'zidane'
|
|
241
|
+
|
|
242
|
+
const spawnTool = createSpawnTool({
|
|
243
|
+
maxConcurrent: 5,
|
|
244
|
+
model: 'claude-haiku-4-5-20251001',
|
|
245
|
+
system: 'You are a focused sub-agent.',
|
|
246
|
+
thinking: 'low',
|
|
247
|
+
onSpawn: (child) => console.log(`started ${child.id}`),
|
|
248
|
+
onComplete: (child, stats) => console.log(`${child.id} done in ${stats.turns} turns`),
|
|
249
|
+
})
|
|
250
|
+
|
|
251
|
+
const harness = defineHarness({
|
|
252
|
+
name: 'orchestrator',
|
|
253
|
+
tools: { spawn: spawnTool },
|
|
254
|
+
})
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
## MCP Servers
|
|
258
|
+
|
|
259
|
+
Connect any MCP-compatible tool server. Tools are namespaced as `mcp_{serverName}_{toolName}`.
|
|
260
|
+
|
|
261
|
+
### Agent-level
|
|
262
|
+
|
|
263
|
+
```ts
|
|
264
|
+
const agent = createAgent({
|
|
265
|
+
harness,
|
|
266
|
+
provider,
|
|
267
|
+
mcpServers: [
|
|
268
|
+
{ name: 'filesystem', transport: 'stdio', command: 'npx', args: ['-y', '@modelcontextprotocol/server-filesystem', '.'] },
|
|
269
|
+
{ name: 'search', transport: 'sse', url: 'http://localhost:3001/sse' },
|
|
270
|
+
{ name: 'api', transport: 'streamable-http', url: 'http://localhost:3002/mcp' },
|
|
271
|
+
],
|
|
272
|
+
})
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
### Harness-level
|
|
276
|
+
|
|
277
|
+
MCP servers can also be declared on the harness so they're shared across all agents using it.
|
|
278
|
+
|
|
279
|
+
```ts
|
|
280
|
+
const harness = defineHarness({
|
|
281
|
+
name: 'with-mcp',
|
|
282
|
+
tools: { ...basicTools },
|
|
283
|
+
mcpServers: [
|
|
284
|
+
{ name: 'db', transport: 'stdio', command: 'node', args: ['db-server.js'] },
|
|
285
|
+
],
|
|
286
|
+
})
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
MCP connections are made lazily on the first `run()` call and reused across subsequent runs. They are closed when `agent.destroy()` is called.
|
|
290
|
+
|
|
291
|
+
## Sessions
|
|
292
|
+
|
|
293
|
+
Sessions give an agent persistent identity, turn history, and run metadata across multiple calls or restarts. Each message exchange is a `SessionTurn` with its own UUID, enabling real-time multiplayer streaming.
|
|
294
|
+
|
|
295
|
+
### SessionTurn
|
|
296
|
+
|
|
297
|
+
Every message in a session is a turn:
|
|
298
|
+
|
|
299
|
+
```ts
|
|
300
|
+
interface SessionTurn {
|
|
301
|
+
id: string // UUID — generated by store or crypto.randomUUID()
|
|
302
|
+
role: 'user' | 'assistant' | 'system'
|
|
303
|
+
content: SessionContentBlock[] // same format used by providers
|
|
304
|
+
usage?: TurnUsage // token usage (assistant turns only)
|
|
305
|
+
createdAt: number // timestamp
|
|
306
|
+
}
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
### Creating a session
|
|
310
|
+
|
|
311
|
+
`createSession` is async — stores can generate IDs server-side (e.g. Supabase).
|
|
312
|
+
|
|
313
|
+
```ts
|
|
314
|
+
import { createSession, createMemoryStore } from 'zidane/session'
|
|
315
|
+
|
|
316
|
+
// In-memory (default, no persistence)
|
|
317
|
+
const session = await createSession({ id: 'my-session', agentId: 'my-agent' })
|
|
318
|
+
|
|
319
|
+
// With a store for persistence
|
|
320
|
+
const store = createMemoryStore()
|
|
321
|
+
const session = await createSession({ id: 'my-session', store })
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
### Storage backends
|
|
325
|
+
|
|
326
|
+
Three built-in stores are available. All implement the full `SessionStore` interface including incremental operations.
|
|
327
|
+
|
|
328
|
+
```ts
|
|
329
|
+
import { createMemoryStore, createSqliteStore, createRemoteStore } from 'zidane/session'
|
|
330
|
+
|
|
331
|
+
// In-memory, fast, no disk I/O, lost on process restart
|
|
332
|
+
const memStore = createMemoryStore()
|
|
333
|
+
|
|
334
|
+
// SQLite, persistent, zero-dependency (uses Bun's built-in SQLite)
|
|
335
|
+
const sqliteStore = createSqliteStore({ path: './sessions.db' })
|
|
336
|
+
|
|
337
|
+
// Remote HTTP, delegates to a custom REST API
|
|
338
|
+
const remoteStore = createRemoteStore({ url: 'https://api.example.com/sessions' })
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
### SessionStore interface
|
|
342
|
+
|
|
343
|
+
```ts
|
|
344
|
+
interface SessionStore {
|
|
345
|
+
// Optional: server-side ID generation
|
|
346
|
+
generateSessionId?: () => string | Promise<string>
|
|
347
|
+
generateTurnId?: () => string | Promise<string>
|
|
348
|
+
|
|
349
|
+
// Core CRUD
|
|
350
|
+
load: (sessionId: string) => Promise<SessionData | null>
|
|
351
|
+
save: (session: SessionData) => Promise<void>
|
|
352
|
+
delete: (sessionId: string) => Promise<void>
|
|
353
|
+
list: (filter?) => Promise<string[]>
|
|
354
|
+
|
|
355
|
+
// Incremental operations (avoids full re-save)
|
|
356
|
+
appendTurns: (sessionId: string, turns: SessionTurn[]) => Promise<void>
|
|
357
|
+
getTurns: (sessionId: string, from?: number, limit?: number) => Promise<SessionTurn[]>
|
|
358
|
+
updateRun: (sessionId: string, run: SessionRun) => Promise<void>
|
|
359
|
+
updateStatus: (sessionId: string, status: SessionStatus) => Promise<void>
|
|
360
|
+
}
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
Custom ID generation lets external databases (e.g. Supabase) provide UUIDs server-side, keeping IDs in sync:
|
|
364
|
+
|
|
365
|
+
```ts
|
|
366
|
+
const store = createRemoteStore({ url: '...' })
|
|
367
|
+
store.generateTurnId = async () => {
|
|
368
|
+
const { data } = await supabase.rpc('gen_random_uuid')
|
|
369
|
+
return data
|
|
370
|
+
}
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
### Agent integration
|
|
374
|
+
|
|
375
|
+
```ts
|
|
376
|
+
const agent = createAgent({
|
|
377
|
+
harness,
|
|
378
|
+
provider,
|
|
379
|
+
session,
|
|
380
|
+
})
|
|
381
|
+
|
|
382
|
+
await agent.run({ prompt: 'hello' })
|
|
383
|
+
await session.save() // persist to store
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
Turns are persisted incrementally after each agent turn via `appendTurns` — not as a full document save. If the agent crashes mid-run, you still have turns up to the last completed turn.
|
|
387
|
+
|
|
388
|
+
### Session status
|
|
389
|
+
|
|
390
|
+
Sessions track their status: `'idle' | 'running' | 'completed' | 'error'`. The agent updates it automatically during runs.
|
|
391
|
+
|
|
392
|
+
```ts
|
|
393
|
+
session.status // 'idle'
|
|
394
|
+
await agent.run({ prompt: 'go' })
|
|
395
|
+
// idle → running → completed (or error)
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
### Session hooks
|
|
399
|
+
|
|
400
|
+
```ts
|
|
401
|
+
agent.hooks.hook('session:start', (ctx) => {
|
|
402
|
+
// ctx.sessionId, ctx.runId, ctx.prompt
|
|
403
|
+
})
|
|
404
|
+
|
|
405
|
+
agent.hooks.hook('session:end', (ctx) => {
|
|
406
|
+
// ctx.sessionId, ctx.runId
|
|
407
|
+
// ctx.status: 'completed' | 'aborted' | 'error'
|
|
408
|
+
})
|
|
409
|
+
|
|
410
|
+
agent.hooks.hook('session:turns', (ctx) => {
|
|
411
|
+
// ctx.sessionId, ctx.count
|
|
412
|
+
// fired after each turn (incremental sync)
|
|
413
|
+
})
|
|
414
|
+
|
|
415
|
+
agent.hooks.hook('session:save', (ctx) => {
|
|
416
|
+
// ctx.sessionId
|
|
417
|
+
// fired after session.save() completes
|
|
418
|
+
})
|
|
419
|
+
|
|
420
|
+
agent.hooks.hook('session:meta', (ctx) => {
|
|
421
|
+
// ctx.sessionId, ctx.key, ctx.value
|
|
422
|
+
// fired when session.setMeta() is called
|
|
423
|
+
})
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
### Restoring a session
|
|
427
|
+
|
|
428
|
+
```ts
|
|
429
|
+
import { loadSession } from 'zidane/session'
|
|
430
|
+
|
|
431
|
+
const session = await loadSession(store, 'my-session')
|
|
432
|
+
if (session) {
|
|
433
|
+
const agent = createAgent({ harness, provider, session })
|
|
434
|
+
await agent.run({ prompt: 'continue from before' })
|
|
435
|
+
}
|
|
436
|
+
```
|
|
437
|
+
|
|
103
438
|
## Hooks
|
|
104
439
|
|
|
105
440
|
The agent uses [hookable](https://github.com/unjs/hookable) for lifecycle events. Every hook receives a mutable context object.
|
|
@@ -108,20 +443,21 @@ The agent uses [hookable](https://github.com/unjs/hookable) for lifecycle events
|
|
|
108
443
|
|
|
109
444
|
```ts
|
|
110
445
|
agent.hooks.hook('system:before', (ctx) => {
|
|
111
|
-
// ctx.system
|
|
446
|
+
// ctx.system: system prompt text
|
|
112
447
|
})
|
|
113
448
|
|
|
114
449
|
agent.hooks.hook('turn:before', (ctx) => {
|
|
115
|
-
// ctx.turn
|
|
116
|
-
// ctx.
|
|
450
|
+
// ctx.turn: turn number
|
|
451
|
+
// ctx.turnId: UUID for this turn (generated before LLM call)
|
|
452
|
+
// ctx.options: StreamOptions being sent to provider
|
|
117
453
|
})
|
|
118
454
|
|
|
119
455
|
agent.hooks.hook('turn:after', (ctx) => {
|
|
120
|
-
// ctx.turn, ctx.usage { input, output }
|
|
456
|
+
// ctx.turn, ctx.turnId, ctx.usage { input, output }
|
|
121
457
|
})
|
|
122
458
|
|
|
123
459
|
agent.hooks.hook('agent:done', (ctx) => {
|
|
124
|
-
// ctx.totalIn, ctx.totalOut, ctx.turns, ctx.elapsed
|
|
460
|
+
// ctx.totalIn, ctx.totalOut, ctx.turns, ctx.elapsed, ctx.children?
|
|
125
461
|
})
|
|
126
462
|
|
|
127
463
|
agent.hooks.hook('agent:abort', () => {
|
|
@@ -133,12 +469,15 @@ agent.hooks.hook('agent:abort', () => {
|
|
|
133
469
|
|
|
134
470
|
```ts
|
|
135
471
|
agent.hooks.hook('stream:text', (ctx) => {
|
|
136
|
-
// ctx.delta
|
|
137
|
-
// ctx.text
|
|
472
|
+
// ctx.delta: new text chunk
|
|
473
|
+
// ctx.text: accumulated text so far
|
|
474
|
+
// ctx.turnId: UUID of the turn being streamed
|
|
475
|
+
// ctx.blockIndex: content block index within the turn
|
|
138
476
|
})
|
|
139
477
|
|
|
140
478
|
agent.hooks.hook('stream:end', (ctx) => {
|
|
141
|
-
// ctx.text
|
|
479
|
+
// ctx.text: final complete text
|
|
480
|
+
// ctx.turnId, ctx.blockIndex
|
|
142
481
|
})
|
|
143
482
|
```
|
|
144
483
|
|
|
@@ -158,7 +497,7 @@ agent.hooks.hook('tool:error', (ctx) => {
|
|
|
158
497
|
})
|
|
159
498
|
```
|
|
160
499
|
|
|
161
|
-
### Tool Gate
|
|
500
|
+
### Tool Gate: block execution
|
|
162
501
|
|
|
163
502
|
Mutate `ctx.block = true` to prevent a tool from running.
|
|
164
503
|
|
|
@@ -171,7 +510,7 @@ agent.hooks.hook('tool:gate', (ctx) => {
|
|
|
171
510
|
})
|
|
172
511
|
```
|
|
173
512
|
|
|
174
|
-
### Tool Transform
|
|
513
|
+
### Tool Transform: modify output
|
|
175
514
|
|
|
176
515
|
Mutate `ctx.result` or `ctx.isError` to transform tool results before they're sent back to the model.
|
|
177
516
|
|
|
@@ -182,7 +521,7 @@ agent.hooks.hook('tool:transform', (ctx) => {
|
|
|
182
521
|
})
|
|
183
522
|
```
|
|
184
523
|
|
|
185
|
-
### Context Transform
|
|
524
|
+
### Context Transform: prune messages
|
|
186
525
|
|
|
187
526
|
Mutate `ctx.messages` before each LLM call for context window management.
|
|
188
527
|
|
|
@@ -193,9 +532,73 @@ agent.hooks.hook('context:transform', (ctx) => {
|
|
|
193
532
|
})
|
|
194
533
|
```
|
|
195
534
|
|
|
196
|
-
|
|
535
|
+
### Spawn hooks
|
|
197
536
|
|
|
198
|
-
|
|
537
|
+
Fired by the `spawn` tool when child agents are created.
|
|
538
|
+
|
|
539
|
+
```ts
|
|
540
|
+
agent.hooks.hook('spawn:before', (ctx) => {
|
|
541
|
+
// ctx.id: child agent id (e.g. 'child-1')
|
|
542
|
+
// ctx.task: the task prompt given to the child
|
|
543
|
+
})
|
|
544
|
+
|
|
545
|
+
agent.hooks.hook('spawn:complete', (ctx) => {
|
|
546
|
+
// ctx.id, ctx.task
|
|
547
|
+
// ctx.stats: AgentStats from the child run
|
|
548
|
+
})
|
|
549
|
+
|
|
550
|
+
agent.hooks.hook('spawn:error', (ctx) => {
|
|
551
|
+
// ctx.id, ctx.task, ctx.error
|
|
552
|
+
})
|
|
553
|
+
```
|
|
554
|
+
|
|
555
|
+
### MCP hooks
|
|
556
|
+
|
|
557
|
+
Fired during MCP server lifecycle.
|
|
558
|
+
|
|
559
|
+
```ts
|
|
560
|
+
agent.hooks.hook('mcp:connect', (ctx) => {
|
|
561
|
+
// ctx.name: server name
|
|
562
|
+
// ctx.transport: 'stdio' | 'sse' | 'streamable-http'
|
|
563
|
+
// ctx.tools: namespaced tool names discovered on this server
|
|
564
|
+
})
|
|
565
|
+
|
|
566
|
+
agent.hooks.hook('mcp:error', (ctx) => {
|
|
567
|
+
// ctx.name: server name
|
|
568
|
+
// ctx.error: connection error
|
|
569
|
+
})
|
|
570
|
+
|
|
571
|
+
agent.hooks.hook('mcp:close', (ctx) => {
|
|
572
|
+
// ctx.name: server name being closed
|
|
573
|
+
})
|
|
574
|
+
|
|
575
|
+
agent.hooks.hook('mcp:tool:before', (ctx) => {
|
|
576
|
+
// ctx.server: MCP server name
|
|
577
|
+
// ctx.tool: original tool name (not namespaced)
|
|
578
|
+
// ctx.input: tool arguments
|
|
579
|
+
})
|
|
580
|
+
|
|
581
|
+
agent.hooks.hook('mcp:tool:after', (ctx) => {
|
|
582
|
+
// ctx.server, ctx.tool, ctx.input
|
|
583
|
+
// ctx.result: tool output string
|
|
584
|
+
})
|
|
585
|
+
|
|
586
|
+
agent.hooks.hook('mcp:tool:error', (ctx) => {
|
|
587
|
+
// ctx.server, ctx.tool, ctx.input, ctx.error
|
|
588
|
+
})
|
|
589
|
+
```
|
|
590
|
+
|
|
591
|
+
### Steering inject
|
|
592
|
+
|
|
593
|
+
```ts
|
|
594
|
+
agent.hooks.hook('steer:inject', (ctx) => {
|
|
595
|
+
// ctx.message: the steering message being injected
|
|
596
|
+
})
|
|
597
|
+
```
|
|
598
|
+
|
|
599
|
+
## Steering and Follow-up
|
|
600
|
+
|
|
601
|
+
### Steering: interrupt mid-run
|
|
199
602
|
|
|
200
603
|
Inject a message while the agent is working. Delivered between tool calls, skipping remaining tools in the current turn.
|
|
201
604
|
|
|
@@ -205,7 +608,7 @@ agent.hooks.hook('tool:after', () => {
|
|
|
205
608
|
})
|
|
206
609
|
```
|
|
207
610
|
|
|
208
|
-
### Follow-up
|
|
611
|
+
### Follow-up, continue after done
|
|
209
612
|
|
|
210
613
|
Queue messages that extend the conversation after the agent finishes.
|
|
211
614
|
|
|
@@ -220,7 +623,7 @@ Execute multiple tool calls from a single turn concurrently.
|
|
|
220
623
|
|
|
221
624
|
```ts
|
|
222
625
|
const agent = createAgent({
|
|
223
|
-
harness
|
|
626
|
+
harness,
|
|
224
627
|
provider,
|
|
225
628
|
toolExecution: 'parallel', // default: 'sequential'
|
|
226
629
|
})
|
|
@@ -246,13 +649,68 @@ await agent.run({
|
|
|
246
649
|
})
|
|
247
650
|
```
|
|
248
651
|
|
|
652
|
+
## Message Format
|
|
653
|
+
|
|
654
|
+
All messages in zidane use the canonical `SessionMessage` format, with or without sessions:
|
|
655
|
+
|
|
656
|
+
```ts
|
|
657
|
+
type SessionContentBlock =
|
|
658
|
+
| { type: 'text', text: string }
|
|
659
|
+
| { type: 'image', mediaType: string, data: string }
|
|
660
|
+
| { type: 'tool_call', id: string, name: string, input: Record<string, unknown> }
|
|
661
|
+
| { type: 'tool_result', callId: string, output: string, isError?: boolean }
|
|
662
|
+
| { type: 'thinking', text: string }
|
|
663
|
+
|
|
664
|
+
interface SessionMessage {
|
|
665
|
+
role: 'user' | 'assistant'
|
|
666
|
+
content: SessionContentBlock[]
|
|
667
|
+
}
|
|
668
|
+
```
|
|
669
|
+
|
|
670
|
+
Providers convert to and from native wire formats internally. Converters are available for external interop:
|
|
671
|
+
|
|
672
|
+
```ts
|
|
673
|
+
import { fromAnthropic, toAnthropic, fromOpenAI, toOpenAI, autoDetectAndConvert } from 'zidane'
|
|
674
|
+
```
|
|
675
|
+
|
|
676
|
+
## Usage Tracking
|
|
677
|
+
|
|
678
|
+
Every turn reports token usage. Provider-specific fields are optional:
|
|
679
|
+
|
|
680
|
+
```ts
|
|
681
|
+
interface TurnUsage {
|
|
682
|
+
input: number
|
|
683
|
+
output: number
|
|
684
|
+
cacheCreation?: number // Anthropic: tokens written to cache
|
|
685
|
+
cacheRead?: number // Anthropic: tokens read from cache
|
|
686
|
+
thinking?: number // thinking tokens used
|
|
687
|
+
cost?: number // USD cost reported by provider (e.g. OpenRouter)
|
|
688
|
+
}
|
|
689
|
+
```
|
|
690
|
+
|
|
691
|
+
Per-turn data is available on `AgentStats` and `SessionRun`:
|
|
692
|
+
|
|
693
|
+
```ts
|
|
694
|
+
const stats = await agent.run({ prompt: 'hello' })
|
|
695
|
+
stats.turnUsage // TurnUsage[] per turn
|
|
696
|
+
stats.cost // total cost (sum of per-turn costs, if reported)
|
|
697
|
+
|
|
698
|
+
// In session runs
|
|
699
|
+
session.runs[0].turnUsage // per-turn breakdown
|
|
700
|
+
session.runs[0].totalUsage // aggregated TurnUsage
|
|
701
|
+
session.runs[0].cost // total cost for this run
|
|
702
|
+
```
|
|
703
|
+
|
|
249
704
|
## State Management
|
|
250
705
|
|
|
251
706
|
```ts
|
|
252
|
-
agent.isRunning
|
|
253
|
-
agent.messages
|
|
254
|
-
agent.
|
|
255
|
-
agent.
|
|
707
|
+
agent.isRunning // boolean: is a run in progress?
|
|
708
|
+
agent.messages // SessionMessage[]: conversation history
|
|
709
|
+
agent.execution // ExecutionContext: where tools run
|
|
710
|
+
agent.handle // ExecutionHandle: spawned context handle
|
|
711
|
+
agent.abort() // cancel the current run
|
|
712
|
+
agent.reset() // clear messages and queues
|
|
713
|
+
await agent.destroy() // clean up execution context and MCP connections
|
|
256
714
|
await agent.waitForIdle() // wait for current run to complete
|
|
257
715
|
```
|
|
258
716
|
|
|
@@ -261,12 +719,25 @@ await agent.waitForIdle() // wait for current run to complete
|
|
|
261
719
|
```
|
|
262
720
|
src/
|
|
263
721
|
types.ts shared types
|
|
264
|
-
agent.ts createAgent, state management
|
|
722
|
+
agent.ts createAgent, AgentHooks, state management
|
|
265
723
|
loop.ts turn execution loop
|
|
266
724
|
start.ts CLI entrypoint
|
|
267
725
|
auth.ts Anthropic OAuth flow
|
|
726
|
+
index.ts package exports
|
|
727
|
+
contexts/
|
|
728
|
+
types.ts ExecutionContext interface, capabilities
|
|
729
|
+
process.ts in-process context (default)
|
|
730
|
+
docker.ts Docker container context
|
|
731
|
+
sandbox.ts remote sandbox context
|
|
732
|
+
index.ts barrel exports
|
|
268
733
|
tools/
|
|
734
|
+
index.ts tool exports
|
|
269
735
|
validation.ts tool argument validation
|
|
736
|
+
shell.ts shell tool
|
|
737
|
+
read-file.ts read_file tool
|
|
738
|
+
write-file.ts write_file tool
|
|
739
|
+
list-files.ts list_files tool
|
|
740
|
+
spawn.ts spawn tool and createSpawnTool factory
|
|
270
741
|
providers/
|
|
271
742
|
index.ts Provider interface
|
|
272
743
|
openai-compat.ts shared OpenAI-compatible utilities
|
|
@@ -274,14 +745,31 @@ src/
|
|
|
274
745
|
openrouter.ts OpenRouter provider
|
|
275
746
|
cerebras.ts Cerebras provider
|
|
276
747
|
harnesses/
|
|
277
|
-
index.ts
|
|
278
|
-
basic.ts shell, read, write, list
|
|
748
|
+
index.ts HarnessConfig, defineHarness, ToolContext
|
|
749
|
+
basic.ts basic harness (shell, read, write, list, spawn)
|
|
750
|
+
mcp/
|
|
751
|
+
index.ts MCP server connection and tool discovery
|
|
752
|
+
session/
|
|
753
|
+
index.ts Session interface, createSession, loadSession
|
|
754
|
+
messages.ts SessionMessage converters (Anthropic/OpenAI)
|
|
755
|
+
memory.ts in-memory session store
|
|
756
|
+
sqlite.ts SQLite-backed session store
|
|
757
|
+
remote.ts HTTP remote session store
|
|
279
758
|
output/
|
|
280
759
|
terminal.ts terminal rendering (md4x)
|
|
281
760
|
test/
|
|
282
761
|
mock-provider.ts mock provider for testing
|
|
283
|
-
|
|
762
|
+
mock-context.ts mock execution context for testing
|
|
763
|
+
agent.test.ts agent loop tests
|
|
764
|
+
contexts.test.ts execution context tests
|
|
765
|
+
harness.test.ts harness tests
|
|
766
|
+
mcp.test.ts MCP connection and hook tests
|
|
767
|
+
spawn.test.ts spawn tool and hook tests
|
|
284
768
|
validation.test.ts validation tests
|
|
769
|
+
providers.test.ts provider tests
|
|
770
|
+
openai-compat.test.ts OpenAI-compat utility tests
|
|
771
|
+
session.test.ts session store and agent integration tests
|
|
772
|
+
session-messages.test.ts SessionMessage converter tests
|
|
285
773
|
```
|
|
286
774
|
|
|
287
775
|
## Testing
|
|
@@ -290,8 +778,9 @@ test/
|
|
|
290
778
|
bun test
|
|
291
779
|
```
|
|
292
780
|
|
|
293
|
-
|
|
781
|
+
300 tests with mock provider and mock execution context, no LLM calls or Docker needed.
|
|
294
782
|
|
|
295
783
|
## License
|
|
296
784
|
|
|
297
785
|
ISC
|
|
786
|
+
|