diff options
| author | Evan Senter <[email protected]> | 2025-04-19 19:45:42 +0100 |
|---|---|---|
| committer | GitHub <[email protected]> | 2025-04-19 19:45:42 +0100 |
| commit | 3fce6cea27d3e6129d6c06e528b62e1b11bf7094 (patch) | |
| tree | 244b8e9ab94f902d65d4bda8739a6538e377ed17 /packages/server/src/tools/write-file.ts | |
| parent | 0c9e1ef61be7db53e6e73b7208b649cd8cbed6c3 (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.ts | 167 |
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 +} |
