zidane 1.6.1 → 1.6.5

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
@@ -34,16 +34,16 @@ All options on `createAgent`:
34
34
  ```ts
35
35
  createAgent({
36
36
  provider, // required: LLM provider
37
+ session, // session for persistence
37
38
  harness: basic, // tool set (default: noTools)
38
39
  behavior: { // agent-level defaults
39
- toolExecution: 'sequential', // or 'parallel'
40
+ toolExecution: 'parallel', // or 'sequential' (default: parallel)
40
41
  maxTurns: 50, // max loop iterations
41
- maxTokens: 16384, // max tokens per LLM response
42
- thinkingBudget: 10240, // exact thinking token budget
42
+ maxTokens: 16384, // max tokens per LLM response
43
+ thinkingBudget: 10240, // exact thinking token budget
43
44
  },
44
45
  execution: createProcessContext(), // where tools run
45
46
  mcpServers: [], // MCP tool servers
46
- session, // session for persistence
47
47
  skills: {}, // skills configuration
48
48
  })
49
49
  ```
@@ -52,7 +52,7 @@ All options on `agent.run()`:
52
52
 
53
53
  ```ts
54
54
  await agent.run({
55
- prompt: 'your task', // required
55
+ prompt: 'your task', // optional when session has existing turns
56
56
  model: 'claude-opus-4-6',
57
57
  system: 'be concise',
58
58
  thinking: 'medium', // off | minimal | low | medium | high
@@ -67,6 +67,8 @@ await agent.run({
67
67
  })
68
68
  ```
69
69
 
70
+ `prompt` is optional when a session with existing turns is provided — the agent resumes from the last turn. This supports apps where the user message is persisted to the session before the agent runs (e.g. WebSocket → session → queue → agent).
71
+
70
72
  Precedence: `run.behavior` > `agent.behavior` > `harness.behavior` > hardcoded defaults.
71
73
 
72
74
  ## CLI
