forked from getagentseal/codeburn
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcodex-cache.ts
More file actions
145 lines (129 loc) · 3.81 KB
/
Copy pathcodex-cache.ts
File metadata and controls
145 lines (129 loc) · 3.81 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
import { readFile, mkdir, stat, open, rename, unlink } from 'fs/promises'
import { existsSync } from 'fs'
import { randomBytes } from 'crypto'
import { join } from 'path'
import { homedir } from 'os'
import type { ParsedProviderCall } from './providers/types.js'
// v4: attribute MCP calls emitted as event_msg/mcp_tool_call_end (issue #478).
// Recent Codex sessions cached under v3 dropped these, so force a re-parse.
const CODEX_CACHE_VERSION = 4
const CACHE_FILE = 'codex-results.json'
type FileFingerprint = { mtimeMs: number; sizeBytes: number }
type FileEntry = {
mtimeMs: number
sizeBytes: number
project: string
calls: ParsedProviderCall[]
}
type ResultCache = {
version: number
files: Record<string, FileEntry>
}
function getCacheDir(): string {
return process.env['CODEBURN_CACHE_DIR'] ?? join(homedir(), '.cache', 'codeburn')
}
function getCachePath(): string {
return join(getCacheDir(), CACHE_FILE)
}
let memCache: ResultCache | null = null
async function loadCache(): Promise<ResultCache> {
if (memCache) return memCache
try {
const raw = await readFile(getCachePath(), 'utf-8')
const cache = JSON.parse(raw) as ResultCache
if (cache.version === CODEX_CACHE_VERSION && cache.files && typeof cache.files === 'object') {
memCache = cache
return cache
}
} catch {}
memCache = { version: CODEX_CACHE_VERSION, files: {} }
return memCache
}
function getEntry(cache: ResultCache, filePath: string, fp: FileFingerprint): FileEntry | null {
if (!Object.hasOwn(cache.files, filePath)) return null
const entry = cache.files[filePath]
if (entry && entry.mtimeMs === fp.mtimeMs && entry.sizeBytes === fp.sizeBytes) {
return entry
}
return null
}
export async function readCachedCodexResults(
filePath: string,
): Promise<ParsedProviderCall[] | null> {
try {
const s = await stat(filePath)
const cache = await loadCache()
const entry = getEntry(cache, filePath, { mtimeMs: s.mtimeMs, sizeBytes: s.size })
return entry?.calls ?? null
} catch {}
return null
}
export async function getCachedCodexProject(
filePath: string,
): Promise<string | null> {
try {
const s = await stat(filePath)
const cache = await loadCache()
const entry = getEntry(cache, filePath, { mtimeMs: s.mtimeMs, sizeBytes: s.size })
return entry?.project ?? null
} catch {}
return null
}
export async function fingerprintFile(
filePath: string,
): Promise<FileFingerprint | null> {
try {
const s = await stat(filePath)
return { mtimeMs: s.mtimeMs, sizeBytes: s.size }
} catch {
return null
}
}
export async function writeCachedCodexResults(
filePath: string,
project: string,
calls: ParsedProviderCall[],
fingerprint: FileFingerprint,
): Promise<void> {
try {
const cache = await loadCache()
cache.files[filePath] = {
mtimeMs: fingerprint.mtimeMs,
sizeBytes: fingerprint.sizeBytes,
project,
calls,
}
} catch {}
}
export async function flushCodexCache(): Promise<void> {
if (!memCache) return
try {
// Evict entries for files that no longer exist on disk
const paths = Object.keys(memCache.files)
for (const p of paths) {
try {
await stat(p)
} catch {
delete memCache.files[p]
}
}
const dir = getCacheDir()
if (!existsSync(dir)) await mkdir(dir, { recursive: true })
const finalPath = getCachePath()
const tempPath = `${finalPath}.${randomBytes(8).toString('hex')}.tmp`
const payload = JSON.stringify(memCache)
const handle = await open(tempPath, 'w', 0o600)
try {
await handle.writeFile(payload, { encoding: 'utf-8' })
await handle.sync()
} finally {
await handle.close()
}
try {
await rename(tempPath, finalPath)
} catch (err) {
try { await unlink(tempPath) } catch {}
throw err
}
} catch {}
}