@vedangiitb/qwintly-core 1.4.2 → 1.4.4

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 (55) hide show
  1. package/dist/ai/prompts/codegen.prompt.d.ts.map +1 -1
  2. package/dist/ai/prompts/codegen.prompt.js +1 -0
  3. package/dist/ai/prompts/codegen.prompt.js.map +1 -1
  4. package/dist/ai/prompts/planner.prompt.d.ts.map +1 -1
  5. package/dist/ai/prompts/planner.prompt.js +1 -0
  6. package/dist/ai/prompts/planner.prompt.js.map +1 -1
  7. package/dist/ai/toolLoop/toolLoopRunner.d.ts.map +1 -1
  8. package/dist/ai/toolLoop/toolLoopRunner.js +13 -0
  9. package/dist/ai/toolLoop/toolLoopRunner.js.map +1 -1
  10. package/dist/ai/tools/helpers/pageConfigJson.helpers.d.ts +13 -0
  11. package/dist/ai/tools/helpers/pageConfigJson.helpers.d.ts.map +1 -1
  12. package/dist/ai/tools/helpers/pageConfigJson.helpers.js +94 -0
  13. package/dist/ai/tools/helpers/pageConfigJson.helpers.js.map +1 -1
  14. package/dist/ai/tools/implementations/createNewRoute.impl.d.ts.map +1 -1
  15. package/dist/ai/tools/implementations/createNewRoute.impl.js +26 -22
  16. package/dist/ai/tools/implementations/createNewRoute.impl.js.map +1 -1
  17. package/dist/ai/tools/implementations/deleteElement.impl.js +2 -2
  18. package/dist/ai/tools/implementations/deleteElement.impl.js.map +1 -1
  19. package/dist/ai/tools/implementations/insertElement.impl.js +2 -2
  20. package/dist/ai/tools/implementations/insertElement.impl.js.map +1 -1
  21. package/dist/ai/tools/implementations/updateClassName.impl.js +2 -2
  22. package/dist/ai/tools/implementations/updateClassName.impl.js.map +1 -1
  23. package/dist/ai/tools/implementations/updateProps.impl.js +2 -2
  24. package/dist/ai/tools/implementations/updateProps.impl.js.map +1 -1
  25. package/dist/ai/tools/schemas/createNewRoute.schema.js +1 -1
  26. package/dist/ai/tools/schemas/createNewRoute.schema.js.map +1 -1
  27. package/dist/ai/tools/schemas/deleteElement.schema.js +1 -1
  28. package/dist/ai/tools/schemas/deleteElement.schema.js.map +1 -1
  29. package/dist/ai/tools/schemas/getAvailableRoutes.schema.d.ts +10 -0
  30. package/dist/ai/tools/schemas/getAvailableRoutes.schema.d.ts.map +1 -0
  31. package/dist/ai/tools/schemas/getAvailableRoutes.schema.js +10 -0
  32. package/dist/ai/tools/schemas/getAvailableRoutes.schema.js.map +1 -0
  33. package/dist/ai/tools/schemas/insertElement.schema.js +1 -1
  34. package/dist/ai/tools/schemas/insertElement.schema.js.map +1 -1
  35. package/dist/ai/tools/schemas/updateClassName.schema.js +1 -1
  36. package/dist/ai/tools/schemas/updateClassName.schema.js.map +1 -1
  37. package/dist/ai/tools/schemas/updateProps.schema.js +1 -1
  38. package/dist/ai/tools/schemas/updateProps.schema.js.map +1 -1
  39. package/dist/ai/tools/toolsets/codegenTools.d.ts.map +1 -1
  40. package/dist/ai/tools/toolsets/codegenTools.js +2 -0
  41. package/dist/ai/tools/toolsets/codegenTools.js.map +1 -1
  42. package/dist/ai/tools/toolsets/plannerTools.d.ts.map +1 -1
  43. package/dist/ai/tools/toolsets/plannerTools.js +2 -0
  44. package/dist/ai/tools/toolsets/plannerTools.js.map +1 -1
  45. package/dist/ai/tools/validators/builderElement.zod.js +2 -2
  46. package/dist/ai/tools/validators/builderElement.zod.js.map +1 -1
  47. package/dist/tests/createNewRoute.impl.test.js +51 -2
  48. package/dist/tests/createNewRoute.impl.test.js.map +1 -1
  49. package/dist/tests/insertUpdate.impl.test.js +93 -0
  50. package/dist/tests/insertUpdate.impl.test.js.map +1 -1
  51. package/dist/tests/toolLoopRunner.routes.test.d.ts +2 -0
  52. package/dist/tests/toolLoopRunner.routes.test.d.ts.map +1 -0
  53. package/dist/tests/toolLoopRunner.routes.test.js +99 -0
  54. package/dist/tests/toolLoopRunner.routes.test.js.map +1 -0
  55. package/package.json +1 -1
@@ -4,6 +4,7 @@ import path from "node:path";
4
4
  import os from "node:os";
5
5
  import fs from "node:fs/promises";
6
6
  import { createCreateNewRouteImpl, DEFAULT_PAGE_CONFIG_JSON, PAGE_TSX_TEMPLATE_STRING, } from "../ai/tools/implementations/createNewRoute.impl.js";
