Starting to modularize into separate cli / server packages. (#55)

* Starting to move a lot of code into packages/server

* More of the massive refactor, builds and runs, some issues though.

* Fixing outstanding issue with double messages.

* Fixing a minor UI issue.

* Fixing the build post-merge.

* Running formatting.

* Addressing comments.
This commit is contained in:
Evan Senter
2025-04-19 19:45:42 +01:00
committed by GitHub
parent 0c9e1ef61b
commit 3fce6cea27
46 changed files with 3946 additions and 3403 deletions

View File

@@ -0,0 +1,167 @@
/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import fs from 'fs';
import path from 'path';
import * as Diff from 'diff'; // Keep for result generation
import { BaseTool, ToolResult, FileDiff } from './tools.js'; // Updated import (Removed ToolResultDisplay)
import { SchemaValidator } from '../utils/schemaValidator.js'; // Updated import
import { makeRelative, shortenPath } from '../utils/paths.js'; // Updated import
import { isNodeError } from '../utils/errors.js'; // Import isNodeError
/**
* Parameters for the WriteFile tool
*/
export interface WriteFileToolParams {
/**
* The absolute path to the file to write to
*/
file_path: string;
/**
* The content to write to the file
*/
content: string;
}
/**
* Implementation of the WriteFile tool logic (moved from CLI)
*/
export class WriteFileLogic extends BaseTool<WriteFileToolParams, ToolResult> {
static readonly Name: string = 'write_file';
private readonly rootDirectory: string;
constructor(rootDirectory: string) {
super(
WriteFileLogic.Name,
'', // Display name handled by CLI wrapper
'', // Description handled by CLI wrapper
{
properties: {
file_path: {
// Renamed from filePath in original schema
description:
"The absolute path to the file to write to (e.g., '/home/user/project/file.txt'). Relative paths are not supported.",
type: 'string',
},
content: {
description: 'The content to write to the file.',
type: 'string',
},
},
required: ['file_path', 'content'], // Use correct param names
type: 'object',
},
);
this.rootDirectory = path.resolve(rootDirectory);
}
private isWithinRoot(pathToCheck: string): boolean {
const normalizedPath = path.normalize(pathToCheck);
const normalizedRoot = path.normalize(this.rootDirectory);
const rootWithSep = normalizedRoot.endsWith(path.sep)
? normalizedRoot
: normalizedRoot + path.sep;
return (
normalizedPath === normalizedRoot ||
normalizedPath.startsWith(rootWithSep)
);
}
validateParams(params: WriteFileToolParams): string | null {
if (
this.schema.parameters &&
!SchemaValidator.validate(
this.schema.parameters as Record<string, unknown>,
params,
)
) {
return 'Parameters failed schema validation.';
}
if (!path.isAbsolute(params.file_path)) {
return `File path must be absolute: ${params.file_path}`;
}
if (!this.isWithinRoot(params.file_path)) {
return `File path must be within the root directory (${this.rootDirectory}): ${params.file_path}`;
}
return null;
}
// Removed shouldConfirmExecute - handled by CLI
getDescription(params: WriteFileToolParams): string {
const relativePath = makeRelative(params.file_path, this.rootDirectory);
return `Writing to ${shortenPath(relativePath)}`;
}
async execute(params: WriteFileToolParams): Promise<ToolResult> {
const validationError = this.validateParams(params);
if (validationError) {
return {
llmContent: `Error: Invalid parameters provided. Reason: ${validationError}`,
returnDisplay: `Error: ${validationError}`,
};
}
let currentContent = '';
let isNewFile = false;
try {
currentContent = fs.readFileSync(params.file_path, 'utf8');
} catch (err: unknown) {
if (isNodeError(err) && err.code === 'ENOENT') {
isNewFile = true;
} else {
// Rethrow other read errors (permissions etc.)
const errorMsg = `Error checking existing file: ${err instanceof Error ? err.message : String(err)}`;
return {
llmContent: `Error checking existing file ${params.file_path}: ${errorMsg}`,
returnDisplay: `Error: ${errorMsg}`,
};
}
}
try {
const dirName = path.dirname(params.file_path);
if (!fs.existsSync(dirName)) {
fs.mkdirSync(dirName, { recursive: true });
}
fs.writeFileSync(params.file_path, params.content, 'utf8');
// Generate diff for display result
const fileName = path.basename(params.file_path);
const fileDiff = Diff.createPatch(
fileName,
currentContent, // Empty if it was a new file
params.content,
'Original',
'Written',
{ context: 3 },
);
const llmSuccessMessage = isNewFile
? `Successfully created and wrote to new file: ${params.file_path}`
: `Successfully overwrote file: ${params.file_path}`;
// The returnDisplay contains the diff
const displayResult: FileDiff = { fileDiff };
return {
llmContent: llmSuccessMessage,
returnDisplay: displayResult,
};
} catch (error) {
const errorMsg = `Error writing to file: ${error instanceof Error ? error.message : String(error)}`;
return {
llmContent: `Error writing to file ${params.file_path}: ${errorMsg}`,
returnDisplay: `Error: ${errorMsg}`,
};
}
}
// ensureParentDirectoriesExist logic moved into execute
}