@empiricalrun/test-gen 0.52.1 → 0.52.2

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 (47) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/dist/agent/browsing/run.d.ts +3 -1
  3. package/dist/agent/browsing/run.d.ts.map +1 -1
  4. package/dist/agent/browsing/run.js +11 -6
  5. package/dist/agent/chat/index.d.ts +2 -1
  6. package/dist/agent/chat/index.d.ts.map +1 -1
  7. package/dist/agent/chat/index.js +15 -18
  8. package/dist/agent/chat/prompt.js +2 -2
  9. package/dist/agent/cua/computer.d.ts +4 -1
  10. package/dist/agent/cua/computer.d.ts.map +1 -1
  11. package/dist/agent/cua/computer.js +12 -2
  12. package/dist/agent/cua/index.d.ts +1 -3
  13. package/dist/agent/cua/index.d.ts.map +1 -1
  14. package/dist/agent/cua/index.js +75 -20
  15. package/dist/agent/cua/model.d.ts.map +1 -1
  16. package/dist/agent/cua/model.js +5 -2
  17. package/dist/bin/index.js +15 -2
  18. package/dist/bin/utils/index.d.ts +1 -0
  19. package/dist/bin/utils/index.d.ts.map +1 -1
  20. package/dist/file/client.d.ts +5 -4
  21. package/dist/file/client.d.ts.map +1 -1
  22. package/dist/file/client.js +10 -17
  23. package/dist/file/server.d.ts +6 -2
  24. package/dist/file/server.d.ts.map +1 -1
  25. package/dist/file/server.js +15 -5
  26. package/dist/index.d.ts.map +1 -1
  27. package/dist/index.js +4 -3
  28. package/dist/tools/codegen-agent.d.ts +1 -1
  29. package/dist/tools/codegen-agent.d.ts.map +1 -1
  30. package/dist/tools/diagnosis-fetcher.d.ts +1 -1
  31. package/dist/tools/diagnosis-fetcher.d.ts.map +1 -1
  32. package/dist/tools/grep.d.ts +1 -1
  33. package/dist/tools/grep.d.ts.map +1 -1
  34. package/dist/tools/test-gen-browser.d.ts +1 -1
  35. package/dist/tools/test-gen-browser.d.ts.map +1 -1
  36. package/dist/tools/test-gen-browser.js +23 -21
  37. package/dist/tools/test-run-fetcher/index.d.ts +1 -1
  38. package/dist/tools/test-run-fetcher/index.d.ts.map +1 -1
  39. package/dist/tools/test-run.d.ts +1 -1
  40. package/dist/tools/test-run.d.ts.map +1 -1
  41. package/package.json +2 -2
  42. package/dist/tools/types.d.ts +0 -38
  43. package/dist/tools/types.d.ts.map +0 -1
  44. package/dist/tools/types.js +0 -12
  45. package/dist/tools/zod-schema.d.ts +0 -19
  46. package/dist/tools/zod-schema.d.ts.map +0 -1
  47. package/dist/tools/zod-schema.js +0 -95
package/CHANGELOG.md CHANGED
@@ -1,5 +1,23 @@
1
1
  # @empiricalrun/test-gen
2
2
 
3
+ ## 0.52.2
4
+
5
+ ### Patch Changes
6
+
7
+ - c490603: feat: input initial prompt with markdown file
8
+ - 68640d2: feat: handover from test-gen tool to chat agent with a summary message
9
+ - ae91e37: fix: cap cua iterations, add tracing and improve logging
10
+ - 0704b28: feat: zod schema for str_replace_editor for gemini to use this tool
11
+ - 02a2439: feat: summarize actions done by cua and rename fileservice
12
+ - 01fa143: feat: custom tool grep added for gemini
13
+ - Updated dependencies [c490603]
14
+ - Updated dependencies [486264f]
15
+ - Updated dependencies [ae91e37]
16
+ - Updated dependencies [0704b28]
17
+ - Updated dependencies [3ed20a3]
18
+ - Updated dependencies [01fa143]
19
+ - @empiricalrun/llm@0.13.0
20
+
3
21
  ## 0.52.1
4
22
 
5
23
  ### Patch Changes
@@ -4,10 +4,12 @@ type GenerateTestsType = {
4
4
  pwProjectsFilter: string[];
5
5
  testGenToken: string;
6
6
  repoDir: string;
7
+ editFileWithGeneratedCode: boolean;
7
8
  };
8
- export declare function generateTestsUsingMasterAgent({ testFilePath, filePathToUpdate, pwProjectsFilter, testGenToken, repoDir, }: GenerateTestsType): Promise<{
9
+ export declare function generateTestsUsingMasterAgent({ testFilePath, filePathToUpdate, pwProjectsFilter, testGenToken, repoDir, editFileWithGeneratedCode, }: GenerateTestsType): Promise<{
9
10
  isError: boolean;
10
11
  error: string;
12
+ actionsSummary?: string;
11
13
  }>;
12
14
  export {};
13
15
  //# sourceMappingURL=run.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../../src/agent/browsing/run.ts"],"names":[],"mappings":"AAiBA,KAAK,iBAAiB,GAAG;IACvB,YAAY,EAAE,MAAM,CAAC;IACrB,gBAAgB,EAAE,MAAM,CAAC;IACzB,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,wBAAsB,6BAA6B,CAAC,EAClD,YAAY,EACZ,gBAAgB,EAChB,gBAAgB,EAChB,YAAY,EACZ,OAAO,GACR,EAAE,iBAAiB,GAAG,OAAO,CAAC;IAC7B,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;CACf,CAAC,CAgFD"}
1
+ {"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../../src/agent/browsing/run.ts"],"names":[],"mappings":"AAiBA,KAAK,iBAAiB,GAAG;IACvB,YAAY,EAAE,MAAM,CAAC;IACrB,gBAAgB,EAAE,MAAM,CAAC;IACzB,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,yBAAyB,EAAE,OAAO,CAAC;CACpC,CAAC;AAEF,wBAAsB,6BAA6B,CAAC,EAClD,YAAY,EACZ,gBAAgB,EAChB,gBAAgB,EAChB,YAAY,EACZ,OAAO,EACP,yBAAyB,GAC1B,EAAE,iBAAiB,GAAG,OAAO,CAAC;IAC7B,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB,CAAC,CAqFD"}
@@ -10,7 +10,7 @@ const web_1 = require("../../bin/utils/platform/web");
10
10
  const server_1 = require("../../file/server");
11
11
  const exec_1 = require("../../utils/exec");
12
12
  const utils_1 = require("./utils");
