aimyapi 1.0.8 → 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.
@@ -0,0 +1,37 @@
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 api:
12
+
13
+ `api.ts`:
14
+ ```ts
15
+ {{API}}
16
+ ```
17
+
18
+ ## Instructions
19
+
20
+ Use the global object defined above to make calls to the API. The API runs in a development environment using mocked data.
21
+
22
+ The provided tools will execute code in a sandbox and you will only have access to the API provided above and the standard javascript api's.
23
+
24
+ You may use top level `await` since your code will be wrapped in an async function before being run.
25
+ You should write safe code and catch exceptions. For errors, use `console.error` with a helpful message.
26
+ Avoid logging PII or sensitive information.
27
+
28
+ You may use `runCode` to experiment or test. Console logs will be returned with the tool results, including 'traces' which show calls to the API.
29
+
30
+ When finished, you must call `submitCode` to check and run the code. Examine the log traces that get returned to confirm that the expected calls were made and the code is running as expected. If not, you may call submit code again. Once finished, respond with "Successfully submitted code".
31
+
32
+ ---
33
+
34
+ {{DOCUMENTATION}}
35
+
36
+ {{CONTEXT}}
37
+
package/src/index.ts CHANGED
@@ -2,16 +2,17 @@ require('dotenv').config()
2
2
  import fs from 'fs';
3
3
  import { OpenAI } from "openai";
4
4
 
5
+ import { LogSource, SandboxLog } from "./logger";
5
6
  import createSandbox, { CodeRunResult } from "./sandbox";
6
7
  import { rejectOpenPromises } from './sandbox-wrappers';
7
8
 
8
9
  import { ChatCompletionMessageParam } from 'openai/resources/index.mjs';
9
- import { QuickJSWASMModule, getQuickJS } from "quickjs-emscripten";
10
+ import { getQuickJS, QuickJSWASMModule } from "quickjs-emscripten";
10
11
 
11
12
  import { AutoParseableTool } from 'openai/lib/parser.js';
12
- import { ZodSchema, z } from 'zod';
13
- import { zodToJsonSchema } from 'zod-to-json-schema';
14
- import { check } from 'zod/v4';
13
+ import { z } from 'zod';
14
+
15
+ import path from 'path';
15
16
 
16
17
  const openai = new OpenAI( { baseURL: process.env.OPENAI_API_BASE_URL || undefined, apiKey: process.env.OPENAI_API_KEY || undefined } );
17
18
 
@@ -46,14 +47,13 @@ export interface GenerateCodeResult {
46
47
  comments?: string;
47
48
  }
48
49
 
50
+ function createLog( message: string, source: LogSource) {
51
+ return { type: "log", args: [message], source } as SandboxLog
52
+ }
49
53
 
50
54
  const wrapCode = (apiGlobalName:string, apiFileDefPath:string, code:string) => `
51
55
  (async () => {
52
- try {
53
- ${code}
54
- } catch(err) {
55
- console.error(err);
56
- }
56
+ ${code}
57
57
  })();
58
58
  `;
59
59
 
@@ -63,7 +63,7 @@ ${code}
63
63
  \`\`\`
64
64
  `;
65
65
 
