bashkit 0.1.3 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/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 ensureCacheMarker(message) {
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,36 +34,41 @@ 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
- transformParams: async ({
41
- params
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";
57
54
  function createE2BSandbox(config = {}) {
58
55
  let sandbox = null;
56
+ let sandboxId = config.sandboxId;
59
57
  const workingDirectory = config.cwd || "/home/user";
60
58
  const timeout = config.timeout ?? 300000;
61
59
  const ensureSandbox = async () => {
62
60
  if (sandbox)
63
61
  return sandbox;
64
- sandbox = await E2BSandboxSDK.create({
65
- apiKey: config.apiKey,
66
- timeoutMs: timeout,
67
- metadata: config.metadata
68
- });
62
+ if (config.sandboxId) {
63
+ sandbox = await E2BSandboxSDK.connect(config.sandboxId);
64
+ } else {
65
+ sandbox = await E2BSandboxSDK.create({
66
+ apiKey: config.apiKey,
67
+ timeoutMs: timeout,
68
+ metadata: config.metadata
69
+ });
70
+ sandboxId = sandbox.sandboxId;
71
+ }
69
72
  return sandbox;
70
73
  };
71
74
  const exec = async (command, options) => {
@@ -111,6 +114,9 @@ function createE2BSandbox(config = {}) {
111
114
  };
112
115
  return {
113
116
  exec,
117
+ get id() {
118
+ return sandboxId;
119
+ },
114
120
  async readFile(path) {
115
121
  const result = await exec(`cat "${path}"`);
116
122
  if (result.exitCode !== 0) {
@@ -230,6 +236,7 @@ function createLocalSandbox(config = {}) {
230
236
  import { Sandbox as VercelSandboxSDK } from "@vercel/sandbox";
231
237
  function createVercelSandbox(config = {}) {
232
238
  let sandbox = null;
239
+ let sandboxId = config.sandboxId;
233
240
  const workingDirectory = config.cwd || "/vercel/sandbox";
234
241
  const resolvedConfig = {
235
242
  runtime: config.runtime ?? "node22",
@@ -250,7 +257,12 @@ function createVercelSandbox(config = {}) {
250
257
  token: config.token
251
258
  });
252
259
  }
253
- sandbox = await VercelSandboxSDK.create(createOptions);
260
+ if (config.sandboxId) {
261
+ sandbox = await VercelSandboxSDK.get({ sandboxId: config.sandboxId });
262
+ } else {
263
+ sandbox = await VercelSandboxSDK.create(createOptions);
264
+ }
265
+ sandboxId = sandbox.sandboxId;
254
266
  return sandbox;
255
267
  };
256
268
  const exec = async (command, options) => {
@@ -304,6 +316,9 @@ function createVercelSandbox(config = {}) {
304
316
  };
305
317
  return {
306
318
  exec,
319
+ get id() {
320
+ return sandboxId;
321
+ },
307
322
  async readFile(path) {
308
323
  const sbx = await ensureSandbox();
309
324
  const stream = await sbx.readFile({ path });
@@ -349,6 +364,132 @@ function createVercelSandbox(config = {}) {
349
364
  }
350
365
  };
351
366
  }
367
+ // src/cache/lru.ts
368
+ class LRUCacheStore {
369
+ cache = new Map;
370
+ maxSize;
371
+ constructor(maxSize = 1000) {
372
+ this.maxSize = maxSize;
373
+ }
374
+ get(key) {
375
+ const entry = this.cache.get(key);
376
+ if (entry) {
377
+ this.cache.delete(key);
378
+ this.cache.set(key, entry);
379
+ }
380
+ return entry;
381
+ }
382
+ set(key, entry) {
383
+ if (this.cache.has(key)) {
384
+ this.cache.delete(key);
385
+ } else if (this.cache.size >= this.maxSize) {
386
+ const firstKey = this.cache.keys().next().value;
387
+ if (firstKey !== undefined) {
388
+ this.cache.delete(firstKey);
389
+ }
390
+ }
391
+ this.cache.set(key, entry);
392
+ }
393
+ delete(key) {
394
+ this.cache.delete(key);
395
+ }
396
+ clear() {
397
+ this.cache.clear();
398
+ }
399
+ size() {
400
+ return this.cache.size;
401
+ }
402
+ }
403
+ // src/cache/cached.ts
404
+ import { createHash } from "crypto";
405
+ function defaultKeyGenerator(toolName, params) {
406
+ const sortedKeys = params && typeof params === "object" ? Object.keys(params).sort() : undefined;
407
+ const serialized = JSON.stringify(params, sortedKeys);
408
+ const hash = createHash("sha256").update(serialized).digest("hex").slice(0, 16);
409
+ return `${toolName}:${hash}`;
410
+ }
411
+ function cached(tool, toolName, options = {}) {
412
+ const {
413
+ ttl = 5 * 60 * 1000,
414
+ store = new LRUCacheStore,
415
+ keyGenerator = defaultKeyGenerator,
416
+ debug = false,
417
+ onHit,
418
+ onMiss
419
+ } = options;
420
+ let hits = 0;
421
+ let misses = 0;
422
+ const log = debug ? console.log.bind(console) : () => {};
423
+ const cachedTool = {
424
+ ...tool,
425
+ execute: async (params, execOptions) => {
426
+ const key = keyGenerator(toolName, params);
427
+ const now = Date.now();
428
+ const entry = await store.get(key);
429
+ if (entry && now - entry.timestamp < ttl) {
430
+ hits++;
431
+ log(`[Cache] HIT ${toolName}:${key.slice(-8)}`);
432
+ onHit?.(toolName, key);
433
+ return entry.result;
434
+ }
435
+ misses++;
436
+ log(`[Cache] MISS ${toolName}:${key.slice(-8)}`);
437
+ onMiss?.(toolName, key);
438
+ if (!tool.execute) {
439
+ throw new Error(`Tool ${toolName} has no execute function`);
440
+ }
441
+ const result = await tool.execute(params, execOptions);
442
+ if (result && typeof result === "object" && !("error" in result)) {
443
+ await store.set(key, { result, timestamp: now });
444
+ log(`[Cache] STORED ${toolName}:${key.slice(-8)}`);
445
+ }
446
+ return result;
447
+ },
448
+ async getStats() {
449
+ const total = hits + misses;
450
+ const size = await store.size?.() ?? 0;
451
+ return {
452
+ hits,
453
+ misses,
454
+ hitRate: total > 0 ? hits / total : 0,
455
+ size
456
+ };
457
+ },
458
+ async clearCache(key) {
459
+ if (key) {
460
+ await store.delete(key);
461
+ } else {
462
+ await store.clear();
463
+ }
464
+ }
465
+ };
466
+ return cachedTool;
467
+ }
468
+ // src/cache/redis.ts
469
+ function createRedisCacheStore(client, options = {}) {
470
+ const { prefix = "bashkit:" } = options;
471
+ return {
472
+ async get(key) {
473
+ const data = await client.get(`${prefix}${key}`);
474
+ return data ? JSON.parse(data) : undefined;
475
+ },
476
+ async set(key, entry) {
477
+ await client.set(`${prefix}${key}`, JSON.stringify(entry));
478
+ },
479
+ async delete(key) {
480
+ await client.del(`${prefix}${key}`);
481
+ },
482
+ async clear() {
483
+ const keys = await client.keys(`${prefix}*`);
484
+ if (keys.length)
485
+ await client.del(keys);
486
+ },
487
+ async size() {
488
+ const keys = await client.keys(`${prefix}*`);
489
+ return keys.length;
490
+ }
491
+ };
492
+ }
352
493
  // src/types.ts
353
494
  var DEFAULT_CONFIG = {
354
495
  defaultTimeout: 120000,
@@ -500,6 +641,9 @@ function createBashTool(sandbox, config) {
500
641
  return tool2({
501
642
  description: BASH_DESCRIPTION,
502
643
  inputSchema: zodSchema2(bashInputSchema),
644
+ strict: config?.strict,
645
+ needsApproval: config?.needsApproval,
646
+ providerOptions: config?.providerOptions,
503
647
  execute: async ({
504
648
  command,
505
649
  timeout,
@@ -576,6 +720,9 @@ function createEditTool(sandbox, config) {
576
720
  return tool3({
577
721
  description: EDIT_DESCRIPTION,
578
722
  inputSchema: zodSchema3(editInputSchema),
723
+ strict: config?.strict,
724
+ needsApproval: config?.needsApproval,
725
+ providerOptions: config?.providerOptions,
579
726
  execute: async ({
580
727
  file_path,
581
728
  old_string,
@@ -750,7 +897,7 @@ Before using this tool, ensure your plan is clear and unambiguous. If there are
750
897
  1. "Search for and understand the implementation of X" - Do NOT use this tool (research task)
751
898
  2. "Help me implement feature Y" - Use this tool after planning the implementation steps
752
899
  3. "Add user authentication" - If unsure about approach (OAuth vs JWT), clarify first, then use this tool`;
753
- function createExitPlanModeTool(config, onPlanSubmit) {
900
+ function createExitPlanModeTool(onPlanSubmit) {
754
901
  return tool5({
755
902
  description: EXIT_PLAN_MODE_DESCRIPTION,
756
903
  inputSchema: zodSchema5(exitPlanModeInputSchema),
@@ -794,6 +941,9 @@ function createGlobTool(sandbox, config) {
794
941
  return tool6({
795
942
  description: GLOB_DESCRIPTION,
796
943
  inputSchema: zodSchema6(globInputSchema),
944
+ strict: config?.strict,
945
+ needsApproval: config?.needsApproval,
946
+ providerOptions: config?.providerOptions,
797
947
  execute: async ({
798
948
  pattern,
799
949
  path
@@ -871,6 +1021,9 @@ function createGrepTool(sandbox, config) {
871
1021
  return tool7({
872
1022
  description: GREP_DESCRIPTION,
873
1023
  inputSchema: zodSchema7(grepInputSchema),
1024
+ strict: config?.strict,
1025
+ needsApproval: config?.needsApproval,
1026
+ providerOptions: config?.providerOptions,
874
1027
  execute: async (input) => {
875
1028
  const {
876
1029
  pattern,
@@ -1080,6 +1233,9 @@ function createReadTool(sandbox, config) {
1080
1233
  return tool8({
1081
1234
  description: READ_DESCRIPTION,
1082
1235
  inputSchema: zodSchema8(readInputSchema),
1236
+ strict: config?.strict,
1237
+ needsApproval: config?.needsApproval,
1238
+ providerOptions: config?.providerOptions,
1083
1239
  execute: async ({
1084
1240
  file_path,
1085
1241
  offset,
@@ -1236,11 +1392,15 @@ function createSkillTool(config) {
1236
1392
  import { generateText, tool as tool10, zodSchema as zodSchema10 } from "ai";
1237
1393
  import Parallel from "parallel-web";
1238
1394
  import { z as z10 } from "zod";
1395
+
1396
+ // src/utils/http-constants.ts
1397
+ var RETRYABLE_STATUS_CODES = [408, 429, 500, 502, 503];
1398
+
1399
+ // src/tools/web-fetch.ts
1239
1400
  var webFetchInputSchema = z10.object({
1240
1401
  url: z10.string().describe("The URL to fetch content from"),
1241
1402
  prompt: z10.string().describe("The prompt to run on the fetched content")
1242
1403
  });
1243
- var RETRYABLE_CODES = [408, 429, 500, 502, 503];
1244
1404
  var WEB_FETCH_DESCRIPTION = `
1245
1405
  - Fetches content from a specified URL and processes it using an AI model
1246
1406
  - Takes a URL and a prompt as input
@@ -1258,10 +1418,13 @@ Usage notes:
1258
1418
  - 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
1419
  `;
1260
1420
  function createWebFetchTool(config) {
1261
- const { apiKey, model } = config;
1421
+ const { apiKey, model, strict, needsApproval, providerOptions } = config;
1262
1422
  return tool10({
1263
1423
  description: WEB_FETCH_DESCRIPTION,
1264
1424
  inputSchema: zodSchema10(webFetchInputSchema),
1425
+ strict,
1426
+ needsApproval,
1427
+ providerOptions,
1265
1428
  execute: async (input) => {
1266
1429
  const { url, prompt } = input;
1267
1430
  try {
@@ -1309,7 +1472,7 @@ ${content}`
1309
1472
  return {
1310
1473
  error: message,
1311
1474
  status_code: statusCode,
1312
- retryable: RETRYABLE_CODES.includes(statusCode)
1475
+ retryable: RETRYABLE_STATUS_CODES.includes(statusCode)
1313
1476
  };
1314
1477
  }
1315
1478
  return {
@@ -1329,7 +1492,6 @@ var webSearchInputSchema = z11.object({
1329
1492
  allowed_domains: z11.array(z11.string()).optional().describe("Only include results from these domains"),
1330
1493
  blocked_domains: z11.array(z11.string()).optional().describe("Never include results from these domains")
1331
1494
  });
1332
- var RETRYABLE_CODES2 = [408, 429, 500, 502, 503];
1333
1495
  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
1496
 
1335
1497
  **Capabilities:**
@@ -1351,10 +1513,13 @@ When searching for recent information, documentation, or current events, use the
1351
1513
  - allowed_domains: Only include results from these domains
1352
1514
  - blocked_domains: Never include results from these domains`;
1353
1515
  function createWebSearchTool(config) {
1354
- const { apiKey } = config;
1516
+ const { apiKey, strict, needsApproval, providerOptions } = config;
1355
1517
  return tool11({
1356
1518
  description: WEB_SEARCH_DESCRIPTION,
1357
1519
  inputSchema: zodSchema11(webSearchInputSchema),
1520
+ strict,
1521
+ needsApproval,
1522
+ providerOptions,
1358
1523
  execute: async (input) => {
1359
1524
  const { query, allowed_domains, blocked_domains } = input;
1360
1525
  try {
@@ -1388,7 +1553,7 @@ function createWebSearchTool(config) {
1388
1553
  return {
1389
1554
  error: message,
1390
1555
  status_code: statusCode,
1391
- retryable: RETRYABLE_CODES2.includes(statusCode)
1556
+ retryable: RETRYABLE_STATUS_CODES.includes(statusCode)
1392
1557
  };
1393
1558
  }
1394
1559
  return {
@@ -1422,6 +1587,9 @@ function createWriteTool(sandbox, config) {
1422
1587
  return tool12({
1423
1588
  description: WRITE_DESCRIPTION,
1424
1589
  inputSchema: zodSchema12(writeInputSchema),
1590
+ strict: config?.strict,
1591
+ needsApproval: config?.needsApproval,
1592
+ providerOptions: config?.providerOptions,
1425
1593
  execute: async ({
1426
1594
  file_path,
1427
1595
  content
@@ -1465,13 +1633,17 @@ import { z as z13 } from "zod";
1465
1633
  var taskInputSchema = z13.object({
1466
1634
  description: z13.string().describe("A short (3-5 word) description of the task"),
1467
1635
  prompt: z13.string().describe("The task for the agent to perform"),
1468
- subagent_type: z13.string().describe("The type of specialized agent to use for this task")
1636
+ subagent_type: z13.string().describe("The type of specialized agent to use for this task"),
1637
+ 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."),
1638
+ 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.")
1469
1639
  });
1470
1640
  var TASK_DESCRIPTION = `Launch a new agent to handle complex, multi-step tasks autonomously.
1471
1641
 
1472
1642
  The Task tool launches specialized agents (subprocesses) that autonomously handle complex tasks. Each agent type has specific capabilities and tools available to it.
1473
1643
 
1474
- When using the Task tool, you must specify a subagent_type parameter to select which agent type to use.
1644
+ **Subagent types:**
1645
+ - Use predefined subagent_type values for common task patterns
1646
+ - For dynamic agents: provide custom system_prompt and/or tools to create a specialized agent on the fly
1475
1647
 
1476
1648
  **When NOT to use the Task tool:**
1477
1649
  - If you want to read a specific file path, use the Read or Glob tool instead, to find the match more quickly
@@ -1517,14 +1689,16 @@ function createTaskTool(config) {
1517
1689
  execute: async ({
1518
1690
  description,
1519
1691
  prompt,
1520
- subagent_type
1692
+ subagent_type,
1693
+ system_prompt,
1694
+ tools: customTools
1521
1695
  }) => {
1522
1696
  const startTime = performance.now();
1523
1697
  try {
1524
1698
  const typeConfig = subagentTypes[subagent_type] || {};
1525
1699
  const model = typeConfig.model || defaultModel;
1526
- const tools = filterTools(allTools, typeConfig.tools);
1527
- const systemPrompt = typeConfig.systemPrompt;
1700
+ const tools = filterTools(allTools, customTools ?? typeConfig.tools);
1701
+ const systemPrompt = system_prompt ?? typeConfig.systemPrompt;
1528
1702
  const commonOptions = {
1529
1703
  model,
1530
1704
  tools,
@@ -1677,7 +1851,7 @@ var TODO_WRITE_DESCRIPTION = `Use this tool to create and manage a structured ta
1677
1851
  - Keep exactly ONE task in_progress at any time
1678
1852
  - ONLY mark completed when FULLY accomplished
1679
1853
  - If blocked/errors, keep in_progress and create new task for the blocker`;
1680
- function createTodoWriteTool(state, config, onUpdate) {
1854
+ function createTodoWriteTool(state, onUpdate) {
1681
1855
  return tool14({
1682
1856
  description: TODO_WRITE_DESCRIPTION,
1683
1857
  inputSchema: zodSchema14(todoWriteInputSchema),
@@ -1709,6 +1883,58 @@ function createTodoWriteTool(state, config, onUpdate) {
1709
1883
  }
1710
1884
 
1711
1885
  // src/tools/index.ts
1886
+ var DEFAULT_CACHEABLE = [
1887
+ "Read",
1888
+ "Glob",
1889
+ "Grep",
1890
+ "WebFetch",
1891
+ "WebSearch"
1892
+ ];
1893
+ function resolveCache(config) {
1894
+ if (!config) {
1895
+ return { store: null, ttl: 0, debug: false, enabled: new Set };
1896
+ }
1897
+ if (config === true) {
1898
+ return {
1899
+ store: new LRUCacheStore,
1900
+ ttl: 5 * 60 * 1000,
1901
+ debug: false,
1902
+ enabled: new Set(DEFAULT_CACHEABLE)
1903
+ };
1904
+ }
1905
+ if (typeof config === "object" && typeof config.get === "function" && typeof config.set === "function" && typeof config.delete === "function" && typeof config.clear === "function") {
1906
+ return {
1907
+ store: config,
1908
+ ttl: 5 * 60 * 1000,
1909
+ debug: false,
1910
+ enabled: new Set(DEFAULT_CACHEABLE)
1911
+ };
1912
+ }
1913
+ const enabled = new Set;
1914
+ for (const tool15 of DEFAULT_CACHEABLE) {
1915
+ if (config[tool15] !== false) {
1916
+ enabled.add(tool15);
1917
+ }
1918
+ }
1919
+ for (const [key, value] of Object.entries(config)) {
1920
+ if (["store", "ttl", "debug", "onHit", "onMiss", "keyGenerator"].includes(key))
1921
+ continue;
1922
+ if (value === true)
1923
+ enabled.add(key);
1924
+ if (value === false)
1925
+ enabled.delete(key);
1926
+ }
1927
+ const cfg = config;
1928
+ return {
1929
+ store: cfg.store ?? new LRUCacheStore,
1930
+ ttl: cfg.ttl ?? 5 * 60 * 1000,
1931
+ debug: cfg.debug ?? false,
1932
+ onHit: cfg.onHit,
1933
+ onMiss: cfg.onMiss,
1934
+ keyGenerator: cfg.keyGenerator,
1935
+ enabled
1936
+ };
1937
+ }
1712
1938
  function createAgentTools(sandbox, config) {
1713
1939
  const toolsConfig = {
1714
1940
  ...DEFAULT_CONFIG.tools,
@@ -1744,6 +1970,21 @@ function createAgentTools(sandbox, config) {
1744
1970
  if (config?.webFetch) {
1745
1971
  tools.WebFetch = createWebFetchTool(config.webFetch);
1746
1972
  }
1973
+ const cacheConfig = resolveCache(config?.cache);
1974
+ if (cacheConfig.store) {
1975
+ for (const [name, tool15] of Object.entries(tools)) {
1976
+ if (cacheConfig.enabled.has(name)) {
1977
+ tools[name] = cached(tool15, name, {
1978
+ store: cacheConfig.store,
1979
+ ttl: cacheConfig.ttl,
1980
+ debug: cacheConfig.debug,
1981
+ onHit: cacheConfig.onHit,
1982
+ onMiss: cacheConfig.onMiss,
1983
+ keyGenerator: cacheConfig.keyGenerator
1984
+ });
1985
+ }
1986
+ }
1987
+ }
1747
1988
  return { tools, planModeState };
1748
1989
  }
1749
1990
  // src/utils/compact-conversation.ts
@@ -2097,11 +2338,13 @@ function contextNeedsCompaction(status) {
2097
2338
  return status.status === "critical";
2098
2339
  }
2099
2340
  // src/skills/discovery.ts
2100
- import { readdir, readFile, stat } from "node:fs/promises";
2341
+ import { readdir as readdir2, readFile as readFile2, stat } from "node:fs/promises";
2101
2342
  import { homedir } from "node:os";
2102
- import { join, resolve } from "node:path";
2343
+ import { join as join2, resolve } from "node:path";
2103
2344
 
2104
2345
  // src/skills/loader.ts
2346
+ import { readdir, readFile } from "node:fs/promises";
2347
+ import { basename, dirname, join } from "node:path";
2105
2348
  function parseSkillMetadata(content, skillPath) {
2106
2349
  const frontmatter = extractFrontmatter(content);
2107
2350
  if (!frontmatter) {
@@ -2190,6 +2433,32 @@ function parseYaml(yaml) {
2190
2433
  }
2191
2434
  return result;
2192
2435
  }
2436
+ async function loadSkillBundle(skillDir) {
2437
+ const files = {};
2438
+ const name = basename(skillDir);
2439
+ async function readDirRecursive(dir, prefix = "") {
2440
+ const entries = await readdir(dir, { withFileTypes: true });
2441
+ for (const entry of entries) {
2442
+ const relativePath = prefix ? `${prefix}/${entry.name}` : entry.name;
2443
+ const fullPath = join(dir, entry.name);
2444
+ if (entry.isDirectory()) {
2445
+ await readDirRecursive(fullPath, relativePath);
2446
+ } else {
2447
+ files[relativePath] = await readFile(fullPath, "utf-8");
2448
+ }
2449
+ }
2450
+ }
2451
+ await readDirRecursive(skillDir);
2452
+ return { name, files };
2453
+ }
2454
+ async function loadSkillBundles(skills) {
2455
+ const bundles = {};
2456
+ for (const skill of skills) {
2457
+ const skillDir = dirname(skill.path);
2458
+ bundles[skill.name] = await loadSkillBundle(skillDir);
2459
+ }
2460
+ return bundles;
2461
+ }
2193
2462
 
2194
2463
  // src/skills/discovery.ts
2195
2464
  var DEFAULT_SKILL_PATHS = [".skills", "~/.bashkit/skills"];
@@ -2212,7 +2481,7 @@ async function discoverSkills(options) {
2212
2481
  }
2213
2482
  function resolvePath(path, cwd) {
2214
2483
  if (path.startsWith("~/")) {
2215
- return join(homedir(), path.slice(2));
2484
+ return join2(homedir(), path.slice(2));
2216
2485
  }
2217
2486
  if (path.startsWith("/")) {
2218
2487
  return path;
@@ -2222,18 +2491,18 @@ function resolvePath(path, cwd) {
2222
2491
  async function scanDirectory(dirPath) {
2223
2492
  const skills = [];
2224
2493
  try {
2225
- const entries = await readdir(dirPath, { withFileTypes: true });
2494
+ const entries = await readdir2(dirPath, { withFileTypes: true });
2226
2495
  for (const entry of entries) {
2227
2496
  if (!entry.isDirectory()) {
2228
2497
  continue;
2229
2498
  }
2230
- const skillPath = join(dirPath, entry.name, "SKILL.md");
2499
+ const skillPath = join2(dirPath, entry.name, "SKILL.md");
2231
2500
  try {
2232
2501
  const skillStat = await stat(skillPath);
2233
2502
  if (!skillStat.isFile()) {
2234
2503
  continue;
2235
2504
  }
2236
- const content = await readFile(skillPath, "utf-8");
2505
+ const content = await readFile2(skillPath, "utf-8");
2237
2506
  const metadata = parseSkillMetadata(content, skillPath);
2238
2507
  if (metadata.name !== entry.name) {
2239
2508
  console.warn(`Skill name "${metadata.name}" does not match folder name "${entry.name}" in ${skillPath}`);
@@ -2406,6 +2675,8 @@ export {
2406
2675
  setupAgentEnvironment,
2407
2676
  pruneMessagesByTokens,
2408
2677
  parseSkillMetadata,
2678
+ loadSkillBundles,
2679
+ loadSkillBundle,
2409
2680
  getContextStatus,
2410
2681
  fetchSkills,
2411
2682
  fetchSkill,
@@ -2420,6 +2691,7 @@ export {
2420
2691
  createTodoWriteTool,
2421
2692
  createTaskTool,
2422
2693
  createSkillTool,
2694
+ createRedisCacheStore,
2423
2695
  createReadTool,
2424
2696
  createLocalSandbox,
2425
2697
  createGrepTool,
@@ -2435,7 +2707,10 @@ export {
2435
2707
  contextNeedsCompaction,
2436
2708
  contextNeedsAttention,
2437
2709
  compactConversation,
2710
+ cached,
2711
+ anthropicPromptCacheMiddlewareV2,
2438
2712
  anthropicPromptCacheMiddleware,
2439
2713
  MODEL_CONTEXT_LIMITS,
2714
+ LRUCacheStore,
2440
2715
  DEFAULT_CONFIG
2441
2716
  };
@@ -1,6 +1,25 @@
1
1
  import type { LanguageModelV2Middleware } from "@ai-sdk/provider";
2
+ import type { LanguageModelMiddleware } from "ai";
2
3
  /**
3
4
  * Middleware that enables Anthropic's prompt caching feature.
5
+ * For AI SDK v5 (LanguageModelV2).
6
+ *
7
+ * @example
8
+ * ```typescript
9
+ * import { wrapLanguageModel } from 'ai';
10
+ * import { anthropic } from '@ai-sdk/anthropic';
11
+ * import { anthropicPromptCacheMiddlewareV2 } from 'bashkit';
12
+ *
13
+ * const model = wrapLanguageModel({
14
+ * model: anthropic('claude-sonnet-4-20250514'),
15
+ * middleware: anthropicPromptCacheMiddlewareV2,
16
+ * });
17
+ * ```
18
+ */
19
+ export declare const anthropicPromptCacheMiddlewareV2: LanguageModelV2Middleware;
20
+ /**
21
+ * Middleware that enables Anthropic's prompt caching feature.
22
+ * For AI SDK v6+ (LanguageModelV3).
4
23
  *
5
24
  * @example
6
25
  * ```typescript
@@ -14,4 +33,4 @@ import type { LanguageModelV2Middleware } from "@ai-sdk/provider";
14
33
  * });
15
34
  * ```
16
35
  */
17
- export declare const anthropicPromptCacheMiddleware: LanguageModelV2Middleware;
36
+ export declare const anthropicPromptCacheMiddleware: LanguageModelMiddleware;
@@ -1 +1 @@
1
- export { anthropicPromptCacheMiddleware } from "./anthropic-cache";
1
+ export { anthropicPromptCacheMiddleware, anthropicPromptCacheMiddlewareV2, } from "./anthropic-cache";
@@ -1,6 +1,8 @@
1
1
  import type { Sandbox } from "./interface";
2
2
  export interface E2BSandboxConfig {
3
3
  apiKey?: string;
4
+ /** Existing sandbox ID to reconnect to instead of creating new */
5
+ sandboxId?: string;
4
6
  template?: string;
5
7
  timeout?: number;
6
8
  cwd?: string;
@@ -18,4 +18,11 @@ export interface Sandbox {
18
18
  fileExists(path: string): Promise<boolean>;
19
19
  isDirectory(path: string): Promise<boolean>;
20
20
  destroy(): Promise<void>;
21
+ /**
22
+ * Sandbox ID for reconnection (cloud providers only).
23
+ * - For new sandboxes: available after first operation
24
+ * - For reconnected sandboxes: available immediately
25
+ * - For local sandboxes: always undefined
26
+ */
27
+ readonly id?: string;
21
28
  }
@@ -4,6 +4,8 @@ export interface VercelSandboxConfig {
4
4
  resources?: {
5
5
  vcpus: number;
6
6
  };
7
+ /** Existing sandbox ID to reconnect to instead of creating new */
8
+ sandboxId?: string;
7
9
  timeout?: number;
8
10
  cwd?: string;
9
11
  teamId?: string;
@@ -2,5 +2,5 @@ export type { DiscoverSkillsOptions, SkillMetadata } from "./types";
2
2
  export type { SkillBundle } from "./fetch";
3
3
  export { discoverSkills } from "./discovery";
4
4
  export { fetchSkill, fetchSkills } from "./fetch";
5
- export { parseSkillMetadata } from "./loader";
5
+ export { parseSkillMetadata, loadSkillBundle, loadSkillBundles, } from "./loader";
6
6
  export { skillsToXml } from "./xml";