11import z from "zod"
22import * as path from "path"
3+ import { Effect } from "effect"
34import { Tool } from "./tool"
45import { LSP } from "../lsp"
56import { createTwoFilesPatch } from "diff"
@@ -9,76 +10,90 @@ import { File } from "../file"
910import { FileWatcher } from "../file/watcher"
1011import { Format } from "../format"
1112import { FileTime } from "../file/time"
12- import { Filesystem } from "../util /filesystem"
13+ import { AppFileSystem } from "../filesystem"
1314import { Instance } from "../project/instance"
1415import { trimDiff } from "./edit"
15- import { assertExternalDirectory } from "./external-directory"
16+ import { assertExternalDirectoryEffect } from "./external-directory"
1617
1718const MAX_DIAGNOSTICS_PER_FILE = 20
1819const MAX_PROJECT_DIAGNOSTICS_FILES = 5
1920
20- export const WriteTool = Tool . define ( "write" , {
21- description : DESCRIPTION ,
22- parameters : z . object ( {
23- content : z . string ( ) . describe ( "The content to write to the file" ) ,
24- filePath : z . string ( ) . describe ( "The absolute path to the file to write (must be absolute, not relative)" ) ,
25- } ) ,
26- async execute ( params , ctx ) {
27- const filepath = path . isAbsolute ( params . filePath ) ? params . filePath : path . join ( Instance . directory , params . filePath )
28- await assertExternalDirectory ( ctx , filepath )
21+ export const WriteTool = Tool . defineEffect (
22+ "write" ,
23+ Effect . gen ( function * ( ) {
24+ const lsp = yield * LSP . Service
25+ const fs = yield * AppFileSystem . Service
26+ const filetime = yield * FileTime . Service
2927
30- const exists = await Filesystem . exists ( filepath )
31- const contentOld = exists ? await Filesystem . readText ( filepath ) : ""
32- if ( exists ) await FileTime . assert ( ctx . sessionID , filepath )
28+ return {
29+ description : DESCRIPTION ,
30+ parameters : z . object ( {
31+ content : z . string ( ) . describe ( "The content to write to the file" ) ,
32+ filePath : z . string ( ) . describe ( "The absolute path to the file to write (must be absolute, not relative)" ) ,
33+ } ) ,
34+ execute : ( params : { content : string ; filePath : string } , ctx : Tool . Context ) =>
35+ Effect . gen ( function * ( ) {
36+ const filepath = path . isAbsolute ( params . filePath ) ? params . filePath : path . join ( Instance . directory , params . filePath )
37+ yield * assertExternalDirectoryEffect ( ctx , filepath )
3338
34- const diff = trimDiff ( createTwoFilesPatch ( filepath , filepath , contentOld , params . content ) )
35- await ctx . ask ( {
36- permission : "edit" ,
37- patterns : [ path . relative ( Instance . worktree , filepath ) ] ,
38- always : [ "*" ] ,
39- metadata : {
40- filepath,
41- diff,
42- } ,
43- } )
39+ const exists = yield * fs . existsSafe ( filepath )
40+ const contentOld = exists ? yield * fs . readFileString ( filepath ) : ""
41+ if ( exists ) yield * filetime . assert ( ctx . sessionID , filepath )
4442
45- await Filesystem . write ( filepath , params . content )
46- await Format . file ( filepath )
47- Bus . publish ( File . Event . Edited , { file : filepath } )
48- await Bus . publish ( FileWatcher . Event . Updated , {
49- file : filepath ,
50- event : exists ? "change" : "add" ,
51- } )
52- await FileTime . read ( ctx . sessionID , filepath )
43+ const diff = trimDiff ( createTwoFilesPatch ( filepath , filepath , contentOld , params . content ) )
44+ yield * Effect . promise ( ( ) =>
45+ ctx . ask ( {
46+ permission : "edit" ,
47+ patterns : [ path . relative ( Instance . worktree , filepath ) ] ,
48+ always : [ "*" ] ,
49+ metadata : {
50+ filepath,
51+ diff,
52+ } ,
53+ } ) ,
54+ )
5355
54- let output = "Wrote file successfully."
55- await LSP . touchFile ( filepath , true )
56- const diagnostics = await LSP . diagnostics ( )
57- const normalizedFilepath = Filesystem . normalizePath ( filepath )
58- let projectDiagnosticsCount = 0
59- for ( const [ file , issues ] of Object . entries ( diagnostics ) ) {
60- const errors = issues . filter ( ( item ) => item . severity === 1 )
61- if ( errors . length === 0 ) continue
62- const limited = errors . slice ( 0 , MAX_DIAGNOSTICS_PER_FILE )
63- const suffix =
64- errors . length > MAX_DIAGNOSTICS_PER_FILE ? `\n... and ${ errors . length - MAX_DIAGNOSTICS_PER_FILE } more` : ""
65- if ( file === normalizedFilepath ) {
66- 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>`
67- continue
68- }
69- if ( projectDiagnosticsCount >= MAX_PROJECT_DIAGNOSTICS_FILES ) continue
70- projectDiagnosticsCount ++
71- output += `\n\nLSP errors detected in other files:\n<diagnostics file="${ file } ">\n${ limited . map ( LSP . Diagnostic . pretty ) . join ( "\n" ) } ${ suffix } \n</diagnostics>`
72- }
56+ yield * fs . writeWithDirs ( filepath , params . content )
57+ yield * Effect . promise ( ( ) => Format . file ( filepath ) )
58+ Bus . publish ( File . Event . Edited , { file : filepath } )
59+ yield * Effect . promise ( ( ) =>
60+ Bus . publish ( FileWatcher . Event . Updated , {
61+ file : filepath ,
62+ event : exists ? "change" : "add" ,
63+ } ) ,
64+ )
65+ yield * filetime . read ( ctx . sessionID , filepath )
7366
74- return {
75- title : path . relative ( Instance . worktree , filepath ) ,
76- metadata : {
77- diagnostics,
78- filepath,
79- exists : exists ,
80- } ,
81- output,
67+ let output = "Wrote file successfully."
68+ yield * lsp . touchFile ( filepath , true )
69+ const diagnostics = yield * lsp . diagnostics ( )
70+ const normalizedFilepath = AppFileSystem . normalizePath ( filepath )
71+ let projectDiagnosticsCount = 0
72+ for ( const [ file , issues ] of Object . entries ( diagnostics ) ) {
73+ const errors = issues . filter ( ( item ) => item . severity === 1 )
74+ if ( errors . length === 0 ) continue
75+ const limited = errors . slice ( 0 , MAX_DIAGNOSTICS_PER_FILE )
76+ const suffix =
77+ errors . length > MAX_DIAGNOSTICS_PER_FILE ? `\n... and ${ errors . length - MAX_DIAGNOSTICS_PER_FILE } more` : ""
78+ if ( file === normalizedFilepath ) {
79+ 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>`
80+ continue
81+ }
82+ if ( projectDiagnosticsCount >= MAX_PROJECT_DIAGNOSTICS_FILES ) continue
83+ projectDiagnosticsCount ++
84+ output += `\n\nLSP errors detected in other files:\n<diagnostics file="${ file } ">\n${ limited . map ( LSP . Diagnostic . pretty ) . join ( "\n" ) } ${ suffix } \n</diagnostics>`
85+ }
86+
87+ return {
88+ title : path . relative ( Instance . worktree , filepath ) ,
89+ metadata : {
90+ diagnostics,
91+ filepath,
92+ exists : exists ,
93+ } ,
94+ output,
95+ }
96+ } ) . pipe ( Effect . orDie , Effect . runPromise ) ,
8297 }
83- } ,
84- } )
98+ } ) ,
99+ )
0 commit comments