@better-agent/plugins 0.1.0-canary.5 → 0.2.0-beta.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/dist/index.d.mts +59 -97
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +373 -363
- package/dist/index.mjs.map +1 -1
- package/package.json +21 -5
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:
|
|
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 `
|
|
226
|
-
function
|
|
227
|
-
requireNonEmptyArray(config.allow, "allow", "plugins.
|
|
228
|
-
for (const entry of config.allow) if (typeof entry !== "string" || !parseAllowEntry(entry)) throw createValidationError$1(`\`
|
|
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 =
|
|
202
|
+
* const plugin = ipAllowlist({
|
|
265
203
|
* allow: ["127.0.0.1", "10.0.0.0/8"],
|
|
266
204
|
* });
|
|
267
205
|
* ```
|
|
268
206
|
*/
|
|
269
|
-
const
|
|
270
|
-
|
|
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
|
|
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
|
-
|
|
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 =
|
|
332
|
+
* const plugin = logging({
|
|
393
333
|
* level: "info",
|
|
394
334
|
* include: { requests: true, toolCalls: true },
|
|
395
335
|
* });
|
|
396
336
|
* ```
|
|
397
337
|
*/
|
|
398
|
-
const
|
|
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.
|
|
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
|
-
...
|
|
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.
|
|
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.
|
|
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.
|
|
394
|
+
conversationId: ctx.threadId,
|
|
460
395
|
data: {
|
|
461
396
|
stepIndex: ctx.stepIndex,
|
|
462
|
-
inputCount: ctx.
|
|
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.
|
|
410
|
+
conversationId: ctx.threadId,
|
|
476
411
|
data: {
|
|
477
412
|
stepIndex: ctx.stepIndex,
|
|
478
|
-
response: config.redactBody ? config.
|
|
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.
|
|
426
|
+
conversationId: ctx.threadId,
|
|
495
427
|
data: {
|
|
496
428
|
toolName: ctx.toolName,
|
|
497
429
|
toolCallId: ctx.toolCallId,
|
|
498
|
-
args: config.redactBody ? config.
|
|
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.
|
|
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.
|
|
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 }) =>
|
|
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.
|
|
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 `
|
|
618
|
-
function
|
|
619
|
-
requirePositiveNumber(config.windowMs, "windowMs", "plugins.
|
|
620
|
-
requirePositiveNumber(config.max, "max", "plugins.
|
|
621
|
-
requirePositiveNumber(config.casRetries ?? 8, "casRetries", "plugins.
|
|
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 =
|
|
554
|
+
* const plugin = rateLimit({
|
|
634
555
|
* windowMs: 60_000,
|
|
635
556
|
* max: 30,
|
|
636
557
|
* });
|
|
637
558
|
* ```
|
|
638
559
|
*/
|
|
639
|
-
const
|
|
640
|
-
|
|
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();
|
|
@@ -722,27 +643,28 @@ const normalizeFileEntries = (files) => files.map((entry) => ({
|
|
|
722
643
|
path: entry.path ?? entry.name ?? "",
|
|
723
644
|
type: entry.type ?? (entry.isDir ? "directory" : "file")
|
|
724
645
|
}));
|
|
725
|
-
/**
|
|
646
|
+
/**
|
|
647
|
+
* Creates a sandbox client backed by the Daytona SDK.
|
|
648
|
+
*/
|
|
726
649
|
function createDaytonaSandboxClient(config = {}) {
|
|
727
650
|
const clientConfig = removeUndefined$1({
|
|
728
651
|
apiKey: config.apiKey,
|
|
729
652
|
apiUrl: config.apiUrl,
|
|
730
653
|
target: config.target
|
|
731
654
|
});
|
|
655
|
+
const toProviderMinutes = (value) => value !== void 0 ? Math.max(1, Math.ceil(value / 6e4)) : void 0;
|
|
732
656
|
const buildCreateParams = (overrides) => {
|
|
733
|
-
const template = overrides?.template
|
|
657
|
+
const template = overrides?.template;
|
|
734
658
|
const templateKind = config.templateKind ?? "snapshot";
|
|
735
659
|
return removeUndefined$1({
|
|
736
660
|
language: config.language,
|
|
737
|
-
envVars: overrides?.envs
|
|
738
|
-
labels: overrides?.metadata
|
|
661
|
+
envVars: overrides?.envs,
|
|
662
|
+
labels: overrides?.metadata,
|
|
739
663
|
public: config.public,
|
|
740
|
-
autoStopInterval:
|
|
741
|
-
autoArchiveInterval:
|
|
742
|
-
autoDeleteInterval:
|
|
743
|
-
...template !== void 0 ? templateKind === "image" ? { image: template } : { snapshot: template } : {}
|
|
744
|
-
...config.snapshot !== void 0 ? { snapshot: config.snapshot } : {},
|
|
745
|
-
...config.image !== void 0 ? { image: config.image } : {}
|
|
664
|
+
autoStopInterval: toProviderMinutes(overrides?.lifecycle?.idleStopMs),
|
|
665
|
+
autoArchiveInterval: toProviderMinutes(overrides?.lifecycle?.archiveAfterMs),
|
|
666
|
+
autoDeleteInterval: toProviderMinutes(overrides?.lifecycle?.deleteAfterMs),
|
|
667
|
+
...template !== void 0 ? templateKind === "image" ? { image: template } : { snapshot: template } : {}
|
|
746
668
|
});
|
|
747
669
|
};
|
|
748
670
|
const getClient = async () => {
|
|
@@ -780,7 +702,7 @@ function createDaytonaSandboxClient(config = {}) {
|
|
|
780
702
|
},
|
|
781
703
|
async createSandbox(params) {
|
|
782
704
|
const client = await getClient();
|
|
783
|
-
const timeout = toSeconds(params?.
|
|
705
|
+
const timeout = toSeconds(params?.startupTimeoutMs);
|
|
784
706
|
return { sandboxId: (await client.create(buildCreateParams(params), { ...timeout !== void 0 ? { timeout } : {} })).id };
|
|
785
707
|
},
|
|
786
708
|
async runCommand(params) {
|
|
@@ -874,7 +796,9 @@ const loadE2B = async () => {
|
|
|
874
796
|
});
|
|
875
797
|
}
|
|
876
798
|
};
|
|
877
|
-
/**
|
|
799
|
+
/**
|
|
800
|
+
* Creates a sandbox client backed by the E2B SDK.
|
|
801
|
+
*/
|
|
878
802
|
function createE2BSandboxClient(config = {}) {
|
|
879
803
|
const connectionOptions = removeUndefined({
|
|
880
804
|
apiKey: config.apiKey,
|
|
@@ -882,11 +806,11 @@ function createE2BSandboxClient(config = {}) {
|
|
|
882
806
|
domain: config.domain,
|
|
883
807
|
requestTimeoutMs: config.requestTimeoutMs
|
|
884
808
|
});
|
|
885
|
-
const toCreateOptions = (
|
|
809
|
+
const toCreateOptions = (params) => removeUndefined({
|
|
886
810
|
...connectionOptions,
|
|
887
|
-
timeoutMs:
|
|
888
|
-
envs:
|
|
889
|
-
metadata:
|
|
811
|
+
timeoutMs: params?.lifecycle?.ttlMs,
|
|
812
|
+
envs: params?.envs,
|
|
813
|
+
metadata: params?.metadata
|
|
890
814
|
});
|
|
891
815
|
const connectSandbox = async (sandboxId) => {
|
|
892
816
|
const { Sandbox } = await loadE2B();
|
|
@@ -910,7 +834,7 @@ function createE2BSandboxClient(config = {}) {
|
|
|
910
834
|
},
|
|
911
835
|
async createSandbox(params) {
|
|
912
836
|
const { Sandbox } = await loadE2B();
|
|
913
|
-
const template = params?.template
|
|
837
|
+
const template = params?.template;
|
|
914
838
|
const options = toCreateOptions(params);
|
|
915
839
|
return { sandboxId: (template !== void 0 ? await Sandbox.create(template, options) : await Sandbox.create(options)).sandboxId };
|
|
916
840
|
},
|
|
@@ -965,11 +889,28 @@ function createMemorySandboxSessionStore() {
|
|
|
965
889
|
|
|
966
890
|
//#endregion
|
|
967
891
|
//#region src/sandbox/validate.ts
|
|
968
|
-
|
|
969
|
-
function
|
|
892
|
+
const hasLifecycleValues = (lifecycle) => Boolean(lifecycle?.ttlMs !== void 0 || lifecycle?.idleStopMs !== void 0 || lifecycle?.archiveAfterMs !== void 0 || lifecycle?.deleteAfterMs !== void 0);
|
|
893
|
+
function validateSandboxCreateParams(clientProvider, params) {
|
|
894
|
+
const provider = clientProvider?.trim().toLowerCase();
|
|
895
|
+
if (!provider) return;
|
|
896
|
+
if (provider === "daytona") {
|
|
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");
|
|
898
|
+
return;
|
|
899
|
+
}
|
|
900
|
+
if (provider === "e2b") {
|
|
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");
|
|
903
|
+
return;
|
|
904
|
+
}
|
|
905
|
+
if (hasLifecycleValues(params.lifecycle) || params.startupTimeoutMs !== void 0) return;
|
|
906
|
+
}
|
|
907
|
+
/** Validates `sandbox` configuration. */
|
|
908
|
+
function validateSandboxConfig(config) {
|
|
970
909
|
const client = config.client;
|
|
971
|
-
if (!client || typeof client !== "object") throw createValidationError$1("`
|
|
972
|
-
if (config.prefix !== void 0 && config.prefix.trim().length === 0) throw createValidationError$1("`
|
|
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");
|
|
912
|
+
if (config.createConfig) validateSandboxCreateParams(config.client.provider, config.createConfig);
|
|
913
|
+
if (config.createDefaults) validateSandboxCreateParams(config.client.provider, config.createDefaults);
|
|
973
914
|
}
|
|
974
915
|
|
|
975
916
|
//#endregion
|
|
@@ -983,10 +924,29 @@ const createValidationError = (message, at, context) => BetterAgentError.fromCod
|
|
|
983
924
|
...context !== void 0 ? { context } : {},
|
|
984
925
|
trace: [{ at }]
|
|
985
926
|
});
|
|
927
|
+
const resolveCreateParams = (overrides, createConfig, createDefaults) => ({
|
|
928
|
+
template: createConfig?.template ?? overrides?.template ?? createDefaults?.template,
|
|
929
|
+
startupTimeoutMs: createConfig?.startupTimeoutMs ?? overrides?.startupTimeoutMs ?? createDefaults?.startupTimeoutMs,
|
|
930
|
+
envs: createDefaults?.envs || overrides?.envs || createConfig?.envs ? {
|
|
931
|
+
...createDefaults?.envs ?? {},
|
|
932
|
+
...overrides?.envs ?? {},
|
|
933
|
+
...createConfig?.envs ?? {}
|
|
934
|
+
} : void 0,
|
|
935
|
+
metadata: createDefaults?.metadata || overrides?.metadata || createConfig?.metadata ? {
|
|
936
|
+
...createDefaults?.metadata ?? {},
|
|
937
|
+
...overrides?.metadata ?? {},
|
|
938
|
+
...createConfig?.metadata ?? {}
|
|
939
|
+
} : void 0,
|
|
940
|
+
lifecycle: createDefaults?.lifecycle || overrides?.lifecycle || createConfig?.lifecycle ? {
|
|
941
|
+
...createDefaults?.lifecycle ?? {},
|
|
942
|
+
...overrides?.lifecycle ?? {},
|
|
943
|
+
...createConfig?.lifecycle ?? {}
|
|
944
|
+
} : void 0
|
|
945
|
+
});
|
|
986
946
|
/**
|
|
987
947
|
* Adds sandbox tools.
|
|
988
948
|
*
|
|
989
|
-
* By default, the plugin reuses one sandbox per `
|
|
949
|
+
* By default, the plugin reuses one sandbox per `threadId`.
|
|
990
950
|
* Use `sessionKey` to customize reuse, or return `null`/`undefined`
|
|
991
951
|
* to disable reuse for a specific tool call.
|
|
992
952
|
*
|
|
@@ -994,31 +954,33 @@ const createValidationError = (message, at, context) => BetterAgentError.fromCod
|
|
|
994
954
|
*
|
|
995
955
|
* @example
|
|
996
956
|
* ```ts
|
|
997
|
-
* import {
|
|
957
|
+
* import { sandbox, createE2BSandboxClient } from "@better-agent/plugins";
|
|
998
958
|
*
|
|
999
|
-
* const plugin =
|
|
959
|
+
* const plugin = sandbox({
|
|
1000
960
|
* client: createE2BSandboxClient({
|
|
1001
961
|
* apiKey: process.env.E2B_API_KEY,
|
|
1002
962
|
* }),
|
|
1003
|
-
*
|
|
963
|
+
* createConfig: {
|
|
1004
964
|
* template: "base",
|
|
1005
|
-
*
|
|
965
|
+
* },
|
|
966
|
+
* createDefaults: {
|
|
967
|
+
* startupTimeoutMs: 90_000,
|
|
1006
968
|
* },
|
|
1007
969
|
* });
|
|
1008
970
|
* ```
|
|
1009
971
|
*/
|
|
1010
|
-
const
|
|
1011
|
-
|
|
1012
|
-
const sandboxClient =
|
|
1013
|
-
const store =
|
|
1014
|
-
const prefix = trimToUndefined(
|
|
972
|
+
const sandbox = (pluginConfig) => {
|
|
973
|
+
validateSandboxConfig(pluginConfig);
|
|
974
|
+
const sandboxClient = pluginConfig.client;
|
|
975
|
+
const store = pluginConfig.store ?? createMemorySandboxSessionStore();
|
|
976
|
+
const prefix = trimToUndefined(pluginConfig.prefix) ?? "sandbox";
|
|
1015
977
|
const nameFor = (suffix) => `${prefix}_${suffix}`;
|
|
1016
978
|
const getSessionPolicy = (ctx, toolName) => {
|
|
1017
|
-
if (
|
|
1018
|
-
const custom = trimToUndefined(
|
|
979
|
+
if (pluginConfig.sessionKey) {
|
|
980
|
+
const custom = trimToUndefined(pluginConfig.sessionKey({
|
|
1019
981
|
runId: ctx.runId,
|
|
1020
|
-
agentName: ctx.agentName,
|
|
1021
|
-
...ctx.
|
|
982
|
+
agentName: ctx.agentName ?? "",
|
|
983
|
+
...ctx.threadId !== void 0 ? { threadId: ctx.threadId } : {},
|
|
1022
984
|
toolName
|
|
1023
985
|
}) ?? void 0);
|
|
1024
986
|
return custom !== void 0 ? {
|
|
@@ -1026,25 +988,22 @@ const sandboxPlugin = (config) => {
|
|
|
1026
988
|
sessionKey: custom
|
|
1027
989
|
} : { kind: "disabled" };
|
|
1028
990
|
}
|
|
1029
|
-
return ctx.
|
|
991
|
+
return ctx.threadId ? {
|
|
1030
992
|
kind: "managed",
|
|
1031
|
-
sessionKey: `
|
|
993
|
+
sessionKey: `thread:${ctx.threadId}`
|
|
1032
994
|
} : { kind: "disabled" };
|
|
1033
995
|
};
|
|
1034
996
|
const getExplicitSandboxId = (value) => {
|
|
1035
997
|
if (value === void 0) return;
|
|
1036
998
|
const trimmed = value.trim();
|
|
1037
|
-
if (!trimmed) throw createValidationError("`sandboxId` must be a non-empty string when provided.", "plugins.
|
|
999
|
+
if (!trimmed) throw createValidationError("`sandboxId` must be a non-empty string when provided.", "plugins.sandbox.sandboxId");
|
|
1038
1000
|
return trimmed;
|
|
1039
1001
|
};
|
|
1040
1002
|
const createSandbox = async (params, ctx, toolName) => {
|
|
1041
1003
|
const sessionPolicy = getSessionPolicy(ctx, toolName);
|
|
1042
|
-
const
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
envs: params?.envs ?? config.defaults?.envs,
|
|
1046
|
-
metadata: params?.metadata ?? config.defaults?.metadata
|
|
1047
|
-
});
|
|
1004
|
+
const resolvedParams = resolveCreateParams(params, pluginConfig.createConfig, pluginConfig.createDefaults);
|
|
1005
|
+
validateSandboxCreateParams(sandboxClient.provider, resolvedParams);
|
|
1006
|
+
const created = await sandboxClient.createSandbox(resolvedParams);
|
|
1048
1007
|
if (sessionPolicy.kind === "managed") await store.set(sessionPolicy.sessionKey, created.sandboxId);
|
|
1049
1008
|
return {
|
|
1050
1009
|
sandboxId: created.sandboxId,
|
|
@@ -1067,10 +1026,9 @@ const sandboxPlugin = (config) => {
|
|
|
1067
1026
|
created: false
|
|
1068
1027
|
};
|
|
1069
1028
|
}
|
|
1070
|
-
if (!params.createIfMissing) throw createValidationError(`No sandbox is available for this ${params.ctx.
|
|
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", {
|
|
1071
1030
|
runId: params.ctx.runId,
|
|
1072
|
-
|
|
1073
|
-
conversationId: params.ctx.conversationId,
|
|
1031
|
+
threadId: params.ctx.threadId,
|
|
1074
1032
|
toolName: params.toolName
|
|
1075
1033
|
});
|
|
1076
1034
|
return await createSandbox(params.createParams, params.ctx, params.toolName);
|
|
@@ -1088,7 +1046,7 @@ const sandboxPlugin = (config) => {
|
|
|
1088
1046
|
properties: {
|
|
1089
1047
|
forceNew: { type: "boolean" },
|
|
1090
1048
|
template: { type: "string" },
|
|
1091
|
-
|
|
1049
|
+
startupTimeoutMs: {
|
|
1092
1050
|
type: "number",
|
|
1093
1051
|
exclusiveMinimum: 0
|
|
1094
1052
|
},
|
|
@@ -1099,6 +1057,28 @@ const sandboxPlugin = (config) => {
|
|
|
1099
1057
|
metadata: {
|
|
1100
1058
|
type: "object",
|
|
1101
1059
|
additionalProperties: { type: "string" }
|
|
1060
|
+
},
|
|
1061
|
+
lifecycle: {
|
|
1062
|
+
type: "object",
|
|
1063
|
+
properties: {
|
|
1064
|
+
ttlMs: {
|
|
1065
|
+
type: "number",
|
|
1066
|
+
exclusiveMinimum: 0
|
|
1067
|
+
},
|
|
1068
|
+
idleStopMs: {
|
|
1069
|
+
type: "number",
|
|
1070
|
+
exclusiveMinimum: 0
|
|
1071
|
+
},
|
|
1072
|
+
archiveAfterMs: {
|
|
1073
|
+
type: "number",
|
|
1074
|
+
exclusiveMinimum: 0
|
|
1075
|
+
},
|
|
1076
|
+
deleteAfterMs: {
|
|
1077
|
+
type: "number",
|
|
1078
|
+
exclusiveMinimum: 0
|
|
1079
|
+
}
|
|
1080
|
+
},
|
|
1081
|
+
additionalProperties: false
|
|
1102
1082
|
}
|
|
1103
1083
|
},
|
|
1104
1084
|
additionalProperties: false
|
|
@@ -1108,12 +1088,16 @@ const sandboxPlugin = (config) => {
|
|
|
1108
1088
|
type: "string",
|
|
1109
1089
|
minLength: 1
|
|
1110
1090
|
};
|
|
1111
|
-
const
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
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) => {
|
|
1117
1101
|
if (!input.forceNew) {
|
|
1118
1102
|
const sessionPolicy = getSessionPolicy(ctx, nameFor("create"));
|
|
1119
1103
|
if (sessionPolicy.kind === "managed") {
|
|
@@ -1128,9 +1112,10 @@ const sandboxPlugin = (config) => {
|
|
|
1128
1112
|
}
|
|
1129
1113
|
const resolved = await createSandbox({
|
|
1130
1114
|
template: input.template,
|
|
1131
|
-
|
|
1115
|
+
startupTimeoutMs: input.startupTimeoutMs,
|
|
1132
1116
|
envs: input.envs,
|
|
1133
|
-
metadata: input.metadata
|
|
1117
|
+
metadata: input.metadata,
|
|
1118
|
+
lifecycle: input.lifecycle
|
|
1134
1119
|
}, ctx, nameFor("create"));
|
|
1135
1120
|
return {
|
|
1136
1121
|
sandboxId: resolved.sandboxId,
|
|
@@ -1138,33 +1123,32 @@ const sandboxPlugin = (config) => {
|
|
|
1138
1123
|
created: true,
|
|
1139
1124
|
...resolved.sessionKey !== void 0 ? { sessionKey: resolved.sessionKey } : {}
|
|
1140
1125
|
};
|
|
1141
|
-
}
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
timeoutMs: {
|
|
1155
|
-
type: "number",
|
|
1156
|
-
exclusiveMinimum: 0
|
|
1157
|
-
},
|
|
1158
|
-
envs: {
|
|
1159
|
-
type: "object",
|
|
1160
|
-
additionalProperties: { type: "string" }
|
|
1161
|
-
}
|
|
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
|
|
1162
1139
|
},
|
|
1163
|
-
|
|
1164
|
-
|
|
1140
|
+
cwd: { type: "string" },
|
|
1141
|
+
timeoutMs: {
|
|
1142
|
+
type: "number",
|
|
1143
|
+
exclusiveMinimum: 0
|
|
1144
|
+
},
|
|
1145
|
+
envs: envsSchema
|
|
1165
1146
|
},
|
|
1166
|
-
|
|
1167
|
-
|
|
1147
|
+
required: ["cmd"],
|
|
1148
|
+
additionalProperties: false
|
|
1149
|
+
},
|
|
1150
|
+
approval: pluginConfig.approvals?.exec,
|
|
1151
|
+
execute: async (input, ctx) => {
|
|
1168
1152
|
const resolved = await resolveSandbox({
|
|
1169
1153
|
sandboxId: input.sandboxId,
|
|
1170
1154
|
createIfMissing: true,
|
|
@@ -1183,20 +1167,22 @@ const sandboxPlugin = (config) => {
|
|
|
1183
1167
|
createdSandbox: resolved.created,
|
|
1184
1168
|
...result
|
|
1185
1169
|
};
|
|
1186
|
-
}
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
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) => {
|
|
1200
1186
|
const resolved = await resolveSandbox({
|
|
1201
1187
|
sandboxId: input.sandboxId,
|
|
1202
1188
|
createIfMissing: true,
|
|
@@ -1211,22 +1197,24 @@ const sandboxPlugin = (config) => {
|
|
|
1211
1197
|
path: input.path
|
|
1212
1198
|
})
|
|
1213
1199
|
};
|
|
1214
|
-
}
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
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" }
|
|
1227
1212
|
},
|
|
1228
|
-
|
|
1229
|
-
|
|
1213
|
+
required: ["path", "content"],
|
|
1214
|
+
additionalProperties: false
|
|
1215
|
+
},
|
|
1216
|
+
approval: pluginConfig.approvals?.writeFile,
|
|
1217
|
+
execute: async (input, ctx) => {
|
|
1230
1218
|
const resolved = await resolveSandbox({
|
|
1231
1219
|
sandboxId: input.sandboxId,
|
|
1232
1220
|
createIfMissing: true,
|
|
@@ -1243,20 +1231,22 @@ const sandboxPlugin = (config) => {
|
|
|
1243
1231
|
createdSandbox: resolved.created,
|
|
1244
1232
|
path: result.path
|
|
1245
1233
|
};
|
|
1246
|
-
}
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
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) => {
|
|
1260
1250
|
const resolved = await resolveSandbox({
|
|
1261
1251
|
sandboxId: input.sandboxId,
|
|
1262
1252
|
createIfMissing: true,
|
|
@@ -1271,20 +1261,22 @@ const sandboxPlugin = (config) => {
|
|
|
1271
1261
|
path: input.path
|
|
1272
1262
|
})
|
|
1273
1263
|
};
|
|
1274
|
-
}
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
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) => {
|
|
1288
1280
|
const resolved = await resolveSandbox({
|
|
1289
1281
|
sandboxId: input.sandboxId,
|
|
1290
1282
|
createIfMissing: true,
|
|
@@ -1300,21 +1292,23 @@ const sandboxPlugin = (config) => {
|
|
|
1300
1292
|
path: input.path,
|
|
1301
1293
|
created: result.created
|
|
1302
1294
|
};
|
|
1303
|
-
}
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
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
|
|
1315
1306
|
},
|
|
1316
|
-
|
|
1317
|
-
|
|
1307
|
+
required: ["path"],
|
|
1308
|
+
additionalProperties: false
|
|
1309
|
+
},
|
|
1310
|
+
approval: pluginConfig.approvals?.removePath,
|
|
1311
|
+
execute: async (input, ctx) => {
|
|
1318
1312
|
const resolved = await resolveSandbox({
|
|
1319
1313
|
sandboxId: input.sandboxId,
|
|
1320
1314
|
createIfMissing: false,
|
|
@@ -1330,24 +1324,26 @@ const sandboxPlugin = (config) => {
|
|
|
1330
1324
|
removed: true,
|
|
1331
1325
|
path: input.path
|
|
1332
1326
|
};
|
|
1333
|
-
}
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
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) => {
|
|
1351
1347
|
const resolved = await resolveSandbox({
|
|
1352
1348
|
sandboxId: input.sandboxId,
|
|
1353
1349
|
createIfMissing: false,
|
|
@@ -1366,17 +1362,19 @@ const sandboxPlugin = (config) => {
|
|
|
1366
1362
|
host,
|
|
1367
1363
|
...preview?.token !== void 0 ? { token: preview.token } : {}
|
|
1368
1364
|
};
|
|
1369
|
-
}
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
},
|
|
1378
|
-
|
|
1379
|
-
}
|
|
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) => {
|
|
1380
1378
|
const resolved = await resolveSandbox({
|
|
1381
1379
|
sandboxId: input.sandboxId,
|
|
1382
1380
|
createIfMissing: false,
|
|
@@ -1390,14 +1388,26 @@ const sandboxPlugin = (config) => {
|
|
|
1390
1388
|
killed: true,
|
|
1391
1389
|
...clearedSessionKey !== void 0 ? { clearedSessionKey } : {}
|
|
1392
1390
|
};
|
|
1393
|
-
}
|
|
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)
|
|
1394
1404
|
];
|
|
1395
1405
|
return {
|
|
1396
|
-
id:
|
|
1406
|
+
id: pluginConfig.id ?? "sandbox",
|
|
1397
1407
|
tools
|
|
1398
1408
|
};
|
|
1399
1409
|
};
|
|
1400
1410
|
|
|
1401
1411
|
//#endregion
|
|
1402
|
-
export {
|
|
1412
|
+
export { createDaytonaSandboxClient, createE2BSandboxClient, createMemorySandboxSessionStore, ipAllowlist, logging, rateLimit, sandbox };
|
|
1403
1413
|
//# sourceMappingURL=index.mjs.map
|