aimyapi 0.1.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.
Files changed (41) hide show
  1. package/.prettierrc +3 -0
  2. package/LICENSE +21 -0
  3. package/README.md +197 -0
  4. package/dist/example/example1.d.ts +1 -0
  5. package/dist/example/example1.js +52 -0
  6. package/dist/example/example2.d.ts +1 -0
  7. package/dist/example/example2.js +66 -0
  8. package/dist/example/example3.d.ts +1 -0
  9. package/dist/example/example3.js +79 -0
  10. package/dist/example/ordering_api.d.ts +36 -0
  11. package/dist/example/ordering_api.js +125 -0
  12. package/dist/example/ordering_api_impl.d.ts +23 -0
  13. package/dist/example/ordering_api_impl.js +128 -0
  14. package/dist/example/sales_api.d.ts +16 -0
  15. package/dist/example/sales_api.js +5 -0
  16. package/dist/example/sales_api_impl.d.ts +7 -0
  17. package/dist/example/sales_api_impl.js +59 -0
  18. package/dist/lib/index.d.ts +26 -0
  19. package/dist/lib/index.js +179 -0
  20. package/dist/lib/sandbox-wrappers.d.ts +9 -0
  21. package/dist/lib/sandbox-wrappers.js +237 -0
  22. package/dist/lib/sandbox.d.ts +14 -0
  23. package/dist/lib/sandbox.js +161 -0
  24. package/dist/lib/utils.d.ts +1 -0
  25. package/dist/lib/utils.js +7 -0
  26. package/example/example1.ts +33 -0
  27. package/example/example2.ts +50 -0
  28. package/example/example3.ts +64 -0
  29. package/example/ordering_api.md +106 -0
  30. package/example/ordering_api.ts +194 -0
  31. package/example/ordering_api_impl.ts +194 -0
  32. package/example/sales_api.md +15 -0
  33. package/example/sales_api.ts +29 -0
  34. package/example/sales_api_impl.ts +60 -0
  35. package/lib/index.ts +247 -0
  36. package/lib/sandbox-wrappers.ts +250 -0
  37. package/lib/sandbox.ts +184 -0
  38. package/lib/utils.ts +3 -0
  39. package/package.json +41 -0
  40. package/prompts/create-task-prompt.md +21 -0
  41. package/tsconfig.json +12 -0
