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 CHANGED
@@ -484,3 +484,111 @@ const { tools } = createAgentTools(sandbox, {
484
484
  });
485
485
  ```
486
486
 
487
+ ## Tool Result Caching
488
+
489
+ Cache tool execution results to avoid redundant operations:
490
+
491
+ ```typescript
492
+ import { createAgentTools, LocalSandbox } from "bashkit";
493
+
494
+ const sandbox = new LocalSandbox("/tmp/workspace");
495
+
496
+ // Enable caching with defaults (LRU, 5min TTL)
497
+ const { tools } = createAgentTools(sandbox, { cache: true });
498
+
499
+ // Or customize caching behavior
500
+ const { tools } = createAgentTools(sandbox, {
501
+ cache: {
502
+ ttl: 10 * 60 * 1000, // 10 minutes
503
+ debug: true, // Log cache hits/misses
504
+ Read: true, // Enable for Read
505
+ Glob: true, // Enable for Glob
506
+ Grep: false, // Disable for Grep
507
+ },
508
+ });
509
+ ```
510
+
511
+ **Default cached tools:** Read, Glob, Grep, WebFetch, WebSearch
512
+
513
+ **Not cached by default:** Bash, Write, Edit (have side effects)
514
+
515
+ ### Cache Callbacks
516
+
517
+ Track cache performance with callbacks:
518
+
519
+ ```typescript
520
+ const { tools } = createAgentTools(sandbox, {
521
+ cache: {
522
+ onHit: (toolName, key) => {
523
+ metrics.increment(`cache.hit.${toolName}`);
524
+ },
525
+ onMiss: (toolName, key) => {
526
+ metrics.increment(`cache.miss.${toolName}`);
527
+ },
528
+ },
529
+ });
530
+ ```
531
+
532
+ ### Cache Stats
533
+
534
+ Cached tools have additional methods:
535
+
536
+ ```typescript
537
+ import type { CachedTool } from "bashkit";
538
+
539
+ const readTool = tools.Read as CachedTool;
540
+
541
+ // Check cache performance (async for Redis compatibility)
542
+ console.log(await readTool.getStats());
543
+ // { hits: 5, misses: 2, hitRate: 0.71, size: 2 }
544
+
545
+ // Clear cache
546
+ await readTool.clearCache(); // Clear all
547
+ await readTool.clearCache("key"); // Clear specific entry
548
+ ```
549
+
550
+ ### Redis Cache Store
551
+
552
+ Use your existing Redis client with the helper:
553
+
554
+ ```typescript
555
+ import { createRedisCacheStore, createAgentTools } from "bashkit";
556
+
557
+ const store = createRedisCacheStore(myRedisClient);
558
+ const { tools } = createAgentTools(sandbox, { cache: store });
559
+ ```
560
+
561
+ Works with `redis`, `ioredis`, or any client with `get`, `set`, `del`, `keys` methods. TTL is handled by the wrapper for consistent behavior across all cache backends.
562
+
563
+ ### Custom Cache Store
564
+
565
+ For other backends, implement the `CacheStore` interface:
566
+
567
+ ```typescript
568
+ import type { CacheStore } from "bashkit";
569
+
570
+ const myStore: CacheStore = {
571
+ get(key) { /* return CacheEntry or undefined */ },
572
+ set(key, entry) { /* store entry */ },
573
+ delete(key) { /* remove entry */ },
574
+ clear() { /* remove all entries */ },
575
+ size() { /* optional: return count */ },
576
+ };
577
+
578
+ const { tools } = createAgentTools(sandbox, { cache: myStore });
579
+ ```
580
+
581
+ ### Standalone Caching
582
+
583
+ Wrap individual tools with caching:
584
+
585
+ ```typescript
586
+ import { cached, LRUCacheStore } from "bashkit";
587
+
588
+ const cachedTool = cached(myTool, "MyTool", {
589
+ ttl: 60000, // 1 minute
590
+ debug: true, // Log cache activity
591
+ store: new LRUCacheStore(500), // Max 500 entries
592
+ });
593
+ ```
594
+
package/README.md CHANGED
@@ -217,6 +217,34 @@ const { tools, planModeState } = createAgentTools(sandbox, {
217
217
  - `allowedPaths` (string[]): Restrict file operations to specific paths
218
218
  - `blockedCommands` (string[]): Block commands containing these strings (Bash)
219
219
 
220
+ #### AI SDK Tool Options (v6+)
221
+
222
+ All tools support AI SDK v6 tool options:
223
+
224
+ ```typescript
225
+ const { tools } = createAgentTools(sandbox, {
226
+ tools: {
227
+ Bash: {
228
+ timeout: 30000,
229
+ // AI SDK v6 options
230
+ needsApproval: true, // Require user approval before execution
231
+ strict: true, // Strict schema validation
232
+ providerOptions: { /* provider-specific options */ },
233
+ },
234
+ Write: {
235
+ // Dynamic approval based on input
236
+ needsApproval: async ({ file_path }) => {
237
+ return file_path.includes('package.json');
238
+ },
239
+ },
240
+ },
241
+ });
242
+ ```
243
+
244
+ - `needsApproval` (boolean | function): Require user approval before tool execution
245
+ - `strict` (boolean): Enable strict schema validation
246
+ - `providerOptions` (object): Provider-specific tool options
247
+
220
248
  ## Sub-agents with Task Tool
221
249
 
222
250
  The Task tool spawns new agents for complex subtasks:
@@ -254,6 +282,26 @@ The parent agent calls Task like any other tool:
254
282
  }}
255
283
  ```