66
- export function zodParseJSON<T>(schema: ZodSchema<T>) {
66
+ export function zodParseJSON<T>(schema: { parse: any }) {
67
67
  return (input: string): T => schema.parse(JSON.parse(input));
68
68
  }
69
69
 
@@ -72,7 +72,7 @@ export function createBasePrompt(apiFilePath:string, apiGlobalName: string, docu
72
72
  const documentationText: string = documentationPath ? fs.readFileSync(documentationPath, 'utf8') : '';
73
73
 
74
74
  //console.time("Loading templates");
75
- const createTaskPrompt: string = fs.readFileSync( './prompts/create-task-prompt.md', 'utf8').replace("{{DOCUMENTATION}}", documentationText).replace("{{API}}", apiText).replace("{{API_GLOBAL_NAME}}", apiGlobalName || "");
75
+ const createTaskPrompt: string = fs.readFileSync( path.resolve(__dirname, '../prompts/generate-code-prompt.md'), 'utf8').replace("{{DOCUMENTATION}}", documentationText).replace("{{API}}", apiText).replace("{{API_GLOBAL_NAME}}", apiGlobalName || "");
76
76
  //console.timeEnd("Loading templates")
77
77
 
78
78
  return createTaskPrompt;
@@ -99,7 +99,7 @@ export const generateCode = async function(instance:AIMyAPIInstance, queryText:s
99
99
  console.log("Generating code for query:", queryText);
100
100
  }
101
101
 
102
- const newHistoryItems = [];
102
+ const newHistoryItems: any[] = [];
103
103
  // checkcode
104
104
  const codingTools: AutoParseableTool<any, true>[] = [
105
105
  {
@@ -111,7 +111,15 @@ export const generateCode = async function(instance:AIMyAPIInstance, queryText:s
111
111
  if(debug) {
112
112
  console.log("Running code...", code);
113
113
  }
114
-
114
+ const checkRes = await instance.checkCode(code);
115
+ if(!checkRes.success) {
116
+ return {
117
+ success: false,
118
+ reason: "Code check failed",
119
+ logs: checkRes.logs,
120
+ }
121
+ }
122
+
115
123
  const res = await instance.runCode(code);
116
124
 
117
125
  if(hideLogsFromAgent) {
@@ -119,30 +127,30 @@ export const generateCode = async function(instance:AIMyAPIInstance, queryText:s
119
127
  }
120
128
  return res;
121
129
  },
122
- description: "Run the provided code in a sandboxed environment. The code will have access to the API object. Returns the result of the code execution.",
130
+ description: "Check and run the provided code in a sandboxed environment. The code will have access to the API object. Returns the result of the code execution. Use this for testing and experimentation.",
123
131
  parse: zodParseJSON(runCodeSchema),
124
- parameters: zodToJsonSchema(runCodeSchema),
132
+ parameters: runCodeSchema.toJSONSchema(),
125
133
  }
126
134
  },
127
- {
128
- type: "function",
129
- function: {
130
- name: "checkCode",
131
- // @ts-ignore
132
- function: async ({code}: {code: string}) => {
133
- if(debug) {
134
- console.log("Checking code...", code);
135
- }
136
- const res = await instance.checkCode(code);
135
+ // {
136
+ // type: "function",
137
+ // function: {
138
+ // name: "checkCode",
139
+ // // @ts-ignore
140
+ // function: async ({code}: {code: string}) => {
141
+ // if(debug) {
142
+ // console.log("Checking code...", code);
143
+ // }
144
+ // const res = await instance.checkCode(code);
137
145
 
138
- return res;
139
-
140
- },
141
- description: "Check the provided code for errors or issues without running it. Returns the result of the code check.",
142
- parse: zodParseJSON(checkCodeSchema),
143
- parameters: zodToJsonSchema(checkCodeSchema),
144
- }
145
- },
146
+ // return res;
147
+
148
+ // },
149
+ // description: "Check the provided code for errors or issues without running it. Returns the result of the code check.",
150
+ // parse: zodParseJSON(checkCodeSchema),
151
+ // parameters: checkCodeSchema.toJSONSchema(),
152
+ // }
153
+ // },
146
154
  {
147
155
  type: "function",
148
156
  function: {
@@ -158,14 +166,21 @@ export const generateCode = async function(instance:AIMyAPIInstance, queryText:s
158
166
  }
159
167
  }
160
168
 
161
- finalSubmittedCode = code;
162
- return {
163
- success: true,
169
+ const res = await instance.runCode(code);
170
+
171
+ if(hideLogsFromAgent) {
172
+ res.logs = [];
173
+ }
174
+
175
+ if(res.success) {
176
+ finalSubmittedCode = code;
164
177
  }
178
+ return res;
179
+
165
180
  },
166
- description: "Submit the provided code as the final code to run. This will end the code generation process and the code will be run in the sandbox. Use this when you are confident in the code you have generated and want to run it.",
181
+ description: "Submit the provided code as a candidate for the final code. The code will be checked and run and output logs and traces will be provided. Check the logs, if satisifed, respond to the user otherwise submit again.",
167
182
  parse: zodParseJSON(submitCodeSchema),
168
- parameters: zodToJsonSchema(submitCodeSchema),
183
+ parameters: submitCodeSchema.toJSONSchema(),
169
184
  }
170
185
  }
171
186
  ];
@@ -193,22 +208,23 @@ export const generateCode = async function(instance:AIMyAPIInstance, queryText:s
193
208
  })
