Skip to content

Commit

Permalink
update to use json
Browse files Browse the repository at this point in the history
  • Loading branch information
ZHallen122 committed Feb 5, 2025
1 parent 183d960 commit 36f2f2e
Show file tree
Hide file tree
Showing 5 changed files with 203 additions and 229 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,9 @@ import { writeFile, rename } from 'fs/promises';
import path from 'path';

export interface FileOperation {
action: 'read' | 'write' | 'delete' | 'rename';
path: string;
originalPath: string;
content?: string;
purpose?: string;
}

export interface LLMFixResponse {
operations: FileOperation[];
reasoning: string;
action: 'write' | 'rename';
originalPath?: string;
renamePath?: string;
code?: string;
}

Expand All @@ -33,52 +26,55 @@ export class FileOperationManager {
// }

for (const op of operations) {
const resolvedPath = path.resolve(this.projectRoot, op.path);

try {
switch (op.action) {
case 'write':
await this.handleWrite(resolvedPath, op);
await this.handleWrite(op);
break;
case 'rename':
await this.handleRename(resolvedPath, op);
await this.handleRename(op);
break;
}
} catch (error) {
this.logger.error(`Failed to ${op.action} ${resolvedPath}: ${error}`);
this.logger.error(
`Failed to ${op.action} ${op.originalPath}: ${error}`,
);
throw error;
}
}
}

private async handleWrite(
filePath: string,
op: FileOperation,
): Promise<void> {
this.safetyChecks(op);
await writeFile(filePath, op.content, 'utf-8');
private async handleWrite(op: FileOperation): Promise<void> {
const originalPath = path.resolve(this.projectRoot, op.originalPath);
this.safetyChecks(originalPath);

this.logger.debug('start update file to: ' + originalPath);
await writeFile(originalPath, op.code, 'utf-8');
}

private async handleRename(
filePath: string,
op: FileOperation,
): Promise<void> {
this.safetyChecks(op);
private async handleRename(op: FileOperation): Promise<void> {
const originalPath = path.resolve(this.projectRoot, op.originalPath);
const RenamePath = path.resolve(this.projectRoot, op.renamePath);

this.safetyChecks(originalPath);
this.safetyChecks(RenamePath);

this.logger.debug('start rename: ' + originalPath);
this.logger.debug('change to name: ' + RenamePath);
// Perform the actual rename
await rename(op.originalPath, filePath);
await rename(originalPath, RenamePath);
}

private safetyChecks(op: FileOperation) {
const targetPath = path.resolve(this.projectRoot, op.path); // Normalize path
private safetyChecks(filePath: string) {
const targetPath = path.resolve(this.projectRoot, filePath); // Normalize path

// Prevent path traversal attacks
if (!targetPath.startsWith(this.projectRoot)) {
throw new Error('Unauthorized file access detected');
}

// Prevent package.json modifications
if (op.path.includes('package.json')) {
if (targetPath.includes('package.json')) {
throw new Error('Modifying package.json requires special approval');
}

Expand All @@ -87,13 +83,10 @@ export class FileOperationManager {
throw new Error(`Attempted to access restricted path: ${targetPath}`);
}

// Limit delete write operations
if (
(op.action === 'delete' || op.action === 'write') &&
!op.path.startsWith('src/')
) {
throw new Error('Can only delete or write files in src/ directory');
}
// Limit write anddelete write operations
// if (path.startsWith('src/')) {
// throw new Error('Can only delete or write files in src/ directory');
// }
}

private isPathAllowed(targetPath: string): boolean {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,103 +1,45 @@
import { XMLParser } from 'fast-xml-parser';
import { FileOperation, LLMFixResponse } from './FileOperationManager';
import { Logger } from '@nestjs/common';
import path from 'path';
import { FileOperation } from './FileOperationManager';

export class FixResponseParser {
private readonly parser: XMLParser;
private readonly logger = new Logger('FixResponseParser');
private logger = console;

constructor() {
this.parser = new XMLParser({
ignoreAttributes: false,
attributeNamePrefix: '',
textNodeName: 'value',
allowBooleanAttributes: true,
alwaysCreateTextNode: true
});
}
// parse the gpt json input
parse(json: string, filePath: string): FileOperation[] {
this.logger.log('Parsing JSON:', json);

parse(xml: string): LLMFixResponse {
let parsedData;
try {
const parsed = this.parser.parse(xml);
return this.transformResponse(parsed);
parsedData = JSON.parse(json);
} catch (error) {
this.logger.error('XML parsing failed', error.stack);
throw new Error('Invalid XML format from LLM');
}
}

private transformResponse(parsed: any): LLMFixResponse {
const result: LLMFixResponse = {
operations: [],
reasoning: ''
};

// Extract reasoning if available
result.reasoning = parsed.FIX.REASONING?.value || 'No reasoning provided';

// Process operations
const actions = parsed.FIX.OPERATIONS?.ACTION || [];
const actionsArray = Array.isArray(actions) ? actions : [actions];

for (const action of actionsArray) {
const operation = this.parseAction(action);
if (operation) result.operations.push(operation);
this.logger.error('Error parsing JSON:', error);
throw new Error('Invalid JSON format');
}

// Handle generated code for WRITE operations
const generatedCode = parsed.FIX.GENERATE?.value;
if (generatedCode) {
this.applyGeneratedCode(result.operations, generatedCode);
if (!parsedData.fix || !parsedData.fix.operations) {
throw new Error("Invalid JSON structure: Missing 'fix.operations'");
}

return result;
}

private parseAction(action: any): FileOperation | null {
try {
switch (action.type.toUpperCase()) {
case 'WRITE':
const operations: FileOperation[] = parsedData.fix.operations
.map((op: any) => {
if (op.type === 'WRITE') {
return {
action: 'write',
path: this.sanitizePath(action.path),
originalPath: '', // Not used for write
content: '' // Temporarily empty
originalPath: filePath,
code: parsedData.fix.generate?.trim(),
};
case 'RENAME':
} else if (op.type === 'RENAME') {
return {
action: 'rename',
path: this.sanitizePath(action.path),
originalPath: this.sanitizePath(action.ORIGINAL_PATH.value)
originalPath: op.original_path,
renamePath: op.path,
code: parsedData.fix.generate?.trim(),
};
default:
this.logger.warn(`Unknown action type: ${action.type}`);
return null;
}
} catch (error) {
this.logger.warn(`Invalid action format: ${JSON.stringify(action)}`);
return null;
}
}

private applyGeneratedCode(operations: FileOperation[], code: string): void {
const writeOperations = operations.filter(op => op.action === 'write');
if (writeOperations.length > 0) {
// Apply generated code to the first WRITE operation
writeOperations[0].content = code;
}
}

private sanitizePath(rawPath: string): string {
const sanitized = path.normalize(rawPath)
.replace(/(\.\.\/|\.\.\\)/g, '') // Prevent path traversal
.replace(/^\/+/, '') // Remove leading slashes
.trim();

if (!sanitized) {
throw new Error(`Invalid path after sanitization: ${rawPath}`);
}
}
return null;
})
.filter(Boolean);

return sanitized;
this.logger.log('Extracted operations:', operations);
return operations;
}
}
}
Loading

0 comments on commit 36f2f2e

Please sign in to comment.