13
- async function generateTestsUsingMasterAgent({ testFilePath, filePathToUpdate, pwProjectsFilter, testGenToken, repoDir, }) {
13
+ async function generateTestsUsingMasterAgent({ testFilePath, filePathToUpdate, pwProjectsFilter, testGenToken, repoDir, editFileWithGeneratedCode, }) {
14
14
  if (!fs_extra_1.default.existsSync(testFilePath)) {
15
15
  throw new Error(`File for master agent to run not found: ${testFilePath}`);
16
16
  }
@@ -18,9 +18,13 @@ async function generateTestsUsingMasterAgent({ testFilePath, filePathToUpdate, p
18
18
  const port = await (0, detect_port_1.default)(3030);
19
19
  // start a file service to handle file updates from agent
20
20
  // - also update the file path with updates when agent is done spitting out code
21
- const fileService = new server_1.FileService({ port, repoDir });
22
- await fileService.startFileService();
23
- fileService.setFilePath(filePathToUpdate);
21
+ const fileServer = new server_1.FileServiceServer({
22
+ port,
23
+ repoDir,
24
+ updateFile: editFileWithGeneratedCode,
25
+ });
26
+ await fileServer.startFileService();
27
+ fileServer.setFilePath(filePathToUpdate);
24
28
  // read playwright config from ./playwright.config.ts of source repo
25
29
  const playwrightConfig = await (0, utils_1.readPlaywrightConfig)(repoDir);
26
30
  // detect the playwright project name for the given test file and playwright config
@@ -42,7 +46,7 @@ async function generateTestsUsingMasterAgent({ testFilePath, filePathToUpdate, p
42
46
  }
43
47
  await (0, exec_1.cmd)(command.split(" "), {
44
48
  env: {
45
- APP_PORT: port.toString(),
49
+ IPC_FILE_SERVICE_PORT: port.toString(),
46
50
  PW_TEST_HTML_REPORT_OPEN: "never",
47
51
  // pass the test gen token so that the agent has the same configuration as cli
48
52
  TEST_GEN_TOKEN: testGenToken,
@@ -75,10 +79,11 @@ async function generateTestsUsingMasterAgent({ testFilePath, filePathToUpdate, p
75
79
  }
76
80
  // remove the test only from the file
77
81
  await (0, web_1.removeTestOnly)(testFilePath);
78
- await fileService.stop();
82
+ await fileServer.stop();
79
83
  return {
80
84
  isError,
81
85
  error,
86
+ actionsSummary: fileServer.getActionsSummary(),
82
87
  };
83
88
  }
84
89
  exports.generateTestsUsingMasterAgent = generateTestsUsingMasterAgent;
@@ -1,5 +1,6 @@
1
- export declare function chatAgent({ selectedModel, useDiskForChatState, }: {
1
+ export declare function chatAgent({ selectedModel, useDiskForChatState, initialPromptContent, }: {
2
2
  selectedModel?: "claude-3-7-sonnet-20250219" | "claude-3-5-sonnet-20241022" | "gemini-2.5-pro-exp-03-25";
3
3
  useDiskForChatState?: boolean;
4
+ initialPromptContent?: string;
4
5
  }): Promise<string>;
5
6
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/agent/chat/index.ts"],"names":[],"mappings":"AA4DA,wBAAsB,SAAS,CAAC,EAC9B,aAA4C,EAC5C,mBAA2B,GAC5B,EAAE;IACD,aAAa,CAAC,EACV,4BAA4B,GAC5B,4BAA4B,GAC5B,0BAA0B,CAAC;IAC/B,mBAAmB,CAAC,EAAE,OAAO,CAAC;CAC/B,mBA2FA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/agent/chat/index.ts"],"names":[],"mappings":"AAgEA,wBAAsB,SAAS,CAAC,EAC9B,aAA4C,EAC5C,mBAA2B,EAC3B,oBAAoB,GACrB,EAAE;IACD,aAAa,CAAC,EACV,4BAA4B,GAC5B,4BAA4B,GAC5B,0BAA0B,CAAC;IAC/B,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,oBAAoB,CAAC,EAAE,MAAM,CAAC;CAC/B,mBAsFA"}
@@ -10,8 +10,8 @@ const grep_1 = require("../../tools/grep");
10
10
  const test_gen_browser_1 = require("../../tools/test-gen-browser");
11
11
  const test_run_1 = require("../../tools/test-run");
12
12
  const test_run_fetcher_1 = require("../../tools/test-run-fetcher");
13
- const zod_schema_1 = require("../../tools/zod-schema");
14
13
  const prompt_1 = require("./prompt");
14
+ // TODO: Add strReplaceEditor for non-Claude models
15
15
  const tools = [
16
16
  test_run_1.runTestTool,
17
17
  test_gen_browser_1.generateTestWithBrowserAgent,
@@ -21,7 +21,7 @@ const tools = [
21
21
  ];
22
22
  const toolExecutors = {
23
23
  ...Object.fromEntries(tools.map((tool) => [tool.schema.name, tool.execute])),
24
- str_replace_editor: (input) => (0, chat_1.strReplaceEditorTool)(input, web_1.validateTypescript),
24
+ str_replace_editor: (input) => (0, chat_1.strReplaceEditorExecutor)(input, web_1.validateTypescript),
25
25
  };
26
26
  function createChatModel(useDiskForChatState, selectedModel) {
27
27
  if (selectedModel.startsWith("claude")) {
@@ -43,9 +43,16 @@ function concludeAgent(usageSummary) {
43
43
  console.log(`\n${(0, picocolors_1.gray)("Usage summary -> " + usageSummary)}`);
44
44
  (0, chat_1.cleanupBackupFiles)(process.cwd());
45
45
  }
46
- async function chatAgent({ selectedModel = "claude-3-7-sonnet-20250219", useDiskForChatState = false, }) {
46
+ async function chatAgent({ selectedModel = "claude-3-7-sonnet-20250219", useDiskForChatState = false, initialPromptContent, }) {
47
47
  let chatModel = createChatModel(useDiskForChatState, selectedModel);
48
48
  let userPrompt = undefined;
49
+ if (initialPromptContent && chatModel.messages.length === 0) {
50
+ chatModel.pushUserMessage(initialPromptContent);
51
+ chatModel.askUserForInput = false;
52
+ }
53
+ else if (initialPromptContent && chatModel.messages.length > 0) {
54
+ console.warn(`Ignoring initial prompt because we have existing messages.`);
55
+ }
49
56
  const handleSigInt = () => {
50
57
  concludeAgent(chatModel.getUsageSummary());
51
58
  process.exit(0);
@@ -81,35 +88,25 @@ async function chatAgent({ selectedModel = "claude-3-7-sonnet-20250219", useDisk
81
88
  }
82
89
  const toolUse = chatModel.getPendingToolCall();
83
90
  if (toolUse) {
84
- const spinner = ora(`Executing tool ${toolUse.name} with args: ${JSON.stringify(toolUse.input)}`).start();
91
+ console.log(`Executing tool ${toolUse.name} with args: ${JSON.stringify(toolUse.input)}`);
85
92
  const toolExecutor = toolExecutors[toolUse.name];
86
93
  if (!toolExecutor) {
87
94
  throw new Error(`Tool ${toolUse.name} not found`);
88
95
  }
89
96
  const toolResult = await toolExecutor(toolUse.input);
90
97
  if (toolResult.isError) {
91
- spinner.fail(`Tool ${toolUse.name} failed with error: ${toolResult.result}`);
98
+ ora(`Tool ${toolUse.name} failed: ${toolResult.result}`).fail();
92
99
  }
93
100
  else {
94
- spinner.succeed(`Tool ${toolUse.name} completed`);
101
+ ora(`Tool ${toolUse.name} completed`).succeed();
95
102
  }
96
- chatModel.pushMessage({
97
- role: "user",
98
- content: [
99
- {
100
- type: "tool_result",
101
- tool_use_id: toolUse.id,
102
- content: toolResult.result,
103
- is_error: toolResult.isError,
104
- },
105
- ],
106
- });
103
+ chatModel.pushToolResultMessage(toolUse, toolResult);
107
104
  continue;
108
105
  }
109
106
  const spinner = ora(`${getModelName(selectedModel)} is working...`).start();
110
107
  const response = await chatModel.getLLMResponse({
111
108
  systemPrompt,
112
- tools: tools.map((tool) => (0, zod_schema_1.zodToOpenAITool)(tool.schema)),
109
+ tools: tools.map((tool) => (0, chat_1.zodToOpenAITool)(tool.schema)),
113
110
  selectedModel,
114
111
  });
115
112
  spinner.stop();
@@ -41,8 +41,8 @@ Or if the user asks you to modify a test, you could use the generateTestWithBrow
41
41
  that a UI selector needs to be updated, using the browser agent is a good idea.
42
42
 
43
43
  Before using generateTestWithBrowserAgent, you need to prepare the test code for the browser agent.
44
- You can do this by using the str_replace_editor tool to add a TODO comment to the test code. This
45
- comment should explain to the browser agent what to do.
44
+ You can do this by using the strReplaceEditor or the text editor tool to add a TODO comment to the test
45
+ code. This comment explains to the browser agent what it needs to do.
46
46
 
47
47
  For example, if the expected modification is to click on a login button, you could add the following comment.
48
48
 
@@ -2,6 +2,9 @@ import { ResponseComputerToolCall } from "openai/resources/responses/responses.m
2
2
  import type { Page } from "playwright";
3
3
  type ComputerAction = ResponseComputerToolCall.Click | ResponseComputerToolCall.DoubleClick | ResponseComputerToolCall.Drag | ResponseComputerToolCall.Keypress | ResponseComputerToolCall.Move | ResponseComputerToolCall.Screenshot | ResponseComputerToolCall.Scroll | ResponseComputerToolCall.Type | ResponseComputerToolCall.Wait;
4
4
  export declare function getScreenshot(page: Page): Promise<string>;
5
- export declare function handleModelAction(page: Page, action: ComputerAction): Promise<string>;
5
+ export declare function handleModelAction(page: Page, action: ComputerAction): Promise<{
6
+ actionSummary: string;
7
+ actionCode: string;
8
+ }>;
6
9
  export {};
7
10
  //# sourceMappingURL=computer.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"computer.d.ts","sourceRoot":"","sources":["../../../src/agent/cua/computer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,wBAAwB,EAAE,MAAM,0CAA0C,CAAC;AACpF,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAEvC,KAAK,cAAc,GACf,wBAAwB,CAAC,KAAK,GAC9B,wBAAwB,CAAC,WAAW,GACpC,wBAAwB,CAAC,IAAI,GAC7B,wBAAwB,CAAC,QAAQ,GACjC,wBAAwB,CAAC,IAAI,GAC7B,wBAAwB,CAAC,UAAU,GACnC,wBAAwB,CAAC,MAAM,GAC/B,wBAAwB,CAAC,IAAI,GAC7B,wBAAwB,CAAC,IAAI,CAAC;AAElC,wBAAsB,aAAa,CAAC,IAAI,EAAE,IAAI,mBAG7C;AAgCD,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,IAAI,EACV,MAAM,EAAE,cAAc,GACrB,OAAO,CAAC,MAAM,CAAC,CA2HjB"}
1
+ {"version":3,"file":"computer.d.ts","sourceRoot":"","sources":["../../../src/agent/cua/computer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,wBAAwB,EAAE,MAAM,0CAA0C,CAAC;AACpF,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAEvC,KAAK,cAAc,GACf,wBAAwB,CAAC,KAAK,GAC9B,wBAAwB,CAAC,WAAW,GACpC,wBAAwB,CAAC,IAAI,GAC7B,wBAAwB,CAAC,QAAQ,GACjC,wBAAwB,CAAC,IAAI,GAC7B,wBAAwB,CAAC,UAAU,GACnC,wBAAwB,CAAC,MAAM,GAC/B,wBAAwB,CAAC,IAAI,GAC7B,wBAAwB,CAAC,IAAI,CAAC;AAElC,wBAAsB,aAAa,CAAC,IAAI,EAAE,IAAI,mBAG7C;AAgCD,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,IAAI,EACV,MAAM,EAAE,cAAc,GACrB,OAAO,CAAC;IACT,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;CACpB,CAAC,CAqID"}
@@ -38,11 +38,13 @@ const CUA_KEY_TO_PLAYWRIGHT_KEY = {
38
38
  async function handleModelAction(page, action) {
39
39
  const actionType = action.type;
40
40
  let actionCode = "";
41
+ let actionSummary = "";
41
42
  try {
42
43
  switch (actionType) {
43
44
  case "click": {
44
45
  const { x, y, button = "left" } = action;
45
46
  console.log(`Action: click at (${x}, ${y}) with button '${button}'`);
47
+ actionSummary = `Click at (${x}, ${y}) with button '${button}'`;
46
48
  let pwButton = undefined;
47
49
  if (button === "left" || button === "right") {
48
50
  pwButton = button;
@@ -72,18 +74,21 @@ async function handleModelAction(page, action) {
72
74
  case "double_click": {
73
75
  const { x, y } = action;
74
76
  console.log(`Action: doubleclick at (${x}, ${y})`);
77
+ actionSummary = `Double click at (${x}, ${y})`;
75
78
  await page.mouse.dblclick(x, y, { button: "left" });
76
79
  break;
77
80
  }
78
81
  case "move": {
79
82
  const { x, y } = action;
80
- console.log(`Action: move to (${x}, ${y})`);
83
+ console.log(`Action: mouse move to (${x}, ${y})`);
84
+ actionSummary = `Mouse move to (${x}, ${y})`;
81
85
  await page.mouse.move(x, y);
82
86
  break;
83
87
  }
84
88
  case "drag": {
85
89
  const { path } = action;
86
90
  console.log(`Action: drag along path ${path}`);
91
+ actionSummary = `Drag along path ${path}`;
87
92
  if (!path || path.length === 0) {
88
93
  break;
89
94
  }
@@ -98,6 +103,7 @@ async function handleModelAction(page, action) {
98
103
  case "scroll": {
99
104
  const { x, y, scroll_x, scroll_y } = action;
100
105
  console.log(`Action: scroll at (${x}, ${y}) with offsets (scroll_x=${scroll_x}, scroll_y=${scroll_y})`);
106
+ actionSummary = `Scroll at (${x}, ${y}) with offsets (scroll_x=${scroll_x}, scroll_y=${scroll_y})`;
101
107
  await page.mouse.move(x, y);
102
108
  await page.evaluate(`window.scrollBy(${scroll_x}, ${scroll_y})`);
103
109
  break;
@@ -109,6 +115,7 @@ async function handleModelAction(page, action) {
109
115
  });
110
116
  const mappedKey = mappedKeys.join("+"); // ["CTRL", "A"] becomes ControlOrMeta+A
111
117
  console.log(`Action: keypress for keys ${keys} -> '${mappedKey}'`);
118
+ actionSummary = `Keypress for keys ${keys} (mapped to '${mappedKey}' for Playwright)`;
112
119
  try {
113
120
  await page.keyboard.press(mappedKey);
114
121
  actionCode = `await page.keyboard.press('${mappedKey}');\n`;
@@ -121,6 +128,7 @@ async function handleModelAction(page, action) {
121
128
  case "type": {
122
129
  const { text } = action;
123
130
  console.log(`Action: type text '${text}'`);
131
+ actionSummary = `Type text '${text}'`;
124
132
  await page.keyboard.type(text);
125
133
  const locator = await page.evaluate(() => {
126
134
  const element = document.activeElement;
@@ -131,12 +139,14 @@ async function handleModelAction(page, action) {
131
139
  }
132
140
  case "wait": {
133
141
  console.log(`Action: wait`);
142
+ actionSummary = `Wait for 2 seconds`;
134
143
  await page.waitForTimeout(2000);
135
144
  break;
136
145
  }
137
146
  case "screenshot": {
138
147
  // Nothing to do as screenshot is taken at each turn
139
148
  console.log(`Action: screenshot`);
149
+ actionSummary = `Screenshot`;
140
150
  break;
141
151
  }
142
152
  default:
@@ -146,6 +156,6 @@ async function handleModelAction(page, action) {
146
156
  catch (e) {
147
157
  console.error("Error handling action", action, ":", e);
148
158
  }
149
- return actionCode;
159
+ return { actionSummary, actionCode };
150
160
  }
151
161
  exports.handleModelAction = handleModelAction;
@@ -1,13 +1,11 @@
1
1
  import { Page } from "playwright";
2
2
  export declare function startPlaywrightCodegen(page: Page): Promise<void>;
3
- /**
4
- * Run the loop that executes computer actions until no 'computer_call' is found.
5
- */
6
3
  export declare function createTestUsingComputerUseAgent({ page, task, }: {
7
4
  page: Page;
8
5
  task: string;
9
6
  }): Promise<{
10
7
  code: string;
11
8
  importPaths: string[];
9
+ actionsSummary: string;
12
10
  }>;
13
11
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/agent/cua/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAMlC,wBAAsB,sBAAsB,CAAC,IAAI,EAAE,IAAI,iBAoBtD;AAED;;GAEG;AACH,wBAAsB,+BAA+B,CAAC,EACpD,IAAI,EACJ,IAAI,GACL,EAAE;IACD,IAAI,EAAE,IAAI,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;CACd,GAAG,OAAO,CAAC;IACV,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,EAAE,CAAC;CACvB,CAAC,CAkFD"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/agent/cua/index.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAOlC,wBAAsB,sBAAsB,CAAC,IAAI,EAAE,IAAI,iBAoBtD;AAED,wBAAsB,+BAA+B,CAAC,EACpD,IAAI,EACJ,IAAI,GACL,EAAE;IACD,IAAI,EAAE,IAAI,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;CACd,GAAG,OAAO,CAAC;IACV,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,cAAc,EAAE,MAAM,CAAC;CACxB,CAAC,CA2JD"}
@@ -1,6 +1,12 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
3
6
  exports.createTestUsingComputerUseAgent = exports.startPlaywrightCodegen = void 0;
7
+ const llm_1 = require("@empiricalrun/llm");
8
+ const crypto_1 = __importDefault(require("crypto"));
9
+ const logger_1 = require("../../bin/logger");
4
10
  const utils_1 = require("../browsing/utils");
5
11
  const computer_1 = require("./computer");
6
12
  const model_1 = require("./model");
@@ -26,16 +32,25 @@ async function startPlaywrightCodegen(page) {
26
32
  await page.pause();
27
33
  }
28
34
  exports.startPlaywrightCodegen = startPlaywrightCodegen;
29
- /**
30
- * Run the loop that executes computer actions until no 'computer_call' is found.
31
- */
32
35
  async function createTestUsingComputerUseAgent({ page, task, }) {
33
- let generatedCode = "";
34
36
  await (0, utils_1.injectPwLocatorGenerator)(page);
35
37
  const screenshotBytes = await (0, computer_1.getScreenshot)(page);
36
38
  const viewport = page.viewportSize();
37
39
  let screenWidth = viewport?.width || 1280;
38
40
  let screenHeight = viewport?.height || 720;
41
+ const logger = new logger_1.CustomLogger({ useReporter: false });
42
+ const trace = llm_1.langfuseInstance?.trace({
43
+ name: "computer-use-agent",
44
+ id: crypto_1.default.randomUUID(),
45
+ input: { task },
46
+ });
47
+ if (trace) {
48
+ const traceUrl = trace.getTraceUrl();
49
+ logger.log(`Starting computer use agent: ${traceUrl}`);
50
+ }
51
+ const span = trace?.span({
52
+ name: "initial-model-call",
53
+ });
39
54
  let response = await (0, model_1.callComputerUseModel)({
40
55
  input: [
41
56
  {
@@ -56,32 +71,63 @@ async function createTestUsingComputerUseAgent({ page, task, }) {
56
71
  screenWidth,
57
72
  screenHeight,
58
73
  });
59
- // eslint-disable-next-line no-constant-condition
60
- while (true) {
74
+ span?.end({ output: response });
75
+ let isTaskDone = false;
76
+ let maxIterations = 15;
77
+ let generatedCode = "";
78
+ let actionsSummary = [];
79
+ let iterationIndex = 0;
80
+ while (!isTaskDone && iterationIndex < maxIterations) {
81
+ actionsSummary.push(`\n# Agent iteration ${iterationIndex}`);
82
+ iterationIndex++;
83
+ const iterationSpan = trace?.span({
84
+ name: `iteration-${iterationIndex}`,
85
+ input: { response },
86
+ });
61
87
  const computerCalls = response.output.filter((item) => item.type === "computer_call");
62
88
  if (computerCalls.length === 0) {
63
- console.log("No computer call found. Output from model:");
64
- /**
65
- * TODO: Sometimes the mdoel will ask for a user confirmation - handle this flow
66
- * item.type is "message", status is "completed", item.content.type is "output_text"
67
- */
68
- response.output.forEach((item) => {
69
- console.log(JSON.stringify(item, null, 2));
70
- });
71
- break; // Exit when no computer calls are issued.
89
+ const assistantOutput = response.output.find((item) => item.type === "message");
90
+ if (assistantOutput) {
91
+ const content = assistantOutput.content.find((item) => item.type === "output_text");
92
+ if (content && "text" in content) {
93
+ // TODO: This ignores `ResponseOutputRefusal` type (refusal from assistant)
94
+ actionsSummary.push(`Agent summary: ${content.text}`);
95
+ }
96
+ }
97
+ isTaskDone = true;
98
+ continue;
99
+ }
100
+ const reasoning = response.output.find(() => (item) => item.type === "reasoning");
101
+ if (reasoning) {
102
+ const reasoningItem = reasoning;
103
+ const summaryText = reasoningItem.summary?.find((item) => item.type === "summary_text")?.text;
104
+ if (summaryText) {
105
+ actionsSummary.push(`Action reasoning: ${summaryText}`);
106
+ }
72
107
  }
73
108
  // We expect at most one computer call per response.
74
109
  const computerCall = computerCalls[0];
75
110
  const lastCallId = computerCall.call_id;
76
111
  const action = computerCall.action;
77
112
  const pendingSafetyChecks = computerCall.pending_safety_checks;
78
- // Execute the action (function defined in step 3)
79
- const actionCode = await (0, computer_1.handleModelAction)(page, action);
80
- generatedCode += actionCode;
81
- await new Promise((resolve) => setTimeout(resolve, 1000)); // Allow time for changes to take effect.
82
- // Take a screenshot after the action (function defined in step 4)
113
+ // Execute the action and take a screenshot
114
+ const { actionSummary, actionCode } = await (0, computer_1.handleModelAction)(page, action);
115
+ actionsSummary.push(`Action executed: ${actionSummary}`);
116
+ if (actionCode) {
117
+ actionsSummary.push(`Generated code: ${actionCode}`);
118
+ generatedCode += actionCode;
119
+ }
120
+ else {
121
+ actionsSummary.push(`No code generated: Will rely on Playwright's ability to auto-wait or auto-scroll`);
122
+ }
123
+ // Allow time for changes to take effect.
124
+ await new Promise((resolve) => setTimeout(resolve, 1000));
83
125
  const screenshotBytes = await (0, computer_1.getScreenshot)(page);
84
126
  // Send the screenshot back as a computer_call_output
127
+ const computerCallSpan = iterationSpan?.span({
128
+ name: "computer-call-output",
129
+ input: { lastCallId, acknowledged_safety_checks: pendingSafetyChecks },
130
+ });
85
131
  response = await (0, model_1.callComputerUseModel)({
86
132
  previousResponseId: response.id,
87
133
  input: [
@@ -98,8 +144,17 @@ async function createTestUsingComputerUseAgent({ page, task, }) {
98
144
  screenWidth,
99
145
  screenHeight,
100
146
  });
147
+ computerCallSpan?.end({ output: response });
148
+ iterationSpan?.end({ output: response });
149
+ }
150
+ if (!isTaskDone) {
151
+ actionsSummary.push(`Max iteration limit hit: Task not done after ${maxIterations} iterations`);
101
152
  }
153
+ trace?.update({
154
+ output: { code: generatedCode, actionsSummary: actionsSummary.join("\n") },
155
+ });
102
156
  return {
157
+ actionsSummary: actionsSummary.join("\n"),
103
158
  code: generatedCode,
104
159
  // TODO: Does not support skills, so import paths are empty
105
160
  importPaths: [],
@@ -1 +1 @@
1
- {"version":3,"file":"model.d.ts","sourceRoot":"","sources":["../../../src/agent/cua/model.ts"],"names":[],"mappings":"AACA,OAAO,EACL,QAAQ,EACR,aAAa,EACd,MAAM,0CAA0C,CAAC;AAQlD,wBAAsB,oBAAoB,CAAC,EACzC,KAAK,EACL,kBAAkB,EAClB,WAAW,EACX,YAAY,GACb,EAAE;IACD,KAAK,EAAE,aAAa,CAAC;IACrB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;CACtB,GAAG,OAAO,CAAC,QAAQ,CAAC,CAqBpB"}
1
+ {"version":3,"file":"model.d.ts","sourceRoot":"","sources":["../../../src/agent/cua/model.ts"],"names":[],"mappings":"AACA,OAAO,EACL,QAAQ,EACR,aAAa,EACd,MAAM,0CAA0C,CAAC;AAWlD,wBAAsB,oBAAoB,CAAC,EACzC,KAAK,EACL,kBAAkB,EAClB,WAAW,EACX,YAAY,GACb,EAAE;IACD,KAAK,EAAE,aAAa,CAAC;IACrB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;CACtB,GAAG,OAAO,CAAC,QAAQ,CAAC,CAqBpB"}
@@ -9,11 +9,14 @@ const INSTRUCTIONS = `You will be asked to execute some actions in a browser con
9
9
  Don't ask the user for confirmations - just execute the actions.
10
10
 
11
11
  For example, if the user message says "Click on Submit button", then
12
- you click on the submit button -- even if it looks like a scary action.`;
12
+ you click on the submit button -- even if it looks like a scary action.
13
+
14
+ If you have been asked to retrieve text or verify something on the UI, then communicate
15
+ that in your responses so that the user can see your thinking process in its entirety.`;
13
16
  async function callComputerUseModel({ input, previousResponseId, screenWidth, screenHeight, }) {
14
17
  const openai = new openai_1.default();
15
18
  return await openai.responses.create({
16
- model: "computer-use-preview",
19
+ model: "computer-use-preview-2025-03-11",
17
20
  previous_response_id: previousResponseId,
18
21
  tools: [
19
22
  {
package/dist/bin/index.js CHANGED
@@ -35,7 +35,7 @@ function setupProcessListeners(cleanup) {
35
35
  events.forEach((event) => process.removeListener(event, cleanup));
36
36
  };
37
37
  }
38
- async function runChatAgent(modelInput, useDiskForChatState) {
38
+ async function runChatAgent(modelInput, useDiskForChatState, initialPromptPath) {
39
39
  const MODEL_MAPPING = {
40
40
  "claude-3-7": "claude-3-7-sonnet-20250219",
41
41
  "3-7": "claude-3-7-sonnet-20250219",
@@ -46,9 +46,20 @@ async function runChatAgent(modelInput, useDiskForChatState) {
46
46
  if (modelInput && !MODEL_MAPPING[modelInput]) {
47
47
  throw new Error(`Invalid chat model: ${modelInput}`);
48
48
  }
49
+ let initialPromptContent = undefined;
50
+ if (initialPromptPath) {
51
+ try {
52
+ const fs = await import("fs");
53
+ initialPromptContent = fs.readFileSync(initialPromptPath, "utf-8");
54
+ }
55
+ catch (error) {
56
+ throw new Error(`Failed to read initial prompt file at ${initialPromptPath}: ${error.message}`);
57
+ }
58
+ }
49
59
  return await (0, chat_1.chatAgent)({
50
60
  selectedModel: modelInput ? MODEL_MAPPING[modelInput] : undefined,
51
61
  useDiskForChatState,
62
+ initialPromptContent,
52
63
  });
53
64
  }
54
65
  async function runAgentsWorkflow(testGenConfig, testGenToken) {
@@ -168,6 +179,7 @@ async function runAgentsWorkflow(testGenConfig, testGenToken) {
168
179
  pwProjectsFilter: testGenConfig.environment?.playwrightProjects,
169
180
  testGenToken,
170
181
  repoDir: process.cwd(),
182
+ editFileWithGeneratedCode: true,
171
183
  });
172
184
  if (isError) {
173
185
  throw new Error(error);
@@ -188,6 +200,7 @@ async function runAgentsWorkflow(testGenConfig, testGenToken) {
188
200
  .option("--use-chat", "Use chat agent (and not the workflow)")
189
201
  .option("--use-disk-for-chat-state", "Save and load chat state from disk")
190
202
  .option("--chat-model <model>", "Chat model to use (claude-3-7-sonnet-20250219 or claude-3-5-sonnet-20241022 or gemini-2.5-pro-exp-03-25)")
203
+ .option("--initial-prompt <path>", "Path to an initial prompt file (e.g. prompt.md)")
191
204
  .parse(process.argv);
192
205
  const options = program.opts();
193
206
  const completedOptions = await (0, utils_2.validateAndCompleteCliOptions)(options);
@@ -211,7 +224,7 @@ async function runAgentsWorkflow(testGenConfig, testGenToken) {
211
224
  // Download the build if repo has a download script
212
225
  await (0, test_build_1.downloadBuild)(testGenConfig.build || {});
213
226
  if (completedOptions.useChat) {
214
- await runChatAgent(completedOptions.chatModel, completedOptions.useDiskForChatState);
227
+ await runChatAgent(completedOptions.chatModel, completedOptions.useDiskForChatState, completedOptions.initialPrompt);
215
228
  return;
216
229
  }
217
230
  let agentUsed;
@@ -6,6 +6,7 @@ export interface CliOptions {
6
6
  suites?: string;
7
7
  useChat?: boolean;
8
8
  useDiskForChatState?: boolean;
9
+ initialPrompt?: string;
9
10
  chatModel?: "claude-3-7" | "3-7" | "claude-3-5" | "3-5" | "claude-3-7-sonnet-20250219" | "claude-3-5-sonnet-20241022" | "gemini-2.5-pro-exp-03-25";
10
11
  }
11
12
  export declare function validateAndCompleteCliOptions(options: CliOptions): Promise<CliOptions>;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/bin/utils/index.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,UAAU;IACzB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,SAAS,CAAC,EACN,YAAY,GACZ,KAAK,GACL,YAAY,GACZ,KAAK,GACL,4BAA4B,GAC5B,4BAA4B,GAC5B,0BAA0B,CAAC;CAChC;AAQD,wBAAsB,6BAA6B,CACjD,OAAO,EAAE,UAAU,GAClB,OAAO,CAAC,UAAU,CAAC,CAyDrB"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/bin/utils/index.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,UAAU;IACzB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,SAAS,CAAC,EACN,YAAY,GACZ,KAAK,GACL,YAAY,GACZ,KAAK,GACL,4BAA4B,GAC5B,4BAA4B,GAC5B,0BAA0B,CAAC;CAChC;AAQD,wBAAsB,6BAA6B,CACjD,OAAO,EAAE,UAAU,GAClB,OAAO,CAAC,UAAU,CAAC,CAyDrB"}
@@ -1,14 +1,15 @@
1
- declare class TestFileService {
1
+ declare class FileServiceClient {
2
2
  baseUrl: string;
3
3
  port: number | undefined;
4
4
  constructor();
5
5
  static isAvailable(): boolean;
6
- updateTest({ generatedCode, task, importPaths, }: {
6
+ updateTest({ generatedCode, task, importPaths, actionsSummary, }: {
7
7
  generatedCode: string;
8
8
  task: string;
9
9
  importPaths: string[];
10
- }): Promise<void>;
10
+ actionsSummary?: string;
11
+ }): Promise<any>;
11
12
  post(path: string, body: any): Promise<any>;
12
13
  }
13
- export default TestFileService;
14
+ export default FileServiceClient;
14
15
  //# sourceMappingURL=client.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/file/client.ts"],"names":[],"mappings":"AAAA,cAAM,eAAe;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,GAAG,SAAS,CAAC;;IASzB,MAAM,CAAC,WAAW;IAIZ,UAAU,CAAC,EACf,aAAa,EACb,IAAI,EACJ,WAAW,GACZ,EAAE;QACD,aAAa,EAAE,MAAM,CAAC;QACtB,IAAI,EAAE,MAAM,CAAC;QACb,WAAW,EAAE,MAAM,EAAE,CAAC;KACvB;IAgBK,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG;CAgBnC;AAED,eAAe,eAAe,CAAC"}
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/file/client.ts"],"names":[],"mappings":"AAAA,cAAM,iBAAiB;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,GAAG,SAAS,CAAC;;IAUzB,MAAM,CAAC,WAAW;IAIZ,UAAU,CAAC,EACf,aAAa,EACb,IAAI,EACJ,WAAW,EACX,cAAc,GACf,EAAE;QACD,aAAa,EAAE,MAAM,CAAC;QACtB,IAAI,EAAE,MAAM,CAAC;QACb,WAAW,EAAE,MAAM,EAAE,CAAC;QACtB,cAAc,CAAC,EAAE,MAAM,CAAC;KACzB;IASK,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG;CAgBnC;AAED,eAAe,iBAAiB,CAAC"}
@@ -1,32 +1,25 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- class TestFileService {
3
+ class FileServiceClient {
4
4
  baseUrl;
5
5
  port;
6
6
  constructor() {
7
- const port = Number(process.env.APP_PORT);
7
+ const port = Number(process.env.IPC_FILE_SERVICE_PORT);
8
8
  if (port && !isNaN(port)) {
9
9
  this.port = port;
10
10
  }
11
11
  this.baseUrl = `http://localhost:${port}`;
12
12
  }
13
13
  static isAvailable() {
14
- return !!Number(process.env.APP_PORT);
14
+ return !!Number(process.env.IPC_FILE_SERVICE_PORT);
15
15
  }
16
- async updateTest({ generatedCode, task, importPaths, }) {
17
- const resp = await fetch(`${this.baseUrl}/test`, {
18
- method: "POST",
19
- headers: {
20
- "Content-Type": "application/json",
21
- },
22
- body: JSON.stringify({ generatedCode, task, importPaths }),
16
+ async updateTest({ generatedCode, task, importPaths, actionsSummary, }) {
17
+ return this.post("/test", {
18
+ generatedCode,
19
+ task,
20
+ importPaths,
21
+ actionsSummary,
23
22
  });
24
- if (!resp.ok) {
25
- throw new Error(resp.statusText);
26
- }
27
- else {
28
- console.log("Generated and updated test successfully");
29
- }
30
23
  }
31
24
  async post(path, body) {
32
25
  const resp = await fetch(`${this.baseUrl}${path}`, {
@@ -45,4 +38,4 @@ class TestFileService {
45
38
  }
46
39
  }
47
40
  }
48
- exports.default = TestFileService;
41
+ exports.default = FileServiceClient;
@@ -1,12 +1,16 @@
1
- export declare class FileService {
1
+ export declare class FileServiceServer {
2
2
  private port;
3
3
  private filePath;
4
4
  private repoDir;
5
5
  private server;
6
- constructor({ port, repoDir }: {
6
+ private actionsSummary;
7
+ private updateFile;
8
+ constructor({ port, repoDir, updateFile, }: {
7
9
  port: number;
8
10
  repoDir: string;
11
+ updateFile: boolean;
9
12
  });
13
+ getActionsSummary(): string | undefined;
10
14
  setFilePath(filePath: string): void;
11
15
  startFileService(): Promise<number>;
12
16
  stop(): Promise<void>;
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/file/server.ts"],"names":[],"mappings":"AAWA,qBAAa,WAAW;IACtB,OAAO,CAAC,IAAI,CAAa;IACzB,OAAO,CAAC,QAAQ,CAAc;IAC9B,OAAO,CAAC,OAAO,CAAc;IAC7B,OAAO,CAAC,MAAM,CAA4C;gBAE9C,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE;IAKhE,WAAW,CAAC,QAAQ,EAAE,MAAM;IAItB,gBAAgB,IAAI,OAAO,CAAC,MAAM,CAAC;IA0CnC,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;CAgB5B;AAED,wBAAsB,gBAAgB,kBAAK"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/file/server.ts"],"names":[],"mappings":"AAWA,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,IAAI,CAAa;IACzB,OAAO,CAAC,QAAQ,CAAc;IAC9B,OAAO,CAAC,OAAO,CAAc;IAC7B,OAAO,CAAC,MAAM,CAA4C;IAC1D,OAAO,CAAC,cAAc,CAAqB;IAC3C,OAAO,CAAC,UAAU,CAAkB;gBAExB,EACV,IAAI,EACJ,OAAO,EACP,UAAU,GACX,EAAE;QACD,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,EAAE,MAAM,CAAC;QAChB,UAAU,EAAE,OAAO,CAAC;KACrB;IAMD,iBAAiB;IAIjB,WAAW,CAAC,QAAQ,EAAE,MAAM;IAItB,gBAAgB,IAAI,OAAO,CAAC,MAAM,CAAC;IA+CnC,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;CAgB5B;AAED,wBAAsB,gBAAgB,kBAAK"}
@@ -3,20 +3,26 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.startFileService = exports.FileService = void 0;
6
+ exports.startFileService = exports.FileServiceServer = void 0;
7
7
  const express_1 = __importDefault(require("express"));
8
8
  const fs_1 = __importDefault(require("fs"));
9
9
  const path_1 = __importDefault(require("path"));
10
10
  const web_1 = require("../bin/utils/platform/web");
11
11
  const ipc_1 = require("../human-in-the-loop/ipc");
12
- class FileService {
12
+ class FileServiceServer {
13
13
  port = 0;
14
14
  filePath = "";
15
15
  repoDir = "";
16
16
  server;
17
- constructor({ port, repoDir }) {
17
+ actionsSummary;
18
+ updateFile = false;
19
+ constructor({ port, repoDir, updateFile, }) {
18
20
  this.port = port;
19
21
  this.repoDir = repoDir;
22
+ this.updateFile = updateFile;
23
+ }
24
+ getActionsSummary() {
25
+ return this.actionsSummary;
20
26
  }
21
27
  setFilePath(filePath) {
22
28
  this.filePath = filePath;
@@ -26,7 +32,11 @@ class FileService {
26
32
  app.use(express_1.default.json());
27
33
  (0, ipc_1.humanLoopRoute)(app);
28
34
  app.post("/test", async (req, res) => {
29
- const { generatedCode, importPaths } = req.body;
35
+ const { generatedCode, importPaths, actionsSummary } = req.body;
36
+ this.actionsSummary = actionsSummary;
37
+ if (!this.updateFile) {
38
+ return res.send({ success: true });
39
+ }
30
40
  try {
31
41
  const testFilePath = path_1.default.resolve(this.repoDir, this.filePath);
32
42
  if (testFilePath) {
@@ -64,6 +74,6 @@ class FileService {
64
74
  });
65
75
  }
66
76
  }
67
- exports.FileService = FileService;
77
+ exports.FileServiceServer = FileServiceServer;
68
78
  async function startFileService() { }
69
79
  exports.startFileService = startFileService;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAQlC,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAepC,wBAAsB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,CAAC,EAAE,SAAS,iBAsD3E"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAQlC,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAqBpC,wBAAsB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,CAAC,EAAE,SAAS,iBAyD3E"}
package/dist/index.js CHANGED
@@ -40,7 +40,7 @@ async function createTest(task, page, scope) {
40
40
  projectRepoName: testGenConfig.options?.metadata.projectRepoName,
41
41
  });
42
42
  }
43
- const fileService = new client_1.default();
43
+ const fileServiceClient = new client_1.default();
44
44
  const useComputerUseAgent = testGenConfig.options?.useComputerUseAgent;
45
45
  let agentResult;
46
46
  if (useComputerUseAgent) {
@@ -61,11 +61,12 @@ async function createTest(task, page, scope) {
61
61
  scopeVars: scope,
62
62
  });
63
63
  }
64
- const { code, importPaths } = agentResult;
65
- await fileService.updateTest({
64
+ const { code, importPaths, actionsSummary } = agentResult;
65
+ await fileServiceClient.updateTest({
66
66
  task,
67
67
  generatedCode: code,
68
68
  importPaths,
69
+ actionsSummary,
69
70
  });
70
71
  // skip the rest of the test once generation is over
71
72
  await (0, pw_test_1.skipTest)();
@@ -1,3 +1,3 @@
1
- import type { Tool } from "./types";
1
+ import type { Tool } from "@empiricalrun/llm/chat";
2
2
  export declare const codegenTool: Tool;
3
3
  //# sourceMappingURL=codegen-agent.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"codegen-agent.d.ts","sourceRoot":"","sources":["../../src/tools/codegen-agent.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AAepC,eAAO,MAAM,WAAW,EAAE,IAyBzB,CAAC"}
1
+ {"version":3,"file":"codegen-agent.d.ts","sourceRoot":"","sources":["../../src/tools/codegen-agent.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,wBAAwB,CAAC;AAmBnD,eAAO,MAAM,WAAW,EAAE,IAyBzB,CAAC"}
@@ -1,3 +1,3 @@
1
- import type { Tool } from "./types";
1
+ import type { Tool } from "@empiricalrun/llm/chat";
2
2
  export declare const diagnosisTool: Tool;
3
3
  //# sourceMappingURL=diagnosis-fetcher.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"diagnosis-fetcher.d.ts","sourceRoot":"","sources":["../../src/tools/diagnosis-fetcher.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AAUpC,eAAO,MAAM,aAAa,EAAE,IAgF3B,CAAC"}
1
+ {"version":3,"file":"diagnosis-fetcher.d.ts","sourceRoot":"","sources":["../../src/tools/diagnosis-fetcher.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,wBAAwB,CAAC;AAanD,eAAO,MAAM,aAAa,EAAE,IAgF3B,CAAC"}
@@ -1,3 +1,3 @@
1
- import { Tool } from "./types";
1
+ import type { Tool } from "@empiricalrun/llm/chat";
2
2
  export declare const grepTool: Tool;
3
3
  //# sourceMappingURL=grep.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"grep.d.ts","sourceRoot":"","sources":["../../src/tools/grep.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,IAAI,EAAc,MAAM,SAAS,CAAC;AAgB3C,eAAO,MAAM,QAAQ,EAAE,IA+CtB,CAAC"}
1
+ {"version":3,"file":"grep.d.ts","sourceRoot":"","sources":["../../src/tools/grep.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAc,MAAM,wBAAwB,CAAC;AAqB/D,eAAO,MAAM,QAAQ,EAAE,IA+CtB,CAAC"}
@@ -1,3 +1,3 @@
1
- import type { Tool } from "./types";
1
+ import type { Tool } from "@empiricalrun/llm/chat";
2
2
  export declare const generateTestWithBrowserAgent: Tool;
3
3
  //# sourceMappingURL=test-gen-browser.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"test-gen-browser.d.ts","sourceRoot":"","sources":["../../src/tools/test-gen-browser.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AAmDpC,eAAO,MAAM,4BAA4B,EAAE,IAuE1C,CAAC"}
1
+ {"version":3,"file":"test-gen-browser.d.ts","sourceRoot":"","sources":["../../src/tools/test-gen-browser.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,wBAAwB,CAAC;AA0DnD,eAAO,MAAM,4BAA4B,EAAE,IA0E1C,CAAC"}
@@ -1,12 +1,15 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
3
6
  exports.generateTestWithBrowserAgent = void 0;
7
+ const promises_1 = __importDefault(require("fs/promises"));
4
8
  const zod_1 = require("zod");
5
9
  const run_1 = require("../agent/browsing/run");
6
10
  const utils_1 = require("../agent/browsing/utils");
7
11
  const web_1 = require("../bin/utils/platform/web");
8
12
  const scenarios_1 = require("../bin/utils/scenarios");
9
- const git_1 = require("../utils/git");
10
13
  const BrowserAgentSchema = zod_1.z.object({
11
14
  testName: zod_1.z.string().describe("The name of the test to create or modify"),
12
15
  testSuites: zod_1.z
@@ -27,10 +30,10 @@ locator/selector for an element on the page.
27
30
 
28
31
  IMPORTANT: Before you invoke this tool, you need to ensure that the test code is correctly prepared for this
29
32
  agent. Preparation involves adding a TODO comment that describes the change that needs to be made. A good
30
- comment calls out the element and browser interactions sto take on them. The TODO comment also has (agent) next to it, to
33
+ comment calls out the element and browser interactions it must take. The TODO comment also has (agent) next to it, to
31
34
  clearly label that the change is for the agent to make.
32
35
 
33
- For example: This is a good TODO comment
36
+ For example, this is a good TODO comment:
34
37
 
35
38
  \`\`\`
36
39
  test("Example test code", async ({ page }) => {
@@ -39,16 +42,12 @@ test("Example test code", async ({ page }) => {
39
42
  });
40
43
  \`\`\`
41
44
 
42
- The browser agent will execute the steps before the TODO comment and replace the TODO comment with the Playwright
43
- code that performs the actions described in the comment. For instance, on the running the tool, the agent will
44
- output the following final code:
45
+ For the above file, the browser environment will execute the steps before the TODO comment and hand-over the control
46
+ to the browser agent. The agent will do the actions described in the TODO comment and then resume control back to the
47
+ test code.
45
48
 
46
- \`\`\`
47
- test("Example test code", async ({ page }) => {
48
- await page.goto("https://example.com");
49
- await page.getByRole("button", { name: "Login" }).click();
50
- });
51
- \`\`\`
49
+ The browser agent will return a summary of actions that it took, and the generated Playwright code for them. You can
50
+ then use the text editor tool to replace the TODO comment with the generated Playwright code.
52
51
  `;
53
52
  exports.generateTestWithBrowserAgent = {
54
53
  schema: {
@@ -72,6 +71,7 @@ exports.generateTestWithBrowserAgent = {
72
71
  result: `Test block not found for test name: "${testName}" in file: "${fileName}" with describe blocks: "${testSuites.join(", ")}"`,
73
72
  };
74
73
  }
74
+ const fileBackup = await promises_1.default.readFile(fileName, "utf-8");
75
75
  try {
76
76
  await (0, utils_1.replaceTodoWithCreateTest)({
77
77
  testCaseName: testName,
@@ -85,7 +85,7 @@ exports.generateTestWithBrowserAgent = {
85
85
  result: `Error running tool: ${error}`,
86
86
  };
87
87
  }
88
- const { isError, error } = await (0, run_1.generateTestsUsingMasterAgent)({
88
+ const toolResult = await (0, run_1.generateTestsUsingMasterAgent)({
89
89
  testFilePath: fileName,
90
90
  filePathToUpdate: fileName,
91
91
  pwProjectsFilter: [project],
@@ -97,25 +97,27 @@ exports.generateTestWithBrowserAgent = {
97
97
  useComputerUseAgent: true,
98
98
  }),
99
99
  repoDir: process.cwd(),
100
+ editFileWithGeneratedCode: false,
100
101
  });
102
+ // Undo the TODO -> createTest change
103
+ await promises_1.default.writeFile(fileName, fileBackup, "utf-8");
104
+ const { isError, error, actionsSummary } = toolResult;
101
105
  if (!isError) {
102
- const gitPatch = (0, git_1.getGitDiff)(fileName);
103
106
  return {
104
107
  isError,
105
- result: `Test was generated successfully. Here is the git patch:
106
- \`\`\`
107
- ${gitPatch}
108
- \`\`\`
108
+ result: `Browser agent has finished running. Here is the summary of actions it took
109
+ and the generated Playwright code:
110
+
111
+ ${actionsSummary}
109
112
  `,
110
113
  };
111
114
  }
112
115
  else {
113
116
  return {
114
117
  isError,
115
- result: `Test was not generated successfully. Here is the error:
116
- \`\`\`
118
+ result: `Browser agent failed to run successfully. Here is the error:
119
+
117
120
  ${error}
118
- \`\`\`
119
121
  `,
120
122
  };
121
123
  }
@@ -1,4 +1,4 @@
1
- import type { Tool } from "../types";
1
+ import type { Tool } from "@empiricalrun/llm/chat";
2
2
  export declare function extractPathAfterSourceRepo(fullPath: string): string;
3
3
  export declare const testRunTool: Tool;
4
4
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/tools/test-run-fetcher/index.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,UAAU,CAAC;AAWrC,wBAAgB,0BAA0B,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAOnE;AAED,eAAO,MAAM,WAAW,EAAE,IA4HzB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/tools/test-run-fetcher/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,wBAAwB,CAAC;AAanD,wBAAgB,0BAA0B,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAOnE;AAED,eAAO,MAAM,WAAW,EAAE,IA4HzB,CAAC"}
@@ -1,3 +1,3 @@
1
- import type { Tool } from "./types";
1
+ import type { Tool } from "@empiricalrun/llm/chat";
2
2
  export declare const runTestTool: Tool;
3
3
  //# sourceMappingURL=test-run.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"test-run.d.ts","sourceRoot":"","sources":["../../src/tools/test-run.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AAsBpC,eAAO,MAAM,WAAW,EAAE,IA8BzB,CAAC"}
1
+ {"version":3,"file":"test-run.d.ts","sourceRoot":"","sources":["../../src/tools/test-run.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,wBAAwB,CAAC;AAwBnD,eAAO,MAAM,WAAW,EAAE,IA8BzB,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@empiricalrun/test-gen",
3
- "version": "0.52.1",
3
+ "version": "0.52.2",
4
4
  "publishConfig": {
5
5
  "registry": "https://registry.npmjs.org/",
6
6
  "access": "public"
@@ -75,7 +75,7 @@
75
75
  "tsx": "^4.16.2",
76
76
  "typescript": "^5.3.3",
77
77
  "zod": "^3.23.8",
78
- "@empiricalrun/llm": "^0.12.0",
78
+ "@empiricalrun/llm": "^0.13.0",
79
79
  "@empiricalrun/r2-uploader": "^0.3.8",
80
80
  "@empiricalrun/test-run": "^0.7.6"
81
81
  },
@@ -1,38 +0,0 @@
1
- import { z } from "zod";
2
- /**
3
- * Base schema for all tools. Each tool should extend this with their specific parameters.
4
- */
5
- export declare const BaseToolSchema: z.ZodObject<{
6
- name: z.ZodString;
7
- description: z.ZodString;
8
- parameters: z.ZodObject<{}, "passthrough", z.ZodTypeAny, z.objectOutputType<{}, z.ZodTypeAny, "passthrough">, z.objectInputType<{}, z.ZodTypeAny, "passthrough">>;
9
- }, "strip", z.ZodTypeAny, {
10
- name: string;
11
- description: string;
12
- parameters: {} & {
13
- [k: string]: unknown;
14
- };
15
- }, {
16
- name: string;
17
- description: string;
18
- parameters: {} & {
19
- [k: string]: unknown;
20
- };
21
- }>;
22
- export type ToolSchema = z.infer<typeof BaseToolSchema>;
23
- /**
24
- * Interface for creating a tool with its schema and execute function
25
- */
26
- export interface Tool {
27
- schema: {
28
- name: string;
29
- description: string;
30
- parameters: z.ZodType;
31
- };
32
- execute: (input: any) => Promise<ToolResult>;
33
- }
34
- export interface ToolResult {
35
- isError: boolean;
36
- result: string;
37
- }
38
- //# sourceMappingURL=types.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/tools/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;GAEG;AACH,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;EAIzB,CAAC;AAEH,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC;AAExD;;GAEG;AACH,MAAM,WAAW,IAAI;IACnB,MAAM,EAAE;QACN,IAAI,EAAE,MAAM,CAAC;QACb,WAAW,EAAE,MAAM,CAAC;QACpB,UAAU,EAAE,CAAC,CAAC,OAAO,CAAC;KACvB,CAAC;IACF,OAAO,EAAE,CAAC,KAAK,EAAE,GAAG,KAAK,OAAO,CAAC,UAAU,CAAC,CAAC;CAC9C;AAED,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;CAChB"}
@@ -1,12 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.BaseToolSchema = void 0;
4
- const zod_1 = require("zod");
5
- /**
6
- * Base schema for all tools. Each tool should extend this with their specific parameters.
7
- */
8
- exports.BaseToolSchema = zod_1.z.object({
9
- name: zod_1.z.string(),
10
- description: zod_1.z.string(),
11
- parameters: zod_1.z.object({}).passthrough(),
12
- });
@@ -1,19 +0,0 @@
1
- import type OpenAI from "openai";
2
- import { z } from "zod";
3
- /**
4
- * Convert a tool schema to OpenAI tool format
5
- */
6
- export declare function zodToOpenAITool(schema: {
7
- name: string;
8
- description: string;
9
- parameters: z.ZodType;
10
- }): OpenAI.Chat.Completions.ChatCompletionTool;
11
- /**
12
- * Convert Zod schema to JSON Schema
13
- */
14
- export declare function zodToJsonSchema(schema: z.ZodType): any;
15
- /**
16
- * Convert specific Zod type to JSON Schema
17
- */
18
- export declare function zodTypeToJsonSchema(zodType: z.ZodType): any;
19
- //# sourceMappingURL=zod-schema.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"zod-schema.d.ts","sourceRoot":"","sources":["../../src/tools/zod-schema.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,MAAM,QAAQ,CAAC;AACjC,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;GAEG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE;IACtC,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,CAAC,CAAC,OAAO,CAAC;CACvB,GAAG,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,kBAAkB,CAS7C;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,CAAC,CAAC,OAAO,GAAG,GAAG,CAuBtD;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,CAAC,CAAC,OAAO,GAAG,GAAG,CAoD3D"}
@@ -1,95 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.zodTypeToJsonSchema = exports.zodToJsonSchema = exports.zodToOpenAITool = void 0;
4
- const zod_1 = require("zod");
5
- /**
6
- * Convert a tool schema to OpenAI tool format
7
- */
8
- function zodToOpenAITool(schema) {
9
- return {
10
- type: "function",
11
- function: {
12
- name: schema.name,
13
- description: schema.description,
14
- parameters: zodToJsonSchema(schema.parameters),
15
- },
16
- };
17
- }
18
- exports.zodToOpenAITool = zodToOpenAITool;
19
- /**
20
- * Convert Zod schema to JSON Schema
21
- */
22
- function zodToJsonSchema(schema) {
23
- if (schema instanceof zod_1.z.ZodObject) {
24
- const shape = schema._def.shape();
25
- const properties = {};
26
- const required = [];
27
- Object.entries(shape).forEach(([key, value]) => {
28
- properties[key] = zodTypeToJsonSchema(value);
29
- // Check if this field is required
30
- if (!(value instanceof zod_1.z.ZodOptional)) {
31
- required.push(key);
32
- }
33
- });
34
- return {
35
- type: "object",
36
- properties,
37
- ...(required.length > 0 ? { required } : {}),
38
- };
39
- }
40
- return { type: "string" }; // Fallback
41
- }
42
- exports.zodToJsonSchema = zodToJsonSchema;
43
- /**
44
- * Convert specific Zod type to JSON Schema
45
- */
46
- function zodTypeToJsonSchema(zodType) {
47
- // Handle string types
48
- if (zodType instanceof zod_1.z.ZodString) {
49
- const schema = { type: "string" };
50
- if (zodType.description)
51
- schema.description = zodType.description;
52
- return schema;
53
- }
54
- // Handle number types
55
- if (zodType instanceof zod_1.z.ZodNumber) {
56
- const schema = { type: "number" };
57
- if (zodType.description)
58
- schema.description = zodType.description;
59
- return schema;
60
- }
61
- // Handle boolean
62
- if (zodType instanceof zod_1.z.ZodBoolean) {
63
- const schema = { type: "boolean" };
64
- if (zodType.description)
65
- schema.description = zodType.description;
66
- return schema;
67
- }
68
- // Handle arrays
69
- if (zodType instanceof zod_1.z.ZodArray) {
70
- return {
71
- type: "array",
72
- items: zodTypeToJsonSchema(zodType._def.type),
73
- ...(zodType.description ? { description: zodType.description } : {}),
74
- };
75
- }
76
- // Handle objects
77
- if (zodType instanceof zod_1.z.ZodObject) {
78
- return zodToJsonSchema(zodType);
79
- }
80
- // Handle enums
81
- if (zodType instanceof zod_1.z.ZodEnum) {
82
- return {
83
- type: "string",
84
- enum: zodType._def.values,
85
- ...(zodType.description ? { description: zodType.description } : {}),
86
- };
87
- }
88
- // Handle optional types
89
- if (zodType instanceof zod_1.z.ZodOptional) {
90
- return zodTypeToJsonSchema(zodType._def.innerType);
91
- }
92
- // Default fallback
93
- return { type: "string" };
94
- }
95
- exports.zodTypeToJsonSchema = zodTypeToJsonSchema;