bashkit 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/AGENTS.md CHANGED
@@ -24,7 +24,7 @@ Runs commands directly on the local machine. Use for development/testing only.
24
24
  import { createAgentTools, LocalSandbox } from "bashkit";
25
25
 
26
26
  const sandbox = new LocalSandbox("/tmp/workspace");
27
- const tools = createAgentTools(sandbox);
27
+ const { tools } = createAgentTools(sandbox);
28
28
  ```
29
29
 
30
30
  ### VercelSandbox (Production)
@@ -38,7 +38,7 @@ const sandbox = new VercelSandbox({
38
38
  runtime: "node22",
39
39
  resources: { vcpus: 2 },
40
40
  });
41
- const tools = createAgentTools(sandbox);
41
+ const { tools } = createAgentTools(sandbox);
42
42
 
43
43
  // Don't forget to cleanup
44
44
  await sandbox.destroy();
@@ -46,7 +46,7 @@ await sandbox.destroy();
46
46
 
47
47
  ## Available Tools
48
48
 
49
- ### Sandbox-based Tools (from createAgentTools)
49
+ ### Default Tools (always included)
50
50
 
51
51
  | Tool | Purpose | Key Inputs |
52
52
  |------|---------|------------|
@@ -57,13 +57,23 @@ await sandbox.destroy();
57
57
  | `Glob` | Find files by pattern | `pattern`, `path?` |
58
58
  | `Grep` | Search file contents | `pattern`, `path?`, `output_mode?`, `-i?`, `-C?` |
59
59
 
60
+ ### Optional Tools (via config)
61
+
62
+ | Tool | Purpose | Config Key |
63
+ |------|---------|------------|
64
+ | `AskUser` | Ask user clarifying questions | `askUser: { onQuestion? }` |
65
+ | `EnterPlanMode` | Enter planning/exploration mode | `planMode: true` |
66
+ | `ExitPlanMode` | Exit planning mode with a plan | `planMode: true` |
67
+ | `Skill` | Execute skills | `skill: { skills }` |
68
+ | `WebSearch` | Search the web | `webSearch: { apiKey }` |
69
+ | `WebFetch` | Fetch URL and process with AI | `webFetch: { apiKey, model }` |
70
+
60
71
  ### Workflow Tools (created separately)
61
72
 
62
73
  | Tool | Purpose | Factory |
63
74
  |------|---------|---------|
64
75
  | `Task` | Spawn sub-agents | `createTaskTool({ model, tools, subagentTypes? })` |
65
76
  | `TodoWrite` | Track task progress | `createTodoWriteTool(state, config?, onUpdate?)` |
66
- | `ExitPlanMode` | Exit planning mode | `createExitPlanModeTool(config?, onPlanSubmit?)` |
67
77
 
68
78
  ### Web Tools (require `parallel-web` peer dependency)
69
79
 
@@ -84,7 +94,7 @@ import {
84
94
  } from "bashkit";
85
95
 
86
96
  const sandbox = new LocalSandbox("/tmp/workspace");
87
- const tools = createAgentTools(sandbox);
97
+ const { tools } = createAgentTools(sandbox);
88
98
 
89
99
  // Wrap model with prompt caching (recommended)
90
100
  const model = wrapLanguageModel({
@@ -112,7 +122,7 @@ await sandbox.destroy();
112
122
 
113
123
  ## Sub-agents with Task Tool
114
124
 
115
- The Task tool spawns new `generateText` calls for complex subtasks:
125
+ The Task tool spawns new agents for complex subtasks:
116
126
 
117
127
  ```typescript
118
128
  import { createTaskTool } from "bashkit";
@@ -147,6 +157,40 @@ The parent agent calls Task like any other tool:
147
157
  }}
148
158
  ```
149
159
 
160
+ ### Streaming Sub-agent Activity to UI
161
+
162
+ Pass a `streamWriter` to stream real-time sub-agent activity:
163
+
164
+ ```typescript
165
+ import { createUIMessageStream } from "ai";
166
+
167
+ const stream = createUIMessageStream({
168
+ execute: async ({ writer }) => {
169
+ const taskTool = createTaskTool({
170
+ model,
171
+ tools: sandboxTools,
172
+ streamWriter: writer, // Enable real-time streaming
173
+ subagentTypes: { ... },
174
+ });
175
+
176
+ const result = streamText({
177
+ model,
178
+ tools: { Task: taskTool },
179
+ ...
180
+ });
181
+
182
+ writer.merge(result.toUIMessageStream());
183
+ },
184
+ });
185
+ ```
186
+
187
+ When `streamWriter` is provided:
188
+ - Uses `streamText` internally (instead of `generateText`)
189
+ - Emits `data-subagent` events: `start`, `tool-call`, `done`, `complete`
190
+ - Events appear in `message.parts` as `{ type: "data-subagent", data: SubagentEventData }`
191
+
192
+ **Note:** TaskOutput does NOT include messages (to avoid context bloat). The UI accesses the full conversation via the streamed `complete` event.
193
+
150
194
  ## Prompt Caching
151
195
 
152
196
  Enable Anthropic prompt caching to reduce costs on repeated prefixes:
@@ -273,7 +317,7 @@ import { anthropic } from "@ai-sdk/anthropic";
273
317
 
274
318
  const skills = await discoverSkills();
275
319
  const sandbox = new LocalSandbox("/tmp/workspace");
276
- const tools = createAgentTools(sandbox);
320
+ const { tools } = createAgentTools(sandbox);
277
321
 
278
322
  const result = await generateText({
279
323
  model: anthropic("claude-sonnet-4-20250514"),
@@ -363,7 +407,7 @@ const systemPrompt = `Save notes to: ${config.workspace.notes}
363
407
  ${skillsToXml(skills)}
364
408
  `;
365
409
 
366
- const tools = createAgentTools(sandbox);
410
+ const { tools } = createAgentTools(sandbox);
367
411
  ```
368
412
 
369
413
  ## Common Patterns
