diff --git a/src/shared/connected-providers-cache.ts b/src/shared/connected-providers-cache.ts index 444c93943..582c26f01 100644 --- a/src/shared/connected-providers-cache.ts +++ b/src/shared/connected-providers-cache.ts @@ -1,7 +1,6 @@ -import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs" -import { join } from "path" import { log } from "./logger" import * as dataPath from "./data-path" +import { createJsonFileCacheStore } from "./json-file-cache-store" const CONNECTED_PROVIDERS_CACHE_FILE = "connected-providers.json" const PROVIDER_MODELS_CACHE_FILE = "provider-models.json" @@ -47,115 +46,52 @@ function isRecord(value: unknown): value is Record { export function createConnectedProvidersCacheStore( getCacheDir: () => string = dataPath.getOmoOpenCodeCacheDir ) { - function getCacheFilePath(filename: string): string { - return join(getCacheDir(), filename) - } - - let memConnected: string[] | null | undefined - let memProviderModels: ProviderModelsCache | null | undefined - - function ensureCacheDir(): void { - const cacheDir = getCacheDir() - if (!existsSync(cacheDir)) { - mkdirSync(cacheDir, { recursive: true }) - } - } + const connectedProvidersCacheStore = createJsonFileCacheStore({ + getCacheDir, + filename: CONNECTED_PROVIDERS_CACHE_FILE, + logPrefix: "connected-providers-cache", + cacheLabel: "Cache", + describe: (value) => ({ count: value.connected.length, updatedAt: value.updatedAt }), + }) + const providerModelsCacheStore = createJsonFileCacheStore({ + getCacheDir, + filename: PROVIDER_MODELS_CACHE_FILE, + logPrefix: "connected-providers-cache", + cacheLabel: "Provider-models cache", + describe: (value) => ({ + providerCount: Object.keys(value.models).length, + updatedAt: value.updatedAt, + }), + }) function readConnectedProvidersCache(): string[] | null { - if (memConnected !== undefined) return memConnected - const cacheFile = getCacheFilePath(CONNECTED_PROVIDERS_CACHE_FILE) - - if (!existsSync(cacheFile)) { - log("[connected-providers-cache] Cache file not found", { cacheFile }) - memConnected = null - return null - } - - try { - const content = readFileSync(cacheFile, "utf-8") - const data = JSON.parse(content) as ConnectedProvidersCache - log("[connected-providers-cache] Read cache", { count: data.connected.length, updatedAt: data.updatedAt }) - memConnected = data.connected - return data.connected - } catch (err) { - log("[connected-providers-cache] Error reading cache", { error: String(err) }) - memConnected = null - return null - } + return connectedProvidersCacheStore.read()?.connected ?? null } function hasConnectedProvidersCache(): boolean { - const cacheFile = getCacheFilePath(CONNECTED_PROVIDERS_CACHE_FILE) - return existsSync(cacheFile) + return connectedProvidersCacheStore.has() } function writeConnectedProvidersCache(connected: string[]): void { - ensureCacheDir() - const cacheFile = getCacheFilePath(CONNECTED_PROVIDERS_CACHE_FILE) - - const data: ConnectedProvidersCache = { + connectedProvidersCacheStore.write({ connected, updatedAt: new Date().toISOString(), - } - - try { - writeFileSync(cacheFile, JSON.stringify(data, null, 2)) - memConnected = connected - log("[connected-providers-cache] Cache written", { count: connected.length }) - } catch (err) { - log("[connected-providers-cache] Error writing cache", { error: String(err) }) - } + }) } function readProviderModelsCache(): ProviderModelsCache | null { - if (memProviderModels !== undefined) return memProviderModels - const cacheFile = getCacheFilePath(PROVIDER_MODELS_CACHE_FILE) - - if (!existsSync(cacheFile)) { - log("[connected-providers-cache] Provider-models cache file not found", { cacheFile }) - memProviderModels = null - return null - } - - try { - const content = readFileSync(cacheFile, "utf-8") - const data = JSON.parse(content) as ProviderModelsCache - log("[connected-providers-cache] Read provider-models cache", { - providerCount: Object.keys(data.models).length, - updatedAt: data.updatedAt, - }) - memProviderModels = data - return data - } catch (err) { - log("[connected-providers-cache] Error reading provider-models cache", { error: String(err) }) - memProviderModels = null - return null - } + return providerModelsCacheStore.read() } function hasProviderModelsCache(): boolean { - const cacheFile = getCacheFilePath(PROVIDER_MODELS_CACHE_FILE) - return existsSync(cacheFile) + return providerModelsCacheStore.has() } function writeProviderModelsCache(data: { models: Record; connected: string[] }): void { - ensureCacheDir() - const cacheFile = getCacheFilePath(PROVIDER_MODELS_CACHE_FILE) - - const cacheData: ProviderModelsCache = { + providerModelsCacheStore.write({ ...data, updatedAt: new Date().toISOString(), - } - - try { - writeFileSync(cacheFile, JSON.stringify(cacheData, null, 2)) - memProviderModels = cacheData - log("[connected-providers-cache] Provider-models cache written", { - providerCount: Object.keys(data.models).length, - }) - } catch (err) { - log("[connected-providers-cache] Error writing provider-models cache", { error: String(err) }) - } + }) } async function updateConnectedProvidersCache(client: { @@ -223,8 +159,8 @@ export function createConnectedProvidersCacheStore( } function _resetMemCacheForTesting(): void { - memConnected = undefined - memProviderModels = undefined + connectedProvidersCacheStore.resetMemory() + providerModelsCacheStore.resetMemory() } return { @@ -256,7 +192,7 @@ export function findProviderModelMetadata( continue } - if (entry?.id === modelID) { + if (entry.id === modelID) { return entry } } diff --git a/src/shared/json-file-cache-store.ts b/src/shared/json-file-cache-store.ts new file mode 100644 index 000000000..5561a66b9 --- /dev/null +++ b/src/shared/json-file-cache-store.ts @@ -0,0 +1,98 @@ +import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs" +import { join } from "node:path" + +import { log } from "./logger" + +type JsonFileCacheStoreOptions = { + getCacheDir: () => string + filename: string + logPrefix: string + cacheLabel: string + describe: (value: TValue) => Record + serialize?: (value: TValue) => string +} + +type JsonFileCacheStore = { + read: () => TValue | null + has: () => boolean + write: (value: TValue) => void + resetMemory: () => void +} + +function toLogLabel(cacheLabel: string): string { + return cacheLabel.toLowerCase() +} + +export function createJsonFileCacheStore( + options: JsonFileCacheStoreOptions, +): JsonFileCacheStore { + let memoryValue: TValue | null | undefined + + 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 { + 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 + 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 + } + + return { + read, + has, + write, + resetMemory, + } +} diff --git a/src/shared/model-capabilities-cache.ts b/src/shared/model-capabilities-cache.ts index bff841c68..37d6b6429 100644 --- a/src/shared/model-capabilities-cache.ts +++ b/src/shared/model-capabilities-cache.ts @@ -1,7 +1,5 @@ -import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs" -import { join } from "path" import * as dataPath from "./data-path" -import { log } from "./logger" +import { createJsonFileCacheStore } from "./json-file-cache-store" import type { ModelCapabilitiesSnapshot, ModelCapabilitiesSnapshotEntry } from "./model-capabilities" export const MODELS_DEV_SOURCE_URL = "https://models.dev/api.json" @@ -162,61 +160,28 @@ export async function fetchModelCapabilitiesSnapshot(args: { export function createModelCapabilitiesCacheStore( getCacheDir: () => string = dataPath.getOmoOpenCodeCacheDir, ) { - let memSnapshot: ModelCapabilitiesSnapshot | null | undefined - - function getCacheFilePath(): string { - return join(getCacheDir(), MODEL_CAPABILITIES_CACHE_FILE) - } - - function ensureCacheDir(): void { - const cacheDir = getCacheDir() - if (!existsSync(cacheDir)) { - mkdirSync(cacheDir, { recursive: true }) - } - } + const snapshotCacheStore = createJsonFileCacheStore({ + getCacheDir, + filename: MODEL_CAPABILITIES_CACHE_FILE, + logPrefix: "model-capabilities-cache", + cacheLabel: "Cache", + describe: (snapshot) => ({ + modelCount: Object.keys(snapshot.models).length, + generatedAt: snapshot.generatedAt, + }), + serialize: (snapshot) => `${JSON.stringify(snapshot, null, 2)}\n`, + }) function readModelCapabilitiesCache(): ModelCapabilitiesSnapshot | null { - if (memSnapshot !== undefined) { - return memSnapshot - } - - const cacheFile = getCacheFilePath() - if (!existsSync(cacheFile)) { - memSnapshot = null - log("[model-capabilities-cache] Cache file not found", { cacheFile }) - return null - } - - try { - const content = readFileSync(cacheFile, "utf-8") - const snapshot = JSON.parse(content) as ModelCapabilitiesSnapshot - memSnapshot = snapshot - log("[model-capabilities-cache] Read cache", { - modelCount: Object.keys(snapshot.models).length, - generatedAt: snapshot.generatedAt, - }) - return snapshot - } catch (error) { - memSnapshot = null - log("[model-capabilities-cache] Error reading cache", { error: String(error) }) - return null - } + return snapshotCacheStore.read() } function hasModelCapabilitiesCache(): boolean { - return existsSync(getCacheFilePath()) + return snapshotCacheStore.has() } function writeModelCapabilitiesCache(snapshot: ModelCapabilitiesSnapshot): void { - ensureCacheDir() - const cacheFile = getCacheFilePath() - - writeFileSync(cacheFile, JSON.stringify(snapshot, null, 2) + "\n") - memSnapshot = snapshot - log("[model-capabilities-cache] Cache written", { - modelCount: Object.keys(snapshot.models).length, - generatedAt: snapshot.generatedAt, - }) + snapshotCacheStore.write(snapshot) } async function refreshModelCapabilitiesCache(args: {