@mastra/agent-builder 0.0.0-experimental-agent-builder-20250815195917 → 0.0.0-pgvector-index-fix-20250905222058

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 (71) hide show
  1. package/CHANGELOG.md +179 -19
  2. package/README.md +7 -20
  3. package/dist/agent/index.d.ts +31 -0
  4. package/dist/agent/index.d.ts.map +1 -0
  5. package/dist/defaults.d.ts +3548 -0
  6. package/dist/defaults.d.ts.map +1 -0
  7. package/dist/index.d.ts +4 -1
  8. package/dist/index.d.ts.map +1 -0
  9. package/dist/index.js +3611 -1229
  10. package/dist/index.js.map +1 -0
  11. package/dist/processors/tool-summary.d.ts +30 -0
  12. package/dist/processors/tool-summary.d.ts.map +1 -0
  13. package/dist/processors/write-file.d.ts +10 -0
  14. package/dist/processors/write-file.d.ts.map +1 -0
  15. package/dist/types.d.ts +1143 -0
  16. package/dist/types.d.ts.map +1 -0
  17. package/dist/utils.d.ts +71 -0
  18. package/dist/utils.d.ts.map +1 -0
  19. package/dist/workflows/index.d.ts +5 -0
  20. package/dist/workflows/index.d.ts.map +1 -0
  21. package/dist/workflows/shared/schema.d.ts +139 -0
  22. package/dist/workflows/shared/schema.d.ts.map +1 -0
  23. package/dist/workflows/task-planning/prompts.d.ts +37 -0
  24. package/dist/workflows/task-planning/prompts.d.ts.map +1 -0
  25. package/dist/workflows/task-planning/schema.d.ts +548 -0
  26. package/dist/workflows/task-planning/schema.d.ts.map +1 -0
  27. package/dist/workflows/task-planning/task-planning.d.ts +992 -0
  28. package/dist/workflows/task-planning/task-planning.d.ts.map +1 -0
  29. package/dist/workflows/template-builder/template-builder.d.ts +1968 -0
  30. package/dist/workflows/template-builder/template-builder.d.ts.map +1 -0
  31. package/dist/workflows/workflow-builder/prompts.d.ts +44 -0
  32. package/dist/workflows/workflow-builder/prompts.d.ts.map +1 -0
  33. package/dist/workflows/workflow-builder/schema.d.ts +1170 -0
  34. package/dist/workflows/workflow-builder/schema.d.ts.map +1 -0
  35. package/dist/workflows/workflow-builder/tools.d.ts +309 -0
  36. package/dist/workflows/workflow-builder/tools.d.ts.map +1 -0
  37. package/dist/workflows/workflow-builder/workflow-builder.d.ts +2714 -0
  38. package/dist/workflows/workflow-builder/workflow-builder.d.ts.map +1 -0
  39. package/dist/workflows/workflow-map.d.ts +3768 -0
  40. package/dist/workflows/workflow-map.d.ts.map +1 -0
  41. package/package.json +39 -10
  42. package/dist/_tsup-dts-rollup.d.cts +0 -13109
  43. package/dist/_tsup-dts-rollup.d.ts +0 -13109
  44. package/dist/index.cjs +0 -3772
  45. package/dist/index.d.cts +0 -1
  46. package/eslint.config.js +0 -11
  47. package/integration-tests/CHANGELOG.md +0 -20
  48. package/integration-tests/README.md +0 -154
  49. package/integration-tests/docker-compose.yml +0 -39
  50. package/integration-tests/package.json +0 -38
  51. package/integration-tests/src/agent-template-behavior.test.ts +0 -103
  52. package/integration-tests/src/fixtures/minimal-mastra-project/env.example +0 -6
  53. package/integration-tests/src/fixtures/minimal-mastra-project/package.json +0 -17
  54. package/integration-tests/src/fixtures/minimal-mastra-project/src/mastra/agents/weather.ts +0 -34
  55. package/integration-tests/src/fixtures/minimal-mastra-project/src/mastra/index.ts +0 -15
  56. package/integration-tests/src/fixtures/minimal-mastra-project/src/mastra/mcp/index.ts +0 -46
  57. package/integration-tests/src/fixtures/minimal-mastra-project/src/mastra/tools/weather.ts +0 -13
  58. package/integration-tests/src/fixtures/minimal-mastra-project/tsconfig.json +0 -17
  59. package/integration-tests/src/template-integration.test.ts +0 -312
  60. package/integration-tests/tsconfig.json +0 -13
  61. package/integration-tests/vitest.config.ts +0 -17
  62. package/src/agent-builder.test.ts +0 -291
  63. package/src/defaults.ts +0 -2728
  64. package/src/index.ts +0 -187
  65. package/src/processors/tool-summary.ts +0 -136
  66. package/src/processors/write-file.ts +0 -17
  67. package/src/types.ts +0 -120
  68. package/src/utils.ts +0 -133
  69. package/src/workflows/index.ts +0 -1541
  70. package/tsconfig.json +0 -5
  71. package/vitest.config.ts +0 -11
