aimyapi 1.0.9 → 1.0.10
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/README.md +28 -2
- package/dist/index.cjs +232 -111
- package/dist/index.d.cts +16 -8
- package/dist/index.d.mts +16 -8
- package/dist/index.d.ts +16 -8
- package/dist/index.js +232 -111
- package/dist/index.mjs +234 -113
- package/example/example1.ts +21 -10
- package/example/sales_api.md +1 -5
- package/example/sales_api.ts +4 -2
- package/example/sales_api_impl.ts +18 -4
- package/package.json +2 -3
- package/prompts/generate-code-prompt.md +37 -0
- package/src/index.ts +519 -495
- package/src/log-tester-inject.ts +233 -0
- package/src/log-tester.ts +185 -0
- package/src/logger.ts +113 -0
- package/src/sandbox-wrappers.ts +91 -46
- package/src/sandbox.ts +254 -277
- package/src/utils.ts +31 -1
- package/prompts/create-task-prompt.md +0 -37
package/src/sandbox-wrappers.ts
CHANGED
|
@@ -1,28 +1,29 @@
|
|
|
1
1
|
import "quickjs-emscripten";
|
|
2
|
-
import { QuickJSDeferredPromise } from "quickjs-emscripten";
|
|
2
|
+
import { isSuccess, QuickJSContext, QuickJSDeferredPromise } from "quickjs-emscripten";
|
|
3
|
+
import { z, ZodError, ZodObject } from "zod";
|
|
4
|
+
import { Logger } from "./logger";
|
|
3
5
|
|
|
6
|
+
type QuickJSPromiseWithId = QuickJSDeferredPromise & { _promiseId: number };
|
|
4
7
|
|
|
5
8
|
let promiseId = 0;
|
|
6
|
-
let openPromises = {};
|
|
9
|
+
let openPromises: Record<number, QuickJSPromiseWithId | null> = {};
|
|
7
10
|
|
|
8
|
-
export function wrapPromise(vm, promise: Promise<any>, beginAsyncProcess: () => void, endAsyncProcess: () => void): any {
|
|
11
|
+
export function wrapPromise(vm: QuickJSContext, promise: Promise<any>, beginAsyncProcess: () => void, endAsyncProcess: () => void, logger: Logger): any {
|
|
9
12
|
promiseId += 1;
|
|
10
13
|
|
|
11
|
-
const vmPromise = vm.newPromise()
|
|
14
|
+
const vmPromise = vm.newPromise() as QuickJSPromiseWithId
|
|
12
15
|
vmPromise._promiseId = promiseId;
|
|
13
16
|
beginAsyncProcess();
|
|
14
17
|
promise.then((...args) => {
|
|
15
18
|
//console.log("Promised resolved", args);
|
|
16
19
|
endAsyncProcess();
|
|
17
|
-
const wrappedArgs = args.map((arg) => wrap(vm, arg, undefined, beginAsyncProcess, endAsyncProcess));
|
|
20
|
+
const wrappedArgs = args.map((arg) => wrap(vm, arg, undefined, beginAsyncProcess, endAsyncProcess, logger));
|
|
18
21
|
openPromises[vmPromise._promiseId] = null;
|
|
19
22
|
vmPromise.resolve(...wrappedArgs);
|
|
20
23
|
wrappedArgs.forEach((arg) => arg.dispose());
|
|
21
|
-
}).catch(
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
vmPromise.reject(...wrappedArgs);
|
|
25
|
-
wrappedArgs.forEach((arg) => arg.dispose());
|
|
24
|
+
}).catch(error => {
|
|
25
|
+
vmPromise.reject(vm.newError(error?.message || "An error occurred"));
|
|
26
|
+
|
|
26
27
|
openPromises[vmPromise._promiseId] = null;
|
|
27
28
|
});
|
|
28
29
|
vmPromise.settled.then( () => {
|
|
@@ -35,37 +36,42 @@ export function wrapPromise(vm, promise: Promise<any>, beginAsyncProcess: () =>
|
|
|
35
36
|
return vmPromise.handle
|
|
36
37
|
}
|
|
37
38
|
|
|
38
|
-
export function rejectOpenPromises(vm) {
|
|
39
|
+
export function rejectOpenPromises(vm: QuickJSContext) {
|
|
39
40
|
for (const [id, promise ] of Object.entries(openPromises)) {
|
|
40
41
|
if (promise !== null) {
|
|
41
42
|
(promise as QuickJSDeferredPromise).reject(vm.newError("Shutting down"));
|
|
42
|
-
openPromises[id] = null;
|
|
43
|
+
openPromises[id as unknown as number] = null;
|
|
43
44
|
}
|
|
44
45
|
}
|
|
45
46
|
|
|
46
47
|
vm.runtime.executePendingJobs();
|
|
47
48
|
}
|
|
48
49
|
|
|
49
|
-
|
|
50
|
+
const isPromise = (value: any) => { return (typeof value === 'object' && typeof value.then === 'function') };
|
|
51
|
+
|
|
52
|
+
// Value - member variable to wrap
|
|
53
|
+
// context - owning object
|
|
54
|
+
export function wrap(vm: QuickJSContext, value: any, context: object | undefined, beginAsyncProcess: () => void, endAsyncProcess: () => void, logger: Logger): any {
|
|
50
55
|
if (value === null) {
|
|
56
|
+
|
|
51
57
|
return null;
|
|
52
|
-
} else if (
|
|
53
|
-
|
|
54
|
-
return wrapPromise(vm, value, beginAsyncProcess, endAsyncProcess);
|
|
58
|
+
} else if (isPromise(value)) {
|
|
59
|
+
return wrapPromise(vm, value, beginAsyncProcess, endAsyncProcess, logger);
|
|
55
60
|
} else if (typeof value === "function") {
|
|
56
61
|
//console.log("Wrapped function")
|
|
57
|
-
return wrapGenericFunction(vm, value, context, beginAsyncProcess, endAsyncProcess);
|
|
62
|
+
return wrapGenericFunction(vm, value, context, beginAsyncProcess, endAsyncProcess, logger);
|
|
58
63
|
} else if (Array.isArray(value)) {
|
|
59
64
|
//console.log("Wrapped array")
|
|
60
|
-
return wrapArray(vm, value, beginAsyncProcess, endAsyncProcess);
|
|
65
|
+
return wrapArray(vm, value, beginAsyncProcess, endAsyncProcess, logger);
|
|
61
66
|
} else if (value === null) {
|
|
62
67
|
//console.log("Wrapped null")
|
|
63
68
|
return vm.null;
|
|
64
69
|
} else if (value instanceof Date) {
|
|
65
|
-
|
|
70
|
+
const res = vm.evalCode(`new Date(${value.getTime()})`);
|
|
71
|
+
return isSuccess(res) ? res.value : null;
|
|
66
72
|
} else if (typeof value === "object") {
|
|
67
73
|
//console.log("Wrapped object")
|
|
68
|
-
return wrapObject(vm, value, beginAsyncProcess, endAsyncProcess);
|
|
74
|
+
return wrapObject(vm, value, beginAsyncProcess, endAsyncProcess, logger);
|
|
69
75
|
} else if (typeof value === "string") {
|
|
70
76
|
//console.log("Wrapped string")
|
|
71
77
|
return vm.newString(value)
|
|
@@ -77,20 +83,21 @@ export function wrap(vm, value: any, context: object, beginAsyncProcess: () =>
|
|
|
77
83
|
return vm.newBigInt(value);
|
|
78
84
|
} else if (typeof value === "boolean") {
|
|
79
85
|
// since there is no newBoolean, we have to do this
|
|
80
|
-
|
|
86
|
+
const res = vm.evalCode(value ? "true" : "false")
|
|
87
|
+
return isSuccess(res) ? res.value : null;
|
|
81
88
|
} else if (typeof value === "undefined") {
|
|
82
89
|
return vm.undefined
|
|
83
90
|
} else {
|
|
91
|
+
console.error("Unknown type", value)
|
|
84
92
|
return null
|
|
85
93
|
}
|
|
86
94
|
}
|
|
87
95
|
|
|
88
|
-
export function wrapObject(vm, obj: any, beginAsyncProcess: () => void, endAsyncProcess: () => void): any {
|
|
96
|
+
export function wrapObject(vm: QuickJSContext, obj: any, beginAsyncProcess: () => void, endAsyncProcess: () => void, logger: Logger): any {
|
|
89
97
|
const vmObject = vm.newObject();
|
|
90
98
|
|
|
91
99
|
for (const [key, value] of Object.entries(obj)) {
|
|
92
|
-
|
|
93
|
-
let wrappedValue = wrap(vm, value, obj, beginAsyncProcess, endAsyncProcess);
|
|
100
|
+
let wrappedValue = wrap(vm, value, obj, beginAsyncProcess, endAsyncProcess, logger);
|
|
94
101
|
|
|
95
102
|
if (wrappedValue !== null) {
|
|
96
103
|
vm.setProp(vmObject, key, wrappedValue);
|
|
@@ -103,30 +110,30 @@ export function wrapObject(vm, obj: any, beginAsyncProcess: () => void, endAsync
|
|
|
103
110
|
return vmObject;
|
|
104
111
|
}
|
|
105
112
|
|
|
106
|
-
export function wrapObjectWhitelist(vm, obj: any, whitelist: string[], beginAsyncProcess: () => void, endAsyncProcess: () => void): any {
|
|
113
|
+
export function wrapObjectWhitelist(vm: QuickJSContext, obj: any, whitelist: string[], beginAsyncProcess: () => void, endAsyncProcess: () => void, logger: Logger): any {
|
|
107
114
|
const vmObject = vm.newObject();
|
|
108
115
|
|
|
109
116
|
whitelist.forEach((key) => {
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
117
|
+
// console.log("wrapping", key, value)
|
|
118
|
+
const value = obj[key];
|
|
119
|
+
const wrappedValue = wrap(vm, value, obj, beginAsyncProcess, endAsyncProcess, logger);
|
|
120
|
+
|
|
121
|
+
if (wrappedValue !== null) {
|
|
122
|
+
vm.setProp(vmObject, key, wrappedValue);
|
|
123
|
+
if (wrappedValue != vm.undefined && wrappedValue != vm.null && wrappedValue.dispose) {
|
|
124
|
+
wrappedValue.dispose();
|
|
125
|
+
}
|
|
118
126
|
}
|
|
119
|
-
}
|
|
120
127
|
});
|
|
121
128
|
|
|
122
129
|
return vmObject;
|
|
123
130
|
}
|
|
124
131
|
|
|
125
|
-
export function wrapArray(vm, arr: any[], beginAsyncProcess: () => void, endAsyncProcess: () => void): any {
|
|
132
|
+
export function wrapArray(vm: QuickJSContext, arr: any[], beginAsyncProcess: () => void, endAsyncProcess: () => void, logger: Logger): any {
|
|
126
133
|
const vmArray = vm.newArray();
|
|
127
134
|
|
|
128
135
|
for (let i = 0; i < arr.length; i++) {
|
|
129
|
-
let wrappedValue = wrap(vm, arr[i], arr, beginAsyncProcess, endAsyncProcess);
|
|
136
|
+
let wrappedValue = wrap(vm, arr[i], arr, beginAsyncProcess, endAsyncProcess, logger);
|
|
130
137
|
vm.setProp(vmArray, i, wrappedValue);
|
|
131
138
|
if (wrappedValue != vm.undefined && wrappedValue != vm.null) {
|
|
132
139
|
wrappedValue.dispose();
|
|
@@ -136,26 +143,64 @@ export function wrapArray(vm, arr: any[], beginAsyncProcess: () => void, endAsyn
|
|
|
136
143
|
return vmArray;
|
|
137
144
|
}
|
|
138
145
|
|
|
139
|
-
|
|
146
|
+
function validationDecorator(schema: ZodObject) {
|
|
147
|
+
return function(...args: unknown[]) {
|
|
148
|
+
let argumentObject: Record<string, unknown> = {};
|
|
149
|
+
const params = Object.keys(schema.def.shape);
|
|
150
|
+
args.forEach((value, index) => {
|
|
151
|
+
if(index < params.length) {
|
|
152
|
+
argumentObject[params[index]] = value;
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
schema.parse(argumentObject);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export function wrapGenericFunction(vm: QuickJSContext, fn: Function, context: object | undefined = undefined, beginAsyncProcess: () => void, endAsyncProcess: () => void, logger: Logger): any {
|
|
140
161
|
const vmFn = vm.newFunction(fn.name, (...args) => {
|
|
141
162
|
const unwrappedArgs: any[] = args.map(vm.dump)
|
|
142
163
|
//console.log("Wrapped function called", fn.name, unwrappedArgs )
|
|
143
164
|
try {
|
|
165
|
+
const validatorName = `_params_${fn.name}`;
|
|
166
|
+
const hasValidator = context != undefined && validatorName in context && "parse" in (context as Record<string, any>)[validatorName];
|
|
167
|
+
|
|
168
|
+
if(hasValidator) {
|
|
169
|
+
const schema = (context as Record<string, ZodObject>)[validatorName];
|
|
170
|
+
try {
|
|
171
|
+
const validator = validationDecorator(schema);
|
|
172
|
+
validator(...unwrappedArgs); // will throw if validation fails
|
|
173
|
+
} catch(e) {
|
|
174
|
+
// Throwing (not vm.throw) is how a host function raises an exception in the VM;
|
|
175
|
+
// newFunction catches it and turns it into a sandbox exception.
|
|
176
|
+
if(e instanceof ZodError) {
|
|
177
|
+
throw new Error(`Parameter validation failed for function "${fn.name}". Expected Parameters: [${Object.keys(schema.def.shape).join(",")}]. Errors:\n${z.prettifyError(e)}`);
|
|
178
|
+
}
|
|
179
|
+
throw new Error(`Parameter validation error calling ${fn.name}`);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
144
183
|
const result = fn.call(context, ...unwrappedArgs);
|
|
145
|
-
|
|
146
|
-
|
|
184
|
+
|
|
185
|
+
logger.trace(fn.name, unwrappedArgs, result);
|
|
186
|
+
|
|
187
|
+
const wrappedResult = wrap(vm, result, undefined, beginAsyncProcess, endAsyncProcess, logger);
|
|
147
188
|
return wrappedResult;
|
|
148
|
-
} catch (e) {
|
|
149
|
-
|
|
189
|
+
} catch (e: any) {
|
|
190
|
+
logger.error([e], "sandbox");
|
|
191
|
+
if(e instanceof Error) {
|
|
192
|
+
throw e;
|
|
193
|
+
}
|
|
194
|
+
throw new Error("An exception occurred calling " + fn.name);
|
|
150
195
|
}
|
|
151
196
|
|
|
152
197
|
});
|
|
153
198
|
return vmFn;
|
|
154
199
|
}
|
|
155
200
|
|
|
156
|
-
export function injectTimingFunctions(vm, cbAddedTimer, cbRemovedTimer, maxTimeout = 600000): void {
|
|
201
|
+
export function injectTimingFunctions(vm: QuickJSContext, cbAddedTimer: () => void, cbRemovedTimer: () => void, maxTimeout = 600000): void {
|
|
157
202
|
// Should we impose a limit on how long things can run for?
|
|
158
|
-
const timeoutFunctionHandles = {};
|
|
203
|
+
const timeoutFunctionHandles: any = {};
|
|
159
204
|
const _setTimeout = vm.newFunction("setTimeout", (vmFnHandle, timeoutHandle) => {
|
|
160
205
|
// Make a copy because otherwise vmFnHandle does not live long enough to call after the timeout
|
|
161
206
|
const vmFnHandleCopy = vmFnHandle.dup()
|
|
@@ -181,12 +226,12 @@ export function injectTimingFunctions(vm, cbAddedTimer, cbRemovedTimer, maxTimeo
|
|
|
181
226
|
}, timeout)
|
|
182
227
|
timeoutFunctionHandles[timeoutID.toString()] = vmFnHandleCopy;
|
|
183
228
|
|
|
184
|
-
return vm.newNumber(timeoutID);
|
|
229
|
+
return vm.newNumber(timeoutID as unknown as number);
|
|
185
230
|
});
|
|
186
231
|
|
|
187
232
|
vm.setProp(vm.global, "setTimeout", _setTimeout);
|
|
188
233
|
_setTimeout.dispose();
|
|
189
|
-
const intervalFunctionHandles = {};
|
|
234
|
+
const intervalFunctionHandles: any = {};
|
|
190
235
|
const _setInterval = vm.newFunction("setInterval", (vmFnHandle, timeoutHandle) => {
|
|
191
236
|
// Make a copy because otherwise vmFnHandle does not live long enough to call after the timeout
|
|
192
237
|
const vmFnHandleCopy = vmFnHandle.dup()
|
|
@@ -221,7 +266,7 @@ export function injectTimingFunctions(vm, cbAddedTimer, cbRemovedTimer, maxTimeo
|
|
|
221
266
|
}, timeout);
|
|
222
267
|
intervalFunctionHandles[intervalId.toString()] = vmFnHandleCopy;
|
|
223
268
|
|
|
224
|
-
return vm.newNumber(intervalId);
|
|
269
|
+
return vm.newNumber(intervalId as unknown as number);
|
|
225
270
|
});
|
|
226
271
|
vm.setProp(vm.global, "setInterval", _setInterval);
|
|
227
272
|
_setInterval.dispose();
|