256
284
 
285
+ ### Dynamic Agents
286
+
287
+ You can create custom agents on the fly by passing `system_prompt` and/or `tools` directly, without predefined subagent types:
288
+
289
+ ```typescript
290
+ // Agent creates a specialized agent dynamically:
291
+ { tool: "Task", args: {
292
+ description: "Analyze security vulnerabilities",
293
+ prompt: "Review the auth code for security issues",
294
+ subagent_type: "custom",
295
+ system_prompt: "You are a security expert. Focus on OWASP top 10 vulnerabilities.",
296
+ tools: ["Read", "Grep", "Glob"]
297
+ }}
298
+ ```
299
+
300
+ This is useful when:
301
+ - The parent agent needs to create specialized agents based on context
302
+ - You want agents to delegate with custom instructions
303
+ - Predefined subagent types don't fit the task
304
+
257
305
  ### Streaming Sub-agent Activity to UI
258
306
 
259
307
  Pass a `streamWriter` to stream real-time sub-agent activity to the UI:
@@ -336,13 +384,111 @@ if (contextNeedsCompaction(status)) {
336
384
  }
337
385
  ```
338
386
 
387
+ ## Tool Result Caching
388
+
389
+ Cache tool execution results to avoid repeated expensive operations:
390
+
391
+ ```typescript
392
+ const { tools } = createAgentTools(sandbox, {
393
+ // Enable caching with defaults (LRU, 5min TTL)
394
+ cache: true,
395
+ });
396
+ ```
397
+
398
+ ### Cache Configuration Options
399
+
400
+ ```typescript
401
+ const { tools } = createAgentTools(sandbox, {
402
+ cache: {
403
+ // Custom TTL (default: 5 minutes)
404
+ ttl: 10 * 60 * 1000,
405
+
406
+ // Enable debug logging
407
+ debug: true,
408
+
409
+ // Per-tool control (defaults: Read, Glob, Grep, WebFetch, WebSearch)
410
+ Read: true,
411
+ Glob: true,
412
+ Grep: false, // Disable for this tool
413
+
414
+ // Enable caching for tools not cached by default
415
+ Bash: true, // Use with caution - has side effects
416
+ },
417
+ });
418
+ ```
419
+
420
+ ### Default Cached Tools
421
+
422
+ By default, these read-only tools are cached when `cache: true`:
423
+ - `Read` - File reading
424
+ - `Glob` - File pattern matching
425
+ - `Grep` - Content searching
426
+ - `WebFetch` - URL fetching
427
+ - `WebSearch` - Web searches
428
+
429
+ Tools with side effects (`Bash`, `Write`, `Edit`) are NOT cached by default but can be enabled.
430
+
431
+ ### Custom Cache Store
432
+
433
+ Implement your own cache backend (e.g., Redis):
434
+
435
+ ```typescript
436
+ import type { CacheStore } from 'bashkit';
437
+
438
+ const redisStore: CacheStore = {
439
+ async get(key) {
440
+ const data = await redis.get(key);
441
+ return data ? JSON.parse(data) : undefined;
442
+ },
443
+ async set(key, entry) {
444
+ await redis.set(key, JSON.stringify(entry));
445
+ },
446
+ async delete(key) {
447
+ await redis.del(key);
448
+ },
449
+ async clear() {
450
+ await redis.flushdb();
451
+ },
452
+ size() {
453
+ return redis.dbsize();
454
+ },
455
+ };
456
+
457
+ const { tools } = createAgentTools(sandbox, {
458
+ cache: redisStore,
459
+ });
460
+ ```
461
+
462
+ ### Standalone Cached Wrapper
463
+
464
+ Wrap individual tools with caching:
465
+
466
+ ```typescript
467
+ import { cached, LRUCacheStore } from 'bashkit';
468
+
469
+ const cachedTool = cached(myTool, 'MyTool', {
470
+ ttl: 5 * 60 * 1000,
471
+ debug: true,
472
+ });
473
+
474
+ // Check cache stats
475
+ console.log(await cachedTool.getStats());
476
+ // { hits: 5, misses: 2, hitRate: 0.71, size: 2 }
477
+
478
+ // Clear cache
479
+ await cachedTool.clearCache();
480
+ ```
481
+
339
482
  ## Prompt Caching
340
483
 
341
484
  Enable Anthropic prompt caching to reduce costs on repeated prefixes:
342
485
 
343
486
  ```typescript