package/src/defaults.ts DELETED
@@ -1,2728 +0,0 @@
1
- import { spawn as nodeSpawn } from 'child_process';
2
- import { readFile, writeFile, mkdir, stat, readdir } from 'fs/promises';
3
- import { join, dirname, relative, isAbsolute, resolve } from 'path';
4
- import { createTool } from '@mastra/core/tools';
5
- import { MCPClient } from '@mastra/mcp';
6
- import { z } from 'zod';
7
- import { exec, spawnSWPM } from './utils';
8
-
9
- export class AgentBuilderDefaults {
10
- static DEFAULT_INSTRUCTIONS = (
11
- projectPath?: string,
12
- ) => `You are a Mastra Expert Agent, specialized in building production-ready AI applications using the Mastra framework. You excel at creating agents, tools, workflows, and complete applications with real, working implementations.
13
-
14
- ## Core Identity & Capabilities
15
-
16
- **Primary Role:** Transform natural language requirements into working Mastra applications
17
- **Key Strength:** Deep knowledge of Mastra patterns, conventions, and best practices
18
- **Output Quality:** Production-ready code that follows Mastra ecosystem standards
19
-
20
- ## Workflow: The MASTRA Method
21
-
22
- Follow this sequence for every coding task:
23
-
24
- IF NO PROJECT EXISTS, USE THE MANAGEPROJECT TOOL TO CREATE A NEW PROJECT
25
-
26
- DO NOT INCLUDE TODOS IN THE CODE, UNLESS SPECIFICALLY ASKED TO DO SO, CREATE REAL WORLD CODE
27
-
28
- ### 1. 🔍 **UNDERSTAND** (Information Gathering)
29
- - **Explore Mastra Docs**: Use docs tools to understand relevant Mastra patterns and APIs
30
- - **Analyze Project**: Use file exploration to understand existing codebase structure
31
- - **Web Research**: Search for packages, examples, or solutions when docs are insufficient
32
- - **Clarify Requirements**: Ask targeted questions only when critical information is missing
33
-
34
- ### 2. 📋 **PLAN** (Strategy & Design)
35
- - **Architecture**: Design using Mastra conventions (agents, tools, workflows, memory)
36
- - **Dependencies**: Identify required packages and Mastra components
37
- - **Integration**: Plan how to integrate with existing project structure
38
- - **Validation**: Define how to test and verify the implementation
39
-
40
- ### 3. 🛠️ **BUILD** (Implementation)
41
- - **Install First**: Use \`manageProject\` tool to install required packages
42
- - **Follow Patterns**: Implement using established Mastra conventions
43
- - **Real Code Only**: Build actual working functionality, never mock implementations
44
- - **Environment Setup**: Create proper .env configuration and documentation
45
-
46
- ### 4. ✅ **VALIDATE** (Quality Assurance)
47
- - **Code Validation**: Run \`validateCode\` with types and lint checks
48
- - **Testing**: Execute tests if available
49
- - **Server Testing**: Use \`manageServer\` and \`httpRequest\` for API validation
50
- - **Fix Issues**: Address all errors before completion
51
-
52
- ## Mastra-Specific Guidelines
53
-
54
- ### Framework Knowledge
55
- - **Agents**: Use \`@mastra/core/agent\` with proper configuration
56
- - **Tools**: Create tools with \`@mastra/core/tools\` and proper schemas
57
- - **Memory**: Implement memory with \`@mastra/memory\` and appropriate processors
58
- - **Workflows**: Build workflows with \`@mastra/core/workflows\`
59
- - **Integrations**: Leverage Mastra's extensive integration ecosystem
60
-
61
- ### Code Standards
62
- - **TypeScript First**: All code must be properly typed
63
- - **Zod Schemas**: Use Zod for all data validation
64
- - **Environment Variables**: Proper .env configuration with examples
65
- - **Error Handling**: Comprehensive error handling with meaningful messages
66
- - **Security**: Never expose credentials or sensitive data
67
-
68
- ### Project Structure
69
- - Follow Mastra project conventions (\`src/mastra/\`, config files)
70
- - Use proper file organization (agents, tools, workflows in separate directories)
71
- - Maintain consistent naming conventions
72
- - Include proper exports and imports
73
-
74
- ## Communication Style
75
-
76
- **Conciseness**: Keep responses focused and actionable
77
- **Clarity**: Explain complex concepts in simple terms
78
- **Directness**: State what you're doing and why
79
- **No Fluff**: Avoid unnecessary explanations or apologies
80
-
81
- ### Response Format
82
- 1. **Brief Status**: One line stating what you're doing
83
- 2. **Tool Usage**: Execute necessary tools
84
- 3. **Results Summary**: Concise summary of what was accomplished
85
- 4. **Next Steps**: Clear indication of completion or next actions
86
-
87
- ## Tool Usage Strategy
88
-
89
- ### File Operations
90
- - **Project-Relative Paths**: All file paths are resolved relative to the project directory (unless absolute paths are used)
91
- - **Read First**: Always read files before editing to understand context
92
- - **Precise Edits**: Use exact text matching for search/replace operations
93
- - **Batch Operations**: Group related file operations when possible
94
-
95
- ### Project Management
96
- - **manageProject**: Use for package installation, project creation, dependency management
97
- - **validateCode**: Always run after code changes to ensure quality
98
- - **manageServer**: Use for testing Mastra server functionality
99
- - **httpRequest**: Test API endpoints and integrations
100
-
101
- ### Information Gathering
102
- - **Mastra Docs**: Primary source for Mastra-specific information
103
- - **Web Search**: Secondary source for packages and external solutions
104
- - **File Exploration**: Understand existing project structure and patterns
105
-
106
- ## Error Handling & Recovery
107
-
108
- ### Validation Failures
109
- - Fix TypeScript errors immediately
110
- - Address linting issues systematically
111
- - Re-validate until clean
112
-
113
- ### Build Issues
114
- - Check dependencies and versions
115
- - Verify Mastra configuration
116
- - Test in isolation when needed
117
-
118
- ### Integration Problems
119
- - Verify API keys and environment setup
120
- - Test connections independently
121
- - Debug with logging and error messages
122
-
123
- ## Security & Best Practices
124
-
125
- **Never:**
126
- - Hard-code API keys or secrets
127
- - Generate mock or placeholder implementations
128
- - Skip error handling
129
- - Ignore TypeScript errors
130
- - Create insecure code patterns
131
- - ask for file paths, you should be able to use the provided tools to explore the file system
132
-
133
- **Always:**
134
- - Use environment variables for configuration
135
- - Implement proper input validation
136
- - Follow security best practices
137
- - Create complete, working implementations
138
- - Test thoroughly before completion
139
-
140
- ## Output Requirements
141
-
142
- ### Code Quality
143
- - ✅ TypeScript compilation passes
144
- - ✅ ESLint validation passes
145
- - ✅ Proper error handling implemented
146
- - ✅ Environment variables configured
147
- - ✅ Tests included when appropriate
148
-
149
- ### Documentation
150
- - ✅ Clear setup instructions
151
- - ✅ Environment variable documentation
152
- - ✅ Usage examples provided
153
- - ✅ API documentation for custom tools
154
-
155
- ### Integration
156
- - ✅ Follows Mastra conventions
157
- - ✅ Integrates with existing project
158
- - ✅ Proper imports and exports
159
- - ✅ Compatible with Mastra ecosystem
160
-
161
- ## Project Context
162
-
163
- **Working Directory**: ${projectPath}
164
- **Focus**: Mastra framework applications
165
- **Goal**: Production-ready implementations
166
-
167
- Remember: You are building real applications, not prototypes. Every implementation should be complete, secure, and ready for production use.
168
-
169
- ## Enhanced Tool Set
170
-
171
- You have access to an enhanced set of tools based on production coding agent patterns:
172
-
173
- ### Task Management
174
- - **taskManager**: Create and track multi-step coding tasks with states (pending, in_progress, completed, blocked). Use this for complex projects that require systematic progress tracking.
175
-
176
- ### Code Discovery & Analysis
177
- - **codeAnalyzer**: Analyze codebase structure, discover definitions (functions, classes, interfaces), map dependencies, and understand architectural patterns.
178
- - **smartSearch**: Intelligent search with context awareness, pattern matching, and relevance scoring.
179
-
180
- ### Advanced File Operations
181
- - **readFile**: Read files with optional line ranges, encoding support, metadata
182
- - **writeFile**: Write files with directory creation
183
- - **listDirectory**: Directory listing with filtering, recursion, metadata
184
- - **multiEdit**: Perform multiple search-replace operations across files atomically with backup creation
185
- - **executeCommand**: Execute shell commands with proper error handling and working directory support
186
-
187
- **Important**: All file paths are resolved relative to the project directory unless absolute paths are provided.
188
-
189
- ### Communication & Workflow
190
- - **askClarification**: Ask users for clarification when requirements are unclear or multiple options exist.
191
- - **attemptCompletion**: Signal task completion with validation status and confidence metrics.
192
-
193
- ### Guidelines for Enhanced Tools:
194
-
195
- 1. **Use taskManager proactively** for any task requiring 3+ steps or complex coordination
196
- 2. **Start with codeAnalyzer** when working with unfamiliar codebases to understand structure
197
- 3. **Use smartSearch** for intelligent pattern discovery across the codebase
198
- 4. **Apply multiEdit** for systematic refactoring across multiple files
199
- 5. **Ask for clarification** when requirements are ambiguous rather than making assumptions
200
- 6. **Signal completion** with comprehensive summaries and validation status
201
-
202
- Use the following basic examples to guide your implementation.
203
-
204
- <examples>
205
- ### Weather Agent
206
- \`\`\`
207
- // ./src/agents/weather-agent.ts
208
- import { openai } from '@ai-sdk/openai';
209
- import { Agent } from '@mastra/core/agent';
210
- import { Memory } from '@mastra/memory';
211
- import { LibSQLStore } from '@mastra/libsql';
212
- import { weatherTool } from '../tools/weather-tool';
213
-
214
- export const weatherAgent = new Agent({
215
- name: 'Weather Agent',
216
- instructions: \${instructions},
217
- model: openai('gpt-4o-mini'),
218
- tools: { weatherTool },
219
- memory: new Memory({
220
- storage: new LibSQLStore({
221
- url: 'file:../mastra.db', // ask user what database to use, use this as the default
222
- }),
223
- }),
224
- });
225
- \`\`\`
226
-
227
- ### Weather Tool
228
- \`\`\`
229
- // ./src/tools/weather-tool.ts
230
- import { createTool } from '@mastra/core/tools';
231
- import { z } from 'zod';
232
- import { getWeather } from '../tools/weather-tool';
233
-
234
- export const weatherTool = createTool({
235
- id: 'get-weather',
236
- description: 'Get current weather for a location',
237
- inputSchema: z.object({
238
- location: z.string().describe('City name'),
239
- }),
240
- outputSchema: z.object({
241
- temperature: z.number(),
242
- feelsLike: z.number(),
243
- humidity: z.number(),
244
- windSpeed: z.number(),
245
- windGust: z.number(),
246
- conditions: z.string(),
247
- location: z.string(),
248
- }),
249
- execute: async ({ context }) => {
250
- return await getWeather(context.location);
251
- },
252
- });
253
- \`\`\`
254
-
255
- ### Weather Workflow
256
- \`\`\`
257
- // ./src/workflows/weather-workflow.ts
258
- import { createStep, createWorkflow } from '@mastra/core/workflows';
259
- import { z } from 'zod';
260
-
261
- const fetchWeather = createStep({
262
- id: 'fetch-weather',
263
- description: 'Fetches weather forecast for a given city',
264
- inputSchema: z.object({
265
- city: z.string().describe('The city to get the weather for'),
266
- }),
267
- outputSchema: forecastSchema,
268
- execute: async ({ inputData }) => {
269
- if (!inputData) {
270
- throw new Error('Input data not found');
271
- }
272
-
273
- const geocodingUrl = \`https://geocoding-api.open-meteo.com/v1/search?name=\${encodeURIComponent(inputData.city)}&count=1\`;
274
- const geocodingResponse = await fetch(geocodingUrl);
275
- const geocodingData = (await geocodingResponse.json()) as {
276
- results: { latitude: number; longitude: number; name: string }[];
277
- };
278
-
279
- if (!geocodingData.results?.[0]) {
280
- throw new Error(\`Location '\${inputData.city}' not found\`);
281
- }
282
-
283
- const { latitude, longitude, name } = geocodingData.results[0];
284
-
285
- const weatherUrl = \`https://api.open-meteo.com/v1/forecast?latitude=\${latitude}&longitude=\${longitude}&current=precipitation,weathercode&timezone=auto,&hourly=precipitation_probability,temperature_2m\`
286
- const response = await fetch(weatherUrl);
287
- const data = (await response.json()) as {
288
- current: {
289
- time: string;
290
- precipitation: number;
291
- weathercode: number;
292
- };
293
- hourly: {
294
- precipitation_probability: number[];
295
- temperature_2m: number[];
296
- };
297
- };
298
-
299
- const forecast = {
300
- date: new Date().toISOString(),
301
- maxTemp: Math.max(...data.hourly.temperature_2m),
302
- minTemp: Math.min(...data.hourly.temperature_2m),
303
- condition: getWeatherCondition(data.current.weathercode),
304
- precipitationChance: data.hourly.precipitation_probability.reduce(
305
- (acc, curr) => Math.max(acc, curr),
306
- 0,
307
- ),
308
- location: name,
309
- };
310
-
311
- return forecast;
312
- },
313
- });
314
-
315
- const planActivities = createStep({
316
- id: 'plan-activities',
317
- description: 'Suggests activities based on weather conditions',
318
- inputSchema: forecastSchema,
319
- outputSchema: z.object({
320
- activities: z.string(),
321
- }),
322
- execute: async ({ inputData, mastra }) => {
323
- const forecast = inputData;
324
-
325
- if (!forecast) {
326
- throw new Error('Forecast data not found');
327
- }
328
-
329
- const agent = mastra?.getAgent('weatherAgent');
330
- if (!agent) {
331
- throw new Error('Weather agent not found');
332
- }
333
-
334
- const prompt = \${weatherWorkflowPrompt}
335
-
336
- const response = await agent.stream([
337
- {
338
- role: 'user',
339
- content: prompt,
340
- },
341
- ]);
342
-
343
- let activitiesText = '';
344
-
345
- for await (const chunk of response.textStream) {
346
- process.stdout.write(chunk);
347
- activitiesText += chunk;
348
- }
349
-
350
- return {
351
- activities: activitiesText,
352
- };
353
- },
354
- });
355
-
356
- const weatherWorkflow = createWorkflow({
357
- id: 'weather-workflow',
358
- inputSchema: z.object({
359
- city: z.string().describe('The city to get the weather for'),
360
- }),
361
- outputSchema: z.object({
362
- activities: z.string(),
363
- }),
364
- })
365
- .then(fetchWeather)
366
- .then(planActivities);
367
-
368
- weatherWorkflow.commit();
369
- \`\`\`
370
- export { weatherWorkflow };
371
- \`\`\`
372
-
373
- ### Mastra instance
374
- \`\`\`
375
- // ./src/mastra.ts
376
-
377
- import { Mastra } from '@mastra/core/mastra';
378
- import { PinoLogger } from '@mastra/loggers';
379
- import { LibSQLStore } from '@mastra/libsql';
380
- import { weatherWorkflow } from './workflows/weather-workflow';
381
- import { weatherAgent } from './agents/weather-agent';
382
-
383
- export const mastra = new Mastra({
384
- workflows: { weatherWorkflow },
385
- agents: { weatherAgent },
386
- storage: new LibSQLStore({
387
- // stores telemetry, evals, ... into memory storage, if it needs to persist, change to file:../mastra.db
388
- url: ":memory:",
389
- }),
390
- logger: new PinoLogger({
391
- name: 'Mastra',
392
- level: 'info',
393
- }),
394
- });
395
- \`\`\`
396
-
397
- ### MCPClient
398
- \`\`\`
399
- // ./src/mcp/client.ts
400
-
401
- import { MCPClient } from '@mastra/mcp-client';
402
-
403
- // leverage existing MCP servers, or create your own
404
- export const mcpClient = new MCPClient({
405
- id: 'example-mcp-client',
406
- servers: {
407
- some-mcp-server: {
408
- command: 'npx',
409
- args: ["some-mcp-server"],
410
- },
411
- },
412
- });
413
-
414
- export const tools = await mcpClient.getTools();
415
- \`\`\`
416
-
417
- </examples>`;
418
-
419
- static DEFAULT_MEMORY_CONFIG = {
420
- lastMessages: 20,
421
- };
422
-
423
- static DEFAULT_FOLDER_STRUCTURE = {
424
- agent: 'src/mastra/agents',
425
- workflow: 'src/mastra/workflows',
426
- tool: 'src/mastra/tools',
427
- 'mcp-server': 'src/mastra/mcp',
428
- network: 'src/mastra/networks',
429
- };
430
-
431
- static DEFAULT_TOOLS = async (projectPath?: string, mode: 'template' | 'code-editor' = 'code-editor') => {
432
- const mcpClient = new MCPClient({
433
- id: 'agent-builder-mcp-client',
434
- servers: {
435
- // web: {
436
- // command: 'node',
437
- // args: ['/Users/daniellew/Documents/Mastra/web-search/build/index.js'],
438
- // },
439
- docs: {
440
- command: 'npx',
441
- args: ['-y', '@mastra/mcp-docs-server'],
442
- },
443
- },
444
- });
445
-
446
- const tools = await mcpClient.getTools();
447
- const filteredTools: Record<string, any> = {};
448
-
449
- Object.keys(tools).forEach(key => {
450
- if (!key.includes('MastraCourse')) {
451
- filteredTools[key] = tools[key];
452
- }
453
- });
454
-
455
- const agentBuilderTools = {
456
- ...filteredTools,
457
-
458
- readFile: createTool({
459
- id: 'read-file',
460
- description: 'Read contents of a file with optional line range selection.',
461
- inputSchema: z.object({
462
- filePath: z.string().describe('Path to the file to read'),
463
- startLine: z.number().optional().describe('Starting line number (1-indexed)'),
464
- endLine: z.number().optional().describe('Ending line number (1-indexed, inclusive)'),
465
- encoding: z.string().default('utf-8').describe('File encoding'),
466
- }),
467
- outputSchema: z.object({
468
- success: z.boolean(),
469
- content: z.string().optional(),
470
- lines: z.array(z.string()).optional(),
471
- metadata: z
472
- .object({
473
- size: z.number(),
474
- totalLines: z.number(),
475
- encoding: z.string(),
476
- lastModified: z.string(),
477
- })
478
- .optional(),
479
- error: z.string().optional(),
480
- }),
481
- execute: async ({ context }) => {
482
- return await AgentBuilderDefaults.readFile({ ...context, projectPath });
483
- },
484
- }),
485
-
486
- writeFile: createTool({
487
- id: 'write-file',
488
- description: 'Write content to a file, with options for creating directories.',
489
- inputSchema: z.object({
490
- filePath: z.string().describe('Path to the file to write'),
491
- content: z.string().describe('Content to write to the file'),
492
- createDirs: z.boolean().default(true).describe("Create parent directories if they don't exist"),
493
- encoding: z.string().default('utf-8').describe('File encoding'),
494
- }),
495
- outputSchema: z.object({
496
- success: z.boolean(),
497
- filePath: z.string(),
498
- bytesWritten: z.number().optional(),
499
- message: z.string(),
500
- error: z.string().optional(),
501
- }),
502
- execute: async ({ context }) => {
503
- return await AgentBuilderDefaults.writeFile({ ...context, projectPath });
504
- },
505
- }),
506
-
507
- listDirectory: createTool({
508
- id: 'list-directory',
509
- description: 'List contents of a directory with filtering and metadata options.',
510
- inputSchema: z.object({
511
- path: z.string().describe('Directory path to list'),
512
- recursive: z.boolean().default(false).describe('List subdirectories recursively'),
513
- includeHidden: z.boolean().default(false).describe('Include hidden files and directories'),
514
- pattern: z.string().optional().describe('Glob pattern to filter files'),
515
- maxDepth: z.number().default(10).describe('Maximum recursion depth'),
516
- includeMetadata: z.boolean().default(true).describe('Include file metadata'),
517
- }),
518
- outputSchema: z.object({
519
- success: z.boolean(),
520
- items: z.array(
521
- z.object({
522
- name: z.string(),
523
- path: z.string(),
524
- type: z.enum(['file', 'directory', 'symlink']),
525
- size: z.number().optional(),
526
- lastModified: z.string().optional(),
527
- permissions: z.string().optional(),
528
- }),
529
- ),
530
- totalItems: z.number(),
531
- path: z.string(),
532
- message: z.string(),
533
- error: z.string().optional(),
534
- }),
535
- execute: async ({ context }) => {
536
- return await AgentBuilderDefaults.listDirectory({ ...context, projectPath });
537
- },
538
- }),
539
-
540
- executeCommand: createTool({
541
- id: 'execute-command',
542
- description: 'Execute shell commands with proper error handling and output capture.',
543
- inputSchema: z.object({
544
- command: z.string().describe('Shell command to execute'),
545
- workingDirectory: z.string().optional().describe('Working directory for command execution'),
546
- timeout: z.number().default(30000).describe('Timeout in milliseconds'),
547
- captureOutput: z.boolean().default(true).describe('Capture command output'),
548
- shell: z.string().optional().describe('Shell to use (defaults to system shell)'),
549
- env: z.record(z.string()).optional().describe('Environment variables'),
550
- }),
551
- outputSchema: z.object({
552
- success: z.boolean(),
553
- exitCode: z.number().optional(),
554
- stdout: z.string().optional(),
555
- stderr: z.string().optional(),
556
- command: z.string(),
557
- workingDirectory: z.string().optional(),
558
- executionTime: z.number().optional(),
559
- error: z.string().optional(),
560
- }),
561
- execute: async ({ context }) => {
562
- return await AgentBuilderDefaults.executeCommand({
563
- ...context,
564
- workingDirectory: context.workingDirectory || projectPath,
565
- });
566
- },
567
- }),
568
- // Enhanced Task Management (Critical for complex coding tasks)
569
- taskManager: createTool({
570
- id: 'task-manager',
571
- description:
572
- 'Create and manage structured task lists for coding sessions. Use this for complex multi-step tasks to track progress and ensure thoroughness.',
573
- inputSchema: z.object({
574
- action: z.enum(['create', 'update', 'list', 'complete', 'remove']).describe('Task management action'),
575
- tasks: z
576
- .array(
577
- z.object({
578
- id: z.string().describe('Unique task identifier'),
579
- content: z.string().describe('Task description, optional if just updating the status').optional(),
580
- status: z.enum(['pending', 'in_progress', 'completed', 'blocked']).describe('Task status'),
581
- priority: z.enum(['high', 'medium', 'low']).default('medium').describe('Task priority'),
582
- dependencies: z.array(z.string()).optional().describe('IDs of tasks this depends on'),
583
- notes: z.string().optional().describe('Additional notes or context'),
584
- }),
585
- )
586
- .optional()
587
- .describe('Tasks to create or update'),
588
- taskId: z.string().optional().describe('Specific task ID for single task operations'),
589
- }),
590
- outputSchema: z.object({
591
- success: z.boolean(),
592
- tasks: z.array(
593
- z.object({
594
- id: z.string(),
595
- content: z.string(),
596
- status: z.string(),
597
- priority: z.string(),
598
- dependencies: z.array(z.string()).optional(),
599
- notes: z.string().optional(),
600
- createdAt: z.string(),
601
- updatedAt: z.string(),
602
- }),
603
- ),
604
- message: z.string(),
605
- }),
606
- execute: async ({ context }) => {
607
- return await AgentBuilderDefaults.manageTaskList(context);
608
- },
609
- }),
610
-
611
- // Advanced File Operations
612
- multiEdit: createTool({
613
- id: 'multi-edit',
614
- description: 'Perform multiple search-replace operations on one or more files in a single atomic operation.',
615
- inputSchema: z.object({
616
- operations: z
617
- .array(
618
- z.object({
619
- filePath: z.string().describe('Path to the file to edit'),
620
- edits: z
621
- .array(
622
- z.object({
623
- oldString: z.string().describe('Exact text to replace'),
624
- newString: z.string().describe('Replacement text'),
625
- replaceAll: z.boolean().default(false).describe('Replace all occurrences'),
626
- }),
627
- )
628
- .describe('List of edit operations for this file'),
629
- }),
630
- )
631
- .describe('File edit operations to perform'),
632
- createBackup: z.boolean().default(false).describe('Create backup files before editing'),
633
- }),
634
- outputSchema: z.object({
635
- success: z.boolean(),
636
- results: z.array(
637
- z.object({
638
- filePath: z.string(),
639
- editsApplied: z.number(),
640
- errors: z.array(z.string()),
641
- backup: z.string().optional(),
642
- }),
643
- ),
644
- message: z.string(),
645
- }),
646
- execute: async ({ context }) => {
647
- return await AgentBuilderDefaults.performMultiEdit({ ...context, projectPath });
648
- },
649
- }),
650
-
651
- // Interactive Communication
652
- askClarification: createTool({
653
- id: 'ask-clarification',
654
- description: 'Ask the user for clarification when requirements are unclear or when multiple options exist.',
655
- inputSchema: z.object({
656
- question: z.string().describe('The specific question to ask'),
657
- options: z
658
- .array(
659
- z.object({
660
- id: z.string(),
661
- description: z.string(),
662
- implications: z.string().optional(),
663
- }),
664
- )
665
- .optional()
666
- .describe('Multiple choice options if applicable'),
667
- context: z.string().optional().describe('Additional context about why clarification is needed'),
668
- urgency: z.enum(['low', 'medium', 'high']).default('medium').describe('How urgent the clarification is'),
669
- }),
670
- outputSchema: z.object({
671
- questionId: z.string(),
672
- question: z.string(),
673
- options: z
674
- .array(
675
- z.object({
676
- id: z.string(),
677
- description: z.string(),
678
- }),
679
- )
680
- .optional(),
681
- awaitingResponse: z.boolean(),
682
- }),
683
- execute: async ({ context }) => {
684
- return await AgentBuilderDefaults.askClarification(context);
685
- },
686
- }),
687
-
688
- // Enhanced Pattern Search
689
- smartSearch: createTool({
690
- id: 'smart-search',
691
- description: 'Intelligent search across codebase with context awareness and pattern matching.',
692
- inputSchema: z.object({
693
- query: z.string().describe('Search query or pattern'),
694
- type: z.enum(['text', 'regex', 'fuzzy', 'semantic']).default('text').describe('Type of search to perform'),
695
- scope: z
696
- .object({
697
- paths: z.array(z.string()).optional().describe('Specific paths to search'),
698
- fileTypes: z.array(z.string()).optional().describe('File extensions to include'),
699
- excludePaths: z.array(z.string()).optional().describe('Paths to exclude'),
700
- maxResults: z.number().default(50).describe('Maximum number of results'),
701
- })
702
- .optional(),
703
- context: z
704
- .object({
705
- beforeLines: z.number().default(2).describe('Lines of context before match'),
706
- afterLines: z.number().default(2).describe('Lines of context after match'),
707
- includeDefinitions: z.boolean().default(false).describe('Include function/class definitions'),
708
- })
709
- .optional(),
710
- }),
711
- outputSchema: z.object({
712
- success: z.boolean(),
713
- matches: z.array(
714
- z.object({
715
- file: z.string(),
716
- line: z.number(),
717
- column: z.number().optional(),
718
- match: z.string(),
719
- context: z.object({
720
- before: z.array(z.string()),
721
- after: z.array(z.string()),
722
- }),
723
- relevance: z.number().optional(),
724
- }),
725
- ),
726
- summary: z.object({
727
- totalMatches: z.number(),
728
- filesSearched: z.number(),
729
- patterns: z.array(z.string()),
730
- }),
731
- }),
732
- execute: async ({ context }) => {
733
- return await AgentBuilderDefaults.performSmartSearch(context);
734
- },
735
- }),
736
-
737
- validateCode: createTool({
738
- id: 'validate-code',
739
- description:
740
- 'Validates generated code through TypeScript compilation, ESLint, schema validation, and other checks',
741
- inputSchema: z.object({
742
- projectPath: z.string().optional().describe('Path to the project to validate (defaults to current project)'),
743
- validationType: z
744
- .array(z.enum(['types', 'lint', 'schemas', 'tests', 'build']))
745
- .describe('Types of validation to perform'),
746
- files: z
747
- .array(z.string())
748
- .optional()
749
- .describe('Specific files to validate (if not provided, validates entire project)'),
750
- }),
751
- outputSchema: z.object({
752
- valid: z.boolean(),
753
- errors: z.array(
754
- z.object({
755
- type: z.enum(['typescript', 'eslint', 'schema', 'test', 'build']),
756
- severity: z.enum(['error', 'warning', 'info']),
757
- message: z.string(),
758
- file: z.string().optional(),
759
- line: z.number().optional(),
760
- column: z.number().optional(),
761
- code: z.string().optional(),
762
- }),
763
- ),
764
- summary: z.object({
765
- totalErrors: z.number(),
766
- totalWarnings: z.number(),
767
- validationsPassed: z.array(z.string()),
768
- validationsFailed: z.array(z.string()),
769
- }),
770
- }),
771
- execute: async ({ context }) => {
772
- const { projectPath: validationProjectPath, validationType, files } = context;
773
- const targetPath = validationProjectPath || projectPath;
774
- return await AgentBuilderDefaults.validateCode({
775
- projectPath: targetPath,
776
- validationType,
777
- files,
778
- });
779
- },
780
- }),
781
- };
782
-
783
- if (mode === 'template') {
784
- return agentBuilderTools;
785
- } else {
786
- return {
787
- ...agentBuilderTools,
788
-
789
- // Web Search (replaces MCP web search)
790
- webSearch: createTool({
791
- id: 'web-search',
792
- description: 'Search the web for current information and return structured results.',
793
- inputSchema: z.object({
794
- query: z.string().describe('Search query'),
795
- maxResults: z.number().default(10).describe('Maximum number of results to return'),
796
- region: z.string().default('us').describe('Search region/country code'),
797
- language: z.string().default('en').describe('Search language'),
798
- includeImages: z.boolean().default(false).describe('Include image results'),
799
- dateRange: z.enum(['day', 'week', 'month', 'year', 'all']).default('all').describe('Date range filter'),
800
- }),
801
- outputSchema: z.object({
802
- success: z.boolean(),
803
- query: z.string(),
804
- results: z.array(
805
- z.object({
806
- title: z.string(),
807
- url: z.string(),
808
- snippet: z.string(),
809
- domain: z.string(),
810
- publishDate: z.string().optional(),
811
- relevanceScore: z.number().optional(),
812
- }),
813
- ),
814
- totalResults: z.number(),
815
- searchTime: z.number(),
816
- suggestions: z.array(z.string()).optional(),
817
- error: z.string().optional(),
818
- }),
819
- execute: async ({ context }) => {
820
- return await AgentBuilderDefaults.webSearch(context);
821
- },
822
- }),
823
-
824
- // Enhanced Code Discovery
825
- codeAnalyzer: createTool({
826
- id: 'code-analyzer',
827
- description: 'Analyze codebase structure, discover definitions, and understand architecture patterns.',
828
- inputSchema: z.object({
829
- action: z
830
- .enum(['definitions', 'dependencies', 'patterns', 'structure'])
831
- .describe('Type of analysis to perform'),
832
- path: z.string().describe('Directory or file path to analyze'),
833
- language: z.string().optional().describe('Programming language filter'),
834
- depth: z.number().default(3).describe('Directory traversal depth'),
835
- includeTests: z.boolean().default(false).describe('Include test files in analysis'),
836
- }),
837
- outputSchema: z.object({
838
- success: z.boolean(),
839
- analysis: z.object({
840
- definitions: z
841
- .array(
842
- z.object({
843
- name: z.string(),
844
- type: z.string(),
845
- file: z.string(),
846
- line: z.number().optional(),
847
- scope: z.string().optional(),
848
- }),
849
- )
850
- .optional(),
851
- dependencies: z
852
- .array(
853
- z.object({
854
- name: z.string(),
855
- type: z.enum(['import', 'require', 'include']),
856
- source: z.string(),
857
- target: z.string(),
858
- }),
859
- )
860
- .optional(),
861
- patterns: z
862
- .array(
863
- z.object({
864
- pattern: z.string(),
865
- description: z.string(),
866
- files: z.array(z.string()),
867
- }),
868
- )
869
- .optional(),
870
- structure: z
871
- .object({
872
- directories: z.number(),
873
- files: z.number(),
874
- languages: z.record(z.number()),
875
- complexity: z.string(),
876
- })
877
- .optional(),
878
- }),
879
- message: z.string(),
880
- }),
881
- execute: async ({ context }) => {
882
- return await AgentBuilderDefaults.analyzeCode(context);
883
- },
884
- }),
885
-
886
- // Task Completion Signaling
887
- attemptCompletion: createTool({
888
- id: 'attempt-completion',
889
- description: 'Signal that you believe the requested task has been completed and provide a summary.',
890
- inputSchema: z.object({
891
- summary: z.string().describe('Summary of what was accomplished'),
892
- changes: z
893
- .array(
894
- z.object({
895
- type: z.enum([
896
- 'file_created',
897
- 'file_modified',
898
- 'file_deleted',
899
- 'command_executed',
900
- 'dependency_added',
901
- ]),
902
- description: z.string(),
903
- path: z.string().optional(),
904
- }),
905
- )
906
- .describe('List of changes made'),
907
- validation: z
908
- .object({
909
- testsRun: z.boolean().default(false),
910
- buildsSuccessfully: z.boolean().default(false),
911
- manualTestingRequired: z.boolean().default(false),
912
- })
913
- .describe('Validation status'),
914
- nextSteps: z.array(z.string()).optional().describe('Suggested next steps or follow-up actions'),
915
- }),
916
- outputSchema: z.object({
917
- completionId: z.string(),
918
- status: z.enum(['completed', 'needs_review', 'needs_testing']),
919
- summary: z.string(),
920
- confidence: z.number().min(0).max(100),
921
- }),
922
- execute: async ({ context }) => {
923
- return await AgentBuilderDefaults.signalCompletion(context);
924
- },
925
- }),
926
-
927
- manageProject: createTool({
928
- id: 'manage-project',
929
- description:
930
- 'Handles project management including creating project structures, managing dependencies, and package operations.',
931
- inputSchema: z.object({
932
- action: z.enum(['create', 'install', 'upgrade']).describe('The action to perform'),
933
- features: z
934
- .array(z.string())
935
- .optional()
936
- .describe('Mastra features to include (e.g., ["agents", "memory", "workflows"])'),
937
- packages: z
938
- .array(
939
- z.object({
940
- name: z.string(),
941
- version: z.string().optional(),
942
- }),
943
- )
944
- .optional()
945
- .describe('Packages to install/upgrade'),
946
- }),
947
- outputSchema: z.object({
948
- success: z.boolean(),
949
- installed: z.array(z.string()).optional(),
950
- upgraded: z.array(z.string()).optional(),
951
- warnings: z.array(z.string()).optional(),
952
- message: z.string().optional(),
953
- details: z.string().optional(),
954
- error: z.string().optional(),
955
- }),
956
- execute: async ({ context }) => {
957
- const { action, features, packages } = context;
958
- try {
959
- switch (action) {
960
- case 'create':
961
- return await AgentBuilderDefaults.createMastraProject({
962
- projectName: projectPath,
963
- features,
964
- });
965
- case 'install':
966
- if (!packages?.length) {
967
- return {
968
- success: false,
969
- message: 'Packages array is required for install action',
970
- };
971
- }
972
- return await AgentBuilderDefaults.installPackages({
973
- packages,
974
- projectPath,
975
- });
976
- case 'upgrade':
977
- if (!packages?.length) {
978
- return {
979
- success: false,
980
- message: 'Packages array is required for upgrade action',
981
- };
982
- }
983
- return await AgentBuilderDefaults.upgradePackages({
984
- packages,
985
- projectPath,
986
- });
987
- // case 'check':
988
- // return await AgentBuilderDefaults.checkProject({
989
- // projectPath,
990
- // });
991
- default:
992
- return {
993
- success: false,
994
- message: `Unknown action: ${action}`,
995
- };
996
- }
997
- } catch (error) {
998
- return {
999
- success: false,
1000
- message: `Error executing ${action}: ${error instanceof Error ? error.message : String(error)}`,
1001
- };
1002
- }
1003
- },
1004
- }),
1005
- manageServer: createTool({
1006
- id: 'manage-server',
1007
- description:
1008
- 'Manages the Mastra server - start, stop, restart, and check status, use the terminal tool to make curl requests to the server. There is an openapi spec for the server at http://localhost:{port}/openapi.json',
1009
- inputSchema: z.object({
1010
- action: z.enum(['start', 'stop', 'restart', 'status']).describe('Server management action'),
1011
- port: z.number().optional().default(4200).describe('Port to run the server on'),
1012
- }),
1013
- outputSchema: z.object({
1014
- success: z.boolean(),
1015
- status: z.enum(['running', 'stopped', 'starting', 'stopping', 'unknown']),
1016
- pid: z.number().optional(),
1017
- port: z.number().optional(),
1018
- url: z.string().optional(),
1019
- message: z.string().optional(),
1020
- stdout: z.array(z.string()).optional().describe('Server output lines captured during startup'),
1021
- error: z.string().optional(),
1022
- }),
1023
- execute: async ({ context }) => {
1024
- const { action, port } = context;
1025
- try {
1026
- switch (action) {
1027
- case 'start':
1028
- return await AgentBuilderDefaults.startMastraServer({
1029
- port,
1030
- projectPath,
1031
- });
1032
- case 'stop':
1033
- return await AgentBuilderDefaults.stopMastraServer({
1034
- port,
1035
- projectPath,
1036
- });
1037
- case 'restart':
1038
- const stopResult = await AgentBuilderDefaults.stopMastraServer({
1039
- port,
1040
- projectPath,
1041
- });
1042
- if (!stopResult.success) {
1043
- return {
1044
- success: false,
1045
- status: 'unknown' as const,
1046
- message: `Failed to restart: could not stop server on port ${port}`,
1047
- error: stopResult.error || 'Unknown stop error',
1048
- };
1049
- }
1050
- await new Promise(resolve => setTimeout(resolve, 500));
1051
- const startResult = await AgentBuilderDefaults.startMastraServer({
1052
- port,
1053
- projectPath,
1054
- });
1055
- if (!startResult.success) {
1056
- return {
1057
- success: false,
1058
- status: 'stopped' as const,
1059
- message: `Failed to restart: server stopped successfully but failed to start on port ${port}`,
1060
- error: startResult.error || 'Unknown start error',
1061
- };
1062
- }
1063
- return {
1064
- ...startResult,
1065
- message: `Mastra server restarted successfully on port ${port}`,
1066
- };
1067
- case 'status':
1068
- return await AgentBuilderDefaults.checkMastraServerStatus({
1069
- port,
1070
- projectPath,
1071
- });
1072
- default:
1073
- return {
1074
- success: false,
1075
- status: 'unknown' as const,
1076
- message: `Unknown action: ${action}`,
1077
- };
1078
- }
1079
- } catch (error) {
1080
- return {
1081
- success: false,
1082
- status: 'unknown' as const,
1083
- message: `Error managing server: ${error instanceof Error ? error.message : String(error)}`,
1084
- };
1085
- }
1086
- },
1087
- }),
1088
- httpRequest: createTool({
1089
- id: 'http-request',
1090
- description: 'Makes HTTP requests to the Mastra server or external APIs for testing and integration',
1091
- inputSchema: z.object({
1092
- method: z.enum(['GET', 'POST', 'PUT', 'DELETE', 'PATCH']).describe('HTTP method'),
1093
- url: z.string().describe('Full URL or path (if baseUrl provided)'),
1094
- baseUrl: z.string().optional().describe('Base URL for the server (e.g., http://localhost:4200)'),
1095
- headers: z.record(z.string()).optional().describe('HTTP headers'),
1096
- body: z.any().optional().describe('Request body (will be JSON stringified if object)'),
1097
- timeout: z.number().optional().default(30000).describe('Request timeout in milliseconds'),
1098
- }),
1099
- outputSchema: z.object({
1100
- success: z.boolean(),
1101
- status: z.number().optional(),
1102
- statusText: z.string().optional(),
1103
- headers: z.record(z.string()).optional(),
1104
- data: z.any().optional(),
1105
- error: z.string().optional(),
1106
- url: z.string(),
1107
- method: z.string(),
1108
- }),
1109
- execute: async ({ context }) => {
1110
- const { method, url, baseUrl, headers, body, timeout } = context;
1111
- try {
1112
- return await AgentBuilderDefaults.makeHttpRequest({
1113
- method,
1114
- url,
1115
- baseUrl,
1116
- headers,
1117
- body,
1118
- timeout,
1119
- });
1120
- } catch (error) {
1121
- return {
1122
- success: false,
1123
- url: baseUrl ? `${baseUrl}${url}` : url,
1124
- method,
1125
- error: error instanceof Error ? error.message : String(error),
1126
- };
1127
- }
1128
- },
1129
- }),
1130
- };
1131
- }
1132
- };
1133
-
1134
- /**
1135
- * Create a new Mastra project using create-mastra CLI
1136
- */
1137
- static async createMastraProject({ features, projectName }: { features?: string[]; projectName?: string }) {
1138
- try {
1139
- const args = ['pnpx', 'create', 'mastra@latest', projectName ?? '', '-l', 'openai', '-k', 'skip'];
1140
-
1141
- if (features && features.length > 0) {
1142
- args.push('--components', features.join(','));
1143
- }
1144
- args.push('--example');
1145
-
1146
- const { stdout, stderr } = await exec(args.join(' '));
1147
-
1148
- return {
1149
- success: true,
1150
- projectPath: `./${projectName}`,
1151
- message: `Successfully created Mastra project: ${projectName}.`,
1152
- details: stdout,
1153
- error: stderr,
1154
- };
1155
- } catch (error) {
1156
- return {
1157
- success: false,
1158
- message: `Failed to create project: ${error instanceof Error ? error.message : String(error)}`,
1159
- };
1160
- }
1161
- }
1162
-
1163
- /**
1164
- * Install packages using the detected package manager
1165
- */
1166
- static async installPackages({
1167
- packages,
1168
- projectPath,
1169
- }: {
1170
- packages: Array<{ name: string; version?: string }>;
1171
- projectPath?: string;
1172
- }) {
1173
- try {
1174
- console.log('Installing packages:', JSON.stringify(packages, null, 2));
1175
-
1176
- const packageStrings = packages.map(p => `${p.name}`);
1177
-
1178
- await spawnSWPM(projectPath || '', 'add', packageStrings);
1179
-
1180
- return {
1181
- success: true,
1182
- installed: packageStrings,
1183
- message: `Successfully installed ${packages.length} package(s).`,
1184
- details: '',
1185
- };
1186
- } catch (error) {
1187
- return {
1188
- success: false,
1189
- message: `Failed to install packages: ${error instanceof Error ? error.message : String(error)}`,
1190
- };
1191
- }
1192
- }
1193
-
1194
- /**
1195
- * Upgrade packages using the detected package manager
1196
- */
1197
- static async upgradePackages({
1198
- packages,
1199
- projectPath,
1200
- }: {
1201
- packages?: Array<{ name: string; version?: string }>;
1202
- projectPath?: string;
1203
- }) {
1204
- try {
1205
- console.log('Upgrading specific packages:', JSON.stringify(packages, null, 2));
1206
-
1207
- let packageNames: string[] = [];
1208
-
1209
- if (packages && packages.length > 0) {
1210
- packageNames = packages.map(p => `${p.name}`);
1211
- }
1212
- await spawnSWPM(projectPath || '', 'upgrade', packageNames);
1213
-
1214
- return {
1215
- success: true,
1216
- upgraded: packages?.map(p => p.name) || ['all packages'],
1217
- message: `Packages upgraded successfully.`,
1218
- details: '',
1219
- };
1220
- } catch (error) {
1221
- return {
1222
- success: false,
1223
- message: `Failed to upgrade packages: ${error instanceof Error ? error.message : String(error)}`,
1224
- };
1225
- }
1226
- }
1227
-
1228
- // /**
1229
- // * Check project health and status
1230
- // */
1231
- // static async checkProject({ projectPath }: { projectPath?: string }) {
1232
- // try {
1233
- // const execOptions = projectPath ? { cwd: projectPath } : {};
1234
-
1235
- // let hasPackageJson = false;
1236
- // let hasMastraConfig = false;
1237
-
1238
- // try {
1239
- // await exec('test -f package.json', execOptions);
1240
- // hasPackageJson = true;
1241
- // } catch {
1242
- // // ignore
1243
- // }
1244
-
1245
- // try {
1246
- // await exec('test -f mastra.config.* || test -d src/mastra || test -d mastra', execOptions);
1247
- // hasMastraConfig = true;
1248
- // } catch {
1249
- // // ignore
1250
- // }
1251
-
1252
- // const warnings: string[] = [];
1253
- // if (!hasPackageJson) {
1254
- // warnings.push('No package.json found - this may not be a Node.js project');
1255
- // }
1256
- // if (!hasMastraConfig) {
1257
- // warnings.push('No Mastra configuration found - run "npx create-mastra" to initialize');
1258
- // }
1259
-
1260
- // return {
1261
- // success: true,
1262
- // message: `Project health check completed for ${projectPath || 'current directory'}`,
1263
- // warnings,
1264
- // checks: {
1265
- // hasPackageJson,
1266
- // hasMastraConfig,
1267
- // },
1268
- // };
1269
- // } catch (error) {
1270
- // return {
1271
- // success: false,
1272
- // message: `Failed to check project: ${error instanceof Error ? error.message : String(error)}`,
1273
- // };
1274
- // }
1275
- // }
1276
-
1277
- /**
1278
- * Start the Mastra server
1279
- */
1280
- static async startMastraServer({
1281
- port = 4200,
1282
- projectPath,
1283
- env = {},
1284
- }: {
1285
- port?: number;
1286
- projectPath?: string;
1287
- env?: Record<string, string>;
1288
- }) {
1289
- try {
1290
- const serverEnv = { ...process.env, ...env, PORT: port.toString() };
1291
- const execOptions = {
1292
- cwd: projectPath || process.cwd(),
1293
- env: serverEnv,
1294
- };
1295
-
1296
- const serverProcess = nodeSpawn('pnpm', ['run', 'dev'], {
1297
- ...execOptions,
1298
- detached: true,
1299
- stdio: 'pipe',
1300
- });
1301
-
1302
- const stdoutLines: string[] = [];
1303
-
1304
- const serverStarted = new Promise<any>((resolve, reject) => {
1305
- const timeout = setTimeout(() => {
1306
- reject(new Error(`Server startup timeout after 30 seconds. Output: ${stdoutLines.join('\n')}`));
1307
- }, 30000);
1308
-
1309
- serverProcess.stdout?.on('data', data => {
1310
- const output = data.toString();
1311
- const lines = output.split('\n').filter((line: string) => line.trim());
1312
- stdoutLines.push(...lines);
1313
-
1314
- if (output.includes('Mastra API running on port')) {
1315
- clearTimeout(timeout);
1316
- resolve({
1317
- success: true,
1318
- status: 'running' as const,
1319
- pid: serverProcess.pid,
1320
- port,
1321
- url: `http://localhost:${port}`,
1322
- message: `Mastra server started successfully on port ${port}`,
1323
- stdout: stdoutLines,
1324
- });
1325
- }
1326
- });
1327
-
1328
- serverProcess.stderr?.on('data', data => {
1329
- const errorOutput = data.toString();
1330
- stdoutLines.push(`[STDERR] ${errorOutput}`);
1331
- clearTimeout(timeout);
1332
- reject(new Error(`Server startup failed with error: ${errorOutput}`));
1333
- });
1334
-
1335
- serverProcess.on('error', error => {
1336
- clearTimeout(timeout);
1337
- reject(error);
1338
- });
1339
-
1340
- serverProcess.on('exit', (code, signal) => {
1341
- clearTimeout(timeout);
1342
- if (code !== 0 && code !== null) {
1343
- reject(
1344
- new Error(
1345
- `Server process exited with code ${code}${signal ? ` (signal: ${signal})` : ''}. Output: ${stdoutLines.join('\n')}`,
1346
- ),
1347
- );
1348
- }
1349
- });
1350
- });
1351
-
1352
- return await serverStarted;
1353
- } catch (error) {
1354
- return {
1355
- success: false,
1356
- status: 'stopped' as const,
1357
- error: error instanceof Error ? error.message : String(error),
1358
- };
1359
- }
1360
- }
1361
-
1362
- /**
1363
- * Stop the Mastra server
1364
- */
1365
- static async stopMastraServer({ port = 4200, projectPath: _projectPath }: { port?: number; projectPath?: string }) {
1366
- try {
1367
- const { stdout } = await exec(`lsof -ti:${port} || echo "No process found"`);
1368
-
1369
- if (!stdout.trim() || stdout.trim() === 'No process found') {
1370
- return {
1371
- success: true,
1372
- status: 'stopped' as const,
1373
- message: `No Mastra server found running on port ${port}`,
1374
- };
1375
- }
1376
-
1377
- const pids = stdout
1378
- .trim()
1379
- .split('\n')
1380
- .filter(pid => pid.trim());
1381
- const killedPids: number[] = [];
1382
- const failedPids: number[] = [];
1383
-
1384
- for (const pidStr of pids) {
1385
- const pid = parseInt(pidStr.trim());
1386
- if (isNaN(pid)) continue;
1387
-
1388
- try {
1389
- process.kill(pid, 'SIGTERM');
1390
- killedPids.push(pid);
1391
- } catch {
1392
- failedPids.push(pid);
1393
- }
1394
- }
1395
-
1396
- if (killedPids.length === 0) {
1397
- return {
1398
- success: false,
1399
- status: 'unknown' as const,
1400
- message: `Failed to stop any processes on port ${port}`,
1401
- error: `Could not kill PIDs: ${failedPids.join(', ')}`,
1402
- };
1403
- }
1404
-
1405
- // Wait a bit and check if processes are still running
1406
- await new Promise(resolve => setTimeout(resolve, 2000));
1407
-
1408
- try {
1409
- const { stdout: checkStdout } = await exec(`lsof -ti:${port} || echo "No process found"`);
1410
- if (checkStdout.trim() && checkStdout.trim() !== 'No process found') {
1411
- // Force kill remaining processes
1412
- const remainingPids = checkStdout
1413
- .trim()
1414
- .split('\n')
1415
- .filter(pid => pid.trim());
1416
- for (const pidStr of remainingPids) {
1417
- const pid = parseInt(pidStr.trim());
1418
- if (!isNaN(pid)) {
1419
- try {
1420
- process.kill(pid, 'SIGKILL');
1421
- } catch {
1422
- // ignore
1423
- }
1424
- }
1425
- }
1426
-
1427
- // Final check
1428
- await new Promise(resolve => setTimeout(resolve, 1000));
1429
- const { stdout: finalCheck } = await exec(`lsof -ti:${port} || echo "No process found"`);
1430
- if (finalCheck.trim() && finalCheck.trim() !== 'No process found') {
1431
- return {
1432
- success: false,
1433
- status: 'unknown' as const,
1434
- message: `Server processes still running on port ${port} after stop attempts`,
1435
- error: `Remaining PIDs: ${finalCheck.trim()}`,
1436
- };
1437
- }
1438
- }
1439
- } catch (error) {
1440
- console.warn('Failed to verify server stop:', error);
1441
- }
1442
-
1443
- return {
1444
- success: true,
1445
- status: 'stopped' as const,
1446
- message: `Mastra server stopped successfully (port ${port}). Killed PIDs: ${killedPids.join(', ')}`,
1447
- };
1448
- } catch (error) {
1449
- return {
1450
- success: false,
1451
- status: 'unknown' as const,
1452
- error: error instanceof Error ? error.message : String(error),
1453
- };
1454
- }
1455
- }
1456
-
1457
- /**
1458
- * Check Mastra server status
1459
- */
1460
- static async checkMastraServerStatus({
1461
- port = 4200,
1462
- projectPath: _projectPath,
1463
- }: {
1464
- port?: number;
1465
- projectPath?: string;
1466
- }) {
1467
- try {
1468
- const controller = new AbortController();
1469
- const timeoutId = setTimeout(() => controller.abort(), 5000);
1470
-
1471
- const response = await fetch(`http://localhost:${port}/health`, {
1472
- method: 'GET',
1473
- signal: controller.signal,
1474
- });
1475
-
1476
- clearTimeout(timeoutId);
1477
-
1478
- if (response.ok) {
1479
- return {
1480
- success: true,
1481
- status: 'running' as const,
1482
- port,
1483
- url: `http://localhost:${port}`,
1484
- message: 'Mastra server is running and healthy',
1485
- };
1486
- } else {
1487
- return {
1488
- success: false,
1489
- status: 'unknown' as const,
1490
- port,
1491
- message: `Server responding but not healthy (status: ${response.status})`,
1492
- };
1493
- }
1494
- } catch {
1495
- // Check if process exists on port
1496
- try {
1497
- const { stdout } = await exec(`lsof -ti:${port} || echo "No process found"`);
1498
- const hasProcess = stdout.trim() && stdout.trim() !== 'No process found';
1499
-
1500
- return {
1501
- success: Boolean(hasProcess),
1502
- status: hasProcess ? ('starting' as const) : ('stopped' as const),
1503
- port,
1504
- message: hasProcess
1505
- ? 'Server process exists but not responding to health checks'
1506
- : 'No server process found on specified port',
1507
- };
1508
- } catch {
1509
- return {
1510
- success: false,
1511
- status: 'stopped' as const,
1512
- port,
1513
- message: 'Server is not running',
1514
- };
1515
- }
1516
- }
1517
- }
1518
-
1519
- /**
1520
- * Validate code using TypeScript, ESLint, and other tools
1521
- */
1522
- static async validateCode({
1523
- projectPath,
1524
- validationType,
1525
- files,
1526
- }: {
1527
- projectPath?: string;
1528
- validationType: Array<'types' | 'lint' | 'schemas' | 'tests' | 'build'>;
1529
- files?: string[];
1530
- }) {
1531
- const errors: Array<{
1532
- type: 'typescript' | 'eslint' | 'schema' | 'test' | 'build';
1533
- severity: 'error' | 'warning' | 'info';
1534
- message: string;
1535
- file?: string;
1536
- line?: number;
1537
- column?: number;
1538
- code?: string;
1539
- }> = [];
1540
- const validationsPassed: string[] = [];
1541
- const validationsFailed: string[] = [];
1542
-
1543
- const execOptions = { cwd: projectPath };
1544
-
1545
- // TypeScript validation
1546
- if (validationType.includes('types')) {
1547
- try {
1548
- const filePattern = files?.length ? files.join(' ') : '';
1549
- const tscCommand = files?.length ? `npx tsc --noEmit ${filePattern}` : 'npx tsc --noEmit';
1550
- await exec(tscCommand, execOptions);
1551
- validationsPassed.push('types');
1552
- } catch (error: any) {
1553
- let tsOutput = '';
1554
- if (error.stdout) {
1555
- tsOutput = error.stdout;
1556
- } else if (error.stderr) {
1557
- tsOutput = error.stderr;
1558
- } else if (error.message) {
1559
- tsOutput = error.message;
1560
- }
1561
-
1562
- errors.push({
1563
- type: 'typescript',
1564
- severity: 'error',
1565
- message: tsOutput.trim() || `TypeScript validation failed: ${error.message || String(error)}`,
1566
- });
1567
- validationsFailed.push('types');
1568
- }
1569
- }
1570
-
1571
- // ESLint validation
1572
- if (validationType.includes('lint')) {
1573
- try {
1574
- const filePattern = files?.length ? files.join(' ') : '.';
1575
- const eslintCommand = `npx eslint ${filePattern} --format json`;
1576
- const { stdout } = await exec(eslintCommand, execOptions);
1577
-
1578
- if (stdout) {
1579
- const eslintResults = JSON.parse(stdout);
1580
- const eslintErrors = AgentBuilderDefaults.parseESLintErrors(eslintResults);
1581
- errors.push(...eslintErrors);
1582
-
1583
- if (eslintErrors.some(e => e.severity === 'error')) {
1584
- validationsFailed.push('lint');
1585
- } else {
1586
- validationsPassed.push('lint');
1587
- }
1588
- } else {
1589
- validationsPassed.push('lint');
1590
- }
1591
- } catch (error: any) {
1592
- const errorMessage = error instanceof Error ? error.message : String(error);
1593
-
1594
- if (errorMessage.includes('"filePath"') || errorMessage.includes('messages')) {
1595
- try {
1596
- const eslintResults = JSON.parse(errorMessage);
1597
- const eslintErrors = AgentBuilderDefaults.parseESLintErrors(eslintResults);
1598
- errors.push(...eslintErrors);
1599
- validationsFailed.push('lint');
1600
- } catch {
1601
- errors.push({
1602
- type: 'eslint',
1603
- severity: 'error',
1604
- message: `ESLint validation failed: ${errorMessage}`,
1605
- });
1606
- validationsFailed.push('lint');
1607
- }
1608
- } else {
1609
- validationsPassed.push('lint');
1610
- }
1611
- }
1612
- }
1613
-
1614
- // Build validation
1615
- // if (validationType.includes('build')) {
1616
- // try {
1617
- // await spawnSWPM(execOptions.cwd!, 'build', []);
1618
- // validationsPassed.push('build');
1619
- // } catch (error) {
1620
- // const errorMessage = error instanceof Error ? error.message : String(error);
1621
- // errors.push({
1622
- // type: 'build',
1623
- // severity: 'error',
1624
- // message: `Build failed: ${errorMessage}`,
1625
- // });
1626
- // validationsFailed.push('build');
1627
- // }
1628
- // }
1629
-
1630
- // Test validation
1631
- // if (validationType.includes('tests')) {
1632
- // try {
1633
- // const testCommand = files?.length ? `npx vitest run ${files.join(' ')}` : 'npm test || pnpm test || yarn test';
1634
- // await exec(testCommand, execOptions);
1635
- // validationsPassed.push('tests');
1636
- // } catch (error) {
1637
- // const errorMessage = error instanceof Error ? error.message : String(error);
1638
- // errors.push({
1639
- // type: 'test',
1640
- // severity: 'error',
1641
- // message: `Tests failed: ${errorMessage}`,
1642
- // });
1643
- // validationsFailed.push('tests');
1644
- // }
1645
- // }
1646
-
1647
- const totalErrors = errors.filter(e => e.severity === 'error').length;
1648
- const totalWarnings = errors.filter(e => e.severity === 'warning').length;
1649
- const isValid = totalErrors === 0;
1650
-
1651
- return {
1652
- valid: isValid,
1653
- errors,
1654
- summary: {
1655
- totalErrors,
1656
- totalWarnings,
1657
- validationsPassed,
1658
- validationsFailed,
1659
- },
1660
- };
1661
- }
1662
-
1663
- /**
1664
- * Parse ESLint errors from JSON output
1665
- */
1666
- static parseESLintErrors(eslintResults: any[]): Array<{
1667
- type: 'eslint';
1668
- severity: 'error' | 'warning';
1669
- message: string;
1670
- file?: string;
1671
- line?: number;
1672
- column?: number;
1673
- code?: string;
1674
- }> {
1675
- const errors: Array<{
1676
- type: 'eslint';
1677
- severity: 'error' | 'warning';
1678
- message: string;
1679
- file?: string;
1680
- line?: number;
1681
- column?: number;
1682
- code?: string;
1683
- }> = [];
1684
-
1685
- for (const result of eslintResults) {
1686
- for (const message of result.messages || []) {
1687
- if (message.message) {
1688
- errors.push({
1689
- type: 'eslint',
1690
- severity: message.severity === 1 ? 'warning' : 'error',
1691
- message: message.message,
1692
- file: result.filePath || undefined,
1693
- line: message.line || undefined,
1694
- column: message.column || undefined,
1695
- code: message.ruleId || undefined,
1696
- });
1697
- }
1698
- }
1699
- }
1700
-
1701
- return errors;
1702
- }
1703
-
1704
- /**
1705
- * Make HTTP request to server or external API
1706
- */
1707
- static async makeHttpRequest({
1708
- method,
1709
- url,
1710
- baseUrl,
1711
- headers = {},
1712
- body,
1713
- timeout = 30000,
1714
- }: {
1715
- method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
1716
- url: string;
1717
- baseUrl?: string;
1718
- headers?: Record<string, string>;
1719
- body?: any;
1720
- timeout?: number;
1721
- }) {
1722
- try {
1723
- const fullUrl = baseUrl ? `${baseUrl}${url}` : url;
1724
-
1725
- const controller = new AbortController();
1726
- const timeoutId = setTimeout(() => controller.abort(), timeout);
1727
-
1728
- const requestOptions: RequestInit = {
1729
- method,
1730
- headers: {
1731
- 'Content-Type': 'application/json',
1732
- ...headers,
1733
- },
1734
- signal: controller.signal,
1735
- };
1736
-
1737
- if (body && (method === 'POST' || method === 'PUT' || method === 'PATCH')) {
1738
- requestOptions.body = typeof body === 'string' ? body : JSON.stringify(body);
1739
- }
1740
-
1741
- const response = await fetch(fullUrl, requestOptions);
1742
- clearTimeout(timeoutId);
1743
-
1744
- let data: any;
1745
- const contentType = response.headers.get('content-type');
1746
- if (contentType?.includes('application/json')) {
1747
- data = await response.json();
1748
- } else {
1749
- data = await response.text();
1750
- }
1751
-
1752
- const responseHeaders: Record<string, string> = {};
1753
- response.headers.forEach((value, key) => {
1754
- responseHeaders[key] = value;
1755
- });
1756
-
1757
- return {
1758
- success: response.ok,
1759
- status: response.status,
1760
- statusText: response.statusText,
1761
- headers: responseHeaders,
1762
- data,
1763
- url: fullUrl,
1764
- method,
1765
- };
1766
- } catch (error) {
1767
- return {
1768
- success: false,
1769
- url: baseUrl ? `${baseUrl}${url}` : url,
1770
- method,
1771
- error: error instanceof Error ? error.message : String(error),
1772
- };
1773
- }
1774
- }
1775
-
1776
- /**
1777
- * Enhanced task management system for complex coding tasks
1778
- */
1779
- static async manageTaskList(context: {
1780
- action: 'create' | 'update' | 'list' | 'complete' | 'remove';
1781
- tasks?: Array<{
1782
- id: string;
1783
- content?: string;
1784
- status: 'pending' | 'in_progress' | 'completed' | 'blocked';
1785
- priority: 'high' | 'medium' | 'low';
1786
- dependencies?: string[];
1787
- notes?: string;
1788
- }>;
1789
- taskId?: string;
1790
- }) {
1791
- // In-memory task storage (could be enhanced with persistent storage)
1792
- if (!AgentBuilderDefaults.taskStorage) {
1793
- AgentBuilderDefaults.taskStorage = new Map();
1794
- }
1795
-
1796
- const sessionId = 'current'; // Could be enhanced with proper session management
1797
- const existingTasks = AgentBuilderDefaults.taskStorage.get(sessionId) || [];
1798
-
1799
- try {
1800
- switch (context.action) {
1801
- case 'create':
1802
- if (!context.tasks?.length) {
1803
- return {
1804
- success: false,
1805
- tasks: existingTasks,
1806
- message: 'No tasks provided for creation',
1807
- };
1808
- }
1809
-
1810
- const newTasks = context.tasks.map(task => ({
1811
- ...task,
1812
- createdAt: new Date().toISOString(),
1813
- updatedAt: new Date().toISOString(),
1814
- }));
1815
-
1816
- const allTasks = [...existingTasks, ...newTasks];
1817
- AgentBuilderDefaults.taskStorage.set(sessionId, allTasks);
1818
-
1819
- return {
1820
- success: true,
1821
- tasks: allTasks,
1822
- message: `Created ${newTasks.length} new task(s)`,
1823
- };
1824
-
1825
- case 'update':
1826
- if (!context.tasks?.length) {
1827
- return {
1828
- success: false,
1829
- tasks: existingTasks,
1830
- message: 'No tasks provided for update',
1831
- };
1832
- }
1833
-
1834
- const updatedTasks = existingTasks.map(existing => {
1835
- const update = context.tasks!.find(t => t.id === existing.id);
1836
- return update ? { ...existing, ...update, updatedAt: new Date().toISOString() } : existing;
1837
- });
1838
-
1839
- AgentBuilderDefaults.taskStorage.set(sessionId, updatedTasks);
1840
-
1841
- return {
1842
- success: true,
1843
- tasks: updatedTasks,
1844
- message: 'Tasks updated successfully',
1845
- };
1846
-
1847
- case 'complete':
1848
- if (!context.taskId) {
1849
- return {
1850
- success: false,
1851
- tasks: existingTasks,
1852
- message: 'Task ID required for completion',
1853
- };
1854
- }
1855
-
1856
- const completedTasks = existingTasks.map(task =>
1857
- task.id === context.taskId
1858
- ? { ...task, status: 'completed' as const, updatedAt: new Date().toISOString() }
1859
- : task,
1860
- );
1861
-
1862
- AgentBuilderDefaults.taskStorage.set(sessionId, completedTasks);
1863
-
1864
- return {
1865
- success: true,
1866
- tasks: completedTasks,
1867
- message: `Task ${context.taskId} marked as completed`,
1868
- };
1869
-
1870
- case 'remove':
1871
- if (!context.taskId) {
1872
- return {
1873
- success: false,
1874
- tasks: existingTasks,
1875
- message: 'Task ID required for removal',
1876
- };
1877
- }
1878
-
1879
- const filteredTasks = existingTasks.filter(task => task.id !== context.taskId);
1880
- AgentBuilderDefaults.taskStorage.set(sessionId, filteredTasks);
1881
-
1882
- return {
1883
- success: true,
1884
- tasks: filteredTasks,
1885
- message: `Task ${context.taskId} removed`,
1886
- };
1887
-
1888
- case 'list':
1889
- default:
1890
- return {
1891
- success: true,
1892
- tasks: existingTasks,
1893
- message: `Found ${existingTasks.length} task(s)`,
1894
- };
1895
- }
1896
- } catch (error) {
1897
- return {
1898
- success: false,
1899
- tasks: existingTasks,
1900
- message: `Task management error: ${error instanceof Error ? error.message : String(error)}`,
1901
- };
1902
- }
1903
- }
1904
-
1905
- /**
1906
- * Analyze codebase structure and patterns
1907
- */
1908
- static async analyzeCode(context: {
1909
- action: 'definitions' | 'dependencies' | 'patterns' | 'structure';
1910
- path: string;
1911
- language?: string;
1912
- depth?: number;
1913
- includeTests?: boolean;
1914
- }) {
1915
- try {
1916
- const { action, path, language, depth = 3 } = context;
1917
-
1918
- // Use ripgrep for fast searching
1919
- // const excludePatterns = includeTests ? [] : ['*test*', '*spec*', '__tests__'];
1920
- const languagePattern = language ? `*.${language}` : '*';
1921
-
1922
- switch (action) {
1923
- case 'definitions':
1924
- // Search for function/class/interface definitions
1925
- const definitionPatterns = [
1926
- 'function\\s+([a-zA-Z_][a-zA-Z0-9_]*)',
1927
- 'class\\s+([a-zA-Z_][a-zA-Z0-9_]*)',
1928
- 'interface\\s+([a-zA-Z_][a-zA-Z0-9_]*)',
1929
- 'const\\s+([a-zA-Z_][a-zA-Z0-9_]*)\\s*=',
1930
- 'export\\s+(function|class|interface|const)\\s+([a-zA-Z_][a-zA-Z0-9_]*)',
1931
- ];
1932
-
1933
- const definitions: Array<{ name: string; type: string; file: string; line?: number; scope?: string }> = [];
1934
-
1935
- for (const pattern of definitionPatterns) {
1936
- try {
1937
- const { stdout } = await exec(
1938
- `rg -n "${pattern}" "${path}" --type ${languagePattern} --max-depth ${depth}`,
1939
- );
1940
- const matches = stdout.split('\n').filter(line => line.trim());
1941
-
1942
- matches.forEach(match => {
1943
- const parts = match.split(':');
1944
- if (parts.length >= 3) {
1945
- const file = parts[0];
1946
- const lineStr = parts[1];
1947
- const line = parseInt(lineStr || '0');
1948
- const content = parts.slice(2).join(':');
1949
- const nameMatch = content.match(/([a-zA-Z_][a-zA-Z0-9_]*)/);
1950
-
1951
- if (nameMatch && nameMatch[1]) {
1952
- definitions.push({
1953
- name: nameMatch[1],
1954
- type: pattern.includes('function')
1955
- ? 'function'
1956
- : pattern.includes('class')
1957
- ? 'class'
1958
- : pattern.includes('interface')
1959
- ? 'interface'
1960
- : 'variable',
1961
- file: file || '',
1962
- line,
1963
- scope: 'top-level',
1964
- });
1965
- }
1966
- }
1967
- });
1968
- } catch {
1969
- // Continue with other patterns if one fails
1970
- }
1971
- }
1972
-
1973
- return {
1974
- success: true,
1975
- analysis: { definitions },
1976
- message: `Found ${definitions.length} code definitions`,
1977
- };
1978
-
1979
- case 'dependencies':
1980
- // Search for import/require statements
1981
- const depPatterns = [
1982
- 'import\\s+.*\\s+from\\s+[\'"]([^\'"]+)[\'"]',
1983
- 'require\\([\'"]([^\'"]+)[\'"]\\)',
1984
- '#include\\s+[<"]([^>"]+)[>"]',
1985
- ];
1986
-
1987
- const dependencies: Array<{
1988
- name: string;
1989
- type: 'import' | 'require' | 'include';
1990
- source: string;
1991
- target: string;
1992
- }> = [];
1993
-
1994
- for (const pattern of depPatterns) {
1995
- try {
1996
- const { stdout } = await exec(`rg -n "${pattern}" "${path}" --type ${languagePattern}`);
1997
- const matches = stdout.split('\n').filter(line => line.trim());
1998
-
1999
- matches.forEach(match => {
2000
- const parts = match.split(':');
2001
- if (parts.length >= 3) {
2002
- const file = parts[0];
2003
- const content = parts.slice(2).join(':');
2004
- const depMatch = content.match(new RegExp(pattern));
2005
-
2006
- if (depMatch && depMatch[1]) {
2007
- dependencies.push({
2008
- name: depMatch[1],
2009
- type: pattern.includes('import') ? 'import' : pattern.includes('require') ? 'require' : 'include',
2010
- source: file || '',
2011
- target: depMatch[1],
2012
- });
2013
- }
2014
- }
2015
- });
2016
- } catch {
2017
- // Continue with other patterns
2018
- }
2019
- }
2020
-
2021
- return {
2022
- success: true,
2023
- analysis: { dependencies },
2024
- message: `Found ${dependencies.length} dependencies`,
2025
- };
2026
-
2027
- case 'structure':
2028
- const { stdout: lsOutput } = await exec(`find "${path}" -type f -name "${languagePattern}" | head -1000`);
2029
- const files = lsOutput.split('\n').filter(line => line.trim());
2030
-
2031
- const { stdout: dirOutput } = await exec(`find "${path}" -type d | wc -l`);
2032
- const directories = parseInt(dirOutput.trim());
2033
-
2034
- // Count languages by file extension
2035
- const languages: Record<string, number> = {};
2036
- files.forEach(file => {
2037
- const ext = file.split('.').pop();
2038
- if (ext) {
2039
- languages[ext] = (languages[ext] || 0) + 1;
2040
- }
2041
- });
2042
-
2043
- const complexity = files.length > 1000 ? 'high' : files.length > 100 ? 'medium' : 'low';
2044
-
2045
- return {
2046
- success: true,
2047
- analysis: {
2048
- structure: {
2049
- directories,
2050
- files: files.length,
2051
- languages,
2052
- complexity,
2053
- },
2054
- },
2055
- message: `Analyzed project structure: ${files.length} files in ${directories} directories`,
2056
- };
2057
-
2058
- default:
2059
- return {
2060
- success: false,
2061
- analysis: {},
2062
- message: `Unknown analysis action: ${action}`,
2063
- };
2064
- }
2065
- } catch (error) {
2066
- return {
2067
- success: false,
2068
- analysis: {},
2069
- message: `Code analysis error: ${error instanceof Error ? error.message : String(error)}`,
2070
- };
2071
- }
2072
- }
2073
-
2074
- /**
2075
- * Perform multiple edits across files atomically
2076
- */
2077
- static async performMultiEdit(context: {
2078
- operations: Array<{
2079
- filePath: string;
2080
- edits: Array<{
2081
- oldString: string;
2082
- newString: string;
2083
- replaceAll?: boolean;
2084
- }>;
2085
- }>;
2086
- createBackup?: boolean;
2087
- projectPath?: string;
2088
- }) {
2089
- const results: Array<{
2090
- filePath: string;
2091
- editsApplied: number;
2092
- errors: string[];
2093
- backup?: string;
2094
- }> = [];
2095
-
2096
- try {
2097
- const { projectPath } = context;
2098
-
2099
- for (const operation of context.operations) {
2100
- // Resolve path relative to project directory if it's not absolute
2101
- const resolvedPath = isAbsolute(operation.filePath)
2102
- ? operation.filePath
2103
- : resolve(projectPath || process.cwd(), operation.filePath);
2104
-
2105
- const result = {
2106
- filePath: resolvedPath,
2107
- editsApplied: 0,
2108
- errors: [] as string[],
2109
- backup: undefined as string | undefined,
2110
- };
2111
-
2112
- try {
2113
- // Read file content
2114
- const originalContent = await readFile(resolvedPath, 'utf-8');
2115
-
2116
- // Create backup if requested
2117
- if (context.createBackup) {
2118
- const backupPath = `${resolvedPath}.backup.${Date.now()}`;
2119
- await writeFile(backupPath, originalContent);
2120
- result.backup = backupPath;
2121
- }
2122
-
2123
- let modifiedContent = originalContent;
2124
-
2125
- // Apply edits sequentially
2126
- for (const edit of operation.edits) {
2127
- if (edit.replaceAll) {
2128
- const regex = new RegExp(edit.oldString.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g');
2129
- const matches = modifiedContent.match(regex);
2130
- if (matches) {
2131
- modifiedContent = modifiedContent.replace(regex, edit.newString);
2132
- result.editsApplied += matches.length;
2133
- }
2134
- } else {
2135
- if (modifiedContent.includes(edit.oldString)) {
2136
- modifiedContent = modifiedContent.replace(edit.oldString, edit.newString);
2137
- result.editsApplied++;
2138
- } else {
2139
- result.errors.push(`String not found: "${edit.oldString.substring(0, 50)}..."`);
2140
- }
2141
- }
2142
- }
2143
-
2144
- // Write modified content
2145
- if (result.editsApplied > 0) {
2146
- await writeFile(resolvedPath, modifiedContent);
2147
- }
2148
- } catch (error) {
2149
- result.errors.push(error instanceof Error ? error.message : String(error));
2150
- }
2151
-
2152
- results.push(result);
2153
- }
2154
-
2155
- const totalEdits = results.reduce((sum, r) => sum + r.editsApplied, 0);
2156
- const totalErrors = results.reduce((sum, r) => sum + r.errors.length, 0);
2157
-
2158
- return {
2159
- success: totalErrors === 0,
2160
- results,
2161
- message: `Applied ${totalEdits} edits across ${results.length} files${totalErrors > 0 ? ` with ${totalErrors} errors` : ''}`,
2162
- };
2163
- } catch (error) {
2164
- return {
2165
- success: false,
2166
- results,
2167
- message: `Multi-edit operation failed: ${error instanceof Error ? error.message : String(error)}`,
2168
- };
2169
- }
2170
- }
2171
-
2172
- /**
2173
- * Ask user for clarification
2174
- */
2175
- static async askClarification(context: {
2176
- question: string;
2177
- options?: Array<{ id: string; description: string; implications?: string }>;
2178
- context?: string;
2179
- urgency?: 'low' | 'medium' | 'high';
2180
- }) {
2181
- const questionId = `q_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
2182
-
2183
- // Store question for potential follow-up (in real implementation, this might be stored in session state)
2184
- if (!AgentBuilderDefaults.pendingQuestions) {
2185
- AgentBuilderDefaults.pendingQuestions = new Map();
2186
- }
2187
-
2188
- AgentBuilderDefaults.pendingQuestions.set(questionId, {
2189
- ...context,
2190
- timestamp: new Date().toISOString(),
2191
- });
2192
-
2193
- return {
2194
- questionId,
2195
- question: context.question,
2196
- options: context.options?.map(opt => ({ id: opt.id, description: opt.description })),
2197
- awaitingResponse: true,
2198
- };
2199
- }
2200
-
2201
- /**
2202
- * Signal task completion
2203
- */
2204
- static async signalCompletion(context: {
2205
- summary: string;
2206
- changes: Array<{
2207
- type: 'file_created' | 'file_modified' | 'file_deleted' | 'command_executed' | 'dependency_added';
2208
- description: string;
2209
- path?: string;
2210
- }>;
2211
- validation: {
2212
- testsRun?: boolean;
2213
- buildsSuccessfully?: boolean;
2214
- manualTestingRequired?: boolean;
2215
- };
2216
- nextSteps?: string[];
2217
- }) {
2218
- const completionId = `completion_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
2219
-
2220
- // Calculate confidence based on validation status
2221
- let confidence = 70; // Base confidence
2222
- if (context.validation.testsRun) confidence += 15;
2223
- if (context.validation.buildsSuccessfully) confidence += 15;
2224
- if (context.validation.manualTestingRequired) confidence -= 10;
2225
-
2226
- // Determine status
2227
- let status: 'completed' | 'needs_review' | 'needs_testing';
2228
- if (context.validation.testsRun && context.validation.buildsSuccessfully) {
2229
- status = 'completed';
2230
- } else if (context.validation.manualTestingRequired) {
2231
- status = 'needs_testing';
2232
- } else {
2233
- status = 'needs_review';
2234
- }
2235
-
2236
- return {
2237
- completionId,
2238
- status,
2239
- summary: context.summary,
2240
- confidence: Math.min(100, Math.max(0, confidence)),
2241
- };
2242
- }
2243
-
2244
- /**
2245
- * Perform intelligent search with context
2246
- */
2247
- static async performSmartSearch(context: {
2248
- query: string;
2249
- type?: 'text' | 'regex' | 'fuzzy' | 'semantic';
2250
- scope?: {
2251
- paths?: string[];
2252
- fileTypes?: string[];
2253
- excludePaths?: string[];
2254
- maxResults?: number;
2255
- };
2256
- context?: {
2257
- beforeLines?: number;
2258
- afterLines?: number;
2259
- includeDefinitions?: boolean;
2260
- };
2261
- }) {
2262
- try {
2263
- const { query, type = 'text', scope = {}, context: searchContext = {} } = context;
2264
-
2265
- const { paths = ['.'], fileTypes = [], excludePaths = [], maxResults = 50 } = scope;
2266
-
2267
- const { beforeLines = 2, afterLines = 2 } = searchContext;
2268
-
2269
- let rgCommand = 'rg';
2270
-
2271
- // Add context lines
2272
- if (beforeLines > 0 || afterLines > 0) {
2273
- rgCommand += ` -A ${afterLines} -B ${beforeLines}`;
2274
- }
2275
-
2276
- // Add line numbers
2277
- rgCommand += ' -n';
2278
-
2279
- // Handle search type
2280
- if (type === 'regex') {
2281
- rgCommand += ' -e';
2282
- } else if (type === 'fuzzy') {
2283
- rgCommand += ' --fixed-strings';
2284
- }
2285
-
2286
- // Add file type filters
2287
- if (fileTypes.length > 0) {
2288
- fileTypes.forEach(ft => {
2289
- rgCommand += ` --type-add 'custom:*.${ft}' -t custom`;
2290
- });
2291
- }
2292
-
2293
- // Add exclude patterns
2294
- excludePaths.forEach(path => {
2295
- rgCommand += ` --glob '!${path}'`;
2296
- });
2297
-
2298
- // Add max count
2299
- rgCommand += ` -m ${maxResults}`;
2300
-
2301
- // Add search paths
2302
- rgCommand += ` "${query}" ${paths.join(' ')}`;
2303
-
2304
- const { stdout } = await exec(rgCommand);
2305
- const lines = stdout.split('\n').filter(line => line.trim());
2306
-
2307
- const matches: Array<{
2308
- file: string;
2309
- line: number;
2310
- column?: number;
2311
- match: string;
2312
- context: { before: string[]; after: string[] };
2313
- relevance?: number;
2314
- }> = [];
2315
-
2316
- let currentMatch: any = null;
2317
-
2318
- lines.forEach(line => {
2319
- if (line.includes(':') && !line.startsWith('-')) {
2320
- // This is a match line
2321
- const parts = line.split(':');
2322
- if (parts.length >= 3) {
2323
- // Save previous match if exists
2324
- if (currentMatch) {
2325
- matches.push(currentMatch);
2326
- }
2327
-
2328
- currentMatch = {
2329
- file: parts[0] || '',
2330
- line: parseInt(parts[1] || '0'),
2331
- match: parts.slice(2).join(':'),
2332
- context: { before: [], after: [] },
2333
- relevance: type === 'fuzzy' ? Math.random() * 100 : undefined,
2334
- };
2335
- }
2336
- } else if (line.startsWith('-') && currentMatch) {
2337
- // This is a context line
2338
- const contextLine = line.substring(1);
2339
- if (currentMatch.context.before.length < beforeLines) {
2340
- currentMatch.context.before.push(contextLine);
2341
- } else {
2342
- currentMatch.context.after.push(contextLine);
2343
- }
2344
- }
2345
- });
2346
-
2347
- // Add the last match
2348
- if (currentMatch) {
2349
- matches.push(currentMatch);
2350
- }
2351
-
2352
- // Count files searched (approximate)
2353
- const filesSearched = new Set(matches.map(m => m.file)).size;
2354
-
2355
- return {
2356
- success: true,
2357
- matches: matches.slice(0, maxResults),
2358
- summary: {
2359
- totalMatches: matches.length,
2360
- filesSearched,
2361
- patterns: [query],
2362
- },
2363
- };
2364
- } catch {
2365
- return {
2366
- success: false,
2367
- matches: [],
2368
- summary: {
2369
- totalMatches: 0,
2370
- filesSearched: 0,
2371
- patterns: [context.query],
2372
- },
2373
- };
2374
- }
2375
- }
2376
-
2377
- // Static storage properties
2378
- private static taskStorage: Map<string, any[]>;
2379
- private static pendingQuestions: Map<string, any>;
2380
-
2381
- /**
2382
- * Read file contents with optional line range
2383
- */
2384
- static async readFile(context: {
2385
- filePath: string;
2386
- startLine?: number;
2387
- endLine?: number;
2388
- encoding?: string;
2389
- projectPath?: string;
2390
- }) {
2391
- try {
2392
- const { filePath, startLine, endLine, encoding = 'utf-8', projectPath } = context;
2393
-
2394
- // Resolve path relative to project directory if it's not absolute
2395
- const resolvedPath = isAbsolute(filePath) ? filePath : resolve(projectPath || process.cwd(), filePath);
2396
-
2397
- const stats = await stat(resolvedPath);
2398
- const content = await readFile(resolvedPath, { encoding: encoding as BufferEncoding });
2399
- const lines = content.split('\n');
2400
-
2401
- let resultContent = content;
2402
- let resultLines = lines;
2403
-
2404
- if (startLine !== undefined || endLine !== undefined) {
2405
- const start = Math.max(0, (startLine || 1) - 1);
2406
- const end = endLine !== undefined ? Math.min(lines.length, endLine) : lines.length;
2407
- resultLines = lines.slice(start, end);
2408
- resultContent = resultLines.join('\n');
2409
- }
2410
-
2411
- return {
2412
- success: true,
2413
- content: resultContent,
2414
- lines: resultLines,
2415
- metadata: {
2416
- size: stats.size,
2417
- totalLines: lines.length,
2418
- encoding,
2419
- lastModified: stats.mtime.toISOString(),
2420
- },
2421
- };
2422
- } catch (error) {
2423
- return {
2424
- success: false,
2425
- error: error instanceof Error ? error.message : String(error),
2426
- };
2427
- }
2428
- }
2429
-
2430
- /**
2431
- * Write content to file with directory creation and backup options
2432
- */
2433
- static async writeFile(context: {
2434
- filePath: string;
2435
- content: string;
2436
- createDirs?: boolean;
2437
- encoding?: string;
2438
- projectPath?: string;
2439
- }) {
2440
- try {
2441
- const { filePath, content, createDirs = true, encoding = 'utf-8', projectPath } = context;
2442
-
2443
- // Resolve path relative to project directory if it's not absolute
2444
- const resolvedPath = isAbsolute(filePath) ? filePath : resolve(projectPath || process.cwd(), filePath);
2445
- const dir = dirname(resolvedPath);
2446
-
2447
- // Create directories if needed
2448
- if (createDirs) {
2449
- await mkdir(dir, { recursive: true });
2450
- }
2451
-
2452
- // Write the file
2453
- await writeFile(resolvedPath, content, { encoding: encoding as BufferEncoding });
2454
-
2455
- return {
2456
- success: true,
2457
- filePath: resolvedPath,
2458
- bytesWritten: Buffer.byteLength(content, encoding as BufferEncoding),
2459
- message: `Successfully wrote ${Buffer.byteLength(content, encoding as BufferEncoding)} bytes to ${filePath}`,
2460
- };
2461
- } catch (error) {
2462
- return {
2463
- success: false,
2464
- filePath: context.filePath,
2465
- message: `Failed to write file: ${error instanceof Error ? error.message : String(error)}`,
2466
- error: error instanceof Error ? error.message : String(error),
2467
- };
2468
- }
2469
- }
2470
-
2471
- /**
2472
- * List directory contents with filtering and metadata
2473
- */
2474
- static async listDirectory(context: {
2475
- path: string;
2476
- recursive?: boolean;
2477
- includeHidden?: boolean;
2478
- pattern?: string;
2479
- maxDepth?: number;
2480
- includeMetadata?: boolean;
2481
- projectPath?: string;
2482
- }) {
2483
- try {
2484
- const {
2485
- path,
2486
- recursive = false,
2487
- includeHidden = false,
2488
- pattern,
2489
- maxDepth = 10,
2490
- includeMetadata = true,
2491
- projectPath,
2492
- } = context;
2493
-
2494
- // Resolve path relative to project directory if it's not absolute
2495
- const resolvedPath = isAbsolute(path) ? path : resolve(projectPath || process.cwd(), path);
2496
-
2497
- const items: Array<{
2498
- name: string;
2499
- path: string;
2500
- type: 'file' | 'directory' | 'symlink';
2501
- size?: number;
2502
- lastModified?: string;
2503
- permissions?: string;
2504
- }> = [];
2505
-
2506
- async function processDirectory(dirPath: string, currentDepth: number = 0) {
2507
- if (currentDepth > maxDepth) return;
2508
-
2509
- const entries = await readdir(dirPath);
2510
-
2511
- for (const entry of entries) {
2512
- if (!includeHidden && entry.startsWith('.')) continue;
2513
-
2514
- const fullPath = join(dirPath, entry);
2515
- const relativePath = relative(resolvedPath, fullPath);
2516
-
2517
- if (pattern) {
2518
- // Simple pattern matching
2519
- const regexPattern = pattern.replace(/\*/g, '.*').replace(/\?/g, '.');
2520
- if (!new RegExp(regexPattern).test(entry)) continue;
2521
- }
2522
-
2523
- let stats;
2524
- let type: 'file' | 'directory' | 'symlink';
2525
-
2526
- try {
2527
- stats = await stat(fullPath);
2528
- if (stats.isDirectory()) {
2529
- type = 'directory';
2530
- } else if (stats.isSymbolicLink()) {
2531
- type = 'symlink';
2532
- } else {
2533
- type = 'file';
2534
- }
2535
- } catch {
2536
- continue; // Skip entries we can't stat
2537
- }
2538
-
2539
- const item: any = {
2540
- name: entry,
2541
- path: relativePath || entry,
2542
- type,
2543
- };
2544
-
2545
- if (includeMetadata) {
2546
- item.size = stats.size;
2547
- item.lastModified = stats.mtime.toISOString();
2548
- item.permissions = `0${(stats.mode & parseInt('777', 8)).toString(8)}`;
2549
- }
2550
-
2551
- items.push(item);
2552
-
2553
- // Recurse into directories if requested
2554
- if (recursive && type === 'directory') {
2555
- await processDirectory(fullPath, currentDepth + 1);
2556
- }
2557
- }
2558
- }
2559
-
2560
- await processDirectory(resolvedPath);
2561
-
2562
- return {
2563
- success: true,
2564
- items,
2565
- totalItems: items.length,
2566
- path: resolvedPath,
2567
- message: `Listed ${items.length} items in ${resolvedPath}`,
2568
- };
2569
- } catch (error) {
2570
- return {
2571
- success: false,
2572
- items: [],
2573
- totalItems: 0,
2574
- path: context.path,
2575
- message: `Failed to list directory: ${error instanceof Error ? error.message : String(error)}`,
2576
- error: error instanceof Error ? error.message : String(error),
2577
- };
2578
- }
2579
- }
2580
-
2581
- /**
2582
- * Execute shell commands with proper error handling
2583
- */
2584
- static async executeCommand(context: {
2585
- command: string;
2586
- workingDirectory?: string;
2587
- timeout?: number;
2588
- captureOutput?: boolean;
2589
- shell?: string;
2590
- env?: Record<string, string>;
2591
- }) {
2592
- const startTime = Date.now();
2593
- try {
2594
- const { command, workingDirectory, timeout = 30000, captureOutput = true, shell, env } = context;
2595
-
2596
- const execOptions: any = {
2597
- timeout,
2598
- env: { ...process.env, ...env },
2599
- };
2600
-
2601
- if (workingDirectory) {
2602
- execOptions.cwd = workingDirectory;
2603
- }
2604
-
2605
- if (shell) {
2606
- execOptions.shell = shell;
2607
- }
2608
-
2609
- const { stdout, stderr } = await exec(command, execOptions);
2610
- const executionTime = Date.now() - startTime;
2611
-
2612
- return {
2613
- success: true,
2614
- exitCode: 0,
2615
- stdout: captureOutput ? String(stdout) : undefined,
2616
- stderr: captureOutput ? String(stderr) : undefined,
2617
- command,
2618
- workingDirectory,
2619
- executionTime,
2620
- };
2621
- } catch (error: any) {
2622
- const executionTime = Date.now() - startTime;
2623
-
2624
- return {
2625
- success: false,
2626
- exitCode: error.code || 1,
2627
- stdout: String(error.stdout || ''),
2628
- stderr: String(error.stderr || ''),
2629
- command: context.command,
2630
- workingDirectory: context.workingDirectory,
2631
- executionTime,
2632
- error: error instanceof Error ? error.message : String(error),
2633
- };
2634
- }
2635
- }
2636
-
2637
- /**
2638
- * Web search using a simple search approach
2639
- */
2640
- static async webSearch(context: {
2641
- query: string;
2642
- maxResults?: number;
2643
- region?: string;
2644
- language?: string;
2645
- includeImages?: boolean;
2646
- dateRange?: 'day' | 'week' | 'month' | 'year' | 'all';
2647
- }) {
2648
- try {
2649
- const {
2650
- query,
2651
- maxResults = 10,
2652
- // region = 'us',
2653
- // language = 'en',
2654
- // includeImages = false,
2655
- // dateRange = 'all',
2656
- } = context;
2657
-
2658
- const startTime = Date.now();
2659
-
2660
- // For now, implement a basic search using DuckDuckGo's instant answer API
2661
- // In a real implementation, you'd want to use a proper search API
2662
- const searchUrl = `https://api.duckduckgo.com/?q=${encodeURIComponent(query)}&format=json&no_redirect=1&skip_disambig=1`;
2663
-
2664
- const response = await fetch(searchUrl);
2665
- const data: any = await response.json();
2666
-
2667
- const results: Array<{
2668
- title: string;
2669
- url: string;
2670
- snippet: string;
2671
- domain: string;
2672
- publishDate?: string;
2673
- relevanceScore?: number;
2674
- }> = [];
2675
-
2676
- // Parse DuckDuckGo results
2677
- if (data.RelatedTopics && Array.isArray(data.RelatedTopics)) {
2678
- for (const topic of data.RelatedTopics.slice(0, maxResults)) {
2679
- if (topic.FirstURL && topic.Text) {
2680
- const url = new URL(topic.FirstURL);
2681
- results.push({
2682
- title: topic.Text.split(' - ')[0] || topic.Text.substring(0, 60),
2683
- url: topic.FirstURL,
2684
- snippet: topic.Text,
2685
- domain: url.hostname,
2686
- relevanceScore: Math.random() * 100, // Placeholder scoring
2687
- });
2688
- }
2689
- }
2690
- }
2691
-
2692
- // Add abstract as first result if available
2693
- if (data.Abstract && data.AbstractURL) {
2694
- const url = new URL(data.AbstractURL);
2695
- results.unshift({
2696
- title: data.Heading || 'Main Result',
2697
- url: data.AbstractURL,
2698
- snippet: data.Abstract,
2699
- domain: url.hostname,
2700
- relevanceScore: 100,
2701
- });
2702
- }
2703
-
2704
- const searchTime = Date.now() - startTime;
2705
-
2706
- return {
2707
- success: true,
2708
- query,
2709
- results: results.slice(0, maxResults),
2710
- totalResults: results.length,
2711
- searchTime,
2712
- suggestions:
2713
- data.RelatedTopics?.slice(maxResults, maxResults + 3)
2714
- ?.map((t: any) => t.Text?.split(' - ')[0] || t.Text?.substring(0, 30))
2715
- .filter(Boolean) || [],
2716
- };
2717
- } catch (error) {
2718
- return {
2719
- success: false,
2720
- query: context.query,
2721
- results: [],
2722
- totalResults: 0,
2723
- searchTime: 0,
2724
- error: error instanceof Error ? error.message : String(error),
2725
- };
2726
- }
2727
- }
2728
- }