@@ -172,6 +174,8 @@ await agent.run({ prompt: 'solve this', thinking: 'high', behavior: { thinkingBu
172
174
  const agent = createAgent({ provider, harness, behavior: { thinkingBudget: 16384 } })
173
175
  ```
174
176
 
177
+ Thinking traces are stored in session turns as `{ type: 'thinking', text }` content blocks and streamed live via the `stream:thinking` hook. Supported by Anthropic (native) and OpenRouter/Cerebras (`reasoning_content`/`reasoning` SSE fields).
178
+
175
179
  ## Hooks
176
180
 
177
181
  Every hook receives a mutable context object.
@@ -205,13 +209,18 @@ agent.hooks.hook('agent:done', (ctx) => {
205
209
 
206
210
  ```ts
207
211
  agent.hooks.hook('stream:text', (ctx) => {
208
- // ctx.delta, ctx.text, ctx.turnId, ctx.blockIndex
212
+ // ctx.delta, ctx.text, ctx.turnId
209
213
  })
210
214
 
211
215
  agent.hooks.hook('stream:end', (ctx) => {
212
- // ctx.text (final), ctx.turnId, ctx.blockIndex
216
+ // ctx.text (final), ctx.turnId
213
217
  // Only fires when there is text content (not on tool-only turns)
214
218
  })
219
+
220
+ agent.hooks.hook('stream:thinking', (ctx) => {
221
+ // ctx.delta, ctx.thinking (accumulated), ctx.turnId
222
+ // Fires when the model streams reasoning traces (Anthropic, OpenRouter)
223
+ })
215
224
  ```
216
225
 
217
226
  ### Tool execution
@@ -297,6 +306,34 @@ const harness = defineHarness({
297
306
 
298
307
  Children inherit the parent's harness and can spawn their own children.
299
308
 
309
+ ## Interaction Tool
310
+
311
+ Let the agent pause and request structured input from the outside world. Not included in any harness by default.
312
+
313
+ ```ts
314
+ import { createInteractionTool, defineHarness, basicTools } from 'zidane'
315
+
316
+ const askUser = createInteractionTool({
317
+ name: 'ask_user',
318
+ schema: {
319
+ type: 'object',
320
+ properties: { question: { type: 'string' } },
321
+ required: ['question'],
322
+ },
323
+ onRequest: async (payload) => {
324
+ const answer = await promptUser(payload.question)
325
+ return { answer }
326
+ },
327
+ })
328
+
329
+ const harness = defineHarness({
330
+ name: 'interactive',
331
+ tools: { ...basicTools, ask_user: askUser },
332
+ })
333
+ ```
334
+
335
+ `onRequest` can be async — the agent waits for the response. Return a string or object (objects are JSON-stringified).
336
+
300
337
  ## Sessions
301
338
 
302
339
  Sessions give an agent persistent turn history and run metadata across calls.
@@ -552,7 +589,7 @@ import type { Agent, SessionTurn, TurnUsage, Provider, ToolDef } from 'zidane/ty
552
589
  bun test
553
590
  ```
554
591
 
555
- 460+ tests with mock provider and execution context. No API keys or Docker needed.
592
+ 484+ tests with mock provider and execution context. No API keys or Docker needed.
556
593
 
557
594
  ## License
558
595
 
@@ -83,7 +83,8 @@ interface SessionTurn {
83
83
  }
84
84
  interface AgentRunOptions {
85
85
  model?: string;
86
- prompt: string;
86
+ /** User prompt. Optional when resuming a session with existing turns. */
87
+ prompt?: string;
87
88
  system?: string;
88
89
  thinking?: ThinkingLevel;
89
90
  images?: ImageContent[];
@@ -150,7 +151,7 @@ declare function openrouter(params?: OpenRouterParams): Provider;
150
151
  interface ToolSpec {
151
152
  name: string;
152
153
  description: string;
153
- input_schema: Record<string, unknown>;
154
+ inputSchema: Record<string, unknown>;
154
155
  }
155
156
  interface ToolCall {
156
157
  id: string;
@@ -163,6 +164,7 @@ interface ToolResult {
163
164
  }
164
165
  interface StreamCallbacks {
165
166
  onText: (delta: string) => void;
167
+ onThinking?: (delta: string) => void;
166
168
  }
167
169
  interface TurnResult {
168
170
  /** Full assistant turn as a SessionMessage */
@@ -433,7 +435,7 @@ interface Session {
433
435
  /** Arbitrary metadata */
434
436
  readonly metadata: Record<string, unknown>;
435
437
  /** Start tracking a new run */
436
- startRun: (runId: string, prompt: string) => void;
438
+ startRun: (runId: string, prompt?: string) => void;
437
439
  /** Mark a run as completed */
438
440
  completeRun: (runId: string, stats: {
439
441
  turns: number;
@@ -507,12 +509,15 @@ interface AgentHooks {
507
509
  delta: string;
508
510
  text: string;
509
511
  turnId: string;
510
- blockIndex: number;
511
512
  }) => void;
512
513
  'stream:end': (ctx: {
513
514
  text: string;
514
515
  turnId: string;
515
- blockIndex: number;
516
+ }) => void;
517
+ 'stream:thinking': (ctx: {
518
+ delta: string;
519
+ thinking: string;
520
+ turnId: string;
516
521
  }) => void;
517
522
  'tool:before': (ctx: {
518
523
  name: string;
@@ -51,7 +51,7 @@ async function connectMcpServers(configs, _clientFactory, hooks) {
51
51
  spec: {
52
52
  name: namespacedName,
53
53
  description: tool.description || "",
54
- input_schema: tool.inputSchema ?? { type: "object", properties: {} }
54
+ inputSchema: tool.inputSchema ?? { type: "object", properties: {} }
55
55
  },
56
56
  execute: async (input, _ctx) => {
57
57
  await hooks?.callHook("mcp:tool:before", { server: config.name, tool: tool.name, input });
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  connectMcpServers
3
- } from "./chunk-YTZOORAP.js";
3
+ } from "./chunk-NNMWWSBY.js";
4
4
  import {
5
5
  buildCatalog,
6
6
  interpolateShellCommands,
@@ -8,12 +8,29 @@ import {
8
8
  resolveSkills
9
9
  } from "./chunk-4C6Y56CC.js";
10
10
 
11
+ // src/tools/interaction.ts
12
+ function createInteractionTool(options) {
13
+ const name = options.name ?? "interaction";
14
+ const description = options.description ?? "Request structured input from the user or external system.";
15
+ return {
16
+ spec: {
17
+ name,
18
+ description,
19
+ inputSchema: options.schema
20
+ },
21
+ async execute(input) {
22
+ const result = await options.onRequest(input);
23
+ return typeof result === "string" ? result : JSON.stringify(result);
24
+ }
25
+ };
26
+ }
27
+
11
28
  // src/tools/list-files.ts
12
29
  var listFiles = {
13
30
  spec: {
14
31
  name: "list_files",
15
32
  description: "List files and directories at the given path (relative to project root).",
16
- input_schema: {
33
+ inputSchema: {
17
34
  type: "object",
18
35
  properties: {
19
36
  path: { type: "string", description: 'Relative directory path (default: ".")' }
@@ -36,7 +53,7 @@ var readFile = {
36
53
  spec: {
37
54
  name: "read_file",
38
55
  description: "Read the contents of a file at the given path (relative to project root).",
39
- input_schema: {
56
+ inputSchema: {
40
57
  type: "object",
41
58
  properties: {
42
59
  path: { type: "string", description: "Relative file path" }
@@ -58,7 +75,7 @@ var shell = {
58
75
  spec: {
59
76
  name: "shell",
60
77
  description: "Execute a shell command and return stdout+stderr. Runs in the project root.",
61
- input_schema: {
78
+ inputSchema: {
62
79
  type: "object",
63
80
  properties: {
64
81
  command: { type: "string", description: "The shell command to run" }
@@ -403,7 +420,7 @@ async function executeTurn(ctx, turn) {
403
420
  await ctx.hooks.callHook("context:transform", { messages });
404
421
  await ctx.hooks.callHook("turn:before", { turn, turnId, options: streamOptions });
405
422
  let currentText = "";
406
- let blockIndex = 0;
423
+ let currentThinking = "";
407
424
  let result;
408
425
  try {
409
426
  result = await ctx.provider.stream(
@@ -411,26 +428,30 @@ async function executeTurn(ctx, turn) {
411
428
  {
412
429
  onText(delta) {
413
430
  currentText += delta;
414
- ctx.hooks.callHook("stream:text", { delta, text: currentText, turnId, blockIndex });
431
+ ctx.hooks.callHook("stream:text", { delta, text: currentText, turnId });
432
+ },
433
+ onThinking(delta) {
434
+ currentThinking += delta;
435
+ ctx.hooks.callHook("stream:thinking", { delta, thinking: currentThinking, turnId });
415
436
  }
416
437
  }
417
438
  );
418
439
  } catch (err) {
440
+ const errorUsage = { input: 0, output: 0 };
419
441
  const errorTurn = {
420
442
  id: turnId,
421
443
  runId: ctx.runId,
422
444
  role: "assistant",
423
445
  content: currentText ? [{ type: "text", text: currentText }] : [],
424
- usage: { input: 0, output: 0 },
446
+ usage: errorUsage,
425
447
  createdAt: Date.now()
426
448
  };
427
449
  ctx.turns.push(errorTurn);
428
- await ctx.hooks.callHook("turn:after", { turn, turnId, usage: errorTurn.usage, message: errorTurn });
450
+ await ctx.hooks.callHook("turn:after", { turn, turnId, usage: errorUsage, message: errorTurn });
429
451
  throw err;
430
452
  }
431
453
  if (currentText) {
432
- await ctx.hooks.callHook("stream:end", { text: currentText, turnId, blockIndex });
433
- blockIndex++;
454
+ await ctx.hooks.callHook("stream:end", { text: currentText, turnId });
434
455
  }
435
456
  const assistantTurn = {
436
457
  id: turnId,
@@ -447,7 +468,7 @@ async function executeTurn(ctx, turn) {
447
468
  const outputSpec = {
448
469
  name: "__output__",
449
470
  description: "Return the final structured output matching the required schema.",
450
- input_schema: ctx.schema
471
+ inputSchema: ctx.schema
451
472
  };
452
473
  const schemaResult = await ctx.provider.stream(
453
474
  {
@@ -503,16 +524,16 @@ async function executeSingleTool(ctx, call) {
503
524
  const gateCtx = { name: call.name, input: call.input, block: false, reason: "Tool execution was blocked" };
504
525
  await ctx.hooks.callHook("tool:gate", gateCtx);
505
526
  if (gateCtx.block) {
506
- return { result: { id: call.id, content: `Blocked: ${gateCtx.reason}` }, steered: false };
527
+ return { result: { id: call.id, content: `Blocked: ${gateCtx.reason}` } };
507
528
  }
508
529
  if (!toolDef) {
509
530
  const err = new Error(`Unknown tool: ${call.name}`);
510
531
  await ctx.hooks.callHook("tool:error", { name: call.name, input: call.input, error: err });
511
- return { result: { id: call.id, content: `Tool error: ${err.message}` }, steered: false };
532
+ return { result: { id: call.id, content: `Tool error: ${err.message}` } };
512
533
  }
513
- const validation = validateToolArgs(call.input, toolDef.spec.input_schema);
534
+ const validation = validateToolArgs(call.input, toolDef.spec.inputSchema);
514
535
  if (!validation.valid) {
515
- return { result: { id: call.id, content: `Validation error: ${validation.error}` }, steered: false };
536
+ return { result: { id: call.id, content: `Validation error: ${validation.error}` } };
516
537
  }
517
538
  await ctx.hooks.callHook("tool:before", { name: call.name, input: call.input });
518
539
  let output;
@@ -537,7 +558,7 @@ async function executeSingleTool(ctx, call) {
537
558
  output = transformCtx.result;
538
559
  isError = transformCtx.isError;
539
560
  await ctx.hooks.callHook("tool:after", { name: call.name, input: call.input, result: output });
540
- return { result: { id: call.id, content: output }, steered: false };
561
+ return { result: { id: call.id, content: output } };
541
562
  }
542
563
  async function executeToolsSequential(ctx, toolCalls) {
543
564
  const results = [];
@@ -583,7 +604,7 @@ async function executeToolsParallel(ctx, toolCalls) {
583
604
  var noTools = { name: "none", system: "You are a helpful assistant.", tools: {} };
584
605
  function resolveBehavior(harnessBehavior, agentBehavior, runBehavior) {
585
606
  return {
586
- toolExecution: runBehavior?.toolExecution ?? agentBehavior?.toolExecution ?? harnessBehavior?.toolExecution ?? "sequential",
607
+ toolExecution: runBehavior?.toolExecution ?? agentBehavior?.toolExecution ?? harnessBehavior?.toolExecution ?? "parallel",
587
608
  maxTurns: runBehavior?.maxTurns ?? agentBehavior?.maxTurns ?? harnessBehavior?.maxTurns,
588
609
  maxTokens: runBehavior?.maxTokens ?? agentBehavior?.maxTokens ?? harnessBehavior?.maxTokens,
589
610
  thinkingBudget: runBehavior?.thinkingBudget ?? agentBehavior?.thinkingBudget ?? harnessBehavior?.thinkingBudget,
@@ -614,13 +635,23 @@ function createAgent({ harness: harnessOption, provider, behavior: agentBehavior
614
635
  if (running) {
615
636
  throw new Error("Agent is already running. Use steer() or followUp() to queue messages, or waitForIdle().");
616
637
  }
638
+ const hasSessionTurns = session && session.turns.length > 0;
639
+ if (!options.prompt && !hasSessionTurns) {
640
+ throw new Error("prompt is required when no session with existing turns is provided");
641
+ }
642
+ if (!options.prompt && hasSessionTurns) {
643
+ const lastTurn = session.turns.at(-1);
644
+ if (lastTurn && lastTurn.role !== "user") {
645
+ throw new Error("cannot resume without prompt: last session turn must be a user message");
646
+ }
647
+ }
617
648
  running = true;
618
649
  abortController = new AbortController();
619
650
  const runId = `run_${++runCounter}`;
620
651
  session?.startRun(runId, options.prompt);
621
652
  if (session) {
622
653
  await session.updateStatus("running");
623
- await hooks.callHook("session:start", { sessionId: session.id, runId, prompt: options.prompt });
654
+ await hooks.callHook("session:start", { sessionId: session.id, runId, prompt: options.prompt ?? "" });
624
655
  }
625
656
  if (options.signal) {
626
657
  if (options.signal.aborted) {
@@ -698,12 +729,12 @@ ${skillsCatalog}`;
698
729
  (t) => ({
699
730
  name: t.spec.name,
700
731
  description: t.spec.description || "",
701
- input_schema: t.spec.input_schema
732
+ inputSchema: t.spec.inputSchema
702
733
  })
703
734
  );
704
735
  const formattedTools = toolSpecs.length > 0 ? provider.formatTools(toolSpecs) : [];
705
736
  const turns = [];
706
- const isResume = session && session.turns.length > 0 && session.runs.length > 0;
737
+ const isResume = session && session.turns.length > 0 && (session.runs.length > 0 || !options.prompt);
707
738
  if (isResume) {
708
739
  turns.push(...session.turns);
709
740
  }
@@ -727,14 +758,16 @@ ${skillsCatalog}`;
727
758
  createdAt: Date.now()
728
759
  });
729
760
  }
730
- const promptMsg = provider.userMessage(options.prompt, options.images);
731
- turns.push({
732
- id: crypto.randomUUID(),
733
- runId,
734
- role: promptMsg.role,
735
- content: promptMsg.content,
736
- createdAt: Date.now()
737
- });
761
+ if (options.prompt) {
762
+ const promptMsg = provider.userMessage(options.prompt, options.images);
763
+ turns.push({
764
+ id: crypto.randomUUID(),
765
+ runId,
766
+ role: promptMsg.role,
767
+ content: promptMsg.content,
768
+ createdAt: Date.now()
769
+ });
770
+ }
738
771
  conversationTurns = turns;
739
772
  let lastPersistedTurnCount = isResume ? session.turns.length : 0;
740
773
  const unregisterSessionSync = session ? hooks.hook("turn:after", async () => {
@@ -945,7 +978,7 @@ function createSpawnTool(options = {}) {
945
978
  spec: {
946
979
  name: "spawn",
947
980
  description: "Spawn a sub-agent to work on a specific task. The sub-agent runs independently with its own tool access and returns its final response. Use this to delegate work, parallelize tasks, or isolate concerns.",
948
- input_schema: {
981
+ inputSchema: {
949
982
  type: "object",
950
983
  properties: {
951
984
  task: {
@@ -1020,7 +1053,7 @@ var writeFile2 = {
1020
1053
  spec: {
1021
1054
  name: "write_file",
1022
1055
  description: "Write content to a file. Creates parent directories if needed.",
1023
- input_schema: {
1056
+ inputSchema: {
1024
1057
  type: "object",
1025
1058
  properties: {
1026
1059
  path: { type: "string", description: "Relative file path" },
@@ -1041,6 +1074,7 @@ export {
1041
1074
  createSandboxContext,
1042
1075
  validateToolArgs,
1043
1076
  createAgent,
1077
+ createInteractionTool,
1044
1078
  listFiles,
1045
1079
  readFile,
1046
1080
  shell,
@@ -4,7 +4,7 @@ import {
4
4
  shell,
5
5
  spawn,
6
6
  writeFile
7
- } from "./chunk-SKLAWY7O.js";
7
+ } from "./chunk-R7PIZ4MU.js";
8
8
 
9
9
  // src/harnesses/basic.ts
10
10
  var basicTools = { shell, readFile, writeFile, listFiles };
@@ -6,6 +6,7 @@ async function consumeSSE(response, callbacks, signal) {
6
6
  const decoder = new TextDecoder();
7
7
  let buffer = "";
8
8
  let text = "";
9
+ let thinking = "";
9
10
  let finishReason = "stop";
10
11
  let usage = { input: 0, output: 0 };
11
12
  const tcMap = /* @__PURE__ */ new Map();
@@ -36,6 +37,11 @@ async function consumeSSE(response, callbacks, signal) {
36
37
  continue;
37
38
  if (choice.finish_reason)
38
39
  finishReason = choice.finish_reason;
40
+ const thinkingDelta = choice.delta?.reasoning_content ?? choice.delta?.reasoning;
41
+ if (thinkingDelta) {
42
+ thinking += thinkingDelta;
43
+ callbacks.onThinking?.(thinkingDelta);
44
+ }
39
45
  if (choice.delta?.content) {
40
46
  text += choice.delta.content;
41
47
  callbacks.onText(choice.delta.content);
@@ -72,7 +78,7 @@ async function consumeSSE(response, callbacks, signal) {
72
78
  name: tc.name,
73
79
  input: tc.args ? JSON.parse(tc.args) : {}
74
80
  }));
75
- return { text, toolCalls, finishReason, usage };
81
+ return { text, thinking, toolCalls, finishReason, usage };
76
82
  }
77
83
  function toOAIMessages(system, messages) {
78
84
  const out = [{ role: "system", content: system }];
@@ -124,7 +130,7 @@ function toOAIMessages(system, messages) {
124
130
  function formatTools(tools) {
125
131
  return tools.map((t) => ({
126
132
  type: "function",
127
- function: { name: t.name, description: t.description, parameters: t.input_schema }
133
+ function: { name: t.name, description: t.description, parameters: t.inputSchema }
128
134
  }));
129
135
  }
130
136
  function userMessage(content, images) {
@@ -156,8 +162,10 @@ function toolResultsMessage(results) {
156
162
  }))
157
163
  };
158
164
  }
159
- function buildAssistantContent(text, toolCalls) {
165
+ function buildAssistantContent(text, toolCalls, thinking) {
160
166
  const content = [];
167
+ if (thinking)
168
+ content.push({ type: "thinking", text: thinking });
161
169
  if (text)
162
170
  content.push({ type: "text", text });
163
171
  for (const tc of toolCalls) {
@@ -307,7 +307,7 @@ async function createSession(options = {}) {
307
307
  data.runs.push({
308
308
  id: runId,
309
309
  startedAt: Date.now(),
310
- prompt,
310
+ prompt: prompt ?? "",
311
311
  status: "running"
312
312
  });
313
313
  touch();
@@ -1,4 +1,4 @@
1
1
  import 'hookable';
2
- export { H as Harness, i as HarnessConfig, u as ToolContext, v as ToolDef, x as ToolMap, a1 as basic, a2 as basicTools, Q as defineHarness, X as noTools } from './agent-C7X6yygt.js';
2
+ export { H as Harness, i as HarnessConfig, u as ToolContext, v as ToolDef, x as ToolMap, a1 as basic, a2 as basicTools, Q as defineHarness, X as noTools } from './agent-BDZbObgy.js';
3
3
  import './types-CKXAp41h.js';
4
4
  import '@modelcontextprotocol/sdk/client/index.js';
package/dist/harnesses.js CHANGED
@@ -3,9 +3,9 @@ import {
3
3
  basic_default,
4
4
  defineHarness,
5
5
  noTools
6
- } from "./chunk-JR4QNFMD.js";
7
- import "./chunk-SKLAWY7O.js";
8
- import "./chunk-YTZOORAP.js";
6
+ } from "./chunk-TWRNLI6I.js";
7
+ import "./chunk-R7PIZ4MU.js";
8
+ import "./chunk-NNMWWSBY.js";
9
9
  import "./chunk-4C6Y56CC.js";
10
10
  export {
11
11
  basic_default as basic,
package/dist/index.d.ts CHANGED
@@ -1,9 +1,9 @@
1
- export { A as Agent, a as AgentBehavior, b as AgentHooks, c as AgentOptions, d as AgentRunOptions, e as AgentStats, h as CreateSessionOptions, H as Harness, i as HarnessConfig, I as ImageContent, M as McpConnection, j as McpServerConfig, R as RemoteStoreOptions, S as Session, k as SessionContentBlock, l as SessionData, m as SessionMessage, n as SessionRun, o as SessionStore, p as SessionTurn, q as SqliteStoreOptions, T as ThinkingLevel, u as ToolContext, v as ToolDef, w as ToolExecutionMode, x as ToolMap, D as TurnUsage, E as autoDetectAndConvert, F as connectMcpServers, G as createAgent, J as createMemoryStore, K as createRemoteStore, L as createSession, N as createSqliteStore, Q as defineHarness, U as fromAnthropic, V as fromOpenAI, W as loadSession, X as noTools, Y as toAnthropic, Z as toOpenAI } from './agent-C7X6yygt.js';
1
+ export { A as Agent, a as AgentBehavior, b as AgentHooks, c as AgentOptions, d as AgentRunOptions, e as AgentStats, h as CreateSessionOptions, H as Harness, i as HarnessConfig, I as ImageContent, M as McpConnection, j as McpServerConfig, R as RemoteStoreOptions, S as Session, k as SessionContentBlock, l as SessionData, m as SessionMessage, n as SessionRun, o as SessionStore, p as SessionTurn, q as SqliteStoreOptions, T as ThinkingLevel, u as ToolContext, v as ToolDef, w as ToolExecutionMode, x as ToolMap, D as TurnUsage, E as autoDetectAndConvert, F as connectMcpServers, G as createAgent, J as createMemoryStore, K as createRemoteStore, L as createSession, N as createSqliteStore, Q as defineHarness, U as fromAnthropic, V as fromOpenAI, W as loadSession, X as noTools, Y as toAnthropic, Z as toOpenAI } from './agent-BDZbObgy.js';
2
2
  import { f as SpawnConfig, b as ExecutionContext } from './types-CKXAp41h.js';
3
3
  export { C as ContextCapabilities, a as ContextType, E as ExecResult, c as ExecutionHandle, S as SkillConfig, d as SkillResource, e as SkillsConfig } from './types-CKXAp41h.js';
4
4
  export { S as SandboxProvider, c as createSandboxContext } from './sandbox-DZn3ybp_.js';
5
5
  export { buildCatalog, defineSkill, discoverSkills, interpolateShellCommands, mergeSkillsConfig, parseSkillFile, resolveSkills, validateSkillName, writeSkillToDisk, writeSkillsToDisk } from './skills.js';
6
- export { C as ChildAgent, S as SpawnToolOptions, a as SpawnToolState, c as createSpawnTool, s as spawn } from './spawn-CIxEFPrs.js';
6
+ export { C as ChildAgent, I as InteractionToolOptions, S as SpawnToolOptions, a as SpawnToolState, c as createInteractionTool, b as createSpawnTool, s as spawn } from './spawn-CQieNB1Y.js';
7
7
  import 'hookable';
8
8
  import '@modelcontextprotocol/sdk/client/index.js';
9
9
 
@@ -30,7 +30,7 @@ declare function createProcessContext(config?: SpawnConfig): ExecutionContext;
30
30
  /**
31
31
  * Zod v4 integration helper.
32
32
  *
33
- * Normalizes the output of z.toJsonSchema() for use as ToolSpec.input_schema.
33
+ * Normalizes the output of z.toJsonSchema() for use as ToolSpec.inputSchema.
34
34
  * Zod is an optional peer dependency — consumers call z.toJsonSchema() themselves.
35
35
  *
36
36
  * Usage:
@@ -40,7 +40,7 @@ declare function createProcessContext(config?: SpawnConfig): ExecutionContext;
40
40
  */
41
41
  /**
42
42
  * Normalize a JSON Schema (e.g. from zod v4's z.toJsonSchema()) for use
43
- * as a ToolSpec.input_schema.
43
+ * as a ToolSpec.inputSchema.
44
44
  *
45
45
  * Strips the $schema key that some providers reject.
46
46
  */
package/dist/index.js CHANGED
@@ -1,32 +1,33 @@
1
1
  import {
2
2
  defineHarness,
3
3
  noTools
4
- } from "./chunk-JR4QNFMD.js";
4
+ } from "./chunk-TWRNLI6I.js";
5
5
  import {
6
6
  createAgent,
7
7
  createDockerContext,
8
+ createInteractionTool,
8
9
  createProcessContext,
9
10
  createSandboxContext,
10
11
  createSpawnTool,
11
12
  spawn
12
- } from "./chunk-SKLAWY7O.js";
13
+ } from "./chunk-R7PIZ4MU.js";
13
14
  import {
14
15
  connectMcpServers
15
- } from "./chunk-YTZOORAP.js";
16
+ } from "./chunk-NNMWWSBY.js";
16
17
  import {
17
18
  createMemoryStore,
18
19
  createRemoteStore,
19
20
  createSession,
20
21
  createSqliteStore,
21
22
  loadSession
22
- } from "./chunk-XT7QBZ47.js";
23
+ } from "./chunk-V4PBD7LE.js";
23
24
  import {
24
25
  autoDetectAndConvert,
25
26
  fromAnthropic,
26
27
  fromOpenAI,
27
28
  toAnthropic,
28
29
  toOpenAI
29
- } from "./chunk-LS57GDAV.js";
30
+ } from "./chunk-UUITMJ7G.js";
30
31
  import {
31
32
  defineSkill
32
33
  } from "./chunk-CFLC2I7D.js";
@@ -53,6 +54,7 @@ export {
53
54
  connectMcpServers,
54
55
  createAgent,
55
56
  createDockerContext,
57
+ createInteractionTool,
56
58
  createMemoryStore,
57
59
  createProcessContext,
58
60
  createRemoteStore,
package/dist/mcp.d.ts CHANGED
@@ -1,4 +1,4 @@
1
1
  import 'hookable';
2
- export { M as McpConnection, j as McpServerConfig, F as connectMcpServers, a3 as resultToString } from './agent-C7X6yygt.js';
2
+ export { M as McpConnection, j as McpServerConfig, F as connectMcpServers, a3 as resultToString } from './agent-BDZbObgy.js';
3
3
  import '@modelcontextprotocol/sdk/client/index.js';
4
4
  import './types-CKXAp41h.js';
package/dist/mcp.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  connectMcpServers,
3
3
  resultToString
4
- } from "./chunk-YTZOORAP.js";
4
+ } from "./chunk-NNMWWSBY.js";
5
5
  export {
6
6
  connectMcpServers,
7
7
  resultToString
@@ -1,4 +1,4 @@
1
- export { f as AnthropicParams, C as CerebrasParams, O as OpenRouterParams, P as Provider, r as StreamCallbacks, s as StreamOptions, t as ToolCall, y as ToolResult, z as ToolSpec, B as TurnResult, _ as anthropic, $ as cerebras, a0 as openrouter } from './agent-C7X6yygt.js';
1
+ export { f as AnthropicParams, C as CerebrasParams, O as OpenRouterParams, P as Provider, r as StreamCallbacks, s as StreamOptions, t as ToolCall, y as ToolResult, z as ToolSpec, B as TurnResult, _ as anthropic, $ as cerebras, a0 as openrouter } from './agent-BDZbObgy.js';
2
2
  import 'hookable';
3
3
  import './types-CKXAp41h.js';
4
4
  import '@modelcontextprotocol/sdk/client/index.js';
package/dist/providers.js CHANGED
@@ -8,7 +8,7 @@ import {
8
8
  toOAIMessages,
9
9
  toolResultsMessage,
10
10
  userMessage
11
- } from "./chunk-LS57GDAV.js";
11
+ } from "./chunk-UUITMJ7G.js";
12
12
 
13
13
  // src/providers/anthropic.ts
14
14
  import { existsSync, readFileSync } from "fs";
@@ -59,7 +59,7 @@ function anthropic(anthropicParams) {
59
59
  return tools.map((t) => ({
60
60
  name: t.name,
61
61
  description: t.description,
62
- input_schema: t.input_schema
62
+ input_schema: t.inputSchema
63
63
  }));
64
64
  },
65
65
  userMessage(content, images) {
@@ -127,6 +127,11 @@ function anthropic(anthropicParams) {
127
127
  text += delta;
128
128
  callbacks.onText(delta);
129
129
  });
130
+ if (callbacks.onThinking) {
131
+ s.on("thinking", (delta) => {
132
+ callbacks.onThinking(delta);
133
+ });
134
+ }
130
135
  const response = await s.finalMessage();
131
136
  const toolCalls = response.content.filter((b) => b.type === "tool_use").map((b) => ({ id: b.id, name: b.name, input: b.input }));
132
137
  return {
@@ -198,7 +203,7 @@ function cerebras(params) {
198
203
  }
199
204
  const result = await consumeSSE(response, callbacks, options.signal);
200
205
  return {
201
- assistantMessage: buildAssistantContent(result.text, result.toolCalls),
206
+ assistantMessage: buildAssistantContent(result.text, result.toolCalls, result.thinking),
202
207
  text: result.text,
203
208
  toolCalls: result.toolCalls,
204
209
  done: result.finishReason === "stop" || result.toolCalls.length === 0,
@@ -266,7 +271,7 @@ function openrouter(params) {
266
271
  }
267
272
  const result = await consumeSSE(response, callbacks, options.signal);
268
273
  return {
269
- assistantMessage: buildAssistantContent(result.text, result.toolCalls),
274
+ assistantMessage: buildAssistantContent(result.text, result.toolCalls, result.thinking),
270
275
  text: result.text,
271
276
  toolCalls: result.toolCalls,
272
277
  done: result.finishReason === "stop" || result.toolCalls.length === 0,
package/dist/session.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export { h as CreateSessionOptions, R as RemoteStoreOptions, S as Session, k as SessionContentBlock, l as SessionData, m as SessionMessage, n as SessionRun, o as SessionStore, p as SessionTurn, q as SqliteStoreOptions, E as autoDetectAndConvert, J as createMemoryStore, K as createRemoteStore, L as createSession, N as createSqliteStore, U as fromAnthropic, V as fromOpenAI, W as loadSession, Y as toAnthropic, Z as toOpenAI } from './agent-C7X6yygt.js';
1
+ export { h as CreateSessionOptions, R as RemoteStoreOptions, S as Session, k as SessionContentBlock, l as SessionData, m as SessionMessage, n as SessionRun, o as SessionStore, p as SessionTurn, q as SqliteStoreOptions, E as autoDetectAndConvert, J as createMemoryStore, K as createRemoteStore, L as createSession, N as createSqliteStore, U as fromAnthropic, V as fromOpenAI, W as loadSession, Y as toAnthropic, Z as toOpenAI } from './agent-BDZbObgy.js';
2
2
  import 'hookable';
3
3
  import './types-CKXAp41h.js';
4
4
  import '@modelcontextprotocol/sdk/client/index.js';
package/dist/session.js CHANGED
@@ -4,14 +4,14 @@ import {
4
4
  createSession,
5
5
  createSqliteStore,
6
6
  loadSession
7
- } from "./chunk-XT7QBZ47.js";
7
+ } from "./chunk-V4PBD7LE.js";
8
8
  import {
9
9
  autoDetectAndConvert,
10
10
  fromAnthropic,
11
11
  fromOpenAI,
12
12
  toAnthropic,
13
13
  toOpenAI
14
- } from "./chunk-LS57GDAV.js";
14
+ } from "./chunk-UUITMJ7G.js";
15
15
  export {
16
16
  autoDetectAndConvert,
17
17
  createMemoryStore,
@@ -1,4 +1,41 @@
1
- import { i as HarnessConfig, e as AgentStats, v as ToolDef } from './agent-C7X6yygt.js';
1
+ import { v as ToolDef, i as HarnessConfig, e as AgentStats } from './agent-BDZbObgy.js';
2
+
3
+ /**
4
+ * Interaction tool — lets the agent request structured input from the outside world.
5
+ *
6
+ * Not included in any harness by default. Add it explicitly:
7
+ *
8
+ * import { createInteractionTool } from 'zidane'
9
+ *
10
+ * const askUser = createInteractionTool({
11
+ * schema: { type: 'object', properties: { question: { type: 'string' } }, required: ['question'] },
12
+ * onRequest: async (payload) => {
13
+ * const answer = await promptUser(payload.question)
14
+ * return { answer }
15
+ * },
16
+ * })
17
+ *
18
+ * const harness = defineHarness({ name: 'interactive', tools: { ...basicTools, ask_user: askUser } })
19
+ */
20
+
21
+ interface InteractionToolOptions {
22
+ /** JSON Schema for the request payload the model sends */
23
+ schema: Record<string, unknown>;
24
+ /** Tool name (default: 'interaction') */
25
+ name?: string;
26
+ /** Tool description shown to the model */
27
+ description?: string;
28
+ /** Called when the model invokes this tool. Receives the validated payload, returns data for the model. */
29
+ onRequest: (payload: Record<string, unknown>) => Promise<Record<string, unknown> | string>;
30
+ }
31
+ /**
32
+ * Create an interaction tool that lets the agent request structured input.
33
+ *
34
+ * The model calls this tool with a payload matching the schema.
35
+ * `onRequest` is called with the payload and should return the response
36
+ * (string or object) that gets sent back to the model as the tool result.
37
+ */
38
+ declare function createInteractionTool(options: InteractionToolOptions): ToolDef;
2
39
 
3
40
  /**
4
41
  * Spawn tool — create sub-agents from a parent agent.
@@ -59,4 +96,4 @@ declare function createSpawnTool(options?: SpawnToolOptions): ToolDef & SpawnToo
59
96
  */
60
97
  declare const spawn: ToolDef & SpawnToolState;
61
98
 
62
- export { type ChildAgent as C, type SpawnToolOptions as S, type SpawnToolState as a, createSpawnTool as c, spawn as s };
99
+ export { type ChildAgent as C, type InteractionToolOptions as I, type SpawnToolOptions as S, type SpawnToolState as a, createSpawnTool as b, createInteractionTool as c, spawn as s };
package/dist/tools.d.ts CHANGED
@@ -1,6 +1,6 @@
1
- import { v as ToolDef } from './agent-C7X6yygt.js';
2
- export { C as ChildAgent, S as SpawnToolOptions, a as SpawnToolState, c as createSpawnTool, s as spawn } from './spawn-CIxEFPrs.js';
3
- export { V as ValidationResult, v as validateToolArgs } from './validation-CwSuvOKf.js';
1
+ export { C as ChildAgent, I as InteractionToolOptions, S as SpawnToolOptions, a as SpawnToolState, c as createInteractionTool, b as createSpawnTool, s as spawn } from './spawn-CQieNB1Y.js';
2
+ import { v as ToolDef } from './agent-BDZbObgy.js';
3
+ export { V as ValidationResult, v as validateToolArgs } from './validation-DOY_k7lW.js';
4
4
  import 'hookable';
5
5
  import './types-CKXAp41h.js';
6
6
  import '@modelcontextprotocol/sdk/client/index.js';
package/dist/tools.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import {
2
+ createInteractionTool,
2
3
  createSpawnTool,
3
4
  listFiles,
4
5
  readFile,
@@ -6,10 +7,11 @@ import {
6
7
  spawn,
7
8
  validateToolArgs,
8
9
  writeFile
9
- } from "./chunk-SKLAWY7O.js";
10
- import "./chunk-YTZOORAP.js";
10
+ } from "./chunk-R7PIZ4MU.js";
11
+ import "./chunk-NNMWWSBY.js";
11
12
  import "./chunk-4C6Y56CC.js";
12
13
  export {
14
+ createInteractionTool,
13
15
  createSpawnTool,
14
16
  listFiles,
15
17
  readFile,
package/dist/types.d.ts CHANGED
@@ -1,7 +1,7 @@
1
- export { A as Agent, a as AgentBehavior, b as AgentHooks, c as AgentOptions, d as AgentRunOptions, e as AgentStats, f as AnthropicParams, C as CerebrasParams, g as ChildRunStats, h as CreateSessionOptions, H as Harness, i as HarnessConfig, I as ImageContent, M as McpConnection, j as McpServerConfig, O as OpenRouterParams, P as Provider, R as RemoteStoreOptions, S as Session, k as SessionContentBlock, l as SessionData, m as SessionMessage, n as SessionRun, o as SessionStore, p as SessionTurn, q as SqliteStoreOptions, r as StreamCallbacks, s as StreamOptions, T as ThinkingLevel, t as ToolCall, u as ToolContext, v as ToolDef, w as ToolExecutionMode, x as ToolMap, y as ToolResult, z as ToolSpec, B as TurnResult, D as TurnUsage } from './agent-C7X6yygt.js';
1
+ export { A as Agent, a as AgentBehavior, b as AgentHooks, c as AgentOptions, d as AgentRunOptions, e as AgentStats, f as AnthropicParams, C as CerebrasParams, g as ChildRunStats, h as CreateSessionOptions, H as Harness, i as HarnessConfig, I as ImageContent, M as McpConnection, j as McpServerConfig, O as OpenRouterParams, P as Provider, R as RemoteStoreOptions, S as Session, k as SessionContentBlock, l as SessionData, m as SessionMessage, n as SessionRun, o as SessionStore, p as SessionTurn, q as SqliteStoreOptions, r as StreamCallbacks, s as StreamOptions, T as ThinkingLevel, t as ToolCall, u as ToolContext, v as ToolDef, w as ToolExecutionMode, x as ToolMap, y as ToolResult, z as ToolSpec, B as TurnResult, D as TurnUsage } from './agent-BDZbObgy.js';
2
2
  export { C as ContextCapabilities, a as ContextType, E as ExecResult, b as ExecutionContext, c as ExecutionHandle, S as SkillConfig, d as SkillResource, e as SkillsConfig, f as SpawnConfig } from './types-CKXAp41h.js';
3
3
  export { S as SandboxProvider } from './sandbox-DZn3ybp_.js';
4
- export { C as ChildAgent, S as SpawnToolOptions, a as SpawnToolState } from './spawn-CIxEFPrs.js';
5
- export { V as ValidationResult } from './validation-CwSuvOKf.js';
4
+ export { C as ChildAgent, I as InteractionToolOptions, S as SpawnToolOptions, a as SpawnToolState } from './spawn-CQieNB1Y.js';
5
+ export { V as ValidationResult } from './validation-DOY_k7lW.js';
6
6
  import 'hookable';
7
7
  import '@modelcontextprotocol/sdk/client/index.js';
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Simple tool argument validation against JSON Schema-style input_schema.
2
+ * Simple tool argument validation against JSON Schema-style inputSchema.
3
3
  * Checks required fields are present; type coercion is left to the tools themselves.
4
4
  */
5
5
  interface ValidationResult {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zidane",
3
- "version": "1.6.1",
3
+ "version": "1.6.5",
4
4
  "description": "an agent that goes straight to the goal",
5
5
  "type": "module",
6
6
  "private": false,
@@ -59,7 +59,8 @@
59
59
  "lint": "eslint .",
60
60
  "lint:fix": "eslint . --fix",
61
61
  "test": "bun test",
62
- "release": "bumpp"
62
+ "release": "bumpp",
63
+ "typecheck": "tsc --noEmit"
63
64
  },
64
65
  "dependencies": {
65
66
  "@anthropic-ai/sdk": "^0.80.0",
@@ -84,6 +85,7 @@
84
85
  "bumpp": "^11.0.1",
85
86
  "eslint": "^10.0.3",
86
87
  "jiti": "^2.6.1",
87
- "tsup": "^8.5.1"
88
+ "tsup": "^8.5.1",
89
+ "typescript": "~5.9.3"
88
90
  }
89
91
  }