@better-agent/plugins 0.1.0-canary.6 → 0.2.0-beta.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.mjs CHANGED
@@ -1,114 +1,25 @@
1
1
  import { BetterAgentError } from "@better-agent/shared/errors";
2
2
  import { defineTool } from "@better-agent/core";
3
3
 
4
- //#region src/shared/json.ts
5
- /** Creates a JSON response with a default content type. */
6
- function jsonResponse(body, init) {
7
- const headers = new Headers(init?.headers);
8
- if (!headers.has("content-type")) headers.set("content-type", "application/json");
9
- return new Response(JSON.stringify(body), {
10
- ...init,
11
- headers
12
- });
13
- }
14
-
15
- //#endregion
16
- //#region src/auth/responses.ts
17
- /** Creates the default unauthorized response. */
18
- function createUnauthorizedResponse() {
19
- return jsonResponse({
20
- error: "unauthorized",
21
- message: "Invalid API key."
22
- }, { status: 401 });
23
- }
24
-
25
- //#endregion
26
- //#region src/shared/validation.ts
27
- /** Creates a plugin validation error. */
28
- function createValidationError$1(message, at) {
29
- return BetterAgentError.fromCode("VALIDATION_FAILED", message, { trace: [{ at }] });
30
- }
31
- /** Requires a positive finite number. */
32
- function requirePositiveNumber(value, name, at) {
33
- if (!Number.isFinite(value) || value <= 0) throw createValidationError$1(`\`${name}\` must be a positive number.`, at);
34
- }
35
- /** Requires a non-empty array. */
36
- function requireNonEmptyArray(value, name, at) {
37
- if (!value || value.length === 0) throw createValidationError$1(`\`${name}\` must contain at least one value.`, at);
38
- }
39
-
40
- //#endregion
41
- //#region src/auth/validate.ts
42
- /** Validates `authPlugin` configuration. */
43
- function validateAuthPluginConfig(config) {
44
- if (!config.validate && (!config.apiKeys || config.apiKeys.length === 0)) throw createValidationError$1("`authPlugin` requires either `apiKeys` or `validate`.", "plugins.authPlugin");
45
- if (config.header !== void 0 && config.header.trim().length === 0) throw createValidationError$1("`authPlugin` requires `header` to be a non-empty string when provided.", "plugins.authPlugin");
46
- if (config.apiKeys) {
47
- if (config.apiKeys.filter((key) => typeof key === "string").map((key) => key.trim()).filter((key) => key.length > 0).length === 0 && !config.validate) throw createValidationError$1("`authPlugin` requires `apiKeys` to contain at least one non-empty key.", "plugins.authPlugin");
48
- }
49
- }
50
-
51
- //#endregion
52
- //#region src/auth/plugin.ts
53
- /**
54
- * Creates an API-key auth plugin.
55
- *
56
- * Provide either `apiKeys` or `validate`.
57
- *
58
- * @example
59
- * ```ts
60
- * const plugin = authPlugin({
61
- * apiKeys: ["dev-key"],
62
- * });
63
- * ```
64
- */
65
- const authPlugin = (config) => {
66
- validateAuthPluginConfig(config);
67
- const header = config.header?.trim() || "x-api-key";
68
- const apiKeys = new Set((config.apiKeys ?? []).map((key) => key.trim()).filter(Boolean));
69
- return {
70
- id: config.id ?? "auth",
71
- guards: [async (ctx) => {
72
- const keyValue = config.getKey ? await config.getKey({
73
- agentName: ctx.agentName,
74
- mode: ctx.mode,
75
- request: ctx.request
76
- }) : ctx.request.headers.get(header);
77
- const key = typeof keyValue === "string" && keyValue.trim().length > 0 ? keyValue : null;
78
- if (config.validate ? await config.validate({
79
- key,
80
- agentName: ctx.agentName,
81
- mode: ctx.mode,
82
- request: ctx.request
83
- }) : key !== null && apiKeys.has(key)) return null;
84
- return config.onUnauthorized ? await config.onUnauthorized({
85
- key,
86
- agentName: ctx.agentName,
87
- mode: ctx.mode,
88
- request: ctx.request
89
- }) : createUnauthorizedResponse();
90
- }]
91
- };
92
- };
93
-
94
- //#endregion
95
4
  //#region src/ip-allowlist/ip.ts
96
5
  /** Parses an IPv4 string. */
97
6
  function parseIpv4(input) {
98
7
  const parts = input.split(".");
99
8
  if (parts.length !== 4) return null;
100
9
  let value = 0n;
10
+ const normalizedParts = [];
101
11
  for (const part of parts) {
102
12
  if (!/^\d+$/.test(part)) return null;
103
13
  const octet = Number(part);
104
14
  if (octet < 0 || octet > 255) return null;
105
15
  value = value << 8n | BigInt(octet);
16
+ normalizedParts.push(String(octet));
106
17
  }
107
18
  return {
108
19
  kind: "ipv4",
109
20
  value,
110
21
  bits: 32,
111
- normalized: parts.join(".")
22
+ normalized: normalizedParts.join(".")
112
23
  };
113
24
  }
114
25
  /** Expands an IPv6 string into eight normalized segments. */
@@ -210,6 +121,18 @@ function parseAllowEntry(input) {
210
121
  };
211
122
  }
212
123
 
124
+ //#endregion
125
+ //#region src/shared/json.ts
126
+ /** Creates a JSON response with a default content type. */
127
+ function jsonResponse(body, init) {
128
+ const headers = new Headers(init?.headers);
129
+ if (!headers.has("content-type")) headers.set("content-type", "application/json");
130
+ return new Response(JSON.stringify(body), {
131
+ ...init,
132
+ headers
133
+ });
134
+ }
135
+
213
136
  //#endregion
