Skip to content

Commit 5e2a0f4

Browse files
feat(shared): add connected-providers-cache for model availability
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode) Co-authored-by: Sisyphus <[email protected]>
1 parent da416b3 commit 5e2a0f4

File tree

10 files changed

+744
-64
lines changed

10 files changed

+744
-64
lines changed

src/cli/doctor/checks/model-resolution.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -199,9 +199,11 @@ function buildDetailsArray(info: ModelResolutionInfo, available: AvailableModels
199199
details.push("═══ Available Models (from cache) ═══")
200200
details.push("")
201201
if (available.cacheExists) {
202-
details.push(` Providers: ${available.providers.length} (${available.providers.slice(0, 8).join(", ")}${available.providers.length > 8 ? "..." : ""})`)
202+
details.push(` Providers in cache: ${available.providers.length}`)
203+
details.push(` Sample: ${available.providers.slice(0, 6).join(", ")}${available.providers.length > 6 ? "..." : ""}`)
203204
details.push(` Total models: ${available.modelCount}`)
204205
details.push(` Cache: ~/.cache/opencode/models.json`)
206+
details.push(` ℹ Runtime: only connected providers used`)
205207
details.push(` Refresh: opencode models --refresh`)
206208
} else {
207209
details.push(" ⚠ Cache not found. Run 'opencode' to populate.")

src/hooks/auto-update-checker/index.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { log } from "../../shared/logger"
66
import { getConfigLoadErrors, clearConfigLoadErrors } from "../../shared/config-errors"
77
import { runBunInstall } from "../../cli/config-manager"
88
import { isModelCacheAvailable } from "../../shared/model-availability"
9+
import { hasConnectedProvidersCache, updateConnectedProvidersCache } from "../../shared/connected-providers-cache"
910
import type { AutoUpdateCheckerOptions } from "./types"
1011

1112
const SISYPHUS_SPINNER = ["·", "•", "●", "○", "◌", "◦", " "]
@@ -77,6 +78,7 @@ export function createAutoUpdateCheckerHook(ctx: PluginInput, options: AutoUpdat
7778

7879
await showConfigErrorsIfAny(ctx)
7980
await showModelCacheWarningIfNeeded(ctx)
81+
await updateAndShowConnectedProvidersCacheStatus(ctx)
8082

8183
if (localDevVersion) {
8284
if (showStartupToast) {
@@ -186,6 +188,29 @@ async function showModelCacheWarningIfNeeded(ctx: PluginInput): Promise<void> {
186188
log("[auto-update-checker] Model cache warning shown")
187189
}
188190

191+
async function updateAndShowConnectedProvidersCacheStatus(ctx: PluginInput): Promise<void> {
192+
const hadCache = hasConnectedProvidersCache()
193+
194+
updateConnectedProvidersCache(ctx.client).catch(() => {})
195+
196+
if (!hadCache) {
197+
await ctx.client.tui
198+
.showToast({
199+
body: {
200+
title: "Connected Providers Cache",
201+
message: "Building provider cache for first time. Restart OpenCode for full model filtering.",
202+
variant: "info" as const,
203+
duration: 8000,
204+
},
205+
})
206+
.catch(() => {})
207+
208+
log("[auto-update-checker] Connected providers cache toast shown (first run)")
209+
} else {
210+
log("[auto-update-checker] Connected providers cache exists, updating in background")
211+
}
212+
}
213+
189214
async function showConfigErrorsIfAny(ctx: PluginInput): Promise<void> {
190215
const errors = getConfigLoadErrors()
191216
if (errors.length === 0) return
Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs"
2+
import { join } from "path"
3+
import { log } from "./logger"
4+
import { getOmoOpenCodeCacheDir } from "./data-path"
5+
6+
const CONNECTED_PROVIDERS_CACHE_FILE = "connected-providers.json"
7+
const PROVIDER_MODELS_CACHE_FILE = "provider-models.json"
8+
9+
interface ConnectedProvidersCache {
10+
connected: string[]
11+
updatedAt: string
12+
}
13+
14+
interface ProviderModelsCache {
15+
models: Record<string, string[]>
16+
connected: string[]
17+
updatedAt: string
18+
}
19+
20+
function getCacheFilePath(filename: string): string {
21+
return join(getOmoOpenCodeCacheDir(), filename)
22+
}
23+
24+
function ensureCacheDir(): void {
25+
const cacheDir = getOmoOpenCodeCacheDir()
26+
if (!existsSync(cacheDir)) {
27+
mkdirSync(cacheDir, { recursive: true })
28+
}
29+
}
30+
31+
/**
32+
* Read the connected providers cache.
33+
* Returns the list of connected provider IDs, or null if cache doesn't exist.
34+
*/
35+
export function readConnectedProvidersCache(): string[] | null {
36+
const cacheFile = getCacheFilePath(CONNECTED_PROVIDERS_CACHE_FILE)
37+
38+
if (!existsSync(cacheFile)) {
39+
log("[connected-providers-cache] Cache file not found", { cacheFile })
40+
return null
41+
}
42+
43+
try {
44+
const content = readFileSync(cacheFile, "utf-8")
45+
const data = JSON.parse(content) as ConnectedProvidersCache
46+
log("[connected-providers-cache] Read cache", { count: data.connected.length, updatedAt: data.updatedAt })
47+
return data.connected
48+
} catch (err) {
49+
log("[connected-providers-cache] Error reading cache", { error: String(err) })
50+
return null
51+
}
52+
}
53+
54+
/**
55+
* Check if connected providers cache exists.
56+
*/
57+
export function hasConnectedProvidersCache(): boolean {
58+
const cacheFile = getCacheFilePath(CONNECTED_PROVIDERS_CACHE_FILE)
59+
return existsSync(cacheFile)
60+
}
61+
62+
/**
63+
* Write the connected providers cache.
64+
*/
65+
function writeConnectedProvidersCache(connected: string[]): void {
66+
ensureCacheDir()
67+
const cacheFile = getCacheFilePath(CONNECTED_PROVIDERS_CACHE_FILE)
68+
69+
const data: ConnectedProvidersCache = {
70+
connected,
71+
updatedAt: new Date().toISOString(),
72+
}
73+
74+
try {
75+
writeFileSync(cacheFile, JSON.stringify(data, null, 2))
76+
log("[connected-providers-cache] Cache written", { count: connected.length })
77+
} catch (err) {
78+
log("[connected-providers-cache] Error writing cache", { error: String(err) })
79+
}
80+
}
81+
82+
/**
83+
* Read the provider-models cache.
84+
* Returns the cache data, or null if cache doesn't exist.
85+
*/
86+
export function readProviderModelsCache(): ProviderModelsCache | null {
87+
const cacheFile = getCacheFilePath(PROVIDER_MODELS_CACHE_FILE)
88+
89+
if (!existsSync(cacheFile)) {
90+
log("[connected-providers-cache] Provider-models cache file not found", { cacheFile })
91+
return null
92+
}
93+
94+
try {
95+
const content = readFileSync(cacheFile, "utf-8")
96+
const data = JSON.parse(content) as ProviderModelsCache
97+
log("[connected-providers-cache] Read provider-models cache", {
98+
providerCount: Object.keys(data.models).length,
99+
updatedAt: data.updatedAt
100+
})
101+
return data
102+
} catch (err) {
103+
log("[connected-providers-cache] Error reading provider-models cache", { error: String(err) })
104+
return null
105+
}
106+
}
107+
108+
/**
109+
* Check if provider-models cache exists.
110+
*/
111+
export function hasProviderModelsCache(): boolean {
112+
const cacheFile = getCacheFilePath(PROVIDER_MODELS_CACHE_FILE)
113+
return existsSync(cacheFile)
114+
}
115+
116+
/**
117+
* Write the provider-models cache.
118+
*/
119+
export function writeProviderModelsCache(data: { models: Record<string, string[]>; connected: string[] }): void {
120+
ensureCacheDir()
121+
const cacheFile = getCacheFilePath(PROVIDER_MODELS_CACHE_FILE)
122+
123+
const cacheData: ProviderModelsCache = {
124+
...data,
125+
updatedAt: new Date().toISOString(),
126+
}
127+
128+
try {
129+
writeFileSync(cacheFile, JSON.stringify(cacheData, null, 2))
130+
log("[connected-providers-cache] Provider-models cache written", {
131+
providerCount: Object.keys(data.models).length
132+
})
133+
} catch (err) {
134+
log("[connected-providers-cache] Error writing provider-models cache", { error: String(err) })
135+
}
136+
}
137+
138+
/**
139+
* Update the connected providers cache by fetching from the client.
140+
* Also updates the provider-models cache with model lists per provider.
141+
*/
142+
export async function updateConnectedProvidersCache(client: {
143+
provider?: {
144+
list?: () => Promise<{ data?: { connected?: string[] } }>
145+
}
146+
model?: {
147+
list?: () => Promise<{ data?: Array<{ id: string; provider: string }> }>
148+
}
149+
}): Promise<void> {
150+
if (!client?.provider?.list) {
151+
log("[connected-providers-cache] client.provider.list not available")
152+
return
153+
}
154+
155+
try {
156+
const result = await client.provider.list()
157+
const connected = result.data?.connected ?? []
158+
log("[connected-providers-cache] Fetched connected providers", { count: connected.length, providers: connected })
159+
160+
writeConnectedProvidersCache(connected)
161+
162+
// Also update provider-models cache if model.list is available
163+
if (client.model?.list) {
164+
try {
165+
const modelsResult = await client.model.list()
166+
const models = modelsResult.data ?? []
167+
168+
const modelsByProvider: Record<string, string[]> = {}
169+
for (const model of models) {
170+
if (!modelsByProvider[model.provider]) {
171+
modelsByProvider[model.provider] = []
172+
}
173+
modelsByProvider[model.provider].push(model.id)
174+
}
175+
176+
writeProviderModelsCache({
177+
models: modelsByProvider,
178+
connected,
179+
})
180+
181+
log("[connected-providers-cache] Provider-models cache updated", {
182+
providerCount: Object.keys(modelsByProvider).length,
183+
totalModels: models.length,
184+
})
185+
} catch (modelErr) {
186+
log("[connected-providers-cache] Error fetching models", { error: String(modelErr) })
187+
}
188+
}
189+
} catch (err) {
190+
log("[connected-providers-cache] Error updating cache", { error: String(err) })
191+
}
192+
}

src/shared/data-path.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,28 @@ export function getDataDir(): string {
2020
export function getOpenCodeStorageDir(): string {
2121
return path.join(getDataDir(), "opencode", "storage")
2222
}
23+
24+
/**
25+
* Returns the user-level cache directory.
26+
* Matches OpenCode's behavior via xdg-basedir:
27+
* - All platforms: XDG_CACHE_HOME or ~/.cache
28+
*/
29+
export function getCacheDir(): string {
30+
return process.env.XDG_CACHE_HOME ?? path.join(os.homedir(), ".cache")
31+
}
32+
33+
/**
34+
* Returns the oh-my-opencode cache directory.
35+
* All platforms: ~/.cache/oh-my-opencode
36+
*/
37+
export function getOmoOpenCodeCacheDir(): string {
38+
return path.join(getCacheDir(), "oh-my-opencode")
39+
}
40+
41+
/**
42+
* Returns the OpenCode cache directory (for reading OpenCode's cache).
43+
* All platforms: ~/.cache/opencode
44+
*/
45+
export function getOpenCodeCacheDir(): string {
46+
return path.join(getCacheDir(), "opencode")
47+
}

src/shared/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ export * from "./agent-tool-restrictions"
2828
export * from "./model-requirements"
2929
export * from "./model-resolver"
3030
export * from "./model-availability"
31+
export * from "./connected-providers-cache"
3132
export * from "./case-insensitive"
3233
export * from "./session-utils"
3334
export * from "./tmux"

0 commit comments

Comments
 (0)