2025-10-26 19:50:41 +00:00
import z from "zod"
2025-06-04 17:33:25 +00:00
import * as path from "path"
import { Tool } from "./tool"
import { LSP } from "../lsp"
2026-01-01 22:54:11 +00:00
import { createTwoFilesPatch } from "diff"
2025-06-04 17:33:25 +00:00
import DESCRIPTION from "./write.txt"
2025-06-27 15:29:20 +00:00
import { Bus } from "../bus"
import { File } from "../file"
2026-01-26 22:46:01 +00:00
import { FileWatcher } from "../file/watcher"
2025-06-27 15:29:20 +00:00
import { FileTime } from "../file/time"
2025-07-31 00:57:52 +00:00
import { Filesystem } from "../util/filesystem"
2025-09-01 21:15:49 +00:00
import { Instance } from "../project/instance"
2026-01-01 22:54:11 +00:00
import { trimDiff } from "./edit"
2026-01-10 23:49:36 +00:00
import { assertExternalDirectory } from "./external-directory"
2025-06-04 17:33:25 +00:00
2025-12-14 01:56:26 +00:00
const MAX_DIAGNOSTICS_PER_FILE = 20
const MAX_PROJECT_DIAGNOSTICS_FILES = 5
2025-07-25 17:29:29 +00:00
export const WriteTool = Tool . define ( "write" , {
2025-06-04 17:33:25 +00:00
description : DESCRIPTION ,
parameters : z.object ( {
content : z.string ( ) . describe ( "The content to write to the file" ) ,
2025-11-08 01:59:02 +00:00
filePath : z.string ( ) . describe ( "The absolute path to the file to write (must be absolute, not relative)" ) ,
2025-06-04 17:33:25 +00:00
} ) ,
async execute ( params , ctx ) {
2025-11-08 01:59:02 +00:00
const filepath = path . isAbsolute ( params . filePath ) ? params.filePath : path.join ( Instance . directory , params . filePath )
2026-01-10 23:49:36 +00:00
await assertExternalDirectory ( ctx , filepath )
2025-06-04 17:33:25 +00:00
2026-02-19 16:32:32 +00:00
const exists = await Filesystem . exists ( filepath )
const contentOld = exists ? await Filesystem . readText ( filepath ) : ""
2025-06-27 15:29:20 +00:00
if ( exists ) await FileTime . assert ( ctx . sessionID , filepath )
2025-06-04 17:33:25 +00:00
2026-01-01 22:54:11 +00:00
const diff = trimDiff ( createTwoFilesPatch ( filepath , filepath , contentOld , params . content ) )
await ctx . ask ( {
permission : "edit" ,
patterns : [ path . relative ( Instance . worktree , filepath ) ] ,
always : [ "*" ] ,
metadata : {
filepath ,
diff ,
} ,
} )
2025-06-04 17:33:25 +00:00
2026-02-19 16:32:32 +00:00
await Filesystem . write ( filepath , params . content )
2025-06-27 15:29:20 +00:00
await Bus . publish ( File . Event . Edited , {
file : filepath ,
} )
2026-01-26 22:46:01 +00:00
await Bus . publish ( FileWatcher . Event . Updated , {
file : filepath ,
event : exists ? "change" : "add" ,
} )
2025-06-27 15:29:20 +00:00
FileTime . read ( ctx . sessionID , filepath )
2025-06-04 17:33:25 +00:00
2026-01-13 02:39:57 +00:00
let output = "Wrote file successfully."
2025-06-06 21:20:01 +00:00
await LSP . touchFile ( filepath , true )
2025-06-04 17:33:25 +00:00
const diagnostics = await LSP . diagnostics ( )
2025-12-16 00:01:03 +00:00
const normalizedFilepath = Filesystem . normalizePath ( filepath )
2025-12-14 01:56:26 +00:00
let projectDiagnosticsCount = 0
2025-06-04 17:33:25 +00:00
for ( const [ file , issues ] of Object . entries ( diagnostics ) ) {
2025-12-26 04:24:48 +00:00
const errors = issues . filter ( ( item ) = > item . severity === 1 )
if ( errors . length === 0 ) continue
const limited = errors . slice ( 0 , MAX_DIAGNOSTICS_PER_FILE )
2025-12-14 01:56:26 +00:00
const suffix =
2025-12-26 04:24:48 +00:00
errors . length > MAX_DIAGNOSTICS_PER_FILE ? ` \ n... and ${ errors . length - MAX_DIAGNOSTICS_PER_FILE } more ` : ""
2025-12-16 00:01:03 +00:00
if ( file === normalizedFilepath ) {
2026-01-14 21:44:44 +00:00
output += ` \ n \ nLSP errors detected in this file, please fix: \ n<diagnostics file=" ${ filepath } "> \ n ${ limited . map ( LSP . Diagnostic . pretty ) . join ( "\n" ) } ${ suffix } \ n</diagnostics> `
2025-06-04 17:33:25 +00:00
continue
}
2025-12-14 01:56:26 +00:00
if ( projectDiagnosticsCount >= MAX_PROJECT_DIAGNOSTICS_FILES ) continue
projectDiagnosticsCount ++
2026-01-13 02:39:57 +00:00
output += ` \ n \ nLSP errors detected in other files: \ n<diagnostics file=" ${ file } "> \ n ${ limited . map ( LSP . Diagnostic . pretty ) . join ( "\n" ) } ${ suffix } \ n</diagnostics> `
2025-06-04 17:33:25 +00:00
}
return {
2025-09-01 21:15:49 +00:00
title : path.relative ( Instance . worktree , filepath ) ,
2025-06-04 17:33:25 +00:00
metadata : {
diagnostics ,
filepath ,
exists : exists ,
} ,
output ,
}
} ,
} )