194
209
  .on('message', async (message: ChatCompletionMessageParam) => {
195
210
  if(debug) {
196
- console.log("Message:", JSON.stringify(message, null, 2));
211
+ if("reasoning_content" in message) {
212
+ console.log(message.reasoning_content);
213
+ }
214
+ //console.log("Message:", JSON.stringify(message, null, 2));
197
215
  }
198
216
 
199
217
  newHistoryItems.push(message);
200
218
  })
201
219
  .on('functionToolCall', (functionCall: OpenAI.Chat.Completions.ChatCompletionMessageFunctionToolCall.Function) => {
202
- if(debug) {
203
- console.log("Function Call:", JSON.stringify(functionCall, null, 2));
204
- }
205
-
206
-
220
+ // if(debug) {
221
+ // console.log("Function Call:", JSON.stringify(functionCall, null, 2));
222
+ // }
207
223
  })
208
224
  .on('functionToolCallResult', (functionCall) => {
209
- if(debug) {
210
- console.log("Function Call Result:", JSON.stringify(functionCall, null, 2));
211
- }
225
+ // if(debug) {
226
+ // console.log("Function Call Result:", JSON.stringify(functionCall, null, 2));
227
+ // }
212
228
  })
213
229
 
214
230
  const finalContent = await runner.finalContent();
@@ -333,7 +349,8 @@ async function createWithAPI(options:AIMyAPIOptions): Promise<AIMyAPIInstance> {
333
349
  console.error("Unknown failure checking code");
334
350
  return {
335
351
  success: false,
336
- logs: [],
352
+ logs: [createLog("Unknown failure checking code", "compiler")],
353
+
337
354
  };
338
355
  }
339
356
 
