@nylorun/agents 0.8.0-beta → 0.10.0-beta

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.
Files changed (60) hide show
  1. package/CHANGELOG.md +167 -0
  2. package/README.md +371 -166
  3. package/dist/a2a/handler.d.ts +59 -0
  4. package/dist/a2a/handler.js +178 -0
  5. package/dist/a2a/index.d.ts +8 -0
  6. package/dist/a2a/index.js +7 -0
  7. package/dist/access.d.ts +101 -0
  8. package/dist/access.js +111 -0
  9. package/dist/action-handler.d.ts +64 -0
  10. package/dist/action-handler.js +283 -0
  11. package/dist/ag-ui/handler.d.ts +12 -4
  12. package/dist/ag-ui/handler.js +87 -281
  13. package/dist/ag-ui/index.d.ts +2 -1
  14. package/dist/ag-ui/index.js +2 -1
  15. package/dist/browser.d.ts +97 -0
  16. package/dist/browser.js +277 -0
  17. package/dist/builder.d.ts +20 -0
  18. package/dist/builder.js +26 -0
  19. package/dist/client.d.ts +3 -230
  20. package/dist/client.js +2 -335
  21. package/dist/connection.d.ts +0 -1
  22. package/dist/connection.js +3 -9
  23. package/dist/delivery-token.d.ts +57 -0
  24. package/dist/delivery-token.js +150 -0
  25. package/dist/execute-action.d.ts +6 -0
  26. package/dist/execute-action.js +24 -0
  27. package/dist/http.d.ts +31 -1
  28. package/dist/http.js +74 -14
  29. package/dist/index.d.ts +13 -9
  30. package/dist/index.js +5 -4
  31. package/dist/mcp/index.d.ts +2 -2
  32. package/dist/mcp/index.js +1 -1
  33. package/dist/sandbox/client.d.ts +4 -5
  34. package/dist/sandbox/client.js +4 -8
  35. package/dist/sandbox/index.d.ts +0 -2
  36. package/dist/sandbox/index.js +0 -1
  37. package/dist/served-definitions.d.ts +20 -0
  38. package/dist/served-definitions.js +100 -0
  39. package/dist/session-client.d.ts +282 -0
  40. package/dist/session-client.js +375 -0
  41. package/dist/sse.js +12 -0
  42. package/dist/studio-embed.d.ts +9 -0
  43. package/dist/studio-embed.js +8 -0
  44. package/dist/token-endpoint.d.ts +23 -0
  45. package/dist/token-endpoint.js +30 -0
  46. package/package.json +15 -8
  47. package/dist/ag-ui/history.d.ts +0 -7
  48. package/dist/ag-ui/history.js +0 -60
  49. package/dist/ag-ui/sse.d.ts +0 -9
  50. package/dist/ag-ui/sse.js +0 -7
  51. package/dist/ag-ui/translate.d.ts +0 -38
  52. package/dist/ag-ui/translate.js +0 -222
  53. package/dist/derived-credentials.d.ts +0 -5
  54. package/dist/derived-credentials.js +0 -17
  55. package/dist/executor.d.ts +0 -20
  56. package/dist/executor.js +0 -353
  57. package/dist/mcp/mcp.d.ts +0 -30
  58. package/dist/mcp/mcp.js +0 -80
  59. package/dist/sandbox/sandbox.d.ts +0 -27
  60. package/dist/sandbox/sandbox.js +0 -92