7
+ import { getAvailableRoutes } from "../ai/tools/helpers/pageConfigJson.helpers.js";
7
8
  const makeRealFs = (overrides) => {
8
9
  return {
9
10
  readFile: async (absolutePath) => fs.readFile(absolutePath, "utf-8"),
@@ -65,13 +66,61 @@ test("create_new_route: atomic rollback on write failure", async () => {
65
66
  await fs.rm(workspaceRoot, { recursive: true, force: true });
66
67
  }
67
68
  });
68
- test("create_new_route: fails when parent route folder does not exist", async () => {
69
+ test("create_new_route: creates parent route when it does not exist", async () => {
69
70
  const workspaceRoot = await fs.mkdtemp(path.join(os.tmpdir(), "qwintly-core-"));
70
71
  try {
71
72
  await fs.mkdir(path.join(workspaceRoot, "app"), { recursive: true });
72
73
  const impl = createCreateNewRouteImpl({ workspaceRoot, fs: makeRealFs() });
73
74
  const res = await impl("/does-not-exist", "settings");
74
- assert.deepEqual(res, { success: false, error: "Parent route missing" });
75
+ assert.equal(res?.success, true);
76
+ assert.equal(res?.route, "/does-not-exist/settings");
77
+ // Check parent route files exist
78
+ const parentPage = await fs.readFile(path.join(workspaceRoot, "app", "does-not-exist", "page.tsx"), "utf-8");
79
+ assert.equal(parentPage, PAGE_TSX_TEMPLATE_STRING);
80
+ const parentConfig = await fs.readFile(path.join(workspaceRoot, "app", "does-not-exist", "pageConfig.json"), "utf-8");
81
+ assert.equal(parentConfig, DEFAULT_PAGE_CONFIG_JSON);
82
+ // Check child route files exist
83
+ const childPage = await fs.readFile(path.join(workspaceRoot, "app", "does-not-exist", "settings", "page.tsx"), "utf-8");
84
+ assert.equal(childPage, PAGE_TSX_TEMPLATE_STRING);
85
+ }
86
+ finally {
87
+ await fs.rm(workspaceRoot, { recursive: true, force: true });
88
+ }
89
+ });
90
+ test("create_new_route: fallback to root route if parent is invalid (e.g. empty or invalid segments)", async () => {
91
+ const workspaceRoot = await fs.mkdtemp(path.join(os.tmpdir(), "qwintly-core-"));
92
+ try {
93
+ await fs.mkdir(path.join(workspaceRoot, "app"), { recursive: true });
94
+ const impl = createCreateNewRouteImpl({ workspaceRoot, fs: makeRealFs() });
95
+ // Empty parent route -> should create under /
96
+ const res1 = await impl("", "settings");
97
+ assert.equal(res1?.success, true);
98
+ assert.equal(res1?.route, "/settings");
99
+ // Invalid segment in parent route -> should fallback to / profile
100
+ const res2 = await impl("/../invalid-parent", "profile");
101
+ assert.equal(res2?.success, true);
102
+ assert.equal(res2?.route, "/profile");
103
+ }
104
+ finally {
105
+ await fs.rm(workspaceRoot, { recursive: true, force: true });
106
+ }
107
+ });
108
+ test("get_available_routes: helper scans app folder recursively and extracts routes", async () => {
109
+ const workspaceRoot = await fs.mkdtemp(path.join(os.tmpdir(), "qwintly-core-"));
110
+ try {
111
+ await fs.mkdir(path.join(workspaceRoot, "app"), { recursive: true });
112
+ // Root route /
113
+ await fs.writeFile(path.join(workspaceRoot, "app", "pageConfig.json"), "{}");
114
+ // Dashboard route /dashboard
115
+ await fs.mkdir(path.join(workspaceRoot, "app", "dashboard"), { recursive: true });
116
+ await fs.writeFile(path.join(workspaceRoot, "app", "dashboard", "pageConfig.json"), "{}");
117
+ // Nested route /dashboard/settings
118
+ await fs.mkdir(path.join(workspaceRoot, "app", "dashboard", "settings"), { recursive: true });
119
+ await fs.writeFile(path.join(workspaceRoot, "app", "dashboard", "settings", "pageConfig.json"), "{}");
120
+ // Non-route folder (no pageConfig.json)
121
+ await fs.mkdir(path.join(workspaceRoot, "app", "dashboard", "helpers"), { recursive: true });
122
+ const routes = await getAvailableRoutes({ workspaceRoot, fs: makeRealFs() });
123
+ assert.deepEqual(routes, ["/", "/dashboard", "/dashboard/settings"]);
75
124
  }
76
125
  finally {
77
126
  await fs.rm(workspaceRoot, { recursive: true, force: true });
@@ -1 +1 @@
1
- {"version":3,"file":"createNewRoute.impl.test.js","sourceRoot":"","sources":["../../src/tests/createNewRoute.impl.test.ts"],"names":[],"mappings":"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","sourcesContent":["import assert from \"node:assert/strict\";\nimport test from \"node:test\";\nimport path from \"node:path\";\nimport os from \"node:os\";\nimport fs from \"node:fs/promises\";\nimport {\n createCreateNewRouteImpl,\n DEFAULT_PAGE_CONFIG_JSON,\n PAGE_TSX_TEMPLATE_STRING,\n} from \"../ai/tools/implementations/createNewRoute.impl.js\";\n\ntype CoreFs = Parameters<typeof createCreateNewRouteImpl>[0][\"fs\"];\n\nconst makeRealFs = (overrides?: Partial<CoreFs>): CoreFs => {\n return {\n readFile: async (absolutePath) => fs.readFile(absolutePath, \"utf-8\"),\n writeFile: async (absolutePath, content) =>\n fs.writeFile(absolutePath, content ?? \"\", \"utf-8\"),\n mkdirp: async (absoluteDir) => {\n await fs.mkdir(absoluteDir, { recursive: true });\n },\n rmFile: async (absolutePath) => fs.rm(absolutePath, { force: true }),\n stat: async (absolutePath) => fs.stat(absolutePath),\n safeReadDir: async (absoluteDir) =>\n fs.readdir(absoluteDir, { withFileTypes: true }),\n ...(overrides ?? {}),\n };\n};\n\ntest(\"create_new_route: creates page.tsx and pageConfig.json under /app\", async () => {\n const workspaceRoot = await fs.mkdtemp(path.join(os.tmpdir(), \"qwintly-core-\"));\n try {\n await fs.mkdir(path.join(workspaceRoot, \"app\", \"dashboard\"), { recursive: true });\n\n const impl = createCreateNewRouteImpl({ workspaceRoot, fs: makeRealFs() } as any);\n const res = await impl(\"/dashboard\", \"settings\");\n assert.deepEqual(res, {\n success: true,\n route: \"/dashboard/settings\",\n created_files: [\n \"app/dashboard/settings/page.tsx\",\n \"app/dashboard/settings/pageConfig.json\",\n ],\n page_config_json: JSON.parse(DEFAULT_PAGE_CONFIG_JSON),\n });\n\n const pageTsx = await fs.readFile(\n path.join(workspaceRoot, \"app\", \"dashboard\", \"settings\", \"page.tsx\"),\n \"utf-8\",\n );\n const pageConfig = await fs.readFile(\n path.join(workspaceRoot, \"app\", \"dashboard\", \"settings\", \"pageConfig.json\"),\n \"utf-8\",\n );\n\n assert.equal(pageTsx, PAGE_TSX_TEMPLATE_STRING);\n assert.equal(pageConfig, DEFAULT_PAGE_CONFIG_JSON);\n } finally {\n await fs.rm(workspaceRoot, { recursive: true, force: true });\n }\n});\n\ntest(\"create_new_route: atomic rollback on write failure\", async () => {\n const workspaceRoot = await fs.mkdtemp(path.join(os.tmpdir(), \"qwintly-core-\"));\n try {\n const parentDir = path.join(workspaceRoot, \"app\", \"dashboard\");\n await fs.mkdir(parentDir, { recursive: true });\n\n const failingFs = makeRealFs({\n writeFile: async (absolutePath, content) => {\n if (absolutePath.replace(/\\\\/g, \"/\").endsWith(\"/pageConfig.json\")) {\n throw new Error(\"simulated write failure\");\n }\n await fs.writeFile(absolutePath, content ?? \"\", \"utf-8\");\n },\n });\n\n const impl = createCreateNewRouteImpl({ workspaceRoot, fs: failingFs } as any);\n const res = await impl(\"/dashboard\", \"settings\");\n assert.equal((res as any)?.success, false);\n\n const entries = await fs.readdir(parentDir);\n assert.ok(!entries.includes(\"settings\"), \"final route dir should not exist\");\n assert.ok(\n entries.every((e) => !e.startsWith(\".qwintly_route_tmp_settings_\")),\n \"temp dir should be cleaned up\",\n );\n } finally {\n await fs.rm(workspaceRoot, { recursive: true, force: true });\n }\n});\n\ntest(\"create_new_route: fails when parent route folder does not exist\", async () => {\n const workspaceRoot = await fs.mkdtemp(path.join(os.tmpdir(), \"qwintly-core-\"));\n try {\n await fs.mkdir(path.join(workspaceRoot, \"app\"), { recursive: true });\n const impl = createCreateNewRouteImpl({ workspaceRoot, fs: makeRealFs() } as any);\n const res = await impl(\"/does-not-exist\", \"settings\");\n assert.deepEqual(res, { success: false, error: \"Parent route missing\" });\n } finally {\n await fs.rm(workspaceRoot, { recursive: true, force: true });\n }\n});\n"]}
1
+ {"version":3,"file":"createNewRoute.impl.test.js","sourceRoot":"","sources":["../../src/tests/createNewRoute.impl.test.ts"],"names":[],"mappings":"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","sourcesContent":["import assert from \"node:assert/strict\";\nimport test from \"node:test\";\nimport path from \"node:path\";\nimport os from \"node:os\";\nimport fs from \"node:fs/promises\";\nimport {\n createCreateNewRouteImpl,\n DEFAULT_PAGE_CONFIG_JSON,\n PAGE_TSX_TEMPLATE_STRING,\n} from \"../ai/tools/implementations/createNewRoute.impl.js\";\nimport { getAvailableRoutes } from \"../ai/tools/helpers/pageConfigJson.helpers.js\";\n\ntype CoreFs = Parameters<typeof createCreateNewRouteImpl>[0][\"fs\"];\n\nconst makeRealFs = (overrides?: Partial<CoreFs>): CoreFs => {\n return {\n readFile: async (absolutePath) => fs.readFile(absolutePath, \"utf-8\"),\n writeFile: async (absolutePath, content) =>\n fs.writeFile(absolutePath, content ?? \"\", \"utf-8\"),\n mkdirp: async (absoluteDir) => {\n await fs.mkdir(absoluteDir, { recursive: true });\n },\n rmFile: async (absolutePath) => fs.rm(absolutePath, { force: true }),\n stat: async (absolutePath) => fs.stat(absolutePath),\n safeReadDir: async (absoluteDir) =>\n fs.readdir(absoluteDir, { withFileTypes: true }),\n ...(overrides ?? {}),\n };\n};\n\ntest(\"create_new_route: creates page.tsx and pageConfig.json under /app\", async () => {\n const workspaceRoot = await fs.mkdtemp(path.join(os.tmpdir(), \"qwintly-core-\"));\n try {\n await fs.mkdir(path.join(workspaceRoot, \"app\", \"dashboard\"), { recursive: true });\n\n const impl = createCreateNewRouteImpl({ workspaceRoot, fs: makeRealFs() } as any);\n const res = await impl(\"/dashboard\", \"settings\");\n assert.deepEqual(res, {\n success: true,\n route: \"/dashboard/settings\",\n created_files: [\n \"app/dashboard/settings/page.tsx\",\n \"app/dashboard/settings/pageConfig.json\",\n ],\n page_config_json: JSON.parse(DEFAULT_PAGE_CONFIG_JSON),\n });\n\n const pageTsx = await fs.readFile(\n path.join(workspaceRoot, \"app\", \"dashboard\", \"settings\", \"page.tsx\"),\n \"utf-8\",\n );\n const pageConfig = await fs.readFile(\n path.join(workspaceRoot, \"app\", \"dashboard\", \"settings\", \"pageConfig.json\"),\n \"utf-8\",\n );\n\n assert.equal(pageTsx, PAGE_TSX_TEMPLATE_STRING);\n assert.equal(pageConfig, DEFAULT_PAGE_CONFIG_JSON);\n } finally {\n await fs.rm(workspaceRoot, { recursive: true, force: true });\n }\n});\n\ntest(\"create_new_route: atomic rollback on write failure\", async () => {\n const workspaceRoot = await fs.mkdtemp(path.join(os.tmpdir(), \"qwintly-core-\"));\n try {\n const parentDir = path.join(workspaceRoot, \"app\", \"dashboard\");\n await fs.mkdir(parentDir, { recursive: true });\n\n const failingFs = makeRealFs({\n writeFile: async (absolutePath, content) => {\n if (absolutePath.replace(/\\\\/g, \"/\").endsWith(\"/pageConfig.json\")) {\n throw new Error(\"simulated write failure\");\n }\n await fs.writeFile(absolutePath, content ?? \"\", \"utf-8\");\n },\n });\n\n const impl = createCreateNewRouteImpl({ workspaceRoot, fs: failingFs } as any);\n const res = await impl(\"/dashboard\", \"settings\");\n assert.equal((res as any)?.success, false);\n\n const entries = await fs.readdir(parentDir);\n assert.ok(!entries.includes(\"settings\"), \"final route dir should not exist\");\n assert.ok(\n entries.every((e) => !e.startsWith(\".qwintly_route_tmp_settings_\")),\n \"temp dir should be cleaned up\",\n );\n } finally {\n await fs.rm(workspaceRoot, { recursive: true, force: true });\n }\n});\n\ntest(\"create_new_route: creates parent route when it does not exist\", async () => {\n const workspaceRoot = await fs.mkdtemp(path.join(os.tmpdir(), \"qwintly-core-\"));\n try {\n await fs.mkdir(path.join(workspaceRoot, \"app\"), { recursive: true });\n const impl = createCreateNewRouteImpl({ workspaceRoot, fs: makeRealFs() } as any);\n const res = await impl(\"/does-not-exist\", \"settings\");\n assert.equal((res as any)?.success, true);\n assert.equal((res as any)?.route, \"/does-not-exist/settings\");\n\n // Check parent route files exist\n const parentPage = await fs.readFile(\n path.join(workspaceRoot, \"app\", \"does-not-exist\", \"page.tsx\"),\n \"utf-8\",\n );\n assert.equal(parentPage, PAGE_TSX_TEMPLATE_STRING);\n\n const parentConfig = await fs.readFile(\n path.join(workspaceRoot, \"app\", \"does-not-exist\", \"pageConfig.json\"),\n \"utf-8\",\n );\n assert.equal(parentConfig, DEFAULT_PAGE_CONFIG_JSON);\n\n // Check child route files exist\n const childPage = await fs.readFile(\n path.join(workspaceRoot, \"app\", \"does-not-exist\", \"settings\", \"page.tsx\"),\n \"utf-8\",\n );\n assert.equal(childPage, PAGE_TSX_TEMPLATE_STRING);\n } finally {\n await fs.rm(workspaceRoot, { recursive: true, force: true });\n }\n});\n\ntest(\"create_new_route: fallback to root route if parent is invalid (e.g. empty or invalid segments)\", async () => {\n const workspaceRoot = await fs.mkdtemp(path.join(os.tmpdir(), \"qwintly-core-\"));\n try {\n await fs.mkdir(path.join(workspaceRoot, \"app\"), { recursive: true });\n const impl = createCreateNewRouteImpl({ workspaceRoot, fs: makeRealFs() } as any);\n \n // Empty parent route -> should create under /\n const res1 = await impl(\"\", \"settings\");\n assert.equal((res1 as any)?.success, true);\n assert.equal((res1 as any)?.route, \"/settings\");\n\n // Invalid segment in parent route -> should fallback to / profile\n const res2 = await impl(\"/../invalid-parent\", \"profile\");\n assert.equal((res2 as any)?.success, true);\n assert.equal((res2 as any)?.route, \"/profile\");\n } finally {\n await fs.rm(workspaceRoot, { recursive: true, force: true });\n }\n});\n\ntest(\"get_available_routes: helper scans app folder recursively and extracts routes\", async () => {\n const workspaceRoot = await fs.mkdtemp(path.join(os.tmpdir(), \"qwintly-core-\"));\n try {\n await fs.mkdir(path.join(workspaceRoot, \"app\"), { recursive: true });\n // Root route /\n await fs.writeFile(path.join(workspaceRoot, \"app\", \"pageConfig.json\"), \"{}\");\n \n // Dashboard route /dashboard\n await fs.mkdir(path.join(workspaceRoot, \"app\", \"dashboard\"), { recursive: true });\n await fs.writeFile(path.join(workspaceRoot, \"app\", \"dashboard\", \"pageConfig.json\"), \"{}\");\n\n // Nested route /dashboard/settings\n await fs.mkdir(path.join(workspaceRoot, \"app\", \"dashboard\", \"settings\"), { recursive: true });\n await fs.writeFile(path.join(workspaceRoot, \"app\", \"dashboard\", \"settings\", \"pageConfig.json\"), \"{}\");\n\n // Non-route folder (no pageConfig.json)\n await fs.mkdir(path.join(workspaceRoot, \"app\", \"dashboard\", \"helpers\"), { recursive: true });\n\n const routes = await getAvailableRoutes({ workspaceRoot, fs: makeRealFs() } as any);\n assert.deepEqual(routes, [\"/\", \"/dashboard\", \"/dashboard/settings\"]);\n } finally {\n await fs.rm(workspaceRoot, { recursive: true, force: true });\n }\n});\n"]}
@@ -6,6 +6,8 @@ import fs from "node:fs/promises";
6
6
  import { createInsertElementImpl } from "../ai/tools/implementations/insertElement.impl.js";
7
7
  import { createUpdatePropsImpl } from "../ai/tools/implementations/updateProps.impl.js";
8
8
  import { createUpdateClassNameImpl } from "../ai/tools/implementations/updateClassName.impl.js";
9
+ import { createDeleteElementImpl } from "../ai/tools/implementations/deleteElement.impl.js";
10
+ import { matchRoute } from "../ai/tools/helpers/pageConfigJson.helpers.js";
9
11
  const makeRealFs = (overrides) => {
10
12
  return {
11
13
  readFile: async (absolutePath) => fs.readFile(absolutePath, "utf-8"),
@@ -77,4 +79,95 @@ test("insert/update tools: inject ids, insert under parent, update props and cla
77
79
  await fs.rm(workspaceRoot, { recursive: true, force: true });
78
80
  }
79
81
  });
82
+ test("matchRoute helper correctly matches physical paths", () => {
83
+ // exact match
84
+ assert.equal(matchRoute("/", "/"), true);
85
+ assert.equal(matchRoute("/dashboard", "/dashboard"), true);
86
+ assert.equal(matchRoute("/dashboard", "/dashboard/"), true);
87
+ assert.equal(matchRoute("/dashboard/", "/dashboard"), true);
88
+ // dynamic segment
89
+ assert.equal(matchRoute("/product/[id]", "/product/123"), true);
90
+ assert.equal(matchRoute("/product/[id]", "/product/hi"), true);
91
+ assert.equal(matchRoute("/product/[id]", "/product/"), false);
92
+ assert.equal(matchRoute("/product/[id]", "/product/123/reviews"), false);
93
+ assert.equal(matchRoute("/product/[id]/reviews", "/product/abc/reviews"), true);
94
+ // catch-all segment
95
+ assert.equal(matchRoute("/blog/[...slug]", "/blog/react"), true);
96
+ assert.equal(matchRoute("/blog/[...slug]", "/blog/react/hooks/state"), true);
97
+ assert.equal(matchRoute("/blog/[...slug]", "/blog"), false); // required catch-all requires at least one segment
98
+ // optional catch-all segment
99
+ assert.equal(matchRoute("/blog/[[...slug]]", "/blog"), true);
100
+ assert.equal(matchRoute("/blog/[[...slug]]", "/blog/react"), true);
101
+ assert.equal(matchRoute("/blog/[[...slug]]", "/blog/react/hooks"), true);
102
+ });
103
+ test("dynamic/nested routes resolution in tools", async () => {
104
+ const workspaceRoot = await fs.mkdtemp(path.join(os.tmpdir(), "qwintly-core-dynamic-"));
105
+ try {
106
+ // 1. Create a dynamic route /product/[id]
107
+ const routeDir = path.join(workspaceRoot, "app", "product", "[id]");
108
+ await fs.mkdir(routeDir, { recursive: true });
109
+ const filePath = path.join(routeDir, "pageConfig.json");
110
+ await fs.writeFile(filePath, JSON.stringify({
111
+ elements: [
112
+ {
113
+ id: "root",
114
+ type: "div",
115
+ children: [{ id: "prod_title", type: "text", props: { text: "Product Name" } }],
116
+ },
117
+ ],
118
+ }, null, 2) + "\n", "utf-8");
119
+ // 2. Create a catch-all route /blog/[...slug]
120
+ const blogDir = path.join(workspaceRoot, "app", "blog", "[...slug]");
121
+ await fs.mkdir(blogDir, { recursive: true });
122
+ const blogFilePath = path.join(blogDir, "pageConfig.json");
123
+ await fs.writeFile(blogFilePath, JSON.stringify({
124
+ elements: [
125
+ {
126
+ id: "blog_root",
127
+ type: "div",
128
+ children: [{ id: "blog_content", type: "text", props: { text: "Blog Post" } }],
129
+ },
130
+ ],
131
+ }, null, 2) + "\n", "utf-8");
132
+ const deps = { workspaceRoot, fs: makeRealFs() };
133
+ const insert = createInsertElementImpl(deps);
134
+ const updateProps = createUpdatePropsImpl(deps);
135
+ const updateClassName = createUpdateClassNameImpl(deps);
136
+ const deleteElement = createDeleteElementImpl(deps);
137
+ // Test insert_element on dynamic route '/product/123'
138
+ const insRes = await insert("/product/123", "root", {
139
+ type: "button",
140
+ props: { text: "Buy Now" },
141
+ });
142
+ assert.equal(insRes.success, true);
143
+ // Verify it updated '/app/product/[id]/pageConfig.json'
144
+ const afterProduct = JSON.parse(await fs.readFile(filePath, "utf-8"));
145
+ assert.equal(afterProduct.elements[0].children.length, 2);
146
+ const newBtn = afterProduct.elements[0].children[1];
147
+ assert.equal(newBtn.type, "button");
148
+ assert.equal(newBtn.props.text, "Buy Now");
149
+ // Test update_props on dynamic route '/product/hi-there'
150
+ const upRes = await updateProps({
151
+ route: "/product/hi-there",
152
+ element_id: "prod_title",
153
+ text: "Updated Product Title",
154
+ });
155
+ assert.equal(upRes.success, true);
156
+ // Test update_classname on catch-all route '/blog/react/hooks/state'
157
+ const classRes = await updateClassName("/blog/react/hooks/state", "blog_content", "text-blue-500");
158
+ assert.equal(classRes.success, true);
159
+ // Verify blog config updated
160
+ const afterBlog = JSON.parse(await fs.readFile(blogFilePath, "utf-8"));
161
+ const updatedTitle = afterBlog.elements[0].children[0];
162
+ assert.equal(updatedTitle.className, "text-blue-500");
163
+ // Test delete_element on catch-all route
164
+ const delRes = await deleteElement("/blog/react/something-else", "blog_content");
165
+ assert.equal(delRes.success, true);
166
+ const afterDelBlog = JSON.parse(await fs.readFile(blogFilePath, "utf-8"));
167
+ assert.equal(afterDelBlog.elements[0].children.length, 0);
168
+ }
169
+ finally {
170
+ await fs.rm(workspaceRoot, { recursive: true, force: true });
171
+ }
172
+ });
80
173
  //# sourceMappingURL=insertUpdate.impl.test.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"insertUpdate.impl.test.js","sourceRoot":"","sources":["../../src/tests/insertUpdate.impl.test.ts"],"names":[],"mappings":"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","sourcesContent":["import assert from \"node:assert/strict\";\nimport test from \"node:test\";\nimport path from \"node:path\";\nimport os from \"node:os\";\nimport fs from \"node:fs/promises\";\nimport { createInsertElementImpl } from \"../ai/tools/implementations/insertElement.impl.js\";\nimport { createUpdatePropsImpl } from \"../ai/tools/implementations/updateProps.impl.js\";\nimport { createUpdateClassNameImpl } from \"../ai/tools/implementations/updateClassName.impl.js\";\n\ntype Deps = Parameters<typeof createInsertElementImpl>[0];\ntype CoreFs = Deps[\"fs\"];\n\nconst makeRealFs = (overrides?: Partial<CoreFs>): CoreFs => {\n return {\n readFile: async (absolutePath) => fs.readFile(absolutePath, \"utf-8\"),\n writeFile: async (absolutePath, content) =>\n fs.writeFile(absolutePath, content ?? \"\", \"utf-8\"),\n mkdirp: async (absoluteDir) => {\n await fs.mkdir(absoluteDir, { recursive: true });\n },\n rmFile: async (absolutePath) => fs.rm(absolutePath, { force: true }),\n stat: async (absolutePath) => fs.stat(absolutePath),\n safeReadDir: async (absoluteDir) =>\n fs.readdir(absoluteDir, { withFileTypes: true }),\n ...(overrides ?? {}),\n };\n};\n\ntest(\"insert/update tools: inject ids, insert under parent, update props and classname\", async () => {\n const workspaceRoot = await fs.mkdtemp(path.join(os.tmpdir(), \"qwintly-core-\"));\n try {\n const routeDir = path.join(workspaceRoot, \"app\", \"a\");\n await fs.mkdir(routeDir, { recursive: true });\n const filePath = path.join(routeDir, \"pageConfig.json\");\n await fs.writeFile(\n filePath,\n JSON.stringify(\n {\n elements: [\n {\n id: \"root\",\n type: \"div\",\n children: [{ id: \"existing\", type: \"text\", props: { text: \"x\" } }],\n },\n ],\n },\n null,\n 2,\n ) + \"\\n\",\n \"utf-8\",\n );\n\n const deps = { workspaceRoot, fs: makeRealFs() } as any;\n const insert = createInsertElementImpl(deps);\n const updateProps = createUpdatePropsImpl(deps);\n const updateClassName = createUpdateClassNameImpl(deps);\n\n const inserted = await insert(\"/a\", \"root\", {\n type: \"text\",\n props: { text: \"hello\" },\n } as any);\n assert.equal(\n (inserted as any).success,\n true,\n `unexpected response: ${JSON.stringify(inserted)}`,\n );\n const insertedId = (inserted as any).inserted_id as string;\n assert.ok(typeof insertedId === \"string\" && insertedId.startsWith(\"el_\"));\n\n const inserted2 = await insert({\n route: \"/a\",\n parent_id: \"root\",\n before_id: \"existing\",\n element: { type: \"text\", props: { text: \"first\" } },\n } as any);\n assert.equal(\n (inserted2 as any).success,\n true,\n `unexpected response: ${JSON.stringify(inserted2)}`,\n );\n const inserted2Id = (inserted2 as any).inserted_id as string;\n assert.ok(typeof inserted2Id === \"string\" && inserted2Id.startsWith(\"el_\"));\n\n const up1 = await updateProps({\n route: \"/a\",\n element_id: insertedId,\n text: \"updated\",\n href: \"/x\",\n } as any);\n assert.equal((up1 as any).success, true);\n\n const up2 = await updateClassName(\"/a\", insertedId, \"text-red-500\");\n assert.equal((up2 as any).success, true);\n\n const after = JSON.parse(await fs.readFile(filePath, \"utf-8\"));\n const children = after.elements[0].children as any[];\n assert.equal(children[0].id, inserted2Id);\n assert.equal(children[1].id, \"existing\");\n\n const found = children.find((e) => e.id === insertedId);\n assert.ok(found);\n assert.equal(found.props.text, \"updated\");\n assert.equal(found.props.href, \"/x\");\n assert.equal(found.className, \"text-red-500\");\n } finally {\n await fs.rm(workspaceRoot, { recursive: true, force: true });\n }\n});\n"]}
1
+ {"version":3,"file":"insertUpdate.impl.test.js","sourceRoot":"","sources":["../../src/tests/insertUpdate.impl.test.ts"],"names":[],"mappings":"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","sourcesContent":["import assert from \"node:assert/strict\";\nimport test from \"node:test\";\nimport path from \"node:path\";\nimport os from \"node:os\";\nimport fs from \"node:fs/promises\";\nimport { createInsertElementImpl } from \"../ai/tools/implementations/insertElement.impl.js\";\nimport { createUpdatePropsImpl } from \"../ai/tools/implementations/updateProps.impl.js\";\nimport { createUpdateClassNameImpl } from \"../ai/tools/implementations/updateClassName.impl.js\";\nimport { createDeleteElementImpl } from \"../ai/tools/implementations/deleteElement.impl.js\";\nimport { matchRoute } from \"../ai/tools/helpers/pageConfigJson.helpers.js\";\n\ntype Deps = Parameters<typeof createInsertElementImpl>[0];\ntype CoreFs = Deps[\"fs\"];\n\nconst makeRealFs = (overrides?: Partial<CoreFs>): CoreFs => {\n return {\n readFile: async (absolutePath) => fs.readFile(absolutePath, \"utf-8\"),\n writeFile: async (absolutePath, content) =>\n fs.writeFile(absolutePath, content ?? \"\", \"utf-8\"),\n mkdirp: async (absoluteDir) => {\n await fs.mkdir(absoluteDir, { recursive: true });\n },\n rmFile: async (absolutePath) => fs.rm(absolutePath, { force: true }),\n stat: async (absolutePath) => fs.stat(absolutePath),\n safeReadDir: async (absoluteDir) =>\n fs.readdir(absoluteDir, { withFileTypes: true }),\n ...(overrides ?? {}),\n };\n};\n\ntest(\"insert/update tools: inject ids, insert under parent, update props and classname\", async () => {\n const workspaceRoot = await fs.mkdtemp(path.join(os.tmpdir(), \"qwintly-core-\"));\n try {\n const routeDir = path.join(workspaceRoot, \"app\", \"a\");\n await fs.mkdir(routeDir, { recursive: true });\n const filePath = path.join(routeDir, \"pageConfig.json\");\n await fs.writeFile(\n filePath,\n JSON.stringify(\n {\n elements: [\n {\n id: \"root\",\n type: \"div\",\n children: [{ id: \"existing\", type: \"text\", props: { text: \"x\" } }],\n },\n ],\n },\n null,\n 2,\n ) + \"\\n\",\n \"utf-8\",\n );\n\n const deps = { workspaceRoot, fs: makeRealFs() } as any;\n const insert = createInsertElementImpl(deps);\n const updateProps = createUpdatePropsImpl(deps);\n const updateClassName = createUpdateClassNameImpl(deps);\n\n const inserted = await insert(\"/a\", \"root\", {\n type: \"text\",\n props: { text: \"hello\" },\n } as any);\n assert.equal(\n (inserted as any).success,\n true,\n `unexpected response: ${JSON.stringify(inserted)}`,\n );\n const insertedId = (inserted as any).inserted_id as string;\n assert.ok(typeof insertedId === \"string\" && insertedId.startsWith(\"el_\"));\n\n const inserted2 = await insert({\n route: \"/a\",\n parent_id: \"root\",\n before_id: \"existing\",\n element: { type: \"text\", props: { text: \"first\" } },\n } as any);\n assert.equal(\n (inserted2 as any).success,\n true,\n `unexpected response: ${JSON.stringify(inserted2)}`,\n );\n const inserted2Id = (inserted2 as any).inserted_id as string;\n assert.ok(typeof inserted2Id === \"string\" && inserted2Id.startsWith(\"el_\"));\n\n const up1 = await updateProps({\n route: \"/a\",\n element_id: insertedId,\n text: \"updated\",\n href: \"/x\",\n } as any);\n assert.equal((up1 as any).success, true);\n\n const up2 = await updateClassName(\"/a\", insertedId, \"text-red-500\");\n assert.equal((up2 as any).success, true);\n\n const after = JSON.parse(await fs.readFile(filePath, \"utf-8\"));\n const children = after.elements[0].children as any[];\n assert.equal(children[0].id, inserted2Id);\n assert.equal(children[1].id, \"existing\");\n\n const found = children.find((e) => e.id === insertedId);\n assert.ok(found);\n assert.equal(found.props.text, \"updated\");\n assert.equal(found.props.href, \"/x\");\n assert.equal(found.className, \"text-red-500\");\n } finally {\n await fs.rm(workspaceRoot, { recursive: true, force: true });\n }\n});\n\ntest(\"matchRoute helper correctly matches physical paths\", () => {\n // exact match\n assert.equal(matchRoute(\"/\", \"/\"), true);\n assert.equal(matchRoute(\"/dashboard\", \"/dashboard\"), true);\n assert.equal(matchRoute(\"/dashboard\", \"/dashboard/\"), true);\n assert.equal(matchRoute(\"/dashboard/\", \"/dashboard\"), true);\n \n // dynamic segment\n assert.equal(matchRoute(\"/product/[id]\", \"/product/123\"), true);\n assert.equal(matchRoute(\"/product/[id]\", \"/product/hi\"), true);\n assert.equal(matchRoute(\"/product/[id]\", \"/product/\"), false);\n assert.equal(matchRoute(\"/product/[id]\", \"/product/123/reviews\"), false);\n assert.equal(matchRoute(\"/product/[id]/reviews\", \"/product/abc/reviews\"), true);\n\n // catch-all segment\n assert.equal(matchRoute(\"/blog/[...slug]\", \"/blog/react\"), true);\n assert.equal(matchRoute(\"/blog/[...slug]\", \"/blog/react/hooks/state\"), true);\n assert.equal(matchRoute(\"/blog/[...slug]\", \"/blog\"), false); // required catch-all requires at least one segment\n\n // optional catch-all segment\n assert.equal(matchRoute(\"/blog/[[...slug]]\", \"/blog\"), true);\n assert.equal(matchRoute(\"/blog/[[...slug]]\", \"/blog/react\"), true);\n assert.equal(matchRoute(\"/blog/[[...slug]]\", \"/blog/react/hooks\"), true);\n});\n\ntest(\"dynamic/nested routes resolution in tools\", async () => {\n const workspaceRoot = await fs.mkdtemp(path.join(os.tmpdir(), \"qwintly-core-dynamic-\"));\n try {\n // 1. Create a dynamic route /product/[id]\n const routeDir = path.join(workspaceRoot, \"app\", \"product\", \"[id]\");\n await fs.mkdir(routeDir, { recursive: true });\n const filePath = path.join(routeDir, \"pageConfig.json\");\n await fs.writeFile(\n filePath,\n JSON.stringify(\n {\n elements: [\n {\n id: \"root\",\n type: \"div\",\n children: [{ id: \"prod_title\", type: \"text\", props: { text: \"Product Name\" } }],\n },\n ],\n },\n null,\n 2,\n ) + \"\\n\",\n \"utf-8\",\n );\n\n // 2. Create a catch-all route /blog/[...slug]\n const blogDir = path.join(workspaceRoot, \"app\", \"blog\", \"[...slug]\");\n await fs.mkdir(blogDir, { recursive: true });\n const blogFilePath = path.join(blogDir, \"pageConfig.json\");\n await fs.writeFile(\n blogFilePath,\n JSON.stringify(\n {\n elements: [\n {\n id: \"blog_root\",\n type: \"div\",\n children: [{ id: \"blog_content\", type: \"text\", props: { text: \"Blog Post\" } }],\n },\n ],\n },\n null,\n 2,\n ) + \"\\n\",\n \"utf-8\",\n );\n\n const deps = { workspaceRoot, fs: makeRealFs() } as any;\n const insert = createInsertElementImpl(deps);\n const updateProps = createUpdatePropsImpl(deps);\n const updateClassName = createUpdateClassNameImpl(deps);\n const deleteElement = createDeleteElementImpl(deps);\n\n // Test insert_element on dynamic route '/product/123'\n const insRes = await insert(\"/product/123\", \"root\", {\n type: \"button\",\n props: { text: \"Buy Now\" },\n } as any);\n assert.equal(insRes.success, true);\n\n // Verify it updated '/app/product/[id]/pageConfig.json'\n const afterProduct = JSON.parse(await fs.readFile(filePath, \"utf-8\"));\n assert.equal(afterProduct.elements[0].children.length, 2);\n const newBtn = afterProduct.elements[0].children[1];\n assert.equal(newBtn.type, \"button\");\n assert.equal(newBtn.props.text, \"Buy Now\");\n\n // Test update_props on dynamic route '/product/hi-there'\n const upRes = await updateProps({\n route: \"/product/hi-there\",\n element_id: \"prod_title\",\n text: \"Updated Product Title\",\n } as any);\n assert.equal(upRes.success, true);\n\n // Test update_classname on catch-all route '/blog/react/hooks/state'\n const classRes = await updateClassName(\"/blog/react/hooks/state\", \"blog_content\", \"text-blue-500\");\n assert.equal(classRes.success, true);\n\n // Verify blog config updated\n const afterBlog = JSON.parse(await fs.readFile(blogFilePath, \"utf-8\"));\n const updatedTitle = afterBlog.elements[0].children[0];\n assert.equal(updatedTitle.className, \"text-blue-500\");\n\n // Test delete_element on catch-all route\n const delRes = await deleteElement(\"/blog/react/something-else\", \"blog_content\");\n assert.equal(delRes.success, true);\n\n const afterDelBlog = JSON.parse(await fs.readFile(blogFilePath, \"utf-8\"));\n assert.equal(afterDelBlog.elements[0].children.length, 0);\n\n } finally {\n await fs.rm(workspaceRoot, { recursive: true, force: true });\n }\n});\n"]}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=toolLoopRunner.routes.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"toolLoopRunner.routes.test.d.ts","sourceRoot":"","sources":["../../src/tests/toolLoopRunner.routes.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,99 @@
1
+ import assert from "node:assert/strict";
2
+ import test from "node:test";
3
+ import path from "node:path";
4
+ import os from "node:os";
5
+ import fs from "node:fs/promises";
6
+ import { FunctionCallingConfigMode } from "@google/genai";
7
+ import { runToolLoop } from "../ai/toolLoop/toolLoopRunner.js";
8
+ test("tool loop: insert_element failure includes available routes and hint", async () => {
9
+ const workspaceRoot = await fs.mkdtemp(path.join(os.tmpdir(), "qwintly-core-runner-"));
10
+ try {
11
+ await fs.mkdir(path.join(workspaceRoot, "app"), { recursive: true });
12
+ // Let's create an existing route
13
+ await fs.mkdir(path.join(workspaceRoot, "app", "dashboard"), { recursive: true });
14
+ await fs.writeFile(path.join(workspaceRoot, "app", "dashboard", "pageConfig.json"), "{}");
15
+ let aiCalls = 0;
16
+ const aiCall = async () => {
17
+ aiCalls += 1;
18
+ if (aiCalls === 1) {
19
+ return {
20
+ functionCalls: [
21
+ {
22
+ name: "insert_element",
23
+ args: {
24
+ route: "/invalid-route",
25
+ parent_id: "root",
26
+ element: { type: "text", props: { text: "hi" } },
27
+ },
28
+ },
29
+ ],
30
+ };
31
+ }
32
+ return { functionCalls: [], text: "ok" };
33
+ };
34
+ const res = await runToolLoop({
35
+ initialContents: [],
36
+ tools: [],
37
+ workspaceRoot,
38
+ aiCall,
39
+ logger: async () => { },
40
+ toolCallingMode: FunctionCallingConfigMode.ANY,
41
+ maxSteps: 5,
42
+ keepFullTrace: false,
43
+ });
44
+ assert.equal(res.finalText, "ok");
45
+ const toolResponses = res.modelContents.filter((c) => c?.role === "user" &&
46
+ Array.isArray(c?.parts) &&
47
+ c.parts.some((p) => p?.functionResponse?.name === "insert_element"));
48
+ assert.equal(toolResponses.length, 1);
49
+ const response = toolResponses[0].parts.find((p) => p?.functionResponse?.name === "insert_element")?.functionResponse?.response;
50
+ assert.equal(response?.success, false);
51
+ assert.match(String(response?.error ?? ""), /insert_element failed/i);
52
+ assert.match(String(response?.error ?? ""), /create_new_route/i);
53
+ assert.deepEqual(response?.available_routes, ["/dashboard"]);
54
+ }
55
+ finally {
56
+ await fs.rm(workspaceRoot, { recursive: true, force: true });
57
+ }
58
+ });
59
+ test("tool loop: get_available_routes retrieves routes", async () => {
60
+ const workspaceRoot = await fs.mkdtemp(path.join(os.tmpdir(), "qwintly-core-runner-"));
61
+ try {
62
+ await fs.mkdir(path.join(workspaceRoot, "app"), { recursive: true });
63
+ await fs.writeFile(path.join(workspaceRoot, "app", "pageConfig.json"), "{}");
64
+ await fs.mkdir(path.join(workspaceRoot, "app", "dashboard"), { recursive: true });
65
+ await fs.writeFile(path.join(workspaceRoot, "app", "dashboard", "pageConfig.json"), "{}");
66
+ let aiCalls = 0;
67
+ const aiCall = async () => {
68
+ aiCalls += 1;
69
+ if (aiCalls === 1) {
70
+ return {
71
+ functionCalls: [{ name: "get_available_routes", args: {} }],
72
+ };
73
+ }
74
+ return { functionCalls: [], text: "ok" };
75
+ };
76
+ const res = await runToolLoop({
77
+ initialContents: [],
78
+ tools: [],
79
+ workspaceRoot,
80
+ aiCall,
81
+ logger: async () => { },
82
+ toolCallingMode: FunctionCallingConfigMode.ANY,
83
+ maxSteps: 5,
84
+ keepFullTrace: false,
85
+ });
86
+ assert.equal(res.finalText, "ok");
87
+ const toolResponses = res.modelContents.filter((c) => c?.role === "user" &&
88
+ Array.isArray(c?.parts) &&
89
+ c.parts.some((p) => p?.functionResponse?.name === "get_available_routes"));
90
+ assert.equal(toolResponses.length, 1);
91
+ const response = toolResponses[0].parts.find((p) => p?.functionResponse?.name === "get_available_routes")?.functionResponse?.response;
92
+ assert.equal(response?.success, true);
93
+ assert.deepEqual(response?.routes, ["/", "/dashboard"]);
94
+ }
95
+ finally {
96
+ await fs.rm(workspaceRoot, { recursive: true, force: true });
97
+ }
98
+ });
99
+ //# sourceMappingURL=toolLoopRunner.routes.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"toolLoopRunner.routes.test.js","sourceRoot":"","sources":["../../src/tests/toolLoopRunner.routes.test.ts"],"names":[],"mappings":"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","sourcesContent":["import assert from \"node:assert/strict\";\nimport test from \"node:test\";\nimport path from \"node:path\";\nimport os from \"node:os\";\nimport fs from \"node:fs/promises\";\nimport { FunctionCallingConfigMode } from \"@google/genai\";\nimport { runToolLoop } from \"../ai/toolLoop/toolLoopRunner.js\";\n\ntest(\"tool loop: insert_element failure includes available routes and hint\", async () => {\n const workspaceRoot = await fs.mkdtemp(path.join(os.tmpdir(), \"qwintly-core-runner-\"));\n try {\n await fs.mkdir(path.join(workspaceRoot, \"app\"), { recursive: true });\n // Let's create an existing route\n await fs.mkdir(path.join(workspaceRoot, \"app\", \"dashboard\"), { recursive: true });\n await fs.writeFile(path.join(workspaceRoot, \"app\", \"dashboard\", \"pageConfig.json\"), \"{}\");\n\n let aiCalls = 0;\n const aiCall = async () => {\n aiCalls += 1;\n if (aiCalls === 1) {\n return {\n functionCalls: [\n {\n name: \"insert_element\",\n args: {\n route: \"/invalid-route\",\n parent_id: \"root\",\n element: { type: \"text\", props: { text: \"hi\" } },\n },\n },\n ],\n };\n }\n return { functionCalls: [], text: \"ok\" };\n };\n\n const res = await runToolLoop({\n initialContents: [],\n tools: [],\n workspaceRoot,\n aiCall,\n logger: async () => {},\n toolCallingMode: FunctionCallingConfigMode.ANY,\n maxSteps: 5,\n keepFullTrace: false,\n });\n\n assert.equal(res.finalText, \"ok\");\n\n const toolResponses = res.modelContents.filter(\n (c: any) =>\n c?.role === \"user\" &&\n Array.isArray(c?.parts) &&\n c.parts.some((p: any) => p?.functionResponse?.name === \"insert_element\"),\n );\n assert.equal(toolResponses.length, 1);\n\n const response = toolResponses[0].parts.find(\n (p: any) => p?.functionResponse?.name === \"insert_element\",\n )?.functionResponse?.response;\n\n assert.equal(response?.success, false);\n assert.match(String(response?.error ?? \"\"), /insert_element failed/i);\n assert.match(String(response?.error ?? \"\"), /create_new_route/i);\n assert.deepEqual(response?.available_routes, [\"/dashboard\"]);\n } finally {\n await fs.rm(workspaceRoot, { recursive: true, force: true });\n }\n});\n\ntest(\"tool loop: get_available_routes retrieves routes\", async () => {\n const workspaceRoot = await fs.mkdtemp(path.join(os.tmpdir(), \"qwintly-core-runner-\"));\n try {\n await fs.mkdir(path.join(workspaceRoot, \"app\"), { recursive: true });\n await fs.writeFile(path.join(workspaceRoot, \"app\", \"pageConfig.json\"), \"{}\");\n await fs.mkdir(path.join(workspaceRoot, \"app\", \"dashboard\"), { recursive: true });\n await fs.writeFile(path.join(workspaceRoot, \"app\", \"dashboard\", \"pageConfig.json\"), \"{}\");\n\n let aiCalls = 0;\n const aiCall = async () => {\n aiCalls += 1;\n if (aiCalls === 1) {\n return {\n functionCalls: [{ name: \"get_available_routes\", args: {} }],\n };\n }\n return { functionCalls: [], text: \"ok\" };\n };\n\n const res = await runToolLoop({\n initialContents: [],\n tools: [],\n workspaceRoot,\n aiCall,\n logger: async () => {},\n toolCallingMode: FunctionCallingConfigMode.ANY,\n maxSteps: 5,\n keepFullTrace: false,\n });\n\n assert.equal(res.finalText, \"ok\");\n\n const toolResponses = res.modelContents.filter(\n (c: any) =>\n c?.role === \"user\" &&\n Array.isArray(c?.parts) &&\n c.parts.some((p: any) => p?.functionResponse?.name === \"get_available_routes\"),\n );\n assert.equal(toolResponses.length, 1);\n\n const response = toolResponses[0].parts.find(\n (p: any) => p?.functionResponse?.name === \"get_available_routes\",\n )?.functionResponse?.response;\n\n assert.equal(response?.success, true);\n assert.deepEqual(response?.routes, [\"/\", \"/dashboard\"]);\n } finally {\n await fs.rm(workspaceRoot, { recursive: true, force: true });\n }\n});\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vedangiitb/qwintly-core",
3
- "version": "1.4.2",
3
+ "version": "1.4.4",
4
4
  "description": "Qwintly Core",
5
5
  "homepage": "https://github.com/vedangiitb/qwintly-core#readme",
6
6
  "bugs": {