@objectql/core 1.7.1 → 1.7.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,746 @@
1
+ "use strict";
2
+ /**
3
+ * ObjectQL AI Agent - Programmatic API for AI-powered application generation
4
+ *
5
+ * This module provides a high-level API for using AI to generate and validate
6
+ * ObjectQL metadata programmatically.
7
+ */
8
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
9
+ if (k2 === undefined) k2 = k;
10
+ var desc = Object.getOwnPropertyDescriptor(m, k);
11
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
12
+ desc = { enumerable: true, get: function() { return m[k]; } };
13
+ }
14
+ Object.defineProperty(o, k2, desc);
15
+ }) : (function(o, m, k, k2) {
16
+ if (k2 === undefined) k2 = k;
17
+ o[k2] = m[k];
18
+ }));
19
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
20
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
21
+ }) : function(o, v) {
22
+ o["default"] = v;
23
+ });
24
+ var __importStar = (this && this.__importStar) || (function () {
25
+ var ownKeys = function(o) {
26
+ ownKeys = Object.getOwnPropertyNames || function (o) {
27
+ var ar = [];
28
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
29
+ return ar;
30
+ };
31
+ return ownKeys(o);
32
+ };
33
+ return function (mod) {
34
+ if (mod && mod.__esModule) return mod;
35
+ var result = {};
36
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
37
+ __setModuleDefault(result, mod);
38
+ return result;
39
+ };
40
+ })();
41
+ var __importDefault = (this && this.__importDefault) || function (mod) {
42
+ return (mod && mod.__esModule) ? mod : { "default": mod };
43
+ };
44
+ Object.defineProperty(exports, "__esModule", { value: true });
45
+ exports.ObjectQLAgent = void 0;
46
+ exports.createAgent = createAgent;
47
+ const openai_1 = __importDefault(require("openai"));
48
+ const yaml = __importStar(require("js-yaml"));
49
+ const validator_1 = require("./validator");
50
+ /**
51
+ * Regular expression patterns for parsing AI responses
52
+ */
53
+ const AI_RESPONSE_PATTERNS = {
54
+ /**
55
+ * Matches YAML file blocks with explicit headers in the format:
56
+ * # filename.object.yml or File: filename.object.yml
57
+ * followed by a YAML code block
58
+ *
59
+ * Groups:
60
+ * 1. filename (e.g., "user.object.yml")
61
+ * 2. YAML content
62
+ */
63
+ FILE_BLOCK_YAML: /(?:^|\n)(?:#|File:)\s*([a-zA-Z0-9_-]+\.[a-z]+\.yml)\s*\n```(?:yaml|yml)?\n([\s\S]*?)```/gi,
64
+ /**
65
+ * Matches TypeScript file blocks with explicit headers in the format:
66
+ * // filename.action.ts or File: filename.hook.ts or filename.test.ts
67
+ * followed by a TypeScript code block
68
+ *
69
+ * Groups:
70
+ * 1. filename (e.g., "approve_order.action.ts", "user.hook.ts", "user.test.ts")
71
+ * 2. TypeScript content
72
+ */
73
+ FILE_BLOCK_TS: /(?:^|\n)(?:\/\/|File:)\s*([a-zA-Z0-9_-]+\.(?:action|hook|test|spec)\.ts)\s*\n```(?:typescript|ts)?\n([\s\S]*?)```/gi,
74
+ /**
75
+ * Matches generic YAML/YML code blocks without explicit headers
76
+ *
77
+ * Groups:
78
+ * 1. YAML content
79
+ */
80
+ CODE_BLOCK_YAML: /```(?:yaml|yml)\n([\s\S]*?)```/g,
81
+ /**
82
+ * Matches generic TypeScript code blocks without explicit headers
83
+ *
84
+ * Groups:
85
+ * 1. TypeScript content
86
+ */
87
+ CODE_BLOCK_TS: /```(?:typescript|ts)\n([\s\S]*?)```/g,
88
+ };
89
+ /**
90
+ * ObjectQL AI Agent for programmatic application generation and validation
91
+ */
92
+ class ObjectQLAgent {
93
+ constructor(config) {
94
+ var _a;
95
+ this.config = {
96
+ apiKey: config.apiKey,
97
+ model: config.model || 'gpt-4',
98
+ temperature: (_a = config.temperature) !== null && _a !== void 0 ? _a : 0.7,
99
+ language: config.language || 'en',
100
+ };
101
+ this.openai = new openai_1.default({ apiKey: this.config.apiKey });
102
+ this.validator = new validator_1.Validator({ language: this.config.language });
103
+ }
104
+ /**
105
+ * Generate application metadata from natural language description
106
+ */
107
+ async generateApp(options) {
108
+ var _a, _b;
109
+ const systemPrompt = this.getSystemPrompt();
110
+ const userPrompt = this.buildGenerationPrompt(options);
111
+ try {
112
+ const completion = await this.openai.chat.completions.create({
113
+ model: this.config.model,
114
+ messages: [
115
+ { role: 'system', content: systemPrompt },
116
+ { role: 'user', content: userPrompt }
117
+ ],
118
+ temperature: this.config.temperature,
119
+ max_tokens: options.maxTokens || 4000,
120
+ });
121
+ const response = (_b = (_a = completion.choices[0]) === null || _a === void 0 ? void 0 : _a.message) === null || _b === void 0 ? void 0 : _b.content;
122
+ if (!response) {
123
+ return {
124
+ success: false,
125
+ files: [],
126
+ errors: ['No response from AI model'],
127
+ };
128
+ }
129
+ // Parse response and extract files
130
+ const files = this.parseGenerationResponse(response);
131
+ return {
132
+ success: files.length > 0,
133
+ files,
134
+ rawResponse: response,
135
+ errors: files.length === 0 ? ['Failed to extract metadata files from response'] : undefined,
136
+ };
137
+ }
138
+ catch (error) {
139
+ return {
140
+ success: false,
141
+ files: [],
142
+ errors: [error instanceof Error ? error.message : 'Unknown error'],
143
+ };
144
+ }
145
+ }
146
+ /**
147
+ * Validate metadata using AI
148
+ */
149
+ async validateMetadata(options) {
150
+ var _a, _b;
151
+ // Parse metadata if it's a string
152
+ let parsedMetadata;
153
+ if (typeof options.metadata === 'string') {
154
+ try {
155
+ parsedMetadata = yaml.load(options.metadata);
156
+ }
157
+ catch (error) {
158
+ return {
159
+ valid: false,
160
+ errors: [{
161
+ message: `YAML parsing error: ${error instanceof Error ? error.message : 'Invalid YAML'}`,
162
+ location: 'root',
163
+ }],
164
+ warnings: [],
165
+ info: [],
166
+ };
167
+ }
168
+ }
169
+ else {
170
+ parsedMetadata = options.metadata;
171
+ }
172
+ // Build validation prompt
173
+ const validationPrompt = this.buildValidationPrompt(options);
174
+ try {
175
+ const completion = await this.openai.chat.completions.create({
176
+ model: this.config.model,
177
+ messages: [
178
+ { role: 'system', content: this.getValidationSystemPrompt() },
179
+ { role: 'user', content: validationPrompt }
180
+ ],
181
+ temperature: 0.3, // Lower temperature for more consistent validation
182
+ max_tokens: 2000,
183
+ });
184
+ const feedback = ((_b = (_a = completion.choices[0]) === null || _a === void 0 ? void 0 : _a.message) === null || _b === void 0 ? void 0 : _b.content) || '';
185
+ // Parse feedback into structured result
186
+ return this.parseFeedback(feedback);
187
+ }
188
+ catch (error) {
189
+ return {
190
+ valid: false,
191
+ errors: [{
192
+ message: `Validation error: ${error instanceof Error ? error.message : 'Unknown error'}`,
193
+ }],
194
+ warnings: [],
195
+ info: [],
196
+ };
197
+ }
198
+ }
199
+ /**
200
+ * Refine existing metadata based on feedback
201
+ */
202
+ async refineMetadata(metadata, feedback, iterations = 1) {
203
+ var _a, _b;
204
+ const systemPrompt = this.getSystemPrompt();
205
+ let currentMetadata = metadata;
206
+ let messages = [
207
+ { role: 'system', content: systemPrompt },
208
+ { role: 'user', content: `Here is the current metadata:\n\n${metadata}\n\nPlease refine it based on this feedback: ${feedback}` }
209
+ ];
210
+ for (let i = 0; i < iterations; i++) {
211
+ try {
212
+ const completion = await this.openai.chat.completions.create({
213
+ model: this.config.model,
214
+ messages,
215
+ temperature: 0.5,
216
+ max_tokens: 4000,
217
+ });
218
+ const response = (_b = (_a = completion.choices[0]) === null || _a === void 0 ? void 0 : _a.message) === null || _b === void 0 ? void 0 : _b.content;
219
+ if (!response)
220
+ break;
221
+ currentMetadata = response;
222
+ messages.push({ role: 'assistant', content: response });
223
+ // If this isn't the last iteration, validate and continue
224
+ if (i < iterations - 1) {
225
+ const validation = await this.validateMetadata({
226
+ metadata: response,
227
+ checkBusinessLogic: true,
228
+ });
229
+ if (validation.valid) {
230
+ break; // Stop if validation passes
231
+ }
232
+ // Add validation feedback for next iteration
233
+ const validationFeedback = [
234
+ ...validation.errors.map(e => `ERROR: ${e.message}`),
235
+ ...validation.warnings.map(w => `WARNING: ${w.message}`)
236
+ ].join('\n');
237
+ messages.push({
238
+ role: 'user',
239
+ content: `Please address these issues:\n${validationFeedback}`
240
+ });
241
+ }
242
+ }
243
+ catch (error) {
244
+ return {
245
+ success: false,
246
+ files: [],
247
+ errors: [error instanceof Error ? error.message : 'Unknown error'],
248
+ };
249
+ }
250
+ }
251
+ const files = this.parseGenerationResponse(currentMetadata);
252
+ return {
253
+ success: files.length > 0,
254
+ files,
255
+ rawResponse: currentMetadata,
256
+ };
257
+ }
258
+ /**
259
+ * Conversational generation with step-by-step refinement
260
+ * This allows users to iteratively improve the application through dialogue
261
+ */
262
+ async generateConversational(options) {
263
+ var _a, _b;
264
+ const systemPrompt = this.getSystemPrompt();
265
+ // Initialize or continue conversation
266
+ const messages = [
267
+ { role: 'system', content: systemPrompt }
268
+ ];
269
+ // Add conversation history if provided
270
+ if (options.conversationHistory) {
271
+ messages.push(...options.conversationHistory.filter(m => m.role !== 'system'));
272
+ }
273
+ // Build the user message
274
+ let userMessage = options.message;
275
+ // If there's a current app state, include it in context
276
+ if (options.currentApp && options.currentApp.files.length > 0) {
277
+ const currentState = options.currentApp.files
278
+ .map(f => `# ${f.filename}\n${f.content}`)
279
+ .join('\n\n---\n\n');
280
+ userMessage = `Current application state:\n\n${currentState}\n\n---\n\nUser request: ${options.message}\n\nPlease update the application according to the user's request. Provide the complete updated files.`;
281
+ }
282
+ messages.push({ role: 'user', content: userMessage });
283
+ try {
284
+ const completion = await this.openai.chat.completions.create({
285
+ model: this.config.model,
286
+ messages,
287
+ temperature: this.config.temperature,
288
+ max_tokens: 4000,
289
+ });
290
+ const response = (_b = (_a = completion.choices[0]) === null || _a === void 0 ? void 0 : _a.message) === null || _b === void 0 ? void 0 : _b.content;
291
+ if (!response) {
292
+ return {
293
+ success: false,
294
+ files: [],
295
+ conversationHistory: [...(options.conversationHistory || []),
296
+ { role: 'user', content: options.message }],
297
+ errors: ['No response from AI model'],
298
+ };
299
+ }
300
+ // Parse the response
301
+ const files = this.parseGenerationResponse(response);
302
+ // Update conversation history
303
+ const updatedHistory = [
304
+ ...(options.conversationHistory || []),
305
+ { role: 'user', content: options.message },
306
+ { role: 'assistant', content: response }
307
+ ];
308
+ // Generate suggestions for next steps
309
+ const suggestions = this.generateSuggestions(files, options.currentApp);
310
+ return {
311
+ success: files.length > 0,
312
+ files,
313
+ rawResponse: response,
314
+ conversationHistory: updatedHistory,
315
+ suggestions,
316
+ errors: files.length === 0 ? ['Failed to extract metadata files from response'] : undefined,
317
+ };
318
+ }
319
+ catch (error) {
320
+ return {
321
+ success: false,
322
+ files: [],
323
+ conversationHistory: [...(options.conversationHistory || []),
324
+ { role: 'user', content: options.message }],
325
+ errors: [error instanceof Error ? error.message : 'Unknown error'],
326
+ };
327
+ }
328
+ }
329
+ /**
330
+ * Generate suggestions for next steps based on current application state
331
+ */
332
+ generateSuggestions(currentFiles, previousApp) {
333
+ const suggestions = [];
334
+ // Check what metadata types are missing
335
+ const fileTypes = new Set(currentFiles.map(f => f.type));
336
+ const allTypes = [
337
+ 'object', 'validation', 'form', 'view', 'page',
338
+ 'menu', 'action', 'hook', 'permission', 'workflow', 'report', 'data'
339
+ ];
340
+ const missingTypes = allTypes.filter(t => !fileTypes.has(t));
341
+ if (missingTypes.length > 0) {
342
+ suggestions.push(`Consider adding: ${missingTypes.join(', ')}`);
343
+ }
344
+ if (!fileTypes.has('permission')) {
345
+ suggestions.push('Add permissions to control access');
346
+ }
347
+ if (!fileTypes.has('menu')) {
348
+ suggestions.push('Create a menu for navigation');
349
+ }
350
+ if (!fileTypes.has('workflow') && fileTypes.has('object')) {
351
+ suggestions.push('Add workflows for approval processes');
352
+ }
353
+ if (!fileTypes.has('report') && fileTypes.has('object')) {
354
+ suggestions.push('Generate reports for analytics');
355
+ }
356
+ return suggestions;
357
+ }
358
+ /**
359
+ * Get system prompt for metadata generation
360
+ */
361
+ getSystemPrompt() {
362
+ return `You are an expert ObjectQL architect. Generate valid ObjectQL metadata in YAML format AND TypeScript implementation files for business logic.
363
+
364
+ Follow ObjectQL metadata standards for ALL metadata types:
365
+
366
+ **1. Core Data Layer:**
367
+ - Objects (*.object.yml): entities, fields, relationships, indexes
368
+ - Validations (*.validation.yml): validation rules, business constraints
369
+ - Data (*.data.yml): seed data and initial records
370
+
371
+ **2. Business Logic Layer (YAML + TypeScript):**
372
+ - Actions (*.action.yml + *.action.ts): custom RPC operations with TypeScript implementation
373
+ - Hooks (*.hook.yml + *.hook.ts): lifecycle triggers with TypeScript implementation
374
+ - beforeCreate, afterCreate, beforeUpdate, afterUpdate, beforeDelete, afterDelete
375
+ - Workflows (*.workflow.yml): approval processes, automation
376
+
377
+ **3. Presentation Layer:**
378
+ - Pages (*.page.yml): composable UI pages with layouts
379
+ - Views (*.view.yml): list views, kanban, calendar displays
380
+ - Forms (*.form.yml): data entry forms with field layouts
381
+ - Reports (*.report.yml): tabular, summary, matrix reports
382
+ - Menus (*.menu.yml): navigation structure
383
+
384
+ **4. Security Layer:**
385
+ - Permissions (*.permission.yml): access control rules
386
+ - Application (*.application.yml): app-level configuration
387
+
388
+ **Field Types:** text, number, boolean, select, date, datetime, lookup, currency, email, phone, url, textarea, formula, file, image
389
+
390
+ **For Actions - Generate BOTH files:**
391
+ Example:
392
+ # approve_order.action.yml
393
+ \`\`\`yaml
394
+ label: Approve Order
395
+ type: record
396
+ params:
397
+ comment:
398
+ type: textarea
399
+ label: Comment
400
+ \`\`\`
401
+
402
+ # approve_order.action.ts
403
+ \`\`\`typescript
404
+ import { ActionContext } from '@objectql/types';
405
+
406
+ export default async function approveOrder(context: ActionContext) {
407
+ const { recordId, params, user, app } = context;
408
+
409
+ // Business logic here
410
+ const record = await app.findOne('orders', { _id: recordId });
411
+
412
+ if (!record) {
413
+ throw new Error('Order not found');
414
+ }
415
+
416
+ await app.update('orders', recordId, {
417
+ status: 'approved',
418
+ approved_by: user.id,
419
+ approved_at: new Date(),
420
+ approval_comment: params.comment
421
+ });
422
+
423
+ return { success: true, message: 'Order approved successfully' };
424
+ }
425
+ \`\`\`
426
+
427
+ **For Hooks - Generate BOTH files:**
428
+ Example:
429
+ # user.hook.yml
430
+ \`\`\`yaml
431
+ triggers:
432
+ - beforeCreate
433
+ - beforeUpdate
434
+ \`\`\`
435
+
436
+ # user.hook.ts
437
+ \`\`\`typescript
438
+ import { HookContext } from '@objectql/types';
439
+
440
+ export async function beforeCreate(context: HookContext) {
441
+ const { data, user } = context;
442
+
443
+ // Auto-assign creator
444
+ data.created_by = user.id;
445
+ data.created_at = new Date();
446
+
447
+ // Validate email uniqueness (example)
448
+ const existing = await context.app.findOne('users', { email: data.email });
449
+ if (existing) {
450
+ throw new Error('Email already exists');
451
+ }
452
+ }
453
+
454
+ export async function beforeUpdate(context: HookContext) {
455
+ const { data, previousData, user } = context;
456
+
457
+ data.updated_by = user.id;
458
+ data.updated_at = new Date();
459
+ }
460
+ \`\`\`
461
+
462
+ **For Tests - Generate test files:**
463
+ Example:
464
+ // approve_order.test.ts
465
+ \`\`\`typescript
466
+ import { describe, it, expect, beforeEach } from '@jest/globals';
467
+ import { ObjectQL } from '@objectql/core';
468
+ import approveOrder from './approve_order.action';
469
+
470
+ describe('approve_order action', () => {
471
+ let app: ObjectQL;
472
+ let testUser: any;
473
+
474
+ beforeEach(async () => {
475
+ // Setup test environment
476
+ app = new ObjectQL(/* test config */);
477
+ await app.connect();
478
+ testUser = { id: 'user123', name: 'Test User' };
479
+ });
480
+
481
+ it('should approve an order successfully', async () => {
482
+ // Create test order
483
+ const order = await app.create('orders', {
484
+ status: 'pending',
485
+ total: 100
486
+ });
487
+
488
+ // Execute action
489
+ const result = await approveOrder({
490
+ recordId: order.id,
491
+ params: { comment: 'Approved' },
492
+ user: testUser,
493
+ app
494
+ });
495
+
496
+ // Verify
497
+ expect(result.success).toBe(true);
498
+ const updated = await app.findOne('orders', { _id: order.id });
499
+ expect(updated.status).toBe('approved');
500
+ });
501
+
502
+ it('should reject if order not found', async () => {
503
+ await expect(approveOrder({
504
+ recordId: 'invalid_id',
505
+ params: { comment: 'Test' },
506
+ user: testUser,
507
+ app
508
+ })).rejects.toThrow('Order not found');
509
+ });
510
+ });
511
+ \`\`\`
512
+
513
+ **Best Practices:**
514
+ - Use snake_case for names
515
+ - Clear, business-friendly labels
516
+ - Include validation rules
517
+ - Add help text for clarity
518
+ - Define proper relationships
519
+ - Consider security from the start
520
+ - Implement actual business logic in TypeScript files
521
+ - Include error handling in implementations
522
+ - Add comments explaining complex logic
523
+ - Write comprehensive tests for all business logic
524
+ - Test both success and failure cases
525
+
526
+ Output format: Provide each file in code blocks with filename headers (e.g., "# filename.object.yml" or "// filename.action.ts").`;
527
+ }
528
+ /**
529
+ * Get system prompt for validation
530
+ */
531
+ getValidationSystemPrompt() {
532
+ return `You are an expert ObjectQL metadata validator. Analyze metadata for:
533
+ 1. YAML structure and syntax
534
+ 2. ObjectQL specification compliance
535
+ 3. Business logic consistency
536
+ 4. Data modeling best practices
537
+ 5. Security considerations
538
+ 6. Performance implications
539
+
540
+ Provide feedback in this format:
541
+ - [ERROR] Location: Issue description
542
+ - [WARNING] Location: Issue description
543
+ - [INFO] Location: Suggestion`;
544
+ }
545
+ /**
546
+ * Build generation prompt based on options
547
+ */
548
+ buildGenerationPrompt(options) {
549
+ const { description, type = 'custom' } = options;
550
+ switch (type) {
551
+ case 'basic':
552
+ return `Generate a minimal ObjectQL application for: ${description}
553
+
554
+ Include:
555
+ - 2-3 core objects with essential fields
556
+ - Basic relationships between objects
557
+ - Simple validation rules
558
+ - At least one form and view per object
559
+ - At least one action with TypeScript implementation
560
+ - At least one hook with TypeScript implementation
561
+
562
+ Output: Provide each file separately with clear filename headers (e.g., "# filename.object.yml" or "// filename.action.ts").`;
563
+ case 'complete':
564
+ return `Generate a complete ObjectQL enterprise application for: ${description}
565
+
566
+ Include ALL necessary metadata types WITH implementations:
567
+ 1. **Objects**: All entities with comprehensive fields
568
+ 2. **Validations**: Business rules and constraints
569
+ 3. **Forms**: Create and edit forms for each object
570
+ 4. **Views**: List views for browsing data
571
+ 5. **Pages**: Dashboard and detail pages
572
+ 6. **Menus**: Navigation structure
573
+ 7. **Actions WITH TypeScript implementations**: Common operations (approve, export, etc.) - Generate BOTH .yml metadata AND .action.ts implementation files
574
+ 8. **Hooks WITH TypeScript implementations**: Lifecycle triggers - Generate .hook.ts implementation files
575
+ 9. **Permissions**: Basic access control
576
+ 10. **Data**: Sample seed data (optional)
577
+ 11. **Workflows**: Approval processes if applicable
578
+ 12. **Reports**: Key reports for analytics
579
+ 13. **Tests**: Generate test files (.test.ts) for actions and hooks to validate business logic
580
+
581
+ Consider:
582
+ - Security and permissions from the start
583
+ - User experience in form/view design
584
+ - Business processes and workflows
585
+ - Data integrity and validation
586
+ - Complete TypeScript implementations for all actions and hooks
587
+ - Test coverage for business logic
588
+
589
+ Output: Provide each file separately with clear filename headers (e.g., "# filename.object.yml" or "// filename.action.ts").`;
590
+ default:
591
+ return `Generate ObjectQL metadata for: ${description}
592
+
593
+ Analyze the requirements and create appropriate metadata across ALL relevant types:
594
+ - Objects, Validations, Forms, Views, Pages, Menus, Actions, Hooks, Permissions, Workflows, Reports, Data, Application
595
+ - For Actions and Hooks: Generate BOTH YAML metadata AND TypeScript implementation files
596
+ - Include test files to validate business logic
597
+
598
+ Output: Provide each file separately with clear filename headers (e.g., "# filename.object.yml" or "// filename.action.ts").`;
599
+ }
600
+ }
601
+ /**
602
+ * Build validation prompt
603
+ */
604
+ buildValidationPrompt(options) {
605
+ const metadataStr = typeof options.metadata === 'string'
606
+ ? options.metadata
607
+ : yaml.dump(options.metadata);
608
+ const checks = [];
609
+ if (options.checkBusinessLogic !== false)
610
+ checks.push('Business logic consistency');
611
+ if (options.checkPerformance)
612
+ checks.push('Performance considerations');
613
+ if (options.checkSecurity)
614
+ checks.push('Security issues');
615
+ return `Validate this ObjectQL metadata file:
616
+
617
+ ${options.filename ? `Filename: ${options.filename}\n` : ''}
618
+ Content:
619
+ \`\`\`yaml
620
+ ${metadataStr}
621
+ \`\`\`
622
+
623
+ Check for:
624
+ - YAML syntax and structure
625
+ - ObjectQL specification compliance
626
+ ${checks.length > 0 ? '- ' + checks.join('\n- ') : ''}
627
+
628
+ Provide feedback in the specified format.`;
629
+ }
630
+ /**
631
+ * Parse generation response and extract files
632
+ */
633
+ parseGenerationResponse(response) {
634
+ const files = [];
635
+ let match;
636
+ // Extract YAML files with explicit headers
637
+ while ((match = AI_RESPONSE_PATTERNS.FILE_BLOCK_YAML.exec(response)) !== null) {
638
+ const filename = match[1];
639
+ const content = match[2].trim();
640
+ const type = this.inferFileType(filename);
641
+ files.push({ filename, content, type });
642
+ }
643
+ // Extract TypeScript files with explicit headers
644
+ while ((match = AI_RESPONSE_PATTERNS.FILE_BLOCK_TS.exec(response)) !== null) {
645
+ const filename = match[1];
646
+ const content = match[2].trim();
647
+ const type = this.inferFileType(filename);
648
+ files.push({ filename, content, type });
649
+ }
650
+ // Fallback: Generic code blocks if no explicit headers found
651
+ if (files.length === 0) {
652
+ let yamlIndex = 0;
653
+ let tsIndex = 0;
654
+ // Try to extract generic YAML blocks
655
+ while ((match = AI_RESPONSE_PATTERNS.CODE_BLOCK_YAML.exec(response)) !== null) {
656
+ const content = match[1].trim();
657
+ const filename = `generated_${yamlIndex}.object.yml`;
658
+ files.push({ filename, content, type: 'object' });
659
+ yamlIndex++;
660
+ }
661
+ // Try to extract generic TypeScript blocks
662
+ while ((match = AI_RESPONSE_PATTERNS.CODE_BLOCK_TS.exec(response)) !== null) {
663
+ const content = match[1].trim();
664
+ // Use generic .ts extension since we can't determine the specific type
665
+ const filename = `generated_${tsIndex}.ts`;
666
+ files.push({ filename, content, type: 'typescript' });
667
+ tsIndex++;
668
+ }
669
+ }
670
+ return files;
671
+ }
672
+ /**
673
+ * Parse validation feedback into structured result
674
+ */
675
+ parseFeedback(feedback) {
676
+ const errors = [];
677
+ const warnings = [];
678
+ const info = [];
679
+ const lines = feedback.split('\n');
680
+ for (let i = 0; i < lines.length; i++) {
681
+ const line = lines[i];
682
+ if (line.includes('[ERROR]')) {
683
+ const message = line.replace(/^\s*-?\s*\[ERROR\]\s*/, '');
684
+ errors.push({ message });
685
+ }
686
+ else if (line.includes('[WARNING]')) {
687
+ const message = line.replace(/^\s*-?\s*\[WARNING\]\s*/, '');
688
+ warnings.push({ message });
689
+ }
690
+ else if (line.includes('[INFO]')) {
691
+ const message = line.replace(/^\s*-?\s*\[INFO\]\s*/, '');
692
+ info.push({ message });
693
+ }
694
+ }
695
+ return {
696
+ valid: errors.length === 0,
697
+ errors,
698
+ warnings,
699
+ info,
700
+ };
701
+ }
702
+ /**
703
+ * Infer file type from filename
704
+ */
705
+ inferFileType(filename) {
706
+ if (filename.includes('.object.yml'))
707
+ return 'object';
708
+ if (filename.includes('.validation.yml'))
709
+ return 'validation';
710
+ if (filename.includes('.form.yml'))
711
+ return 'form';
712
+ if (filename.includes('.view.yml'))
713
+ return 'view';
714
+ if (filename.includes('.page.yml'))
715
+ return 'page';
716
+ if (filename.includes('.menu.yml'))
717
+ return 'menu';
718
+ if (filename.includes('.action.yml'))
719
+ return 'action';
720
+ if (filename.includes('.hook.yml'))
721
+ return 'hook';
722
+ if (filename.includes('.permission.yml'))
723
+ return 'permission';
724
+ if (filename.includes('.workflow.yml'))
725
+ return 'workflow';
726
+ if (filename.includes('.report.yml'))
727
+ return 'report';
728
+ if (filename.includes('.data.yml'))
729
+ return 'data';
730
+ if (filename.includes('.application.yml') || filename.includes('.app.yml'))
731
+ return 'application';
732
+ if (filename.includes('.action.ts') || filename.includes('.hook.ts'))
733
+ return 'typescript';
734
+ if (filename.includes('.test.ts') || filename.includes('.spec.ts'))
735
+ return 'test';
736
+ return 'other';
737
+ }
738
+ }
739
+ exports.ObjectQLAgent = ObjectQLAgent;
740
+ /**
741
+ * Convenience function to create an agent instance
742
+ */
743
+ function createAgent(apiKey, options) {
744
+ return new ObjectQLAgent({ apiKey, ...options });
745
+ }
746
+ //# sourceMappingURL=ai-agent.js.map