@@ -386,7 +430,7 @@ import {
386
430
  const sandbox = new LocalSandbox("/tmp/workspace");
387
431
 
388
432
  // 2. Create sandbox tools
389
- const sandboxTools = createAgentTools(sandbox);
433
+ const { tools: sandboxTools } = createAgentTools(sandbox);
390
434
 
391
435
  // 3. Create model with caching
392
436
  const model = wrapLanguageModel({
@@ -424,7 +468,7 @@ await sandbox.destroy();
424
468
  Restrict tools with configuration:
425
469
 
426
470
  ```typescript
427
- const tools = createAgentTools(sandbox, {
471
+ const { tools } = createAgentTools(sandbox, {
428
472
  tools: {
429
473
  Bash: {
430
474
  enabled: true,
package/README.md CHANGED
@@ -43,7 +43,7 @@ import { generateText, stepCountIs } from 'ai';
43
43
  const sandbox = createLocalSandbox({ cwd: '/tmp/workspace' });
44
44
 
45
45
  // Create tools bound to the sandbox
46
- const tools = createAgentTools(sandbox);
46
+ const { tools } = createAgentTools(sandbox);
47
47
 
48
48
  // Use with Vercel AI SDK
49
49
  const result = await generateText({
@@ -74,7 +74,7 @@ const sandbox = createVercelSandbox({
74
74
  resources: { vcpus: 2 },
75
75
  });
76
76
 
77
- const tools = createAgentTools(sandbox);
77
+ const { tools } = createAgentTools(sandbox);
78
78
 
79
79
  const result = streamText({
80
80
  model: anthropic('claude-sonnet-4-5'),
@@ -89,7 +89,7 @@ await sandbox.destroy();
89
89
 
90
90
  ## Available Tools
91
91
 
92
- ### Sandbox-based Tools (from `createAgentTools`)
92
+ ### Default Tools (always included)
93
93
 
94
94
  | Tool | Purpose | Key Inputs |
95
95
  |------|---------|------------|
@@ -100,13 +100,23 @@ await sandbox.destroy();
100
100
  | `Glob` | Find files by pattern | `pattern`, `path?` |
101
101
  | `Grep` | Search file contents | `pattern`, `path?`, `output_mode?`, `-i?`, `-C?` |
102
102
 
103
+ ### Optional Tools (via config)
104
+
105
+ | Tool | Purpose | Config Key |
106
+ |------|---------|------------|
107
+ | `AskUser` | Ask user clarifying questions | `askUser: { onQuestion? }` |
108
+ | `EnterPlanMode` | Enter planning/exploration mode | `planMode: true` |
109
+ | `ExitPlanMode` | Exit planning mode with a plan | `planMode: true` |
110
+ | `Skill` | Execute skills | `skill: { skills }` |
111
+ | `WebSearch` | Search the web | `webSearch: { apiKey }` |
112
+ | `WebFetch` | Fetch URL and process with AI | `webFetch: { apiKey, model }` |
113
+
103
114
  ### Workflow Tools (created separately)
104
115
 
105
116
  | Tool | Purpose | Factory |
106
117
  |------|---------|---------|
107
118
  | `Task` | Spawn sub-agents | `createTaskTool({ model, tools, subagentTypes? })` |
108
119
  | `TodoWrite` | Track task progress | `createTodoWriteTool(state, config?, onUpdate?)` |
109
- | `ExitPlanMode` | Exit planning mode | `createExitPlanModeTool(config?, onPlanSubmit?)` |
110
120
 
111
121
  ### Web Tools (require `parallel-web` peer dependency)
112
122
 
@@ -154,10 +164,30 @@ const sandbox = createE2BSandbox({
154
164
 
155
165
  ## Configuration
156
166
 
157
- You can configure tools with security restrictions and limits:
167
+ You can configure tools with security restrictions and limits, and enable optional tools:
158
168
 
159
169
  ```typescript
160
- const tools = createAgentTools(sandbox, {
170
+ const { tools, planModeState } = createAgentTools(sandbox, {
171
+ // Enable optional tools
172
+ askUser: {
173
+ onQuestion: async (question) => {
174
+ // Return user's answer, or undefined to return awaiting_response
175
+ return await promptUser(question);
176
+ },
177
+ },
178
+ planMode: true, // Enables EnterPlanMode and ExitPlanMode
179
+ skill: {
180
+ skills: discoveredSkills, // From discoverSkills()
181
+ },
182
+ webSearch: {
183
+ apiKey: process.env.PARALLEL_API_KEY,
184
+ },
185
+ webFetch: {
186
+ apiKey: process.env.PARALLEL_API_KEY,
187
+ model: anthropic('claude-haiku-4'),
188
+ },
189
+
190
+ // Tool-specific config
161
191
  tools: {
162
192
  Bash: {
163
193
  timeout: 30000,
@@ -171,13 +201,6 @@ const tools = createAgentTools(sandbox, {
171
201
  maxFileSize: 1_000_000, // 1MB limit
172
202
  },
173
203
  },
174
- webSearch: {
175
- apiKey: process.env.PARALLEL_API_KEY,
176
- },
177
- webFetch: {
178
- apiKey: process.env.PARALLEL_API_KEY,
179
- model: anthropic('claude-haiku-4'),
180
- },
181
204
  });
182
205
  ```
183
206
 
@@ -196,7 +219,7 @@ const tools = createAgentTools(sandbox, {
196
219
 
197
220
  ## Sub-agents with Task Tool
198
221
 
199
- The Task tool spawns new `generateText` calls for complex subtasks:
222
+ The Task tool spawns new agents for complex subtasks:
200
223
 
201
224
  ```typescript
202
225
  import { createTaskTool } from 'bashkit';
@@ -231,6 +254,46 @@ The parent agent calls Task like any other tool:
231
254
  }}
232
255
  ```
233
256
 
257
+ ### Streaming Sub-agent Activity to UI
258
+
259
+ Pass a `streamWriter` to stream real-time sub-agent activity to the UI:
260
+
261
+ ```typescript
262
+ import { createUIMessageStream } from 'ai';
263
+
264
+ const stream = createUIMessageStream({
265
+ execute: async ({ writer }) => {
266
+ const taskTool = createTaskTool({
267
+ model,
268
+ tools: sandboxTools,
269
+ streamWriter: writer, // Enable real-time streaming
270
+ subagentTypes: { ... },
271
+ });
272
+
273
+ // Use with streamText
274
+ const result = streamText({
275
+ model,
276
+ tools: { Task: taskTool },
277
+ ...
278
+ });
279
+
280
+ writer.merge(result.toUIMessageStream());
281
+ },
282
+ });
283
+ ```
284
+
285
+ When `streamWriter` is provided:
286
+ - Uses `streamText` internally (instead of `generateText`)
287
+ - Emits `data-subagent` events to the UI stream:
288
+ - `start` - Sub-agent begins work
289
+ - `tool-call` - Each tool the sub-agent uses (with args)
290
+ - `done` - Sub-agent finished
291
+ - `complete` - Full messages array for UI access
292
+
293
+ These appear in `message.parts` on the client as `{ type: "data-subagent", data: SubagentEventData }`.
294
+
295
+ **Important:** The TaskOutput returned to the lead agent does NOT include messages (to avoid context bloat). The UI accesses the full conversation via the streamed `complete` event.
296
+
234
297
  ## Context Management
235
298
 
236
299
  ### Conversation Compaction
@@ -335,7 +398,7 @@ import { discoverSkills, skillsToXml, createAgentTools, createLocalSandbox } fro
335
398
 
336
399
  const skills = await discoverSkills();
337
400
  const sandbox = createLocalSandbox({ cwd: '/tmp/workspace' });
338
- const tools = createAgentTools(sandbox);
401
+ const { tools } = createAgentTools(sandbox);
339
402
 
340
403
  const result = await generateText({
341
404
  model: anthropic('claude-sonnet-4-5'),
@@ -504,7 +567,7 @@ ${skillsToXml(skills)}
504
567
  `;
505
568
 
506
569
  // Create tools and run
507
- const tools = createAgentTools(sandbox);
570
+ const { tools } = createAgentTools(sandbox);
508
571
 
509
572
  const result = await generateText({
510
573
  model: anthropic('claude-sonnet-4-5'),
@@ -571,7 +634,7 @@ class DockerSandbox implements Sandbox {
571
634
  }
572
635
 
573
636
  const sandbox = new DockerSandbox();
574
- const tools = createAgentTools(sandbox);
637
+ const { tools } = createAgentTools(sandbox);
575
638
  ```
576
639
 
577
640
  ## Architecture
@@ -641,7 +704,13 @@ Creates a set of agent tools bound to a sandbox instance.
641
704
 
642
705
  - `createTaskTool(config)` - Spawn sub-agents for complex tasks
643
706
  - `createTodoWriteTool(state, config?, onUpdate?)` - Track task progress
644
- - `createExitPlanModeTool(config?, onPlanSubmit?)` - Exit planning mode
707
+
708
+ ### Optional Tools (also available via config)
709
+
710
+ - `createAskUserTool(onQuestion?)` - Ask user for clarification
711
+ - `createEnterPlanModeTool(state)` - Enter planning/exploration mode
712
+ - `createExitPlanModeTool(state, onPlanSubmit?)` - Exit planning mode with a plan
713
+ - `createSkillTool(skills)` - Execute loaded skills
645
714
 
646
715
  ### Utilities
647
716
 
package/dist/cli/init.js CHANGED
@@ -54,7 +54,7 @@ export const config: AgentConfig = {${webTools ? `
54
54
  ` : ""}};
55
55
 
56
56
  // Create tools
57
- export const tools = createAgentTools(sandbox${webTools ? ", config" : ""});
57
+ export const { tools } = createAgentTools(sandbox${webTools ? ", config" : ""});
58
58
  `;
59
59
  const configPath = join(process.cwd(), "bashkit.config.ts");
60
60
  if (existsSync(configPath)) {
@@ -141,7 +141,7 @@ async function init() {
141
141
  stdio: "inherit"
142
142
  });
143
143
  s.stop("Dependencies installed ✓");
144
- } catch (error) {
144
+ } catch {
145
145
  s.stop("Installation failed ✗");
146
146
  console.error(`
147
147
  ❌ Failed to install dependencies`);
package/dist/index.d.ts CHANGED
@@ -1,10 +1,11 @@
1
+ export type { UIMessageStreamWriter } from "ai";
1
2
  export { anthropicPromptCacheMiddleware } from "./middleware";
2
3
  export type { E2BSandboxConfig, LocalSandboxConfig, VercelSandboxConfig, } from "./sandbox";
3
4
  export { createE2BSandbox, createLocalSandbox, createVercelSandbox, } from "./sandbox";
4
5
  export type { ExecOptions, ExecResult, Sandbox } from "./sandbox/interface";
5
- export type { BashError, BashOutput, EditError, EditOutput, ExitPlanModeError, ExitPlanModeOutput, GlobError, GlobOutput, GrepContentOutput, GrepCountOutput, GrepError, GrepFilesOutput, GrepMatch, GrepOutput, ReadDirectoryOutput, ReadError, ReadOutput, ReadTextOutput, SubagentStepEvent, SubagentTypeConfig, TaskError, TaskOutput, TaskToolConfig, TodoItem, TodoState, TodoWriteError, TodoWriteOutput, WebFetchError, WebFetchOutput, WebFetchToolConfig, WebSearchError, WebSearchOutput, WebSearchResult, WebSearchToolConfig, WriteError, WriteOutput, } from "./tools";
6
- export { createAgentTools, createBashTool, createEditTool, createExitPlanModeTool, createGlobTool, createGrepTool, createReadTool, createTaskTool, createTodoWriteTool, createWebFetchTool, createWebSearchTool, createWriteTool, } from "./tools";
7
- export type { AgentConfig, ToolConfig, WebFetchConfig, WebSearchConfig, } from "./types";
6
+ export type { AgentToolsResult, AskUserError, AskUserOutput, AskUserResponseHandler, BashError, BashOutput, EditError, EditOutput, EnterPlanModeError, EnterPlanModeOutput, ExitPlanModeError, ExitPlanModeOutput, PlanModeState, GlobError, GlobOutput, GrepContentOutput, GrepCountOutput, GrepError, GrepFilesOutput, GrepMatch, GrepOutput, ReadDirectoryOutput, ReadError, ReadOutput, ReadTextOutput, SkillError, SkillOutput, SkillToolConfig, SubagentEventData, SubagentStepEvent, SubagentTypeConfig, TaskError, TaskOutput, TaskToolConfig, TodoItem, TodoState, TodoWriteError, TodoWriteOutput, WebFetchError, WebFetchOutput, WebFetchToolConfig, WebSearchError, WebSearchOutput, WebSearchResult, WebSearchToolConfig, WriteError, WriteOutput, } from "./tools";
7
+ export { createAgentTools, createAskUserTool, createBashTool, createEditTool, createEnterPlanModeTool, createExitPlanModeTool, createGlobTool, createGrepTool, createReadTool, createSkillTool, createTaskTool, createTodoWriteTool, createWebFetchTool, createWebSearchTool, createWriteTool, } from "./tools";
8
+ export type { AgentConfig, AskUserConfig, SkillConfig, ToolConfig, WebFetchConfig, WebSearchConfig, } from "./types";
8
9
  export { DEFAULT_CONFIG } from "./types";
9
10
  export type { CompactConversationConfig, CompactConversationResult, CompactConversationState, ContextMetrics, ContextStatus, ContextStatusConfig, ContextStatusLevel, ModelContextLimit, PruneMessagesConfig, } from "./utils";
10
11
  export { compactConversation, contextNeedsAttention, contextNeedsCompaction, createCompactConfig, estimateMessagesTokens, estimateMessageTokens, estimateTokens, getContextStatus, MODEL_CONTEXT_LIMITS, pruneMessagesByTokens, } from "./utils";
package/dist/index.js CHANGED
@@ -358,14 +358,59 @@ var DEFAULT_CONFIG = {
358
358
  }
359
359
  };
360
360
 
361
- // src/tools/bash.ts
361
+ // src/tools/ask-user.ts
362
362
  import { tool, zodSchema } from "ai";
363
363
  import { z } from "zod";
364
- var bashInputSchema = z.object({
365
- command: z.string().describe("The command to execute"),
366
- timeout: z.number().optional().describe("Optional timeout in milliseconds (max 600000)"),
367
- description: z.string().optional().describe("Clear, concise description of what this command does in 5-10 words"),
368
- run_in_background: z.boolean().optional().describe("Set to true to run this command in the background")
364
+ var askUserInputSchema = z.object({
365
+ question: z.string().describe("The question to ask the user. Be specific and concise.")
366
+ });
367
+ var ASK_USER_DESCRIPTION = `Ask the user a clarifying question when you need more information to proceed.
368
+
369
+ **When to use:**
370
+ - You need clarification on ambiguous requirements
371
+ - Multiple valid approaches exist and user preference matters
372
+ - You're about to make a decision with significant consequences
373
+ - Required information is missing from the context
374
+
375
+ **When NOT to use:**
376
+ - You can make a reasonable assumption
377
+ - The question is trivial or can be inferred
378
+ - You're just being overly cautious
379
+
380
+ Keep questions specific and actionable. Avoid yes/no questions when you need details.`;
381
+ function createAskUserTool(onQuestion) {
382
+ return tool({
383
+ description: ASK_USER_DESCRIPTION,
384
+ inputSchema: zodSchema(askUserInputSchema),
385
+ execute: async ({
386
+ question
387
+ }) => {
388
+ try {
389
+ if (onQuestion) {
390
+ const answer = await onQuestion(question);
391
+ return { answer };
392
+ }
393
+ return {
394
+ question,
395
+ awaiting_response: true
396
+ };
397
+ } catch (error) {
398
+ return {
399
+ error: error instanceof Error ? error.message : "Unknown error"
400
+ };
401
+ }
402
+ }
403
+ });
404
+ }
405
+
406
+ // src/tools/bash.ts
407
+ import { tool as tool2, zodSchema as zodSchema2 } from "ai";
408
+ import { z as z2 } from "zod";
409
+ var bashInputSchema = z2.object({
410
+ command: z2.string().describe("The command to execute"),
411
+ timeout: z2.number().optional().describe("Optional timeout in milliseconds (max 600000)"),
412
+ description: z2.string().optional().describe("Clear, concise description of what this command does in 5-10 words"),
413
+ run_in_background: z2.boolean().optional().describe("Set to true to run this command in the background")
369
414
  });
370
415
  var BASH_DESCRIPTION = `Executes bash commands in a persistent shell session with optional timeout and background execution.
371
416
 
@@ -379,9 +424,9 @@ var BASH_DESCRIPTION = `Executes bash commands in a persistent shell session wit
379
424
  function createBashTool(sandbox, config) {
380
425
  const maxOutputLength = config?.maxOutputLength ?? 30000;
381
426
  const defaultTimeout = config?.timeout ?? 120000;
382
- return tool({
427
+ return tool2({
383
428
  description: BASH_DESCRIPTION,
384
- inputSchema: zodSchema(bashInputSchema),
429
+ inputSchema: zodSchema2(bashInputSchema),
385
430
  execute: async ({
386
431
  command,
387
432
  timeout,
@@ -429,18 +474,18 @@ function createBashTool(sandbox, config) {
429
474
  }
430
475
 
431
476
  // src/tools/edit.ts
432
- import { tool as tool2, zodSchema as zodSchema2 } from "ai";
433
- import { z as z2 } from "zod";
434
- var editInputSchema = z2.object({
435
- file_path: z2.string().describe("The absolute path to the file to modify"),
436
- old_string: z2.string().describe("The text to replace"),
437
- new_string: z2.string().describe("The text to replace it with (must be different from old_string)"),
438
- replace_all: z2.boolean().optional().describe("Replace all occurrences of old_string (default false)")
477
+ import { tool as tool3, zodSchema as zodSchema3 } from "ai";
478
+ import { z as z3 } from "zod";
479
+ var editInputSchema = z3.object({
480
+ file_path: z3.string().describe("The absolute path to the file to modify"),
481
+ old_string: z3.string().describe("The text to replace"),
482
+ new_string: z3.string().describe("The text to replace it with (must be different from old_string)"),
483
+ replace_all: z3.boolean().optional().describe("Replace all occurrences of old_string (default false)")
439
484
  });
440
485
  function createEditTool(sandbox, config) {
441
- return tool2({
486
+ return tool3({
442
487
  description: "Performs exact string replacements in files.",
443
- inputSchema: zodSchema2(editInputSchema),
488
+ inputSchema: zodSchema3(editInputSchema),
444
489
  execute: async ({
445
490
  file_path,
446
491
  old_string,
@@ -495,17 +540,104 @@ function createEditTool(sandbox, config) {
495
540
  });
496
541
  }
497
542
 
543
+ // src/tools/enter-plan-mode.ts
544
+ import { tool as tool4, zodSchema as zodSchema4 } from "ai";
545
+ import { z as z4 } from "zod";
546
+ var enterPlanModeInputSchema = z4.object({
547
+ reason: z4.string().describe("Brief explanation of why you're entering planning mode (e.g., 'Need to explore codebase architecture before implementing feature')")
548
+ });
549
+ var ENTER_PLAN_MODE_DESCRIPTION = `Enter planning mode to explore and design implementation approaches before making changes.
550
+
551
+ **When to use:**
552
+ - Complex tasks requiring research or exploration
553
+ - Need to understand codebase structure before implementing
554
+ - Multiple approaches possible and you need to evaluate trade-offs
555
+ - User explicitly asks you to plan before executing
556
+
557
+ **In planning mode:**
558
+ - Focus on reading, searching, and understanding
559
+ - Avoid making file changes (use Read, Grep, Glob instead of Write, Edit)
560
+ - Document your findings and proposed approach
561
+ - Use ExitPlanMode when ready with a plan for user approval
562
+
563
+ **When NOT to use:**
564
+ - Simple, well-defined tasks
565
+ - You already understand the codebase and approach
566
+ - User wants immediate execution`;
567
+ function createEnterPlanModeTool(state, onEnter) {
568
+ return tool4({
569
+ description: ENTER_PLAN_MODE_DESCRIPTION,
570
+ inputSchema: zodSchema4(enterPlanModeInputSchema),
571
+ execute: async ({
572
+ reason
573
+ }) => {
574
+ try {
575
+ if (state.isActive) {
576
+ return {
577
+ error: "Already in planning mode. Use ExitPlanMode to exit."
578
+ };
579
+ }
580
+ state.isActive = true;
581
+ state.enteredAt = new Date;
582
+ state.reason = reason;
583
+ if (onEnter) {
584
+ await onEnter(reason);
585
+ }
586
+ return {
587
+ message: `Entered planning mode: ${reason}. Use Read, Grep, and Glob to explore. Call ExitPlanMode when ready with a plan.`,
588
+ mode: "planning"
589
+ };
590
+ } catch (error) {
591
+ return {
592
+ error: error instanceof Error ? error.message : "Unknown error"
593
+ };
594
+ }
595
+ }
596
+ });
597
+ }
598
+
599
+ // src/tools/exit-plan-mode.ts
600
+ import { tool as tool5, zodSchema as zodSchema5 } from "ai";
601
+ import { z as z5 } from "zod";
602
+ var exitPlanModeInputSchema = z5.object({
603
+ plan: z5.string().describe("The plan to present to the user for approval")
604
+ });
605
+ function createExitPlanModeTool(config, onPlanSubmit) {
606
+ return tool5({
607
+ description: "Exits planning mode and prompts the user to approve the plan. Use this when you have finished planning and want user confirmation before proceeding.",
608
+ inputSchema: zodSchema5(exitPlanModeInputSchema),
609
+ execute: async ({
610
+ plan
611
+ }) => {
612
+ try {
613
+ let approved;
614
+ if (onPlanSubmit) {
615
+ approved = await onPlanSubmit(plan);
616
+ }
617
+ return {
618
+ message: approved ? "Plan approved, proceeding with execution" : "Plan submitted for review",
619
+ approved
620
+ };
621
+ } catch (error) {
622
+ return {
623
+ error: error instanceof Error ? error.message : "Unknown error"
624
+ };
625
+ }
626
+ }
627
+ });
628
+ }
629
+
498
630
  // src/tools/glob.ts
499
- import { tool as tool3, zodSchema as zodSchema3 } from "ai";
500
- import { z as z3 } from "zod";
501
- var globInputSchema = z3.object({
502
- pattern: z3.string().describe('Glob pattern to match files (e.g., "**/*.ts", "src/**/*.js", "*.md")'),
503
- path: z3.string().optional().describe("Directory to search in (defaults to working directory)")
631
+ import { tool as tool6, zodSchema as zodSchema6 } from "ai";
632
+ import { z as z6 } from "zod";
633
+ var globInputSchema = z6.object({
634
+ pattern: z6.string().describe('Glob pattern to match files (e.g., "**/*.ts", "src/**/*.js", "*.md")'),
635
+ path: z6.string().optional().describe("Directory to search in (defaults to working directory)")
504
636
  });
505
637
  function createGlobTool(sandbox, config) {
506
- return tool3({
638
+ return tool6({
507
639
  description: "Search for files matching a glob pattern. Returns file paths sorted by modification time. Use this instead of `find` command.",
508
- inputSchema: zodSchema3(globInputSchema),
640
+ inputSchema: zodSchema6(globInputSchema),
509
641
  execute: async ({
510
642
  pattern,
511
643
  path
@@ -539,26 +671,26 @@ function createGlobTool(sandbox, config) {
539
671
  }
540
672
 
541
673
  // src/tools/grep.ts
542
- import { tool as tool4, zodSchema as zodSchema4 } from "ai";
543
- import { z as z4 } from "zod";
544
- var grepInputSchema = z4.object({
545
- pattern: z4.string().describe("The regular expression pattern to search for"),
546
- path: z4.string().optional().describe("File or directory to search in (defaults to cwd)"),
547
- glob: z4.string().optional().describe('Glob pattern to filter files (e.g. "*.js")'),
548
- type: z4.string().optional().describe('File type to search (e.g. "js", "py", "rust")'),
549
- output_mode: z4.enum(["content", "files_with_matches", "count"]).optional().describe('Output mode: "content", "files_with_matches", or "count"'),
550
- "-i": z4.boolean().optional().describe("Case insensitive search"),
551
- "-n": z4.boolean().optional().describe("Show line numbers (for content mode)"),
552
- "-B": z4.number().optional().describe("Lines to show before each match"),
553
- "-A": z4.number().optional().describe("Lines to show after each match"),
554
- "-C": z4.number().optional().describe("Lines to show before and after each match"),
555
- head_limit: z4.number().optional().describe("Limit output to first N lines/entries"),
556
- multiline: z4.boolean().optional().describe("Enable multiline mode")
674
+ import { tool as tool7, zodSchema as zodSchema7 } from "ai";
675
+ import { z as z7 } from "zod";
676
+ var grepInputSchema = z7.object({
677
+ pattern: z7.string().describe("The regular expression pattern to search for"),
678
+ path: z7.string().optional().describe("File or directory to search in (defaults to cwd)"),
679
+ glob: z7.string().optional().describe('Glob pattern to filter files (e.g. "*.js")'),
680
+ type: z7.string().optional().describe('File type to search (e.g. "js", "py", "rust")'),
681
+ output_mode: z7.enum(["content", "files_with_matches", "count"]).optional().describe('Output mode: "content", "files_with_matches", or "count"'),
682
+ "-i": z7.boolean().optional().describe("Case insensitive search"),
683
+ "-n": z7.boolean().optional().describe("Show line numbers (for content mode)"),
684
+ "-B": z7.number().optional().describe("Lines to show before each match"),
685
+ "-A": z7.number().optional().describe("Lines to show after each match"),
686
+ "-C": z7.number().optional().describe("Lines to show before and after each match"),
687
+ head_limit: z7.number().optional().describe("Limit output to first N lines/entries"),
688
+ multiline: z7.boolean().optional().describe("Enable multiline mode")
557
689
  });
558
690
  function createGrepTool(sandbox, config) {
559
- return tool4({
691
+ return tool7({
560
692
  description: "Powerful search tool built on ripgrep with regex support. Use this instead of the grep command.",
561
- inputSchema: zodSchema4(grepInputSchema),
693
+ inputSchema: zodSchema7(grepInputSchema),
562
694
  execute: async (input) => {
563
695
  const {
564
696
  pattern,
@@ -668,17 +800,17 @@ function createGrepTool(sandbox, config) {
668
800
  }
669
801
 
670
802
  // src/tools/read.ts
671
- import { tool as tool5, zodSchema as zodSchema5 } from "ai";
672
- import { z as z5 } from "zod";
673
- var readInputSchema = z5.object({
674
- file_path: z5.string().describe("Absolute path to file or directory"),
675
- offset: z5.number().optional().describe("Line number to start reading from (1-indexed)"),
676
- limit: z5.number().optional().describe("Maximum number of lines to read")
803
+ import { tool as tool8, zodSchema as zodSchema8 } from "ai";
804
+ import { z as z8 } from "zod";
805
+ var readInputSchema = z8.object({
806
+ file_path: z8.string().describe("Absolute path to file or directory"),
807
+ offset: z8.number().optional().describe("Line number to start reading from (1-indexed)"),
808
+ limit: z8.number().optional().describe("Maximum number of lines to read")
677
809
  });
678
810
  function createReadTool(sandbox, config) {
679
- return tool5({
811
+ return tool8({
680
812
  description: "Read the contents of a file or list directory entries. For text files, returns numbered lines with total line count. For directories, returns file/folder names. Use this instead of `cat`, `head`, or `tail` commands.",
681
- inputSchema: zodSchema5(readInputSchema),
813
+ inputSchema: zodSchema8(readInputSchema),
682
814
  execute: async ({
683
815
  file_path,
684
816
  offset,
@@ -760,20 +892,91 @@ function createReadTool(sandbox, config) {
760
892
  });
761
893
  }
762
894
 
895
+ // src/tools/skill.ts
896
+ import { tool as tool9, zodSchema as zodSchema9 } from "ai";
897
+ import { z as z9 } from "zod";
898
+ var skillInputSchema = z9.object({
899
+ name: z9.string().describe("The name of the skill to activate (from available skills list)")
900
+ });
901
+ var SKILL_DESCRIPTION = `Activate a skill to get specialized instructions for a task.
902
+
903
+ **How skills work:**
904
+ 1. Skills are pre-loaded at startup (only metadata: name, description, path)
905
+ 2. Calling this tool loads the full skill instructions from SKILL.md
906
+ 3. Follow the returned instructions to complete the task
907
+ 4. Some skills restrict which tools you can use (allowed_tools)
908
+
909
+ **When to use:**
910
+ - A task matches a skill's description
911
+ - You need specialized knowledge for a domain (e.g., PDF processing, web research)
912
+ - The skill provides step-by-step guidance you should follow
913
+
914
+ **Available skills are listed in the system prompt.**`;
915
+ function createSkillTool(config) {
916
+ const { skills, sandbox, onActivate } = config;
917
+ return tool9({
918
+ description: SKILL_DESCRIPTION,
919
+ inputSchema: zodSchema9(skillInputSchema),
920
+ execute: async ({
921
+ name
922
+ }) => {
923
+ try {
924
+ const skill = skills[name];
925
+ if (!skill) {
926
+ const available = Object.keys(skills);
927
+ if (available.length === 0) {
928
+ return { error: "No skills are available." };
929
+ }
930
+ return {
931
+ error: `Skill '${name}' not found. Available skills: ${available.join(", ")}`
932
+ };
933
+ }
934
+ let instructions;
935
+ if (!sandbox) {
936
+ return {
937
+ error: `Cannot load skill '${name}': no sandbox provided to read ${skill.path}`
938
+ };
939
+ }
940
+ const content = await sandbox.readFile(skill.path);
941
+ const frontmatterEnd = content.indexOf(`
942
+ ---`, 4);
943
+ if (frontmatterEnd !== -1) {
944
+ instructions = content.slice(frontmatterEnd + 4).trim();
945
+ } else {
946
+ instructions = content;
947
+ }
948
+ if (onActivate) {
949
+ await onActivate(skill, instructions);
950
+ }
951
+ return {
952
+ name: skill.name,
953
+ instructions,
954
+ allowed_tools: skill.allowedTools,
955
+ message: skill.allowedTools ? `Skill '${name}' activated. Restricted to tools: ${skill.allowedTools.join(", ")}` : `Skill '${name}' activated. Follow the instructions below.`
956
+ };
957
+ } catch (error) {
958
+ return {
959
+ error: error instanceof Error ? error.message : "Unknown error"
960
+ };
961
+ }
962
+ }
963
+ });
964
+ }
965
+
763
966
  // src/tools/web-fetch.ts
764
- import { generateText, tool as tool6, zodSchema as zodSchema6 } from "ai";
967
+ import { generateText, tool as tool10, zodSchema as zodSchema10 } from "ai";
765
968
  import Parallel from "parallel-web";
766
- import { z as z6 } from "zod";
767
- var webFetchInputSchema = z6.object({
768
- url: z6.string().describe("The URL to fetch content from"),
769
- prompt: z6.string().describe("The prompt to run on the fetched content")
969
+ import { z as z10 } from "zod";
970
+ var webFetchInputSchema = z10.object({
971
+ url: z10.string().describe("The URL to fetch content from"),
972
+ prompt: z10.string().describe("The prompt to run on the fetched content")
770
973
  });
771
974
  var RETRYABLE_CODES = [408, 429, 500, 502, 503];
772
975
  function createWebFetchTool(config) {
773
976
  const { apiKey, model } = config;
774
- return tool6({
977
+ return tool10({
775
978
  description: "Fetches content from a URL and processes it with an AI model. Use this to analyze web pages, extract information, or summarize content.",
776
- inputSchema: zodSchema6(webFetchInputSchema),
979
+ inputSchema: zodSchema10(webFetchInputSchema),
777
980
  execute: async (input) => {
778
981
  const { url, prompt } = input;
779
982
  try {
@@ -833,20 +1036,20 @@ ${content}`
833
1036
  }
834
1037
 
835
1038
  // src/tools/web-search.ts
836
- import { tool as tool7, zodSchema as zodSchema7 } from "ai";
1039
+ import { tool as tool11, zodSchema as zodSchema11 } from "ai";
837
1040
  import Parallel2 from "parallel-web";
838
- import { z as z7 } from "zod";
839
- var webSearchInputSchema = z7.object({
840
- query: z7.string().describe("The search query to use"),
841
- allowed_domains: z7.array(z7.string()).optional().describe("Only include results from these domains"),
842
- blocked_domains: z7.array(z7.string()).optional().describe("Never include results from these domains")
1041
+ import { z as z11 } from "zod";
1042
+ var webSearchInputSchema = z11.object({
1043
+ query: z11.string().describe("The search query to use"),
1044
+ allowed_domains: z11.array(z11.string()).optional().describe("Only include results from these domains"),
1045
+ blocked_domains: z11.array(z11.string()).optional().describe("Never include results from these domains")
843
1046
  });
844
1047
  var RETRYABLE_CODES2 = [408, 429, 500, 502, 503];
845
1048
  function createWebSearchTool(config) {
846
1049
  const { apiKey } = config;
847
- return tool7({
1050
+ return tool11({
848
1051
  description: "Searches the web and returns formatted results. Use this to find current information, documentation, articles, and more.",
849
- inputSchema: zodSchema7(webSearchInputSchema),
1052
+ inputSchema: zodSchema11(webSearchInputSchema),
850
1053
  execute: async (input) => {
851
1054
  const { query, allowed_domains, blocked_domains } = input;
852
1055
  try {
@@ -892,16 +1095,16 @@ function createWebSearchTool(config) {
892
1095
  }
893
1096
 
894
1097
  // src/tools/write.ts
895
- import { tool as tool8, zodSchema as zodSchema8 } from "ai";
896
- import { z as z8 } from "zod";
897
- var writeInputSchema = z8.object({
898
- file_path: z8.string().describe("Path to the file to write"),
899
- content: z8.string().describe("Content to write to the file")
1098
+ import { tool as tool12, zodSchema as zodSchema12 } from "ai";
1099
+ import { z as z12 } from "zod";
1100
+ var writeInputSchema = z12.object({
1101
+ file_path: z12.string().describe("Path to the file to write"),
1102
+ content: z12.string().describe("Content to write to the file")
900
1103
  });
901
1104
  function createWriteTool(sandbox, config) {
902
- return tool8({
1105
+ return tool12({
903
1106
  description: "Write content to a file. Creates the file if it does not exist, overwrites if it does.",
904
- inputSchema: zodSchema8(writeInputSchema),
1107
+ inputSchema: zodSchema12(writeInputSchema),
905
1108
  execute: async ({
906
1109
  file_path,
907
1110
  content
@@ -933,49 +1136,24 @@ function createWriteTool(sandbox, config) {
933
1136
  }
934
1137
  });
935
1138
  }
936
- // src/tools/exit-plan-mode.ts
937
- import { tool as tool9, zodSchema as zodSchema9 } from "ai";
938
- import { z as z9 } from "zod";
939
- var exitPlanModeInputSchema = z9.object({
940
- plan: z9.string().describe("The plan to present to the user for approval")
941
- });
942
- function createExitPlanModeTool(config, onPlanSubmit) {
943
- return tool9({
944
- description: "Exits planning mode and prompts the user to approve the plan. Use this when you have finished planning and want user confirmation before proceeding.",
945
- inputSchema: zodSchema9(exitPlanModeInputSchema),
946
- execute: async ({
947
- plan
948
- }) => {
949
- try {
950
- let approved;
951
- if (onPlanSubmit) {
952
- approved = await onPlanSubmit(plan);
953
- }
954
- return {
955
- message: approved ? "Plan approved, proceeding with execution" : "Plan submitted for review",
956
- approved
957
- };
958
- } catch (error) {
959
- return {
960
- error: error instanceof Error ? error.message : "Unknown error"
961
- };
962
- }
963
- }
964
- });
965
- }
966
1139
  // src/tools/task.ts
967
1140
  import {
968
1141
  generateText as generateText2,
1142
+ streamText,
969
1143
  stepCountIs,
970
- tool as tool10,
971
- zodSchema as zodSchema10
1144
+ tool as tool13,
1145
+ zodSchema as zodSchema13
972
1146
  } from "ai";
973
- import { z as z10 } from "zod";
974
- var taskInputSchema = z10.object({
975
- description: z10.string().describe("A short (3-5 word) description of the task"),
976
- prompt: z10.string().describe("The task for the agent to perform"),
977
- subagent_type: z10.string().describe("The type of specialized agent to use for this task")
1147
+ import { z as z13 } from "zod";
1148
+ var taskInputSchema = z13.object({
1149
+ description: z13.string().describe("A short (3-5 word) description of the task"),
1150
+ prompt: z13.string().describe("The task for the agent to perform"),
1151
+ subagent_type: z13.string().describe("The type of specialized agent to use for this task")
978
1152
  });
1153
+ var eventCounter = 0;
1154
+ function generateEventId() {
1155
+ return `subagent-${Date.now()}-${++eventCounter}`;
1156
+ }
979
1157
  function filterTools(allTools, allowedTools) {
980
1158
  if (!allowedTools)
981
1159
  return allTools;
@@ -992,14 +1170,13 @@ function createTaskTool(config) {
992
1170
  model: defaultModel,
993
1171
  tools: allTools,
994
1172
  subagentTypes = {},
995
- costPerInputToken = 0.000003,
996
- costPerOutputToken = 0.000015,
997
1173
  defaultStopWhen,
998
- defaultOnStepFinish
1174
+ defaultOnStepFinish,
1175
+ streamWriter
999
1176
  } = config;
1000
- return tool10({
1177
+ return tool13({
1001
1178
  description: "Launches a new agent to handle complex, multi-step tasks autonomously. Use this for tasks that require multiple steps, research, or specialized expertise.",
1002
- inputSchema: zodSchema10(taskInputSchema),
1179
+ inputSchema: zodSchema13(taskInputSchema),
1003
1180
  execute: async ({
1004
1181
  description,
1005
1182
  prompt,
@@ -1011,13 +1188,88 @@ function createTaskTool(config) {
1011
1188
  const model = typeConfig.model || defaultModel;
1012
1189
  const tools = filterTools(allTools, typeConfig.tools);
1013
1190
  const systemPrompt = typeConfig.systemPrompt;
1014
- const result = await generateText2({
1191
+ const commonOptions = {
1015
1192
  model,
1016
1193
  tools,
1017
1194
  system: systemPrompt,
1018
1195
  prompt,
1019
1196
  stopWhen: typeConfig.stopWhen ?? defaultStopWhen ?? stepCountIs(15),
1020
- prepareStep: typeConfig.prepareStep,
1197
+ prepareStep: typeConfig.prepareStep
1198
+ };
1199
+ if (streamWriter) {
1200
+ const startId = generateEventId();
1201
+ streamWriter.write({
1202
+ type: "data-subagent",
1203
+ id: startId,
1204
+ data: {
1205
+ event: "start",
1206
+ subagent: subagent_type,
1207
+ description
1208
+ }
1209
+ });
1210
+ const result2 = streamText({
1211
+ ...commonOptions,
1212
+ onStepFinish: async (step) => {
1213
+ if (step.toolCalls?.length) {
1214
+ for (const tc of step.toolCalls) {
1215
+ const eventId = generateEventId();
1216
+ streamWriter.write({
1217
+ type: "data-subagent",
1218
+ id: eventId,
1219
+ data: {
1220
+ event: "tool-call",
1221
+ subagent: subagent_type,
1222
+ description,
1223
+ toolName: tc.toolName,
1224
+ args: tc.input
1225
+ }
1226
+ });
1227
+ }
1228
+ }
1229
+ await typeConfig.onStepFinish?.(step);
1230
+ await defaultOnStepFinish?.({
1231
+ subagentType: subagent_type,
1232
+ description,
1233
+ step
1234
+ });
1235
+ }
1236
+ });
1237
+ const text = await result2.text;
1238
+ const usage2 = await result2.usage;
1239
+ const response = await result2.response;
1240
+ streamWriter.write({
1241
+ type: "data-subagent",
1242
+ id: generateEventId(),
1243
+ data: {
1244
+ event: "done",
1245
+ subagent: subagent_type,
1246
+ description
1247
+ }
1248
+ });
1249
+ streamWriter.write({
1250
+ type: "data-subagent",
1251
+ id: generateEventId(),
1252
+ data: {
1253
+ event: "complete",
1254
+ subagent: subagent_type,
1255
+ description,
1256
+ messages: response.messages
1257
+ }
1258
+ });
1259
+ const durationMs2 = Math.round(performance.now() - startTime);
1260
+ return {
1261
+ result: text,
1262
+ usage: usage2.inputTokens !== undefined && usage2.outputTokens !== undefined ? {
1263
+ input_tokens: usage2.inputTokens,
1264
+ output_tokens: usage2.outputTokens
1265
+ } : undefined,
1266
+ duration_ms: durationMs2,
1267
+ subagent: subagent_type,
1268
+ description
1269
+ };
1270
+ }
1271
+ const result = await generateText2({
1272
+ ...commonOptions,
1021
1273
  onStepFinish: async (step) => {
1022
1274
  await typeConfig.onStepFinish?.(step);
1023
1275
  await defaultOnStepFinish?.({
@@ -1032,38 +1284,34 @@ function createTaskTool(config) {
1032
1284
  input_tokens: result.usage.inputTokens,
1033
1285
  output_tokens: result.usage.outputTokens
1034
1286
  } : undefined;
1035
- let totalCostUsd;
1036
- if (usage) {
1037
- totalCostUsd = usage.input_tokens * costPerInputToken + usage.output_tokens * costPerOutputToken;
1038
- }
1039
1287
  return {
1040
1288
  result: result.text,
1041
1289
  usage,
1042
- total_cost_usd: totalCostUsd,
1043
- duration_ms: durationMs
1290
+ duration_ms: durationMs,
1291
+ subagent: subagent_type,
1292
+ description
1044
1293
  };
1045
1294
  } catch (error) {
1046
- return {
1047
- error: error instanceof Error ? error.message : "Unknown error"
1048
- };
1295
+ const errorMessage = error instanceof Error ? error.message : "Unknown error";
1296
+ return { error: errorMessage };
1049
1297
  }
1050
1298
  }
1051
1299
  });
1052
1300
  }
1053
1301
  // src/tools/todo-write.ts
1054
- import { tool as tool11, zodSchema as zodSchema11 } from "ai";
1055
- import { z as z11 } from "zod";
1056
- var todoWriteInputSchema = z11.object({
1057
- todos: z11.array(z11.object({
1058
- content: z11.string().describe("The task description"),
1059
- status: z11.enum(["pending", "in_progress", "completed"]).describe("The task status"),
1060
- activeForm: z11.string().describe("Active form of the task description")
1302
+ import { tool as tool14, zodSchema as zodSchema14 } from "ai";
1303
+ import { z as z14 } from "zod";
1304
+ var todoWriteInputSchema = z14.object({
1305
+ todos: z14.array(z14.object({
1306
+ content: z14.string().describe("The task description"),
1307
+ status: z14.enum(["pending", "in_progress", "completed"]).describe("The task status"),
1308
+ activeForm: z14.string().describe("Active form of the task description")
1061
1309
  })).describe("The updated todo list")
1062
1310
  });
1063
1311
  function createTodoWriteTool(state, config, onUpdate) {
1064
- return tool11({
1312
+ return tool14({
1065
1313
  description: "Creates and manages a structured task list for tracking progress. Use this to plan complex tasks and track completion.",
1066
- inputSchema: zodSchema11(todoWriteInputSchema),
1314
+ inputSchema: zodSchema14(todoWriteInputSchema),
1067
1315
  execute: async ({
1068
1316
  todos
1069
1317
  }) => {
@@ -1105,13 +1353,29 @@ function createAgentTools(sandbox, config) {
1105
1353
  Glob: createGlobTool(sandbox, toolsConfig.Glob),
1106
1354
  Grep: createGrepTool(sandbox, toolsConfig.Grep)
1107
1355
  };
1356
+ let planModeState;
1357
+ if (config?.askUser) {
1358
+ tools.AskUser = createAskUserTool(config.askUser.onQuestion);
1359
+ }
1360
+ if (config?.planMode) {
1361
+ planModeState = { isActive: false };
1362
+ tools.EnterPlanMode = createEnterPlanModeTool(planModeState);
1363
+ tools.ExitPlanMode = createExitPlanModeTool();
1364
+ }
1365
+ if (config?.skill) {
1366
+ tools.Skill = createSkillTool({
1367
+ skills: config.skill.skills,
1368
+ sandbox,
1369
+ onActivate: config.skill.onActivate
1370
+ });
1371
+ }
1108
1372
  if (config?.webSearch) {
1109
1373
  tools.WebSearch = createWebSearchTool(config.webSearch);
1110
1374
  }
1111
1375
  if (config?.webFetch) {
1112
1376
  tools.WebFetch = createWebFetchTool(config.webFetch);
1113
1377
  }
1114
- return tools;
1378
+ return { tools, planModeState };
1115
1379
  }
1116
1380
  // src/utils/compact-conversation.ts
1117
1381
  import { generateText as generateText3 } from "ai";
@@ -1786,15 +2050,18 @@ export {
1786
2050
  createVercelSandbox,
1787
2051
  createTodoWriteTool,
1788
2052
  createTaskTool,
2053
+ createSkillTool,
1789
2054
  createReadTool,
1790
2055
  createLocalSandbox,
1791
2056
  createGrepTool,
1792
2057
  createGlobTool,
1793
2058
  createExitPlanModeTool,
2059
+ createEnterPlanModeTool,
1794
2060
  createEditTool,
1795
2061
  createE2BSandbox,
1796
2062
  createCompactConfig,
1797
2063
  createBashTool,
2064
+ createAskUserTool,
1798
2065
  createAgentTools,
1799
2066
  contextNeedsCompaction,
1800
2067
  contextNeedsAttention,
@@ -0,0 +1,23 @@
1
+ export interface AskUserOutput {
2
+ question: string;
3
+ awaiting_response: true;
4
+ }
5
+ export interface AskUserError {
6
+ error: string;
7
+ }
8
+ export type AskUserResponseHandler = (question: string) => Promise<string> | string;
9
+ /**
10
+ * Creates a tool for asking the user clarifying questions.
11
+ *
12
+ * The response handler can be:
13
+ * - Synchronous: returns the answer immediately
14
+ * - Async: waits for user input (e.g., from a UI)
15
+ * - Undefined: tool returns awaiting_response flag for external handling
16
+ *
17
+ * @param onQuestion - Optional callback to handle the question and return an answer
18
+ */
19
+ export declare function createAskUserTool(onQuestion?: AskUserResponseHandler): import("ai").Tool<{
20
+ question: string;
21
+ }, AskUserOutput | AskUserError | {
22
+ answer: string;
23
+ }>;
@@ -0,0 +1,22 @@
1
+ export interface PlanModeState {
2
+ isActive: boolean;
3
+ enteredAt?: Date;
4
+ reason?: string;
5
+ }
6
+ export interface EnterPlanModeOutput {
7
+ message: string;
8
+ mode: "planning";
9
+ }
10
+ export interface EnterPlanModeError {
11
+ error: string;
12
+ }
13
+ /**
14
+ * Creates a tool for entering planning mode.
15
+ * Works in conjunction with ExitPlanMode for plan-then-execute workflows.
16
+ *
17
+ * @param state - Shared state object to track planning mode
18
+ * @param onEnter - Optional callback when entering planning mode
19
+ */
20
+ export declare function createEnterPlanModeTool(state: PlanModeState, onEnter?: (reason: string) => void | Promise<void>): import("ai").Tool<{
21
+ reason: string;
22
+ }, EnterPlanModeOutput | EnterPlanModeError>;
@@ -1,30 +1,60 @@
1
1
  import type { ToolSet } from "ai";
2
2
  import type { Sandbox } from "../sandbox/interface";
3
3
  import type { AgentConfig } from "../types";
4
+ import { type PlanModeState } from "./enter-plan-mode";
4
5
  /**
5
- * Creates all sandbox-based agent tools for AI SDK's generateText/streamText.
6
- * Returns an object with Bash, Read, Write, Edit, Glob, Grep tools.
7
- * Optionally includes WebSearch and WebFetch if configured.
6
+ * Result from createAgentTools including tools and optional shared state.
7
+ */
8
+ export interface AgentToolsResult {
9
+ /** All configured tools for use with generateText/streamText */
10
+ tools: ToolSet;
11
+ /** Shared plan mode state (only present when planMode is enabled) */
12
+ planModeState?: PlanModeState;
13
+ }
14
+ /**
15
+ * Creates agent tools for AI SDK's generateText/streamText.
16
+ *
17
+ * **Default tools (always included):**
18
+ * - Bash, Read, Write, Edit, Glob, Grep (sandbox operations)
19
+ *
20
+ * **Optional tools (via config):**
21
+ * - `askUser` — AskUser tool for clarifying questions
22
+ * - `planMode` — EnterPlanMode, ExitPlanMode for interactive planning
23
+ * - `skill` — Skill tool for skill execution
24
+ * - `webSearch` — WebSearch tool
25
+ * - `webFetch` — WebFetch tool
8
26
  *
9
27
  * @param sandbox - The sandbox to execute commands in
10
- * @param config - Optional configuration for individual tools and web tools
28
+ * @param config - Optional configuration for tools
29
+ * @returns Object with tools and optional planModeState
11
30
  *
12
31
  * @example
13
- * // Basic sandbox tools only
14
- * const tools = createAgentTools(sandbox);
32
+ * // Basic usage (lean default for background agents)
33
+ * const { tools } = createAgentTools(sandbox);
34
+ *
35
+ * @example
36
+ * // Interactive agent with plan mode
37
+ * const { tools, planModeState } = createAgentTools(sandbox, {
38
+ * planMode: true,
39
+ * askUser: { onQuestion: async (q) => await promptUser(q) },
40
+ * });
15
41
  *
16
42
  * @example
17
- * // With web tools included
18
- * const tools = createAgentTools(sandbox, {
43
+ * // With web and skill tools
44
+ * const { tools } = createAgentTools(sandbox, {
19
45
  * webSearch: { apiKey: process.env.PARALLEL_API_KEY },
20
- * webFetch: { apiKey: process.env.PARALLEL_API_KEY, model: haiku },
46
+ * skill: { skills: discoveredSkills },
21
47
  * });
22
48
  */
23
- export declare function createAgentTools(sandbox: Sandbox, config?: AgentConfig): ToolSet;
49
+ export declare function createAgentTools(sandbox: Sandbox, config?: AgentConfig): AgentToolsResult;
50
+ export type { AskUserError, AskUserOutput, AskUserResponseHandler, } from "./ask-user";
51
+ export { createAskUserTool } from "./ask-user";
24
52
  export type { BashError, BashOutput } from "./bash";
25
53
  export { createBashTool } from "./bash";
26
54
  export type { EditError, EditOutput } from "./edit";
27
55
  export { createEditTool } from "./edit";
56
+ export type { EnterPlanModeError, EnterPlanModeOutput, PlanModeState, } from "./enter-plan-mode";
57
+ export { createEnterPlanModeTool } from "./enter-plan-mode";
28
58
  export type { ExitPlanModeError, ExitPlanModeOutput } from "./exit-plan-mode";
29
59
  export { createExitPlanModeTool } from "./exit-plan-mode";
30
60
  export type { GlobError, GlobOutput } from "./glob";
@@ -33,7 +63,9 @@ export type { GrepContentOutput, GrepCountOutput, GrepError, GrepFilesOutput, Gr
33
63
  export { createGrepTool } from "./grep";
34
64
  export type { ReadDirectoryOutput, ReadError, ReadOutput, ReadTextOutput, } from "./read";
35
65
  export { createReadTool } from "./read";
36
- export type { SubagentStepEvent, SubagentTypeConfig, TaskError, TaskOutput, TaskToolConfig, } from "./task";
66
+ export type { SkillError, SkillOutput, SkillToolConfig } from "./skill";
67
+ export { createSkillTool } from "./skill";
68
+ export type { SubagentEventData, SubagentStepEvent, SubagentTypeConfig, TaskError, TaskOutput, TaskToolConfig, } from "./task";
37
69
  export { createTaskTool } from "./task";
38
70
  export type { TodoItem, TodoState, TodoWriteError, TodoWriteOutput, } from "./todo-write";
39
71
  export { createTodoWriteTool } from "./todo-write";
@@ -0,0 +1,27 @@
1
+ import type { Sandbox } from "../sandbox/interface";
2
+ import type { SkillMetadata } from "../skills/types";
3
+ export interface SkillOutput {
4
+ name: string;
5
+ instructions: string;
6
+ allowed_tools?: string[];
7
+ message: string;
8
+ }
9
+ export interface SkillError {
10
+ error: string;
11
+ }
12
+ export interface SkillToolConfig {
13
+ /** Map of skill name to metadata (from discoverSkills or setupAgentEnvironment) */
14
+ skills: Record<string, SkillMetadata>;
15
+ /** Sandbox for reading skill files (optional if skills have embedded content) */
16
+ sandbox?: Sandbox;
17
+ /** Callback when a skill is activated */
18
+ onActivate?: (skill: SkillMetadata, instructions: string) => void | Promise<void>;
19
+ }
20
+ /**
21
+ * Creates a tool for activating and loading skills.
22
+ *
23
+ * @param config - Configuration with available skills and optional sandbox
24
+ */
25
+ export declare function createSkillTool(config: SkillToolConfig): import("ai").Tool<{
26
+ name: string;
27
+ }, SkillOutput | SkillError>;
@@ -1,12 +1,13 @@
1
- import { type LanguageModel, type PrepareStepFunction, type StepResult, type StopCondition, type Tool, type ToolSet } from "ai";
1
+ import { type ModelMessage, type LanguageModel, type PrepareStepFunction, type StepResult, type StopCondition, type UIMessageStreamWriter, type Tool, type ToolSet } from "ai";
2
2
  export interface TaskOutput {
3
3
  result: string;
4
4
  usage?: {
5
5
  input_tokens: number;
6
6
  output_tokens: number;
7
7
  };
8
- total_cost_usd?: number;
9
8
  duration_ms?: number;
9
+ subagent?: string;
10
+ description?: string;
10
11
  }
11
12
  export interface TaskError {
12
13
  error: string;
@@ -17,6 +18,15 @@ export interface SubagentStepEvent {
17
18
  description: string;
18
19
  step: StepResult<ToolSet>;
19
20
  }
21
+ /** Data format for streamed subagent events (appears in message.parts as type: "data-subagent") */
22
+ export interface SubagentEventData {
23
+ event: "start" | "tool-call" | "done" | "complete";
24
+ subagent: string;
25
+ description: string;
26
+ toolName?: string;
27
+ args?: Record<string, unknown>;
28
+ messages?: ModelMessage[];
29
+ }
20
30
  export interface SubagentTypeConfig {
21
31
  /** Model to use for this subagent type */
22
32
  model?: LanguageModel;
@@ -38,13 +48,11 @@ export interface TaskToolConfig {
38
48
  tools: ToolSet;
39
49
  /** Configuration for each subagent type */
40
50
  subagentTypes?: Record<string, SubagentTypeConfig>;
41
- /** Cost per input token for usage tracking */
42
- costPerInputToken?: number;
43
- /** Cost per output token for usage tracking */
44
- costPerOutputToken?: number;
45
51
  /** Default stop condition for subagents (default: stepCountIs(15)) */
46
52
  defaultStopWhen?: StopCondition<ToolSet>;
47
53
  /** Default callback for each step any subagent takes */
48
54
  defaultOnStepFinish?: (event: SubagentStepEvent) => void | Promise<void>;
55
+ /** Optional stream writer for real-time subagent activity (uses streamText instead of generateText) */
56
+ streamWriter?: UIMessageStreamWriter;
49
57
  }
50
58
  export declare function createTaskTool(config: TaskToolConfig): Tool;
package/dist/types.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import type { LanguageModel } from "ai";
2
+ import type { SkillMetadata } from "./skills/types";
2
3
  export type ToolConfig = {
3
4
  timeout?: number;
4
5
  maxFileSize?: number;
@@ -13,6 +14,16 @@ export type WebFetchConfig = {
13
14
  apiKey: string;
14
15
  model: LanguageModel;
15
16
  };
17
+ export type AskUserConfig = {
18
+ /** Callback to handle questions and return answers */
19
+ onQuestion?: (question: string) => Promise<string> | string;
20
+ };
21
+ export type SkillConfig = {
22
+ /** Map of skill name to metadata */
23
+ skills: Record<string, SkillMetadata>;
24
+ /** Callback when a skill is activated */
25
+ onActivate?: (skill: SkillMetadata, instructions: string) => void | Promise<void>;
26
+ };
16
27
  export type AgentConfig = {
17
28
  tools?: {
18
29
  Bash?: ToolConfig;
@@ -22,6 +33,12 @@ export type AgentConfig = {
22
33
  Glob?: ToolConfig;
23
34
  Grep?: ToolConfig;
24
35
  };
36
+ /** Include AskUser tool for user clarification */
37
+ askUser?: AskUserConfig;
38
+ /** Include EnterPlanMode and ExitPlanMode tools for interactive planning */
39
+ planMode?: boolean;
40
+ /** Include Skill tool with this config */
41
+ skill?: SkillConfig;
25
42
  /** Include WebSearch tool with this config */
26
43
  webSearch?: WebSearchConfig;
27
44
  /** Include WebFetch tool with this config */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bashkit",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Agentic coding tools for the Vercel AI SDK",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",