summaryrefslogtreecommitdiff
path: root/packages/server/src/tools/write-file.ts
diff options
context:
space:
mode:
authorEvan Senter <[email protected]>2025-04-19 19:45:42 +0100
committerGitHub <[email protected]>2025-04-19 19:45:42 +0100
commit3fce6cea27d3e6129d6c06e528b62e1b11bf7094 (patch)
tree244b8e9ab94f902d65d4bda8739a6538e377ed17 /packages/server/src/tools/write-file.ts
parent0c9e1ef61be7db53e6e73b7208b649cd8cbed6c3 (diff)
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.
Diffstat (limited to 'packages/server/src/tools/write-file.ts')
-rw-r--r--packages/server/src/tools/write-file.ts167
1 files changed, 167 insertions, 0 deletions
diff --git a/packages/server/src/tools/write-file.ts b/packages/server/src/tools/write-file.ts
new file mode 100644
index 00000000..ce723061
--- /dev/null
+++ b/packages/server/src/tools/write-file.ts
@@ -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
+}