Files
oh-my-opencode/src/shared/json-file-cache-store.ts
T
garnetlyx 177c51b640 fix: use in-process flag instead of existsSync for cache availability
In OpenCode plugin sandbox, existsSync fails across different hook
contexts (config vs chat.message) because each runs in an isolated
virtual filesystem. Add writtenInCurrentProcess flags to track cache
writes at the process level, eliminating spurious Model Cache Not
Found toast warnings.

Closes #3412
2026-04-13 23:40:51 -07:00

113 lines
2.9 KiB
TypeScript

import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs"
import { join } from "node:path"
import { log } from "./logger"
type JsonFileCacheStoreOptions<TValue> = {
getCacheDir: () => string
filename: string
logPrefix: string
cacheLabel: string
describe: (value: TValue) => Record<string, unknown>
serialize?: (value: TValue) => string
}
type JsonFileCacheStore<TValue> = {
read: () => TValue | null
has: () => boolean
write: (value: TValue) => void
resetMemory: () => void
}
function toLogLabel(cacheLabel: string): string {
return cacheLabel.toLowerCase()
}
export function createJsonFileCacheStore<TValue>(
options: JsonFileCacheStoreOptions<TValue>,
): JsonFileCacheStore<TValue> {
let memoryValue: TValue | null | undefined
let writtenInCurrentProcess = false
function getCacheFilePath(): string {
return join(options.getCacheDir(), options.filename)
}
function ensureCacheDir(): void {
const cacheDir = options.getCacheDir()
if (!existsSync(cacheDir)) {
mkdirSync(cacheDir, { recursive: true })
}
}
function read(): TValue | null {
if (memoryValue !== undefined) {
return memoryValue
}
const cacheFile = getCacheFilePath()
if (!existsSync(cacheFile)) {
memoryValue = null
log(`[${options.logPrefix}] ${options.cacheLabel} file not found`, { cacheFile })
return null
}
try {
const content = readFileSync(cacheFile, "utf-8")
const value = JSON.parse(content) as TValue
memoryValue = value
log(`[${options.logPrefix}] Read ${toLogLabel(options.cacheLabel)}`, options.describe(value))
return value
} catch (error) {
memoryValue = null
log(`[${options.logPrefix}] Error reading ${toLogLabel(options.cacheLabel)}`, {
error: String(error),
})
return null
}
}
function has(): boolean {
// First check if we have a valid in-memory cache value
// This handles sandbox environments where existsSync may fail across contexts
if (memoryValue !== undefined && memoryValue !== null) {
return true
}
// Check if we've written to this cache in the current process
// This helps in sandbox environments where filesystem state may not persist across contexts
if (writtenInCurrentProcess) {
return true
}
// Fall back to filesystem check
return existsSync(getCacheFilePath())
}
function write(value: TValue): void {
ensureCacheDir()
const cacheFile = getCacheFilePath()
try {
writeFileSync(cacheFile, options.serialize?.(value) ?? JSON.stringify(value, null, 2))
memoryValue = value
writtenInCurrentProcess = true
log(`[${options.logPrefix}] ${options.cacheLabel} written`, options.describe(value))
} catch (error) {
log(`[${options.logPrefix}] Error writing ${toLogLabel(options.cacheLabel)}`, {
error: String(error),
})
}
}
function resetMemory(): void {
memoryValue = undefined
writtenInCurrentProcess = false
}
return {
read,
has,
write,
resetMemory,
}
}