344
487
  import { wrapLanguageModel } from 'ai';
488
+ // AI SDK v6+
345
489
  import { anthropicPromptCacheMiddleware } from 'bashkit';
490
+ // AI SDK v5
491
+ // import { anthropicPromptCacheMiddlewareV2 } from 'bashkit';
346
492
 
347
493
  const model = wrapLanguageModel({
348
494
  model: anthropic('claude-sonnet-4-5'),
@@ -731,7 +877,8 @@ Creates a set of agent tools bound to a sandbox instance.
731
877
 
732
878
  ### Middleware
733
879
 
734
- - `anthropicPromptCacheMiddleware` - Enable prompt caching for Anthropic models
880
+ - `anthropicPromptCacheMiddleware` - Enable prompt caching for Anthropic models (AI SDK v6+)
881
+ - `anthropicPromptCacheMiddlewareV2` - Enable prompt caching for Anthropic models (AI SDK v5)
735
882
 
736
883
  ## Future Roadmap
737
884
 
@@ -0,0 +1,37 @@
1
+ import type { Tool } from "ai";
2
+ import type { CacheOptions, CacheStats } from "./types";
3
+ /**
4
+ * Extended tool with cache methods.
5
+ */
6
+ export type CachedTool<T extends Tool = Tool> = T & {
7
+ /** Get cache statistics (hits, misses, hitRate, size) */
8
+ getStats(): Promise<CacheStats>;
9
+ /** Clear cache. Pass key to clear specific entry, or no args to clear all. */
10
+ clearCache(key?: string): Promise<void>;
11
+ };
12
+ /**
13
+ * Wraps an AI SDK tool with caching capabilities.
14
+ *
15
+ * Caches successful tool results (results without an 'error' property).
16
+ * Cache hits return immediately without re-executing the tool.
17
+ *
18
+ * @param tool - The AI SDK tool to wrap
19
+ * @param toolName - Name used in cache keys (e.g., 'Read', 'Glob')
20
+ * @param options - Cache configuration options
21
+ * @returns Cached tool with getStats() and clearCache() methods
22
+ *
23
+ * @example
24
+ * ```typescript
25
+ * import { cached, LRUCacheStore } from 'bashkit';
26
+ *
27
+ * const cachedReadTool = cached(readTool, 'Read', {
28
+ * ttl: 5 * 60 * 1000, // 5 minutes
29
+ * debug: true,
30
+ * });
31
+ *
32
+ * // Check stats (async for Redis compatibility)
33
+ * console.log(await cachedReadTool.getStats());
34
+ * // { hits: 5, misses: 2, hitRate: 0.71, size: 2 }
35
+ * ```
36
+ */
37
+ export declare function cached<T extends Tool>(tool: T, toolName: string, options?: CacheOptions): CachedTool<T>;
@@ -0,0 +1,6 @@
1
+ export type { CacheEntry, CacheOptions, CacheStats, CacheStore, } from "./types";
2
+ export { LRUCacheStore } from "./lru";
3
+ export { cached } from "./cached";
4
+ export type { CachedTool } from "./cached";
5
+ export { createRedisCacheStore } from "./redis";
6
+ export type { RedisCacheStoreOptions, RedisClient } from "./redis";
@@ -0,0 +1,25 @@
1
+ import type { CacheEntry, CacheStore } from "./types";
2
+ /**
3
+ * LRU (Least Recently Used) cache implementation.
4
+ *
5
+ * Uses a Map for O(1) access. When items are retrieved, they're moved to the
6
+ * end (most recently used). When capacity is reached, the oldest (first) entry
7
+ * is removed.
8
+ *
9
+ * @example
10
+ * ```typescript
11
+ * const cache = new LRUCacheStore(1000);
12
+ * cache.set('key', { result: data, timestamp: Date.now() });
13
+ * const entry = cache.get('key');
14
+ * ```
15
+ */
16
+ export declare class LRUCacheStore<T = unknown> implements CacheStore<T> {
17
+ private cache;
18
+ private maxSize;
19
+ constructor(maxSize?: number);
20
+ get(key: string): CacheEntry<T> | undefined;
21
+ set(key: string, entry: CacheEntry<T>): void;
22
+ delete(key: string): void;
23
+ clear(): void;
24
+ size(): number;
25
+ }
@@ -0,0 +1,41 @@
1
+ import type { CacheStore } from "./types";
2
+ /**
3
+ * Minimal interface for Redis-like clients.
4
+ * Compatible with `redis`, `ioredis`, and similar libraries.
5
+ */
6
+ export interface RedisClient {
7
+ get(key: string): Promise<string | null>;
8
+ set(key: string, value: string): Promise<unknown>;
9
+ del(key: string | string[]): Promise<unknown>;
10
+ keys(pattern: string): Promise<string[]>;
11
+ }
12
+ /**
13
+ * Options for Redis cache store.
14
+ */
15
+ export interface RedisCacheStoreOptions {
16
+ /** Key prefix for namespacing (default: "bashkit:") */
17
+ prefix?: string;
18
+ }
19
+ /**
20
+ * Creates a CacheStore from an existing Redis client.
21
+ *
22
+ * TTL is handled by the cached() wrapper, not Redis. This ensures
23
+ * consistent TTL behavior across all cache backends.
24
+ *
25
+ * @param client - Your Redis client (redis, ioredis, etc.)
26
+ * @param options - Configuration options (prefix)
27
+ * @returns CacheStore compatible with bashkit caching
28
+ *
29
+ * @example
30
+ * ```typescript
31
+ * import { createClient } from "redis";
32
+ * import { createRedisCacheStore, createAgentTools } from "bashkit";
33
+ *
34
+ * const redis = createClient();
35
+ * await redis.connect();
36
+ *
37
+ * const store = createRedisCacheStore(redis);
38
+ * const { tools } = createAgentTools(sandbox, { cache: store });
39
+ * ```
40
+ */
41
+ export declare function createRedisCacheStore(client: RedisClient, options?: RedisCacheStoreOptions): CacheStore;
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Cache entry with result and timestamp for TTL checks.
3
+ */
4
+ export interface CacheEntry<T = unknown> {
5
+ result: T;
6
+ timestamp: number;
7
+ }
8
+ /**
9
+ * Cache store interface for tool result caching.
10
+ * Supports both sync and async operations for different backends (LRU, Redis, etc.)
11
+ */
12
+ export interface CacheStore<T = unknown> {
13
+ /** Get cached entry by key */
14
+ get(key: string): CacheEntry<T> | undefined | Promise<CacheEntry<T> | undefined>;
15
+ /** Set cache entry */
16
+ set(key: string, entry: CacheEntry<T>): void | Promise<void>;
17
+ /** Delete cache entry */
18
+ delete(key: string): void | Promise<void>;
19
+ /** Clear all entries */
20
+ clear(): void | Promise<void>;
21
+ /** Get current cache size (optional, for stats) */
22
+ size?(): number | Promise<number>;
23
+ }
24
+ /**
25
+ * Options for the cached() tool wrapper.
26
+ */
27
+ export interface CacheOptions {
28
+ /** TTL in milliseconds (default: 5 minutes) */
29
+ ttl?: number;
30
+ /** Custom cache store (default: LRUCacheStore) */
31
+ store?: CacheStore;
32
+ /** Custom key generator */
33
+ keyGenerator?: (toolName: string, params: unknown) => string;
34
+ /** Enable debug logging for cache hits/misses */
35
+ debug?: boolean;
36
+ /** Callback when cache hit occurs */
37
+ onHit?: (toolName: string, key: string) => void;
38
+ /** Callback when cache miss occurs */
39
+ onMiss?: (toolName: string, key: string) => void;
40
+ }
41
+ /**
42
+ * Cache statistics returned by getStats().
43
+ */
44
+ export interface CacheStats {
45
+ /** Number of cache hits */
46
+ hits: number;
47
+ /** Number of cache misses */
48
+ misses: number;
49
+ /** Hit rate (0-1) */
50
+ hitRate: number;
51
+ /** Current cache size */
52
+ size: number;
53
+ }
package/dist/index.d.ts CHANGED
@@ -1,15 +1,17 @@
1
- export type { UIMessageStreamWriter } from "ai";
2
- export { anthropicPromptCacheMiddleware } from "./middleware";
1
+ export type { UIMessageStreamWriter, StreamTextResult, Tool, ToolSet, LanguageModel, LanguageModelMiddleware, Output, } from "ai";
2
+ export { anthropicPromptCacheMiddleware, anthropicPromptCacheMiddlewareV2, } from "./middleware";
3
3
  export type { E2BSandboxConfig, LocalSandboxConfig, VercelSandboxConfig, } from "./sandbox";
4
4
  export { createE2BSandbox, createLocalSandbox, createVercelSandbox, } from "./sandbox";
5
5
  export type { ExecOptions, ExecResult, Sandbox } from "./sandbox/interface";
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
+ 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, WebSearchError, WebSearchOutput, WebSearchResult, WriteError, WriteOutput, } from "./tools";
7
7
  export { createAgentTools, createAskUserTool, createBashTool, createEditTool, createEnterPlanModeTool, createExitPlanModeTool, createGlobTool, createGrepTool, createReadTool, createSkillTool, createTaskTool, createTodoWriteTool, createWebFetchTool, createWebSearchTool, createWriteTool, } from "./tools";
8
- export type { AgentConfig, AskUserConfig, SkillConfig, ToolConfig, WebFetchConfig, WebSearchConfig, } from "./types";
8
+ export type { AgentConfig, AskUserConfig, CacheConfig, SkillConfig, ToolConfig, WebFetchConfig, WebSearchConfig, } from "./types";
9
9
  export { DEFAULT_CONFIG } from "./types";
10
+ export type { CachedTool, CacheEntry, CacheOptions, CacheStats, CacheStore, RedisCacheStoreOptions, RedisClient, } from "./cache";
11
+ export { cached, createRedisCacheStore, LRUCacheStore } from "./cache";
10
12
  export type { CompactConversationConfig, CompactConversationResult, CompactConversationState, ContextMetrics, ContextStatus, ContextStatusConfig, ContextStatusLevel, ModelContextLimit, PruneMessagesConfig, } from "./utils";
11
13
  export { compactConversation, contextNeedsAttention, contextNeedsCompaction, createCompactConfig, estimateMessagesTokens, estimateMessageTokens, estimateTokens, getContextStatus, MODEL_CONTEXT_LIMITS, pruneMessagesByTokens, } from "./utils";
12
14
  export type { DiscoverSkillsOptions, SkillBundle, SkillMetadata, } from "./skills";
13
- export { discoverSkills, fetchSkill, fetchSkills, parseSkillMetadata, skillsToXml, } from "./skills";
15
+ export { discoverSkills, fetchSkill, fetchSkills, loadSkillBundle, loadSkillBundles, parseSkillMetadata, skillsToXml, } from "./skills";
14
16
  export type { AgentEnvironmentConfig, SetupResult, SkillContent, } from "./setup";
15
17
  export { setupAgentEnvironment } from "./setup";