@@ -386,7 +403,7 @@ async function createWithAPI(options:AIMyAPIOptions): Promise<AIMyAPIInstance> {
386
403
 
387
404
  // Create sandbox
388
405
  // NOTE: not sure why I have to replace the backslash here
389
- const {vm, runCode: runTask, isAsyncProcessRunning} = await createSandbox(QuickJS, apiExports ? {
406
+ const {vm, runCode: sandbox_runCode, isAsyncProcessRunning} = await createSandbox(QuickJS, apiExports ? {
390
407
  [apiDefFilePath]: apiExports,
391
408
  } : undefined, {
392
409
  ...apiGlobals,
@@ -401,7 +418,7 @@ async function createWithAPI(options:AIMyAPIOptions): Promise<AIMyAPIInstance> {
401
418
  console.log("Running Code:\n", code);
402
419
  }
403
420
 
404
- const res = await runTask(wrapCode(apiGlobalName, apiDefFilePath, code));
421
+ const res = await sandbox_runCode(wrapCode(apiGlobalName, apiDefFilePath, code));
405
422
 
406
423
  if(!res) {
407
424
  console.error("Unknown failure running task");
@@ -441,29 +458,36 @@ async function createWithAPI(options:AIMyAPIOptions): Promise<AIMyAPIInstance> {
441
458
  // Create prompt
442
459
 
443
460
  if(debug) {
444
- console.log("Query:", userQuery);
461
+ console.log("[debug] Query:", userQuery);
445
462
  }
446
463
 
447
464
  // Generate Code
448
465
  console.time("Generate Task")
466
+ const prompt = createTaskPrompt.replace("{{CONTEXT}}", currentContext ? JSON.stringify(currentContext, null, 2) : "" );
449
467
  const generatedCode:GenerateCodeResult | null = await generateCode(instance,
450
468
  userQuery,
451
469
  [],
452
- createTaskPrompt.replace("{{CONTEXT}}", currentContext ? JSON.stringify(currentContext, null, 2) : "" ),
470
+ prompt,
453
471
  debug,
454
472
  hideLogsFromAgent,
455
473
  model,
456
474
  additionalModelOptions
457
475
  );
458
476
  console.timeEnd("Generate Task")
477
+
478
+ console.log(`generated code result\nSuccess?: ${generatedCode?.success}\nComments: ${generatedCode?.comments}\nRunning Code...`);
459
479
 
460
- console.time("Run Code")
480
+
481
+ console.time("Run Code took")
461
482
  if(generatedCode?.code) {
462
- await instance.runCode(generatedCode.code);
483
+ const codeRunResult = await instance.runCode(generatedCode.code);
484
+ if(!codeRunResult.success) {
485
+ console.error("Error running generated code");
486
+ }
463
487
  } else {
464
488
  console.error("No code generated", generatedCode);
465
489
  }
466
- console.timeEnd("Run Code")
490
+ console.timeEnd("Run Code took")
467
491
 
468
492
  return generatedCode;
469
493
  }
@@ -0,0 +1,233 @@
1
+ type LogSource = "sandbox" | "compiler" | "runner";
2
+ type LogEntryType = "log" | "error" | "trace";
3
+
4
+ type SandboxLog = {
5
+ type: "log" | "error";
6
+ args: any[];
7
+ source: LogSource
8
+ } | {
9
+ type: "trace";
10
+ fnName: string,
11
+ fnArgs: any[],
12
+ result: any
13
+ };
14
+
15
+ const has = Object.prototype.hasOwnProperty;
16
+
17
+ function dequal(foo: any, bar: any) {
18
+ let ctor, len;
19
+ if (foo === bar) return true;
20
+
21
+ if (foo && bar && (ctor=foo.constructor) === bar.constructor) {
22
+ if (ctor === Date) return foo.getTime() === bar.getTime();
23
+ if (ctor === RegExp) return foo.toString() === bar.toString();
24
+
25
+ if (ctor === Array) {
26
+ if ((len=foo.length) === bar.length) {
27
+ while (len-- && dequal(foo[len], bar[len]));
28
+ }
29
+ return len === -1;
30
+ }
31
+
32
+ if (!ctor || typeof foo === 'object') {
33
+ len = 0;
34
+ for (ctor in foo) {
35
+ if (has.call(foo, ctor) && ++len && !has.call(bar, ctor)) return false;
36
+ if (!(ctor in bar) || !dequal(foo[ctor], bar[ctor])) return false;
37
+ }
38
+ return Object.keys(bar).length === len;
39
+ }
40
+ }
41
+
42
+ return foo !== foo && bar !== bar;
43
+ }
44
+
45
+ /** Return true to pass. Returning a falsy value or throwing fails the check. */
46
+ type ArgumentsEvaluator = (args: any[]) => any;
47
+ /** Return true to pass. Returning a falsy value or throwing fails the check. */
48
+ type ResultEvaluator = (result: any) => any;
49
+
50
+ /**
51
+ * One step in a test sequence. Gets the logs and the current position, checks
52
+ * the entries there, and returns the position of the next unchecked entry.
53
+ * Throws TestFailed if the check fails.
54
+ */
55
+ type TestStep = (logs: SandboxLog[], index: number) => number;
56
+
57
+ class TestFailed extends Error {
58
+ constructor(message: string) {
59
+ super(message);
60
+ this.name = "TestFailed";
61
+ }
62
+ }
63
+
64
+ function describeEntry(entry: SandboxLog | undefined): string {
65
+ if (!entry) return "end of logs";
66
+ const short = (v: any) => JSON.stringify(v);
67
+ if (entry.type === "trace") {
68
+ return `trace ${entry.fnName}(${entry.fnArgs.map(short).join(", ")}) -> ${short(entry.result)}`;
69
+ }
70
+ return `${entry.type} [${entry.source}] ${entry.args.map(short).join(" ")}`;
71
+ }
72
+
73
+ function nameMatches(pattern: string, name: string): boolean {
74
+ return pattern === "*" || pattern === name;
75
+ }
76
+
77
+ /** Runs an evaluator and throws TestFailed if it returns falsy or throws. */
78
+ function evaluate(evaluator: ((value: any) => any) | undefined, value: any, what: string, index: number, entry: SandboxLog) {
79
+ if (!evaluator) return;
80
+ let passed: any;
81
+ try {
82
+ passed = evaluator(value);
83
+ } catch (e: any) {
84
+ throw new TestFailed(`Log #${index}: ${what} check threw: ${e?.message ?? e}. Entry: ${describeEntry(entry)}`);
85
+ }
86
+ if (!passed) {
87
+ throw new TestFailed(`Log #${index}: ${what} check failed. Entry: ${describeEntry(entry)}`);
88
+ }
89
+ }
90
+
91
+ function checkTrace(logs: SandboxLog[], index: number, functionName: string, argumentsEvaluator?: ArgumentsEvaluator, resultEvaluator?: ResultEvaluator) {
92
+ const entry = logs[index];
93
+ if (!entry || entry.type !== "trace" || !nameMatches(functionName, entry.fnName)) {
94
+ throw new TestFailed(`Log #${index}: expected trace for "${functionName}", got ${describeEntry(entry)}`);
95
+ }
96
+ evaluate(argumentsEvaluator, entry.fnArgs, `arguments of "${entry.fnName}"`, index, entry);
97
+ evaluate(resultEvaluator, entry.result, `result of "${entry.fnName}"`, index, entry);
98
+ }
99
+
100
+ function expectEntryType(type: "log" | "error", argumentsEvaluator?: ArgumentsEvaluator): TestStep {
101
+ return (logs, index) => {
102
+ const entry = logs[index];
103
+ if (!entry || entry.type !== type) {
104
+ throw new TestFailed(`Log #${index}: expected ${type}, got ${describeEntry(entry)}`);
105
+ }
106
+ evaluate(argumentsEvaluator, entry.args, `${type} arguments`, index, entry);
107
+ return index + 1;
108
+ };
109
+ }
110
+
111
+ /**
112
+ * Checks the logs from a sandbox run against an expected sequence of entries.
113
+ *
114
+ * @example
115
+ * test.sequence(
116
+ * test.expectTrace("getSalesData"),
117
+ * test.expectTrace("display", (args) => args[0].includes("The month with the highest sales was")),
118
+ * );
119
+ */
120
+ class LogTester {
121
+ private source: SandboxLog[];
122
+
123
+ constructor(source: SandboxLog[]) {
124
+ this.source = source;
125
+ }
126
+
127
+ private get logs(): SandboxLog[] {
128
+ return this.source;
129
+ }
130
+
131
+ /**
132
+ * Runs the steps in order, starting at the first log entry. Throws TestFailed
133
+ * on the first failing step. Entries after the last step are ignored.
134
+ * Accepts steps as separate arguments or as one array.
135
+ */
136
+ sequence(...steps: (TestStep | TestStep[])[]): true {
137
+ const logs = this.logs;
138
+ let index = 0;
139
+ for (const step of steps.flat()) {
140
+ if (typeof step !== "function") {
141
+ throw new TestFailed(`sequence() expects test steps (e.g. test.expectTrace(...)), got ${typeof step}`);
142
+ }
143
+ index = step(logs, index);
144
+ }
145
+ return true;
146
+ }
147
+
148
+ /** Next entry must be a trace for `functionName` ("*" for any function). */
149
+ expectTrace(functionName: string, argumentsEvaluator?: ArgumentsEvaluator, resultEvaluator?: ResultEvaluator): TestStep {
150
+ return (logs, index) => {
151
+ checkTrace(logs, index, functionName, argumentsEvaluator, resultEvaluator);
152
+ return index + 1;
153
+ };
154
+ }
155
+
156
+ /** Next entry can be anything, but there must be one. */
157
+ expectAny(): TestStep {
158
+ return (logs, index) => {
159
+ if (index >= logs.length) {
160
+ throw new TestFailed(`Log #${index}: expected any entry, got end of logs`);
161
+ }
162
+ return index + 1;
163
+ };
164
+ }
165
+
166
+ /** Skips ahead to just before the next trace for `functionName` ("*" for any). Fails if there is none. */
167
+ skipToTrace(functionName: string): TestStep {
168
+ return (logs, index) => {
169
+ for (let i = index; i < logs.length; i++) {
170
+ const entry = logs[i];
171
+ if (entry.type === "trace" && nameMatches(functionName, entry.fnName)) {
172
+ return i;
173
+ }
174
+ }
175
+ throw new TestFailed(`Log #${index}: no trace for "${functionName}" found from here to the end of the logs`);
176
+ };
177
+ }
178
+
179
+ /**
180
+ * Consecutive traces for `functionName` ("*" for any).
181
+ * With `num` > 0, exactly that many must be next (more are left for later steps).
182
+ * With `num` 0 or undefined, takes as many as are next, including none.
183
+ * The evaluators are checked against every matched trace.
184
+ */
185
+ expectMultipleTraces(functionName: string, num?: number, argumentsEvaluator?: ArgumentsEvaluator, resultEvaluator?: ResultEvaluator): TestStep {
186
+ return (logs, index) => {
187
+ if (num && num > 0) {
188
+ for (let n = 0; n < num; n++) {
189
+ const entry = logs[index + n];
190
+ if (!entry || entry.type !== "trace" || !nameMatches(functionName, entry.fnName)) {
191
+ throw new TestFailed(`Log #${index + n}: expected ${num} traces for "${functionName}" but found ${n}. Got ${describeEntry(entry)}`);
192
+ }
193
+ checkTrace(logs, index + n, functionName, argumentsEvaluator, resultEvaluator);
194
+ }
195
+ return index + num;
196
+ }
197
+
198
+ let i = index;
199
+ while (i < logs.length) {
200
+ const entry = logs[i];
201
+ if (entry.type !== "trace" || !nameMatches(functionName, entry.fnName)) break;
202
+ checkTrace(logs, i, functionName, argumentsEvaluator, resultEvaluator);
203
+ i++;
204
+ }
205
+ return i;
206
+ };
207
+ }
208
+
209
+ /** Next entry must be a log (console.log etc). The evaluator gets the logged arguments. */
210
+ expectLog(argumentsEvaluator?: ArgumentsEvaluator): TestStep {
211
+ return expectEntryType("log", argumentsEvaluator);
212
+ }
213
+
214
+ /** Next entry must be an error. The evaluator gets the error arguments. */
215
+ expectError(argumentsEvaluator?: ArgumentsEvaluator): TestStep {
216
+ return expectEntryType("error", argumentsEvaluator);
217
+ }
218
+
219
+ /** Deep equality. Returns true if equal, otherwise throws TestFailed, so it works inside evaluators. */
220
+ equals(actual: any, expected: any): true {
221
+ if (!dequal(actual, expected)) {
222
+ throw new TestFailed(`Expected ${JSON.stringify(expected)}, got ${JSON.stringify(actual)}`);
223
+ }
224
+ return true;
225
+ }
226
+ }
227
+
228
+
229
+ // const test = new LogTester(logs);
230
+ // test.sequence(
231
+ // test.expectTrace("display", (args) => args[0][0].sales == 100)
232
+ // )
233
+ // console.log("success");