package/dist/executor.js DELETED
@@ -1,353 +0,0 @@
1
- import { AgentManifestSchema, ActionSchema, ActionClaimResponseSchema, } from "@nylorun/core/contracts";
2
- import { agentFrom, isBuiltWorkflow, } from "@nylorun/core/define";
3
- import { AgentsClient, assertNoMiddlewareClosures, } from "./client.js";
4
- import { resolveConnection } from "./connection.js";
5
- import { deriveExecutorToken } from "./derived-credentials.js";
6
- import { Transport, RuntimeError, delay, env, id, segment, } from "./http.js";
7
- import { readSSE } from "./sse.js";
8
- import { executeAction } from "./execute-action.js";
9
- import { createActionSandbox, definitionDeclaresSandbox, } from "./sandbox/client.js";
10
- function implementationVersionOf(options) {
11
- return (options.implementationVersion ??
12
- env("NYLORUN_IMPLEMENTATION_VERSION") ??
13
- "dev");
14
- }
15
- function buildAgents(options) {
16
- const agents = new Map();
17
- const addAgent = (built) => {
18
- assertNoMiddlewareClosures(built);
19
- AgentManifestSchema.parse(built.manifest);
20
- if (agents.has(built.id))
21
- throw new Error(`Duplicate connected agent ${built.id}`);
22
- agents.set(built.id, built);
23
- };
24
- for (const source of options.agents) {
25
- if (isBuiltWorkflow(source)) {
26
- if (agents.has(source.id))
27
- throw new Error(`Duplicate connected agent ${source.id}`);
28
- agents.set(source.id, source);
29
- for (const binding of Object.values(source.getBinding().agents))
30
- addAgent(agentFrom(binding.manifest, binding.implementations));
31
- continue;
32
- }
33
- const built = source.build?.() ?? source;
34
- if (isBuiltWorkflow(built)) {
35
- if (agents.has(built.id))
36
- throw new Error(`Duplicate connected agent ${built.id}`);
37
- agents.set(built.id, built);
38
- for (const binding of Object.values(built.getBinding().agents))
39
- addAgent(agentFrom(binding.manifest, binding.implementations));
40
- continue;
41
- }
42
- addAgent(built);
43
- }
44
- if (!agents.size)
45
- throw new Error("connectAgents requires at least one agent");
46
- return agents;
47
- }
48
- function connectExecutorMode(options, runtime, agents) {
49
- const transport = new Transport(runtime, "executor");
50
- const version = implementationVersionOf(options);
51
- const controller = new AbortController();
52
- const signal = controller.signal;
53
- const active = new Map();
54
- let resolveReady;
55
- let rejectReady;
56
- const ready = new Promise((resolve, reject) => {
57
- resolveReady = resolve;
58
- rejectReady = reject;
59
- });
60
- // Avoid an unhandled rejection for callers that immediately close the handle.
61
- void ready.catch(() => { });
62
- const report = (error) => {
63
- try {
64
- options.onError?.(error);
65
- }
66
- catch { }
67
- };
68
- let discovering = false, dirty = false;
69
- const discover = async () => {
70
- dirty = true;
71
- if (discovering)
72
- return;
73
- discovering = true;
74
- try {
75
- while (dirty && !signal.aborted) {
76
- dirty = false;
77
- const response = await transport.json("/v1/actions", "GET", undefined, signal);
78
- for (const item of response.actions) {
79
- // Skip actions this SDK cannot run (e.g. from an older Runtime) instead of stalling discovery.
80
- const parsed = ActionSchema.safeParse(item);
81
- if (!parsed.success) {
82
- report(parsed.error);
83
- continue;
84
- }
85
- const action = parsed.data;
86
- const agent = agents.get(action.agentId);
87
- if (!agent || action.status !== "pending" || active.has(action.actionId))
88
- continue;
89
- const work = new AbortController();
90
- active.set(action.actionId, work);
91
- let delivered = false;
92
- void processAction(action, agent, work)
93
- .then(() => {
94
- delivered = true;
95
- })
96
- .catch((error) => {
97
- if (!signal.aborted)
98
- report(error);
99
- })
100
- .finally(() => {
101
- active.delete(action.actionId);
102
- // Completion-driven discovery drains bounded server pages without periodic polling.
103
- if (delivered && !signal.aborted)
104
- void discover().catch(report);
105
- });
106
- }
107
- }
108
- }
109
- finally {
110
- discovering = false;
111
- }
112
- };
113
- const processAction = async (action, agent, work) => {
114
- const stop = () => work.abort(signal.reason);
115
- signal.addEventListener("abort", stop, { once: true });
116
- let renewal;
117
- let renewing = true;
118
- let wakeRenewal;
119
- try {
120
- const claim = ActionClaimResponseSchema.parse(await transport.json(`/v1/actions/${segment(action.actionId)}/claim`, "POST", {
121
- requestId: id(),
122
- implementationVersion: version,
123
- }, work.signal));
124
- if (claim.action.actionId !== action.actionId)
125
- throw new Error("Claim definition mismatch");
126
- let expires = Date.parse(claim.leaseExpiresAt);
127
- renewal = (async () => {
128
- while (renewing && !work.signal.aborted) {
129
- const remaining = expires - Date.now();
130
- if (!Number.isFinite(remaining) || remaining <= 0)
131
- throw new Error("Executor lease expired");
132
- await new Promise((resolve) => {
133
- const finish = () => {
134
- clearTimeout(timer);
135
- work.signal.removeEventListener("abort", finish);
136
- resolve();
137
- };
138
- const timer = setTimeout(finish, Math.max(50, Math.floor(remaining / 3)));
139
- wakeRenewal = finish;
140
- work.signal.addEventListener("abort", finish, { once: true });
141
- });
142
- if (!renewing || work.signal.aborted)
143
- return;
144
- const renewed = await transport.json(`/v1/actions/${segment(action.actionId)}/heartbeat`, "POST", {
145
- requestId: id(),
146
- claimId: claim.claimId,
147
- generation: claim.generation,
148
- }, work.signal);
149
- expires = Date.parse(renewed.leaseExpiresAt);
150
- }
151
- })().catch((error) => {
152
- if (renewing && !work.signal.aborted) {
153
- work.abort(error);
154
- report(error);
155
- }
156
- });
157
- const outcome = await executeAction(claim.action, agent, work.signal, {
158
- sandbox: definitionDeclaresSandbox(agent.manifest)
159
- ? createActionSandbox({
160
- transport,
161
- actionId: action.actionId,
162
- claimId: claim.claimId,
163
- generation: claim.generation,
164
- signal: work.signal,
165
- })
166
- : undefined,
167
- });
168
- work.signal.throwIfAborted();
169
- const command = {
170
- type: "action_result",
171
- requestId: id(),
172
- idempotencyKey: `action:${action.actionId}:${claim.generation}`,
173
- actionId: action.actionId,
174
- claimId: claim.claimId,
175
- generation: claim.generation,
176
- outcome,
177
- };
178
- // Retrying delivery never re-invokes the tool/hook. Lost acknowledgements use the same receipt key.
179
- for (let attempt = 0;; attempt++) {
180
- try {
181
- await transport.json(`/v1/sessions/${segment(action.sessionId)}/commands`, "POST", command, work.signal);
182
- break;
183
- }
184
- catch (error) {
185
- if (work.signal.aborted ||
186
- attempt >= 4 ||
187
- (error instanceof RuntimeError &&
188
- error.status < 500 &&
189
- ![408, 429].includes(error.status)))
190
- throw error;
191
- await delay(Math.min(4000, 250 * 2 ** attempt), work.signal);
192
- }
193
- }
194
- }
195
- finally {
196
- renewing = false;
197
- wakeRenewal?.();
198
- await renewal;
199
- signal.removeEventListener("abort", stop);
200
- }
201
- };
202
- const loop = (async () => {
203
- let retry = 250;
204
- while (!signal.aborted) {
205
- const subscription = new AbortController();
206
- const stop = () => subscription.abort(signal.reason);
207
- signal.addEventListener("abort", stop, { once: true });
208
- try {
209
- const response = await transport.request("/v1/executors/connect", {
210
- signal: subscription.signal,
211
- headers: { Accept: "text/event-stream" },
212
- });
213
- if (!response.headers.get("content-type")?.includes("text/event-stream"))
214
- throw new Error("Executor endpoint did not return SSE");
215
- // Subscription exists before initial discovery; notifications can safely accumulate in its stream.
216
- await discover();
217
- resolveReady();
218
- for await (const frame of readSSE(response, subscription.signal)) {
219
- retry = 250;
220
- if (frame.event === "work_available" ||
221
- (frame.data && JSON.parse(frame.data).type === "work_available"))
222
- await discover();
223
- }
224
- }
225
- catch (error) {
226
- if (signal.aborted)
227
- break;
228
- report(error);
229
- if (error instanceof RuntimeError &&
230
- [400, 401, 403, 404].includes(error.status)) {
231
- rejectReady(error);
232
- controller.abort(error);
233
- break;
234
- }
235
- }
236
- finally {
237
- subscription.abort();
238
- signal.removeEventListener("abort", stop);
239
- }
240
- if (!signal.aborted)
241
- await delay(retry, signal).catch(() => { });
242
- retry = Math.min(30000, retry * 2);
243
- }
244
- rejectReady(signal.reason ?? new Error("Executor connection closed"));
245
- })();
246
- return {
247
- ready,
248
- async close() {
249
- controller.abort(new Error("Executor connection closed"));
250
- for (const work of active.values())
251
- work.abort(signal.reason);
252
- await loop;
253
- },
254
- };
255
- }
256
- function connectApplicationMode(options, application, agents) {
257
- const version = implementationVersionOf(options);
258
- const children = [];
259
- let resolveReady;
260
- let rejectReady;
261
- const ready = new Promise((resolve, reject) => {
262
- resolveReady = resolve;
263
- rejectReady = reject;
264
- });
265
- void ready.catch(() => { });
266
- const boot = (async () => {
267
- for (const agent of agents.values()) {
268
- await application.saveAgent(agent, { implementationVersion: version });
269
- }
270
- const registrations = [...agents.values()].map((agent) => ({
271
- agentId: agent.id,
272
- token: deriveExecutorToken(application.transport.key, application.transport.tenant, agent.id),
273
- implementationVersion: version,
274
- }));
275
- await application.transport.json("/v1/executors", "PUT", {
276
- executors: registrations,
277
- });
278
- for (const agent of agents.values()) {
279
- const token = deriveExecutorToken(application.transport.key, application.transport.tenant, agent.id);
280
- const child = connectExecutorMode({
281
- agents: [agent],
282
- implementationVersion: version,
283
- onError: options.onError,
284
- }, {
285
- url: application.transport.url,
286
- tenant: application.transport.tenant,
287
- key: token,
288
- fetch: application.transport.fetcher,
289
- }, new Map([[agent.id, agent]]));
290
- children.push(child);
291
- }
292
- await Promise.all(children.map((child) => child.ready));
293
- resolveReady();
294
- })().catch((error) => {
295
- rejectReady(error);
296
- });
297
- return {
298
- ready,
299
- async close() {
300
- await boot.catch(() => { });
301
- await Promise.allSettled(children.map((child) => child.close()));
302
- },
303
- };
304
- }
305
- export function connectAgents(options) {
306
- const agents = buildAgents(options);
307
- // D§7.2 mode table: runtime → executor; application → application;
308
- // neither → resolveConnection() role.
309
- if (options.runtime) {
310
- return connectExecutorMode(options, options.runtime, agents);
311
- }
312
- if (options.application) {
313
- return connectApplicationMode(options, options.application, agents);
314
- }
315
- let resolveReady;
316
- let rejectReady;
317
- const ready = new Promise((resolve, reject) => {
318
- resolveReady = resolve;
319
- rejectReady = reject;
320
- });
321
- void ready.catch(() => { });
322
- let handle;
323
- const boot = (async () => {
324
- const connection = await resolveConnection();
325
- if (connection.role === "executor") {
326
- handle = connectExecutorMode(options, {
327
- url: connection.url,
328
- tenant: connection.tenant,
329
- key: connection.key,
330
- }, agents);
331
- }
332
- else {
333
- const application = new AgentsClient({
334
- url: connection.url,
335
- tenant: connection.tenant,
336
- key: connection.key,
337
- });
338
- handle = connectApplicationMode(options, application, agents);
339
- }
340
- await handle.ready;
341
- resolveReady();
342
- })().catch((error) => {
343
- rejectReady(error);
344
- });
345
- return {
346
- ready,
347
- async close() {
348
- await boot.catch(() => { });
349
- await handle?.close();
350
- },
351
- };
352
- }
353
- export { deriveExecutorToken };
package/dist/mcp/mcp.d.ts DELETED
@@ -1,30 +0,0 @@
1
- import type { CapabilityDeclaration, McpServerManifest } from "@nylorun/core/define";
2
- export interface McpOptions {
3
- /** Capability id. Defaults to `"mcp"`. */
4
- readonly id?: string;
5
- }
6
- export interface McpCapability extends CapabilityDeclaration {
7
- readonly mcpServers: Readonly<Record<string, McpServerManifest>>;
8
- }
9
- export declare class McpError extends Error {
10
- readonly code: string;
11
- constructor(code: string, message: string);
12
- }
13
- /**
14
- * Declare MCP servers as one capability for `Agent.use`.
15
- *
16
- * The map shape matches agent-plugins `mcpServers` and manifest v3: each key
17
- * must equal that server's `name`. Supported transports are `stdio`,
18
- * `streamable-http`, and `sse` (see https://agent-plugins.org/plugin-authors/mcp-servers).
19
- *
20
- * ```ts
21
- * Agent({...}).use(mcp({
22
- * github: {
23
- * name: "github",
24
- * type: "streamable-http",
25
- * url: "https://mcp.example.com/github",
26
- * },
27
- * }))
28
- * ```
29
- */
30
- export declare function mcp(servers: Readonly<Record<string, McpServerManifest>>, options?: McpOptions): McpCapability;
package/dist/mcp/mcp.js DELETED
@@ -1,80 +0,0 @@
1
- export class McpError extends Error {
2
- code;
3
- constructor(code, message) {
4
- super(message);
5
- this.name = "McpError";
6
- this.code = code;
7
- }
8
- }
9
- /**
10
- * Declare MCP servers as one capability for `Agent.use`.
11
- *
12
- * The map shape matches agent-plugins `mcpServers` and manifest v3: each key
13
- * must equal that server's `name`. Supported transports are `stdio`,
14
- * `streamable-http`, and `sse` (see https://agent-plugins.org/plugin-authors/mcp-servers).
15
- *
16
- * ```ts
17
- * Agent({...}).use(mcp({
18
- * github: {
19
- * name: "github",
20
- * type: "streamable-http",
21
- * url: "https://mcp.example.com/github",
22
- * },
23
- * }))
24
- * ```
25
- */
26
- export function mcp(servers, options = {}) {
27
- const id = options.id ?? "mcp";
28
- if (!isRecord(servers)) {
29
- throw new McpError("mcp.invalid", "mcp() expects a non-empty map of MCP servers");
30
- }
31
- const entries = Object.entries(servers);
32
- if (entries.length === 0) {
33
- throw new McpError("mcp.empty", "mcp() requires at least one MCP server");
34
- }
35
- const mcpServers = {};
36
- for (const [key, server] of entries) {
37
- if (!isMcpServer(server)) {
38
- throw new McpError("mcp.invalid-server", `MCP server '${key}' is not a valid stdio, streamable-http, or sse declaration`);
39
- }
40
- if (server.name !== key) {
41
- throw new McpError("mcp.name-mismatch", `MCP server key '${key}' must equal the server name '${server.name}'`);
42
- }
43
- mcpServers[key] = freezeServer(server);
44
- }
45
- return { id, mcpServers: Object.freeze(mcpServers) };
46
- }
47
- function freezeServer(server) {
48
- if (server.type === "stdio") {
49
- return Object.freeze({
50
- name: server.name,
51
- type: "stdio",
52
- command: server.command,
53
- ...(server.args === undefined ? {} : { args: Object.freeze([...server.args]) }),
54
- ...(server.env === undefined
55
- ? {}
56
- : { env: Object.freeze({ ...server.env }) }),
57
- ...(server.cwd === undefined ? {} : { cwd: server.cwd }),
58
- });
59
- }
60
- return Object.freeze({
61
- name: server.name,
62
- type: server.type,
63
- url: server.url,
64
- ...(server.headers === undefined
65
- ? {}
66
- : { headers: Object.freeze({ ...server.headers }) }),
67
- });
68
- }
69
- function isMcpServer(value) {
70
- if (!isRecord(value) || typeof value.name !== "string" || !value.name)
71
- return false;
72
- if (value.type === "stdio")
73
- return typeof value.command === "string" && value.command.length > 0;
74
- if (value.type === "streamable-http" || value.type === "sse")
75
- return typeof value.url === "string" && value.url.length > 0;
76
- return false;
77
- }
78
- function isRecord(value) {
79
- return !!value && typeof value === "object" && !Array.isArray(value);
80
- }
@@ -1,27 +0,0 @@
1
- import type { CapabilityDeclaration, SandboxManifest, ToolDefinition } from "@nylorun/core/define";
2
- /** What computer the agent needs. The Runtime decides where it runs. */
3
- export type SandboxOptions = SandboxManifest;
4
- export interface SandboxCapabilityOptions {
5
- /** Capability id. Defaults to `"sandbox"`. */
6
- readonly id?: string;
7
- }
8
- export interface SandboxCapability extends CapabilityDeclaration {
9
- readonly sandbox: SandboxManifest;
10
- readonly tools: readonly ToolDefinition[];
11
- }
12
- export declare class SandboxError extends Error {
13
- readonly code: string;
14
- constructor(code: string, message: string);
15
- }
16
- /**
17
- * Give the agent an isolated Linux machine with a persistent `/workspace`.
18
- *
19
- * Adds the built-in `bash`, `read`, `write`, `edit`, `grep` and `glob` tools. They run in the
20
- * Nylorun Runtime, which picks the backend. Agent code declares requirements only.
21
- *
22
- * ```ts
23
- * Agent({ id: "analyst", instructions: "..." }).use(sandbox())
24
- * Agent({ ... }).use(sandbox({ image: "node:24", network: { allow: ["api.github.com"] } }))
25
- * ```
26
- */
27
- export declare function sandbox(options?: SandboxOptions, capability?: SandboxCapabilityOptions): SandboxCapability;
@@ -1,92 +0,0 @@
1
- import { SANDBOX_DEFERRED_FIELDS, SANDBOX_FIELDS, SANDBOX_INSTRUCTIONS, SANDBOX_NETWORK_PRESETS, createSandboxTools, isSandboxHostPattern, parseSandboxDuration, parseSandboxSize, } from "@nylorun/core/define";
2
- export class SandboxError extends Error {
3
- code;
4
- constructor(code, message) {
5
- super(message);
6
- this.name = "SandboxError";
7
- this.code = code;
8
- }
9
- }
10
- /**
11
- * Give the agent an isolated Linux machine with a persistent `/workspace`.
12
- *
13
- * Adds the built-in `bash`, `read`, `write`, `edit`, `grep` and `glob` tools. They run in the
14
- * Nylorun Runtime, which picks the backend. Agent code declares requirements only.
15
- *
16
- * ```ts
17
- * Agent({ id: "analyst", instructions: "..." }).use(sandbox())
18
- * Agent({ ... }).use(sandbox({ image: "node:24", network: { allow: ["api.github.com"] } }))
19
- * ```
20
- */
21
- export function sandbox(options = {}, capability = {}) {
22
- const id = capability.id ?? "sandbox";
23
- if (!isRecord(options))
24
- throw new SandboxError("sandbox.invalid", "sandbox() expects an options object");
25
- for (const key of Object.keys(options)) {
26
- if (SANDBOX_DEFERRED_FIELDS.includes(key))
27
- throw new SandboxError("sandbox.unsupported", `sandbox({ ${key} }) is not supported in this version. ` +
28
- `Supported options: ${SANDBOX_FIELDS.join(", ")}. See docs/design/sandboxes.md for what is planned.`);
29
- if (!SANDBOX_FIELDS.includes(key))
30
- throw new SandboxError("sandbox.unknown-option", `Unknown sandbox option '${key}'. Supported options: ${SANDBOX_FIELDS.join(", ")}`);
31
- }
32
- return Object.freeze({
33
- id,
34
- instructions: Object.freeze([SANDBOX_INSTRUCTIONS]),
35
- tools: createSandboxTools(),
36
- sandbox: freezeOptions(options),
37
- });
38
- }
39
- function freezeOptions(options) {
40
- const result = {};
41
- if (options.image !== undefined) {
42
- if (typeof options.image !== "string" || options.image.trim().length === 0)
43
- throw new SandboxError("sandbox.invalid-image", "sandbox({ image }) must be a non-empty OCI image reference");
44
- result.image = options.image;
45
- }
46
- if (options.network !== undefined) {
47
- if (!isRecord(options.network))
48
- throw new SandboxError("sandbox.invalid-network", "sandbox({ network }) must be an object");
49
- for (const key of Object.keys(options.network))
50
- if (key !== "preset" && key !== "allow")
51
- throw new SandboxError("sandbox.invalid-network", `Unknown network option '${key}'. Use preset and allow.`);
52
- const { preset, allow } = options.network;
53
- if (preset !== undefined && !SANDBOX_NETWORK_PRESETS.includes(preset))
54
- throw new SandboxError("sandbox.invalid-network", `network.preset must be one of ${SANDBOX_NETWORK_PRESETS.join(", ")}`);
55
- if (allow !== undefined) {
56
- if (!Array.isArray(allow))
57
- throw new SandboxError("sandbox.invalid-network", "network.allow must be an array of host names");
58
- for (const host of allow)
59
- if (typeof host !== "string" || !isSandboxHostPattern(host))
60
- throw new SandboxError("sandbox.invalid-network", `network.allow entry '${String(host)}' must be a host name such as api.github.com or *.example.com (no scheme, port or path)`);
61
- }
62
- result.network = Object.freeze({
63
- ...(preset === undefined ? {} : { preset: preset }),
64
- ...(allow === undefined ? {} : { allow: Object.freeze([...allow]) }),
65
- });
66
- }
67
- if (options.resources !== undefined) {
68
- if (!isRecord(options.resources))
69
- throw new SandboxError("sandbox.invalid-resources", "sandbox({ resources }) must be an object");
70
- for (const key of Object.keys(options.resources))
71
- if (key !== "cpus" && key !== "memory")
72
- throw new SandboxError("sandbox.invalid-resources", `resources.${key} is not supported in this version. Use cpus and memory.`);
73
- const { cpus, memory } = options.resources;
74
- if (cpus !== undefined && (!Number.isInteger(cpus) || cpus < 1 || cpus > 64))
75
- throw new SandboxError("sandbox.invalid-resources", "resources.cpus must be an integer from 1 to 64");
76
- if (memory !== undefined && (typeof memory !== "string" || parseSandboxSize(memory) === undefined))
77
- throw new SandboxError("sandbox.invalid-resources", "resources.memory must be a size such as 512MiB or 2GiB");
78
- result.resources = Object.freeze({
79
- ...(cpus === undefined ? {} : { cpus }),
80
- ...(memory === undefined ? {} : { memory }),
81
- });
82
- }
83
- if (options.idle !== undefined) {
84
- if (typeof options.idle !== "string" || parseSandboxDuration(options.idle) === undefined)
85
- throw new SandboxError("sandbox.invalid-idle", "sandbox({ idle }) must be a duration such as 30s, 15m or 1h");
86
- result.idle = options.idle;
87
- }
88
- return Object.freeze(result);
89
- }
90
- function isRecord(value) {
91
- return !!value && typeof value === "object" && !Array.isArray(value);
92
- }