@@ -5,109 +5,109 @@ import { Flag } from "@/flag/flag"
55import type { SessionID } from "@/session/schema"
66import { Log } from "../util"
77
8- export namespace FileTime {
9- const log = Log . create ( { service : "file.time" } )
10-
11- export type Stamp = {
12- readonly read : Date
13- readonly mtime : number | undefined
14- readonly size : number | undefined
15- }
16-
17- const session = ( reads : Map < SessionID , Map < string , Stamp > > , sessionID : SessionID ) => {
18- const value = reads . get ( sessionID )
19- if ( value ) return value
20-
21- const next = new Map < string , Stamp > ( )
22- reads . set ( sessionID , next )
23- return next
24- }
25-
26- interface State {
27- reads : Map < SessionID , Map < string , Stamp > >
28- locks : Map < string , Semaphore . Semaphore >
29- }
30-
31- export interface Interface {
32- readonly read : ( sessionID : SessionID , file : string ) => Effect . Effect < void >
33- readonly get : ( sessionID : SessionID , file : string ) => Effect . Effect < Date | undefined >
34- readonly assert : ( sessionID : SessionID , filepath : string ) => Effect . Effect < void >
35- readonly withLock : < T > ( filepath : string , fn : ( ) => Effect . Effect < T > ) => Effect . Effect < T >
36- }
37-
38- export class Service extends Context . Service < Service , Interface > ( ) ( "@opencode/FileTime" ) { }
39-
40- export const layer = Layer . effect (
41- Service ,
42- Effect . gen ( function * ( ) {
43- const fsys = yield * AppFileSystem . Service
44- const disableCheck = yield * Flag . OPENCODE_DISABLE_FILETIME_CHECK
45-
46- const stamp = Effect . fnUntraced ( function * ( file : string ) {
47- const info = yield * fsys . stat ( file ) . pipe ( Effect . catch ( ( ) => Effect . void ) )
48- return {
49- read : yield * DateTime . nowAsDate ,
50- mtime : info ? Option . getOrUndefined ( info . mtime ) ?. getTime ( ) : undefined ,
51- size : info ? Number ( info . size ) : undefined ,
52- }
53- } )
54- const state = yield * InstanceState . make < State > (
55- Effect . fn ( "FileTime.state" ) ( ( ) =>
56- Effect . succeed ( {
57- reads : new Map < SessionID , Map < string , Stamp > > ( ) ,
58- locks : new Map < string , Semaphore . Semaphore > ( ) ,
59- } ) ,
60- ) ,
61- )
8+ const log = Log . create ( { service : "file.time" } )
9+
10+ export type Stamp = {
11+ readonly read : Date
12+ readonly mtime : number | undefined
13+ readonly size : number | undefined
14+ }
15+
16+ const session = ( reads : Map < SessionID , Map < string , Stamp > > , sessionID : SessionID ) => {
17+ const value = reads . get ( sessionID )
18+ if ( value ) return value
19+
20+ const next = new Map < string , Stamp > ( )
21+ reads . set ( sessionID , next )
22+ return next
23+ }
24+
25+ interface State {
26+ reads : Map < SessionID , Map < string , Stamp > >
27+ locks : Map < string , Semaphore . Semaphore >
28+ }
6229
63- const getLock = Effect . fn ( "FileTime.lock" ) ( function * ( filepath : string ) {
64- filepath = AppFileSystem . normalizePath ( filepath )
65- const locks = ( yield * InstanceState . get ( state ) ) . locks
66- const lock = locks . get ( filepath )
67- if ( lock ) return lock
68-
69- const next = Semaphore . makeUnsafe ( 1 )
70- locks . set ( filepath , next )
71- return next
72- } )
73-
74- const read = Effect . fn ( "FileTime.read" ) ( function * ( sessionID : SessionID , file : string ) {
75- file = AppFileSystem . normalizePath ( file )
76- const reads = ( yield * InstanceState . get ( state ) ) . reads
77- log . info ( "read" , { sessionID, file } )
78- session ( reads , sessionID ) . set ( file , yield * stamp ( file ) )
79- } )
80-
81- const get = Effect . fn ( "FileTime.get" ) ( function * ( sessionID : SessionID , file : string ) {
82- file = AppFileSystem . normalizePath ( file )
83- const reads = ( yield * InstanceState . get ( state ) ) . reads
84- return reads . get ( sessionID ) ?. get ( file ) ?. read
85- } )
86-
87- const assert = Effect . fn ( "FileTime.assert" ) ( function * ( sessionID : SessionID , filepath : string ) {
88- if ( disableCheck ) return
89- filepath = AppFileSystem . normalizePath ( filepath )
90-
91- const reads = ( yield * InstanceState . get ( state ) ) . reads
92- const time = reads . get ( sessionID ) ?. get ( filepath )
93- if ( ! time ) throw new Error ( `You must read file ${ filepath } before overwriting it. Use the Read tool first` )
94-
95- const next = yield * stamp ( filepath )
96- const changed = next . mtime !== time . mtime || next . size !== time . size
97- if ( ! changed ) return
98-
99- throw new Error (
100- `File ${ filepath } has been modified since it was last read.\nLast modification: ${ new Date ( next . mtime ?? next . read . getTime ( ) ) . toISOString ( ) } \nLast read: ${ time . read . toISOString ( ) } \n\nPlease read the file again before modifying it.` ,
101- )
102- } )
103-
104- const withLock = Effect . fn ( "FileTime.withLock" ) ( function * < T > ( filepath : string , fn : ( ) => Effect . Effect < T > ) {
105- return yield * fn ( ) . pipe ( ( yield * getLock ( filepath ) ) . withPermits ( 1 ) )
106- } )
107-
108- return Service . of ( { read, get, assert, withLock } )
109- } ) ,
110- ) . pipe ( Layer . orDie )
111-
112- export const defaultLayer = layer . pipe ( Layer . provide ( AppFileSystem . defaultLayer ) )
30+ export interface Interface {
31+ readonly read : ( sessionID : SessionID , file : string ) => Effect . Effect < void >
32+ readonly get : ( sessionID : SessionID , file : string ) => Effect . Effect < Date | undefined >
33+ readonly assert : ( sessionID : SessionID , filepath : string ) => Effect . Effect < void >
34+ readonly withLock : < T > ( filepath : string , fn : ( ) => Effect . Effect < T > ) => Effect . Effect < T >
11335}
36+
37+ export class Service extends Context . Service < Service , Interface > ( ) ( "@opencode/FileTime" ) { }
38+
39+ export const layer = Layer . effect (
40+ Service ,
41+ Effect . gen ( function * ( ) {
42+ const fsys = yield * AppFileSystem . Service
43+ const disableCheck = yield * Flag . OPENCODE_DISABLE_FILETIME_CHECK
44+
45+ const stamp = Effect . fnUntraced ( function * ( file : string ) {
46+ const info = yield * fsys . stat ( file ) . pipe ( Effect . catch ( ( ) => Effect . void ) )
47+ return {
48+ read : yield * DateTime . nowAsDate ,
49+ mtime : info ? Option . getOrUndefined ( info . mtime ) ?. getTime ( ) : undefined ,
50+ size : info ? Number ( info . size ) : undefined ,
51+ }
52+ } )
53+ const state = yield * InstanceState . make < State > (
54+ Effect . fn ( "FileTime.state" ) ( ( ) =>
55+ Effect . succeed ( {
56+ reads : new Map < SessionID , Map < string , Stamp > > ( ) ,
57+ locks : new Map < string , Semaphore . Semaphore > ( ) ,
58+ } ) ,
59+ ) ,
60+ )
61+
62+ const getLock = Effect . fn ( "FileTime.lock" ) ( function * ( filepath : string ) {
63+ filepath = AppFileSystem . normalizePath ( filepath )
64+ const locks = ( yield * InstanceState . get ( state ) ) . locks
65+ const lock = locks . get ( filepath )
66+ if ( lock ) return lock
67+
68+ const next = Semaphore . makeUnsafe ( 1 )
69+ locks . set ( filepath , next )
70+ return next
71+ } )
72+
73+ const read = Effect . fn ( "FileTime.read" ) ( function * ( sessionID : SessionID , file : string ) {
74+ file = AppFileSystem . normalizePath ( file )
75+ const reads = ( yield * InstanceState . get ( state ) ) . reads
76+ log . info ( "read" , { sessionID, file } )
77+ session ( reads , sessionID ) . set ( file , yield * stamp ( file ) )
78+ } )
79+
80+ const get = Effect . fn ( "FileTime.get" ) ( function * ( sessionID : SessionID , file : string ) {
81+ file = AppFileSystem . normalizePath ( file )
82+ const reads = ( yield * InstanceState . get ( state ) ) . reads
83+ return reads . get ( sessionID ) ?. get ( file ) ?. read
84+ } )
85+
86+ const assert = Effect . fn ( "FileTime.assert" ) ( function * ( sessionID : SessionID , filepath : string ) {
87+ if ( disableCheck ) return
88+ filepath = AppFileSystem . normalizePath ( filepath )
89+
90+ const reads = ( yield * InstanceState . get ( state ) ) . reads
91+ const time = reads . get ( sessionID ) ?. get ( filepath )
92+ if ( ! time ) throw new Error ( `You must read file ${ filepath } before overwriting it. Use the Read tool first` )
93+
94+ const next = yield * stamp ( filepath )
95+ const changed = next . mtime !== time . mtime || next . size !== time . size
96+ if ( ! changed ) return
97+
98+ throw new Error (
99+ `File ${ filepath } has been modified since it was last read.\nLast modification: ${ new Date ( next . mtime ?? next . read . getTime ( ) ) . toISOString ( ) } \nLast read: ${ time . read . toISOString ( ) } \n\nPlease read the file again before modifying it.` ,
100+ )
101+ } )
102+
103+ const withLock = Effect . fn ( "FileTime.withLock" ) ( function * < T > ( filepath : string , fn : ( ) => Effect . Effect < T > ) {
104+ return yield * fn ( ) . pipe ( ( yield * getLock ( filepath ) ) . withPermits ( 1 ) )
105+ } )
106+
107+ return Service . of ( { read, get, assert, withLock } )
108+ } ) ,
109+ ) . pipe ( Layer . orDie )
110+
111+ export const defaultLayer = layer . pipe ( Layer . provide ( AppFileSystem . defaultLayer ) )
112+
113+ export * as FileTime from "./time"
0 commit comments