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.ts
CHANGED
|
@@ -1,277 +1,254 @@
|
|
|
1
|
-
import "quickjs-emscripten";
|
|
2
|
-
|
|
3
|
-
import { QuickJSWASMModule } from "quickjs-emscripten";
|
|
4
|
-
import * as ts from "typescript";
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
export type
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
let
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
const
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
logger.error(["
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
return {
|
|
257
|
-
success: !errorState,
|
|
258
|
-
logs: logger.logs,
|
|
259
|
-
};
|
|
260
|
-
} else {
|
|
261
|
-
logger.error(["Unknown execution result:", result], "runner");
|
|
262
|
-
return {
|
|
263
|
-
success: false,
|
|
264
|
-
logs: logger.logs,
|
|
265
|
-
};
|
|
266
|
-
}
|
|
267
|
-
} catch (e) {
|
|
268
|
-
logger.error(["Error running code:", e], "runner");
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
return {
|
|
272
|
-
success: false,
|
|
273
|
-
logs: logger.logs,
|
|
274
|
-
};
|
|
275
|
-
},
|
|
276
|
-
};
|
|
277
|
-
}
|
|
1
|
+
import "quickjs-emscripten";
|
|
2
|
+
|
|
3
|
+
import { QuickJSWASMModule } from "quickjs-emscripten";
|
|
4
|
+
import * as ts from "typescript";
|
|
5
|
+
import { Logger, SandboxLog } from "./logger";
|
|
6
|
+
import {
|
|
7
|
+
injectTimingFunctions,
|
|
8
|
+
wrap,
|
|
9
|
+
wrapObject,
|
|
10
|
+
wrapObjectWhitelist,
|
|
11
|
+
} from "./sandbox-wrappers";
|
|
12
|
+
|
|
13
|
+
// Should we use "noEmitOnError": true ?
|
|
14
|
+
function tsCompile(
|
|
15
|
+
source: string,
|
|
16
|
+
options?: ts.TranspileOptions
|
|
17
|
+
): string {
|
|
18
|
+
// Default options -- you could also perform a merge, or use the project tsconfig.json
|
|
19
|
+
if (!options) {
|
|
20
|
+
options = {
|
|
21
|
+
compilerOptions: {
|
|
22
|
+
target: ts.ScriptTarget.ES2022,
|
|
23
|
+
module: ts.ModuleKind.None,
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
return ts.transpileModule(source, options).outputText;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface GlobalOption {
|
|
31
|
+
value: any;
|
|
32
|
+
whitelist?: string[];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface Globals {
|
|
36
|
+
[key: string]: GlobalOption;
|
|
37
|
+
}
|
|
38
|
+
export type CodeRunResult = {
|
|
39
|
+
success: boolean;
|
|
40
|
+
logs: SandboxLog[];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
// function whitelist can be produced using something like:
|
|
45
|
+
// const assistantFunctions = Object.getOwnPropertyNames(Object.getPrototypeOf(apiObject))
|
|
46
|
+
// .filter((f) => f !== "constructor" && !f.startsWith("_"));
|
|
47
|
+
|
|
48
|
+
// Is there some way we can treat the API and globals the same?
|
|
49
|
+
// We could just provide an object for all globals (including the API) and also have the whitelist as a parameter..
|
|
50
|
+
export default async function createSandbox(QuickJS: QuickJSWASMModule, requireLookup:any = {}, globals: Globals = {}, debug:boolean = false) {
|
|
51
|
+
const vm = QuickJS.newContext();
|
|
52
|
+
|
|
53
|
+
let errorState = false;
|
|
54
|
+
let lastError = "";
|
|
55
|
+
|
|
56
|
+
let asyncProcessesRunning = 0;
|
|
57
|
+
|
|
58
|
+
let beginAsyncProcess = () => {
|
|
59
|
+
asyncProcessesRunning++;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
let endAsyncProcess = () => {
|
|
63
|
+
if (asyncProcessesRunning > 0) {
|
|
64
|
+
asyncProcessesRunning--;
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
let isAsyncProcessRunning = () => {
|
|
69
|
+
//console.log("asyncProcessesRunning:", asyncProcessesRunning);
|
|
70
|
+
return asyncProcessesRunning > 0;
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
const logger = new Logger(debug);
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
// const assistantHandle = wrapObjectWhitelist(vm, apiObject, functionWhitelist, beginAsyncProcess, endAsyncProcess);
|
|
78
|
+
// vm.setProp(vm.global, apiGlobalName, assistantHandle)
|
|
79
|
+
|
|
80
|
+
// assistantHandle.dispose();
|
|
81
|
+
|
|
82
|
+
const logHandle = vm.newFunction("log", (...args:any) => {
|
|
83
|
+
const nativeArgs = args.map(vm.dump);
|
|
84
|
+
logger.log(nativeArgs, "sandbox");
|
|
85
|
+
});
|
|
86
|
+
const consoleHandle = vm.newObject();
|
|
87
|
+
const exportsHandle = vm.newObject();
|
|
88
|
+
|
|
89
|
+
const errorHandle = vm.newFunction("error", (...args:any) => {
|
|
90
|
+
try {
|
|
91
|
+
const nativeArgs = args.map(vm.dump);
|
|
92
|
+
|
|
93
|
+
logger.error(nativeArgs, "sandbox");
|
|
94
|
+
lastError = JSON.stringify(nativeArgs);
|
|
95
|
+
errorState = true;
|
|
96
|
+
} catch (e) {
|
|
97
|
+
logger.error(["? Error in error:", e], "sandbox");
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
const globalNames = Object.getOwnPropertyNames(globals);
|
|
102
|
+
for (let i = 0; i < globalNames.length; i++) {
|
|
103
|
+
const globalOption: GlobalOption = globals[globalNames[i]];
|
|
104
|
+
const globalObj = globalOption.whitelist
|
|
105
|
+
? wrapObjectWhitelist(
|
|
106
|
+
vm,
|
|
107
|
+
globalOption.value,
|
|
108
|
+
globalOption.whitelist,
|
|
109
|
+
beginAsyncProcess,
|
|
110
|
+
endAsyncProcess,
|
|
111
|
+
logger
|
|
112
|
+
)
|
|
113
|
+
: wrap(vm, globalOption.value, globalOption, beginAsyncProcess, endAsyncProcess, logger);
|
|
114
|
+
|
|
115
|
+
vm.setProp(vm.global, globalNames[i], globalObj);
|
|
116
|
+
globalObj.dispose();
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
injectTimingFunctions(vm, beginAsyncProcess, endAsyncProcess);
|
|
120
|
+
|
|
121
|
+
const requireHandle = vm.newFunction("require", (...args) => {
|
|
122
|
+
const nativeArgs = args.map(vm.dump);
|
|
123
|
+
if(requireLookup[nativeArgs[0] as string]) {
|
|
124
|
+
const returnObj = wrapObject(vm, requireLookup[nativeArgs[0] as string], beginAsyncProcess, endAsyncProcess, logger);
|
|
125
|
+
return returnObj;
|
|
126
|
+
} else {
|
|
127
|
+
return vm.undefined;
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
vm.setProp(consoleHandle, "log", logHandle);
|
|
132
|
+
vm.setProp(consoleHandle, "error", errorHandle);
|
|
133
|
+
vm.setProp(vm.global, "require", requireHandle);
|
|
134
|
+
vm.setProp(vm.global, "console", consoleHandle);
|
|
135
|
+
vm.setProp(vm.global, "exports", exportsHandle);
|
|
136
|
+
|
|
137
|
+
consoleHandle.dispose();
|
|
138
|
+
logHandle.dispose();
|
|
139
|
+
exportsHandle.dispose();
|
|
140
|
+
errorHandle.dispose();
|
|
141
|
+
requireHandle.dispose();
|
|
142
|
+
|
|
143
|
+
return {
|
|
144
|
+
vm,
|
|
145
|
+
getLastError: () => {
|
|
146
|
+
return lastError;
|
|
147
|
+
},
|
|
148
|
+
isAsyncProcessRunning,
|
|
149
|
+
// make sure code passes the typescript compilation
|
|
150
|
+
checkCode: (code: string) => {
|
|
151
|
+
logger.clear();
|
|
152
|
+
try {
|
|
153
|
+
const compiled = tsCompile(code);
|
|
154
|
+
if (!compiled || compiled.length === 0) {
|
|
155
|
+
logger.error(["Could not compile:", code], "compiler");
|
|
156
|
+
return {
|
|
157
|
+
success: false,
|
|
158
|
+
logs: logger.getLogs(),
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
} catch (e) {
|
|
162
|
+
logger.error(["Error testing code:", e], "compiler");
|
|
163
|
+
return {
|
|
164
|
+
success: false,
|
|
165
|
+
logs: logger.getLogs(),
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return {
|
|
170
|
+
success: true,
|
|
171
|
+
logs: logger.getLogs(),
|
|
172
|
+
}
|
|
173
|
+
},
|
|
174
|
+
runCode: async (code: string): Promise<CodeRunResult> => {
|
|
175
|
+
try {
|
|
176
|
+
logger.clear();
|
|
177
|
+
const compiled = tsCompile(code);
|
|
178
|
+
|
|
179
|
+
if (!compiled || compiled.length === 0) {
|
|
180
|
+
logger.error(["Could not compile:", code], "compiler");
|
|
181
|
+
return {
|
|
182
|
+
success: false,
|
|
183
|
+
logs: logger.getLogs()
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// View compiled typescript code for debugging
|
|
188
|
+
// if(debug)
|
|
189
|
+
// console.log(compiled);
|
|
190
|
+
|
|
191
|
+
errorState = false;
|
|
192
|
+
const result = vm.evalCode(compiled);
|
|
193
|
+
|
|
194
|
+
if (result.error) {
|
|
195
|
+
// log out the compiled program with line numbers
|
|
196
|
+
const lines = compiled.split("\n").map((line, index) => `${index + 1}: ${line}`);
|
|
197
|
+
|
|
198
|
+
logger.error(["Execution failed:", lines.join("\n"), vm.dump(result.error)], "runner");
|
|
199
|
+
|
|
200
|
+
result.error.dispose();
|
|
201
|
+
return {
|
|
202
|
+
success: false,
|
|
203
|
+
logs: logger.getPrettyLogs(),
|
|
204
|
+
};
|
|
205
|
+
} else if("value" in result) {
|
|
206
|
+
//console.log("Task Result:", vm.dump(result.value), errorState);
|
|
207
|
+
// The wrapped code returns a promise; wait for it to settle.
|
|
208
|
+
const settled = vm.resolvePromise(result.value);
|
|
209
|
+
result.value.dispose();
|
|
210
|
+
vm.runtime.executePendingJobs(); // start the async function running
|
|
211
|
+
const promiseResult = await settled;
|
|
212
|
+
|
|
213
|
+
if (promiseResult.error) {
|
|
214
|
+
const err = vm.dump(promiseResult.error);
|
|
215
|
+
promiseResult.error.dispose();
|
|
216
|
+
const lines = compiled.split("\n").map((l, i) => `${i + 1}: ${l}`);
|
|
217
|
+
logger.error(["Uncaught exception:", err, "\n", lines.join("\n")], "runner");
|
|
218
|
+
return {
|
|
219
|
+
success: false,
|
|
220
|
+
logs: logger.getPrettyLogs(),
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
// @ts-ignore this is a success response
|
|
224
|
+
promiseResult.value.dispose();
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
if (errorState) {
|
|
229
|
+
const lines = compiled.split("\n").map((line, index) => `${index + 1}: ${line}`);
|
|
230
|
+
logger.error(["An error was reported during execution. Compiled code with line numbers:\n", lines.join("\n")], "runner");
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
return {
|
|
234
|
+
success: !errorState,
|
|
235
|
+
logs: logger.getPrettyLogs(),
|
|
236
|
+
};
|
|
237
|
+
} else {
|
|
238
|
+
logger.error(["Unknown execution result:", result], "runner");
|
|
239
|
+
return {
|
|
240
|
+
success: false,
|
|
241
|
+
logs: logger.getPrettyLogs(),
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
} catch (e) {
|
|
245
|
+
logger.error(["Error running code:", e], "runner");
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
return {
|
|
249
|
+
success: false,
|
|
250
|
+
logs: logger.getLogs(),
|
|
251
|
+
};
|
|
252
|
+
},
|
|
253
|
+
};
|
|
254
|
+
}
|
package/src/utils.ts
CHANGED
|
@@ -1,3 +1,33 @@
|
|
|
1
1
|
export function delay(time):Promise<void> {
|
|
2
2
|
return new Promise<void>(resolve => setTimeout(resolve, time));
|
|
3
|
-
}
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
const has = Object.prototype.hasOwnProperty;
|
|
6
|
+
|
|
7
|
+
export function dequal(foo: any, bar: any) {
|
|
8
|
+
let ctor, len;
|
|
9
|
+
if (foo === bar) return true;
|
|
10
|
+
|
|
11
|
+
if (foo && bar && (ctor=foo.constructor) === bar.constructor) {
|
|
12
|
+
if (ctor === Date) return foo.getTime() === bar.getTime();
|
|
13
|
+
if (ctor === RegExp) return foo.toString() === bar.toString();
|
|
14
|
+
|
|
15
|
+
if (ctor === Array) {
|
|
16
|
+
if ((len=foo.length) === bar.length) {
|
|
17
|
+
while (len-- && dequal(foo[len], bar[len]));
|
|
18
|
+
}
|
|
19
|
+
return len === -1;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (!ctor || typeof foo === 'object') {
|
|
23
|
+
len = 0;
|
|
24
|
+
for (ctor in foo) {
|
|
25
|
+
if (has.call(foo, ctor) && ++len && !has.call(bar, ctor)) return false;
|
|
26
|
+
if (!(ctor in bar) || !dequal(foo[ctor], bar[ctor])) return false;
|
|
27
|
+
}
|
|
28
|
+
return Object.keys(bar).length === len;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return foo !== foo && bar !== bar;
|
|
33
|
+
}
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
Most recent user query:
|
|
3
|
-
{{USER_QUERY}}
|
|
4
|
-
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
Based on the prior conversation, write a typescript script that performs the operation requested by the user and call the `submitCode` tool with the script.
|
|
8
|
-
|
|
9
|
-
## API documentation
|
|
10
|
-
|
|
11
|
-
You can make use of this global API object:
|
|
12
|
-
|
|
13
|
-
`api.ts`:
|
|
14
|
-
```typescript
|
|
15
|
-
{{API}}
|
|
16
|
-
```
|
|
17
|
-
|
|
18
|
-
## Instructions
|
|
19
|
-
|
|
20
|
-
Use the global api object to make calls to the API.
|
|
21
|
-
|
|
22
|
-
You may use top level `await`.
|
|
23
|
-
|
|
24
|
-
If needed, you may use `runCode` to run any non-destructive API calls to gather data for your script. Nothing will be visible to you unless you `console.log` it.
|
|
25
|
-
|
|
26
|
-
You may use the `checkCode` tool to confirm that your code passes typescript compilation and doesn't have errors before submitting.
|
|
27
|
-
|
|
28
|
-
In your code, `console.log` and `console.error` can be used to understand the execution or indicate that an error happened, but debug logs should be removed in the final version (errors can stay).
|
|
29
|
-
|
|
30
|
-
When finished, you must call `submitCode` to submit the final version of the code, then respond with "Successfully submitted code" on success. On failure, correct any errors and submit again.
|
|
31
|
-
|
|
32
|
-
---
|
|
33
|
-
|
|
34
|
-
{{DOCUMENTATION}}
|
|
35
|
-
|
|
36
|
-
{{CONTEXT}}
|
|
37
|
-
|