bashkit 0.1.2 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +108 -0
- package/README.md +148 -1
- package/dist/cache/cached.d.ts +37 -0
- package/dist/cache/index.d.ts +6 -0
- package/dist/cache/lru.d.ts +25 -0
- package/dist/cache/redis.d.ts +41 -0
- package/dist/cache/types.d.ts +53 -0
- package/dist/index.d.ts +7 -5
- package/dist/index.js +745 -119
- package/dist/middleware/anthropic-cache.d.ts +20 -1
- package/dist/middleware/index.d.ts +1 -1
- package/dist/skills/index.d.ts +1 -1
- package/dist/skills/loader.d.ts +36 -0
- package/dist/tools/ask-user.d.ts +41 -11
- package/dist/tools/exit-plan-mode.d.ts +1 -2
- package/dist/tools/grep.d.ts +3 -2
- package/dist/tools/index.d.ts +2 -2
- package/dist/tools/task.d.ts +11 -1
- package/dist/tools/todo-write.d.ts +1 -2
- package/dist/tools/web-constants.d.ts +5 -0
- package/dist/tools/web-fetch.d.ts +2 -6
- package/dist/tools/web-search.d.ts +2 -4
- package/dist/types.d.ts +58 -4
- package/dist/utils/http-constants.d.ts +5 -0
- package/package.json +4 -3
package/dist/index.js
CHANGED
|
@@ -18,10 +18,8 @@ var __toESM = (mod, isNodeMode, target) => {
|
|
|
18
18
|
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
19
19
|
|
|
20
20
|
// src/middleware/anthropic-cache.ts
|
|
21
|
-
function
|
|
22
|
-
if (!message)
|
|
23
|
-
return;
|
|
24
|
-
if (!("content" in message))
|
|
21
|
+
function addCacheMarker(message) {
|
|
22
|
+
if (!message || !("content" in message))
|
|
25
23
|
return;
|
|
26
24
|
const content = message.content;
|
|
27
25
|
if (!content || !Array.isArray(content))
|
|
@@ -36,21 +34,20 @@ function ensureCacheMarker(message) {
|
|
|
36
34
|
}
|
|
37
35
|
};
|
|
38
36
|
}
|
|
37
|
+
function applyCacheMarkers(params) {
|
|
38
|
+
const messages = params.prompt;
|
|
39
|
+
if (!messages || messages.length === 0)
|
|
40
|
+
return params;
|
|
41
|
+
addCacheMarker(messages.at(-1));
|
|
42
|
+
addCacheMarker(messages.slice(0, -1).findLast((m) => m.role !== "assistant"));
|
|
43
|
+
return params;
|
|
44
|
+
}
|
|
45
|
+
var anthropicPromptCacheMiddlewareV2 = {
|
|
46
|
+
transformParams: async ({ params }) => applyCacheMarkers(params)
|
|
47
|
+
};
|
|
39
48
|
var anthropicPromptCacheMiddleware = {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
}) => {
|
|
43
|
-
const messages = params.prompt;
|
|
44
|
-
if (!messages || messages.length === 0) {
|
|
45
|
-
return params;
|
|
46
|
-
}
|
|
47
|
-
ensureCacheMarker(messages.at(-1));
|
|
48
|
-
ensureCacheMarker(messages.slice(0, -1).findLast((m) => m.role !== "assistant"));
|
|
49
|
-
return {
|
|
50
|
-
...params,
|
|
51
|
-
prompt: messages
|
|
52
|
-
};
|
|
53
|
-
}
|
|
49
|
+
specificationVersion: "v3",
|
|
50
|
+
transformParams: async ({ params }) => applyCacheMarkers(params)
|
|
54
51
|
};
|
|
55
52
|
// src/sandbox/e2b.ts
|
|
56
53
|
import { Sandbox as E2BSandboxSDK } from "@e2b/code-interpreter";
|
|
@@ -349,6 +346,132 @@ function createVercelSandbox(config = {}) {
|
|
|
349
346
|
}
|
|
350
347
|
};
|
|
351
348
|
}
|
|
349
|
+
// src/cache/lru.ts
|
|
350
|
+
class LRUCacheStore {
|
|
351
|
+
cache = new Map;
|
|
352
|
+
maxSize;
|
|
353
|
+
constructor(maxSize = 1000) {
|
|
354
|
+
this.maxSize = maxSize;
|
|
355
|
+
}
|
|
356
|
+
get(key) {
|
|
357
|
+
const entry = this.cache.get(key);
|
|
358
|
+
if (entry) {
|
|
359
|
+
this.cache.delete(key);
|
|
360
|
+
this.cache.set(key, entry);
|
|
361
|
+
}
|
|
362
|
+
return entry;
|
|
363
|
+
}
|
|
364
|
+
set(key, entry) {
|
|
365
|
+
if (this.cache.has(key)) {
|
|
366
|
+
this.cache.delete(key);
|
|
367
|
+
} else if (this.cache.size >= this.maxSize) {
|
|
368
|
+
const firstKey = this.cache.keys().next().value;
|
|
369
|
+
if (firstKey !== undefined) {
|
|
370
|
+
this.cache.delete(firstKey);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
this.cache.set(key, entry);
|
|
374
|
+
}
|
|
375
|
+
delete(key) {
|
|
376
|
+
this.cache.delete(key);
|
|
377
|
+
}
|
|
378
|
+
clear() {
|
|
379
|
+
this.cache.clear();
|
|
380
|
+
}
|
|
381
|
+
size() {
|
|
382
|
+
return this.cache.size;
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
// src/cache/cached.ts
|
|
386
|
+
import { createHash } from "crypto";
|
|
387
|
+
function defaultKeyGenerator(toolName, params) {
|
|
388
|
+
const sortedKeys = params && typeof params === "object" ? Object.keys(params).sort() : undefined;
|
|
389
|
+
const serialized = JSON.stringify(params, sortedKeys);
|
|
390
|
+
const hash = createHash("sha256").update(serialized).digest("hex").slice(0, 16);
|
|
391
|
+
return `${toolName}:${hash}`;
|
|
392
|
+
}
|
|
393
|
+
function cached(tool, toolName, options = {}) {
|
|
394
|
+
const {
|
|
395
|
+
ttl = 5 * 60 * 1000,
|
|
396
|
+
store = new LRUCacheStore,
|
|
397
|
+
keyGenerator = defaultKeyGenerator,
|
|
398
|
+
debug = false,
|
|
399
|
+
onHit,
|
|
400
|
+
onMiss
|
|
401
|
+
} = options;
|
|
402
|
+
let hits = 0;
|
|
403
|
+
let misses = 0;
|
|
404
|
+
const log = debug ? console.log.bind(console) : () => {};
|
|
405
|
+
const cachedTool = {
|
|
406
|
+
...tool,
|
|
407
|
+
execute: async (params, execOptions) => {
|
|
408
|
+
const key = keyGenerator(toolName, params);
|
|
409
|
+
const now = Date.now();
|
|
410
|
+
const entry = await store.get(key);
|
|
411
|
+
if (entry && now - entry.timestamp < ttl) {
|
|
412
|
+
hits++;
|
|
413
|
+
log(`[Cache] HIT ${toolName}:${key.slice(-8)}`);
|
|
414
|
+
onHit?.(toolName, key);
|
|
415
|
+
return entry.result;
|
|
416
|
+
}
|
|
417
|
+
misses++;
|
|
418
|
+
log(`[Cache] MISS ${toolName}:${key.slice(-8)}`);
|
|
419
|
+
onMiss?.(toolName, key);
|
|
420
|
+
if (!tool.execute) {
|
|
421
|
+
throw new Error(`Tool ${toolName} has no execute function`);
|
|
422
|
+
}
|
|
423
|
+
const result = await tool.execute(params, execOptions);
|
|
424
|
+
if (result && typeof result === "object" && !("error" in result)) {
|
|
425
|
+
await store.set(key, { result, timestamp: now });
|
|
426
|
+
log(`[Cache] STORED ${toolName}:${key.slice(-8)}`);
|
|
427
|
+
}
|
|
428
|
+
return result;
|
|
429
|
+
},
|
|
430
|
+
async getStats() {
|
|
431
|
+
const total = hits + misses;
|
|
432
|
+
const size = await store.size?.() ?? 0;
|
|
433
|
+
return {
|
|
434
|
+
hits,
|
|
435
|
+
misses,
|
|
436
|
+
hitRate: total > 0 ? hits / total : 0,
|
|
437
|
+
size
|
|
438
|
+
};
|
|
439
|
+
},
|
|
440
|
+
async clearCache(key) {
|
|
441
|
+
if (key) {
|
|
442
|
+
await store.delete(key);
|
|
443
|
+
} else {
|
|
444
|
+
await store.clear();
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
};
|
|
448
|
+
return cachedTool;
|
|
449
|
+
}
|
|
450
|
+
// src/cache/redis.ts
|
|
451
|
+
function createRedisCacheStore(client, options = {}) {
|
|
452
|
+
const { prefix = "bashkit:" } = options;
|
|
453
|
+
return {
|
|
454
|
+
async get(key) {
|
|
455
|
+
const data = await client.get(`${prefix}${key}`);
|
|
456
|
+
return data ? JSON.parse(data) : undefined;
|
|
457
|
+
},
|
|
458
|
+
async set(key, entry) {
|
|
459
|
+
await client.set(`${prefix}${key}`, JSON.stringify(entry));
|
|
460
|
+
},
|
|
461
|
+
async delete(key) {
|
|
462
|
+
await client.del(`${prefix}${key}`);
|
|
463
|
+
},
|
|
464
|
+
async clear() {
|
|
465
|
+
const keys = await client.keys(`${prefix}*`);
|
|
466
|
+
if (keys.length)
|
|
467
|
+
await client.del(keys);
|
|
468
|
+
},
|
|
469
|
+
async size() {
|
|
470
|
+
const keys = await client.keys(`${prefix}*`);
|
|
471
|
+
return keys.length;
|
|
472
|
+
}
|
|
473
|
+
};
|
|
474
|
+
}
|
|
352
475
|
// src/types.ts
|
|
353
476
|
var DEFAULT_CONFIG = {
|
|
354
477
|
defaultTimeout: 120000,
|
|
@@ -361,10 +484,27 @@ var DEFAULT_CONFIG = {
|
|
|
361
484
|
// src/tools/ask-user.ts
|
|
362
485
|
import { tool, zodSchema } from "ai";
|
|
363
486
|
import { z } from "zod";
|
|
487
|
+
var questionOptionSchema = z.object({
|
|
488
|
+
label: z.string().describe("The display text for this option. Should be concise (1-5 words). Add '(Recommended)' suffix for suggested options."),
|
|
489
|
+
description: z.string().optional().describe("Explanation of what this option means or its implications.")
|
|
490
|
+
});
|
|
491
|
+
var structuredQuestionSchema = z.object({
|
|
492
|
+
header: z.string().optional().describe("Very short label displayed as a chip/tag (max 12 chars). Examples: 'Auth method', 'Library', 'Approach'."),
|
|
493
|
+
question: z.string().describe("The complete question to ask the user. Should be clear and specific."),
|
|
494
|
+
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."),
|
|
495
|
+
multiSelect: z.boolean().optional().describe("Set to true to allow the user to select multiple options instead of just one.")
|
|
496
|
+
});
|
|
364
497
|
var askUserInputSchema = z.object({
|
|
365
|
-
question: z.string().describe("
|
|
498
|
+
question: z.string().optional().describe("Simple question string (for backward compatibility). Use 'questions' for structured multi-choice."),
|
|
499
|
+
questions: z.array(structuredQuestionSchema).min(1).max(4).optional().describe("Structured questions with options (1-4 questions).")
|
|
366
500
|
});
|
|
367
|
-
var ASK_USER_DESCRIPTION = `
|
|
501
|
+
var ASK_USER_DESCRIPTION = `Use this tool when you need to ask the user questions during execution.
|
|
502
|
+
|
|
503
|
+
**Capabilities:**
|
|
504
|
+
- Gather user preferences or requirements
|
|
505
|
+
- Clarify ambiguous instructions
|
|
506
|
+
- Get decisions on implementation choices
|
|
507
|
+
- Offer choices about what direction to take
|
|
368
508
|
|
|
369
509
|
**When to use:**
|
|
370
510
|
- You need clarification on ambiguous requirements
|
|
@@ -377,23 +517,54 @@ var ASK_USER_DESCRIPTION = `Ask the user a clarifying question when you need mor
|
|
|
377
517
|
- The question is trivial or can be inferred
|
|
378
518
|
- You're just being overly cautious
|
|
379
519
|
|
|
380
|
-
|
|
381
|
-
|
|
520
|
+
**Simple question format:**
|
|
521
|
+
Use the 'question' parameter for a single free-form question.
|
|
522
|
+
|
|
523
|
+
**Structured questions format:**
|
|
524
|
+
Use the 'questions' parameter for multiple-choice questions with options:
|
|
525
|
+
- 1-4 questions allowed
|
|
526
|
+
- Each question can have 2-4 options with labels and descriptions
|
|
527
|
+
- Use multiSelect: true to allow multiple answers
|
|
528
|
+
- Users can always select "Other" to provide custom text input
|
|
529
|
+
- Place recommended option first and add "(Recommended)" to label`;
|
|
530
|
+
function createAskUserTool(config) {
|
|
531
|
+
const normalizedConfig = typeof config === "function" ? { onQuestion: config } : config ?? {};
|
|
382
532
|
return tool({
|
|
383
533
|
description: ASK_USER_DESCRIPTION,
|
|
384
534
|
inputSchema: zodSchema(askUserInputSchema),
|
|
385
|
-
execute: async ({
|
|
386
|
-
question
|
|
387
|
-
}) => {
|
|
535
|
+
execute: async (input) => {
|
|
388
536
|
try {
|
|
389
|
-
if (
|
|
390
|
-
|
|
391
|
-
|
|
537
|
+
if (!input.question && !input.questions) {
|
|
538
|
+
return {
|
|
539
|
+
error: "Either 'question' or 'questions' must be provided"
|
|
540
|
+
};
|
|
392
541
|
}
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
542
|
+
if (input.questions && input.questions.length > 0) {
|
|
543
|
+
if (normalizedConfig.onStructuredQuestions) {
|
|
544
|
+
const answers = await normalizedConfig.onStructuredQuestions(input.questions);
|
|
545
|
+
const firstKey = Object.keys(answers)[0];
|
|
546
|
+
const firstAnswer = answers[firstKey];
|
|
547
|
+
return {
|
|
548
|
+
answer: Array.isArray(firstAnswer) ? firstAnswer.join(", ") : firstAnswer,
|
|
549
|
+
answers
|
|
550
|
+
};
|
|
551
|
+
}
|
|
552
|
+
return {
|
|
553
|
+
questions: input.questions,
|
|
554
|
+
awaiting_response: true
|
|
555
|
+
};
|
|
556
|
+
}
|
|
557
|
+
if (input.question) {
|
|
558
|
+
if (normalizedConfig.onQuestion) {
|
|
559
|
+
const answer = await normalizedConfig.onQuestion(input.question);
|
|
560
|
+
return { answer };
|
|
561
|
+
}
|
|
562
|
+
return {
|
|
563
|
+
question: input.question,
|
|
564
|
+
awaiting_response: true
|
|
565
|
+
};
|
|
566
|
+
}
|
|
567
|
+
return { error: "No question provided" };
|
|
397
568
|
} catch (error) {
|
|
398
569
|
return {
|
|
399
570
|
error: error instanceof Error ? error.message : "Unknown error"
|
|
@@ -412,21 +583,49 @@ var bashInputSchema = z2.object({
|
|
|
412
583
|
description: z2.string().optional().describe("Clear, concise description of what this command does in 5-10 words"),
|
|
413
584
|
run_in_background: z2.boolean().optional().describe("Set to true to run this command in the background")
|
|
414
585
|
});
|
|
415
|
-
var BASH_DESCRIPTION = `Executes bash
|
|
586
|
+
var BASH_DESCRIPTION = `Executes a bash command in a persistent shell session with optional timeout.
|
|
416
587
|
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
- If
|
|
423
|
-
-
|
|
588
|
+
IMPORTANT: For file operations (reading, writing, editing, searching, finding files) - use the specialized tools instead of bash commands.
|
|
589
|
+
|
|
590
|
+
Before executing the command, please follow these steps:
|
|
591
|
+
|
|
592
|
+
1. Directory Verification:
|
|
593
|
+
- If the command will create new directories or files, first use \`ls\` to verify the parent directory exists and is the correct location
|
|
594
|
+
- For example, before running "mkdir foo/bar", first use \`ls foo\` to check that "foo" exists
|
|
595
|
+
|
|
596
|
+
2. Command Execution:
|
|
597
|
+
- Always quote file paths that contain spaces with double quotes (e.g., cd "/path/with spaces")
|
|
598
|
+
- Examples of proper quoting:
|
|
599
|
+
- cd "/Users/name/My Documents" (correct)
|
|
600
|
+
- cd /Users/name/My Documents (incorrect - will fail)
|
|
601
|
+
- After ensuring proper quoting, execute the command
|
|
602
|
+
|
|
603
|
+
Usage notes:
|
|
604
|
+
- The command argument is required
|
|
605
|
+
- You can specify an optional timeout in milliseconds (max 600000ms / 10 minutes). Default is 120000ms (2 minutes).
|
|
606
|
+
- It is very helpful if you write a clear, concise description of what this command does in 5-10 words
|
|
607
|
+
- If the output exceeds 30000 characters, output will be truncated
|
|
608
|
+
- Avoid using \`find\`, \`grep\`, \`cat\`, \`head\`, \`tail\`, \`sed\`, \`awk\`, or \`echo\` commands. Instead, use dedicated tools:
|
|
609
|
+
- File search: Use Glob (NOT find or ls)
|
|
610
|
+
- Content search: Use Grep (NOT grep or rg)
|
|
611
|
+
- Read files: Use Read (NOT cat/head/tail)
|
|
612
|
+
- Edit files: Use Edit (NOT sed/awk)
|
|
613
|
+
- Write files: Use Write (NOT echo >/cat <<EOF)
|
|
614
|
+
- When issuing multiple commands:
|
|
615
|
+
- If commands are independent, make multiple Bash tool calls in parallel
|
|
616
|
+
- If commands depend on each other, use '&&' to chain them (e.g., \`git add . && git commit -m "message"\`)
|
|
617
|
+
- Use ';' only when you need sequential execution but don't care if earlier commands fail
|
|
618
|
+
- DO NOT use newlines to separate commands
|
|
619
|
+
- Try to maintain your current working directory by using absolute paths and avoiding \`cd\``;
|
|
424
620
|
function createBashTool(sandbox, config) {
|
|
425
621
|
const maxOutputLength = config?.maxOutputLength ?? 30000;
|
|
426
622
|
const defaultTimeout = config?.timeout ?? 120000;
|
|
427
623
|
return tool2({
|
|
428
624
|
description: BASH_DESCRIPTION,
|
|
429
625
|
inputSchema: zodSchema2(bashInputSchema),
|
|
626
|
+
strict: config?.strict,
|
|
627
|
+
needsApproval: config?.needsApproval,
|
|
628
|
+
providerOptions: config?.providerOptions,
|
|
430
629
|
execute: async ({
|
|
431
630
|
command,
|
|
432
631
|
timeout,
|
|
@@ -482,10 +681,30 @@ var editInputSchema = z3.object({
|
|
|
482
681
|
new_string: z3.string().describe("The text to replace it with (must be different from old_string)"),
|
|
483
682
|
replace_all: z3.boolean().optional().describe("Replace all occurrences of old_string (default false)")
|
|
484
683
|
});
|
|
684
|
+
var EDIT_DESCRIPTION = `Performs exact string replacements in files.
|
|
685
|
+
|
|
686
|
+
**Important guidelines:**
|
|
687
|
+
- You MUST use the Read tool first before editing any file
|
|
688
|
+
- Preserve exact indentation (tabs/spaces) when replacing text
|
|
689
|
+
- The old_string must be unique in the file, or the edit will fail
|
|
690
|
+
- If old_string appears multiple times, either provide more context to make it unique, or use replace_all=true
|
|
691
|
+
|
|
692
|
+
**Parameters:**
|
|
693
|
+
- old_string: The exact text to find and replace (must match exactly, including whitespace)
|
|
694
|
+
- new_string: The replacement text (must be different from old_string)
|
|
695
|
+
- replace_all: Set to true to replace all occurrences (useful for renaming variables)
|
|
696
|
+
|
|
697
|
+
**When to use:**
|
|
698
|
+
- Making targeted changes to existing files
|
|
699
|
+
- Renaming variables or functions (with replace_all=true)
|
|
700
|
+
- Updating specific sections`;
|
|
485
701
|
function createEditTool(sandbox, config) {
|
|
486
702
|
return tool3({
|
|
487
|
-
description:
|
|
703
|
+
description: EDIT_DESCRIPTION,
|
|
488
704
|
inputSchema: zodSchema3(editInputSchema),
|
|
705
|
+
strict: config?.strict,
|
|
706
|
+
needsApproval: config?.needsApproval,
|
|
707
|
+
providerOptions: config?.providerOptions,
|
|
489
708
|
execute: async ({
|
|
490
709
|
file_path,
|
|
491
710
|
old_string,
|
|
@@ -546,24 +765,61 @@ import { z as z4 } from "zod";
|
|
|
546
765
|
var enterPlanModeInputSchema = z4.object({
|
|
547
766
|
reason: z4.string().describe("Brief explanation of why you're entering planning mode (e.g., 'Need to explore codebase architecture before implementing feature')")
|
|
548
767
|
});
|
|
549
|
-
var ENTER_PLAN_MODE_DESCRIPTION = `
|
|
768
|
+
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
769
|
|
|
551
|
-
|
|
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
|
|
770
|
+
## When to Use This Tool
|
|
556
771
|
|
|
557
|
-
**
|
|
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
|
|
772
|
+
**Prefer using EnterPlanMode** for tasks unless they're simple. Use it when ANY of these conditions apply:
|
|
562
773
|
|
|
563
|
-
**
|
|
564
|
-
-
|
|
565
|
-
-
|
|
566
|
-
|
|
774
|
+
1. **New Functionality**: Adding meaningful new capabilities
|
|
775
|
+
- Example: "Add a new report" - what format? What data?
|
|
776
|
+
- Example: "Add validation" - what rules? What error messages?
|
|
777
|
+
|
|
778
|
+
2. **Multiple Valid Approaches**: The task can be solved in several different ways
|
|
779
|
+
- Example: "Add caching" - could use Redis, in-memory, file-based, etc.
|
|
780
|
+
- Example: "Improve performance" - many optimization strategies possible
|
|
781
|
+
|
|
782
|
+
3. **Modifications**: Changes that affect existing behavior or structure
|
|
783
|
+
- Example: "Update the workflow" - what exactly should change?
|
|
784
|
+
- Example: "Refactor this component" - what's the target architecture?
|
|
785
|
+
|
|
786
|
+
4. **Architectural Decisions**: The task requires choosing between patterns or technologies
|
|
787
|
+
- Example: "Add real-time updates" - WebSockets vs SSE vs polling
|
|
788
|
+
- Example: "Implement state management" - different approaches possible
|
|
789
|
+
|
|
790
|
+
5. **Multi-File Changes**: The task will likely touch more than 2-3 files
|
|
791
|
+
|
|
792
|
+
6. **Unclear Requirements**: You need to explore before understanding the full scope
|
|
793
|
+
- Example: "Make this faster" - need to profile and identify bottlenecks
|
|
794
|
+
- Example: "Fix the bug" - need to investigate root cause
|
|
795
|
+
|
|
796
|
+
7. **User Preferences Matter**: The approach could reasonably go multiple ways
|
|
797
|
+
- If you would use AskUser to clarify the approach, use EnterPlanMode instead
|
|
798
|
+
- Plan mode lets you explore first, then present options with context
|
|
799
|
+
|
|
800
|
+
## When NOT to Use This Tool
|
|
801
|
+
|
|
802
|
+
Only skip EnterPlanMode for simple tasks:
|
|
803
|
+
- Single-line or few-line fixes (typos, obvious bugs, small tweaks)
|
|
804
|
+
- Adding a single function with clear requirements
|
|
805
|
+
- Tasks where the user has given very specific, detailed instructions
|
|
806
|
+
- Pure research/exploration tasks
|
|
807
|
+
|
|
808
|
+
## What Happens in Plan Mode
|
|
809
|
+
|
|
810
|
+
In plan mode, you'll:
|
|
811
|
+
1. Thoroughly explore using Glob, Grep, and Read tools
|
|
812
|
+
2. Understand existing patterns and architecture
|
|
813
|
+
3. Design an approach
|
|
814
|
+
4. Present your plan to the user for approval
|
|
815
|
+
5. Use AskUser if you need to clarify approaches
|
|
816
|
+
6. Exit plan mode with ExitPlanMode when ready to execute
|
|
817
|
+
|
|
818
|
+
## Important Notes
|
|
819
|
+
|
|
820
|
+
- This tool REQUIRES user approval - they must consent to entering plan mode
|
|
821
|
+
- If unsure whether to use it, err on the side of planning - it's better to get alignment upfront than to redo work
|
|
822
|
+
- Users appreciate being consulted before significant changes`;
|
|
567
823
|
function createEnterPlanModeTool(state, onEnter) {
|
|
568
824
|
return tool4({
|
|
569
825
|
description: ENTER_PLAN_MODE_DESCRIPTION,
|
|
@@ -602,9 +858,30 @@ import { z as z5 } from "zod";
|
|
|
602
858
|
var exitPlanModeInputSchema = z5.object({
|
|
603
859
|
plan: z5.string().describe("The plan to present to the user for approval")
|
|
604
860
|
});
|
|
605
|
-
|
|
861
|
+
var EXIT_PLAN_MODE_DESCRIPTION = `Use this tool when you are in plan mode and have finished planning and are ready for user approval.
|
|
862
|
+
|
|
863
|
+
## How This Tool Works
|
|
864
|
+
- Pass your completed plan as a parameter
|
|
865
|
+
- This tool signals that you're done planning and ready for the user to review
|
|
866
|
+
- The user will see your plan and can approve or request changes
|
|
867
|
+
|
|
868
|
+
## When to Use This Tool
|
|
869
|
+
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.
|
|
870
|
+
|
|
871
|
+
## Handling Ambiguity in Plans
|
|
872
|
+
Before using this tool, ensure your plan is clear and unambiguous. If there are multiple valid approaches or unclear requirements:
|
|
873
|
+
1. Ask the user to clarify (use AskUser tool if available)
|
|
874
|
+
2. Ask about specific implementation choices (e.g., architectural patterns, which library to use)
|
|
875
|
+
3. Clarify any assumptions that could affect the implementation
|
|
876
|
+
4. Only proceed with ExitPlanMode after resolving ambiguities
|
|
877
|
+
|
|
878
|
+
## Examples
|
|
879
|
+
1. "Search for and understand the implementation of X" - Do NOT use this tool (research task)
|
|
880
|
+
2. "Help me implement feature Y" - Use this tool after planning the implementation steps
|
|
881
|
+
3. "Add user authentication" - If unsure about approach (OAuth vs JWT), clarify first, then use this tool`;
|
|
882
|
+
function createExitPlanModeTool(onPlanSubmit) {
|
|
606
883
|
return tool5({
|
|
607
|
-
description:
|
|
884
|
+
description: EXIT_PLAN_MODE_DESCRIPTION,
|
|
608
885
|
inputSchema: zodSchema5(exitPlanModeInputSchema),
|
|
609
886
|
execute: async ({
|
|
610
887
|
plan
|
|
@@ -634,10 +911,21 @@ var globInputSchema = z6.object({
|
|
|
634
911
|
pattern: z6.string().describe('Glob pattern to match files (e.g., "**/*.ts", "src/**/*.js", "*.md")'),
|
|
635
912
|
path: z6.string().optional().describe("Directory to search in (defaults to working directory)")
|
|
636
913
|
});
|
|
914
|
+
var GLOB_DESCRIPTION = `
|
|
915
|
+
- Fast file pattern matching tool that works with any codebase size
|
|
916
|
+
- Supports glob patterns like "**/*.js" or "src/**/*.ts"
|
|
917
|
+
- Returns matching file paths sorted by modification time
|
|
918
|
+
- Use this tool when you need to find files by name patterns
|
|
919
|
+
- When you are doing an open ended search that may require multiple rounds of globbing and grepping, use the Task tool instead
|
|
920
|
+
- It is always better to speculatively perform multiple searches in parallel if they are potentially useful
|
|
921
|
+
`;
|
|
637
922
|
function createGlobTool(sandbox, config) {
|
|
638
923
|
return tool6({
|
|
639
|
-
description:
|
|
924
|
+
description: GLOB_DESCRIPTION,
|
|
640
925
|
inputSchema: zodSchema6(globInputSchema),
|
|
926
|
+
strict: config?.strict,
|
|
927
|
+
needsApproval: config?.needsApproval,
|
|
928
|
+
providerOptions: config?.providerOptions,
|
|
641
929
|
execute: async ({
|
|
642
930
|
pattern,
|
|
643
931
|
path
|
|
@@ -674,36 +962,64 @@ function createGlobTool(sandbox, config) {
|
|
|
674
962
|
import { tool as tool7, zodSchema as zodSchema7 } from "ai";
|
|
675
963
|
import { z as z7 } from "zod";
|
|
676
964
|
var grepInputSchema = z7.object({
|
|
677
|
-
pattern: z7.string().describe("The regular expression pattern to search for"),
|
|
965
|
+
pattern: z7.string().describe("The regular expression pattern to search for in file contents"),
|
|
678
966
|
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")'),
|
|
967
|
+
glob: z7.string().optional().describe('Glob pattern to filter files (e.g. "*.js", "*.{ts,tsx}")'),
|
|
680
968
|
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",
|
|
969
|
+
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
970
|
"-i": z7.boolean().optional().describe("Case insensitive search"),
|
|
683
|
-
"-n": z7.boolean().optional().describe("Show line numbers
|
|
684
|
-
"-B": z7.number().optional().describe("
|
|
685
|
-
"-A": z7.number().optional().describe("
|
|
686
|
-
"-C": z7.number().optional().describe("
|
|
687
|
-
head_limit: z7.number().optional().describe("Limit output to first N lines/entries"),
|
|
688
|
-
|
|
971
|
+
"-n": z7.boolean().optional().describe("Show line numbers in output. Requires output_mode: 'content'. Defaults to true."),
|
|
972
|
+
"-B": z7.number().optional().describe("Number of lines to show before each match. Requires output_mode: 'content'."),
|
|
973
|
+
"-A": z7.number().optional().describe("Number of lines to show after each match. Requires output_mode: 'content'."),
|
|
974
|
+
"-C": z7.number().optional().describe("Number of lines to show before and after each match. Requires output_mode: 'content'."),
|
|
975
|
+
head_limit: z7.number().optional().describe("Limit output to first N lines/entries. Works across all output modes. Defaults to 0 (unlimited)."),
|
|
976
|
+
offset: z7.number().optional().describe("Skip first N lines/entries before applying head_limit. Works across all output modes. Defaults to 0."),
|
|
977
|
+
multiline: z7.boolean().optional().describe("Enable multiline mode where patterns can span lines (requires ripgrep). Default: false.")
|
|
689
978
|
});
|
|
979
|
+
var GREP_DESCRIPTION = `A powerful content search tool with regex support. Use this instead of running grep commands directly.
|
|
980
|
+
|
|
981
|
+
**Usage:**
|
|
982
|
+
- ALWAYS use Grep for search tasks. NEVER invoke \`grep\` or \`rg\` as a Bash command.
|
|
983
|
+
- Supports regex syntax (e.g., "log.*Error", "function\\s+\\w+")
|
|
984
|
+
- Filter files with glob parameter (e.g., "*.js", "**/*.tsx") or type parameter (e.g., "js", "py", "rust")
|
|
985
|
+
|
|
986
|
+
**Output modes:**
|
|
987
|
+
- "content": Shows matching lines with optional context
|
|
988
|
+
- "files_with_matches": Shows only file paths (default)
|
|
989
|
+
- "count": Shows match counts per file
|
|
990
|
+
|
|
991
|
+
**Context options (content mode only):**
|
|
992
|
+
- -B: Lines to show before each match
|
|
993
|
+
- -A: Lines to show after each match
|
|
994
|
+
- -C: Lines to show before and after each match
|
|
995
|
+
|
|
996
|
+
**Pagination:**
|
|
997
|
+
- Use offset to skip results (useful for pagination)
|
|
998
|
+
- Use head_limit to limit total results returned
|
|
999
|
+
|
|
1000
|
+
**Note:** Set useRipgrep: true in config for better performance and multiline support (requires ripgrep installed).`;
|
|
690
1001
|
function createGrepTool(sandbox, config) {
|
|
1002
|
+
const useRipgrep = config?.useRipgrep ?? false;
|
|
691
1003
|
return tool7({
|
|
692
|
-
description:
|
|
1004
|
+
description: GREP_DESCRIPTION,
|
|
693
1005
|
inputSchema: zodSchema7(grepInputSchema),
|
|
1006
|
+
strict: config?.strict,
|
|
1007
|
+
needsApproval: config?.needsApproval,
|
|
1008
|
+
providerOptions: config?.providerOptions,
|
|
694
1009
|
execute: async (input) => {
|
|
695
1010
|
const {
|
|
696
1011
|
pattern,
|
|
697
1012
|
path,
|
|
698
1013
|
glob,
|
|
699
1014
|
type,
|
|
700
|
-
output_mode = "
|
|
1015
|
+
output_mode = "files_with_matches",
|
|
701
1016
|
"-i": caseInsensitive,
|
|
702
1017
|
"-n": showLineNumbers = true,
|
|
703
1018
|
"-B": beforeContext,
|
|
704
1019
|
"-A": afterContext,
|
|
705
1020
|
"-C": context,
|
|
706
1021
|
head_limit,
|
|
1022
|
+
offset = 0,
|
|
707
1023
|
multiline
|
|
708
1024
|
} = input;
|
|
709
1025
|
const searchPath = path || ".";
|
|
@@ -713,32 +1029,52 @@ function createGrepTool(sandbox, config) {
|
|
|
713
1029
|
return { error: `Path not allowed: ${searchPath}` };
|
|
714
1030
|
}
|
|
715
1031
|
}
|
|
1032
|
+
if (multiline && !useRipgrep) {
|
|
1033
|
+
return {
|
|
1034
|
+
error: "Multiline mode requires ripgrep. Set useRipgrep: true in config."
|
|
1035
|
+
};
|
|
1036
|
+
}
|
|
716
1037
|
try {
|
|
717
|
-
|
|
718
|
-
if (
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
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}`);
|
|
1038
|
+
let paginationSuffix = "";
|
|
1039
|
+
if (offset > 0) {
|
|
1040
|
+
paginationSuffix += ` | tail -n +${offset + 1}`;
|
|
1041
|
+
}
|
|
1042
|
+
if (head_limit && head_limit > 0) {
|
|
1043
|
+
paginationSuffix += ` | head -${head_limit}`;
|
|
731
1044
|
}
|
|
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
1045
|
let cmd;
|
|
1046
|
+
if (useRipgrep) {
|
|
1047
|
+
cmd = buildRipgrepCommand({
|
|
1048
|
+
pattern,
|
|
1049
|
+
searchPath,
|
|
1050
|
+
output_mode,
|
|
1051
|
+
caseInsensitive,
|
|
1052
|
+
showLineNumbers,
|
|
1053
|
+
beforeContext,
|
|
1054
|
+
afterContext,
|
|
1055
|
+
context,
|
|
1056
|
+
glob,
|
|
1057
|
+
type,
|
|
1058
|
+
multiline,
|
|
1059
|
+
paginationSuffix
|
|
1060
|
+
});
|
|
1061
|
+
} else {
|
|
1062
|
+
cmd = buildGrepCommand({
|
|
1063
|
+
pattern,
|
|
1064
|
+
searchPath,
|
|
1065
|
+
output_mode,
|
|
1066
|
+
caseInsensitive,
|
|
1067
|
+
showLineNumbers,
|
|
1068
|
+
beforeContext,
|
|
1069
|
+
afterContext,
|
|
1070
|
+
context,
|
|
1071
|
+
glob,
|
|
1072
|
+
type,
|
|
1073
|
+
paginationSuffix
|
|
1074
|
+
});
|
|
1075
|
+
}
|
|
1076
|
+
const result = await sandbox.exec(cmd, { timeout: config?.timeout });
|
|
739
1077
|
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
1078
|
const files = result.stdout.split(`
|
|
743
1079
|
`).filter(Boolean);
|
|
744
1080
|
return {
|
|
@@ -746,8 +1082,6 @@ function createGrepTool(sandbox, config) {
|
|
|
746
1082
|
count: files.length
|
|
747
1083
|
};
|
|
748
1084
|
} 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
1085
|
const lines = result.stdout.split(`
|
|
752
1086
|
`).filter(Boolean);
|
|
753
1087
|
const counts = lines.map((line) => {
|
|
@@ -763,8 +1097,6 @@ function createGrepTool(sandbox, config) {
|
|
|
763
1097
|
total
|
|
764
1098
|
};
|
|
765
1099
|
} else {
|
|
766
|
-
cmd = `grep -rn ${flagStr} "${pattern}" ${searchPath} 2>/dev/null | head -${limit}`;
|
|
767
|
-
const result = await sandbox.exec(cmd, { timeout: config?.timeout });
|
|
768
1100
|
if (!result.stdout.trim()) {
|
|
769
1101
|
return {
|
|
770
1102
|
matches: [],
|
|
@@ -798,6 +1130,66 @@ function createGrepTool(sandbox, config) {
|
|
|
798
1130
|
}
|
|
799
1131
|
});
|
|
800
1132
|
}
|
|
1133
|
+
function buildRipgrepCommand(opts) {
|
|
1134
|
+
const flags = [];
|
|
1135
|
+
if (opts.caseInsensitive)
|
|
1136
|
+
flags.push("-i");
|
|
1137
|
+
if (opts.multiline)
|
|
1138
|
+
flags.push("-U", "--multiline-dotall");
|
|
1139
|
+
if (opts.output_mode === "content") {
|
|
1140
|
+
if (opts.showLineNumbers)
|
|
1141
|
+
flags.push("-n");
|
|
1142
|
+
if (opts.context) {
|
|
1143
|
+
flags.push(`-C ${opts.context}`);
|
|
1144
|
+
} else {
|
|
1145
|
+
if (opts.beforeContext)
|
|
1146
|
+
flags.push(`-B ${opts.beforeContext}`);
|
|
1147
|
+
if (opts.afterContext)
|
|
1148
|
+
flags.push(`-A ${opts.afterContext}`);
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
1151
|
+
if (opts.glob)
|
|
1152
|
+
flags.push(`-g "${opts.glob}"`);
|
|
1153
|
+
if (opts.type)
|
|
1154
|
+
flags.push(`-t ${opts.type}`);
|
|
1155
|
+
const flagStr = flags.join(" ");
|
|
1156
|
+
if (opts.output_mode === "files_with_matches") {
|
|
1157
|
+
return `rg -l ${flagStr} "${opts.pattern}" ${opts.searchPath} 2>/dev/null${opts.paginationSuffix}`;
|
|
1158
|
+
} else if (opts.output_mode === "count") {
|
|
1159
|
+
return `rg -c ${flagStr} "${opts.pattern}" ${opts.searchPath} 2>/dev/null${opts.paginationSuffix}`;
|
|
1160
|
+
} else {
|
|
1161
|
+
return `rg ${flagStr} "${opts.pattern}" ${opts.searchPath} 2>/dev/null${opts.paginationSuffix}`;
|
|
1162
|
+
}
|
|
1163
|
+
}
|
|
1164
|
+
function buildGrepCommand(opts) {
|
|
1165
|
+
const flags = ["-r"];
|
|
1166
|
+
if (opts.caseInsensitive)
|
|
1167
|
+
flags.push("-i");
|
|
1168
|
+
if (opts.output_mode === "content") {
|
|
1169
|
+
if (opts.showLineNumbers)
|
|
1170
|
+
flags.push("-n");
|
|
1171
|
+
if (opts.context) {
|
|
1172
|
+
flags.push(`-C ${opts.context}`);
|
|
1173
|
+
} else {
|
|
1174
|
+
if (opts.beforeContext)
|
|
1175
|
+
flags.push(`-B ${opts.beforeContext}`);
|
|
1176
|
+
if (opts.afterContext)
|
|
1177
|
+
flags.push(`-A ${opts.afterContext}`);
|
|
1178
|
+
}
|
|
1179
|
+
}
|
|
1180
|
+
if (opts.glob)
|
|
1181
|
+
flags.push(`--include="${opts.glob}"`);
|
|
1182
|
+
if (opts.type)
|
|
1183
|
+
flags.push(`--include="*.${opts.type}"`);
|
|
1184
|
+
const flagStr = flags.join(" ");
|
|
1185
|
+
if (opts.output_mode === "files_with_matches") {
|
|
1186
|
+
return `grep -l ${flagStr} "${opts.pattern}" ${opts.searchPath} 2>/dev/null${opts.paginationSuffix}`;
|
|
1187
|
+
} else if (opts.output_mode === "count") {
|
|
1188
|
+
return `grep -c ${flagStr} "${opts.pattern}" ${opts.searchPath} 2>/dev/null | grep -v ':0$'${opts.paginationSuffix}`;
|
|
1189
|
+
} else {
|
|
1190
|
+
return `grep ${flagStr} "${opts.pattern}" ${opts.searchPath} 2>/dev/null${opts.paginationSuffix}`;
|
|
1191
|
+
}
|
|
1192
|
+
}
|
|
801
1193
|
|
|
802
1194
|
// src/tools/read.ts
|
|
803
1195
|
import { tool as tool8, zodSchema as zodSchema8 } from "ai";
|
|
@@ -807,10 +1199,25 @@ var readInputSchema = z8.object({
|
|
|
807
1199
|
offset: z8.number().optional().describe("Line number to start reading from (1-indexed)"),
|
|
808
1200
|
limit: z8.number().optional().describe("Maximum number of lines to read")
|
|
809
1201
|
});
|
|
1202
|
+
var READ_DESCRIPTION = `Reads a file from the local filesystem. You can access any file directly by using this tool.
|
|
1203
|
+
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.
|
|
1204
|
+
|
|
1205
|
+
Usage:
|
|
1206
|
+
- The file_path parameter must be an absolute path, not a relative path
|
|
1207
|
+
- By default, it reads up to 500 lines starting from the beginning of the file
|
|
1208
|
+
- You can optionally specify a line offset and limit (especially handy for long files)
|
|
1209
|
+
- Results are returned with line numbers starting at 1
|
|
1210
|
+
- This tool can only read text files, not binary files (images, PDFs, etc.)
|
|
1211
|
+
- This tool can only read files, not directories. To read a directory, use an ls command via the Bash tool.
|
|
1212
|
+
- It is always better to speculatively read multiple potentially useful files in parallel
|
|
1213
|
+
- If you read a file that exists but has empty contents you will receive a warning in place of file contents`;
|
|
810
1214
|
function createReadTool(sandbox, config) {
|
|
811
1215
|
return tool8({
|
|
812
|
-
description:
|
|
1216
|
+
description: READ_DESCRIPTION,
|
|
813
1217
|
inputSchema: zodSchema8(readInputSchema),
|
|
1218
|
+
strict: config?.strict,
|
|
1219
|
+
needsApproval: config?.needsApproval,
|
|
1220
|
+
providerOptions: config?.providerOptions,
|
|
814
1221
|
execute: async ({
|
|
815
1222
|
file_path,
|
|
816
1223
|
offset,
|
|
@@ -967,16 +1374,39 @@ function createSkillTool(config) {
|
|
|
967
1374
|
import { generateText, tool as tool10, zodSchema as zodSchema10 } from "ai";
|
|
968
1375
|
import Parallel from "parallel-web";
|
|
969
1376
|
import { z as z10 } from "zod";
|
|
1377
|
+
|
|
1378
|
+
// src/utils/http-constants.ts
|
|
1379
|
+
var RETRYABLE_STATUS_CODES = [408, 429, 500, 502, 503];
|
|
1380
|
+
|
|
1381
|
+
// src/tools/web-fetch.ts
|
|
970
1382
|
var webFetchInputSchema = z10.object({
|
|
971
1383
|
url: z10.string().describe("The URL to fetch content from"),
|
|
972
1384
|
prompt: z10.string().describe("The prompt to run on the fetched content")
|
|
973
1385
|
});
|
|
974
|
-
var
|
|
1386
|
+
var WEB_FETCH_DESCRIPTION = `
|
|
1387
|
+
- Fetches content from a specified URL and processes it using an AI model
|
|
1388
|
+
- Takes a URL and a prompt as input
|
|
1389
|
+
- Fetches the URL content and extracts text
|
|
1390
|
+
- Processes the content with the prompt using the configured model
|
|
1391
|
+
- Returns the model's response about the content
|
|
1392
|
+
- Use this tool when you need to retrieve and analyze web content
|
|
1393
|
+
|
|
1394
|
+
Usage notes:
|
|
1395
|
+
- The URL must be a fully-formed valid URL
|
|
1396
|
+
- HTTP URLs will be automatically upgraded to HTTPS
|
|
1397
|
+
- The prompt should describe what information you want to extract from the page
|
|
1398
|
+
- This tool is read-only and does not modify any files
|
|
1399
|
+
- Results may be summarized if the content is very large
|
|
1400
|
+
- 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.
|
|
1401
|
+
`;
|
|
975
1402
|
function createWebFetchTool(config) {
|
|
976
|
-
const { apiKey, model } = config;
|
|
1403
|
+
const { apiKey, model, strict, needsApproval, providerOptions } = config;
|
|
977
1404
|
return tool10({
|
|
978
|
-
description:
|
|
1405
|
+
description: WEB_FETCH_DESCRIPTION,
|
|
979
1406
|
inputSchema: zodSchema10(webFetchInputSchema),
|
|
1407
|
+
strict,
|
|
1408
|
+
needsApproval,
|
|
1409
|
+
providerOptions,
|
|
980
1410
|
execute: async (input) => {
|
|
981
1411
|
const { url, prompt } = input;
|
|
982
1412
|
try {
|
|
@@ -1024,7 +1454,7 @@ ${content}`
|
|
|
1024
1454
|
return {
|
|
1025
1455
|
error: message,
|
|
1026
1456
|
status_code: statusCode,
|
|
1027
|
-
retryable:
|
|
1457
|
+
retryable: RETRYABLE_STATUS_CODES.includes(statusCode)
|
|
1028
1458
|
};
|
|
1029
1459
|
}
|
|
1030
1460
|
return {
|
|
@@ -1044,12 +1474,34 @@ var webSearchInputSchema = z11.object({
|
|
|
1044
1474
|
allowed_domains: z11.array(z11.string()).optional().describe("Only include results from these domains"),
|
|
1045
1475
|
blocked_domains: z11.array(z11.string()).optional().describe("Never include results from these domains")
|
|
1046
1476
|
});
|
|
1047
|
-
var
|
|
1477
|
+
var WEB_SEARCH_DESCRIPTION = `Searches the web and returns results with links. Use this for accessing up-to-date information beyond your knowledge cutoff.
|
|
1478
|
+
|
|
1479
|
+
**Capabilities:**
|
|
1480
|
+
- Provides current information for recent events and data
|
|
1481
|
+
- Returns results formatted with titles, URLs, and snippets
|
|
1482
|
+
- Supports domain filtering to include or block specific websites
|
|
1483
|
+
|
|
1484
|
+
**CRITICAL REQUIREMENT - You MUST follow this:**
|
|
1485
|
+
After answering using search results, you MUST include a "Sources:" section listing relevant URLs as markdown hyperlinks:
|
|
1486
|
+
|
|
1487
|
+
Sources:
|
|
1488
|
+
- [Source Title 1](https://example.com/1)
|
|
1489
|
+
- [Source Title 2](https://example.com/2)
|
|
1490
|
+
|
|
1491
|
+
**Important - Use the correct year:**
|
|
1492
|
+
When searching for recent information, documentation, or current events, use the current year in your query (e.g., "React documentation 2025" not "2024").
|
|
1493
|
+
|
|
1494
|
+
**Domain filtering:**
|
|
1495
|
+
- allowed_domains: Only include results from these domains
|
|
1496
|
+
- blocked_domains: Never include results from these domains`;
|
|
1048
1497
|
function createWebSearchTool(config) {
|
|
1049
|
-
const { apiKey } = config;
|
|
1498
|
+
const { apiKey, strict, needsApproval, providerOptions } = config;
|
|
1050
1499
|
return tool11({
|
|
1051
|
-
description:
|
|
1500
|
+
description: WEB_SEARCH_DESCRIPTION,
|
|
1052
1501
|
inputSchema: zodSchema11(webSearchInputSchema),
|
|
1502
|
+
strict,
|
|
1503
|
+
needsApproval,
|
|
1504
|
+
providerOptions,
|
|
1053
1505
|
execute: async (input) => {
|
|
1054
1506
|
const { query, allowed_domains, blocked_domains } = input;
|
|
1055
1507
|
try {
|
|
@@ -1083,7 +1535,7 @@ function createWebSearchTool(config) {
|
|
|
1083
1535
|
return {
|
|
1084
1536
|
error: message,
|
|
1085
1537
|
status_code: statusCode,
|
|
1086
|
-
retryable:
|
|
1538
|
+
retryable: RETRYABLE_STATUS_CODES.includes(statusCode)
|
|
1087
1539
|
};
|
|
1088
1540
|
}
|
|
1089
1541
|
return {
|
|
@@ -1101,10 +1553,25 @@ var writeInputSchema = z12.object({
|
|
|
1101
1553
|
file_path: z12.string().describe("Path to the file to write"),
|
|
1102
1554
|
content: z12.string().describe("Content to write to the file")
|
|
1103
1555
|
});
|
|
1556
|
+
var WRITE_DESCRIPTION = `Writes content to a file on the filesystem.
|
|
1557
|
+
|
|
1558
|
+
**Important guidelines:**
|
|
1559
|
+
- This tool will overwrite existing files at the provided path
|
|
1560
|
+
- If modifying an existing file, you MUST use the Read tool first to read the file's contents
|
|
1561
|
+
- ALWAYS prefer editing existing files over creating new ones
|
|
1562
|
+
- NEVER proactively create documentation files (*.md) or README files unless explicitly requested
|
|
1563
|
+
- The file_path must be an absolute path, not relative
|
|
1564
|
+
|
|
1565
|
+
**When to use Write vs Edit:**
|
|
1566
|
+
- Use Write for creating new files or completely replacing file contents
|
|
1567
|
+
- Use Edit for making targeted changes to existing files (preferred for modifications)`;
|
|
1104
1568
|
function createWriteTool(sandbox, config) {
|
|
1105
1569
|
return tool12({
|
|
1106
|
-
description:
|
|
1570
|
+
description: WRITE_DESCRIPTION,
|
|
1107
1571
|
inputSchema: zodSchema12(writeInputSchema),
|
|
1572
|
+
strict: config?.strict,
|
|
1573
|
+
needsApproval: config?.needsApproval,
|
|
1574
|
+
providerOptions: config?.providerOptions,
|
|
1108
1575
|
execute: async ({
|
|
1109
1576
|
file_path,
|
|
1110
1577
|
content
|
|
@@ -1148,8 +1615,32 @@ import { z as z13 } from "zod";
|
|
|
1148
1615
|
var taskInputSchema = z13.object({
|
|
1149
1616
|
description: z13.string().describe("A short (3-5 word) description of the task"),
|
|
1150
1617
|
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")
|
|
1618
|
+
subagent_type: z13.string().describe("The type of specialized agent to use for this task"),
|
|
1619
|
+
system_prompt: z13.string().optional().describe("Optional custom system prompt for this agent. If provided, overrides the default system prompt for the subagent type. Use this to create dynamic, specialized agents on the fly."),
|
|
1620
|
+
tools: z13.array(z13.string()).optional().describe("Optional list of tool names this agent can use (e.g., ['Read', 'Grep', 'WebSearch']). If provided, overrides the default tools for the subagent type. Use this to restrict or expand the agent's capabilities.")
|
|
1152
1621
|
});
|
|
1622
|
+
var TASK_DESCRIPTION = `Launch a new agent to handle complex, multi-step tasks autonomously.
|
|
1623
|
+
|
|
1624
|
+
The Task tool launches specialized agents (subprocesses) that autonomously handle complex tasks. Each agent type has specific capabilities and tools available to it.
|
|
1625
|
+
|
|
1626
|
+
**Subagent types:**
|
|
1627
|
+
- Use predefined subagent_type values for common task patterns
|
|
1628
|
+
- For dynamic agents: provide custom system_prompt and/or tools to create a specialized agent on the fly
|
|
1629
|
+
|
|
1630
|
+
**When NOT to use the Task tool:**
|
|
1631
|
+
- If you want to read a specific file path, use the Read or Glob tool instead, to find the match more quickly
|
|
1632
|
+
- If you are searching for a specific class definition like "class Foo", use the Glob tool instead, to find the match more quickly
|
|
1633
|
+
- 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
|
|
1634
|
+
- Other tasks that are not related to the available agent types
|
|
1635
|
+
|
|
1636
|
+
**Usage notes:**
|
|
1637
|
+
- Always include a short description (3-5 words) summarizing what the agent will do
|
|
1638
|
+
- Launch multiple agents concurrently whenever possible, to maximize performance; to do that, use a single message with multiple tool uses
|
|
1639
|
+
- 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.
|
|
1640
|
+
- Provide clear, detailed prompts so the agent can work autonomously and return exactly the information you need.
|
|
1641
|
+
- The agent's outputs should generally be trusted
|
|
1642
|
+
- 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
|
|
1643
|
+
- 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.`;
|
|
1153
1644
|
var eventCounter = 0;
|
|
1154
1645
|
function generateEventId() {
|
|
1155
1646
|
return `subagent-${Date.now()}-${++eventCounter}`;
|
|
@@ -1175,19 +1666,21 @@ function createTaskTool(config) {
|
|
|
1175
1666
|
streamWriter
|
|
1176
1667
|
} = config;
|
|
1177
1668
|
return tool13({
|
|
1178
|
-
description:
|
|
1669
|
+
description: TASK_DESCRIPTION,
|
|
1179
1670
|
inputSchema: zodSchema13(taskInputSchema),
|
|
1180
1671
|
execute: async ({
|
|
1181
1672
|
description,
|
|
1182
1673
|
prompt,
|
|
1183
|
-
subagent_type
|
|
1674
|
+
subagent_type,
|
|
1675
|
+
system_prompt,
|
|
1676
|
+
tools: customTools
|
|
1184
1677
|
}) => {
|
|
1185
1678
|
const startTime = performance.now();
|
|
1186
1679
|
try {
|
|
1187
1680
|
const typeConfig = subagentTypes[subagent_type] || {};
|
|
1188
1681
|
const model = typeConfig.model || defaultModel;
|
|
1189
|
-
const tools = filterTools(allTools, typeConfig.tools);
|
|
1190
|
-
const systemPrompt = typeConfig.systemPrompt;
|
|
1682
|
+
const tools = filterTools(allTools, customTools ?? typeConfig.tools);
|
|
1683
|
+
const systemPrompt = system_prompt ?? typeConfig.systemPrompt;
|
|
1191
1684
|
const commonOptions = {
|
|
1192
1685
|
model,
|
|
1193
1686
|
tools,
|
|
@@ -1308,9 +1801,41 @@ var todoWriteInputSchema = z14.object({
|
|
|
1308
1801
|
activeForm: z14.string().describe("Active form of the task description")
|
|
1309
1802
|
})).describe("The updated todo list")
|
|
1310
1803
|
});
|
|
1311
|
-
|
|
1804
|
+
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.
|
|
1805
|
+
|
|
1806
|
+
**When to use this tool proactively:**
|
|
1807
|
+
1. Complex multi-step tasks - When a task requires 3 or more distinct steps
|
|
1808
|
+
2. Non-trivial tasks - Tasks requiring careful planning or multiple operations
|
|
1809
|
+
3. User explicitly requests a todo list
|
|
1810
|
+
4. User provides multiple tasks - Numbered lists or comma-separated items
|
|
1811
|
+
5. After receiving new instructions - Immediately capture requirements as todos
|
|
1812
|
+
6. When starting work - Mark task as in_progress BEFORE beginning
|
|
1813
|
+
7. After completing - Mark as completed and add any follow-up tasks discovered
|
|
1814
|
+
|
|
1815
|
+
**When NOT to use:**
|
|
1816
|
+
1. Single, straightforward tasks
|
|
1817
|
+
2. Trivial tasks with no organizational benefit
|
|
1818
|
+
3. Tasks completable in less than 3 trivial steps
|
|
1819
|
+
4. Purely conversational or informational requests
|
|
1820
|
+
|
|
1821
|
+
**Task states:**
|
|
1822
|
+
- pending: Not yet started
|
|
1823
|
+
- in_progress: Currently working on (limit to ONE at a time)
|
|
1824
|
+
- completed: Finished successfully
|
|
1825
|
+
|
|
1826
|
+
**Task format (both required):**
|
|
1827
|
+
- content: Imperative form ("Run tests", "Analyze data")
|
|
1828
|
+
- activeForm: Present continuous form ("Running tests", "Analyzing data")
|
|
1829
|
+
|
|
1830
|
+
**Task management rules:**
|
|
1831
|
+
- Update status in real-time as you work
|
|
1832
|
+
- Mark complete IMMEDIATELY after finishing (don't batch)
|
|
1833
|
+
- Keep exactly ONE task in_progress at any time
|
|
1834
|
+
- ONLY mark completed when FULLY accomplished
|
|
1835
|
+
- If blocked/errors, keep in_progress and create new task for the blocker`;
|
|
1836
|
+
function createTodoWriteTool(state, onUpdate) {
|
|
1312
1837
|
return tool14({
|
|
1313
|
-
description:
|
|
1838
|
+
description: TODO_WRITE_DESCRIPTION,
|
|
1314
1839
|
inputSchema: zodSchema14(todoWriteInputSchema),
|
|
1315
1840
|
execute: async ({
|
|
1316
1841
|
todos
|
|
@@ -1340,6 +1865,58 @@ function createTodoWriteTool(state, config, onUpdate) {
|
|
|
1340
1865
|
}
|
|
1341
1866
|
|
|
1342
1867
|
// src/tools/index.ts
|
|
1868
|
+
var DEFAULT_CACHEABLE = [
|
|
1869
|
+
"Read",
|
|
1870
|
+
"Glob",
|
|
1871
|
+
"Grep",
|
|
1872
|
+
"WebFetch",
|
|
1873
|
+
"WebSearch"
|
|
1874
|
+
];
|
|
1875
|
+
function resolveCache(config) {
|
|
1876
|
+
if (!config) {
|
|
1877
|
+
return { store: null, ttl: 0, debug: false, enabled: new Set };
|
|
1878
|
+
}
|
|
1879
|
+
if (config === true) {
|
|
1880
|
+
return {
|
|
1881
|
+
store: new LRUCacheStore,
|
|
1882
|
+
ttl: 5 * 60 * 1000,
|
|
1883
|
+
debug: false,
|
|
1884
|
+
enabled: new Set(DEFAULT_CACHEABLE)
|
|
1885
|
+
};
|
|
1886
|
+
}
|
|
1887
|
+
if (typeof config === "object" && typeof config.get === "function" && typeof config.set === "function" && typeof config.delete === "function" && typeof config.clear === "function") {
|
|
1888
|
+
return {
|
|
1889
|
+
store: config,
|
|
1890
|
+
ttl: 5 * 60 * 1000,
|
|
1891
|
+
debug: false,
|
|
1892
|
+
enabled: new Set(DEFAULT_CACHEABLE)
|
|
1893
|
+
};
|
|
1894
|
+
}
|
|
1895
|
+
const enabled = new Set;
|
|
1896
|
+
for (const tool15 of DEFAULT_CACHEABLE) {
|
|
1897
|
+
if (config[tool15] !== false) {
|
|
1898
|
+
enabled.add(tool15);
|
|
1899
|
+
}
|
|
1900
|
+
}
|
|
1901
|
+
for (const [key, value] of Object.entries(config)) {
|
|
1902
|
+
if (["store", "ttl", "debug", "onHit", "onMiss", "keyGenerator"].includes(key))
|
|
1903
|
+
continue;
|
|
1904
|
+
if (value === true)
|
|
1905
|
+
enabled.add(key);
|
|
1906
|
+
if (value === false)
|
|
1907
|
+
enabled.delete(key);
|
|
1908
|
+
}
|
|
1909
|
+
const cfg = config;
|
|
1910
|
+
return {
|
|
1911
|
+
store: cfg.store ?? new LRUCacheStore,
|
|
1912
|
+
ttl: cfg.ttl ?? 5 * 60 * 1000,
|
|
1913
|
+
debug: cfg.debug ?? false,
|
|
1914
|
+
onHit: cfg.onHit,
|
|
1915
|
+
onMiss: cfg.onMiss,
|
|
1916
|
+
keyGenerator: cfg.keyGenerator,
|
|
1917
|
+
enabled
|
|
1918
|
+
};
|
|
1919
|
+
}
|
|
1343
1920
|
function createAgentTools(sandbox, config) {
|
|
1344
1921
|
const toolsConfig = {
|
|
1345
1922
|
...DEFAULT_CONFIG.tools,
|
|
@@ -1375,6 +1952,21 @@ function createAgentTools(sandbox, config) {
|
|
|
1375
1952
|
if (config?.webFetch) {
|
|
1376
1953
|
tools.WebFetch = createWebFetchTool(config.webFetch);
|
|
1377
1954
|
}
|
|
1955
|
+
const cacheConfig = resolveCache(config?.cache);
|
|
1956
|
+
if (cacheConfig.store) {
|
|
1957
|
+
for (const [name, tool15] of Object.entries(tools)) {
|
|
1958
|
+
if (cacheConfig.enabled.has(name)) {
|
|
1959
|
+
tools[name] = cached(tool15, name, {
|
|
1960
|
+
store: cacheConfig.store,
|
|
1961
|
+
ttl: cacheConfig.ttl,
|
|
1962
|
+
debug: cacheConfig.debug,
|
|
1963
|
+
onHit: cacheConfig.onHit,
|
|
1964
|
+
onMiss: cacheConfig.onMiss,
|
|
1965
|
+
keyGenerator: cacheConfig.keyGenerator
|
|
1966
|
+
});
|
|
1967
|
+
}
|
|
1968
|
+
}
|
|
1969
|
+
}
|
|
1378
1970
|
return { tools, planModeState };
|
|
1379
1971
|
}
|
|
1380
1972
|
// src/utils/compact-conversation.ts
|
|
@@ -1728,11 +2320,13 @@ function contextNeedsCompaction(status) {
|
|
|
1728
2320
|
return status.status === "critical";
|
|
1729
2321
|
}
|
|
1730
2322
|
// src/skills/discovery.ts
|
|
1731
|
-
import { readdir, readFile, stat } from "node:fs/promises";
|
|
2323
|
+
import { readdir as readdir2, readFile as readFile2, stat } from "node:fs/promises";
|
|
1732
2324
|
import { homedir } from "node:os";
|
|
1733
|
-
import { join, resolve } from "node:path";
|
|
2325
|
+
import { join as join2, resolve } from "node:path";
|
|
1734
2326
|
|
|
1735
2327
|
// src/skills/loader.ts
|
|
2328
|
+
import { readdir, readFile } from "node:fs/promises";
|
|
2329
|
+
import { basename, dirname, join } from "node:path";
|
|
1736
2330
|
function parseSkillMetadata(content, skillPath) {
|
|
1737
2331
|
const frontmatter = extractFrontmatter(content);
|
|
1738
2332
|
if (!frontmatter) {
|
|
@@ -1821,6 +2415,32 @@ function parseYaml(yaml) {
|
|
|
1821
2415
|
}
|
|
1822
2416
|
return result;
|
|
1823
2417
|
}
|
|
2418
|
+
async function loadSkillBundle(skillDir) {
|
|
2419
|
+
const files = {};
|
|
2420
|
+
const name = basename(skillDir);
|
|
2421
|
+
async function readDirRecursive(dir, prefix = "") {
|
|
2422
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
2423
|
+
for (const entry of entries) {
|
|
2424
|
+
const relativePath = prefix ? `${prefix}/${entry.name}` : entry.name;
|
|
2425
|
+
const fullPath = join(dir, entry.name);
|
|
2426
|
+
if (entry.isDirectory()) {
|
|
2427
|
+
await readDirRecursive(fullPath, relativePath);
|
|
2428
|
+
} else {
|
|
2429
|
+
files[relativePath] = await readFile(fullPath, "utf-8");
|
|
2430
|
+
}
|
|
2431
|
+
}
|
|
2432
|
+
}
|
|
2433
|
+
await readDirRecursive(skillDir);
|
|
2434
|
+
return { name, files };
|
|
2435
|
+
}
|
|
2436
|
+
async function loadSkillBundles(skills) {
|
|
2437
|
+
const bundles = {};
|
|
2438
|
+
for (const skill of skills) {
|
|
2439
|
+
const skillDir = dirname(skill.path);
|
|
2440
|
+
bundles[skill.name] = await loadSkillBundle(skillDir);
|
|
2441
|
+
}
|
|
2442
|
+
return bundles;
|
|
2443
|
+
}
|
|
1824
2444
|
|
|
1825
2445
|
// src/skills/discovery.ts
|
|
1826
2446
|
var DEFAULT_SKILL_PATHS = [".skills", "~/.bashkit/skills"];
|
|
@@ -1843,7 +2463,7 @@ async function discoverSkills(options) {
|
|
|
1843
2463
|
}
|
|
1844
2464
|
function resolvePath(path, cwd) {
|
|
1845
2465
|
if (path.startsWith("~/")) {
|
|
1846
|
-
return
|
|
2466
|
+
return join2(homedir(), path.slice(2));
|
|
1847
2467
|
}
|
|
1848
2468
|
if (path.startsWith("/")) {
|
|
1849
2469
|
return path;
|
|
@@ -1853,18 +2473,18 @@ function resolvePath(path, cwd) {
|
|
|
1853
2473
|
async function scanDirectory(dirPath) {
|
|
1854
2474
|
const skills = [];
|
|
1855
2475
|
try {
|
|
1856
|
-
const entries = await
|
|
2476
|
+
const entries = await readdir2(dirPath, { withFileTypes: true });
|
|
1857
2477
|
for (const entry of entries) {
|
|
1858
2478
|
if (!entry.isDirectory()) {
|
|
1859
2479
|
continue;
|
|
1860
2480
|
}
|
|
1861
|
-
const skillPath =
|
|
2481
|
+
const skillPath = join2(dirPath, entry.name, "SKILL.md");
|
|
1862
2482
|
try {
|
|
1863
2483
|
const skillStat = await stat(skillPath);
|
|
1864
2484
|
if (!skillStat.isFile()) {
|
|
1865
2485
|
continue;
|
|
1866
2486
|
}
|
|
1867
|
-
const content = await
|
|
2487
|
+
const content = await readFile2(skillPath, "utf-8");
|
|
1868
2488
|
const metadata = parseSkillMetadata(content, skillPath);
|
|
1869
2489
|
if (metadata.name !== entry.name) {
|
|
1870
2490
|
console.warn(`Skill name "${metadata.name}" does not match folder name "${entry.name}" in ${skillPath}`);
|
|
@@ -2037,6 +2657,8 @@ export {
|
|
|
2037
2657
|
setupAgentEnvironment,
|
|
2038
2658
|
pruneMessagesByTokens,
|
|
2039
2659
|
parseSkillMetadata,
|
|
2660
|
+
loadSkillBundles,
|
|
2661
|
+
loadSkillBundle,
|
|
2040
2662
|
getContextStatus,
|
|
2041
2663
|
fetchSkills,
|
|
2042
2664
|
fetchSkill,
|
|
@@ -2051,6 +2673,7 @@ export {
|
|
|
2051
2673
|
createTodoWriteTool,
|
|
2052
2674
|
createTaskTool,
|
|
2053
2675
|
createSkillTool,
|
|
2676
|
+
createRedisCacheStore,
|
|
2054
2677
|
createReadTool,
|
|
2055
2678
|
createLocalSandbox,
|
|
2056
2679
|
createGrepTool,
|
|
@@ -2066,7 +2689,10 @@ export {
|
|
|
2066
2689
|
contextNeedsCompaction,
|
|
2067
2690
|
contextNeedsAttention,
|
|
2068
2691
|
compactConversation,
|
|
2692
|
+
cached,
|
|
2693
|
+
anthropicPromptCacheMiddlewareV2,
|
|
2069
2694
|
anthropicPromptCacheMiddleware,
|
|
2070
2695
|
MODEL_CONTEXT_LIMITS,
|
|
2696
|
+
LRUCacheStore,
|
|
2071
2697
|
DEFAULT_CONFIG
|
|
2072
2698
|
};
|