214
137
  //#region src/ip-allowlist/responses.ts
215
138
  /** Creates the default IP denied response. */
@@ -220,12 +143,27 @@ function createIpDeniedResponse() {
220
143
  }, { status: 403 });
221
144
  }
222
145
 
146
+ //#endregion
147
+ //#region src/shared/validation.ts
148
+ /** Creates a plugin validation error. */
149
+ function createValidationError$1(message, at) {
150
+ return BetterAgentError.fromCode("VALIDATION_FAILED", message, { trace: [{ at }] });
151
+ }
152
+ /** Requires a positive finite number. */
153
+ function requirePositiveNumber(value, name, at) {
154
+ if (!Number.isFinite(value) || value <= 0) throw createValidationError$1(`\`${name}\` must be a positive number.`, at);
155
+ }
156
+ /** Requires a non-empty array. */
157
+ function requireNonEmptyArray(value, name, at) {
158
+ if (!value || value.length === 0) throw createValidationError$1(`\`${name}\` must contain at least one value.`, at);
159
+ }
160
+
223
161
  //#endregion
224
162
  //#region src/ip-allowlist/validate.ts
225
- /** Validates `ipAllowlistPlugin` configuration. */
226
- function validateIpAllowlistPluginConfig(config) {
227
- requireNonEmptyArray(config.allow, "allow", "plugins.ipAllowlistPlugin");
228
- for (const entry of config.allow) if (typeof entry !== "string" || !parseAllowEntry(entry)) throw createValidationError$1(`\`ipAllowlistPlugin\` received an invalid allow entry: '${String(entry)}'.`, "plugins.ipAllowlistPlugin");
163
+ /** Validates `ipAllowlist` configuration. */
164
+ function validateIpAllowlistConfig(config) {
165
+ requireNonEmptyArray(config.allow, "allow", "plugins.ipAllowlist");
166
+ for (const entry of config.allow) if (typeof entry !== "string" || !parseAllowEntry(entry)) throw createValidationError$1(`\`ipAllowlist\` received an invalid allow entry: '${String(entry)}'.`, "plugins.ipAllowlist");
229
167
  }
230
168
 
231
169
  //#endregion
@@ -261,13 +199,13 @@ function getDirectIp(request) {
261
199
  *
262
200
  * @example
263
201
  * ```ts
264
- * const plugin = ipAllowlistPlugin({
202
+ * const plugin = ipAllowlist({
265
203
  * allow: ["127.0.0.1", "10.0.0.0/8"],
266
204
  * });
267
205
  * ```
268
206
  */
269
- const ipAllowlistPlugin = (config) => {
270
- validateIpAllowlistPluginConfig(config);
207
+ const ipAllowlist = (config) => {
208
+ validateIpAllowlistConfig(config);
271
209
  const matchers = config.allow.map((entry) => {
272
210
  const matcher = parseAllowEntry(entry);
273
211
  if (!matcher) throw new Error(`Invalid allowlist entry: ${entry}`);
@@ -278,7 +216,6 @@ const ipAllowlistPlugin = (config) => {
278
216
  guards: [async (ctx) => {
279
217
  const resolvedIp = config.getIp ? await config.getIp({
280
218
  agentName: ctx.agentName,
281
- mode: ctx.mode,
282
219
  request: ctx.request
283
220
  }) : config.trustProxy ? getProxyIp(ctx.request) : getDirectIp(ctx.request);
284
221
  const normalizedIp = typeof resolvedIp === "string" && resolvedIp.trim().length > 0 ? normalizeIp(resolvedIp) : null;
@@ -287,7 +224,6 @@ const ipAllowlistPlugin = (config) => {
287
224
  return config.onDenied ? await config.onDenied({
288
225
  ip: normalizedIp,
289
226
  agentName: ctx.agentName,
290
- mode: ctx.mode,
291
227
  request: ctx.request
292
228
  }) : createIpDeniedResponse();
293
229
  }]
@@ -325,11 +261,6 @@ function redactHeaders(headers, extraHeaders) {
325
261
  return result;
326
262
  }
327
263
 
328
- //#endregion
329
- //#region src/logging/validate.ts
330
- /** Validates `loggingPlugin` configuration. */
331
- function validateLoggingPluginConfig(_config) {}
332
-
333
264
  //#endregion
334
265
  //#region src/logging/plugin.ts
335
266
  function safeInvoke(fn, payload) {
@@ -338,6 +269,14 @@ function safeInvoke(fn, payload) {
338
269
  fn(payload);
339
270
  } catch {}
340
271
  }
272
+ function safeMap(fn, input) {
273
+ if (!fn) return input;
274
+ try {
275
+ return fn(input);
276
+ } catch {
277
+ return input;
278
+ }
279
+ }
341
280
  /** Resolves the logger methods for the configured sink. */
342
281
  function getLoggerMethods(config) {
343
282
  return {
@@ -350,7 +289,7 @@ function getLoggerMethods(config) {
350
289
  /** Emits one log entry. */
351
290
  function emitLog(config, entry) {
352
291
  if (!shouldLog(config.level ?? "info", entry)) return;
353
- const output = config.format ? config.format(entry) : entry;
292
+ const output = safeMap(config.format, entry);
354
293
  safeInvoke(getLoggerMethods(config)[entry.level], output);
355
294
  }
356
295
  /** Maps one runtime event to a log level. */
@@ -358,16 +297,25 @@ function getEventLevel(event) {
358
297
  if (event.type.endsWith("_ERROR")) return "error";
359
298
  return "info";
360
299
  }
361
- /** Creates request log data. */
362
- function createRequestData(ctx) {
300
+ function createRedactedRequestData(ctx, config) {
363
301
  return {
364
- mode: ctx.mode,
365
302
  url: ctx.request.url,
366
303
  method: ctx.request.method,
367
- headers: redactHeaders(ctx.request.headers),
304
+ headers: redactHeaders(ctx.request.headers, config.redactHeaders),
368
305
  input: ctx.input
369
306
  };
370
307
  }
308
+ function redactBody(config, phase, body) {
309
+ if (!config.redactBody) return body;
310
+ try {
311
+ return config.redactBody({
312
+ body,
313
+ phase
314
+ });
315
+ } catch {
316
+ return body;
317
+ }
318
+ }
371
319
  /** Creates step log data. */
372
320
  function createStepData(ctx) {
373
321
  return {
@@ -376,49 +324,36 @@ function createStepData(ctx) {
376
324
  messageCount: ctx.messages.length
377
325
  };
378
326
  }
379
- /** Creates save log data. */
380
- function createSaveData(ctx) {
381
- const messageCount = ctx.items.filter((item) => item.type === "message").length;
382
- return {
383
- itemCount: ctx.items.length,
384
- messageCount
385
- };
386
- }
387
327
  /**
388
328
  * Creates a logging plugin.
389
329
  *
390
330
  * @example
391
331
  * ```ts
392
- * const plugin = loggingPlugin({
332
+ * const plugin = logging({
393
333
  * level: "info",
394
334
  * include: { requests: true, toolCalls: true },
395
335
  * });
396
336
  * ```
397
337
  */
398
- const loggingPlugin = (config = {}) => {
399
- /* @__PURE__ */ validateLoggingPluginConfig(config);
338
+ const logging = (config = {}) => {
400
339
  const include = {
401
340
  requests: config.include?.requests ?? true,
402
341
  events: config.include?.events ?? true,
403
342
  steps: config.include?.steps ?? true,
404
343
  modelCalls: config.include?.modelCalls ?? true,
405
344
  toolCalls: config.include?.toolCalls ?? true,
406
- saves: config.include?.saves ?? false,
407
345
  errors: config.include?.errors ?? true
408
346
  };
409
347
  const plugin = { id: config.id ?? "logging" };
410
348
  if (include.requests) plugin.guards = [async (ctx) => {
411
- const body = config.redactBody ? config.redactBody({
412
- body: ctx.input,
413
- phase: "request"
414
- }) : ctx.input;
349
+ const body = config.redactBody ? redactBody(config, "request", ctx.input) : ctx.input;
415
350
  emitLog(config, {
416
351
  level: "info",
417
352
  event: "request.received",
418
353
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
419
354
  agentName: ctx.agentName,
420
355
  data: {
421
- ...createRequestData(ctx),
356
+ ...createRedactedRequestData(ctx, config),
422
357
  input: body
423
358
  }
424
359
  });
@@ -430,10 +365,10 @@ const loggingPlugin = (config = {}) => {
430
365
  emitLog(config, {
431
366
  level,
432
367
  event: "run.event",
433
- timestamp: new Date(event.timestamp).toISOString(),
368
+ timestamp: new Date(event.timestamp ?? Date.now()).toISOString(),
434
369
  agentName: ctx.agentName,
435
370
  runId: ctx.runId,
436
- conversationId: ctx.conversationId,
371
+ conversationId: ctx.threadId,
437
372
  data: { type: event.type }
438
373
  });
439
374
  };
@@ -444,7 +379,7 @@ const loggingPlugin = (config = {}) => {
444
379
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
445
380
  agentName: ctx.agentName,
446
381
  runId: ctx.runId,
447
- conversationId: ctx.conversationId,
382
+ conversationId: ctx.threadId,
448
383
  data: createStepData(ctx)
449
384
  });
450
385
  };
@@ -456,10 +391,10 @@ const loggingPlugin = (config = {}) => {
456
391
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
457
392
  agentName: ctx.agentName,
458
393
  runId: ctx.runId,
459
- conversationId: ctx.conversationId,
394
+ conversationId: ctx.threadId,
460
395
  data: {
461
396
  stepIndex: ctx.stepIndex,
462
- inputCount: ctx.input.length,
397
+ inputCount: ctx.messages.length,
463
398
  toolCount: ctx.tools.length,
464
399
  toolChoice: ctx.toolChoice
465
400
  }
@@ -472,13 +407,10 @@ const loggingPlugin = (config = {}) => {
472
407
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
473
408
  agentName: ctx.agentName,
474
409
  runId: ctx.runId,
475
- conversationId: ctx.conversationId,
410
+ conversationId: ctx.threadId,
476
411
  data: {
477
412
  stepIndex: ctx.stepIndex,
478
- response: config.redactBody ? config.redactBody({
479
- body: ctx.response,
480
- phase: "response"
481
- }) : ctx.response
413
+ response: config.redactBody ? redactBody(config, "response", ctx.response) : ctx.response
482
414
  }
483
415
  });
484
416
  };
@@ -491,14 +423,11 @@ const loggingPlugin = (config = {}) => {
491
423
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
492
424
  agentName: ctx.agentName,
493
425
  runId: ctx.runId,
494
- conversationId: ctx.conversationId,
426
+ conversationId: ctx.threadId,
495
427
  data: {
496
428
  toolName: ctx.toolName,
497
429
  toolCallId: ctx.toolCallId,
498
- args: config.redactBody ? config.redactBody({
499
- body: ctx.args,
500
- phase: "tool_args"
501
- }) : ctx.args
430
+ args: config.redactBody ? redactBody(config, "tool_args", ctx.input) : ctx.input
502
431
  }
503
432
  });
504
433
  };
@@ -509,36 +438,16 @@ const loggingPlugin = (config = {}) => {
509
438
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
510
439
  agentName: ctx.agentName,
511
440
  runId: ctx.runId,
512
- conversationId: ctx.conversationId,
441
+ conversationId: ctx.threadId,
513
442
  data: {
514
443
  toolName: ctx.toolName,
515
444
  toolCallId: ctx.toolCallId,
516
445
  error: ctx.error,
517
- result: config.redactBody ? config.redactBody({
518
- body: ctx.result,
519
- phase: "tool_result"
520
- }) : ctx.result
446
+ result: config.redactBody ? redactBody(config, "tool_result", ctx.result) : ctx.result
521
447
  }
522
448
  });
523
449
  };
524
450
  }
525
- if (include.saves) plugin.onBeforeSave = async (ctx) => {
526
- emitLog(config, {
527
- level: "debug",
528
- event: "save.before",
529
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
530
- agentName: ctx.agentName,
531
- runId: ctx.runId,
532
- conversationId: ctx.conversationId,
533
- data: {
534
- ...createSaveData(ctx),
535
- items: config.redactBody ? config.redactBody({
536
- body: ctx.items,
537
- phase: "save"
538
- }) : ctx.items
539
- }
540
- });
541
- };
542
451
  return plugin;
543
452
  };
544
453
 
@@ -562,17 +471,29 @@ function createBucket(params) {
562
471
  /** Creates an in-memory CAS store for rate limiting. */
563
472
  function createMemoryStore() {
564
473
  const rows = /* @__PURE__ */ new Map();
474
+ const pruneExpiredRows = (bucket) => {
475
+ const nowMs = bucket.now.getTime();
476
+ for (const [id, row] of rows) if (row.windowEndMs <= nowMs) rows.delete(id);
477
+ };
478
+ const toStoredRow = (bucket, state) => ({
479
+ ...state,
480
+ windowEndMs: bucket.windowEnd.getTime()
481
+ });
565
482
  return {
566
- read: async ({ bucket }) => rows.get(bucket.id) ?? null,
483
+ read: async ({ bucket }) => {
484
+ pruneExpiredRows(bucket);
485
+ return rows.get(bucket.id) ?? null;
486
+ },
567
487
  write: async ({ bucket, prevVersion, next }) => {
488
+ pruneExpiredRows(bucket);
568
489
  const current = rows.get(bucket.id) ?? null;
569
490
  if (prevVersion === null) {
570
491
  if (current) return false;
571
- rows.set(bucket.id, next);
492
+ rows.set(bucket.id, toStoredRow(bucket, next));
572
493
  return true;
573
494
  }
574
495
  if (!current || current.version !== prevVersion) return false;
575
- rows.set(bucket.id, next);
496
+ rows.set(bucket.id, toStoredRow(bucket, next));
576
497
  return true;
577
498
  }
578
499
  };
@@ -609,16 +530,16 @@ function createRateLimitStorageUnavailableResponse() {
609
530
  }
610
531
  /** Creates the CAS retries exceeded error. */
611
532
  function createCasRetriesExceededError() {
612
- return BetterAgentError.fromCode("INTERNAL", "Rate limit write failed after CAS retries.", { trace: [{ at: "plugins.rateLimitPlugin" }] });
533
+ return BetterAgentError.fromCode("INTERNAL", "Rate limit write failed after CAS retries.", { trace: [{ at: "plugins.rateLimit" }] });
613
534
  }
614
535
 
615
536
  //#endregion
616
537
  //#region src/rate-limit/validate.ts
617
- /** Validates `rateLimitPlugin` configuration. */
618
- function validateRateLimitPluginConfig(config) {
619
- requirePositiveNumber(config.windowMs, "windowMs", "plugins.rateLimitPlugin");
620
- requirePositiveNumber(config.max, "max", "plugins.rateLimitPlugin");
621
- requirePositiveNumber(config.casRetries ?? 8, "casRetries", "plugins.rateLimitPlugin");
538
+ /** Validates `rateLimit` configuration. */
539
+ function validateRateLimitConfig(config) {
540
+ requirePositiveNumber(config.windowMs, "windowMs", "plugins.rateLimit");
541
+ requirePositiveNumber(config.max, "max", "plugins.rateLimit");
542
+ requirePositiveNumber(config.casRetries ?? 8, "casRetries", "plugins.rateLimit");
622
543
  }
623
544
 
624
545
  //#endregion
@@ -630,23 +551,23 @@ function validateRateLimitPluginConfig(config) {
630
551
  *
631
552
  * @example
632
553
  * ```ts
633
- * const plugin = rateLimitPlugin({
554
+ * const plugin = rateLimit({
634
555
  * windowMs: 60_000,
635
556
  * max: 30,
636
557
  * });
637
558
  * ```
638
559
  */
639
- const rateLimitPlugin = (config) => {
640
- validateRateLimitPluginConfig(config);
560
+ const rateLimit = (config) => {
561
+ validateRateLimitConfig(config);
641
562
  const casRetries = config.casRetries ?? 8;
642
563
  const storage = config.storage ?? createMemoryStore();
643
564
  return {
644
565
  id: config.id ?? "rate-limit",
645
566
  guards: [async (ctx) => {
646
567
  const request = {
647
- mode: ctx.mode,
648
568
  agentName: ctx.agentName,
649
- request: ctx.request
569
+ request: ctx.request,
570
+ auth: ctx.auth
650
571
  };
651
572
  const now = /* @__PURE__ */ new Date();
652
573
  const nowMs = now.getTime();
@@ -973,21 +894,21 @@ function validateSandboxCreateParams(clientProvider, params) {
973
894
  const provider = clientProvider?.trim().toLowerCase();
974
895
  if (!provider) return;
975
896
  if (provider === "daytona") {
976
- if (params.lifecycle?.ttlMs !== void 0) throw createValidationError$1("`lifecycle.ttlMs` is not supported by the Daytona sandbox client. Use `startupTimeoutMs` for creation readiness and Daytona lifecycle fields like `idleStopMs`, `archiveAfterMs`, or `deleteAfterMs` instead.", "plugins.sandboxPlugin.createConfig.lifecycle.ttlMs");
897
+ if (params.lifecycle?.ttlMs !== void 0) throw createValidationError$1("`lifecycle.ttlMs` is not supported by the Daytona sandbox client. Use `startupTimeoutMs` for creation readiness and Daytona lifecycle fields like `idleStopMs`, `archiveAfterMs`, or `deleteAfterMs` instead.", "plugins.sandbox.createConfig.lifecycle.ttlMs");
977
898
  return;
978
899
  }
979
900
  if (provider === "e2b") {
980
- if (params.startupTimeoutMs !== void 0) throw createValidationError$1("`startupTimeoutMs` is not supported by the E2B sandbox client. Use `lifecycle.ttlMs` to control sandbox lifetime.", "plugins.sandboxPlugin.createConfig.startupTimeoutMs");
981
- if (params.lifecycle?.idleStopMs !== void 0 || params.lifecycle?.archiveAfterMs !== void 0 || params.lifecycle?.deleteAfterMs !== void 0) throw createValidationError$1("`lifecycle.idleStopMs`, `lifecycle.archiveAfterMs`, and `lifecycle.deleteAfterMs` are not supported by the E2B sandbox client.", "plugins.sandboxPlugin.createConfig.lifecycle");
901
+ if (params.startupTimeoutMs !== void 0) throw createValidationError$1("`startupTimeoutMs` is not supported by the E2B sandbox client. Use `lifecycle.ttlMs` to control sandbox lifetime.", "plugins.sandbox.createConfig.startupTimeoutMs");
902
+ if (params.lifecycle?.idleStopMs !== void 0 || params.lifecycle?.archiveAfterMs !== void 0 || params.lifecycle?.deleteAfterMs !== void 0) throw createValidationError$1("`lifecycle.idleStopMs`, `lifecycle.archiveAfterMs`, and `lifecycle.deleteAfterMs` are not supported by the E2B sandbox client.", "plugins.sandbox.createConfig.lifecycle");
982
903
  return;
983
904
  }
984
905
  if (hasLifecycleValues(params.lifecycle) || params.startupTimeoutMs !== void 0) return;
985
906
  }
986
- /** Validates `sandboxPlugin` configuration. */
987
- function validateSandboxPluginConfig(config) {
907
+ /** Validates `sandbox` configuration. */
908
+ function validateSandboxConfig(config) {
988
909
  const client = config.client;
989
- if (!client || typeof client !== "object") throw createValidationError$1("`sandboxPlugin` requires a `client`.", "plugins.sandboxPlugin");
990
- if (config.prefix !== void 0 && config.prefix.trim().length === 0) throw createValidationError$1("`sandboxPlugin` requires `prefix` to be a non-empty string when provided.", "plugins.sandboxPlugin");
910
+ if (!client || typeof client !== "object") throw createValidationError$1("`sandbox` requires a `client`.", "plugins.sandbox");
911
+ if (config.prefix !== void 0 && config.prefix.trim().length === 0) throw createValidationError$1("`sandbox` requires `prefix` to be a non-empty string when provided.", "plugins.sandbox");
991
912
  if (config.createConfig) validateSandboxCreateParams(config.client.provider, config.createConfig);
992
913
  if (config.createDefaults) validateSandboxCreateParams(config.client.provider, config.createDefaults);
993
914
  }
@@ -1025,7 +946,7 @@ const resolveCreateParams = (overrides, createConfig, createDefaults) => ({
1025
946
  /**
1026
947
  * Adds sandbox tools.
1027
948
  *
1028
- * By default, the plugin reuses one sandbox per `conversationId`.
949
+ * By default, the plugin reuses one sandbox per `threadId`.
1029
950
  * Use `sessionKey` to customize reuse, or return `null`/`undefined`
1030
951
  * to disable reuse for a specific tool call.
1031
952
  *
@@ -1033,9 +954,9 @@ const resolveCreateParams = (overrides, createConfig, createDefaults) => ({
1033
954
  *
1034
955
  * @example
1035
956
  * ```ts
1036
- * import { sandboxPlugin, createE2BSandboxClient } from "@better-agent/plugins";
957
+ * import { sandbox, createE2BSandboxClient } from "@better-agent/plugins";
1037
958
  *
1038
- * const plugin = sandboxPlugin({
959
+ * const plugin = sandbox({
1039
960
  * client: createE2BSandboxClient({
1040
961
  * apiKey: process.env.E2B_API_KEY,
1041
962
  * }),
@@ -1048,8 +969,8 @@ const resolveCreateParams = (overrides, createConfig, createDefaults) => ({
1048
969
  * });
1049
970
  * ```
1050
971
  */
1051
- const sandboxPlugin = (pluginConfig) => {
1052
- validateSandboxPluginConfig(pluginConfig);
972
+ const sandbox = (pluginConfig) => {
973
+ validateSandboxConfig(pluginConfig);
1053
974
  const sandboxClient = pluginConfig.client;
1054
975
  const store = pluginConfig.store ?? createMemorySandboxSessionStore();
1055
976
  const prefix = trimToUndefined(pluginConfig.prefix) ?? "sandbox";
@@ -1058,8 +979,8 @@ const sandboxPlugin = (pluginConfig) => {
1058
979
  if (pluginConfig.sessionKey) {
1059
980
  const custom = trimToUndefined(pluginConfig.sessionKey({
1060
981
  runId: ctx.runId,
1061
- agentName: ctx.agentName,
1062
- ...ctx.conversationId !== void 0 ? { conversationId: ctx.conversationId } : {},
982
+ agentName: ctx.agentName ?? "",
983
+ ...ctx.threadId !== void 0 ? { threadId: ctx.threadId } : {},
1063
984
  toolName
1064
985
  }) ?? void 0);
1065
986
  return custom !== void 0 ? {
@@ -1067,15 +988,15 @@ const sandboxPlugin = (pluginConfig) => {
1067
988
  sessionKey: custom
1068
989
  } : { kind: "disabled" };
1069
990
  }
1070
- return ctx.conversationId ? {
991
+ return ctx.threadId ? {
1071
992
  kind: "managed",
1072
- sessionKey: `conversation:${ctx.conversationId}`
993
+ sessionKey: `thread:${ctx.threadId}`
1073
994
  } : { kind: "disabled" };
1074
995
  };
1075
996
  const getExplicitSandboxId = (value) => {
1076
997
  if (value === void 0) return;
1077
998
  const trimmed = value.trim();
1078
- if (!trimmed) throw createValidationError("`sandboxId` must be a non-empty string when provided.", "plugins.sandboxPlugin.sandboxId");
999
+ if (!trimmed) throw createValidationError("`sandboxId` must be a non-empty string when provided.", "plugins.sandbox.sandboxId");
1079
1000
  return trimmed;
1080
1001
  };
1081
1002
  const createSandbox = async (params, ctx, toolName) => {
@@ -1105,10 +1026,9 @@ const sandboxPlugin = (pluginConfig) => {
1105
1026
  created: false
1106
1027
  };
1107
1028
  }
1108
- if (!params.createIfMissing) throw createValidationError(`No sandbox is available for this ${params.ctx.conversationId ? "conversation" : "run"}. Create one first with '${nameFor("create")}' or pass a sandboxId explicitly.`, "plugins.sandboxPlugin.resolveSandbox.missing", {
1029
+ if (!params.createIfMissing) throw createValidationError(`No sandbox is available for this ${params.ctx.threadId ? "thread" : "run"}. Create one first with '${nameFor("create")}' or pass a sandboxId explicitly.`, "plugins.sandbox.resolveSandbox.missing", {
1109
1030
  runId: params.ctx.runId,
1110
- agentName: params.ctx.agentName,
1111
- conversationId: params.ctx.conversationId,
1031
+ threadId: params.ctx.threadId,
1112
1032
  toolName: params.toolName
1113
1033
  });
1114
1034
  return await createSandbox(params.createParams, params.ctx, params.toolName);
@@ -1168,12 +1088,16 @@ const sandboxPlugin = (pluginConfig) => {
1168
1088
  type: "string",
1169
1089
  minLength: 1
1170
1090
  };
1171
- const tools = [
1172
- defineTool({
1173
- name: nameFor("create"),
1174
- description: "Create a sandbox, or reuse the current conversation sandbox unless forceNew is true.",
1175
- schema: createSchema
1176
- }).server(async (input, ctx) => {
1091
+ const envsSchema = {
1092
+ type: "object",
1093
+ additionalProperties: { type: "string" }
1094
+ };
1095
+ const createTool = defineTool({
1096
+ name: nameFor("create"),
1097
+ description: "Create a sandbox, or reuse the current thread sandbox unless forceNew is true.",
1098
+ target: "server",
1099
+ inputSchema: createSchema,
1100
+ execute: async (input, ctx) => {
1177
1101
  if (!input.forceNew) {
1178
1102
  const sessionPolicy = getSessionPolicy(ctx, nameFor("create"));
1179
1103
  if (sessionPolicy.kind === "managed") {
@@ -1199,33 +1123,32 @@ const sandboxPlugin = (pluginConfig) => {
1199
1123
  created: true,
1200
1124
  ...resolved.sessionKey !== void 0 ? { sessionKey: resolved.sessionKey } : {}
1201
1125
  };
1202
- }),
1203
- defineTool({
1204
- name: nameFor("exec"),
1205
- description: "Run one shell command inside a sandbox. Creates a sandbox automatically when none exists for the conversation.",
1206
- schema: {
1207
- type: "object",
1208
- properties: {
1209
- sandboxId: sandboxIdSchema,
1210
- cmd: {
1211
- type: "string",
1212
- minLength: 1
1213
- },
1214
- cwd: { type: "string" },
1215
- timeoutMs: {
1216
- type: "number",
1217
- exclusiveMinimum: 0
1218
- },
1219
- envs: {
1220
- type: "object",
1221
- additionalProperties: { type: "string" }
1222
- }
1126
+ }
1127
+ });
1128
+ const execTool = defineTool({
1129
+ name: nameFor("exec"),
1130
+ description: "Run one shell command inside a sandbox. Creates a sandbox automatically when none exists for the thread.",
1131
+ target: "server",
1132
+ inputSchema: {
1133
+ type: "object",
1134
+ properties: {
1135
+ sandboxId: sandboxIdSchema,
1136
+ cmd: {
1137
+ type: "string",
1138
+ minLength: 1
1223
1139
  },
1224
- required: ["cmd"],
1225
- additionalProperties: false
1140
+ cwd: { type: "string" },
1141
+ timeoutMs: {
1142
+ type: "number",
1143
+ exclusiveMinimum: 0
1144
+ },
1145
+ envs: envsSchema
1226
1146
  },
1227
- approval: pluginConfig.approvals?.exec
1228
- }).server(async (input, ctx) => {
1147
+ required: ["cmd"],
1148
+ additionalProperties: false
1149
+ },
1150
+ approval: pluginConfig.approvals?.exec,
1151
+ execute: async (input, ctx) => {
1229
1152
  const resolved = await resolveSandbox({
1230
1153
  sandboxId: input.sandboxId,
1231
1154
  createIfMissing: true,
@@ -1244,20 +1167,22 @@ const sandboxPlugin = (pluginConfig) => {
1244
1167
  createdSandbox: resolved.created,
1245
1168
  ...result
1246
1169
  };
1247
- }),
1248
- defineTool({
1249
- name: nameFor("read_file"),
1250
- description: "Read one text file from a sandbox. Creates a sandbox automatically when none exists for the conversation.",
1251
- schema: {
1252
- type: "object",
1253
- properties: {
1254
- sandboxId: sandboxIdSchema,
1255
- path: pathSchema
1256
- },
1257
- required: ["path"],
1258
- additionalProperties: false
1259
- }
1260
- }).server(async (input, ctx) => {
1170
+ }
1171
+ });
1172
+ const readFileTool = defineTool({
1173
+ name: nameFor("read_file"),
1174
+ description: "Read one text file from a sandbox. Creates a sandbox automatically when none exists for the thread.",
1175
+ target: "server",
1176
+ inputSchema: {
1177
+ type: "object",
1178
+ properties: {
1179
+ sandboxId: sandboxIdSchema,
1180
+ path: pathSchema
1181
+ },
1182
+ required: ["path"],
1183
+ additionalProperties: false
1184
+ },
1185
+ execute: async (input, ctx) => {
1261
1186
  const resolved = await resolveSandbox({
1262
1187
  sandboxId: input.sandboxId,
1263
1188
  createIfMissing: true,
@@ -1272,22 +1197,24 @@ const sandboxPlugin = (pluginConfig) => {
1272
1197
  path: input.path
1273
1198
  })
1274
1199
  };
1275
- }),
1276
- defineTool({
1277
- name: nameFor("write_file"),
1278
- description: "Write one text file into a sandbox. Creates a sandbox automatically when none exists for the conversation.",
1279
- schema: {
1280
- type: "object",
1281
- properties: {
1282
- sandboxId: sandboxIdSchema,
1283
- path: pathSchema,
1284
- content: { type: "string" }
1285
- },
1286
- required: ["path", "content"],
1287
- additionalProperties: false
1200
+ }
1201
+ });
1202
+ const writeFileTool = defineTool({
1203
+ name: nameFor("write_file"),
1204
+ description: "Write one text file into a sandbox. Creates a sandbox automatically when none exists for the thread.",
1205
+ target: "server",
1206
+ inputSchema: {
1207
+ type: "object",
1208
+ properties: {
1209
+ sandboxId: sandboxIdSchema,
1210
+ path: pathSchema,
1211
+ content: { type: "string" }
1288
1212
  },
1289
- approval: pluginConfig.approvals?.writeFile
1290
- }).server(async (input, ctx) => {
1213
+ required: ["path", "content"],
1214
+ additionalProperties: false
1215
+ },
1216
+ approval: pluginConfig.approvals?.writeFile,
1217
+ execute: async (input, ctx) => {
1291
1218
  const resolved = await resolveSandbox({
1292
1219
  sandboxId: input.sandboxId,
1293
1220
  createIfMissing: true,
@@ -1304,20 +1231,22 @@ const sandboxPlugin = (pluginConfig) => {
1304
1231
  createdSandbox: resolved.created,
1305
1232
  path: result.path
1306
1233
  };
1307
- }),
1308
- defineTool({
1309
- name: nameFor("list_files"),
1310
- description: "List directory entries inside a sandbox. Creates a sandbox automatically when none exists for the conversation.",
1311
- schema: {
1312
- type: "object",
1313
- properties: {
1314
- sandboxId: sandboxIdSchema,
1315
- path: pathSchema
1316
- },
1317
- required: ["path"],
1318
- additionalProperties: false
1319
- }
1320
- }).server(async (input, ctx) => {
1234
+ }
1235
+ });
1236
+ const listFilesTool = defineTool({
1237
+ name: nameFor("list_files"),
1238
+ description: "List directory entries inside a sandbox. Creates a sandbox automatically when none exists for the thread.",
1239
+ target: "server",
1240
+ inputSchema: {
1241
+ type: "object",
1242
+ properties: {
1243
+ sandboxId: sandboxIdSchema,
1244
+ path: pathSchema
1245
+ },
1246
+ required: ["path"],
1247
+ additionalProperties: false
1248
+ },
1249
+ execute: async (input, ctx) => {
1321
1250
  const resolved = await resolveSandbox({
1322
1251
  sandboxId: input.sandboxId,
1323
1252
  createIfMissing: true,
@@ -1332,20 +1261,22 @@ const sandboxPlugin = (pluginConfig) => {
1332
1261
  path: input.path
1333
1262
  })
1334
1263
  };
1335
- }),
1336
- defineTool({
1337
- name: nameFor("make_dir"),
1338
- description: "Create a directory inside a sandbox. Creates a sandbox automatically when none exists for the conversation.",
1339
- schema: {
1340
- type: "object",
1341
- properties: {
1342
- sandboxId: sandboxIdSchema,
1343
- path: pathSchema
1344
- },
1345
- required: ["path"],
1346
- additionalProperties: false
1347
- }
1348
- }).server(async (input, ctx) => {
1264
+ }
1265
+ });
1266
+ const makeDirTool = defineTool({
1267
+ name: nameFor("make_dir"),
1268
+ description: "Create a directory inside a sandbox. Creates a sandbox automatically when none exists for the thread.",
1269
+ target: "server",
1270
+ inputSchema: {
1271
+ type: "object",
1272
+ properties: {
1273
+ sandboxId: sandboxIdSchema,
1274
+ path: pathSchema
1275
+ },
1276
+ required: ["path"],
1277
+ additionalProperties: false
1278
+ },
1279
+ execute: async (input, ctx) => {
1349
1280
  const resolved = await resolveSandbox({
1350
1281
  sandboxId: input.sandboxId,
1351
1282
  createIfMissing: true,
@@ -1361,21 +1292,23 @@ const sandboxPlugin = (pluginConfig) => {
1361
1292
  path: input.path,
1362
1293
  created: result.created
1363
1294
  };
1364
- }),
1365
- defineTool({
1366
- name: nameFor("remove_path"),
1367
- description: "Remove a file or directory from a sandbox.",
1368
- schema: {
1369
- type: "object",
1370
- properties: {
1371
- sandboxId: sandboxIdSchema,
1372
- path: pathSchema
1373
- },
1374
- required: ["path"],
1375
- additionalProperties: false
1295
+ }
1296
+ });
1297
+ const removePathTool = defineTool({
1298
+ name: nameFor("remove_path"),
1299
+ description: "Remove a file or directory from a sandbox.",
1300
+ target: "server",
1301
+ inputSchema: {
1302
+ type: "object",
1303
+ properties: {
1304
+ sandboxId: sandboxIdSchema,
1305
+ path: pathSchema
1376
1306
  },
1377
- approval: pluginConfig.approvals?.removePath
1378
- }).server(async (input, ctx) => {
1307
+ required: ["path"],
1308
+ additionalProperties: false
1309
+ },
1310
+ approval: pluginConfig.approvals?.removePath,
1311
+ execute: async (input, ctx) => {
1379
1312
  const resolved = await resolveSandbox({
1380
1313
  sandboxId: input.sandboxId,
1381
1314
  createIfMissing: false,
@@ -1391,24 +1324,26 @@ const sandboxPlugin = (pluginConfig) => {
1391
1324
  removed: true,
1392
1325
  path: input.path
1393
1326
  };
1394
- }),
1395
- defineTool({
1396
- name: nameFor("get_host"),
1397
- description: "Expose one sandbox port as a host URL so callers can reach an app running inside the sandbox.",
1398
- schema: {
1399
- type: "object",
1400
- properties: {
1401
- sandboxId: sandboxIdSchema,
1402
- port: {
1403
- type: "number",
1404
- minimum: 1,
1405
- maximum: 65535
1406
- }
1407
- },
1408
- required: ["port"],
1409
- additionalProperties: false
1410
- }
1411
- }).server(async (input, ctx) => {
1327
+ }
1328
+ });
1329
+ const getHostTool = defineTool({
1330
+ name: nameFor("get_host"),
1331
+ description: "Expose one sandbox port as a host URL so callers can reach an app running inside the sandbox.",
1332
+ target: "server",
1333
+ inputSchema: {
1334
+ type: "object",
1335
+ properties: {
1336
+ sandboxId: sandboxIdSchema,
1337
+ port: {
1338
+ type: "number",
1339
+ minimum: 1,
1340
+ maximum: 65535
1341
+ }
1342
+ },
1343
+ required: ["port"],
1344
+ additionalProperties: false
1345
+ },
1346
+ execute: async (input, ctx) => {
1412
1347
  const resolved = await resolveSandbox({
1413
1348
  sandboxId: input.sandboxId,
1414
1349
  createIfMissing: false,
@@ -1427,17 +1362,19 @@ const sandboxPlugin = (pluginConfig) => {
1427
1362
  host,
1428
1363
  ...preview?.token !== void 0 ? { token: preview.token } : {}
1429
1364
  };
1430
- }),
1431
- defineTool({
1432
- name: nameFor("kill"),
1433
- description: "Terminate a sandbox and clear the current conversation sandbox when applicable.",
1434
- schema: {
1435
- type: "object",
1436
- properties: { sandboxId: sandboxIdSchema },
1437
- additionalProperties: false
1438
- },
1439
- approval: pluginConfig.approvals?.killSandbox
1440
- }).server(async (input, ctx) => {
1365
+ }
1366
+ });
1367
+ const killTool = defineTool({
1368
+ name: nameFor("kill"),
1369
+ description: "Terminate a sandbox and clear the current thread sandbox when applicable.",
1370
+ target: "server",
1371
+ inputSchema: {
1372
+ type: "object",
1373
+ properties: { sandboxId: sandboxIdSchema },
1374
+ additionalProperties: false
1375
+ },
1376
+ approval: pluginConfig.approvals?.killSandbox,
1377
+ execute: async (input, ctx) => {
1441
1378
  const resolved = await resolveSandbox({
1442
1379
  sandboxId: input.sandboxId,
1443
1380
  createIfMissing: false,
@@ -1451,7 +1388,19 @@ const sandboxPlugin = (pluginConfig) => {
1451
1388
  killed: true,
1452
1389
  ...clearedSessionKey !== void 0 ? { clearedSessionKey } : {}
1453
1390
  };
1454
- })
1391
+ }
1392
+ });
1393
+ const toAnyTool = (tool) => tool;
1394
+ const tools = [
1395
+ toAnyTool(createTool),
1396
+ toAnyTool(execTool),
1397
+ toAnyTool(readFileTool),
1398
+ toAnyTool(writeFileTool),
1399
+ toAnyTool(listFilesTool),
1400
+ toAnyTool(makeDirTool),
1401
+ toAnyTool(removePathTool),
1402
+ toAnyTool(getHostTool),
1403
+ toAnyTool(killTool)
1455
1404
  ];
1456
1405
  return {
1457
1406
  id: pluginConfig.id ?? "sandbox",
@@ -1460,5 +1409,5 @@ const sandboxPlugin = (pluginConfig) => {
1460
1409
  };
1461
1410
 
1462
1411
  //#endregion
1463
- export { authPlugin, createDaytonaSandboxClient, createE2BSandboxClient, createMemorySandboxSessionStore, ipAllowlistPlugin, loggingPlugin, rateLimitPlugin, sandboxPlugin };
1412
+ export { createDaytonaSandboxClient, createE2BSandboxClient, createMemorySandboxSessionStore, ipAllowlist, logging, rateLimit, sandbox };
1464
1413
  //# sourceMappingURL=index.mjs.map