@@ -0,0 +1,128 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.OrderingAPI = void 0;
4
+ const utils_1 = require("../lib/utils");
5
+ const ordering_api_1 = require("./ordering_api");
6
+ // TODO: make functions to print out an item, and print out the current order with the price totals.
7
+ class OrderingAPI {
8
+ constructor() {
9
+ this._order = [];
10
+ this._lastOrderIdNumber = 0;
11
+ this._isCompleted = false;
12
+ this._history = [];
13
+ }
14
+ // Mock implementation, doesn't actually send an email
15
+ sendEmail(to, subject, body) {
16
+ console.log(`Sending email to "${to}" with subject "${subject}" and body\n"${body}"`);
17
+ return (0, utils_1.delay)(1000);
18
+ }
19
+ respondToUser(text) {
20
+ console.log(text);
21
+ this._history.push({
22
+ role: "assistant",
23
+ content: "Response output: " + text,
24
+ });
25
+ }
26
+ delay(milliseconds) {
27
+ return (0, utils_1.delay)(milliseconds);
28
+ }
29
+ getCurrentOrder() {
30
+ return this._order;
31
+ }
32
+ // addItemToOrder(item: MenuItemBase) {
33
+ // item.orderId = "" + (this._lastOrderIdNumber++);
34
+ // this._order.push(item);
35
+ // }
36
+ getCustomizationByName(itemName, name) {
37
+ const item = ordering_api_1.Menu.find((item) => item.name === itemName);
38
+ if (!item) {
39
+ return undefined;
40
+ }
41
+ return item.allowedCustomizations.find((customization) => customization.name === name);
42
+ }
43
+ addItemToOrder(itemName, customizationNames, toppings) {
44
+ const item = ordering_api_1.Menu.find((item) => item.name === itemName);
45
+ if (!item) {
46
+ throw new Error(`Item with name "${itemName}" not found`);
47
+ }
48
+ const customizations = customizationNames
49
+ .map((customizationName) => {
50
+ return this.getCustomizationByName(itemName, customizationName);
51
+ })
52
+ .filter((customization) => customization !== undefined);
53
+ const newItem = {
54
+ ...item,
55
+ customizations,
56
+ toppings: toppings,
57
+ orderId: "" + this._lastOrderIdNumber++,
58
+ };
59
+ this._order.push(newItem);
60
+ this.respondToUser(`Added ${itemName} with id ${newItem.orderId}`);
61
+ this.displayItem(newItem);
62
+ }
63
+ getItemInOrder(itemOrderId) {
64
+ const item = this._order.find((item) => item.orderId === itemOrderId);
65
+ if (!item) {
66
+ return undefined;
67
+ }
68
+ return item;
69
+ }
70
+ removeItemFromOrder(itemOrderId) {
71
+ this._order = this._order.filter((item) => item.orderId !== itemOrderId);
72
+ this.respondToUser(`Removed item ${itemOrderId}`);
73
+ }
74
+ modifyItemInOrder(itemOrderId, customizationNames, toppings) {
75
+ const existingIndex = this._order.findIndex((item) => item.orderId === itemOrderId);
76
+ if (existingIndex !== -1) {
77
+ const customizations = customizationNames
78
+ .map((customizationName) => {
79
+ return this.getCustomizationByName(this._order[existingIndex].name, customizationName);
80
+ })
81
+ .filter((customization) => customization !== undefined);
82
+ this._order[existingIndex].customizations = customizations;
83
+ this._order[existingIndex].toppings = toppings;
84
+ }
85
+ else {
86
+ throw new Error(`Item with id #"${itemOrderId}" not found in order`);
87
+ }
88
+ this.respondToUser(`Modified item ${itemOrderId}`);
89
+ }
90
+ getOrderTotal() {
91
+ const total = this._order.reduce((total, item) => {
92
+ const customizationTotal = item.customizations.reduce((total, customization) => {
93
+ return total + customization?.priceAdjustment || 0;
94
+ }, 0);
95
+ return total + item.price + customizationTotal;
96
+ }, 0);
97
+ return total;
98
+ }
99
+ completeOrder() {
100
+ this.displayOrder();
101
+ this.respondToUser("Order completed!");
102
+ this._lastOrderIdNumber = 0;
103
+ // clear history
104
+ this._history = [];
105
+ this._isCompleted = true;
106
+ }
107
+ displayItem(item) {
108
+ this.respondToUser(`\t#${item.orderId || ""} ${item.name}...\t\t$${item.price.toFixed(2)}`);
109
+ item.toppings.forEach((topping) => {
110
+ this.respondToUser(`\t\t${topping}`);
111
+ });
112
+ item.customizations.forEach((customization) => {
113
+ this.respondToUser(`\t\t${customization.name}...\t$${customization.priceAdjustment.toFixed(2)}`);
114
+ });
115
+ }
116
+ displayOrder() {
117
+ this.respondToUser("----\n\nDisplaying order:\n\n");
118
+ this._order.forEach((item) => this.displayItem(item));
119
+ this.respondToUser(`--\tTotal:\t\t$${this.getOrderTotal()}`);
120
+ }
121
+ _addMessageToHistory(message) {
122
+ this._history.push(message);
123
+ }
124
+ _getHistory() {
125
+ return this._history;
126
+ }
127
+ }
128
+ exports.OrderingAPI = OrderingAPI;
@@ -0,0 +1,16 @@
1
+ export declare const API_VERSION = "1.0.1";
2
+ export interface SalesData {
3
+ sales: number;
4
+ month: number;
5
+ year: number;
6
+ businessUnit: "Advertising" | "Hardware" | "Software";
7
+ }
8
+ export interface APIInterface {
9
+ sendEmail(to: string, subject: string, body: string): Promise<void>;
10
+ getSalesData(): SalesData[];
11
+ print(text: string): void;
12
+ delay(milliseconds: number): Promise<void>;
13
+ }
14
+ declare global {
15
+ var api: APIInterface;
16
+ }
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.API_VERSION = void 0;
4
+ exports.API_VERSION = "1.0.1";
5
+ ;
@@ -0,0 +1,7 @@
1
+ import { APIInterface, SalesData } from "./sales_api";
2
+ export declare class API implements APIInterface {
3
+ sendEmail(to: string, subject: string, body: string): Promise<void>;
4
+ getSalesData(): SalesData[];
5
+ print(text: string): void;
6
+ delay(milliseconds: number): Promise<void>;
7
+ }
@@ -0,0 +1,59 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.API = void 0;
4
+ const utils_1 = require("../lib/utils");
5
+ class API {
6
+ // Mock implementation, doesn't actually send an email
7
+ sendEmail(to, subject, body) {
8
+ console.log(`Sending email to "${to}" with subject "${subject}" and body\n"${body}"`);
9
+ return (0, utils_1.delay)(1000);
10
+ }
11
+ getSalesData() {
12
+ return [
13
+ { sales: 100, month: 1, year: 2020, businessUnit: "Advertising" },
14
+ { sales: 200, month: 2, year: 2020, businessUnit: "Advertising" },
15
+ { sales: 300, month: 3, year: 2020, businessUnit: "Advertising" },
16
+ { sales: 400, month: 4, year: 2020, businessUnit: "Advertising" },
17
+ { sales: 500, month: 5, year: 2020, businessUnit: "Advertising" },
18
+ { sales: 600, month: 6, year: 2020, businessUnit: "Advertising" },
19
+ { sales: 700, month: 7, year: 2020, businessUnit: "Advertising" },
20
+ { sales: 800, month: 8, year: 2020, businessUnit: "Advertising" },
21
+ { sales: 900, month: 9, year: 2020, businessUnit: "Advertising" },
22
+ { sales: 1000, month: 10, year: 2020, businessUnit: "Advertising" },
23
+ { sales: 1100, month: 11, year: 2020, businessUnit: "Advertising" },
24
+ { sales: 1100, month: 12, year: 2020, businessUnit: "Advertising" },
25
+ { sales: 1200, month: 1, year: 2020, businessUnit: "Software" },
26
+ { sales: 1300, month: 2, year: 2020, businessUnit: "Software" },
27
+ { sales: 1400, month: 3, year: 2020, businessUnit: "Software" },
28
+ { sales: 1500, month: 4, year: 2020, businessUnit: "Software" },
29
+ { sales: 1600, month: 5, year: 2020, businessUnit: "Software" },
30
+ { sales: 1700, month: 6, year: 2020, businessUnit: "Software" },
31
+ { sales: 1800, month: 7, year: 2020, businessUnit: "Software" },
32
+ { sales: 1800, month: 8, year: 2020, businessUnit: "Software" },
33
+ { sales: 1500, month: 9, year: 2020, businessUnit: "Software" },
34
+ { sales: 1200, month: 10, year: 2020, businessUnit: "Software" },
35
+ { sales: 1000, month: 11, year: 2020, businessUnit: "Software" },
36
+ { sales: 100, month: 12, year: 2020, businessUnit: "Software" },
37
+ { sales: 1200, month: 1, year: 2020, businessUnit: "Hardware" },
38
+ { sales: 1300, month: 2, year: 2020, businessUnit: "Hardware" },
39
+ { sales: 1400, month: 3, year: 2020, businessUnit: "Hardware" },
40
+ { sales: 1500, month: 4, year: 2020, businessUnit: "Hardware" },
41
+ { sales: 1600, month: 5, year: 2020, businessUnit: "Hardware" },
42
+ { sales: 1700, month: 6, year: 2020, businessUnit: "Hardware" },
43
+ { sales: 1800, month: 7, year: 2020, businessUnit: "Hardware" },
44
+ { sales: 1800, month: 8, year: 2020, businessUnit: "Hardware" },
45
+ { sales: 1500, month: 9, year: 2020, businessUnit: "Hardware" },
46
+ { sales: 1200, month: 10, year: 2020, businessUnit: "Hardware" },
47
+ { sales: 1000, month: 11, year: 2020, businessUnit: "Hardware" },
48
+ { sales: 100, month: 12, year: 2020, businessUnit: "Hardware" },
49
+ ];
50
+ }
51
+ ;
52
+ print(text) {
53
+ console.log(text);
54
+ }
55
+ delay(milliseconds) {
56
+ return (0, utils_1.delay)(milliseconds);
57
+ }
58
+ }
59
+ exports.API = API;
@@ -0,0 +1,26 @@
1
+ import { ChatCompletionRequestMessage } from "openai";
2
+ import { QuickJSWASMModule } from "quickjs-emscripten";
3
+ export declare function createBasePrompt(apiFilePath: string, documentationPath: string): string;
4
+ export declare const generateCode: (queryText: string, userChatHistory: ChatCompletionRequestMessage[], createTaskPrompt: string, apiPath: string, debug?: boolean) => Promise<string>;
5
+ export interface AIMyAPIOptions {
6
+ apiObject: object;
7
+ apiExports: object;
8
+ apiGlobals?: object;
9
+ apiDefFilePath: string;
10
+ apiGlobalName?: string;
11
+ apiDocsPath?: string;
12
+ debug?: boolean;
13
+ }
14
+ export interface CreateWithAPIExports {
15
+ generateCode: (queryText: string, userChatHistory: ChatCompletionRequestMessage[]) => Promise<string>;
16
+ runCode: (task: string) => Promise<void>;
17
+ processRequest: (userQuery: string, context?: object) => Promise<string>;
18
+ }
19
+ export interface AIMyAPIExports {
20
+ createWithAPI: (options: AIMyAPIOptions) => Promise<CreateWithAPIExports>;
21
+ createBasePrompt: (apiFilePath: string, documentationPath: string) => string;
22
+ generateTask: (queryText: string, userChatHistory: any[], createTaskPrompt: string, apiPath: string) => Promise<string>;
23
+ createSandbox: (QuickJS: QuickJSWASMModule, globals: any) => Promise<any>;
24
+ }
25
+ declare const aimyapi: AIMyAPIExports;
26
+ export default aimyapi;
@@ -0,0 +1,179 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.generateCode = exports.createBasePrompt = void 0;
7
+ require('dotenv').config();
8
+ const openai_1 = require("openai");
9
+ const fs_1 = __importDefault(require("fs"));
10
+ const path_1 = __importDefault(require("path"));
11
+ const sandbox_1 = __importDefault(require("./sandbox"));
12
+ const sandbox_wrappers_1 = require("./sandbox-wrappers");
13
+ const quickjs_emscripten_1 = require("quickjs-emscripten");
14
+ const configuration = new openai_1.Configuration({
15
+ apiKey: process.env.OPENAI_API_KEY,
16
+ });
17
+ const openai = new openai_1.OpenAIApi(configuration);
18
+ // function formatObject(obj: any): string {
19
+ // return Object.keys(obj).reduce((acc, key) => {
20
+ // return `${acc}${key}:\n${obj[key]}\n\n`;
21
+ // }, "");
22
+ // }
23
+ function createBasePrompt(apiFilePath, documentationPath) {
24
+ const apiText = fs_1.default.readFileSync(apiFilePath, 'utf8');
25
+ const documentationText = documentationPath ? fs_1.default.readFileSync(documentationPath, 'utf8') : '';
26
+ //console.time("Loading templates");
27
+ const createTaskPrompt = fs_1.default.readFileSync(path_1.default.join(__dirname, '../prompts/create-task-prompt.md'), 'utf8').replace("{{DOCUMENTATION}}", documentationText).replace("{{API}}", apiText);
28
+ //console.timeEnd("Loading templates")
29
+ return createTaskPrompt;
30
+ }
31
+ exports.createBasePrompt = createBasePrompt;
32
+ // userChatHistory is not currently used but could be in the future.
33
+ const generateCode = async function (queryText, userChatHistory, createTaskPrompt, apiPath, debug = false) {
34
+ if (!queryText)
35
+ return;
36
+ let generatedCode = '';
37
+ const prompt = createTaskPrompt.replace("{{QUERY_TEXT}}", queryText);
38
+ // TODO: I should switch this back to gpt3.5 turbo since it's cheaper
39
+ try {
40
+ // const task = await openai.createCompletion(
41
+ // {
42
+ // model: "text-davinci-003",
43
+ // prompt,
44
+ // temperature: 0.1,
45
+ // max_tokens: 1100,
46
+ // }
47
+ // );
48
+ const results = await openai.createChatCompletion({
49
+ model: "gpt-3.5-turbo",
50
+ messages: [
51
+ {
52
+ role: "system",
53
+ content: prompt,
54
+ },
55
+ ...userChatHistory,
56
+ {
57
+ role: "user",
58
+ content: `${queryText}`,
59
+ name: "user"
60
+ }
61
+ ],
62
+ temperature: 0.1,
63
+ max_tokens: 800,
64
+ });
65
+ if (debug)
66
+ console.log(results.data.usage);
67
+ const response = results.data.choices[0].message.content;
68
+ if (debug)
69
+ console.log(response);
70
+ // Return only the code between ``` and ``` (usually at the end of the completion)
71
+ const codeStart = response.indexOf('```');
72
+ const codeEnd = response.lastIndexOf('```');
73
+ if (codeStart === -1 || codeEnd === -1 || codeStart === codeEnd)
74
+ return '';
75
+ generatedCode = response.substring(codeStart + 3, codeEnd).replace('typescript', '');
76
+ generatedCode = `// Query=${queryText}\n` + generatedCode.replace("./api.ts", apiPath);
77
+ return generatedCode;
78
+ }
79
+ catch (err) {
80
+ console.error(err);
81
+ return '';
82
+ }
83
+ };
84
+ exports.generateCode = generateCode;
85
+ let QuickJS = null;
86
+ ;
87
+ async function createWithAPI(options) {
88
+ // extend options with defaults
89
+ options = {
90
+ apiGlobalName: "api",
91
+ apiGlobals: {},
92
+ debug: false,
93
+ ...options
94
+ };
95
+ if (!options.apiObject || !options.apiDefFilePath) {
96
+ throw new Error("apiObject and apiFilePath are required");
97
+ }
98
+ const { apiObject, apiGlobalName, apiExports, apiDefFilePath, apiDocsPath, debug } = options;
99
+ const whitelist = Object.getOwnPropertyNames(Object.getPrototypeOf(apiObject)).filter((f) => f !== "constructor" && !f.startsWith("_"));
100
+ const createTaskPrompt = createBasePrompt(apiDefFilePath, apiDocsPath);
101
+ return {
102
+ generateCode: async function (queryText, userChatHistory, currentContext = undefined) {
103
+ if (!queryText)
104
+ return '';
105
+ return await (0, exports.generateCode)(queryText, userChatHistory, createTaskPrompt.replace("{{CONTEXT}}", currentContext ? "```" + JSON.stringify(currentContext, null, 2) + "```" : ""), apiDefFilePath, debug);
106
+ },
107
+ runCode: async function (generatedCode) {
108
+ if (!QuickJS) {
109
+ // Create QuickJS
110
+ if (debug)
111
+ console.log("Creating quick.js");
112
+ QuickJS = await (0, quickjs_emscripten_1.getQuickJS)();
113
+ }
114
+ let apiGlobals = {};
115
+ for (const key in options.apiGlobals) {
116
+ apiGlobals[key] = {
117
+ value: options.apiGlobals[key],
118
+ };
119
+ }
120
+ // Create sandbox
121
+ // NOTE: not sure why I have to replace the backslash here
122
+ const { vm, runTask, isAsyncProcessRunning } = await (0, sandbox_1.default)(QuickJS, {
123
+ [apiDefFilePath.replaceAll("\\", "")]: apiExports,
124
+ }, {
125
+ ...apiGlobals,
126
+ [apiGlobalName]: {
127
+ value: apiObject,
128
+ whitelist
129
+ }
130
+ }, options.debug);
131
+ try {
132
+ const res = await runTask(generatedCode);
133
+ if (!res) {
134
+ console.error("Task unsuccessful");
135
+ }
136
+ while (isAsyncProcessRunning()) {
137
+ await new Promise((resolve) => setTimeout(resolve, 100));
138
+ }
139
+ if (debug) {
140
+ console.log("Completed");
141
+ }
142
+ }
143
+ catch (err) {
144
+ if (debug) {
145
+ console.error("Error running task:", err);
146
+ }
147
+ }
148
+ await new Promise((resolve) => setTimeout(resolve, 100));
149
+ (0, sandbox_wrappers_1.rejectOpenPromises)(vm);
150
+ vm.dispose();
151
+ },
152
+ processRequest: async function (userQuery, currentContext = undefined) {
153
+ // Create prompt
154
+ if (debug) {
155
+ console.log("Query:", userQuery);
156
+ }
157
+ // Generate Code
158
+ console.time("Generate Task");
159
+ const generatedCode = await (0, exports.generateCode)(userQuery, [], createTaskPrompt.replace("{{CONTEXT}}", currentContext ? "```" + JSON.stringify(currentContext, null, 2) + "```" : ""), apiDefFilePath);
160
+ console.timeEnd("Generate Task");
161
+ if (debug) {
162
+ console.log("Generated Code:", generatedCode);
163
+ }
164
+ console.time("Run Code");
165
+ await this.runCode(generatedCode);
166
+ console.timeEnd("Run Code");
167
+ return generatedCode;
168
+ }
169
+ };
170
+ }
171
+ const aimyapi = {
172
+ // Standard way of using the library
173
+ createWithAPI,
174
+ // functions for customized usage
175
+ createBasePrompt,
176
+ generateTask: exports.generateCode,
177
+ createSandbox: sandbox_1.default,
178
+ };
179
+ exports.default = aimyapi;
@@ -0,0 +1,9 @@
1
+ import "quickjs-emscripten";
2
+ export declare function wrapPromise(vm: any, promise: Promise<any>, beginAsyncProcess: () => void, endAsyncProcess: () => void): any;
3
+ export declare function rejectOpenPromises(vm: any): void;
4
+ export declare function wrap(vm: any, value: any, context: object, beginAsyncProcess: () => void, endAsyncProcess: () => void): any;
5
+ export declare function wrapObject(vm: any, obj: any, beginAsyncProcess: () => void, endAsyncProcess: () => void): any;
6
+ export declare function wrapObjectWhitelist(vm: any, obj: any, whitelist: string[], beginAsyncProcess: () => void, endAsyncProcess: () => void): any;
7
+ export declare function wrapArray(vm: any, arr: any[], beginAsyncProcess: () => void, endAsyncProcess: () => void): any;
8
+ export declare function wrapGenericFunction(vm: any, fn: Function, context: object, beginAsyncProcess: () => void, endAsyncProcess: () => void): any;
9
+ export declare function injectTimingFunctions(vm: any, cbAddedTimer: any, cbRemovedTimer: any, maxTimeout?: number): void;
@@ -0,0 +1,237 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.injectTimingFunctions = exports.wrapGenericFunction = exports.wrapArray = exports.wrapObjectWhitelist = exports.wrapObject = exports.wrap = exports.rejectOpenPromises = exports.wrapPromise = void 0;
4
+ require("quickjs-emscripten");
5
+ let promiseId = 0;
6
+ let openPromises = {};
7
+ function wrapPromise(vm, promise, beginAsyncProcess, endAsyncProcess) {
8
+ promiseId += 1;
9
+ const vmPromise = vm.newPromise();
10
+ vmPromise._promiseId = promiseId;
11
+ beginAsyncProcess();
12
+ promise.then((...args) => {
13
+ //console.log("Promised resolved", args);
14
+ endAsyncProcess();
15
+ const wrappedArgs = args.map((arg) => wrap(vm, arg, undefined, beginAsyncProcess, endAsyncProcess));
16
+ openPromises[vmPromise._promiseId] = null;
17
+ vmPromise.resolve(...wrappedArgs);
18
+ wrappedArgs.forEach((arg) => arg.dispose());
19
+ }).catch((...args) => {
20
+ //console.log("Promised rejected", args);
21
+ const wrappedArgs = args.map((arg) => wrap(vm, arg, undefined, beginAsyncProcess, endAsyncProcess));
22
+ vmPromise.reject(...wrappedArgs);
23
+ wrappedArgs.forEach((arg) => arg.dispose());
24
+ openPromises[vmPromise._promiseId] = null;
25
+ });
26
+ vmPromise.settled.then(() => {
27
+ endAsyncProcess();
28
+ vm.runtime.executePendingJobs();
29
+ });
30
+ openPromises[promiseId] = vmPromise;
31
+ return vmPromise.handle;
32
+ }
33
+ exports.wrapPromise = wrapPromise;
34
+ function rejectOpenPromises(vm) {
35
+ for (const [id, promise] of Object.entries(openPromises)) {
36
+ if (promise !== null) {
37
+ promise.reject(vm.newError("Shutting down"));
38
+ openPromises[id] = null;
39
+ }
40
+ }
41
+ vm.runtime.executePendingJobs();
42
+ }
43
+ exports.rejectOpenPromises = rejectOpenPromises;
44
+ function wrap(vm, value, context, beginAsyncProcess, endAsyncProcess) {
45
+ if (value === null) {
46
+ return null;
47
+ }
48
+ else if (typeof value === 'object' && typeof value.then === 'function') {
49
+ //console.log("Wrapped promise");
50
+ return wrapPromise(vm, value, beginAsyncProcess, endAsyncProcess);
51
+ }
52
+ else if (typeof value === "function") {
53
+ //console.log("Wrapped function")
54
+ return wrapGenericFunction(vm, value, context, beginAsyncProcess, endAsyncProcess);
55
+ }
56
+ else if (Array.isArray(value)) {
57
+ //console.log("Wrapped array")
58
+ return wrapArray(vm, value, beginAsyncProcess, endAsyncProcess);
59
+ }
60
+ else if (value === null) {
61
+ //console.log("Wrapped null")
62
+ return vm.null;
63
+ }
64
+ else if (typeof value === "object") {
65
+ //console.log("Wrapped object")
66
+ return wrapObject(vm, value, beginAsyncProcess, endAsyncProcess);
67
+ }
68
+ else if (typeof value === "string") {
69
+ //console.log("Wrapped string")
70
+ return vm.newString(value);
71
+ }
72
+ else if (typeof value === "number") {
73
+ //console.log("Wrapped number")
74
+ return vm.newNumber(value);
75
+ }
76
+ else if (typeof value === "bigint") {
77
+ //console.log("Wrapped bigint")
78
+ return vm.newBigInt(value);
79
+ }
80
+ else if (typeof value === "boolean") {
81
+ // since there is no newBoolean, we have to do this
82
+ return vm.evalCode(value ? "true" : "false").value;
83
+ }
84
+ else if (typeof value === "undefined") {
85
+ return vm.undefined;
86
+ }
87
+ else {
88
+ return null;
89
+ }
90
+ }
91
+ exports.wrap = wrap;
92
+ function wrapObject(vm, obj, beginAsyncProcess, endAsyncProcess) {
93
+ const vmObject = vm.newObject();
94
+ for (const [key, value] of Object.entries(obj)) {
95
+ //console.log("wrapping", key, value)
96
+ let wrappedValue = wrap(vm, value, obj, beginAsyncProcess, endAsyncProcess);
97
+ if (wrappedValue !== null) {
98
+ vm.setProp(vmObject, key, wrappedValue);
99
+ if (wrappedValue != vm.undefined && wrappedValue != vm.null && wrappedValue.dispose) {
100
+ wrappedValue.dispose();
101
+ }
102
+ }
103
+ }
104
+ return vmObject;
105
+ }
106
+ exports.wrapObject = wrapObject;
107
+ function wrapObjectWhitelist(vm, obj, whitelist, beginAsyncProcess, endAsyncProcess) {
108
+ const vmObject = vm.newObject();
109
+ whitelist.forEach((key) => {
110
+ const value = obj[key];
111
+ //console.log("wrapping", key, value)
112
+ const wrappedValue = wrap(vm, value, obj, beginAsyncProcess, endAsyncProcess);
113
+ if (wrappedValue !== null) {
114
+ vm.setProp(vmObject, key, wrappedValue);
115
+ if (wrappedValue != vm.undefined && wrappedValue != vm.null && wrappedValue.dispose) {
116
+ wrappedValue.dispose();
117
+ }
118
+ }
119
+ });
120
+ return vmObject;
121
+ }
122
+ exports.wrapObjectWhitelist = wrapObjectWhitelist;
123
+ function wrapArray(vm, arr, beginAsyncProcess, endAsyncProcess) {
124
+ const vmArray = vm.newArray();
125
+ for (let i = 0; i < arr.length; i++) {
126
+ let wrappedValue = wrap(vm, arr[i], arr, beginAsyncProcess, endAsyncProcess);
127
+ vm.setProp(vmArray, i, wrappedValue);
128
+ if (wrappedValue != vm.undefined && wrappedValue != vm.null) {
129
+ wrappedValue.dispose();
130
+ }
131
+ }
132
+ return vmArray;
133
+ }
134
+ exports.wrapArray = wrapArray;
135
+ function wrapGenericFunction(vm, fn, context = undefined, beginAsyncProcess, endAsyncProcess) {
136
+ const vmFn = vm.newFunction(fn.name, (...args) => {
137
+ const unwrappedArgs = args.map(vm.dump);
138
+ //console.log("Wrapped function called", fn.name, unwrappedArgs )
139
+ try {
140
+ const result = fn.call(context, ...unwrappedArgs);
141
+ //console.log("result", result);
142
+ const wrappedResult = wrap(vm, result, undefined, beginAsyncProcess, endAsyncProcess);
143
+ return wrappedResult;
144
+ }
145
+ catch (e) {
146
+ console.log("Error", e);
147
+ }
148
+ });
149
+ return vmFn;
150
+ }
151
+ exports.wrapGenericFunction = wrapGenericFunction;
152
+ function injectTimingFunctions(vm, cbAddedTimer, cbRemovedTimer, maxTimeout = 600000) {
153
+ // Should we impose a limit on how long things can run for?
154
+ const timeoutFunctionHandles = {};
155
+ const _setTimeout = vm.newFunction("setTimeout", (vmFnHandle, timeoutHandle) => {
156
+ // Make a copy because otherwise vmFnHandle does not live long enough to call after the timeout
157
+ const vmFnHandleCopy = vmFnHandle.dup();
158
+ let timeout = vm.dump(timeoutHandle);
159
+ // cap timeout at max timeout
160
+ if (timeout > maxTimeout) {
161
+ timeout = maxTimeout;
162
+ }
163
+ // console.log("Sandbox calling timeout for: " + timeout);
164
+ cbAddedTimer();
165
+ const timeoutID = setTimeout(() => {
166
+ timeoutFunctionHandles[timeoutID.toString()] = null;
167
+ // callFunction(vmFnHandleCopy) will call the vm function
168
+ // in the context of the vm
169
+ // we pass vm.undefined because we need to pass something for the "this" argument
170
+ //console.log("Sandbox called timeout function")
171
+ cbRemovedTimer();
172
+ vm.callFunction(vmFnHandleCopy, vm.undefined);
173
+ vmFnHandleCopy.dispose();
174
+ vm.runtime.executePendingJobs();
175
+ }, timeout);
176
+ timeoutFunctionHandles[timeoutID.toString()] = vmFnHandleCopy;
177
+ return vm.newNumber(timeoutID);
178
+ });
179
+ vm.setProp(vm.global, "setTimeout", _setTimeout);
180
+ _setTimeout.dispose();
181
+ const intervalFunctionHandles = {};
182
+ const _setInterval = vm.newFunction("setInterval", (vmFnHandle, timeoutHandle) => {
183
+ // Make a copy because otherwise vmFnHandle does not live long enough to call after the timeout
184
+ const vmFnHandleCopy = vmFnHandle.dup();
185
+ let timeout = vm.dump(timeoutHandle);
186
+ // cap timeout at max timeout
187
+ if (timeout > maxTimeout) {
188
+ timeout = maxTimeout;
189
+ }
190
+ //console.log("Sandbox calling interval for: " + timeout);
191
+ const maxRepetitions = 99;
192
+ let repetitions = 0;
193
+ cbAddedTimer();
194
+ const intervalId = setInterval(() => {
195
+ repetitions += 1;
196
+ intervalFunctionHandles[intervalId.toString()] = null;
197
+ // callFunction(vmFnHandleCopy) will call the vm function
198
+ // in the context of the vm
199
+ // we pass vm.undefined because we need to pass something for the "this" argument
200
+ vm.callFunction(vmFnHandleCopy, vm.undefined);
201
+ vm.runtime.executePendingJobs();
202
+ if (repetitions > maxRepetitions) {
203
+ console.log("Sandbox interval exceeded max repetitions");
204
+ clearInterval(intervalId);
205
+ cbRemovedTimer();
206
+ return;
207
+ }
208
+ }, timeout);
209
+ intervalFunctionHandles[intervalId.toString()] = vmFnHandleCopy;
210
+ return vm.newNumber(intervalId);
211
+ });
212
+ vm.setProp(vm.global, "setInterval", _setInterval);
213
+ _setInterval.dispose();
214
+ const _clearTimeout = vm.newFunction("clearTimeout", (timeoutIdHandle) => {
215
+ const timeoutId = vm.dump(timeoutIdHandle);
216
+ const timeoutHandle = timeoutFunctionHandles[timeoutId.toString()];
217
+ if (timeoutHandle) {
218
+ cbRemovedTimer();
219
+ timeoutHandle.dispose();
220
+ clearTimeout(timeoutId);
221
+ }
222
+ });
223
+ vm.setProp(vm.global, "clearTimeout", _clearTimeout);
224
+ _clearTimeout.dispose();
225
+ const _clearInterval = vm.newFunction("clearInterval", (intervalIdHandle) => {
226
+ const intervalId = vm.dump(intervalIdHandle);
227
+ const intervalHandle = intervalFunctionHandles[intervalId.toString()];
228
+ if (intervalId) {
229
+ cbRemovedTimer();
230
+ intervalHandle.dispose();
231
+ clearInterval(intervalId);
232
+ }
233
+ });
234
+ vm.setProp(vm.global, "clearInterval", _clearInterval);
235
+ _clearInterval.dispose();
236
+ }
237
+ exports.injectTimingFunctions = injectTimingFunctions;