ai-evaluate 2.1.8 → 2.2.0
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/evaluate.d.ts.map +1 -1
- package/dist/evaluate.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/miniflare-pool.d.ts.map +1 -1
- package/dist/miniflare-pool.js.map +1 -1
- package/dist/node.d.ts.map +1 -1
- package/dist/node.js.map +1 -1
- package/dist/static/index.d.ts +111 -0
- package/dist/static/index.d.ts.map +1 -0
- package/dist/static/index.js +347 -0
- package/dist/static/index.js.map +1 -0
- package/dist/type-guards.d.ts.map +1 -1
- package/dist/type-guards.js.map +1 -1
- package/dist/worker-template/core.d.ts.map +1 -1
- package/dist/worker-template/core.js +1 -1
- package/dist/worker-template/core.js.map +1 -1
- package/package.json +17 -4
- package/public/capnweb.mjs +220 -0
- package/public/index.mjs +426 -0
- package/public/scaffold.mjs +198 -0
- package/.turbo/turbo-build.log +0 -4
- package/.turbo/turbo-test.log +0 -54
- package/.turbo/turbo-typecheck.log +0 -4
- package/CHANGELOG.md +0 -48
- package/example/package.json +0 -20
- package/example/src/index.ts +0 -221
- package/example/wrangler.jsonc +0 -25
- package/src/capnweb-bundle.ts +0 -2596
- package/src/evaluate.ts +0 -329
- package/src/index.ts +0 -23
- package/src/miniflare-pool.ts +0 -395
- package/src/node.ts +0 -245
- package/src/repl.ts +0 -228
- package/src/shared.ts +0 -186
- package/src/type-guards.ts +0 -323
- package/src/types.ts +0 -196
- package/src/validation.ts +0 -120
- package/src/worker-template/code-transforms.ts +0 -32
- package/src/worker-template/core.ts +0 -557
- package/src/worker-template/helpers.ts +0 -90
- package/src/worker-template/index.ts +0 -23
- package/src/worker-template/sdk-generator.ts +0 -2515
- package/src/worker-template/test-generator.ts +0 -358
- package/test/evaluate-extended.test.js +0 -429
- package/test/evaluate-extended.test.ts +0 -469
- package/test/evaluate.test.js +0 -235
- package/test/evaluate.test.ts +0 -253
- package/test/index.test.js +0 -77
- package/test/index.test.ts +0 -95
- package/test/miniflare-pool.test.ts +0 -246
- package/test/node.test.ts +0 -467
- package/test/security.test.ts +0 -1009
- package/test/shared.test.ts +0 -105
- package/test/type-guards.test.ts +0 -303
- package/test/validation.test.ts +0 -240
- package/test/worker-template.test.js +0 -365
- package/test/worker-template.test.ts +0 -432
- package/tsconfig.json +0 -22
- package/vitest.config.js +0 -21
- package/vitest.config.ts +0 -28
package/src/evaluate.ts
DELETED
|
@@ -1,329 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Evaluate code in a sandboxed environment
|
|
3
|
-
*
|
|
4
|
-
* Uses Cloudflare worker_loaders for secure code execution.
|
|
5
|
-
* For Node.js/local development, import from 'ai-evaluate/node' instead.
|
|
6
|
-
*
|
|
7
|
-
* Requires:
|
|
8
|
-
* - LOADER binding (worker_loaders)
|
|
9
|
-
* - TEST binding (ai-tests service) - optional, only needed for test assertions
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
import type { EvaluateOptions, EvaluateResult, WorkerLoader, SandboxEnv } from './types.js'
|
|
13
|
-
import { generateWorkerCode } from './worker-template/index.js'
|
|
14
|
-
import { CAPNWEB_SOURCE } from './capnweb-bundle.js'
|
|
15
|
-
import { COMPATIBILITY_DATE, normalizeImport, extractPackageName } from './shared.js'
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Generate a minimal worker for simple script execution
|
|
19
|
-
* This doesn't require capnweb or TEST binding
|
|
20
|
-
*/
|
|
21
|
-
function generateSimpleWorkerCode(options: {
|
|
22
|
-
module?: string
|
|
23
|
-
script?: string
|
|
24
|
-
imports?: string[]
|
|
25
|
-
}): string {
|
|
26
|
-
const { module = '', script = '', imports = [] } = options
|
|
27
|
-
|
|
28
|
-
// Build import statements for pre-fetched external modules
|
|
29
|
-
// Modules are fetched by the host worker and included in the worker definition
|
|
30
|
-
const importStatements = imports
|
|
31
|
-
.map((url, i) => `import * as __import${i}__ from './__external_${i}__.js';`)
|
|
32
|
-
.join('\n')
|
|
33
|
-
|
|
34
|
-
// Make imports available as globals
|
|
35
|
-
const importGlobals = imports
|
|
36
|
-
.map((specifier, i) => {
|
|
37
|
-
const pkgName = extractPackageName(specifier, i)
|
|
38
|
-
const varName = pkgName === 'lodash' ? '_' : pkgName
|
|
39
|
-
return `globalThis.${varName} = __import${i}__.default || __import${i}__;
|
|
40
|
-
globalThis.pkg = __import${i}__.default || __import${i}__;`
|
|
41
|
-
})
|
|
42
|
-
.join('\n')
|
|
43
|
-
|
|
44
|
-
// Wrap script to capture return value (code is embedded at build time, no eval)
|
|
45
|
-
const wrappedScript = script
|
|
46
|
-
? `const __executeScript__ = async () => { ${script} }; const __result__ = await __executeScript__();`
|
|
47
|
-
: 'const __result__ = undefined;'
|
|
48
|
-
|
|
49
|
-
return `
|
|
50
|
-
// Simple Sandbox Worker
|
|
51
|
-
${importStatements}
|
|
52
|
-
|
|
53
|
-
const logs = [];
|
|
54
|
-
|
|
55
|
-
// Capture console output
|
|
56
|
-
const originalConsole = { ...console };
|
|
57
|
-
const captureConsole = (level) => (...args) => {
|
|
58
|
-
logs.push({
|
|
59
|
-
level,
|
|
60
|
-
message: args.map(a => typeof a === 'object' ? JSON.stringify(a) : String(a)).join(' '),
|
|
61
|
-
timestamp: Date.now()
|
|
62
|
-
});
|
|
63
|
-
originalConsole[level](...args);
|
|
64
|
-
};
|
|
65
|
-
console.log = captureConsole('log');
|
|
66
|
-
console.warn = captureConsole('warn');
|
|
67
|
-
console.error = captureConsole('error');
|
|
68
|
-
console.info = captureConsole('info');
|
|
69
|
-
|
|
70
|
-
// Make imports available globally
|
|
71
|
-
${importGlobals}
|
|
72
|
-
|
|
73
|
-
// User module code (if any)
|
|
74
|
-
${module}
|
|
75
|
-
|
|
76
|
-
export default {
|
|
77
|
-
async fetch(request, env) {
|
|
78
|
-
try {
|
|
79
|
-
// Execute the script (embedded at generation time - no new Function())
|
|
80
|
-
${wrappedScript}
|
|
81
|
-
|
|
82
|
-
return Response.json({
|
|
83
|
-
success: true,
|
|
84
|
-
value: __result__,
|
|
85
|
-
logs,
|
|
86
|
-
duration: 0
|
|
87
|
-
});
|
|
88
|
-
} catch (error) {
|
|
89
|
-
return Response.json({
|
|
90
|
-
success: false,
|
|
91
|
-
error: error.message || String(error),
|
|
92
|
-
logs,
|
|
93
|
-
duration: 0
|
|
94
|
-
});
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
};
|
|
98
|
-
`
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
/**
|
|
102
|
-
* Evaluate code in a sandboxed worker
|
|
103
|
-
*
|
|
104
|
-
* @example
|
|
105
|
-
* ```ts
|
|
106
|
-
* import { evaluate } from 'ai-evaluate'
|
|
107
|
-
*
|
|
108
|
-
* // Run a simple script
|
|
109
|
-
* const result = await evaluate({
|
|
110
|
-
* script: 'return 1 + 1'
|
|
111
|
-
* }, env)
|
|
112
|
-
* // { success: true, value: 2, logs: [], duration: ... }
|
|
113
|
-
* ```
|
|
114
|
-
*/
|
|
115
|
-
export async function evaluate(
|
|
116
|
-
options: EvaluateOptions,
|
|
117
|
-
env?: SandboxEnv
|
|
118
|
-
): Promise<EvaluateResult> {
|
|
119
|
-
const start = Date.now()
|
|
120
|
-
|
|
121
|
-
try {
|
|
122
|
-
// Require worker_loaders binding (check lowercase first, then legacy uppercase)
|
|
123
|
-
const loader = env?.loader || env?.LOADER
|
|
124
|
-
if (!loader) {
|
|
125
|
-
return {
|
|
126
|
-
success: false,
|
|
127
|
-
logs: [],
|
|
128
|
-
error:
|
|
129
|
-
'Sandbox requires worker_loaders binding. Add to wrangler.toml: [[worker_loaders]] binding = "LOADER". For Node.js, use: import { evaluate } from "ai-evaluate/node"',
|
|
130
|
-
duration: Date.now() - start,
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
// Use simple worker for basic script execution (no tests, no SDK)
|
|
135
|
-
const useSimpleWorker = !options.tests && !options.sdk
|
|
136
|
-
|
|
137
|
-
if (useSimpleWorker) {
|
|
138
|
-
return await evaluateSimple(options, loader, start)
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
// Use full worker template for tests and SDK features
|
|
142
|
-
return await evaluateWithWorkerLoader(options, loader, env?.TEST, start)
|
|
143
|
-
} catch (error) {
|
|
144
|
-
return {
|
|
145
|
-
success: false,
|
|
146
|
-
logs: [],
|
|
147
|
-
error: error instanceof Error ? error.message : String(error),
|
|
148
|
-
duration: Date.now() - start,
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
/**
|
|
154
|
-
* Pre-fetch external modules from URLs or package names
|
|
155
|
-
* Returns a map of module name to source code
|
|
156
|
-
*
|
|
157
|
-
* Supports:
|
|
158
|
-
* - Full URLs: https://esm.sh/lodash@4.17.21
|
|
159
|
-
* - Bare package names: lodash, lodash@4.17.21, @scope/pkg
|
|
160
|
-
*
|
|
161
|
-
* Handles esm.sh's redirect-style modules by following the internal import paths.
|
|
162
|
-
*/
|
|
163
|
-
async function prefetchModules(imports: string[]): Promise<Record<string, string>> {
|
|
164
|
-
const modules: Record<string, string> = {}
|
|
165
|
-
|
|
166
|
-
await Promise.all(
|
|
167
|
-
imports.map(async (specifier, i) => {
|
|
168
|
-
try {
|
|
169
|
-
// Normalize bare package names to esm.sh URLs
|
|
170
|
-
const url = normalizeImport(specifier)
|
|
171
|
-
|
|
172
|
-
// For esm.sh URLs, try to get the bundled version directly
|
|
173
|
-
let fetchUrl = url
|
|
174
|
-
if (url.includes('esm.sh/') && !url.includes('.mjs') && !url.includes('.js')) {
|
|
175
|
-
// Parse the esm.sh URL to construct the bundle path
|
|
176
|
-
// e.g., https://esm.sh/lodash@4.17.21 -> https://esm.sh/lodash@4.17.21/es2022/lodash.bundle.mjs
|
|
177
|
-
const urlObj = new URL(url)
|
|
178
|
-
const pathParts = urlObj.pathname.slice(1).split('/')
|
|
179
|
-
const pkgSpec = pathParts[0] // e.g., "lodash@4.17.21"
|
|
180
|
-
const pkgName = pkgSpec.split('@')[0]
|
|
181
|
-
fetchUrl = `${urlObj.origin}/${pkgSpec}/es2022/${pkgName}.bundle.mjs`
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
const response = await fetch(fetchUrl, { redirect: 'follow' })
|
|
185
|
-
if (!response.ok) {
|
|
186
|
-
// Fallback to original URL if bundle URL fails
|
|
187
|
-
const fallbackResponse = await fetch(url, { redirect: 'follow' })
|
|
188
|
-
if (!fallbackResponse.ok) {
|
|
189
|
-
throw new Error(`Failed to fetch ${url}: ${fallbackResponse.status}`)
|
|
190
|
-
}
|
|
191
|
-
const source = await fallbackResponse.text()
|
|
192
|
-
modules[`__external_${i}__.js`] = source
|
|
193
|
-
return
|
|
194
|
-
}
|
|
195
|
-
const source = await response.text()
|
|
196
|
-
// Use a simple module name that can be imported
|
|
197
|
-
const moduleName = `__external_${i}__.js`
|
|
198
|
-
modules[moduleName] = source
|
|
199
|
-
} catch (error) {
|
|
200
|
-
throw new Error(
|
|
201
|
-
`Failed to fetch import ${specifier}: ${
|
|
202
|
-
error instanceof Error ? error.message : String(error)
|
|
203
|
-
}`
|
|
204
|
-
)
|
|
205
|
-
}
|
|
206
|
-
})
|
|
207
|
-
)
|
|
208
|
-
|
|
209
|
-
return modules
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
/**
|
|
213
|
-
* Simple evaluation without capnweb/TEST dependencies
|
|
214
|
-
*/
|
|
215
|
-
async function evaluateSimple(
|
|
216
|
-
options: EvaluateOptions,
|
|
217
|
-
loader: WorkerLoader,
|
|
218
|
-
start: number
|
|
219
|
-
): Promise<EvaluateResult> {
|
|
220
|
-
// Pre-fetch any external modules
|
|
221
|
-
let externalModules: Record<string, string> = {}
|
|
222
|
-
if (options.imports && options.imports.length > 0) {
|
|
223
|
-
try {
|
|
224
|
-
externalModules = await prefetchModules(options.imports)
|
|
225
|
-
} catch (error) {
|
|
226
|
-
return {
|
|
227
|
-
success: false,
|
|
228
|
-
logs: [],
|
|
229
|
-
error: error instanceof Error ? error.message : String(error),
|
|
230
|
-
duration: Date.now() - start,
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
const workerCode = generateSimpleWorkerCode({
|
|
236
|
-
module: options.module,
|
|
237
|
-
script: options.script,
|
|
238
|
-
imports: options.imports,
|
|
239
|
-
})
|
|
240
|
-
|
|
241
|
-
const id = `sandbox-${Date.now()}-${Math.random().toString(36).slice(2)}`
|
|
242
|
-
|
|
243
|
-
const worker = loader.get(id, async () => ({
|
|
244
|
-
mainModule: 'worker.js',
|
|
245
|
-
modules: {
|
|
246
|
-
'worker.js': workerCode,
|
|
247
|
-
...externalModules,
|
|
248
|
-
},
|
|
249
|
-
compatibilityDate: COMPATIBILITY_DATE,
|
|
250
|
-
// Block network if fetch is false or null
|
|
251
|
-
globalOutbound: options.fetch === false || options.fetch === null ? null : undefined,
|
|
252
|
-
}))
|
|
253
|
-
|
|
254
|
-
// Get the entrypoint and call fetch
|
|
255
|
-
const entrypoint = worker.getEntrypoint()
|
|
256
|
-
const response = await entrypoint.fetch(new Request('http://sandbox/execute'))
|
|
257
|
-
const result = (await response.json()) as EvaluateResult
|
|
258
|
-
|
|
259
|
-
return {
|
|
260
|
-
...result,
|
|
261
|
-
duration: Date.now() - start,
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
/**
|
|
266
|
-
* Evaluate using full worker template with capnweb and TEST binding
|
|
267
|
-
*/
|
|
268
|
-
async function evaluateWithWorkerLoader(
|
|
269
|
-
options: EvaluateOptions,
|
|
270
|
-
loader: WorkerLoader,
|
|
271
|
-
testService: unknown,
|
|
272
|
-
start: number
|
|
273
|
-
): Promise<EvaluateResult> {
|
|
274
|
-
const workerCode = generateWorkerCode({
|
|
275
|
-
module: options.module,
|
|
276
|
-
tests: options.tests,
|
|
277
|
-
script: options.script,
|
|
278
|
-
sdk: options.sdk,
|
|
279
|
-
imports: options.imports,
|
|
280
|
-
fetch: options.fetch,
|
|
281
|
-
})
|
|
282
|
-
const id = `sandbox-${Date.now()}-${Math.random().toString(36).slice(2)}`
|
|
283
|
-
|
|
284
|
-
const worker = loader.get(id, async () => ({
|
|
285
|
-
mainModule: 'worker.js',
|
|
286
|
-
modules: {
|
|
287
|
-
'worker.js': workerCode,
|
|
288
|
-
// Include capnweb as a module so the worker can import it
|
|
289
|
-
'capnweb.js': CAPNWEB_SOURCE,
|
|
290
|
-
},
|
|
291
|
-
compatibilityDate: COMPATIBILITY_DATE,
|
|
292
|
-
// Block network if fetch is false or null
|
|
293
|
-
globalOutbound: options.fetch === false || options.fetch === null ? null : undefined,
|
|
294
|
-
bindings: {
|
|
295
|
-
TEST: testService,
|
|
296
|
-
},
|
|
297
|
-
}))
|
|
298
|
-
|
|
299
|
-
// Get the entrypoint and call fetch (required by Cloudflare worker_loaders API)
|
|
300
|
-
const entrypoint = worker.getEntrypoint()
|
|
301
|
-
const response = await entrypoint.fetch(new Request('http://sandbox/execute'))
|
|
302
|
-
const result = (await response.json()) as EvaluateResult
|
|
303
|
-
|
|
304
|
-
return {
|
|
305
|
-
...result,
|
|
306
|
-
duration: Date.now() - start,
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
/**
|
|
311
|
-
* Create an evaluate function bound to a specific environment
|
|
312
|
-
*
|
|
313
|
-
* Useful for Cloudflare Workers where env is passed to fetch handler.
|
|
314
|
-
*
|
|
315
|
-
* @example
|
|
316
|
-
* ```ts
|
|
317
|
-
* // In a Cloudflare Worker
|
|
318
|
-
* export default {
|
|
319
|
-
* async fetch(request, env) {
|
|
320
|
-
* const sandbox = createEvaluator(env)
|
|
321
|
-
* const result = await sandbox({ script: '1 + 1' })
|
|
322
|
-
* return Response.json(result)
|
|
323
|
-
* }
|
|
324
|
-
* }
|
|
325
|
-
* ```
|
|
326
|
-
*/
|
|
327
|
-
export function createEvaluator(env: SandboxEnv) {
|
|
328
|
-
return (options: EvaluateOptions) => evaluate(options, env)
|
|
329
|
-
}
|
package/src/index.ts
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ai-sandbox - Secure code execution in sandboxed environments
|
|
3
|
-
*
|
|
4
|
-
* Provides evaluate() for running untrusted code safely using:
|
|
5
|
-
* - Cloudflare worker_loaders in production
|
|
6
|
-
* - Miniflare in development/Node.js
|
|
7
|
-
*
|
|
8
|
-
* @packageDocumentation
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
export { evaluate, createEvaluator } from './evaluate.js'
|
|
12
|
-
export { normalizeImport, normalizeImports } from './shared.js'
|
|
13
|
-
|
|
14
|
-
export type {
|
|
15
|
-
EvaluateOptions,
|
|
16
|
-
EvaluateResult,
|
|
17
|
-
LogEntry,
|
|
18
|
-
TestResults,
|
|
19
|
-
TestResult,
|
|
20
|
-
SandboxEnv,
|
|
21
|
-
SDKConfig,
|
|
22
|
-
FetchConfig,
|
|
23
|
-
} from './types.js'
|