promptarchitect 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.vscodeignore +7 -0
- package/CHANGELOG.md +28 -0
- package/LICENSE +44 -0
- package/README.md +200 -0
- package/docs/CHAT_UI_REDESIGN_PLAN.md +371 -0
- package/images/hub-icon.svg +6 -0
- package/images/prompt-lab-icon.svg +11 -0
- package/package.json +519 -0
- package/src/agentPrompts.ts +278 -0
- package/src/agentService.ts +630 -0
- package/src/api.ts +223 -0
- package/src/authService.ts +556 -0
- package/src/chatPanel.ts +979 -0
- package/src/extension.ts +822 -0
- package/src/providers/aiChatViewProvider.ts +1023 -0
- package/src/providers/environmentTreeProvider.ts +311 -0
- package/src/providers/index.ts +9 -0
- package/src/providers/notesTreeProvider.ts +301 -0
- package/src/providers/quickAccessTreeProvider.ts +328 -0
- package/src/providers/scriptsTreeProvider.ts +324 -0
- package/src/refinerPanel.ts +620 -0
- package/src/templates.ts +61 -0
- package/src/workspaceIndexer.ts +766 -0
- package/tsconfig.json +16 -0
|
@@ -0,0 +1,630 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent Service
|
|
3
|
+
*
|
|
4
|
+
* Powers the AI agent capabilities within VS Code.
|
|
5
|
+
* Handles tool composition, workspace context, and autonomous actions.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import * as vscode from 'vscode';
|
|
9
|
+
import * as path from 'path';
|
|
10
|
+
import * as fs from 'fs/promises';
|
|
11
|
+
import { PromptArchitectAPI } from './api';
|
|
12
|
+
import { WorkspaceIndexer } from './workspaceIndexer';
|
|
13
|
+
|
|
14
|
+
// ============================================================================
|
|
15
|
+
// TYPES
|
|
16
|
+
// ============================================================================
|
|
17
|
+
|
|
18
|
+
export interface AgentAction {
|
|
19
|
+
type: 'read_file' | 'write_file' | 'execute_command' | 'search_files' |
|
|
20
|
+
'insert_code' | 'open_file' | 'refine_prompt' | 'generate_prompt' |
|
|
21
|
+
'run_test' | 'install_package' | 'git_status';
|
|
22
|
+
params: Record<string, any>;
|
|
23
|
+
description: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface AgentResponse {
|
|
27
|
+
message: string;
|
|
28
|
+
actions?: AgentAction[];
|
|
29
|
+
suggestions?: string[];
|
|
30
|
+
codeBlocks?: CodeBlock[];
|
|
31
|
+
workspaceAware?: boolean;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface CodeBlock {
|
|
35
|
+
language: string;
|
|
36
|
+
code: string;
|
|
37
|
+
filename?: string;
|
|
38
|
+
action?: 'insert' | 'replace' | 'create';
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface WorkspaceContext {
|
|
42
|
+
name: string;
|
|
43
|
+
techStack: string[];
|
|
44
|
+
dependencies: Record<string, string>;
|
|
45
|
+
structure: string[];
|
|
46
|
+
currentFile?: {
|
|
47
|
+
path: string;
|
|
48
|
+
language: string;
|
|
49
|
+
content?: string;
|
|
50
|
+
selection?: string;
|
|
51
|
+
};
|
|
52
|
+
recentFiles: string[];
|
|
53
|
+
gitStatus?: string;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface AgentConfig {
|
|
57
|
+
autoInjectContext: boolean;
|
|
58
|
+
proactiveRefine: boolean;
|
|
59
|
+
suggestActions: boolean;
|
|
60
|
+
streamResponses: boolean;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// ============================================================================
|
|
64
|
+
// AGENT SERVICE
|
|
65
|
+
// ============================================================================
|
|
66
|
+
|
|
67
|
+
export class AgentService {
|
|
68
|
+
private context: vscode.ExtensionContext;
|
|
69
|
+
private api: PromptArchitectAPI;
|
|
70
|
+
private workspaceIndexer: WorkspaceIndexer;
|
|
71
|
+
private config: AgentConfig;
|
|
72
|
+
private recentFiles: string[] = [];
|
|
73
|
+
private static instance: AgentService;
|
|
74
|
+
|
|
75
|
+
private constructor(
|
|
76
|
+
context: vscode.ExtensionContext,
|
|
77
|
+
api: PromptArchitectAPI,
|
|
78
|
+
workspaceIndexer: WorkspaceIndexer
|
|
79
|
+
) {
|
|
80
|
+
this.context = context;
|
|
81
|
+
this.api = api;
|
|
82
|
+
this.workspaceIndexer = workspaceIndexer;
|
|
83
|
+
this.config = this.loadConfig();
|
|
84
|
+
|
|
85
|
+
// Track file opens
|
|
86
|
+
this.setupFileTracking();
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
static getInstance(
|
|
90
|
+
context: vscode.ExtensionContext,
|
|
91
|
+
api: PromptArchitectAPI,
|
|
92
|
+
workspaceIndexer: WorkspaceIndexer
|
|
93
|
+
): AgentService {
|
|
94
|
+
if (!AgentService.instance) {
|
|
95
|
+
AgentService.instance = new AgentService(context, api, workspaceIndexer);
|
|
96
|
+
}
|
|
97
|
+
return AgentService.instance;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
private loadConfig(): AgentConfig {
|
|
101
|
+
const vsConfig = vscode.workspace.getConfiguration('promptarchitect');
|
|
102
|
+
return {
|
|
103
|
+
autoInjectContext: vsConfig.get<boolean>('autoInjectContext', true),
|
|
104
|
+
proactiveRefine: vsConfig.get<boolean>('proactiveRefine', false),
|
|
105
|
+
suggestActions: vsConfig.get<boolean>('suggestActions', true),
|
|
106
|
+
streamResponses: vsConfig.get<boolean>('streamResponses', true),
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
private setupFileTracking() {
|
|
111
|
+
vscode.window.onDidChangeActiveTextEditor((editor) => {
|
|
112
|
+
if (editor) {
|
|
113
|
+
const filePath = editor.document.uri.fsPath;
|
|
114
|
+
this.recentFiles = [
|
|
115
|
+
filePath,
|
|
116
|
+
...this.recentFiles.filter(f => f !== filePath).slice(0, 9)
|
|
117
|
+
];
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// ============================================================================
|
|
123
|
+
// WORKSPACE CONTEXT
|
|
124
|
+
// ============================================================================
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Get comprehensive workspace context for agent operations
|
|
128
|
+
*/
|
|
129
|
+
async getWorkspaceContext(): Promise<WorkspaceContext> {
|
|
130
|
+
const workspaceFolder = vscode.workspace.workspaceFolders?.[0];
|
|
131
|
+
const workspaceName = workspaceFolder?.name || 'Unknown';
|
|
132
|
+
const workspacePath = workspaceFolder?.uri.fsPath || '';
|
|
133
|
+
|
|
134
|
+
// Get indexed data
|
|
135
|
+
const index = this.workspaceIndexer.getIndex();
|
|
136
|
+
|
|
137
|
+
// Get current file context
|
|
138
|
+
const editor = vscode.window.activeTextEditor;
|
|
139
|
+
let currentFile: WorkspaceContext['currentFile'];
|
|
140
|
+
|
|
141
|
+
if (editor) {
|
|
142
|
+
currentFile = {
|
|
143
|
+
path: path.relative(workspacePath, editor.document.uri.fsPath),
|
|
144
|
+
language: editor.document.languageId,
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
// Include selection if any
|
|
148
|
+
if (!editor.selection.isEmpty) {
|
|
149
|
+
currentFile.selection = editor.document.getText(editor.selection);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Get git status
|
|
154
|
+
let gitStatus: string | undefined;
|
|
155
|
+
try {
|
|
156
|
+
const { exec } = await import('child_process');
|
|
157
|
+
const { promisify } = await import('util');
|
|
158
|
+
const execAsync = promisify(exec);
|
|
159
|
+
const { stdout } = await execAsync('git status --porcelain', { cwd: workspacePath });
|
|
160
|
+
if (stdout.trim()) {
|
|
161
|
+
gitStatus = stdout.trim();
|
|
162
|
+
}
|
|
163
|
+
} catch {
|
|
164
|
+
// Git not available or not a git repo
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Get dependencies from package.json
|
|
168
|
+
let dependencies: Record<string, string> = {};
|
|
169
|
+
let devDependencies: Record<string, string> = {};
|
|
170
|
+
let techStack: string[] = [];
|
|
171
|
+
|
|
172
|
+
try {
|
|
173
|
+
const pkgPath = path.join(workspacePath, 'package.json');
|
|
174
|
+
const pkgContent = await fs.readFile(pkgPath, 'utf-8');
|
|
175
|
+
const pkg = JSON.parse(pkgContent);
|
|
176
|
+
dependencies = pkg.dependencies || {};
|
|
177
|
+
devDependencies = pkg.devDependencies || {};
|
|
178
|
+
|
|
179
|
+
// Detect tech stack
|
|
180
|
+
const allDeps = { ...dependencies, ...devDependencies };
|
|
181
|
+
if (allDeps['react']) techStack.push('React');
|
|
182
|
+
if (allDeps['next']) techStack.push('Next.js');
|
|
183
|
+
if (allDeps['vue']) techStack.push('Vue.js');
|
|
184
|
+
if (allDeps['express']) techStack.push('Express.js');
|
|
185
|
+
if (allDeps['typescript']) techStack.push('TypeScript');
|
|
186
|
+
if (allDeps['tailwindcss']) techStack.push('Tailwind CSS');
|
|
187
|
+
if (allDeps['firebase']) techStack.push('Firebase');
|
|
188
|
+
if (allDeps['@google/generative-ai']) techStack.push('Google Gemini');
|
|
189
|
+
if (allDeps['vite']) techStack.push('Vite');
|
|
190
|
+
} catch {
|
|
191
|
+
// No package.json
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Get structure from index or scan
|
|
195
|
+
const structure = index?.structure.map(s => s.path) || [];
|
|
196
|
+
|
|
197
|
+
return {
|
|
198
|
+
name: workspaceName,
|
|
199
|
+
techStack,
|
|
200
|
+
dependencies: { ...dependencies, ...devDependencies },
|
|
201
|
+
structure: structure.slice(0, 50),
|
|
202
|
+
currentFile,
|
|
203
|
+
recentFiles: this.recentFiles.map(f => path.relative(workspacePath, f)).slice(0, 5),
|
|
204
|
+
gitStatus,
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Format workspace context for inclusion in prompts
|
|
210
|
+
*/
|
|
211
|
+
formatContextForPrompt(context: WorkspaceContext): string {
|
|
212
|
+
const parts: string[] = [];
|
|
213
|
+
|
|
214
|
+
parts.push(`## Project: ${context.name}`);
|
|
215
|
+
|
|
216
|
+
if (context.techStack.length > 0) {
|
|
217
|
+
parts.push(`**Tech Stack:** ${context.techStack.join(', ')}`);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if (context.currentFile) {
|
|
221
|
+
parts.push(`\n**Current File:** ${context.currentFile.path} (${context.currentFile.language})`);
|
|
222
|
+
if (context.currentFile.selection) {
|
|
223
|
+
parts.push(`\n**Selected Code:**\n\`\`\`${context.currentFile.language}\n${context.currentFile.selection}\n\`\`\``);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (context.recentFiles.length > 0) {
|
|
228
|
+
parts.push(`\n**Recent Files:** ${context.recentFiles.join(', ')}`);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
if (context.gitStatus) {
|
|
232
|
+
parts.push(`\n**Git Status:** ${context.gitStatus.split('\n').length} file(s) modified`);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
return parts.join('\n');
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// ============================================================================
|
|
239
|
+
// AGENT ACTIONS
|
|
240
|
+
// ============================================================================
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Execute an agent action
|
|
244
|
+
*/
|
|
245
|
+
async executeAction(action: AgentAction): Promise<string> {
|
|
246
|
+
switch (action.type) {
|
|
247
|
+
case 'read_file':
|
|
248
|
+
return this.readFile(action.params.path);
|
|
249
|
+
|
|
250
|
+
case 'write_file':
|
|
251
|
+
return this.writeFile(action.params.path, action.params.content);
|
|
252
|
+
|
|
253
|
+
case 'execute_command':
|
|
254
|
+
return this.executeCommand(action.params.command);
|
|
255
|
+
|
|
256
|
+
case 'search_files':
|
|
257
|
+
return this.searchFiles(action.params.query, action.params.filePattern);
|
|
258
|
+
|
|
259
|
+
case 'insert_code':
|
|
260
|
+
return this.insertCode(action.params.code, action.params.position);
|
|
261
|
+
|
|
262
|
+
case 'open_file':
|
|
263
|
+
return this.openFile(action.params.path, action.params.line);
|
|
264
|
+
|
|
265
|
+
case 'run_test':
|
|
266
|
+
return this.runTests(action.params.file);
|
|
267
|
+
|
|
268
|
+
case 'install_package':
|
|
269
|
+
return this.installPackage(action.params.packages, action.params.dev);
|
|
270
|
+
|
|
271
|
+
case 'git_status':
|
|
272
|
+
return this.getGitStatus();
|
|
273
|
+
|
|
274
|
+
default:
|
|
275
|
+
return `Unknown action: ${action.type}`;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
private async readFile(filePath: string): Promise<string> {
|
|
280
|
+
const workspacePath = vscode.workspace.workspaceFolders?.[0]?.uri.fsPath || '';
|
|
281
|
+
const absolutePath = path.isAbsolute(filePath) ? filePath : path.join(workspacePath, filePath);
|
|
282
|
+
|
|
283
|
+
try {
|
|
284
|
+
const content = await fs.readFile(absolutePath, 'utf-8');
|
|
285
|
+
const lines = content.split('\n');
|
|
286
|
+
if (lines.length > 100) {
|
|
287
|
+
return `File: ${filePath} (${lines.length} lines, showing first 100)\n\n${lines.slice(0, 100).join('\n')}\n\n... (truncated)`;
|
|
288
|
+
}
|
|
289
|
+
return `File: ${filePath}\n\n${content}`;
|
|
290
|
+
} catch (error) {
|
|
291
|
+
return `Error reading file: ${error}`;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
private async writeFile(filePath: string, content: string): Promise<string> {
|
|
296
|
+
const workspacePath = vscode.workspace.workspaceFolders?.[0]?.uri.fsPath || '';
|
|
297
|
+
const absolutePath = path.isAbsolute(filePath) ? filePath : path.join(workspacePath, filePath);
|
|
298
|
+
|
|
299
|
+
try {
|
|
300
|
+
await fs.mkdir(path.dirname(absolutePath), { recursive: true });
|
|
301
|
+
await fs.writeFile(absolutePath, content, 'utf-8');
|
|
302
|
+
|
|
303
|
+
// Open the file
|
|
304
|
+
const doc = await vscode.workspace.openTextDocument(absolutePath);
|
|
305
|
+
await vscode.window.showTextDocument(doc);
|
|
306
|
+
|
|
307
|
+
return `File written: ${filePath}`;
|
|
308
|
+
} catch (error) {
|
|
309
|
+
return `Error writing file: ${error}`;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
private async executeCommand(command: string): Promise<string> {
|
|
314
|
+
const workspacePath = vscode.workspace.workspaceFolders?.[0]?.uri.fsPath || '';
|
|
315
|
+
|
|
316
|
+
try {
|
|
317
|
+
const { exec } = await import('child_process');
|
|
318
|
+
const { promisify } = await import('util');
|
|
319
|
+
const execAsync = promisify(exec);
|
|
320
|
+
|
|
321
|
+
const { stdout, stderr } = await execAsync(command, {
|
|
322
|
+
cwd: workspacePath,
|
|
323
|
+
timeout: 30000,
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
return `Command: ${command}\n\nOutput:\n${stdout}${stderr ? `\nErrors:\n${stderr}` : ''}`;
|
|
327
|
+
} catch (error: any) {
|
|
328
|
+
return `Command failed: ${command}\n\nError: ${error.message}\n${error.stderr || ''}`;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
private async searchFiles(query: string, filePattern?: string): Promise<string> {
|
|
333
|
+
const files = await vscode.workspace.findFiles(
|
|
334
|
+
filePattern || '**/*',
|
|
335
|
+
'**/node_modules/**'
|
|
336
|
+
);
|
|
337
|
+
|
|
338
|
+
const matches: string[] = [];
|
|
339
|
+
const regex = new RegExp(query, 'gi');
|
|
340
|
+
|
|
341
|
+
for (const file of files.slice(0, 50)) {
|
|
342
|
+
try {
|
|
343
|
+
const doc = await vscode.workspace.openTextDocument(file);
|
|
344
|
+
const content = doc.getText();
|
|
345
|
+
const lines = content.split('\n');
|
|
346
|
+
|
|
347
|
+
lines.forEach((line, index) => {
|
|
348
|
+
if (regex.test(line)) {
|
|
349
|
+
const relativePath = vscode.workspace.asRelativePath(file);
|
|
350
|
+
matches.push(`${relativePath}:${index + 1}: ${line.trim()}`);
|
|
351
|
+
}
|
|
352
|
+
});
|
|
353
|
+
} catch {
|
|
354
|
+
// Skip files that can't be read
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
if (matches.length >= 20) break;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
return matches.length > 0
|
|
361
|
+
? `Found ${matches.length} matches:\n\n${matches.join('\n')}`
|
|
362
|
+
: 'No matches found';
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
private async insertCode(code: string, position?: 'cursor' | 'end' | 'start'): Promise<string> {
|
|
366
|
+
const editor = vscode.window.activeTextEditor;
|
|
367
|
+
if (!editor) {
|
|
368
|
+
return 'No active editor';
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
await editor.edit((editBuilder) => {
|
|
372
|
+
if (position === 'start') {
|
|
373
|
+
editBuilder.insert(new vscode.Position(0, 0), code + '\n');
|
|
374
|
+
} else if (position === 'end') {
|
|
375
|
+
const lastLine = editor.document.lineCount - 1;
|
|
376
|
+
const lastChar = editor.document.lineAt(lastLine).text.length;
|
|
377
|
+
editBuilder.insert(new vscode.Position(lastLine, lastChar), '\n' + code);
|
|
378
|
+
} else {
|
|
379
|
+
// Default: at cursor or replace selection
|
|
380
|
+
if (editor.selection.isEmpty) {
|
|
381
|
+
editBuilder.insert(editor.selection.active, code);
|
|
382
|
+
} else {
|
|
383
|
+
editBuilder.replace(editor.selection, code);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
return 'Code inserted successfully';
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
private async openFile(filePath: string, line?: number): Promise<string> {
|
|
392
|
+
const workspacePath = vscode.workspace.workspaceFolders?.[0]?.uri.fsPath || '';
|
|
393
|
+
const absolutePath = path.isAbsolute(filePath) ? filePath : path.join(workspacePath, filePath);
|
|
394
|
+
|
|
395
|
+
try {
|
|
396
|
+
const doc = await vscode.workspace.openTextDocument(absolutePath);
|
|
397
|
+
const editor = await vscode.window.showTextDocument(doc);
|
|
398
|
+
|
|
399
|
+
if (line !== undefined) {
|
|
400
|
+
const position = new vscode.Position(line - 1, 0);
|
|
401
|
+
editor.selection = new vscode.Selection(position, position);
|
|
402
|
+
editor.revealRange(new vscode.Range(position, position), vscode.TextEditorRevealType.InCenter);
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
return `Opened: ${filePath}`;
|
|
406
|
+
} catch (error) {
|
|
407
|
+
return `Error opening file: ${error}`;
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
private async runTests(file?: string): Promise<string> {
|
|
412
|
+
const command = file ? `npm test -- ${file}` : 'npm test';
|
|
413
|
+
return this.executeCommand(command);
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
private async installPackage(packages: string[], dev: boolean = false): Promise<string> {
|
|
417
|
+
const flag = dev ? '--save-dev' : '';
|
|
418
|
+
const command = `npm install ${flag} ${packages.join(' ')}`;
|
|
419
|
+
return this.executeCommand(command);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
private async getGitStatus(): Promise<string> {
|
|
423
|
+
return this.executeCommand('git status');
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
// ============================================================================
|
|
427
|
+
// INTELLIGENT SUGGESTIONS
|
|
428
|
+
// ============================================================================
|
|
429
|
+
|
|
430
|
+
/**
|
|
431
|
+
* Get smart suggestions based on current context
|
|
432
|
+
*/
|
|
433
|
+
async getSuggestions(): Promise<string[]> {
|
|
434
|
+
const suggestions: string[] = [];
|
|
435
|
+
const context = await this.getWorkspaceContext();
|
|
436
|
+
|
|
437
|
+
// Suggest based on current file
|
|
438
|
+
if (context.currentFile) {
|
|
439
|
+
const ext = path.extname(context.currentFile.path);
|
|
440
|
+
|
|
441
|
+
if (['.ts', '.tsx', '.js', '.jsx'].includes(ext)) {
|
|
442
|
+
suggestions.push('Explain this code');
|
|
443
|
+
suggestions.push('Add TypeScript types');
|
|
444
|
+
suggestions.push('Refactor for better readability');
|
|
445
|
+
suggestions.push('Add error handling');
|
|
446
|
+
suggestions.push('Write unit tests for this');
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
if (context.currentFile.selection) {
|
|
450
|
+
suggestions.push('Explain selected code');
|
|
451
|
+
suggestions.push('Refactor selected code');
|
|
452
|
+
suggestions.push('Generate documentation');
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
// Suggest based on git status
|
|
457
|
+
if (context.gitStatus) {
|
|
458
|
+
suggestions.push('Generate commit message');
|
|
459
|
+
suggestions.push('Review my changes');
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
// Suggest based on tech stack
|
|
463
|
+
if (context.techStack.includes('React')) {
|
|
464
|
+
suggestions.push('Create a new React component');
|
|
465
|
+
suggestions.push('Add React hooks');
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
if (context.techStack.includes('TypeScript')) {
|
|
469
|
+
suggestions.push('Fix TypeScript errors');
|
|
470
|
+
suggestions.push('Improve type definitions');
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
return suggestions.slice(0, 5);
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
// ============================================================================
|
|
477
|
+
// ENHANCED CHAT PROCESSING
|
|
478
|
+
// ============================================================================
|
|
479
|
+
|
|
480
|
+
/**
|
|
481
|
+
* Process a chat message with full agent capabilities
|
|
482
|
+
*/
|
|
483
|
+
async processAgentMessage(
|
|
484
|
+
message: string,
|
|
485
|
+
history: Array<{ role: string; content: string }>
|
|
486
|
+
): Promise<AgentResponse> {
|
|
487
|
+
// Get workspace context if enabled
|
|
488
|
+
let contextString = '';
|
|
489
|
+
if (this.config.autoInjectContext) {
|
|
490
|
+
const context = await this.getWorkspaceContext();
|
|
491
|
+
contextString = this.formatContextForPrompt(context);
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
// Build enhanced message with context
|
|
495
|
+
const enhancedMessage = contextString
|
|
496
|
+
? `${contextString}\n\n---\n\n**User Request:**\n${message}`
|
|
497
|
+
: message;
|
|
498
|
+
|
|
499
|
+
try {
|
|
500
|
+
// Call the API with enhanced message
|
|
501
|
+
const response = await this.api.chat({
|
|
502
|
+
message: enhancedMessage,
|
|
503
|
+
history,
|
|
504
|
+
context: contextString,
|
|
505
|
+
});
|
|
506
|
+
|
|
507
|
+
// Parse response for actions and code blocks
|
|
508
|
+
const codeBlocks = this.extractCodeBlocks(response.reply);
|
|
509
|
+
const actions = this.detectActions(response.reply, codeBlocks);
|
|
510
|
+
const suggestions = await this.getSuggestions();
|
|
511
|
+
|
|
512
|
+
return {
|
|
513
|
+
message: response.reply,
|
|
514
|
+
actions,
|
|
515
|
+
suggestions: this.config.suggestActions ? suggestions : undefined,
|
|
516
|
+
codeBlocks,
|
|
517
|
+
workspaceAware: !!contextString,
|
|
518
|
+
};
|
|
519
|
+
} catch (error) {
|
|
520
|
+
return {
|
|
521
|
+
message: `Error: ${error}`,
|
|
522
|
+
workspaceAware: false,
|
|
523
|
+
};
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
/**
|
|
528
|
+
* Extract code blocks from response
|
|
529
|
+
*/
|
|
530
|
+
private extractCodeBlocks(text: string): CodeBlock[] {
|
|
531
|
+
const blocks: CodeBlock[] = [];
|
|
532
|
+
const regex = /```(\w*)\n([\s\S]*?)```/g;
|
|
533
|
+
let match;
|
|
534
|
+
|
|
535
|
+
while ((match = regex.exec(text)) !== null) {
|
|
536
|
+
blocks.push({
|
|
537
|
+
language: match[1] || 'text',
|
|
538
|
+
code: match[2].trim(),
|
|
539
|
+
});
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
return blocks;
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
/**
|
|
546
|
+
* Detect suggested actions from response
|
|
547
|
+
*/
|
|
548
|
+
private detectActions(text: string, codeBlocks: CodeBlock[]): AgentAction[] {
|
|
549
|
+
const actions: AgentAction[] = [];
|
|
550
|
+
|
|
551
|
+
// If there are code blocks, suggest insert action
|
|
552
|
+
if (codeBlocks.length > 0) {
|
|
553
|
+
actions.push({
|
|
554
|
+
type: 'insert_code',
|
|
555
|
+
params: { code: codeBlocks[0].code },
|
|
556
|
+
description: 'Insert code at cursor',
|
|
557
|
+
});
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
// Detect file references
|
|
561
|
+
const fileMatch = text.match(/(?:file|create|write)\s+[`"]?([^\s`"]+\.[a-z]+)[`"]?/i);
|
|
562
|
+
if (fileMatch) {
|
|
563
|
+
actions.push({
|
|
564
|
+
type: 'open_file',
|
|
565
|
+
params: { path: fileMatch[1] },
|
|
566
|
+
description: `Open ${fileMatch[1]}`,
|
|
567
|
+
});
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
// Detect command suggestions
|
|
571
|
+
const commandMatch = text.match(/(?:run|execute)\s+[`"]([^`"]+)[`"]/i);
|
|
572
|
+
if (commandMatch) {
|
|
573
|
+
actions.push({
|
|
574
|
+
type: 'execute_command',
|
|
575
|
+
params: { command: commandMatch[1] },
|
|
576
|
+
description: `Run: ${commandMatch[1]}`,
|
|
577
|
+
});
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
// Detect npm install suggestions
|
|
581
|
+
const npmMatch = text.match(/npm install\s+([^\s`]+)/);
|
|
582
|
+
if (npmMatch) {
|
|
583
|
+
actions.push({
|
|
584
|
+
type: 'install_package',
|
|
585
|
+
params: { packages: [npmMatch[1]] },
|
|
586
|
+
description: `Install ${npmMatch[1]}`,
|
|
587
|
+
});
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
return actions;
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
// ============================================================================
|
|
594
|
+
// PROACTIVE FEATURES
|
|
595
|
+
// ============================================================================
|
|
596
|
+
|
|
597
|
+
/**
|
|
598
|
+
* Get proactive refinement for selected text
|
|
599
|
+
*/
|
|
600
|
+
async getProactiveRefinement(): Promise<string | null> {
|
|
601
|
+
if (!this.config.proactiveRefine) return null;
|
|
602
|
+
|
|
603
|
+
const editor = vscode.window.activeTextEditor;
|
|
604
|
+
if (!editor || editor.selection.isEmpty) return null;
|
|
605
|
+
|
|
606
|
+
const selectedText = editor.document.getText(editor.selection);
|
|
607
|
+
|
|
608
|
+
// Check if it looks like a prompt (has some length and structure)
|
|
609
|
+
if (selectedText.length < 10 || selectedText.length > 2000) return null;
|
|
610
|
+
|
|
611
|
+
try {
|
|
612
|
+
const result = await this.api.analyzePrompt({ prompt: selectedText });
|
|
613
|
+
|
|
614
|
+
// Only suggest refinement if score is below threshold
|
|
615
|
+
if (result.overallScore < 70 && result.suggestions.length > 0) {
|
|
616
|
+
return result.suggestions[0];
|
|
617
|
+
}
|
|
618
|
+
} catch {
|
|
619
|
+
// Ignore analysis errors
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
return null;
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
dispose() {
|
|
626
|
+
// Cleanup if needed
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
export default AgentService;
|