refactor: improve test isolation and DI for cache/port-utils/resolve-file-uri
- connected-providers-cache: extract factory pattern (createConnectedProvidersCacheStore) for testable cache dir injection - port-utils.test: environment-independent tests with real socket probing and contiguous port detection - resolve-file-uri.test: mock homedir instead of touching real home directory - github-triage: update SKILL.md
This commit is contained in:
@@ -25,172 +25,177 @@ interface ProviderModelsCache {
|
||||
updatedAt: string
|
||||
}
|
||||
|
||||
function getCacheFilePath(filename: string): string {
|
||||
return join(dataPath.getOmoOpenCodeCacheDir(), filename)
|
||||
}
|
||||
|
||||
function ensureCacheDir(): void {
|
||||
const cacheDir = dataPath.getOmoOpenCodeCacheDir()
|
||||
if (!existsSync(cacheDir)) {
|
||||
mkdirSync(cacheDir, { recursive: true })
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the connected providers cache.
|
||||
* Returns the list of connected provider IDs, or null if cache doesn't exist.
|
||||
*/
|
||||
export function readConnectedProvidersCache(): string[] | null {
|
||||
const cacheFile = getCacheFilePath(CONNECTED_PROVIDERS_CACHE_FILE)
|
||||
|
||||
if (!existsSync(cacheFile)) {
|
||||
log("[connected-providers-cache] Cache file not found", { cacheFile })
|
||||
return null
|
||||
export function createConnectedProvidersCacheStore(
|
||||
getCacheDir: () => string = dataPath.getOmoOpenCodeCacheDir
|
||||
) {
|
||||
function getCacheFilePath(filename: string): string {
|
||||
return join(getCacheDir(), filename)
|
||||
}
|
||||
|
||||
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 })
|
||||
return data.connected
|
||||
} catch (err) {
|
||||
log("[connected-providers-cache] Error reading cache", { error: String(err) })
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if connected providers cache exists.
|
||||
*/
|
||||
export function hasConnectedProvidersCache(): boolean {
|
||||
const cacheFile = getCacheFilePath(CONNECTED_PROVIDERS_CACHE_FILE)
|
||||
return existsSync(cacheFile)
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the connected providers cache.
|
||||
*/
|
||||
function writeConnectedProvidersCache(connected: string[]): void {
|
||||
ensureCacheDir()
|
||||
const cacheFile = getCacheFilePath(CONNECTED_PROVIDERS_CACHE_FILE)
|
||||
|
||||
const data: ConnectedProvidersCache = {
|
||||
connected,
|
||||
updatedAt: new Date().toISOString(),
|
||||
function ensureCacheDir(): void {
|
||||
const cacheDir = getCacheDir()
|
||||
if (!existsSync(cacheDir)) {
|
||||
mkdirSync(cacheDir, { recursive: true })
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
writeFileSync(cacheFile, JSON.stringify(data, null, 2))
|
||||
log("[connected-providers-cache] Cache written", { count: connected.length })
|
||||
} catch (err) {
|
||||
log("[connected-providers-cache] Error writing cache", { error: String(err) })
|
||||
}
|
||||
}
|
||||
function readConnectedProvidersCache(): string[] | null {
|
||||
const cacheFile = getCacheFilePath(CONNECTED_PROVIDERS_CACHE_FILE)
|
||||
|
||||
/**
|
||||
* Read the provider-models cache.
|
||||
* Returns the cache data, or null if cache doesn't exist.
|
||||
*/
|
||||
export function readProviderModelsCache(): ProviderModelsCache | null {
|
||||
const cacheFile = getCacheFilePath(PROVIDER_MODELS_CACHE_FILE)
|
||||
|
||||
if (!existsSync(cacheFile)) {
|
||||
log("[connected-providers-cache] Provider-models cache file not found", { cacheFile })
|
||||
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
|
||||
})
|
||||
return data
|
||||
} catch (err) {
|
||||
log("[connected-providers-cache] Error reading provider-models cache", { error: String(err) })
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if provider-models cache exists.
|
||||
*/
|
||||
export function hasProviderModelsCache(): boolean {
|
||||
const cacheFile = getCacheFilePath(PROVIDER_MODELS_CACHE_FILE)
|
||||
return existsSync(cacheFile)
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the provider-models cache.
|
||||
*/
|
||||
export function writeProviderModelsCache(data: { models: Record<string, string[]>; connected: string[] }): void {
|
||||
ensureCacheDir()
|
||||
const cacheFile = getCacheFilePath(PROVIDER_MODELS_CACHE_FILE)
|
||||
|
||||
const cacheData: ProviderModelsCache = {
|
||||
...data,
|
||||
updatedAt: new Date().toISOString(),
|
||||
}
|
||||
|
||||
try {
|
||||
writeFileSync(cacheFile, JSON.stringify(cacheData, null, 2))
|
||||
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) })
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the connected providers cache by fetching from the client.
|
||||
* Also updates the provider-models cache with model lists per provider.
|
||||
*/
|
||||
export async function updateConnectedProvidersCache(client: {
|
||||
provider?: {
|
||||
list?: () => Promise<{
|
||||
data?: {
|
||||
connected?: string[]
|
||||
all?: Array<{ id: string; models?: Record<string, unknown> }>
|
||||
}
|
||||
}>
|
||||
}
|
||||
}): Promise<void> {
|
||||
if (!client?.provider?.list) {
|
||||
log("[connected-providers-cache] client.provider.list not available")
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await client.provider.list()
|
||||
const connected = result.data?.connected ?? []
|
||||
log("[connected-providers-cache] Fetched connected providers", { count: connected.length, providers: connected })
|
||||
|
||||
writeConnectedProvidersCache(connected)
|
||||
|
||||
const modelsByProvider: Record<string, string[]> = {}
|
||||
const allProviders = result.data?.all ?? []
|
||||
|
||||
for (const provider of allProviders) {
|
||||
if (provider.models) {
|
||||
const modelIds = Object.keys(provider.models)
|
||||
if (modelIds.length > 0) {
|
||||
modelsByProvider[provider.id] = modelIds
|
||||
}
|
||||
}
|
||||
if (!existsSync(cacheFile)) {
|
||||
log("[connected-providers-cache] Cache file not found", { cacheFile })
|
||||
return null
|
||||
}
|
||||
|
||||
log("[connected-providers-cache] Extracted models from provider list", {
|
||||
providerCount: Object.keys(modelsByProvider).length,
|
||||
totalModels: Object.values(modelsByProvider).reduce((sum, ids) => sum + ids.length, 0),
|
||||
})
|
||||
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 })
|
||||
return data.connected
|
||||
} catch (err) {
|
||||
log("[connected-providers-cache] Error reading cache", { error: String(err) })
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
writeProviderModelsCache({
|
||||
models: modelsByProvider,
|
||||
function hasConnectedProvidersCache(): boolean {
|
||||
const cacheFile = getCacheFilePath(CONNECTED_PROVIDERS_CACHE_FILE)
|
||||
return existsSync(cacheFile)
|
||||
}
|
||||
|
||||
function writeConnectedProvidersCache(connected: string[]): void {
|
||||
ensureCacheDir()
|
||||
const cacheFile = getCacheFilePath(CONNECTED_PROVIDERS_CACHE_FILE)
|
||||
|
||||
const data: ConnectedProvidersCache = {
|
||||
connected,
|
||||
})
|
||||
} catch (err) {
|
||||
log("[connected-providers-cache] Error updating cache", { error: String(err) })
|
||||
updatedAt: new Date().toISOString(),
|
||||
}
|
||||
|
||||
try {
|
||||
writeFileSync(cacheFile, JSON.stringify(data, null, 2))
|
||||
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 {
|
||||
const cacheFile = getCacheFilePath(PROVIDER_MODELS_CACHE_FILE)
|
||||
|
||||
if (!existsSync(cacheFile)) {
|
||||
log("[connected-providers-cache] Provider-models cache file not found", { cacheFile })
|
||||
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,
|
||||
})
|
||||
return data
|
||||
} catch (err) {
|
||||
log("[connected-providers-cache] Error reading provider-models cache", { error: String(err) })
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
function hasProviderModelsCache(): boolean {
|
||||
const cacheFile = getCacheFilePath(PROVIDER_MODELS_CACHE_FILE)
|
||||
return existsSync(cacheFile)
|
||||
}
|
||||
|
||||
function writeProviderModelsCache(data: { models: Record<string, string[]>; connected: string[] }): void {
|
||||
ensureCacheDir()
|
||||
const cacheFile = getCacheFilePath(PROVIDER_MODELS_CACHE_FILE)
|
||||
|
||||
const cacheData: ProviderModelsCache = {
|
||||
...data,
|
||||
updatedAt: new Date().toISOString(),
|
||||
}
|
||||
|
||||
try {
|
||||
writeFileSync(cacheFile, JSON.stringify(cacheData, null, 2))
|
||||
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: {
|
||||
provider?: {
|
||||
list?: () => Promise<{
|
||||
data?: {
|
||||
connected?: string[]
|
||||
all?: Array<{ id: string; models?: Record<string, unknown> }>
|
||||
}
|
||||
}>
|
||||
}
|
||||
}): Promise<void> {
|
||||
if (!client?.provider?.list) {
|
||||
log("[connected-providers-cache] client.provider.list not available")
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await client.provider.list()
|
||||
const connected = result.data?.connected ?? []
|
||||
log("[connected-providers-cache] Fetched connected providers", {
|
||||
count: connected.length,
|
||||
providers: connected,
|
||||
})
|
||||
|
||||
writeConnectedProvidersCache(connected)
|
||||
|
||||
const modelsByProvider: Record<string, string[]> = {}
|
||||
const allProviders = result.data?.all ?? []
|
||||
|
||||
for (const provider of allProviders) {
|
||||
if (provider.models) {
|
||||
const modelIds = Object.keys(provider.models)
|
||||
if (modelIds.length > 0) {
|
||||
modelsByProvider[provider.id] = modelIds
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log("[connected-providers-cache] Extracted models from provider list", {
|
||||
providerCount: Object.keys(modelsByProvider).length,
|
||||
totalModels: Object.values(modelsByProvider).reduce((sum, ids) => sum + ids.length, 0),
|
||||
})
|
||||
|
||||
writeProviderModelsCache({
|
||||
models: modelsByProvider,
|
||||
connected,
|
||||
})
|
||||
} catch (err) {
|
||||
log("[connected-providers-cache] Error updating cache", { error: String(err) })
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
readConnectedProvidersCache,
|
||||
hasConnectedProvidersCache,
|
||||
readProviderModelsCache,
|
||||
hasProviderModelsCache,
|
||||
writeProviderModelsCache,
|
||||
updateConnectedProvidersCache,
|
||||
}
|
||||
}
|
||||
|
||||
const defaultConnectedProvidersCacheStore = createConnectedProvidersCacheStore(
|
||||
() => dataPath.getOmoOpenCodeCacheDir()
|
||||
)
|
||||
|
||||
export const {
|
||||
readConnectedProvidersCache,
|
||||
hasConnectedProvidersCache,
|
||||
readProviderModelsCache,
|
||||
hasProviderModelsCache,
|
||||
writeProviderModelsCache,
|
||||
updateConnectedProvidersCache,
|
||||
} = defaultConnectedProvidersCacheStore
|
||||
|
||||
Reference in New Issue
Block a user