refactor(shared): extract shared cache factory to deduplicate cache patterns
This commit is contained in:
@@ -1,7 +1,6 @@
|
|||||||
import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs"
|
|
||||||
import { join } from "path"
|
|
||||||
import { log } from "./logger"
|
import { log } from "./logger"
|
||||||
import * as dataPath from "./data-path"
|
import * as dataPath from "./data-path"
|
||||||
|
import { createJsonFileCacheStore } from "./json-file-cache-store"
|
||||||
|
|
||||||
const CONNECTED_PROVIDERS_CACHE_FILE = "connected-providers.json"
|
const CONNECTED_PROVIDERS_CACHE_FILE = "connected-providers.json"
|
||||||
const PROVIDER_MODELS_CACHE_FILE = "provider-models.json"
|
const PROVIDER_MODELS_CACHE_FILE = "provider-models.json"
|
||||||
@@ -47,115 +46,52 @@ function isRecord(value: unknown): value is Record<string, unknown> {
|
|||||||
export function createConnectedProvidersCacheStore(
|
export function createConnectedProvidersCacheStore(
|
||||||
getCacheDir: () => string = dataPath.getOmoOpenCodeCacheDir
|
getCacheDir: () => string = dataPath.getOmoOpenCodeCacheDir
|
||||||
) {
|
) {
|
||||||
function getCacheFilePath(filename: string): string {
|
const connectedProvidersCacheStore = createJsonFileCacheStore<ConnectedProvidersCache>({
|
||||||
return join(getCacheDir(), filename)
|
getCacheDir,
|
||||||
}
|
filename: CONNECTED_PROVIDERS_CACHE_FILE,
|
||||||
|
logPrefix: "connected-providers-cache",
|
||||||
let memConnected: string[] | null | undefined
|
cacheLabel: "Cache",
|
||||||
let memProviderModels: ProviderModelsCache | null | undefined
|
describe: (value) => ({ count: value.connected.length, updatedAt: value.updatedAt }),
|
||||||
|
})
|
||||||
function ensureCacheDir(): void {
|
const providerModelsCacheStore = createJsonFileCacheStore<ProviderModelsCache>({
|
||||||
const cacheDir = getCacheDir()
|
getCacheDir,
|
||||||
if (!existsSync(cacheDir)) {
|
filename: PROVIDER_MODELS_CACHE_FILE,
|
||||||
mkdirSync(cacheDir, { recursive: true })
|
logPrefix: "connected-providers-cache",
|
||||||
}
|
cacheLabel: "Provider-models cache",
|
||||||
}
|
describe: (value) => ({
|
||||||
|
providerCount: Object.keys(value.models).length,
|
||||||
|
updatedAt: value.updatedAt,
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
|
||||||
function readConnectedProvidersCache(): string[] | null {
|
function readConnectedProvidersCache(): string[] | null {
|
||||||
if (memConnected !== undefined) return memConnected
|
return connectedProvidersCacheStore.read()?.connected ?? null
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function hasConnectedProvidersCache(): boolean {
|
function hasConnectedProvidersCache(): boolean {
|
||||||
const cacheFile = getCacheFilePath(CONNECTED_PROVIDERS_CACHE_FILE)
|
return connectedProvidersCacheStore.has()
|
||||||
return existsSync(cacheFile)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function writeConnectedProvidersCache(connected: string[]): void {
|
function writeConnectedProvidersCache(connected: string[]): void {
|
||||||
ensureCacheDir()
|
connectedProvidersCacheStore.write({
|
||||||
const cacheFile = getCacheFilePath(CONNECTED_PROVIDERS_CACHE_FILE)
|
|
||||||
|
|
||||||
const data: ConnectedProvidersCache = {
|
|
||||||
connected,
|
connected,
|
||||||
updatedAt: new Date().toISOString(),
|
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 {
|
function readProviderModelsCache(): ProviderModelsCache | null {
|
||||||
if (memProviderModels !== undefined) return memProviderModels
|
return providerModelsCacheStore.read()
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function hasProviderModelsCache(): boolean {
|
function hasProviderModelsCache(): boolean {
|
||||||
const cacheFile = getCacheFilePath(PROVIDER_MODELS_CACHE_FILE)
|
return providerModelsCacheStore.has()
|
||||||
return existsSync(cacheFile)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function writeProviderModelsCache(data: { models: Record<string, string[] | ModelMetadata[]>; connected: string[] }): void {
|
function writeProviderModelsCache(data: { models: Record<string, string[] | ModelMetadata[]>; connected: string[] }): void {
|
||||||
ensureCacheDir()
|
providerModelsCacheStore.write({
|
||||||
const cacheFile = getCacheFilePath(PROVIDER_MODELS_CACHE_FILE)
|
|
||||||
|
|
||||||
const cacheData: ProviderModelsCache = {
|
|
||||||
...data,
|
...data,
|
||||||
updatedAt: new Date().toISOString(),
|
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: {
|
async function updateConnectedProvidersCache(client: {
|
||||||
@@ -223,8 +159,8 @@ export function createConnectedProvidersCacheStore(
|
|||||||
}
|
}
|
||||||
|
|
||||||
function _resetMemCacheForTesting(): void {
|
function _resetMemCacheForTesting(): void {
|
||||||
memConnected = undefined
|
connectedProvidersCacheStore.resetMemory()
|
||||||
memProviderModels = undefined
|
providerModelsCacheStore.resetMemory()
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -256,7 +192,7 @@ export function findProviderModelMetadata(
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if (entry?.id === modelID) {
|
if (entry.id === modelID) {
|
||||||
return entry
|
return entry
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,98 @@
|
|||||||
|
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
|
||||||
|
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,5 @@
|
|||||||
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs"
|
|
||||||
import { join } from "path"
|
|
||||||
import * as dataPath from "./data-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"
|
import type { ModelCapabilitiesSnapshot, ModelCapabilitiesSnapshotEntry } from "./model-capabilities"
|
||||||
|
|
||||||
export const MODELS_DEV_SOURCE_URL = "https://models.dev/api.json"
|
export const MODELS_DEV_SOURCE_URL = "https://models.dev/api.json"
|
||||||
@@ -162,61 +160,28 @@ export async function fetchModelCapabilitiesSnapshot(args: {
|
|||||||
export function createModelCapabilitiesCacheStore(
|
export function createModelCapabilitiesCacheStore(
|
||||||
getCacheDir: () => string = dataPath.getOmoOpenCodeCacheDir,
|
getCacheDir: () => string = dataPath.getOmoOpenCodeCacheDir,
|
||||||
) {
|
) {
|
||||||
let memSnapshot: ModelCapabilitiesSnapshot | null | undefined
|
const snapshotCacheStore = createJsonFileCacheStore<ModelCapabilitiesSnapshot>({
|
||||||
|
getCacheDir,
|
||||||
function getCacheFilePath(): string {
|
filename: MODEL_CAPABILITIES_CACHE_FILE,
|
||||||
return join(getCacheDir(), MODEL_CAPABILITIES_CACHE_FILE)
|
logPrefix: "model-capabilities-cache",
|
||||||
}
|
cacheLabel: "Cache",
|
||||||
|
describe: (snapshot) => ({
|
||||||
function ensureCacheDir(): void {
|
modelCount: Object.keys(snapshot.models).length,
|
||||||
const cacheDir = getCacheDir()
|
generatedAt: snapshot.generatedAt,
|
||||||
if (!existsSync(cacheDir)) {
|
}),
|
||||||
mkdirSync(cacheDir, { recursive: true })
|
serialize: (snapshot) => `${JSON.stringify(snapshot, null, 2)}\n`,
|
||||||
}
|
})
|
||||||
}
|
|
||||||
|
|
||||||
function readModelCapabilitiesCache(): ModelCapabilitiesSnapshot | null {
|
function readModelCapabilitiesCache(): ModelCapabilitiesSnapshot | null {
|
||||||
if (memSnapshot !== undefined) {
|
return snapshotCacheStore.read()
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function hasModelCapabilitiesCache(): boolean {
|
function hasModelCapabilitiesCache(): boolean {
|
||||||
return existsSync(getCacheFilePath())
|
return snapshotCacheStore.has()
|
||||||
}
|
}
|
||||||
|
|
||||||
function writeModelCapabilitiesCache(snapshot: ModelCapabilitiesSnapshot): void {
|
function writeModelCapabilitiesCache(snapshot: ModelCapabilitiesSnapshot): void {
|
||||||
ensureCacheDir()
|
snapshotCacheStore.write(snapshot)
|
||||||
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,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function refreshModelCapabilitiesCache(args: {
|
async function refreshModelCapabilitiesCache(args: {
|
||||||
|
|||||||
Reference in New Issue
Block a user