refactor(shared): improve model availability and resolution module structure

- Use namespace import for connected-providers-cache for better clarity
- Add explicit type annotation for modelsByProvider to improve type safety
- Update tests to reflect refactored module organization
- Improve code organization while maintaining functionality

🤖 Generated with assistance of OhMyOpenCode
This commit is contained in:
YeonGyu-Kim 2026-02-08 18:41:35 +09:00
parent 1324fee30f
commit 7788ba3d8a
3 changed files with 35 additions and 28 deletions

View File

@ -1,38 +1,43 @@
import { describe, it, expect, beforeEach, afterEach, beforeAll, afterAll, mock } from "bun:test" declare const require: (name: string) => any
const { describe, it, expect, beforeEach, afterEach, beforeAll } = require("bun:test")
import { mkdtempSync, writeFileSync, rmSync } from "fs" import { mkdtempSync, writeFileSync, rmSync } from "fs"
import { tmpdir } from "os" import { tmpdir } from "os"
import { join } from "path" import { join } from "path"
import { fuzzyMatchModel, isModelAvailable } from "./model-name-matcher"
let activeCacheHomeDir: string | null = null let __resetModelCache: () => void
const DEFAULT_CACHE_HOME_DIR = join(tmpdir(), "opencode-test-default-cache") let fetchAvailableModels: (client?: unknown, options?: { connectedProviders?: string[] | null }) => Promise<Set<string>>
let fuzzyMatchModel: (target: string, available: Set<string>, providers?: string[]) => string | null
let isModelAvailable: (targetModel: string, availableModels: Set<string>) => boolean
let getConnectedProviders: (client: unknown) => Promise<string[]>
mock.module("./data-path", () => ({ beforeAll(async () => {
getDataDir: () => activeCacheHomeDir ?? DEFAULT_CACHE_HOME_DIR, ;({
getOpenCodeStorageDir: () => join(activeCacheHomeDir ?? DEFAULT_CACHE_HOME_DIR, "opencode", "storage"), __resetModelCache,
getCacheDir: () => activeCacheHomeDir ?? DEFAULT_CACHE_HOME_DIR, fetchAvailableModels,
getOmoOpenCodeCacheDir: () => join(activeCacheHomeDir ?? DEFAULT_CACHE_HOME_DIR, "oh-my-opencode"), fuzzyMatchModel,
getOpenCodeCacheDir: () => join(activeCacheHomeDir ?? DEFAULT_CACHE_HOME_DIR, "opencode"), isModelAvailable,
})) getConnectedProviders,
} = await import("./model-availability"))
})
describe("fetchAvailableModels", () => { describe("fetchAvailableModels", () => {
let tempDir: string let tempDir: string
let fetchAvailableModels: (client?: unknown, options?: { connectedProviders?: string[] | null }) => Promise<Set<string>> let originalXdgCache: string | undefined
let __resetModelCache: () => void
beforeAll(async () => {
;({ fetchAvailableModels } = await import("./available-models-fetcher"))
;({ __resetModelCache } = await import("./model-cache-availability"))
})
beforeEach(() => { beforeEach(() => {
__resetModelCache() __resetModelCache()
tempDir = mkdtempSync(join(tmpdir(), "opencode-test-")) tempDir = mkdtempSync(join(tmpdir(), "opencode-test-"))
activeCacheHomeDir = tempDir originalXdgCache = process.env.XDG_CACHE_HOME
process.env.XDG_CACHE_HOME = tempDir
}) })
afterEach(() => { afterEach(() => {
activeCacheHomeDir = null if (originalXdgCache !== undefined) {
process.env.XDG_CACHE_HOME = originalXdgCache
} else {
delete process.env.XDG_CACHE_HOME
}
rmSync(tempDir, { recursive: true, force: true }) rmSync(tempDir, { recursive: true, force: true })
}) })

View File

@ -2,7 +2,7 @@ import { existsSync, readFileSync } from "fs"
import { join } from "path" import { join } from "path"
import { log } from "./logger" import { log } from "./logger"
import { getOpenCodeCacheDir } from "./data-path" import { getOpenCodeCacheDir } from "./data-path"
import { readProviderModelsCache, hasProviderModelsCache, readConnectedProvidersCache } from "./connected-providers-cache" import * as connectedProvidersCache from "./connected-providers-cache"
/** /**
* Fuzzy match a target model name against available models * Fuzzy match a target model name against available models
@ -181,7 +181,7 @@ export async function fetchAvailableModels(
const connectedSet = new Set(connectedProvidersList) const connectedSet = new Set(connectedProvidersList)
const modelSet = new Set<string>() const modelSet = new Set<string>()
const providerModelsCache = readProviderModelsCache() const providerModelsCache = connectedProvidersCache.readProviderModelsCache()
if (providerModelsCache) { if (providerModelsCache) {
const providerCount = Object.keys(providerModelsCache.models).length const providerCount = Object.keys(providerModelsCache.models).length
if (providerCount === 0) { if (providerCount === 0) {
@ -189,7 +189,8 @@ export async function fetchAvailableModels(
} else { } else {
log("[fetchAvailableModels] using provider-models cache (whitelist-filtered)") log("[fetchAvailableModels] using provider-models cache (whitelist-filtered)")
for (const [providerId, modelIds] of Object.entries(providerModelsCache.models)) { const modelsByProvider = providerModelsCache.models as Record<string, Array<string | { id?: string }>>
for (const [providerId, modelIds] of Object.entries(modelsByProvider)) {
if (!connectedSet.has(providerId)) { if (!connectedSet.has(providerId)) {
continue continue
} }
@ -300,7 +301,7 @@ export function isAnyFallbackModelAvailable(
// Fallback: check if any provider in the chain is connected // Fallback: check if any provider in the chain is connected
// This handles race conditions where availableModels is empty or incomplete // This handles race conditions where availableModels is empty or incomplete
// but we know the provider is connected. // but we know the provider is connected.
const connectedProviders = readConnectedProvidersCache() const connectedProviders = connectedProvidersCache.readConnectedProvidersCache()
if (connectedProviders) { if (connectedProviders) {
const connectedSet = new Set(connectedProviders) const connectedSet = new Set(connectedProviders)
for (const entry of fallbackChain) { for (const entry of fallbackChain) {
@ -332,7 +333,7 @@ export function isAnyProviderConnected(
} }
} }
const connectedProviders = readConnectedProvidersCache() const connectedProviders = connectedProvidersCache.readConnectedProvidersCache()
if (connectedProviders) { if (connectedProviders) {
const connectedSet = new Set(connectedProviders) const connectedSet = new Set(connectedProviders)
for (const provider of providers) { for (const provider of providers) {
@ -349,7 +350,7 @@ export function isAnyProviderConnected(
export function __resetModelCache(): void {} export function __resetModelCache(): void {}
export function isModelCacheAvailable(): boolean { export function isModelCacheAvailable(): boolean {
if (hasProviderModelsCache()) { if (connectedProvidersCache.hasProviderModelsCache()) {
return true return true
} }
const cacheFile = join(getOpenCodeCacheDir(), "models.json") const cacheFile = join(getOpenCodeCacheDir(), "models.json")

View File

@ -1,5 +1,5 @@
import { log } from "./logger" import { log } from "./logger"
import { readConnectedProvidersCache } from "./connected-providers-cache" import * as connectedProvidersCache from "./connected-providers-cache"
import { fuzzyMatchModel } from "./model-availability" import { fuzzyMatchModel } from "./model-availability"
import type { FallbackEntry } from "./model-requirements" import type { FallbackEntry } from "./model-requirements"
@ -11,6 +11,7 @@ export type ModelResolutionRequest = {
} }
constraints: { constraints: {
availableModels: Set<string> availableModels: Set<string>
connectedProviders?: string[] | null
} }
policy?: { policy?: {
fallbackChain?: FallbackEntry[] fallbackChain?: FallbackEntry[]
@ -73,7 +74,7 @@ export function resolveModelPipeline(
return { model: match, provenance: "category-default", attempted } return { model: match, provenance: "category-default", attempted }
} }
} else { } else {
const connectedProviders = readConnectedProvidersCache() const connectedProviders = constraints.connectedProviders ?? connectedProvidersCache.readConnectedProvidersCache()
if (connectedProviders === null) { if (connectedProviders === null) {
log("Model resolved via category default (no cache, first run)", { log("Model resolved via category default (no cache, first run)", {
model: normalizedCategoryDefault, model: normalizedCategoryDefault,
@ -98,7 +99,7 @@ export function resolveModelPipeline(
if (fallbackChain && fallbackChain.length > 0) { if (fallbackChain && fallbackChain.length > 0) {
if (availableModels.size === 0) { if (availableModels.size === 0) {
const connectedProviders = readConnectedProvidersCache() const connectedProviders = constraints.connectedProviders ?? connectedProvidersCache.readConnectedProvidersCache()
const connectedSet = connectedProviders ? new Set(connectedProviders) : null const connectedSet = connectedProviders ? new Set(connectedProviders) : null
if (connectedSet === null) { if (connectedSet === null) {