chrometools-mcp 3.3.8 → 3.3.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +40 -0
- package/README.md +129 -24
- package/SPEC-pom-integration.md +227 -0
- package/SPEC-swagger-api-tools.md +3101 -0
- package/index.js +503 -198
- package/package.json +2 -1
- package/pom/apom-tree-converter.js +5 -26
- package/recorder/page-object-generator.js +45 -1
- package/server/tool-definitions.js +54 -5
- package/server/tool-schemas.js +29 -0
- package/test-swagger-phase1.mjs +959 -0
- package/utils/api-generators/api-models-python.js +448 -0
- package/utils/api-generators/api-models-typescript.js +375 -0
- package/utils/code-generators/code-generator-base.js +111 -6
- package/utils/code-generators/playwright-python.js +74 -0
- package/utils/code-generators/playwright-typescript.js +69 -0
- package/utils/code-generators/pom-integrator.js +373 -0
- package/utils/code-generators/selenium-java.js +72 -0
- package/utils/code-generators/selenium-python.js +75 -0
- package/utils/hints-generator.js +114 -19
- package/utils/openapi/helpers.js +25 -0
- package/utils/openapi/parser.js +448 -0
- package/utils/openapi/ref-resolver.js +149 -0
- package/utils/openapi/type-mapper.js +174 -0
- package/nul +0 -0
package/index.js
CHANGED
|
@@ -72,6 +72,12 @@ import {PlaywrightPythonGenerator} from './utils/code-generators/playwright-pyth
|
|
|
72
72
|
import {SeleniumPythonGenerator} from './utils/code-generators/selenium-python.js';
|
|
73
73
|
import {SeleniumJavaGenerator} from './utils/code-generators/selenium-java.js';
|
|
74
74
|
import {FileAppender} from './utils/code-generators/file-appender.js';
|
|
75
|
+
import {parsePomFile} from './utils/code-generators/pom-integrator.js';
|
|
76
|
+
|
|
77
|
+
// Import OpenAPI / Swagger tools
|
|
78
|
+
import {OpenAPIParser} from './utils/openapi/parser.js';
|
|
79
|
+
import {ApiModelsTypeScriptGenerator} from './utils/api-generators/api-models-typescript.js';
|
|
80
|
+
import {ApiModelsPythonGenerator} from './utils/api-generators/api-models-python.js';
|
|
75
81
|
// Import Figma tools
|
|
76
82
|
import {
|
|
77
83
|
collectAllText,
|
|
@@ -343,6 +349,9 @@ async function executeToolInternal(name, args) {
|
|
|
343
349
|
|
|
344
350
|
let hintsText = '\n\n** AI HINTS **';
|
|
345
351
|
hintsText += `\nPage type: ${hints.pageType}`;
|
|
352
|
+
if (hints.heading) {
|
|
353
|
+
hintsText += `\nPage heading: "${hints.heading}"`;
|
|
354
|
+
}
|
|
346
355
|
if (hints.availableActions.length > 0) {
|
|
347
356
|
hintsText += `\nAvailable actions: ${hints.availableActions.join(', ')}`;
|
|
348
357
|
}
|
|
@@ -521,10 +530,32 @@ async function executeToolInternal(name, args) {
|
|
|
521
530
|
|
|
522
531
|
// 3. Format output with hints and diagnostics
|
|
523
532
|
let hintsText = '\n\n** AI HINTS **';
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
533
|
+
|
|
534
|
+
// Modal: show title, body text, and actions
|
|
535
|
+
if (hints.modalOpened && hints.newElements.some(e => e.type === 'modal')) {
|
|
536
|
+
const modal = hints.newElements.find(e => e.type === 'modal');
|
|
537
|
+
let modalText = 'Modal opened';
|
|
538
|
+
if (modal.title) modalText += `: "${modal.title}"`;
|
|
539
|
+
if (modal.text) modalText += `\n ${modal.text}`;
|
|
540
|
+
if (modal.actions?.length) modalText += `\n Actions: [${modal.actions.join('] [')}]`;
|
|
541
|
+
hintsText += '\n' + modalText;
|
|
542
|
+
} else if (hints.modalOpened) {
|
|
543
|
+
hintsText += '\nModal opened - interact with it or close';
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
// Overlay: show items
|
|
547
|
+
const overlay = hints.newElements.find(e => e.type === 'dropdown' || e.type === 'menu');
|
|
548
|
+
if (overlay?.items?.length) {
|
|
549
|
+
const label = overlay.type === 'menu' ? 'Menu' : 'Dropdown';
|
|
550
|
+
hintsText += `\n${label} with ${overlay.totalCount} options: ${overlay.items.join(', ')}`;
|
|
527
551
|
}
|
|
552
|
+
|
|
553
|
+
// Other new elements (alerts, etc.)
|
|
554
|
+
const otherElements = hints.newElements.filter(e => e.type !== 'modal' && e.type !== 'dropdown' && e.type !== 'menu');
|
|
555
|
+
if (otherElements.length > 0) {
|
|
556
|
+
hintsText += `\nNew elements appeared: ${otherElements.map(e => e.text ? `${e.type}: ${e.text}` : e.type).join(', ')}`;
|
|
557
|
+
}
|
|
558
|
+
|
|
528
559
|
if (hints.suggestedNext.length > 0) {
|
|
529
560
|
hintsText += `\nSuggested next: ${hints.suggestedNext.join('; ')}`;
|
|
530
561
|
}
|
|
@@ -1245,6 +1276,7 @@ async function executeToolInternal(name, args) {
|
|
|
1245
1276
|
|
|
1246
1277
|
const distance = validatedArgs.distance || 100;
|
|
1247
1278
|
const duration = validatedArgs.duration || 500;
|
|
1279
|
+
const mode = validatedArgs.mode || 'native';
|
|
1248
1280
|
|
|
1249
1281
|
// Calculate drag deltas based on direction
|
|
1250
1282
|
let deltaX = 0;
|
|
@@ -1281,62 +1313,166 @@ async function executeToolInternal(name, args) {
|
|
|
1281
1313
|
break;
|
|
1282
1314
|
}
|
|
1283
1315
|
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
const
|
|
1287
|
-
|
|
1288
|
-
|
|
1316
|
+
if (mode === 'synthetic') {
|
|
1317
|
+
// Synthetic mode: dispatch DOM events for better JS library compatibility
|
|
1318
|
+
const result = await page.evaluate((selector, deltaX, deltaY, duration) => {
|
|
1319
|
+
const element = document.querySelector(selector);
|
|
1320
|
+
if (!element) {
|
|
1321
|
+
return { success: false, error: `Element not found: ${selector}` };
|
|
1322
|
+
}
|
|
1323
|
+
|
|
1324
|
+
const rect = element.getBoundingClientRect();
|
|
1325
|
+
const startX = rect.left + rect.width / 2;
|
|
1326
|
+
const startY = rect.top + rect.height / 2;
|
|
1327
|
+
const endX = startX + deltaX;
|
|
1328
|
+
const endY = startY + deltaY;
|
|
1329
|
+
|
|
1330
|
+
// Helper to create mouse/pointer event
|
|
1331
|
+
const createEvent = (type, clientX, clientY, buttons = 0) => {
|
|
1332
|
+
// Try PointerEvent first (modern browsers)
|
|
1333
|
+
if (typeof PointerEvent !== 'undefined') {
|
|
1334
|
+
return new PointerEvent(type, {
|
|
1335
|
+
bubbles: true,
|
|
1336
|
+
cancelable: true,
|
|
1337
|
+
view: window,
|
|
1338
|
+
clientX,
|
|
1339
|
+
clientY,
|
|
1340
|
+
screenX: clientX,
|
|
1341
|
+
screenY: clientY,
|
|
1342
|
+
buttons,
|
|
1343
|
+
button: 0,
|
|
1344
|
+
pointerId: 1,
|
|
1345
|
+
pointerType: 'mouse',
|
|
1346
|
+
isPrimary: true
|
|
1347
|
+
});
|
|
1348
|
+
}
|
|
1349
|
+
// Fallback to MouseEvent
|
|
1350
|
+
return new MouseEvent(type, {
|
|
1351
|
+
bubbles: true,
|
|
1352
|
+
cancelable: true,
|
|
1353
|
+
view: window,
|
|
1354
|
+
clientX,
|
|
1355
|
+
clientY,
|
|
1356
|
+
screenX: clientX,
|
|
1357
|
+
screenY: clientY,
|
|
1358
|
+
buttons,
|
|
1359
|
+
button: 0
|
|
1360
|
+
});
|
|
1361
|
+
};
|
|
1362
|
+
|
|
1363
|
+
// Dispatch mousedown/pointerdown
|
|
1364
|
+
element.dispatchEvent(createEvent('pointerdown', startX, startY, 1));
|
|
1365
|
+
element.dispatchEvent(createEvent('mousedown', startX, startY, 1));
|
|
1366
|
+
|
|
1367
|
+
// Dispatch intermediate mousemove/pointermove events
|
|
1368
|
+
const steps = Math.max(10, Math.floor(duration / 20));
|
|
1369
|
+
const stepDelay = duration / steps;
|
|
1370
|
+
|
|
1371
|
+
return new Promise((resolve) => {
|
|
1372
|
+
let currentStep = 0;
|
|
1373
|
+
|
|
1374
|
+
const moveInterval = setInterval(() => {
|
|
1375
|
+
currentStep++;
|
|
1376
|
+
const progress = currentStep / steps;
|
|
1377
|
+
const currentX = startX + (deltaX * progress);
|
|
1378
|
+
const currentY = startY + (deltaY * progress);
|
|
1379
|
+
|
|
1380
|
+
element.dispatchEvent(createEvent('pointermove', currentX, currentY, 1));
|
|
1381
|
+
element.dispatchEvent(createEvent('mousemove', currentX, currentY, 1));
|
|
1382
|
+
|
|
1383
|
+
if (currentStep >= steps) {
|
|
1384
|
+
clearInterval(moveInterval);
|
|
1385
|
+
|
|
1386
|
+
// Dispatch mouseup/pointerup
|
|
1387
|
+
element.dispatchEvent(createEvent('pointerup', endX, endY, 0));
|
|
1388
|
+
element.dispatchEvent(createEvent('mouseup', endX, endY, 0));
|
|
1389
|
+
element.dispatchEvent(createEvent('click', endX, endY, 0));
|
|
1390
|
+
|
|
1391
|
+
resolve({
|
|
1392
|
+
success: true,
|
|
1393
|
+
startX: Math.round(startX),
|
|
1394
|
+
startY: Math.round(startY),
|
|
1395
|
+
endX: Math.round(endX),
|
|
1396
|
+
endY: Math.round(endY),
|
|
1397
|
+
mode: 'synthetic'
|
|
1398
|
+
});
|
|
1399
|
+
}
|
|
1400
|
+
}, stepDelay);
|
|
1401
|
+
});
|
|
1402
|
+
}, validatedArgs.selector, deltaX, deltaY, duration);
|
|
1403
|
+
|
|
1404
|
+
if (!result.success) {
|
|
1405
|
+
throw new Error(result.error);
|
|
1289
1406
|
}
|
|
1290
1407
|
|
|
1291
|
-
const rect = element.getBoundingClientRect();
|
|
1292
1408
|
return {
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1409
|
+
content: [{
|
|
1410
|
+
type: "text",
|
|
1411
|
+
text: `Dragged ${validatedArgs.selector} ${validatedArgs.direction} by ${distance}px (${result.mode} mode):\n` +
|
|
1412
|
+
` Start position: (${result.startX}, ${result.startY})\n` +
|
|
1413
|
+
` End position: (${result.endX}, ${result.endY})\n` +
|
|
1414
|
+
` Delta: (${deltaX}px, ${deltaY}px)\n` +
|
|
1415
|
+
` Duration: ${duration}ms\n` +
|
|
1416
|
+
` Events: pointerdown → ${Math.floor(duration / 20)} × pointermove → pointerup`
|
|
1417
|
+
}],
|
|
1298
1418
|
};
|
|
1299
|
-
}
|
|
1419
|
+
} else {
|
|
1420
|
+
// Native mode: use Puppeteer mouse API (default, faster)
|
|
1421
|
+
const elementInfo = await page.evaluate((selector) => {
|
|
1422
|
+
const element = document.querySelector(selector);
|
|
1423
|
+
if (!element) {
|
|
1424
|
+
return { success: false, error: `Element not found: ${selector}` };
|
|
1425
|
+
}
|
|
1300
1426
|
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1427
|
+
const rect = element.getBoundingClientRect();
|
|
1428
|
+
return {
|
|
1429
|
+
success: true,
|
|
1430
|
+
centerX: rect.left + rect.width / 2,
|
|
1431
|
+
centerY: rect.top + rect.height / 2,
|
|
1432
|
+
width: rect.width,
|
|
1433
|
+
height: rect.height
|
|
1434
|
+
};
|
|
1435
|
+
}, validatedArgs.selector);
|
|
1304
1436
|
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
const endX = startX + deltaX;
|
|
1309
|
-
const endY = startY + deltaY;
|
|
1437
|
+
if (!elementInfo.success) {
|
|
1438
|
+
throw new Error(elementInfo.error);
|
|
1439
|
+
}
|
|
1310
1440
|
|
|
1311
|
-
|
|
1312
|
-
|
|
1441
|
+
const startX = elementInfo.centerX;
|
|
1442
|
+
const startY = elementInfo.centerY;
|
|
1443
|
+
const endX = startX + deltaX;
|
|
1444
|
+
const endY = startY + deltaY;
|
|
1313
1445
|
|
|
1314
|
-
|
|
1315
|
-
|
|
1446
|
+
// Move to start position
|
|
1447
|
+
await page.mouse.move(startX, startY);
|
|
1316
1448
|
|
|
1317
|
-
|
|
1318
|
-
|
|
1449
|
+
// Press mouse button (start drag)
|
|
1450
|
+
await page.mouse.down();
|
|
1319
1451
|
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
await page.mouse.move(endX, endY, { steps });
|
|
1452
|
+
// Wait a bit to ensure drag is registered
|
|
1453
|
+
await new Promise(resolve => setTimeout(resolve, 50));
|
|
1323
1454
|
|
|
1324
|
-
|
|
1325
|
-
|
|
1455
|
+
// Move mouse to end position (drag)
|
|
1456
|
+
const steps = Math.max(10, Math.floor(duration / 20)); // Smooth movement
|
|
1457
|
+
await page.mouse.move(endX, endY, { steps });
|
|
1326
1458
|
|
|
1327
|
-
|
|
1328
|
-
|
|
1459
|
+
// Wait for duration
|
|
1460
|
+
await new Promise(resolve => setTimeout(resolve, Math.max(0, duration - steps * 20)));
|
|
1329
1461
|
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1462
|
+
// Release mouse button (end drag)
|
|
1463
|
+
await page.mouse.up();
|
|
1464
|
+
|
|
1465
|
+
return {
|
|
1466
|
+
content: [{
|
|
1467
|
+
type: "text",
|
|
1468
|
+
text: `Dragged ${validatedArgs.selector} ${validatedArgs.direction} by ${distance}px (native mode):\n` +
|
|
1469
|
+
` Start position: (${Math.round(startX)}, ${Math.round(startY)})\n` +
|
|
1470
|
+
` End position: (${Math.round(endX)}, ${Math.round(endY)})\n` +
|
|
1471
|
+
` Delta: (${deltaX}px, ${deltaY}px)\n` +
|
|
1472
|
+
` Duration: ${duration}ms`
|
|
1473
|
+
}],
|
|
1474
|
+
};
|
|
1475
|
+
}
|
|
1340
1476
|
}
|
|
1341
1477
|
|
|
1342
1478
|
if (name === "scrollHorizontal") {
|
|
@@ -1515,6 +1651,9 @@ async function executeToolInternal(name, args) {
|
|
|
1515
1651
|
|
|
1516
1652
|
let hintsText = '\n\n** AI HINTS **';
|
|
1517
1653
|
hintsText += `\nPage type: ${hints.pageType}`;
|
|
1654
|
+
if (hints.heading) {
|
|
1655
|
+
hintsText += `\nPage heading: "${hints.heading}"`;
|
|
1656
|
+
}
|
|
1518
1657
|
if (hints.availableActions.length > 0) {
|
|
1519
1658
|
hintsText += `\nAvailable actions: ${hints.availableActions.join(', ')}`;
|
|
1520
1659
|
}
|
|
@@ -2105,9 +2244,12 @@ Start coding now.`;
|
|
|
2105
2244
|
const maxResults = validatedArgs.maxResults || 5;
|
|
2106
2245
|
|
|
2107
2246
|
// Execute smart search in page context
|
|
2108
|
-
const results = await page.evaluate((description, maxResults, utilsCode) => {
|
|
2247
|
+
const results = await page.evaluate((description, maxResults, utilsCode, selectorResolverCode) => {
|
|
2109
2248
|
// Inject utilities into page context
|
|
2110
2249
|
eval(utilsCode);
|
|
2250
|
+
if (typeof registerElement === 'undefined') {
|
|
2251
|
+
eval(selectorResolverCode);
|
|
2252
|
+
}
|
|
2111
2253
|
|
|
2112
2254
|
// Determine element type from description
|
|
2113
2255
|
const elementType = determineElementType(description);
|
|
@@ -2163,18 +2305,29 @@ Start coding now.`;
|
|
|
2163
2305
|
});
|
|
2164
2306
|
|
|
2165
2307
|
// Filter and sort
|
|
2166
|
-
|
|
2308
|
+
const filtered = analyzed
|
|
2167
2309
|
.filter(r => r.score > 5) // Minimum threshold
|
|
2168
2310
|
.sort((a, b) => b.score - a.score)
|
|
2169
2311
|
.slice(0, maxResults);
|
|
2170
2312
|
|
|
2171
|
-
|
|
2313
|
+
// Register found elements in APOM registry and assign IDs
|
|
2314
|
+
filtered.forEach((result, idx) => {
|
|
2315
|
+
const apomId = `smart_${result.type}_${idx}`;
|
|
2316
|
+
result.id = apomId;
|
|
2317
|
+
if (typeof registerElement === 'function') {
|
|
2318
|
+
registerElement(apomId, result.selector, { source: 'smartFindElement' });
|
|
2319
|
+
}
|
|
2320
|
+
});
|
|
2321
|
+
|
|
2322
|
+
return filtered;
|
|
2323
|
+
|
|
2324
|
+
}, validatedArgs.description, maxResults, elementFinderUtils, selectorResolver);
|
|
2172
2325
|
|
|
2173
2326
|
const hints = {
|
|
2174
2327
|
totalCandidates: results.length,
|
|
2175
2328
|
bestMatch: results[0] || null,
|
|
2176
2329
|
suggestion: results.length > 0
|
|
2177
|
-
? `Use selector: ${results[0].selector}`
|
|
2330
|
+
? `Use id: "${results[0].id}" or selector: ${results[0].selector}`
|
|
2178
2331
|
: 'No good matches found. Try a different description.',
|
|
2179
2332
|
};
|
|
2180
2333
|
|
|
@@ -2536,8 +2689,11 @@ Start coding now.`;
|
|
|
2536
2689
|
const validatedArgs = schemas.FindElementsByTextSchema.parse(args);
|
|
2537
2690
|
const page = await getLastOpenPage();
|
|
2538
2691
|
|
|
2539
|
-
const elements = await page.evaluate((text, exact, caseSensitive, utilsCode) => {
|
|
2692
|
+
const elements = await page.evaluate((text, exact, caseSensitive, utilsCode, selectorResolverCode) => {
|
|
2540
2693
|
eval(utilsCode);
|
|
2694
|
+
if (typeof registerElement === 'undefined') {
|
|
2695
|
+
eval(selectorResolverCode);
|
|
2696
|
+
}
|
|
2541
2697
|
|
|
2542
2698
|
const results = [];
|
|
2543
2699
|
const searchText = caseSensitive ? text : text.toLowerCase();
|
|
@@ -2564,9 +2720,16 @@ Start coding now.`;
|
|
|
2564
2720
|
: compareText.includes(searchText);
|
|
2565
2721
|
|
|
2566
2722
|
if (matches) {
|
|
2723
|
+
const selector = getUniqueSelectorInPage(el);
|
|
2724
|
+
const type = el.tagName.toLowerCase();
|
|
2725
|
+
const apomId = `text_${type}_${results.length}`;
|
|
2726
|
+
if (typeof registerElement === 'function') {
|
|
2727
|
+
registerElement(apomId, selector, { source: 'findElementsByText' });
|
|
2728
|
+
}
|
|
2567
2729
|
results.push({
|
|
2568
|
-
|
|
2569
|
-
|
|
2730
|
+
id: apomId,
|
|
2731
|
+
selector,
|
|
2732
|
+
type,
|
|
2570
2733
|
text: elementText.substring(0, 100), // Only first 100 chars for preview
|
|
2571
2734
|
visible: el.offsetParent !== null, // Add visibility check
|
|
2572
2735
|
});
|
|
@@ -2574,7 +2737,7 @@ Start coding now.`;
|
|
|
2574
2737
|
});
|
|
2575
2738
|
|
|
2576
2739
|
return results;
|
|
2577
|
-
}, validatedArgs.text, validatedArgs.exact || false, validatedArgs.caseSensitive || false, elementFinderUtils);
|
|
2740
|
+
}, validatedArgs.text, validatedArgs.exact || false, validatedArgs.caseSensitive || false, elementFinderUtils, selectorResolver);
|
|
2578
2741
|
|
|
2579
2742
|
// Prioritize visible elements and limit results to prevent token overflow
|
|
2580
2743
|
const visibleElements = elements.filter(el => el.visible);
|
|
@@ -3089,13 +3252,102 @@ Start coding now.`;
|
|
|
3089
3252
|
};
|
|
3090
3253
|
}
|
|
3091
3254
|
|
|
3255
|
+
// Resolve pageObjectMode (backward compat: generatePageObject: true -> 'generate')
|
|
3256
|
+
const pageObjectMode = args.pageObjectMode || (args.generatePageObject ? 'generate' : 'none');
|
|
3257
|
+
|
|
3092
3258
|
// Select generator based on language
|
|
3093
|
-
let generator;
|
|
3094
3259
|
const options = {
|
|
3095
3260
|
cleanSelectors: args.cleanSelectors !== false, // default true
|
|
3096
3261
|
includeComments: args.includeComments !== false, // default true
|
|
3097
3262
|
};
|
|
3098
3263
|
|
|
3264
|
+
// Resolve POM elements for integrated modes
|
|
3265
|
+
let pomElements = null;
|
|
3266
|
+
let pomClassName = null;
|
|
3267
|
+
let pomImportPath = null;
|
|
3268
|
+
let pageObjectData = null;
|
|
3269
|
+
|
|
3270
|
+
if (pageObjectMode === 'generate-integrated' || pageObjectMode === 'generate') {
|
|
3271
|
+
try {
|
|
3272
|
+
const entryUrl = scenario.metadata?.entryUrl;
|
|
3273
|
+
if (entryUrl) {
|
|
3274
|
+
let page;
|
|
3275
|
+
try {
|
|
3276
|
+
page = await getLastOpenPage();
|
|
3277
|
+
const currentUrl = page.url();
|
|
3278
|
+
if (currentUrl !== entryUrl) {
|
|
3279
|
+
await page.goto(entryUrl, { waitUntil: 'networkidle2' });
|
|
3280
|
+
}
|
|
3281
|
+
} catch (error) {
|
|
3282
|
+
page = await getOrCreatePage(entryUrl);
|
|
3283
|
+
}
|
|
3284
|
+
|
|
3285
|
+
const pageObjectOptions = {
|
|
3286
|
+
className: args.pageObjectClassName || null,
|
|
3287
|
+
framework: args.language,
|
|
3288
|
+
includeComments: args.includeComments !== false,
|
|
3289
|
+
groupElements: true
|
|
3290
|
+
};
|
|
3291
|
+
|
|
3292
|
+
const pageObjectResult = await generatePageObject(page, pageObjectOptions);
|
|
3293
|
+
if (pageObjectResult.success) {
|
|
3294
|
+
const extension = args.language.includes('typescript') ? '.ts' :
|
|
3295
|
+
args.language.includes('java') ? '.java' : '.py';
|
|
3296
|
+
pageObjectData = {
|
|
3297
|
+
code: pageObjectResult.code,
|
|
3298
|
+
className: pageObjectResult.className,
|
|
3299
|
+
suggestedFileName: `${pageObjectResult.className}${extension}`,
|
|
3300
|
+
elementCount: pageObjectResult.elementCount
|
|
3301
|
+
};
|
|
3302
|
+
|
|
3303
|
+
if (pageObjectMode === 'generate-integrated') {
|
|
3304
|
+
pomElements = pageObjectResult.elements;
|
|
3305
|
+
pomClassName = pageObjectResult.className;
|
|
3306
|
+
}
|
|
3307
|
+
}
|
|
3308
|
+
}
|
|
3309
|
+
} catch (error) {
|
|
3310
|
+
// Page Object generation failed, continue without it
|
|
3311
|
+
}
|
|
3312
|
+
} else if (pageObjectMode === 'use-existing') {
|
|
3313
|
+
if (!args.pageObjectFile) {
|
|
3314
|
+
return {
|
|
3315
|
+
content: [{
|
|
3316
|
+
type: 'text',
|
|
3317
|
+
text: JSON.stringify({
|
|
3318
|
+
error: "pageObjectFile is required for 'use-existing' mode"
|
|
3319
|
+
}, null, 2)
|
|
3320
|
+
}],
|
|
3321
|
+
isError: true
|
|
3322
|
+
};
|
|
3323
|
+
}
|
|
3324
|
+
|
|
3325
|
+
try {
|
|
3326
|
+
const pomContent = FileAppender.readFile(args.pageObjectFile);
|
|
3327
|
+
const parsed = parsePomFile(pomContent, args.language);
|
|
3328
|
+
pomElements = parsed.elements;
|
|
3329
|
+
pomClassName = parsed.className;
|
|
3330
|
+
} catch (error) {
|
|
3331
|
+
return {
|
|
3332
|
+
content: [{
|
|
3333
|
+
type: 'text',
|
|
3334
|
+
text: JSON.stringify({
|
|
3335
|
+
error: `Failed to parse POM file: ${error.message}`
|
|
3336
|
+
}, null, 2)
|
|
3337
|
+
}],
|
|
3338
|
+
isError: true
|
|
3339
|
+
};
|
|
3340
|
+
}
|
|
3341
|
+
}
|
|
3342
|
+
|
|
3343
|
+
// Add POM options to generator
|
|
3344
|
+
if (pomElements && pomClassName) {
|
|
3345
|
+
options.pomElements = pomElements;
|
|
3346
|
+
options.pomClassName = pomClassName;
|
|
3347
|
+
options.pomImportPath = pomImportPath;
|
|
3348
|
+
}
|
|
3349
|
+
|
|
3350
|
+
let generator;
|
|
3099
3351
|
switch (args.language) {
|
|
3100
3352
|
case 'playwright-typescript':
|
|
3101
3353
|
generator = new PlaywrightTypeScriptGenerator(options);
|
|
@@ -3134,59 +3386,21 @@ Start coding now.`;
|
|
|
3134
3386
|
referenceTestName: args.referenceTestName
|
|
3135
3387
|
};
|
|
3136
3388
|
|
|
3137
|
-
// Generate Page Object if requested
|
|
3138
|
-
let pageObjectData = null;
|
|
3139
|
-
if (args.generatePageObject) {
|
|
3140
|
-
try {
|
|
3141
|
-
const entryUrl = scenario.metadata?.entryUrl;
|
|
3142
|
-
if (entryUrl) {
|
|
3143
|
-
let page;
|
|
3144
|
-
try {
|
|
3145
|
-
page = await getLastOpenPage();
|
|
3146
|
-
const currentUrl = page.url();
|
|
3147
|
-
if (currentUrl !== entryUrl) {
|
|
3148
|
-
await page.goto(entryUrl, { waitUntil: 'networkidle2' });
|
|
3149
|
-
}
|
|
3150
|
-
} catch (error) {
|
|
3151
|
-
page = await getOrCreatePage(entryUrl);
|
|
3152
|
-
}
|
|
3153
|
-
|
|
3154
|
-
const pageObjectOptions = {
|
|
3155
|
-
className: args.pageObjectClassName || null,
|
|
3156
|
-
framework: args.language,
|
|
3157
|
-
includeComments: args.includeComments !== false,
|
|
3158
|
-
groupElements: true
|
|
3159
|
-
};
|
|
3160
|
-
|
|
3161
|
-
const pageObjectResult = await generatePageObject(page, pageObjectOptions);
|
|
3162
|
-
if (pageObjectResult.success) {
|
|
3163
|
-
// Suggest filename based on className
|
|
3164
|
-
const extension = args.language.includes('typescript') ? '.ts' :
|
|
3165
|
-
args.language.includes('java') ? '.java' : '.py';
|
|
3166
|
-
pageObjectData = {
|
|
3167
|
-
code: pageObjectResult.code,
|
|
3168
|
-
className: pageObjectResult.className,
|
|
3169
|
-
suggestedFileName: `${pageObjectResult.className}${extension}`,
|
|
3170
|
-
elementCount: pageObjectResult.elementCount
|
|
3171
|
-
};
|
|
3172
|
-
}
|
|
3173
|
-
}
|
|
3174
|
-
} catch (error) {
|
|
3175
|
-
// Page Object generation failed, continue without it
|
|
3176
|
-
}
|
|
3177
|
-
}
|
|
3178
|
-
|
|
3179
3389
|
// Return JSON with instructions for Claude Code to append the test
|
|
3180
3390
|
const result = {
|
|
3181
3391
|
action: 'append_test',
|
|
3182
3392
|
targetFile: args.targetFile,
|
|
3183
|
-
testCode: testOnly,
|
|
3393
|
+
testCode: testOnly,
|
|
3184
3394
|
testName: args.testName || scenario.metadata?.name,
|
|
3185
3395
|
insertPosition: appendOptions.insertPosition,
|
|
3186
3396
|
referenceTestName: appendOptions.referenceTestName,
|
|
3187
3397
|
instruction: `Read file '${args.targetFile}', append the testCode at position '${appendOptions.insertPosition}', then write the file back.`
|
|
3188
3398
|
};
|
|
3189
3399
|
|
|
3400
|
+
if (pomClassName) {
|
|
3401
|
+
result.pomIntegration = { className: pomClassName, mode: pageObjectMode };
|
|
3402
|
+
}
|
|
3403
|
+
|
|
3190
3404
|
if (pageObjectData) {
|
|
3191
3405
|
result.pageObject = pageObjectData;
|
|
3192
3406
|
result.instruction += ` Also create a Page Object file '${pageObjectData.suggestedFileName}' with the provided pageObject.code.`;
|
|
@@ -3228,86 +3442,48 @@ Start coding now.`;
|
|
|
3228
3442
|
};
|
|
3229
3443
|
}
|
|
3230
3444
|
|
|
3231
|
-
//
|
|
3232
|
-
|
|
3445
|
+
// Resolve pageObjectMode (backward compat: generatePageObject: true -> 'generate')
|
|
3446
|
+
const pageObjectMode = args.pageObjectMode || (args.generatePageObject ? 'generate' : 'none');
|
|
3447
|
+
|
|
3233
3448
|
const options = {
|
|
3234
3449
|
cleanSelectors: args.cleanSelectors !== false, // default true
|
|
3235
3450
|
includeComments: args.includeComments !== false, // default true
|
|
3236
3451
|
};
|
|
3237
3452
|
|
|
3238
|
-
|
|
3239
|
-
|
|
3240
|
-
|
|
3241
|
-
|
|
3242
|
-
|
|
3243
|
-
|
|
3244
|
-
|
|
3245
|
-
|
|
3246
|
-
generator = new SeleniumPythonGenerator(options);
|
|
3247
|
-
break;
|
|
3248
|
-
case 'selenium-java':
|
|
3249
|
-
generator = new SeleniumJavaGenerator(options);
|
|
3250
|
-
break;
|
|
3251
|
-
default:
|
|
3453
|
+
// Resolve POM elements for integrated modes
|
|
3454
|
+
let pomElements = null;
|
|
3455
|
+
let pomClassName = null;
|
|
3456
|
+
let pageObjectData = null;
|
|
3457
|
+
const entryUrl = scenario.metadata?.entryUrl;
|
|
3458
|
+
|
|
3459
|
+
if (pageObjectMode === 'generate-integrated' || pageObjectMode === 'generate') {
|
|
3460
|
+
if (!entryUrl) {
|
|
3252
3461
|
return {
|
|
3253
3462
|
content: [{
|
|
3254
3463
|
type: 'text',
|
|
3255
3464
|
text: JSON.stringify({
|
|
3256
|
-
error:
|
|
3465
|
+
error: 'Cannot generate Page Object: scenario has no entryUrl in metadata'
|
|
3257
3466
|
}, null, 2)
|
|
3258
3467
|
}],
|
|
3259
3468
|
isError: true
|
|
3260
3469
|
};
|
|
3261
|
-
|
|
3262
|
-
|
|
3263
|
-
// Generate test code with full imports
|
|
3264
|
-
const testCode = generator.generate(scenario, options);
|
|
3265
|
-
|
|
3266
|
-
// Generate suggested filename
|
|
3267
|
-
const testName = scenario.metadata?.name || 'test';
|
|
3268
|
-
const extension = args.language.includes('typescript') ? '.spec.ts' :
|
|
3269
|
-
args.language.includes('java') ? 'Test.java' :
|
|
3270
|
-
args.language.includes('python') ? '_test.py' : '.test.js';
|
|
3271
|
-
const suggestedFileName = args.language.includes('java')
|
|
3272
|
-
? testName.charAt(0).toUpperCase() + testName.slice(1) + 'Test.java'
|
|
3273
|
-
: testName.replace(/\s+/g, '_').toLowerCase() + extension;
|
|
3470
|
+
}
|
|
3274
3471
|
|
|
3275
|
-
// If generatePageObject is requested, also generate Page Object class
|
|
3276
|
-
if (args.generatePageObject) {
|
|
3277
3472
|
try {
|
|
3278
|
-
// Get page - need to open at scenario's entry URL
|
|
3279
3473
|
let page;
|
|
3280
|
-
const entryUrl = scenario.metadata?.entryUrl;
|
|
3281
|
-
|
|
3282
|
-
if (!entryUrl) {
|
|
3283
|
-
return {
|
|
3284
|
-
content: [{
|
|
3285
|
-
type: 'text',
|
|
3286
|
-
text: JSON.stringify({
|
|
3287
|
-
error: 'Cannot generate Page Object: scenario has no entryUrl in metadata'
|
|
3288
|
-
}, null, 2)
|
|
3289
|
-
}],
|
|
3290
|
-
isError: true
|
|
3291
|
-
};
|
|
3292
|
-
}
|
|
3293
|
-
|
|
3294
|
-
// Try to get existing page or open new one
|
|
3295
3474
|
try {
|
|
3296
3475
|
page = await getLastOpenPage();
|
|
3297
|
-
// Navigate to entry URL if current page is different
|
|
3298
3476
|
const currentUrl = page.url();
|
|
3299
3477
|
if (currentUrl !== entryUrl) {
|
|
3300
3478
|
await page.goto(entryUrl, { waitUntil: 'networkidle2' });
|
|
3301
3479
|
}
|
|
3302
3480
|
} catch (error) {
|
|
3303
|
-
// No page open, create new one
|
|
3304
3481
|
page = await getOrCreatePage(entryUrl);
|
|
3305
3482
|
}
|
|
3306
3483
|
|
|
3307
|
-
// Generate Page Object
|
|
3308
3484
|
const pageObjectOptions = {
|
|
3309
3485
|
className: args.pageObjectClassName || null,
|
|
3310
|
-
framework: args.language,
|
|
3486
|
+
framework: args.language,
|
|
3311
3487
|
includeComments: args.includeComments !== false,
|
|
3312
3488
|
groupElements: true
|
|
3313
3489
|
};
|
|
@@ -3315,71 +3491,119 @@ Start coding now.`;
|
|
|
3315
3491
|
const pageObjectResult = await generatePageObject(page, pageObjectOptions);
|
|
3316
3492
|
|
|
3317
3493
|
if (pageObjectResult.success) {
|
|
3318
|
-
// Suggest Page Object filename
|
|
3319
3494
|
const poExtension = args.language.includes('typescript') ? '.ts' :
|
|
3320
3495
|
args.language.includes('java') ? '.java' : '.py';
|
|
3321
|
-
|
|
3322
|
-
|
|
3323
|
-
|
|
3324
|
-
|
|
3325
|
-
|
|
3326
|
-
type: 'text',
|
|
3327
|
-
text: JSON.stringify({
|
|
3328
|
-
action: 'create_new_file',
|
|
3329
|
-
suggestedFileName: suggestedFileName,
|
|
3330
|
-
testCode: testCode,
|
|
3331
|
-
pageObject: {
|
|
3332
|
-
code: pageObjectResult.code,
|
|
3333
|
-
className: pageObjectResult.className,
|
|
3334
|
-
suggestedFileName: pageObjectFileName,
|
|
3335
|
-
elementCount: pageObjectResult.elementCount
|
|
3336
|
-
},
|
|
3337
|
-
instruction: `Create a new test file '${suggestedFileName}' with the testCode. Also create a Page Object file '${pageObjectFileName}' with the pageObject.code.`
|
|
3338
|
-
}, null, 2)
|
|
3339
|
-
}]
|
|
3340
|
-
};
|
|
3341
|
-
} else {
|
|
3342
|
-
// Page Object generation failed, return test code only with warning
|
|
3343
|
-
return {
|
|
3344
|
-
content: [{
|
|
3345
|
-
type: 'text',
|
|
3346
|
-
text: JSON.stringify({
|
|
3347
|
-
action: 'create_new_file',
|
|
3348
|
-
suggestedFileName: suggestedFileName,
|
|
3349
|
-
testCode: testCode,
|
|
3350
|
-
warning: 'Page Object generation failed: ' + (pageObjectResult.error || 'Unknown error'),
|
|
3351
|
-
instruction: `Create a new test file '${suggestedFileName}' with the testCode.`
|
|
3352
|
-
}, null, 2)
|
|
3353
|
-
}]
|
|
3496
|
+
pageObjectData = {
|
|
3497
|
+
code: pageObjectResult.code,
|
|
3498
|
+
className: pageObjectResult.className,
|
|
3499
|
+
suggestedFileName: `${pageObjectResult.className}${poExtension}`,
|
|
3500
|
+
elementCount: pageObjectResult.elementCount
|
|
3354
3501
|
};
|
|
3502
|
+
|
|
3503
|
+
if (pageObjectMode === 'generate-integrated') {
|
|
3504
|
+
pomElements = pageObjectResult.elements;
|
|
3505
|
+
pomClassName = pageObjectResult.className;
|
|
3506
|
+
}
|
|
3355
3507
|
}
|
|
3356
3508
|
} catch (error) {
|
|
3357
|
-
// Page Object generation failed,
|
|
3509
|
+
// Page Object generation failed, continue without it
|
|
3510
|
+
}
|
|
3511
|
+
} else if (pageObjectMode === 'use-existing') {
|
|
3512
|
+
if (!args.pageObjectFile) {
|
|
3358
3513
|
return {
|
|
3359
3514
|
content: [{
|
|
3360
3515
|
type: 'text',
|
|
3361
3516
|
text: JSON.stringify({
|
|
3362
|
-
|
|
3363
|
-
suggestedFileName: suggestedFileName,
|
|
3364
|
-
testCode: testCode,
|
|
3365
|
-
warning: 'Page Object generation error: ' + error.message,
|
|
3366
|
-
instruction: `Create a new test file '${suggestedFileName}' with the testCode.`
|
|
3517
|
+
error: "pageObjectFile is required for 'use-existing' mode"
|
|
3367
3518
|
}, null, 2)
|
|
3368
|
-
}]
|
|
3519
|
+
}],
|
|
3520
|
+
isError: true
|
|
3521
|
+
};
|
|
3522
|
+
}
|
|
3523
|
+
|
|
3524
|
+
try {
|
|
3525
|
+
const pomContent = FileAppender.readFile(args.pageObjectFile);
|
|
3526
|
+
const parsed = parsePomFile(pomContent, args.language);
|
|
3527
|
+
pomElements = parsed.elements;
|
|
3528
|
+
pomClassName = parsed.className;
|
|
3529
|
+
} catch (error) {
|
|
3530
|
+
return {
|
|
3531
|
+
content: [{
|
|
3532
|
+
type: 'text',
|
|
3533
|
+
text: JSON.stringify({
|
|
3534
|
+
error: `Failed to parse POM file: ${error.message}`
|
|
3535
|
+
}, null, 2)
|
|
3536
|
+
}],
|
|
3537
|
+
isError: true
|
|
3369
3538
|
};
|
|
3370
3539
|
}
|
|
3371
3540
|
}
|
|
3372
3541
|
|
|
3373
|
-
//
|
|
3542
|
+
// Add POM options to generator
|
|
3543
|
+
if (pomElements && pomClassName) {
|
|
3544
|
+
options.pomElements = pomElements;
|
|
3545
|
+
options.pomClassName = pomClassName;
|
|
3546
|
+
}
|
|
3547
|
+
|
|
3548
|
+
let generator;
|
|
3549
|
+
switch (args.language) {
|
|
3550
|
+
case 'playwright-typescript':
|
|
3551
|
+
generator = new PlaywrightTypeScriptGenerator(options);
|
|
3552
|
+
break;
|
|
3553
|
+
case 'playwright-python':
|
|
3554
|
+
generator = new PlaywrightPythonGenerator(options);
|
|
3555
|
+
break;
|
|
3556
|
+
case 'selenium-python':
|
|
3557
|
+
generator = new SeleniumPythonGenerator(options);
|
|
3558
|
+
break;
|
|
3559
|
+
case 'selenium-java':
|
|
3560
|
+
generator = new SeleniumJavaGenerator(options);
|
|
3561
|
+
break;
|
|
3562
|
+
default:
|
|
3563
|
+
return {
|
|
3564
|
+
content: [{
|
|
3565
|
+
type: 'text',
|
|
3566
|
+
text: JSON.stringify({
|
|
3567
|
+
error: `Unknown language: ${args.language}. Supported: playwright-typescript, playwright-python, selenium-python, selenium-java`
|
|
3568
|
+
}, null, 2)
|
|
3569
|
+
}],
|
|
3570
|
+
isError: true
|
|
3571
|
+
};
|
|
3572
|
+
}
|
|
3573
|
+
|
|
3574
|
+
// Generate test code with full imports
|
|
3575
|
+
const testCode = generator.generate(scenario, options);
|
|
3576
|
+
|
|
3577
|
+
// Generate suggested filename
|
|
3578
|
+
const testName = scenario.metadata?.name || 'test';
|
|
3579
|
+
const extension = args.language.includes('typescript') ? '.spec.ts' :
|
|
3580
|
+
args.language.includes('java') ? 'Test.java' :
|
|
3581
|
+
args.language.includes('python') ? '_test.py' : '.test.js';
|
|
3582
|
+
const suggestedFileName = args.language.includes('java')
|
|
3583
|
+
? testName.charAt(0).toUpperCase() + testName.slice(1) + 'Test.java'
|
|
3584
|
+
: testName.replace(/\s+/g, '_').toLowerCase() + extension;
|
|
3585
|
+
|
|
3586
|
+
// Build result
|
|
3587
|
+
const result = {
|
|
3588
|
+
action: 'create_new_file',
|
|
3589
|
+
suggestedFileName: suggestedFileName,
|
|
3590
|
+
testCode: testCode,
|
|
3591
|
+
instruction: `Create a new test file '${suggestedFileName}' with the testCode.`
|
|
3592
|
+
};
|
|
3593
|
+
|
|
3594
|
+
if (pomClassName) {
|
|
3595
|
+
result.pomIntegration = { className: pomClassName, mode: pageObjectMode };
|
|
3596
|
+
}
|
|
3597
|
+
|
|
3598
|
+
if (pageObjectData) {
|
|
3599
|
+
result.pageObject = pageObjectData;
|
|
3600
|
+
result.instruction = `Create a new test file '${suggestedFileName}' with the testCode. Also create a Page Object file '${pageObjectData.suggestedFileName}' with the pageObject.code.`;
|
|
3601
|
+
}
|
|
3602
|
+
|
|
3374
3603
|
return {
|
|
3375
3604
|
content: [{
|
|
3376
3605
|
type: 'text',
|
|
3377
|
-
text: JSON.stringify(
|
|
3378
|
-
action: 'create_new_file',
|
|
3379
|
-
suggestedFileName: suggestedFileName,
|
|
3380
|
-
testCode: testCode,
|
|
3381
|
-
instruction: `Create a new test file '${suggestedFileName}' with the testCode.`
|
|
3382
|
-
}, null, 2)
|
|
3606
|
+
text: JSON.stringify(result, null, 2)
|
|
3383
3607
|
}]
|
|
3384
3608
|
};
|
|
3385
3609
|
}
|
|
@@ -3541,6 +3765,87 @@ Start coding now.`;
|
|
|
3541
3765
|
};
|
|
3542
3766
|
}
|
|
3543
3767
|
|
|
3768
|
+
// ========== API / Swagger Tools ==========
|
|
3769
|
+
|
|
3770
|
+
if (name === "loadSwagger") {
|
|
3771
|
+
const validatedArgs = schemas.LoadSwaggerSchema.parse(args);
|
|
3772
|
+
const parser = await OpenAPIParser.load(validatedArgs.source, validatedArgs.format || 'auto');
|
|
3773
|
+
const summary = parser.getSummary();
|
|
3774
|
+
|
|
3775
|
+
return {
|
|
3776
|
+
content: [{
|
|
3777
|
+
type: 'text',
|
|
3778
|
+
text: JSON.stringify({
|
|
3779
|
+
success: true,
|
|
3780
|
+
...summary,
|
|
3781
|
+
instruction: 'Use generateApiModels to generate typed models from these schemas.'
|
|
3782
|
+
}, null, 2)
|
|
3783
|
+
}]
|
|
3784
|
+
};
|
|
3785
|
+
}
|
|
3786
|
+
|
|
3787
|
+
if (name === "generateApiModels") {
|
|
3788
|
+
const validatedArgs = schemas.GenerateApiModelsSchema.parse(args);
|
|
3789
|
+
const parser = await OpenAPIParser.load(validatedArgs.source, validatedArgs.format || 'auto');
|
|
3790
|
+
let schemasObj = parser.getSchemas();
|
|
3791
|
+
|
|
3792
|
+
// Filter schemas if specified
|
|
3793
|
+
if (validatedArgs.schemas && validatedArgs.schemas.length > 0) {
|
|
3794
|
+
const filtered = {};
|
|
3795
|
+
for (const schemaName of validatedArgs.schemas) {
|
|
3796
|
+
if (schemasObj[schemaName]) filtered[schemaName] = schemasObj[schemaName];
|
|
3797
|
+
}
|
|
3798
|
+
schemasObj = filtered;
|
|
3799
|
+
}
|
|
3800
|
+
|
|
3801
|
+
const metadata = {
|
|
3802
|
+
title: parser.spec.info?.title || '',
|
|
3803
|
+
source: validatedArgs.source,
|
|
3804
|
+
version: parser.version
|
|
3805
|
+
};
|
|
3806
|
+
|
|
3807
|
+
let code, suggestedFileName;
|
|
3808
|
+
|
|
3809
|
+
if (validatedArgs.language === 'typescript') {
|
|
3810
|
+
const generator = new ApiModelsTypeScriptGenerator(schemasObj, {
|
|
3811
|
+
style: validatedArgs.style || 'interface',
|
|
3812
|
+
includeEnums: validatedArgs.includeEnums !== false,
|
|
3813
|
+
includeValidation: validatedArgs.includeValidation || false,
|
|
3814
|
+
});
|
|
3815
|
+
code = generator.generate(metadata);
|
|
3816
|
+
const titleSlug = (metadata.title || 'api').toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
|
|
3817
|
+
suggestedFileName = `${titleSlug}.models.ts`;
|
|
3818
|
+
} else {
|
|
3819
|
+
const generator = new ApiModelsPythonGenerator(schemasObj, {
|
|
3820
|
+
style: validatedArgs.pythonStyle || 'dataclass',
|
|
3821
|
+
includeEnums: validatedArgs.includeEnums !== false,
|
|
3822
|
+
includeValidation: validatedArgs.includeValidation || false,
|
|
3823
|
+
});
|
|
3824
|
+
code = generator.generate(metadata);
|
|
3825
|
+
const titleSlug = (metadata.title || 'api').toLowerCase().replace(/[^a-z0-9]+/g, '_').replace(/^_|_$/g, '');
|
|
3826
|
+
suggestedFileName = `${titleSlug}_models.py`;
|
|
3827
|
+
}
|
|
3828
|
+
|
|
3829
|
+
const schemaCount = Object.keys(schemasObj).length;
|
|
3830
|
+
const enumCount = Object.values(schemasObj).filter(s => s.enum && s.type === 'string').length;
|
|
3831
|
+
|
|
3832
|
+
return {
|
|
3833
|
+
content: [{
|
|
3834
|
+
type: 'text',
|
|
3835
|
+
text: JSON.stringify({
|
|
3836
|
+
action: 'create_new_file',
|
|
3837
|
+
suggestedFileName,
|
|
3838
|
+
code,
|
|
3839
|
+
schemaCount,
|
|
3840
|
+
enumCount,
|
|
3841
|
+
language: validatedArgs.language,
|
|
3842
|
+
source: validatedArgs.source,
|
|
3843
|
+
instruction: `Create file '${suggestedFileName}' with the generated code.`
|
|
3844
|
+
}, null, 2)
|
|
3845
|
+
}]
|
|
3846
|
+
};
|
|
3847
|
+
}
|
|
3848
|
+
|
|
3544
3849
|
return {
|
|
3545
3850
|
content: [
|
|
3546
3851
|
{
|