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/repl.ts
DELETED
|
@@ -1,228 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* REPL session support for ai-evaluate
|
|
3
|
-
*
|
|
4
|
-
* Provides persistent evaluation sessions with context preservation.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import type { EvaluateOptions, EvaluateResult, SandboxEnv, SDKConfig } from './types.js'
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* REPL session configuration
|
|
11
|
-
*/
|
|
12
|
-
export interface ReplSessionConfig {
|
|
13
|
-
/** Use local Miniflare instead of remote workers */
|
|
14
|
-
local?: boolean
|
|
15
|
-
/** Authentication token for remote execution */
|
|
16
|
-
auth?: string
|
|
17
|
-
/** SDK configuration for platform primitives */
|
|
18
|
-
sdk?: SDKConfig | boolean
|
|
19
|
-
/** Code to run when session starts (defines globals, imports) */
|
|
20
|
-
prelude?: string
|
|
21
|
-
/** Timeout for each evaluation in milliseconds */
|
|
22
|
-
timeout?: number
|
|
23
|
-
/** Allow network access */
|
|
24
|
-
allowNetwork?: boolean
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Result from a REPL evaluation
|
|
29
|
-
*/
|
|
30
|
-
export interface ReplEvalResult extends EvaluateResult {
|
|
31
|
-
/** Variables exported during evaluation */
|
|
32
|
-
exports?: Record<string, unknown>
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* REPL session with persistent context
|
|
37
|
-
*/
|
|
38
|
-
export interface ReplSession {
|
|
39
|
-
/** Evaluate code in the session context */
|
|
40
|
-
eval(code: string): Promise<ReplEvalResult>
|
|
41
|
-
|
|
42
|
-
/** Get current session context (accumulated exports) */
|
|
43
|
-
getContext(): Record<string, unknown>
|
|
44
|
-
|
|
45
|
-
/** Set a value in the session context */
|
|
46
|
-
setContext(key: string, value: unknown): void
|
|
47
|
-
|
|
48
|
-
/** Clear the session context */
|
|
49
|
-
clearContext(): void
|
|
50
|
-
|
|
51
|
-
/** Run prelude code (called automatically on first eval) */
|
|
52
|
-
runPrelude(): Promise<void>
|
|
53
|
-
|
|
54
|
-
/** Close the session and release resources */
|
|
55
|
-
close(): Promise<void>
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* Create a REPL session for interactive code evaluation
|
|
60
|
-
*
|
|
61
|
-
* @example
|
|
62
|
-
* ```ts
|
|
63
|
-
* import { createReplSession } from 'ai-evaluate/repl'
|
|
64
|
-
*
|
|
65
|
-
* const session = await createReplSession({ local: true })
|
|
66
|
-
*
|
|
67
|
-
* await session.eval('const sum = (a, b) => a + b')
|
|
68
|
-
* const result = await session.eval('sum(1, 2)')
|
|
69
|
-
* console.log(result.value) // 3
|
|
70
|
-
*
|
|
71
|
-
* await session.close()
|
|
72
|
-
* ```
|
|
73
|
-
*/
|
|
74
|
-
export async function createReplSession(
|
|
75
|
-
config?: ReplSessionConfig,
|
|
76
|
-
env?: SandboxEnv
|
|
77
|
-
): Promise<ReplSession> {
|
|
78
|
-
// Context accumulates across evaluations
|
|
79
|
-
let context: Record<string, unknown> = {}
|
|
80
|
-
let preludeRun = false
|
|
81
|
-
let miniflare: unknown = null
|
|
82
|
-
|
|
83
|
-
// Build module code from accumulated context
|
|
84
|
-
function buildContextModule(): string {
|
|
85
|
-
const entries = Object.entries(context)
|
|
86
|
-
if (entries.length === 0) return ''
|
|
87
|
-
|
|
88
|
-
return entries
|
|
89
|
-
.map(([key, value]) => {
|
|
90
|
-
if (typeof value === 'function') {
|
|
91
|
-
return `export const ${key} = ${value.toString()}`
|
|
92
|
-
}
|
|
93
|
-
return `export const ${key} = ${JSON.stringify(value)}`
|
|
94
|
-
})
|
|
95
|
-
.join('\n')
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
// Get the evaluate function (local or remote)
|
|
99
|
-
async function getEvaluate(): Promise<
|
|
100
|
-
(options: EvaluateOptions, env?: SandboxEnv) => Promise<EvaluateResult>
|
|
101
|
-
> {
|
|
102
|
-
if (config?.local) {
|
|
103
|
-
// Use local Miniflare via node.ts
|
|
104
|
-
const { evaluate } = await import('./node.js')
|
|
105
|
-
return evaluate
|
|
106
|
-
} else if (env) {
|
|
107
|
-
// Use remote worker loaders
|
|
108
|
-
const { evaluate } = await import('./evaluate.js')
|
|
109
|
-
return (options) => evaluate(options, env)
|
|
110
|
-
} else {
|
|
111
|
-
// Default to local if no env
|
|
112
|
-
const { evaluate } = await import('./node.js')
|
|
113
|
-
return evaluate
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
const evaluate = await getEvaluate()
|
|
118
|
-
|
|
119
|
-
async function runPrelude(): Promise<void> {
|
|
120
|
-
if (preludeRun || !config?.prelude) return
|
|
121
|
-
preludeRun = true
|
|
122
|
-
|
|
123
|
-
const result = await evaluate(
|
|
124
|
-
{
|
|
125
|
-
module: config.prelude,
|
|
126
|
-
script: 'return Object.keys(module)',
|
|
127
|
-
sdk: config.sdk,
|
|
128
|
-
timeout: config.timeout,
|
|
129
|
-
fetch: config.allowNetwork === false ? null : undefined,
|
|
130
|
-
},
|
|
131
|
-
env
|
|
132
|
-
)
|
|
133
|
-
|
|
134
|
-
if (result.success && Array.isArray(result.value)) {
|
|
135
|
-
// Store exported names in context
|
|
136
|
-
for (const key of result.value) {
|
|
137
|
-
context[key] = `__prelude_${key}__`
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
return {
|
|
143
|
-
async eval(code: string): Promise<ReplEvalResult> {
|
|
144
|
-
await runPrelude()
|
|
145
|
-
|
|
146
|
-
// Wrap code to capture any declared variables
|
|
147
|
-
const contextModule = buildContextModule()
|
|
148
|
-
const preludeModule = config?.prelude || ''
|
|
149
|
-
|
|
150
|
-
// Try to parse as expression first, then as statement
|
|
151
|
-
const isExpression =
|
|
152
|
-
!code.includes('const ') &&
|
|
153
|
-
!code.includes('let ') &&
|
|
154
|
-
!code.includes('function ') &&
|
|
155
|
-
!code.includes('class ') &&
|
|
156
|
-
!code.includes('export ')
|
|
157
|
-
|
|
158
|
-
const script = isExpression
|
|
159
|
-
? `return (${code})`
|
|
160
|
-
: code.includes('return ')
|
|
161
|
-
? code
|
|
162
|
-
: `${code}\nreturn undefined`
|
|
163
|
-
|
|
164
|
-
const result = await evaluate(
|
|
165
|
-
{
|
|
166
|
-
module: preludeModule + '\n' + contextModule,
|
|
167
|
-
script,
|
|
168
|
-
sdk: config?.sdk,
|
|
169
|
-
timeout: config?.timeout,
|
|
170
|
-
fetch: config?.allowNetwork === false ? null : undefined,
|
|
171
|
-
},
|
|
172
|
-
env
|
|
173
|
-
)
|
|
174
|
-
|
|
175
|
-
return result
|
|
176
|
-
},
|
|
177
|
-
|
|
178
|
-
getContext(): Record<string, unknown> {
|
|
179
|
-
return { ...context }
|
|
180
|
-
},
|
|
181
|
-
|
|
182
|
-
setContext(key: string, value: unknown): void {
|
|
183
|
-
context[key] = value
|
|
184
|
-
},
|
|
185
|
-
|
|
186
|
-
clearContext(): void {
|
|
187
|
-
context = {}
|
|
188
|
-
},
|
|
189
|
-
|
|
190
|
-
runPrelude,
|
|
191
|
-
|
|
192
|
-
async close(): Promise<void> {
|
|
193
|
-
context = {}
|
|
194
|
-
preludeRun = false
|
|
195
|
-
if (
|
|
196
|
-
miniflare &&
|
|
197
|
-
typeof (miniflare as { dispose?: () => Promise<void> }).dispose === 'function'
|
|
198
|
-
) {
|
|
199
|
-
await (miniflare as { dispose: () => Promise<void> }).dispose()
|
|
200
|
-
miniflare = null
|
|
201
|
-
}
|
|
202
|
-
},
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
/**
|
|
207
|
-
* Quick evaluation helper for one-off evaluations
|
|
208
|
-
*
|
|
209
|
-
* @example
|
|
210
|
-
* ```ts
|
|
211
|
-
* import { quickEval } from 'ai-evaluate/repl'
|
|
212
|
-
*
|
|
213
|
-
* const result = await quickEval('1 + 2 * 3')
|
|
214
|
-
* console.log(result.value) // 7
|
|
215
|
-
* ```
|
|
216
|
-
*/
|
|
217
|
-
export async function quickEval(
|
|
218
|
-
code: string,
|
|
219
|
-
config?: ReplSessionConfig,
|
|
220
|
-
env?: SandboxEnv
|
|
221
|
-
): Promise<ReplEvalResult> {
|
|
222
|
-
const session = await createReplSession(config, env)
|
|
223
|
-
try {
|
|
224
|
-
return await session.eval(code)
|
|
225
|
-
} finally {
|
|
226
|
-
await session.close()
|
|
227
|
-
}
|
|
228
|
-
}
|
package/src/shared.ts
DELETED
|
@@ -1,186 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Shared utilities for ai-evaluate
|
|
3
|
-
*
|
|
4
|
-
* Contains constants and helper functions used by both
|
|
5
|
-
* evaluate.ts (Workers) and node.ts (Node.js/Miniflare)
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import type { EvaluateResult } from './types.js'
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Compatibility date for dynamic workers (2026)
|
|
12
|
-
*/
|
|
13
|
-
export const COMPATIBILITY_DATE = '2026-01-01'
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Normalize an import specifier to a full URL
|
|
17
|
-
*
|
|
18
|
-
* Supports:
|
|
19
|
-
* - Full URLs: https://esm.sh/lodash@4.17.21 (unchanged)
|
|
20
|
-
* - Bare package names: lodash -> https://esm.sh/lodash
|
|
21
|
-
* - Package with version: lodash@4.17.21 -> https://esm.sh/lodash@4.17.21
|
|
22
|
-
* - Scoped packages: @scope/pkg -> https://esm.sh/@scope/pkg
|
|
23
|
-
*/
|
|
24
|
-
export function normalizeImport(specifier: string): string {
|
|
25
|
-
// Already a URL - return as-is
|
|
26
|
-
if (specifier.includes('://')) {
|
|
27
|
-
return specifier
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
// Bare package name or scoped package - prepend esm.sh
|
|
31
|
-
return `https://esm.sh/${specifier}`
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Normalize an array of import specifiers
|
|
36
|
-
*/
|
|
37
|
-
export function normalizeImports(imports: string[] | undefined): string[] | undefined {
|
|
38
|
-
if (!imports || imports.length === 0) return imports
|
|
39
|
-
return imports.map(normalizeImport)
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Extract package name from import specifier for variable naming
|
|
44
|
-
* Supports: lodash, lodash@4.17.21, @scope/pkg, https://esm.sh/lodash
|
|
45
|
-
*/
|
|
46
|
-
export function extractPackageName(specifier: string, index: number): string {
|
|
47
|
-
let pkgName: string
|
|
48
|
-
if (specifier.includes('://')) {
|
|
49
|
-
// Full URL - extract from path
|
|
50
|
-
const match = specifier.match(/esm\.sh\/(@?[^@/]+)/)
|
|
51
|
-
pkgName = match ? match[1].replace(/^@/, '').replace(/-/g, '_') : `pkg${index}`
|
|
52
|
-
} else {
|
|
53
|
-
// Bare package name - extract before @ version
|
|
54
|
-
pkgName = specifier.split('@')[0].replace(/^@/, '').replace(/-/g, '_').replace(/\//g, '_')
|
|
55
|
-
}
|
|
56
|
-
return pkgName
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Default sandbox URL for worker fetch requests
|
|
61
|
-
*/
|
|
62
|
-
export const SANDBOX_URL = 'http://sandbox/execute'
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Generate a unique sandbox worker ID
|
|
66
|
-
*/
|
|
67
|
-
export const generateSandboxId = (): string =>
|
|
68
|
-
`sandbox-${Date.now()}-${Math.random().toString(36).slice(2)}`
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Create an error result with consistent structure
|
|
72
|
-
*/
|
|
73
|
-
export function createErrorResult(error: unknown, start: number): EvaluateResult {
|
|
74
|
-
return {
|
|
75
|
-
success: false,
|
|
76
|
-
logs: [],
|
|
77
|
-
error: error instanceof Error ? error.message : String(error),
|
|
78
|
-
duration: Date.now() - start,
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* Process a result from worker execution, adding duration
|
|
84
|
-
*/
|
|
85
|
-
export function processResult(result: EvaluateResult, start: number): EvaluateResult {
|
|
86
|
-
return {
|
|
87
|
-
...result,
|
|
88
|
-
duration: Date.now() - start,
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* Check if a domain matches a pattern (supports wildcards)
|
|
94
|
-
* @param domain - The domain to check (e.g., 'api.example.com')
|
|
95
|
-
* @param pattern - The pattern to match against (e.g., '*.example.com' or 'api.example.com')
|
|
96
|
-
* @returns true if the domain matches the pattern
|
|
97
|
-
*/
|
|
98
|
-
export function matchesDomainPattern(domain: string, pattern: string): boolean {
|
|
99
|
-
// Normalize both to lowercase
|
|
100
|
-
const normalizedDomain = domain.toLowerCase()
|
|
101
|
-
const normalizedPattern = pattern.toLowerCase()
|
|
102
|
-
|
|
103
|
-
// Exact match
|
|
104
|
-
if (normalizedDomain === normalizedPattern) {
|
|
105
|
-
return true
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
// Wildcard pattern: *.example.com
|
|
109
|
-
if (normalizedPattern.startsWith('*.')) {
|
|
110
|
-
const suffix = normalizedPattern.slice(2) // Remove '*.'
|
|
111
|
-
// Domain must end with the suffix and have at least one character before it
|
|
112
|
-
// e.g., 'api.example.com' matches '*.example.com'
|
|
113
|
-
// but 'example.com' does not match '*.example.com'
|
|
114
|
-
return normalizedDomain.endsWith('.' + suffix) || normalizedDomain === suffix
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
return false
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
/**
|
|
121
|
-
* Check if a URL's domain is in the allowed list
|
|
122
|
-
* @param url - The URL to check
|
|
123
|
-
* @param allowedDomains - List of allowed domains (supports wildcards like '*.example.com')
|
|
124
|
-
* @returns true if the URL's domain is allowed
|
|
125
|
-
*/
|
|
126
|
-
export function isDomainAllowed(url: string, allowedDomains: string[]): boolean {
|
|
127
|
-
try {
|
|
128
|
-
const parsedUrl = new URL(url)
|
|
129
|
-
const hostname = parsedUrl.hostname
|
|
130
|
-
|
|
131
|
-
for (const pattern of allowedDomains) {
|
|
132
|
-
if (matchesDomainPattern(hostname, pattern)) {
|
|
133
|
-
return true
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
return false
|
|
138
|
-
} catch {
|
|
139
|
-
// Invalid URL - not allowed
|
|
140
|
-
return false
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
/**
|
|
145
|
-
* Generate JavaScript code for domain checking in workers
|
|
146
|
-
* This is embedded into the worker source code
|
|
147
|
-
*/
|
|
148
|
-
export function generateDomainCheckCode(allowedDomains: string[]): string {
|
|
149
|
-
const domainsJson = JSON.stringify(allowedDomains)
|
|
150
|
-
|
|
151
|
-
return `
|
|
152
|
-
// Domain allowlist checking
|
|
153
|
-
const __allowedDomains__ = ${domainsJson};
|
|
154
|
-
|
|
155
|
-
const __matchesDomainPattern__ = (domain, pattern) => {
|
|
156
|
-
const normalizedDomain = domain.toLowerCase();
|
|
157
|
-
const normalizedPattern = pattern.toLowerCase();
|
|
158
|
-
if (normalizedDomain === normalizedPattern) return true;
|
|
159
|
-
if (normalizedPattern.startsWith('*.')) {
|
|
160
|
-
const suffix = normalizedPattern.slice(2);
|
|
161
|
-
return normalizedDomain.endsWith('.' + suffix) || normalizedDomain === suffix;
|
|
162
|
-
}
|
|
163
|
-
return false;
|
|
164
|
-
};
|
|
165
|
-
|
|
166
|
-
const __isDomainAllowed__ = (url) => {
|
|
167
|
-
try {
|
|
168
|
-
const parsedUrl = new URL(url);
|
|
169
|
-
const hostname = parsedUrl.hostname;
|
|
170
|
-
for (const pattern of __allowedDomains__) {
|
|
171
|
-
if (__matchesDomainPattern__(hostname, pattern)) return true;
|
|
172
|
-
}
|
|
173
|
-
return false;
|
|
174
|
-
} catch { return false; }
|
|
175
|
-
};
|
|
176
|
-
|
|
177
|
-
const __originalFetch__ = globalThis.fetch;
|
|
178
|
-
globalThis.fetch = async (input, init) => {
|
|
179
|
-
const url = typeof input === 'string' ? input : input instanceof Request ? input.url : String(input);
|
|
180
|
-
if (!__isDomainAllowed__(url)) {
|
|
181
|
-
throw new Error(\`Network access blocked: domain not in allowlist. Attempted: \${new URL(url).hostname}\`);
|
|
182
|
-
}
|
|
183
|
-
return __originalFetch__(input, init);
|
|
184
|
-
};
|
|
185
|
-
`
|
|
186
|
-
}
|