bashkit 0.1.1 → 0.1.3

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
@@ -122,7 +122,7 @@ await sandbox.destroy();
122
122
 
123
123
  ## Sub-agents with Task Tool
124
124
 
125
- The Task tool spawns new `generateText` calls for complex subtasks:
125
+ The Task tool spawns new agents for complex subtasks:
126
126
 
127
127
  ```typescript
128
128
  import { createTaskTool } from "bashkit";
@@ -157,6 +157,40 @@ The parent agent calls Task like any other tool:
157
157
  }}
158
158
  ```
159
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
+
160
194
  ## Prompt Caching
161
195
 
162
196
  Enable Anthropic prompt caching to reduce costs on repeated prefixes:
package/README.md CHANGED
@@ -219,7 +219,7 @@ const { tools, planModeState } = createAgentTools(sandbox, {
219
219
 
220
220
  ## Sub-agents with Task Tool
221
221
 
222
- The Task tool spawns new `generateText` calls for complex subtasks:
222
+ The Task tool spawns new agents for complex subtasks:
223
223
 
224
224
  ```typescript
225
225
  import { createTaskTool } from 'bashkit';
@@ -254,6 +254,46 @@ The parent agent calls Task like any other tool:
254
254
  }}
255
255
  ```
256
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
+
257
297
  ## Context Management
258
298
 
259
299
  ### Conversation Compaction
package/dist/index.d.ts CHANGED
@@ -1,8 +1,9 @@
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 { 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, SubagentStepEvent, SubagentTypeConfig, TaskError, TaskOutput, TaskToolConfig, TodoItem, TodoState, TodoWriteError, TodoWriteOutput, WebFetchError, WebFetchOutput, WebFetchToolConfig, WebSearchError, WebSearchOutput, WebSearchResult, WebSearchToolConfig, WriteError, WriteOutput, } from "./tools";
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";
6
7
  export { createAgentTools, createAskUserTool, createBashTool, createEditTool, createEnterPlanModeTool, createExitPlanModeTool, createGlobTool, createGrepTool, createReadTool, createSkillTool, createTaskTool, createTodoWriteTool, createWebFetchTool, createWebSearchTool, createWriteTool, } from "./tools";
7
8
  export type { AgentConfig, AskUserConfig, SkillConfig, ToolConfig, WebFetchConfig, WebSearchConfig, } from "./types";
8
9
  export { DEFAULT_CONFIG } from "./types";
package/dist/index.js CHANGED
@@ -361,10 +361,27 @@ var DEFAULT_CONFIG = {
361
361
  // src/tools/ask-user.ts
362
362
  import { tool, zodSchema } from "ai";
363
363
  import { z } from "zod";
364
+ var questionOptionSchema = z.object({
365
+ label: z.string().describe("The display text for this option. Should be concise (1-5 words). Add '(Recommended)' suffix for suggested options."),
366
+ description: z.string().optional().describe("Explanation of what this option means or its implications.")
367
+ });
368
+ var structuredQuestionSchema = z.object({
369
+ header: z.string().optional().describe("Very short label displayed as a chip/tag (max 12 chars). Examples: 'Auth method', 'Library', 'Approach'."),
370
+ question: z.string().describe("The complete question to ask the user. Should be clear and specific."),
371
+ options: z.array(questionOptionSchema).min(2).max(4).optional().describe("Available choices for this question. 2-4 options. An 'Other' option is automatically available to users."),
372
+ multiSelect: z.boolean().optional().describe("Set to true to allow the user to select multiple options instead of just one.")
373
+ });
364
374
  var askUserInputSchema = z.object({
365
- question: z.string().describe("The question to ask the user. Be specific and concise.")
375
+ question: z.string().optional().describe("Simple question string (for backward compatibility). Use 'questions' for structured multi-choice."),
376
+ questions: z.array(structuredQuestionSchema).min(1).max(4).optional().describe("Structured questions with options (1-4 questions).")
366
377
  });
367
- var ASK_USER_DESCRIPTION = `Ask the user a clarifying question when you need more information to proceed.
378
+ var ASK_USER_DESCRIPTION = `Use this tool when you need to ask the user questions during execution.
379
+
380
+ **Capabilities:**
381
+ - Gather user preferences or requirements
382
+ - Clarify ambiguous instructions
383
+ - Get decisions on implementation choices
384
+ - Offer choices about what direction to take
368
385
 
369
386
  **When to use:**
370
387
  - You need clarification on ambiguous requirements
@@ -377,23 +394,54 @@ var ASK_USER_DESCRIPTION = `Ask the user a clarifying question when you need mor
377
394
  - The question is trivial or can be inferred
378
395
  - You're just being overly cautious
379
396
 
380
- Keep questions specific and actionable. Avoid yes/no questions when you need details.`;
381
- function createAskUserTool(onQuestion) {
397
+ **Simple question format:**
398
+ Use the 'question' parameter for a single free-form question.
399
+
400
+ **Structured questions format:**
401
+ Use the 'questions' parameter for multiple-choice questions with options:
402
+ - 1-4 questions allowed
403
+ - Each question can have 2-4 options with labels and descriptions
404
+ - Use multiSelect: true to allow multiple answers
405
+ - Users can always select "Other" to provide custom text input
406
+ - Place recommended option first and add "(Recommended)" to label`;
407
+ function createAskUserTool(config) {
408
+ const normalizedConfig = typeof config === "function" ? { onQuestion: config } : config ?? {};
382
409
  return tool({
383
410
  description: ASK_USER_DESCRIPTION,
384
411
  inputSchema: zodSchema(askUserInputSchema),
385
- execute: async ({
386
- question
387
- }) => {
412
+ execute: async (input) => {
388
413
  try {
389
- if (onQuestion) {
390
- const answer = await onQuestion(question);
391
- return { answer };
414
+ if (!input.question && !input.questions) {
415
+ return {
416
+ error: "Either 'question' or 'questions' must be provided"
417
+ };
392
418
  }
393
- return {
394
- question,
395
- awaiting_response: true
396
- };
419
+ if (input.questions && input.questions.length > 0) {
420
+ if (normalizedConfig.onStructuredQuestions) {
421
+ const answers = await normalizedConfig.onStructuredQuestions(input.questions);
422
+ const firstKey = Object.keys(answers)[0];
423
+ const firstAnswer = answers[firstKey];
424
+ return {
425
+ answer: Array.isArray(firstAnswer) ? firstAnswer.join(", ") : firstAnswer,
426
+ answers
427
+ };
428
+ }
429
+ return {
430
+ questions: input.questions,
431
+ awaiting_response: true
432
+ };
433
+ }
434
+ if (input.question) {
435
+ if (normalizedConfig.onQuestion) {
436
+ const answer = await normalizedConfig.onQuestion(input.question);
437
+ return { answer };
438
+ }
439
+ return {
440
+ question: input.question,
441
+ awaiting_response: true
442
+ };
443
+ }
444
+ return { error: "No question provided" };
397
445
  } catch (error) {
398
446
  return {
399
447
  error: error instanceof Error ? error.message : "Unknown error"
@@ -412,15 +460,40 @@ var bashInputSchema = z2.object({
412
460
  description: z2.string().optional().describe("Clear, concise description of what this command does in 5-10 words"),
413
461
  run_in_background: z2.boolean().optional().describe("Set to true to run this command in the background")
414
462
  });
415
- var BASH_DESCRIPTION = `Executes bash commands in a persistent shell session with optional timeout and background execution.
463
+ var BASH_DESCRIPTION = `Executes a bash command in a persistent shell session with optional timeout.
416
464
 
417
- **Important guidelines:**
418
- - Always quote file paths containing spaces with double quotes (e.g., cd "/path/with spaces")
419
- - Avoid using search commands like \`find\` and \`grep\` - use the Glob and Grep tools instead
420
- - Avoid using \`cat\`, \`head\`, \`tail\` - use the Read tool instead
421
- - When issuing multiple commands, use \`;\` or \`&&\` to separate them (not newlines)
422
- - If output exceeds 30000 characters, it will be truncated
423
- - Default timeout is 2 minutes; maximum is 10 minutes`;
465
+ IMPORTANT: For file operations (reading, writing, editing, searching, finding files) - use the specialized tools instead of bash commands.
466
+
467
+ Before executing the command, please follow these steps:
468
+
469
+ 1. Directory Verification:
470
+ - If the command will create new directories or files, first use \`ls\` to verify the parent directory exists and is the correct location
471
+ - For example, before running "mkdir foo/bar", first use \`ls foo\` to check that "foo" exists
472
+
473
+ 2. Command Execution:
474
+ - Always quote file paths that contain spaces with double quotes (e.g., cd "/path/with spaces")
475
+ - Examples of proper quoting:
476
+ - cd "/Users/name/My Documents" (correct)
477
+ - cd /Users/name/My Documents (incorrect - will fail)
478
+ - After ensuring proper quoting, execute the command
479
+
480
+ Usage notes:
481
+ - The command argument is required
482
+ - You can specify an optional timeout in milliseconds (max 600000ms / 10 minutes). Default is 120000ms (2 minutes).
483
+ - It is very helpful if you write a clear, concise description of what this command does in 5-10 words
484
+ - If the output exceeds 30000 characters, output will be truncated
485
+ - Avoid using \`find\`, \`grep\`, \`cat\`, \`head\`, \`tail\`, \`sed\`, \`awk\`, or \`echo\` commands. Instead, use dedicated tools:
486
+ - File search: Use Glob (NOT find or ls)
487
+ - Content search: Use Grep (NOT grep or rg)
488
+ - Read files: Use Read (NOT cat/head/tail)
489
+ - Edit files: Use Edit (NOT sed/awk)
490
+ - Write files: Use Write (NOT echo >/cat <<EOF)
491
+ - When issuing multiple commands:
492
+ - If commands are independent, make multiple Bash tool calls in parallel
493
+ - If commands depend on each other, use '&&' to chain them (e.g., \`git add . && git commit -m "message"\`)
494
+ - Use ';' only when you need sequential execution but don't care if earlier commands fail
495
+ - DO NOT use newlines to separate commands
496
+ - Try to maintain your current working directory by using absolute paths and avoiding \`cd\``;
424
497
  function createBashTool(sandbox, config) {
425
498
  const maxOutputLength = config?.maxOutputLength ?? 30000;
426
499
  const defaultTimeout = config?.timeout ?? 120000;
@@ -482,9 +555,26 @@ var editInputSchema = z3.object({
482
555
  new_string: z3.string().describe("The text to replace it with (must be different from old_string)"),
483
556
  replace_all: z3.boolean().optional().describe("Replace all occurrences of old_string (default false)")
484
557
  });
558
+ var EDIT_DESCRIPTION = `Performs exact string replacements in files.
559
+
560
+ **Important guidelines:**
561
+ - You MUST use the Read tool first before editing any file
562
+ - Preserve exact indentation (tabs/spaces) when replacing text
563
+ - The old_string must be unique in the file, or the edit will fail
564
+ - If old_string appears multiple times, either provide more context to make it unique, or use replace_all=true
565
+
566
+ **Parameters:**
567
+ - old_string: The exact text to find and replace (must match exactly, including whitespace)
568
+ - new_string: The replacement text (must be different from old_string)
569
+ - replace_all: Set to true to replace all occurrences (useful for renaming variables)
570
+
571
+ **When to use:**
572
+ - Making targeted changes to existing files
573
+ - Renaming variables or functions (with replace_all=true)
574
+ - Updating specific sections`;
485
575
  function createEditTool(sandbox, config) {
486
576
  return tool3({
487
- description: "Performs exact string replacements in files.",
577
+ description: EDIT_DESCRIPTION,
488
578
  inputSchema: zodSchema3(editInputSchema),
489
579
  execute: async ({
490
580
  file_path,
@@ -546,24 +636,61 @@ import { z as z4 } from "zod";
546
636
  var enterPlanModeInputSchema = z4.object({
547
637
  reason: z4.string().describe("Brief explanation of why you're entering planning mode (e.g., 'Need to explore codebase architecture before implementing feature')")
548
638
  });
549
- var ENTER_PLAN_MODE_DESCRIPTION = `Enter planning mode to explore and design implementation approaches before making changes.
639
+ var ENTER_PLAN_MODE_DESCRIPTION = `Use this tool proactively when you're about to start a non-trivial task. Getting user sign-off on your approach prevents wasted effort and ensures alignment. This tool transitions you into plan mode where you can explore and design an approach for user approval.
550
640
 
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
641
+ ## When to Use This Tool
556
642
 
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
643
+ **Prefer using EnterPlanMode** for tasks unless they're simple. Use it when ANY of these conditions apply:
562
644
 
563
- **When NOT to use:**
564
- - Simple, well-defined tasks
565
- - You already understand the codebase and approach
566
- - User wants immediate execution`;
645
+ 1. **New Functionality**: Adding meaningful new capabilities
646
+ - Example: "Add a new report" - what format? What data?
647
+ - Example: "Add validation" - what rules? What error messages?
648
+
649
+ 2. **Multiple Valid Approaches**: The task can be solved in several different ways
650
+ - Example: "Add caching" - could use Redis, in-memory, file-based, etc.
651
+ - Example: "Improve performance" - many optimization strategies possible
652
+
653
+ 3. **Modifications**: Changes that affect existing behavior or structure
654
+ - Example: "Update the workflow" - what exactly should change?
655
+ - Example: "Refactor this component" - what's the target architecture?
656
+
657
+ 4. **Architectural Decisions**: The task requires choosing between patterns or technologies
658
+ - Example: "Add real-time updates" - WebSockets vs SSE vs polling
659
+ - Example: "Implement state management" - different approaches possible
660
+
661
+ 5. **Multi-File Changes**: The task will likely touch more than 2-3 files
662
+
663
+ 6. **Unclear Requirements**: You need to explore before understanding the full scope
664
+ - Example: "Make this faster" - need to profile and identify bottlenecks
665
+ - Example: "Fix the bug" - need to investigate root cause
666
+
667
+ 7. **User Preferences Matter**: The approach could reasonably go multiple ways
668
+ - If you would use AskUser to clarify the approach, use EnterPlanMode instead
669
+ - Plan mode lets you explore first, then present options with context
670
+
671
+ ## When NOT to Use This Tool
672
+
673
+ Only skip EnterPlanMode for simple tasks:
674
+ - Single-line or few-line fixes (typos, obvious bugs, small tweaks)
675
+ - Adding a single function with clear requirements
676
+ - Tasks where the user has given very specific, detailed instructions
677
+ - Pure research/exploration tasks
678
+
679
+ ## What Happens in Plan Mode
680
+
681
+ In plan mode, you'll:
682
+ 1. Thoroughly explore using Glob, Grep, and Read tools
683
+ 2. Understand existing patterns and architecture
684
+ 3. Design an approach
685
+ 4. Present your plan to the user for approval
686
+ 5. Use AskUser if you need to clarify approaches
687
+ 6. Exit plan mode with ExitPlanMode when ready to execute
688
+
689
+ ## Important Notes
690
+
691
+ - This tool REQUIRES user approval - they must consent to entering plan mode
692
+ - If unsure whether to use it, err on the side of planning - it's better to get alignment upfront than to redo work
693
+ - Users appreciate being consulted before significant changes`;
567
694
  function createEnterPlanModeTool(state, onEnter) {
568
695
  return tool4({
569
696
  description: ENTER_PLAN_MODE_DESCRIPTION,
@@ -602,9 +729,30 @@ import { z as z5 } from "zod";
602
729
  var exitPlanModeInputSchema = z5.object({
603
730
  plan: z5.string().describe("The plan to present to the user for approval")
604
731
  });
732
+ var EXIT_PLAN_MODE_DESCRIPTION = `Use this tool when you are in plan mode and have finished planning and are ready for user approval.
733
+
734
+ ## How This Tool Works
735
+ - Pass your completed plan as a parameter
736
+ - This tool signals that you're done planning and ready for the user to review
737
+ - The user will see your plan and can approve or request changes
738
+
739
+ ## When to Use This Tool
740
+ IMPORTANT: Only use this tool when the task requires planning implementation steps. For research tasks where you're gathering information, searching files, or understanding the codebase - do NOT use this tool.
741
+
742
+ ## Handling Ambiguity in Plans
743
+ Before using this tool, ensure your plan is clear and unambiguous. If there are multiple valid approaches or unclear requirements:
744
+ 1. Ask the user to clarify (use AskUser tool if available)
745
+ 2. Ask about specific implementation choices (e.g., architectural patterns, which library to use)
746
+ 3. Clarify any assumptions that could affect the implementation
747
+ 4. Only proceed with ExitPlanMode after resolving ambiguities
748
+
749
+ ## Examples
750
+ 1. "Search for and understand the implementation of X" - Do NOT use this tool (research task)
751
+ 2. "Help me implement feature Y" - Use this tool after planning the implementation steps
752
+ 3. "Add user authentication" - If unsure about approach (OAuth vs JWT), clarify first, then use this tool`;
605
753
  function createExitPlanModeTool(config, onPlanSubmit) {
606
754
  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.",
755
+ description: EXIT_PLAN_MODE_DESCRIPTION,
608
756
  inputSchema: zodSchema5(exitPlanModeInputSchema),
609
757
  execute: async ({
610
758
  plan
@@ -634,9 +782,17 @@ var globInputSchema = z6.object({
634
782
  pattern: z6.string().describe('Glob pattern to match files (e.g., "**/*.ts", "src/**/*.js", "*.md")'),
635
783
  path: z6.string().optional().describe("Directory to search in (defaults to working directory)")
636
784
  });
785
+ var GLOB_DESCRIPTION = `
786
+ - Fast file pattern matching tool that works with any codebase size
787
+ - Supports glob patterns like "**/*.js" or "src/**/*.ts"
788
+ - Returns matching file paths sorted by modification time
789
+ - Use this tool when you need to find files by name patterns
790
+ - When you are doing an open ended search that may require multiple rounds of globbing and grepping, use the Task tool instead
791
+ - It is always better to speculatively perform multiple searches in parallel if they are potentially useful
792
+ `;
637
793
  function createGlobTool(sandbox, config) {
638
794
  return tool6({
639
- description: "Search for files matching a glob pattern. Returns file paths sorted by modification time. Use this instead of `find` command.",
795
+ description: GLOB_DESCRIPTION,
640
796
  inputSchema: zodSchema6(globInputSchema),
641
797
  execute: async ({
642
798
  pattern,
@@ -674,22 +830,46 @@ function createGlobTool(sandbox, config) {
674
830
  import { tool as tool7, zodSchema as zodSchema7 } from "ai";
675
831
  import { z as z7 } from "zod";
676
832
  var grepInputSchema = z7.object({
677
- pattern: z7.string().describe("The regular expression pattern to search for"),
833
+ pattern: z7.string().describe("The regular expression pattern to search for in file contents"),
678
834
  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")'),
835
+ glob: z7.string().optional().describe('Glob pattern to filter files (e.g. "*.js", "*.{ts,tsx}")'),
680
836
  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"'),
837
+ output_mode: z7.enum(["content", "files_with_matches", "count"]).optional().describe('Output mode: "content" shows matching lines, "files_with_matches" shows file paths (default), "count" shows match counts'),
682
838
  "-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")
839
+ "-n": z7.boolean().optional().describe("Show line numbers in output. Requires output_mode: 'content'. Defaults to true."),
840
+ "-B": z7.number().optional().describe("Number of lines to show before each match. Requires output_mode: 'content'."),
841
+ "-A": z7.number().optional().describe("Number of lines to show after each match. Requires output_mode: 'content'."),
842
+ "-C": z7.number().optional().describe("Number of lines to show before and after each match. Requires output_mode: 'content'."),
843
+ head_limit: z7.number().optional().describe("Limit output to first N lines/entries. Works across all output modes. Defaults to 0 (unlimited)."),
844
+ offset: z7.number().optional().describe("Skip first N lines/entries before applying head_limit. Works across all output modes. Defaults to 0."),
845
+ multiline: z7.boolean().optional().describe("Enable multiline mode where patterns can span lines (requires ripgrep). Default: false.")
689
846
  });
847
+ var GREP_DESCRIPTION = `A powerful content search tool with regex support. Use this instead of running grep commands directly.
848
+
849
+ **Usage:**
850
+ - ALWAYS use Grep for search tasks. NEVER invoke \`grep\` or \`rg\` as a Bash command.
851
+ - Supports regex syntax (e.g., "log.*Error", "function\\s+\\w+")
852
+ - Filter files with glob parameter (e.g., "*.js", "**/*.tsx") or type parameter (e.g., "js", "py", "rust")
853
+
854
+ **Output modes:**
855
+ - "content": Shows matching lines with optional context
856
+ - "files_with_matches": Shows only file paths (default)
857
+ - "count": Shows match counts per file
858
+
859
+ **Context options (content mode only):**
860
+ - -B: Lines to show before each match
861
+ - -A: Lines to show after each match
862
+ - -C: Lines to show before and after each match
863
+
864
+ **Pagination:**
865
+ - Use offset to skip results (useful for pagination)
866
+ - Use head_limit to limit total results returned
867
+
868
+ **Note:** Set useRipgrep: true in config for better performance and multiline support (requires ripgrep installed).`;
690
869
  function createGrepTool(sandbox, config) {
870
+ const useRipgrep = config?.useRipgrep ?? false;
691
871
  return tool7({
692
- description: "Powerful search tool built on ripgrep with regex support. Use this instead of the grep command.",
872
+ description: GREP_DESCRIPTION,
693
873
  inputSchema: zodSchema7(grepInputSchema),
694
874
  execute: async (input) => {
695
875
  const {
@@ -697,13 +877,14 @@ function createGrepTool(sandbox, config) {
697
877
  path,
698
878
  glob,
699
879
  type,
700
- output_mode = "content",
880
+ output_mode = "files_with_matches",
701
881
  "-i": caseInsensitive,
702
882
  "-n": showLineNumbers = true,
703
883
  "-B": beforeContext,
704
884
  "-A": afterContext,
705
885
  "-C": context,
706
886
  head_limit,
887
+ offset = 0,
707
888
  multiline
708
889
  } = input;
709
890
  const searchPath = path || ".";
@@ -713,32 +894,52 @@ function createGrepTool(sandbox, config) {
713
894
  return { error: `Path not allowed: ${searchPath}` };
714
895
  }
715
896
  }
897
+ if (multiline && !useRipgrep) {
898
+ return {
899
+ error: "Multiline mode requires ripgrep. Set useRipgrep: true in config."
900
+ };
901
+ }
716
902
  try {
717
- const flags = [];
718
- if (caseInsensitive)
719
- flags.push("-i");
720
- if (showLineNumbers && output_mode === "content")
721
- flags.push("-n");
722
- if (multiline)
723
- flags.push("-U");
724
- if (context) {
725
- flags.push(`-C ${context}`);
726
- } else {
727
- if (beforeContext)
728
- flags.push(`-B ${beforeContext}`);
729
- if (afterContext)
730
- flags.push(`-A ${afterContext}`);
903
+ let paginationSuffix = "";
904
+ if (offset > 0) {
905
+ paginationSuffix += ` | tail -n +${offset + 1}`;
906
+ }
907
+ if (head_limit && head_limit > 0) {
908
+ paginationSuffix += ` | head -${head_limit}`;
731
909
  }
732
- if (glob)
733
- flags.push(`--include="${glob}"`);
734
- if (type)
735
- flags.push(`--include="*.${type}"`);
736
- const flagStr = flags.join(" ");
737
- const limit = head_limit || 1000;
738
910
  let cmd;
911
+ if (useRipgrep) {
912
+ cmd = buildRipgrepCommand({
913
+ pattern,
914
+ searchPath,
915
+ output_mode,
916
+ caseInsensitive,
917
+ showLineNumbers,
918
+ beforeContext,
919
+ afterContext,
920
+ context,
921
+ glob,
922
+ type,
923
+ multiline,
924
+ paginationSuffix
925
+ });
926
+ } else {
927
+ cmd = buildGrepCommand({
928
+ pattern,
929
+ searchPath,
930
+ output_mode,
931
+ caseInsensitive,
932
+ showLineNumbers,
933
+ beforeContext,
934
+ afterContext,
935
+ context,
936
+ glob,
937
+ type,
938
+ paginationSuffix
939
+ });
940
+ }
941
+ const result = await sandbox.exec(cmd, { timeout: config?.timeout });
739
942
  if (output_mode === "files_with_matches") {
740
- cmd = `grep -rl ${flagStr} "${pattern}" ${searchPath} 2>/dev/null | head -${limit}`;
741
- const result = await sandbox.exec(cmd, { timeout: config?.timeout });
742
943
  const files = result.stdout.split(`
743
944
  `).filter(Boolean);
744
945
  return {
@@ -746,8 +947,6 @@ function createGrepTool(sandbox, config) {
746
947
  count: files.length
747
948
  };
748
949
  } else if (output_mode === "count") {
749
- cmd = `grep -rc ${flagStr} "${pattern}" ${searchPath} 2>/dev/null | grep -v ':0$' | head -${limit}`;
750
- const result = await sandbox.exec(cmd, { timeout: config?.timeout });
751
950
  const lines = result.stdout.split(`
752
951
  `).filter(Boolean);
753
952
  const counts = lines.map((line) => {
@@ -763,8 +962,6 @@ function createGrepTool(sandbox, config) {
763
962
  total
764
963
  };
765
964
  } else {
766
- cmd = `grep -rn ${flagStr} "${pattern}" ${searchPath} 2>/dev/null | head -${limit}`;
767
- const result = await sandbox.exec(cmd, { timeout: config?.timeout });
768
965
  if (!result.stdout.trim()) {
769
966
  return {
770
967
  matches: [],
@@ -798,6 +995,66 @@ function createGrepTool(sandbox, config) {
798
995
  }
799
996
  });
800
997
  }
998
+ function buildRipgrepCommand(opts) {
999
+ const flags = [];
1000
+ if (opts.caseInsensitive)
1001
+ flags.push("-i");
1002
+ if (opts.multiline)
1003
+ flags.push("-U", "--multiline-dotall");
1004
+ if (opts.output_mode === "content") {
1005
+ if (opts.showLineNumbers)
1006
+ flags.push("-n");
1007
+ if (opts.context) {
1008
+ flags.push(`-C ${opts.context}`);
1009
+ } else {
1010
+ if (opts.beforeContext)
1011
+ flags.push(`-B ${opts.beforeContext}`);
1012
+ if (opts.afterContext)
1013
+ flags.push(`-A ${opts.afterContext}`);
1014
+ }
1015
+ }
1016
+ if (opts.glob)
1017
+ flags.push(`-g "${opts.glob}"`);
1018
+ if (opts.type)
1019
+ flags.push(`-t ${opts.type}`);
1020
+ const flagStr = flags.join(" ");
1021
+ if (opts.output_mode === "files_with_matches") {
1022
+ return `rg -l ${flagStr} "${opts.pattern}" ${opts.searchPath} 2>/dev/null${opts.paginationSuffix}`;
1023
+ } else if (opts.output_mode === "count") {
1024
+ return `rg -c ${flagStr} "${opts.pattern}" ${opts.searchPath} 2>/dev/null${opts.paginationSuffix}`;
1025
+ } else {
1026
+ return `rg ${flagStr} "${opts.pattern}" ${opts.searchPath} 2>/dev/null${opts.paginationSuffix}`;
1027
+ }
1028
+ }
1029
+ function buildGrepCommand(opts) {
1030
+ const flags = ["-r"];
1031
+ if (opts.caseInsensitive)
1032
+ flags.push("-i");
1033
+ if (opts.output_mode === "content") {
1034
+ if (opts.showLineNumbers)
1035
+ flags.push("-n");
1036
+ if (opts.context) {
1037
+ flags.push(`-C ${opts.context}`);
1038
+ } else {
1039
+ if (opts.beforeContext)
1040
+ flags.push(`-B ${opts.beforeContext}`);
1041
+ if (opts.afterContext)
1042
+ flags.push(`-A ${opts.afterContext}`);
1043
+ }
1044
+ }
1045
+ if (opts.glob)
1046
+ flags.push(`--include="${opts.glob}"`);
1047
+ if (opts.type)
1048
+ flags.push(`--include="*.${opts.type}"`);
1049
+ const flagStr = flags.join(" ");
1050
+ if (opts.output_mode === "files_with_matches") {
1051
+ return `grep -l ${flagStr} "${opts.pattern}" ${opts.searchPath} 2>/dev/null${opts.paginationSuffix}`;
1052
+ } else if (opts.output_mode === "count") {
1053
+ return `grep -c ${flagStr} "${opts.pattern}" ${opts.searchPath} 2>/dev/null | grep -v ':0$'${opts.paginationSuffix}`;
1054
+ } else {
1055
+ return `grep ${flagStr} "${opts.pattern}" ${opts.searchPath} 2>/dev/null${opts.paginationSuffix}`;
1056
+ }
1057
+ }
801
1058
 
802
1059
  // src/tools/read.ts
803
1060
  import { tool as tool8, zodSchema as zodSchema8 } from "ai";
@@ -807,9 +1064,21 @@ var readInputSchema = z8.object({
807
1064
  offset: z8.number().optional().describe("Line number to start reading from (1-indexed)"),
808
1065
  limit: z8.number().optional().describe("Maximum number of lines to read")
809
1066
  });
1067
+ var READ_DESCRIPTION = `Reads a file from the local filesystem. You can access any file directly by using this tool.
1068
+ Assume this tool is able to read all files on the machine. If the User provides a path to a file assume that path is valid. It is okay to read a file that does not exist; an error will be returned.
1069
+
1070
+ Usage:
1071
+ - The file_path parameter must be an absolute path, not a relative path
1072
+ - By default, it reads up to 500 lines starting from the beginning of the file
1073
+ - You can optionally specify a line offset and limit (especially handy for long files)
1074
+ - Results are returned with line numbers starting at 1
1075
+ - This tool can only read text files, not binary files (images, PDFs, etc.)
1076
+ - This tool can only read files, not directories. To read a directory, use an ls command via the Bash tool.
1077
+ - It is always better to speculatively read multiple potentially useful files in parallel
1078
+ - If you read a file that exists but has empty contents you will receive a warning in place of file contents`;
810
1079
  function createReadTool(sandbox, config) {
811
1080
  return tool8({
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.",
1081
+ description: READ_DESCRIPTION,
813
1082
  inputSchema: zodSchema8(readInputSchema),
814
1083
  execute: async ({
815
1084
  file_path,
@@ -972,10 +1241,26 @@ var webFetchInputSchema = z10.object({
972
1241
  prompt: z10.string().describe("The prompt to run on the fetched content")
973
1242
  });
974
1243
  var RETRYABLE_CODES = [408, 429, 500, 502, 503];
1244
+ var WEB_FETCH_DESCRIPTION = `
1245
+ - Fetches content from a specified URL and processes it using an AI model
1246
+ - Takes a URL and a prompt as input
1247
+ - Fetches the URL content and extracts text
1248
+ - Processes the content with the prompt using the configured model
1249
+ - Returns the model's response about the content
1250
+ - Use this tool when you need to retrieve and analyze web content
1251
+
1252
+ Usage notes:
1253
+ - The URL must be a fully-formed valid URL
1254
+ - HTTP URLs will be automatically upgraded to HTTPS
1255
+ - The prompt should describe what information you want to extract from the page
1256
+ - This tool is read-only and does not modify any files
1257
+ - Results may be summarized if the content is very large
1258
+ - When a URL redirects to a different host, the tool will inform you and provide the redirect URL. You should then make a new WebFetch request with the redirect URL to fetch the content.
1259
+ `;
975
1260
  function createWebFetchTool(config) {
976
1261
  const { apiKey, model } = config;
977
1262
  return tool10({
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.",
1263
+ description: WEB_FETCH_DESCRIPTION,
979
1264
  inputSchema: zodSchema10(webFetchInputSchema),
980
1265
  execute: async (input) => {
981
1266
  const { url, prompt } = input;
@@ -1045,10 +1330,30 @@ var webSearchInputSchema = z11.object({
1045
1330
  blocked_domains: z11.array(z11.string()).optional().describe("Never include results from these domains")
1046
1331
  });
1047
1332
  var RETRYABLE_CODES2 = [408, 429, 500, 502, 503];
1333
+ var WEB_SEARCH_DESCRIPTION = `Searches the web and returns results with links. Use this for accessing up-to-date information beyond your knowledge cutoff.
1334
+
1335
+ **Capabilities:**
1336
+ - Provides current information for recent events and data
1337
+ - Returns results formatted with titles, URLs, and snippets
1338
+ - Supports domain filtering to include or block specific websites
1339
+
1340
+ **CRITICAL REQUIREMENT - You MUST follow this:**
1341
+ After answering using search results, you MUST include a "Sources:" section listing relevant URLs as markdown hyperlinks:
1342
+
1343
+ Sources:
1344
+ - [Source Title 1](https://example.com/1)
1345
+ - [Source Title 2](https://example.com/2)
1346
+
1347
+ **Important - Use the correct year:**
1348
+ When searching for recent information, documentation, or current events, use the current year in your query (e.g., "React documentation 2025" not "2024").
1349
+
1350
+ **Domain filtering:**
1351
+ - allowed_domains: Only include results from these domains
1352
+ - blocked_domains: Never include results from these domains`;
1048
1353
  function createWebSearchTool(config) {
1049
1354
  const { apiKey } = config;
1050
1355
  return tool11({
1051
- description: "Searches the web and returns formatted results. Use this to find current information, documentation, articles, and more.",
1356
+ description: WEB_SEARCH_DESCRIPTION,
1052
1357
  inputSchema: zodSchema11(webSearchInputSchema),
1053
1358
  execute: async (input) => {
1054
1359
  const { query, allowed_domains, blocked_domains } = input;
@@ -1101,9 +1406,21 @@ var writeInputSchema = z12.object({
1101
1406
  file_path: z12.string().describe("Path to the file to write"),
1102
1407
  content: z12.string().describe("Content to write to the file")
1103
1408
  });
1409
+ var WRITE_DESCRIPTION = `Writes content to a file on the filesystem.
1410
+
1411
+ **Important guidelines:**
1412
+ - This tool will overwrite existing files at the provided path
1413
+ - If modifying an existing file, you MUST use the Read tool first to read the file's contents
1414
+ - ALWAYS prefer editing existing files over creating new ones
1415
+ - NEVER proactively create documentation files (*.md) or README files unless explicitly requested
1416
+ - The file_path must be an absolute path, not relative
1417
+
1418
+ **When to use Write vs Edit:**
1419
+ - Use Write for creating new files or completely replacing file contents
1420
+ - Use Edit for making targeted changes to existing files (preferred for modifications)`;
1104
1421
  function createWriteTool(sandbox, config) {
1105
1422
  return tool12({
1106
- description: "Write content to a file. Creates the file if it does not exist, overwrites if it does.",
1423
+ description: WRITE_DESCRIPTION,
1107
1424
  inputSchema: zodSchema12(writeInputSchema),
1108
1425
  execute: async ({
1109
1426
  file_path,
@@ -1139,6 +1456,7 @@ function createWriteTool(sandbox, config) {
1139
1456
  // src/tools/task.ts
1140
1457
  import {
1141
1458
  generateText as generateText2,
1459
+ streamText,
1142
1460
  stepCountIs,
1143
1461
  tool as tool13,
1144
1462
  zodSchema as zodSchema13
@@ -1149,6 +1467,30 @@ var taskInputSchema = z13.object({
1149
1467
  prompt: z13.string().describe("The task for the agent to perform"),
1150
1468
  subagent_type: z13.string().describe("The type of specialized agent to use for this task")
1151
1469
  });
1470
+ var TASK_DESCRIPTION = `Launch a new agent to handle complex, multi-step tasks autonomously.
1471
+
1472
+ The Task tool launches specialized agents (subprocesses) that autonomously handle complex tasks. Each agent type has specific capabilities and tools available to it.
1473
+
1474
+ When using the Task tool, you must specify a subagent_type parameter to select which agent type to use.
1475
+
1476
+ **When NOT to use the Task tool:**
1477
+ - If you want to read a specific file path, use the Read or Glob tool instead, to find the match more quickly
1478
+ - If you are searching for a specific class definition like "class Foo", use the Glob tool instead, to find the match more quickly
1479
+ - If you are searching for code within a specific file or set of 2-3 files, use the Read tool instead, to find the match more quickly
1480
+ - Other tasks that are not related to the available agent types
1481
+
1482
+ **Usage notes:**
1483
+ - Always include a short description (3-5 words) summarizing what the agent will do
1484
+ - Launch multiple agents concurrently whenever possible, to maximize performance; to do that, use a single message with multiple tool uses
1485
+ - When the agent is done, it will return a single message back to you. The result returned by the agent is not visible to the user. To show the user the result, you should send a text message back to the user with a concise summary of the result.
1486
+ - Provide clear, detailed prompts so the agent can work autonomously and return exactly the information you need.
1487
+ - The agent's outputs should generally be trusted
1488
+ - Clearly tell the agent whether you expect it to write code or just to do research (search, file reads, web fetches, etc.), since it is not aware of the user's intent
1489
+ - If the agent description mentions that it should be used proactively, then you should try your best to use it without the user having to ask for it first. Use your judgement.`;
1490
+ var eventCounter = 0;
1491
+ function generateEventId() {
1492
+ return `subagent-${Date.now()}-${++eventCounter}`;
1493
+ }
1152
1494
  function filterTools(allTools, allowedTools) {
1153
1495
  if (!allowedTools)
1154
1496
  return allTools;
@@ -1165,13 +1507,12 @@ function createTaskTool(config) {
1165
1507
  model: defaultModel,
1166
1508
  tools: allTools,
1167
1509
  subagentTypes = {},
1168
- costPerInputToken = 0.000003,
1169
- costPerOutputToken = 0.000015,
1170
1510
  defaultStopWhen,
1171
- defaultOnStepFinish
1511
+ defaultOnStepFinish,
1512
+ streamWriter
1172
1513
  } = config;
1173
1514
  return tool13({
1174
- description: "Launches a new agent to handle complex, multi-step tasks autonomously. Use this for tasks that require multiple steps, research, or specialized expertise.",
1515
+ description: TASK_DESCRIPTION,
1175
1516
  inputSchema: zodSchema13(taskInputSchema),
1176
1517
  execute: async ({
1177
1518
  description,
@@ -1184,13 +1525,88 @@ function createTaskTool(config) {
1184
1525
  const model = typeConfig.model || defaultModel;
1185
1526
  const tools = filterTools(allTools, typeConfig.tools);
1186
1527
  const systemPrompt = typeConfig.systemPrompt;
1187
- const result = await generateText2({
1528
+ const commonOptions = {
1188
1529
  model,
1189
1530
  tools,
1190
1531
  system: systemPrompt,
1191
1532
  prompt,
1192
1533
  stopWhen: typeConfig.stopWhen ?? defaultStopWhen ?? stepCountIs(15),
1193
- prepareStep: typeConfig.prepareStep,
1534
+ prepareStep: typeConfig.prepareStep
1535
+ };
1536
+ if (streamWriter) {
1537
+ const startId = generateEventId();
1538
+ streamWriter.write({
1539
+ type: "data-subagent",
1540
+ id: startId,
1541
+ data: {
1542
+ event: "start",
1543
+ subagent: subagent_type,
1544
+ description
1545
+ }
1546
+ });
1547
+ const result2 = streamText({
1548
+ ...commonOptions,
1549
+ onStepFinish: async (step) => {
1550
+ if (step.toolCalls?.length) {
1551
+ for (const tc of step.toolCalls) {
1552
+ const eventId = generateEventId();
1553
+ streamWriter.write({
1554
+ type: "data-subagent",
1555
+ id: eventId,
1556
+ data: {
1557
+ event: "tool-call",
1558
+ subagent: subagent_type,
1559
+ description,
1560
+ toolName: tc.toolName,
1561
+ args: tc.input
1562
+ }
1563
+ });
1564
+ }
1565
+ }
1566
+ await typeConfig.onStepFinish?.(step);
1567
+ await defaultOnStepFinish?.({
1568
+ subagentType: subagent_type,
1569
+ description,
1570
+ step
1571
+ });
1572
+ }
1573
+ });
1574
+ const text = await result2.text;
1575
+ const usage2 = await result2.usage;
1576
+ const response = await result2.response;
1577
+ streamWriter.write({
1578
+ type: "data-subagent",
1579
+ id: generateEventId(),
1580
+ data: {
1581
+ event: "done",
1582
+ subagent: subagent_type,
1583
+ description
1584
+ }
1585
+ });
1586
+ streamWriter.write({
1587
+ type: "data-subagent",
1588
+ id: generateEventId(),
1589
+ data: {
1590
+ event: "complete",
1591
+ subagent: subagent_type,
1592
+ description,
1593
+ messages: response.messages
1594
+ }
1595
+ });
1596
+ const durationMs2 = Math.round(performance.now() - startTime);
1597
+ return {
1598
+ result: text,
1599
+ usage: usage2.inputTokens !== undefined && usage2.outputTokens !== undefined ? {
1600
+ input_tokens: usage2.inputTokens,
1601
+ output_tokens: usage2.outputTokens
1602
+ } : undefined,
1603
+ duration_ms: durationMs2,
1604
+ subagent: subagent_type,
1605
+ description
1606
+ };
1607
+ }
1608
+ const result = await generateText2({
1609
+ ...commonOptions,
1194
1610
  onStepFinish: async (step) => {
1195
1611
  await typeConfig.onStepFinish?.(step);
1196
1612
  await defaultOnStepFinish?.({
@@ -1205,20 +1621,16 @@ function createTaskTool(config) {
1205
1621
  input_tokens: result.usage.inputTokens,
1206
1622
  output_tokens: result.usage.outputTokens
1207
1623
  } : undefined;
1208
- let totalCostUsd;
1209
- if (usage) {
1210
- totalCostUsd = usage.input_tokens * costPerInputToken + usage.output_tokens * costPerOutputToken;
1211
- }
1212
1624
  return {
1213
1625
  result: result.text,
1214
1626
  usage,
1215
- total_cost_usd: totalCostUsd,
1216
- duration_ms: durationMs
1627
+ duration_ms: durationMs,
1628
+ subagent: subagent_type,
1629
+ description
1217
1630
  };
1218
1631
  } catch (error) {
1219
- return {
1220
- error: error instanceof Error ? error.message : "Unknown error"
1221
- };
1632
+ const errorMessage = error instanceof Error ? error.message : "Unknown error";
1633
+ return { error: errorMessage };
1222
1634
  }
1223
1635
  }
1224
1636
  });
@@ -1233,9 +1645,41 @@ var todoWriteInputSchema = z14.object({
1233
1645
  activeForm: z14.string().describe("Active form of the task description")
1234
1646
  })).describe("The updated todo list")
1235
1647
  });
1648
+ var TODO_WRITE_DESCRIPTION = `Use this tool to create and manage a structured task list for tracking progress. This helps organize complex tasks and gives the user visibility into your work.
1649
+
1650
+ **When to use this tool proactively:**
1651
+ 1. Complex multi-step tasks - When a task requires 3 or more distinct steps
1652
+ 2. Non-trivial tasks - Tasks requiring careful planning or multiple operations
1653
+ 3. User explicitly requests a todo list
1654
+ 4. User provides multiple tasks - Numbered lists or comma-separated items
1655
+ 5. After receiving new instructions - Immediately capture requirements as todos
1656
+ 6. When starting work - Mark task as in_progress BEFORE beginning
1657
+ 7. After completing - Mark as completed and add any follow-up tasks discovered
1658
+
1659
+ **When NOT to use:**
1660
+ 1. Single, straightforward tasks
1661
+ 2. Trivial tasks with no organizational benefit
1662
+ 3. Tasks completable in less than 3 trivial steps
1663
+ 4. Purely conversational or informational requests
1664
+
1665
+ **Task states:**
1666
+ - pending: Not yet started
1667
+ - in_progress: Currently working on (limit to ONE at a time)
1668
+ - completed: Finished successfully
1669
+
1670
+ **Task format (both required):**
1671
+ - content: Imperative form ("Run tests", "Analyze data")
1672
+ - activeForm: Present continuous form ("Running tests", "Analyzing data")
1673
+
1674
+ **Task management rules:**
1675
+ - Update status in real-time as you work
1676
+ - Mark complete IMMEDIATELY after finishing (don't batch)
1677
+ - Keep exactly ONE task in_progress at any time
1678
+ - ONLY mark completed when FULLY accomplished
1679
+ - If blocked/errors, keep in_progress and create new task for the blocker`;
1236
1680
  function createTodoWriteTool(state, config, onUpdate) {
1237
1681
  return tool14({
1238
- description: "Creates and manages a structured task list for tracking progress. Use this to plan complex tasks and track completion.",
1682
+ description: TODO_WRITE_DESCRIPTION,
1239
1683
  inputSchema: zodSchema14(todoWriteInputSchema),
1240
1684
  execute: async ({
1241
1685
  todos
@@ -1,23 +1,53 @@
1
- export interface AskUserOutput {
1
+ export interface QuestionOption {
2
+ label: string;
3
+ description?: string;
4
+ }
5
+ export interface StructuredQuestion {
6
+ header?: string;
7
+ question: string;
8
+ options?: QuestionOption[];
9
+ multiSelect?: boolean;
10
+ }
11
+ export interface AskUserSimpleOutput {
2
12
  question: string;
3
13
  awaiting_response: true;
4
14
  }
15
+ export interface AskUserStructuredOutput {
16
+ questions: StructuredQuestion[];
17
+ awaiting_response: true;
18
+ }
19
+ export type AskUserOutput = AskUserSimpleOutput | AskUserStructuredOutput;
5
20
  export interface AskUserError {
6
21
  error: string;
7
22
  }
23
+ export interface AskUserAnswerOutput {
24
+ answer: string;
25
+ answers?: Record<string, string | string[]>;
26
+ }
8
27
  export type AskUserResponseHandler = (question: string) => Promise<string> | string;
28
+ export type AskUserStructuredHandler = (questions: StructuredQuestion[]) => Promise<Record<string, string | string[]>> | Record<string, string | string[]>;
29
+ export interface AskUserToolConfig {
30
+ /** Handler for simple string questions */
31
+ onQuestion?: AskUserResponseHandler;
32
+ /** Handler for structured questions with options */
33
+ onStructuredQuestions?: AskUserStructuredHandler;
34
+ }
9
35
  /**
10
36
  * Creates a tool for asking the user clarifying questions.
11
37
  *
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
38
+ * Supports both simple string questions and structured multi-choice questions.
16
39
  *
17
- * @param onQuestion - Optional callback to handle the question and return an answer
40
+ * @param config - Configuration with optional handlers for questions
18
41
  */
19
- export declare function createAskUserTool(onQuestion?: AskUserResponseHandler): import("ai").Tool<{
20
- question: string;
21
- }, AskUserOutput | AskUserError | {
22
- answer: string;
23
- }>;
42
+ export declare function createAskUserTool(config?: AskUserToolConfig | AskUserResponseHandler): import("ai").Tool<{
43
+ question?: string | undefined;
44
+ questions?: {
45
+ question: string;
46
+ header?: string | undefined;
47
+ options?: {
48
+ label: string;
49
+ description?: string | undefined;
50
+ }[] | undefined;
51
+ multiSelect?: boolean | undefined;
52
+ }[] | undefined;
53
+ }, AskUserOutput | AskUserError | AskUserAnswerOutput>;
@@ -1,5 +1,5 @@
1
1
  import type { Sandbox } from "../sandbox/interface";
2
- import type { ToolConfig } from "../types";
2
+ import type { GrepToolConfig } from "../types";
3
3
  export interface GrepMatch {
4
4
  file: string;
5
5
  line_number?: number;
@@ -26,7 +26,7 @@ export interface GrepError {
26
26
  error: string;
27
27
  }
28
28
  export type GrepOutput = GrepContentOutput | GrepFilesOutput | GrepCountOutput | GrepError;
29
- export declare function createGrepTool(sandbox: Sandbox, config?: ToolConfig): import("ai").Tool<{
29
+ export declare function createGrepTool(sandbox: Sandbox, config?: GrepToolConfig): import("ai").Tool<{
30
30
  pattern: string;
31
31
  path?: string | undefined;
32
32
  glob?: string | undefined;
@@ -38,5 +38,6 @@ export declare function createGrepTool(sandbox: Sandbox, config?: ToolConfig): i
38
38
  "-A"?: number | undefined;
39
39
  "-C"?: number | undefined;
40
40
  head_limit?: number | undefined;
41
+ offset?: number | undefined;
41
42
  multiline?: boolean | undefined;
42
43
  }, GrepOutput>;
@@ -65,7 +65,7 @@ export type { ReadDirectoryOutput, ReadError, ReadOutput, ReadTextOutput, } from
65
65
  export { createReadTool } from "./read";
66
66
  export type { SkillError, SkillOutput, SkillToolConfig } from "./skill";
67
67
  export { createSkillTool } from "./skill";
68
- export type { SubagentStepEvent, SubagentTypeConfig, TaskError, TaskOutput, TaskToolConfig, } from "./task";
68
+ export type { SubagentEventData, SubagentStepEvent, SubagentTypeConfig, TaskError, TaskOutput, TaskToolConfig, } from "./task";
69
69
  export { createTaskTool } from "./task";
70
70
  export type { TodoItem, TodoState, TodoWriteError, TodoWriteOutput, } from "./todo-write";
71
71
  export { createTodoWriteTool } from "./todo-write";
@@ -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
@@ -7,6 +7,10 @@ export type ToolConfig = {
7
7
  allowedPaths?: string[];
8
8
  blockedCommands?: string[];
9
9
  };
10
+ export type GrepToolConfig = ToolConfig & {
11
+ /** Use ripgrep (rg) instead of grep. Requires ripgrep to be installed. Default: false */
12
+ useRipgrep?: boolean;
13
+ };
10
14
  export type WebSearchConfig = {
11
15
  apiKey: string;
12
16
  };
@@ -31,7 +35,7 @@ export type AgentConfig = {
31
35
  Write?: ToolConfig;
32
36
  Edit?: ToolConfig;
33
37
  Glob?: ToolConfig;
34
- Grep?: ToolConfig;
38
+ Grep?: GrepToolConfig;
35
39
  };
36
40
  /** Include AskUser tool for user clarification */
37
41
  askUser?: AskUserConfig;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bashkit",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "Agentic coding tools for the Vercel AI SDK",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,94 +0,0 @@
1
- /**
2
- * A diagnostic (error/warning) from the language server.
3
- */
4
- export interface LspDiagnostic {
5
- file: string;
6
- line: number;
7
- column: number;
8
- endLine?: number;
9
- endColumn?: number;
10
- severity: "error" | "warning" | "info" | "hint";
11
- message: string;
12
- source?: string;
13
- code?: string | number;
14
- }
15
- /**
16
- * A location in the codebase (for go-to-definition, references, etc.)
17
- */
18
- export interface LspLocation {
19
- file: string;
20
- line: number;
21
- column: number;
22
- endLine?: number;
23
- endColumn?: number;
24
- preview?: string;
25
- }
26
- /**
27
- * Hover information for a symbol.
28
- */
29
- export interface LspHoverInfo {
30
- contents: string;
31
- range?: {
32
- startLine: number;
33
- startColumn: number;
34
- endLine: number;
35
- endColumn: number;
36
- };
37
- }
38
- /**
39
- * Interface that LSP providers must implement.
40
- * This abstracts over different LSP implementations (vscode, standalone servers, etc.)
41
- */
42
- export interface LspProvider {
43
- /** Get diagnostics for a file or the entire workspace */
44
- getDiagnostics(file?: string): Promise<LspDiagnostic[]>;
45
- /** Go to definition of symbol at position */
46
- getDefinition(file: string, line: number, column: number): Promise<LspLocation[]>;
47
- /** Find all references to symbol at position */
48
- getReferences(file: string, line: number, column: number): Promise<LspLocation[]>;
49
- /** Get hover information for symbol at position */
50
- getHover(file: string, line: number, column: number): Promise<LspHoverInfo | null>;
51
- /** Get available actions at position (optional) */
52
- getCodeActions?(file: string, line: number, column: number): Promise<Array<{
53
- title: string;
54
- kind?: string;
55
- }>>;
56
- }
57
- export interface LspDiagnosticsOutput {
58
- type: "diagnostics";
59
- diagnostics: LspDiagnostic[];
60
- counts: {
61
- errors: number;
62
- warnings: number;
63
- info: number;
64
- hints: number;
65
- };
66
- }
67
- export interface LspDefinitionOutput {
68
- type: "definition";
69
- locations: LspLocation[];
70
- }
71
- export interface LspReferencesOutput {
72
- type: "references";
73
- locations: LspLocation[];
74
- count: number;
75
- }
76
- export interface LspHoverOutput {
77
- type: "hover";
78
- info: LspHoverInfo | null;
79
- }
80
- export interface LspError {
81
- error: string;
82
- }
83
- export type LspOutput = LspDiagnosticsOutput | LspDefinitionOutput | LspReferencesOutput | LspHoverOutput | LspError;
84
- /**
85
- * Creates an LSP tool for code intelligence operations.
86
- *
87
- * @param provider - An implementation of LspProvider that interfaces with language servers
88
- */
89
- export declare function createLspTool(provider: LspProvider): import("ai").Tool<{
90
- action: "diagnostics" | "definition" | "references" | "hover";
91
- file?: string | undefined;
92
- line?: number | undefined;
93
- column?: number | undefined;
94
- }, LspOutput>;