Skip to content

Commit bd3a3bc

Browse files
committed
fix: handle both string[] and object[] formats in provider-models cache
Category delegation fails when provider-models.json contains model objects with metadata (id, provider, context, output) instead of plain strings. Line 196 in model-availability.ts assumes string[] format, causing: - Object concatenation: `${providerId}/${modelId}` becomes "ollama/[object Object]" - Empty availableModels Set passed to resolveModelPipeline() - Error: "Model not configured for category" This is the root cause of issue code-yeongyu#1508 where delegate_task(category='quick') fails despite direct agent routing (delegate_task(subagent_type='explore')) working correctly. Changes: - model-availability.ts: Add type check to handle both string and object formats - connected-providers-cache.ts: Update ProviderModelsCache interface to accept both formats - model-availability.test.ts: Add 4 test cases for object[] format handling Direct agent routing bypasses fetchAvailableModels() entirely, explaining why it works while category routing fails. This fix enables category delegation to work with manually-populated Ollama model caches. Fixes code-yeongyu#1508
1 parent 291f41f commit bd3a3bc

3 files changed

Lines changed: 90 additions & 9 deletions

File tree

src/shared/connected-providers-cache.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,16 @@ interface ConnectedProvidersCache {
1111
updatedAt: string
1212
}
1313

14+
interface ModelMetadata {
15+
id: string
16+
provider?: string
17+
context?: number
18+
output?: number
19+
name?: string
20+
}
21+
1422
interface ProviderModelsCache {
15-
models: Record<string, string[]>
23+
models: Record<string, string[] | ModelMetadata[]>
1624
connected: string[]
1725
updatedAt: string
1826
}

src/shared/model-availability.test.ts

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -619,7 +619,7 @@ describe("fetchAvailableModels with provider-models cache (whitelist-filtered)",
619619
rmSync(tempDir, { recursive: true, force: true })
620620
})
621621

622-
function writeProviderModelsCache(data: { models: Record<string, string[]>; connected: string[] }) {
622+
function writeProviderModelsCache(data: { models: Record<string, string[] | any[]>; connected: string[] }) {
623623
const cacheDir = join(tempDir, "oh-my-opencode")
624624
require("fs").mkdirSync(cacheDir, { recursive: true })
625625
writeFileSync(join(cacheDir, "provider-models.json"), JSON.stringify({
@@ -723,6 +723,72 @@ describe("fetchAvailableModels with provider-models cache (whitelist-filtered)",
723723
expect(result.has("anthropic/claude-opus-4-5")).toBe(false)
724724
expect(result.has("google/gemini-3-pro")).toBe(false)
725725
})
726+
727+
it("should handle object[] format with metadata (Ollama-style)", async () => {
728+
writeProviderModelsCache({
729+
models: {
730+
ollama: [
731+
{ id: "ministral-3:14b-32k-agent", provider: "ollama", context: 32768, output: 8192 },
732+
{ id: "qwen3-coder:32k-agent", provider: "ollama", context: 32768, output: 8192 }
733+
]
734+
},
735+
connected: ["ollama"]
736+
})
737+
738+
const result = await fetchAvailableModels(undefined, {
739+
connectedProviders: ["ollama"]
740+
})
741+
742+
expect(result.size).toBe(2)
743+
expect(result.has("ollama/ministral-3:14b-32k-agent")).toBe(true)
744+
expect(result.has("ollama/qwen3-coder:32k-agent")).toBe(true)
745+
})
746+
747+
it("should handle mixed string[] and object[] formats across providers", async () => {
748+
writeProviderModelsCache({
749+
models: {
750+
anthropic: ["claude-opus-4-5", "claude-sonnet-4-5"],
751+
ollama: [
752+
{ id: "ministral-3:14b-32k-agent", provider: "ollama" },
753+
{ id: "qwen3-coder:32k-agent", provider: "ollama" }
754+
]
755+
},
756+
connected: ["anthropic", "ollama"]
757+
})
758+
759+
const result = await fetchAvailableModels(undefined, {
760+
connectedProviders: ["anthropic", "ollama"]
761+
})
762+
763+
expect(result.size).toBe(4)
764+
expect(result.has("anthropic/claude-opus-4-5")).toBe(true)
765+
expect(result.has("anthropic/claude-sonnet-4-5")).toBe(true)
766+
expect(result.has("ollama/ministral-3:14b-32k-agent")).toBe(true)
767+
expect(result.has("ollama/qwen3-coder:32k-agent")).toBe(true)
768+
})
769+
770+
it("should skip invalid entries in object[] format", async () => {
771+
writeProviderModelsCache({
772+
models: {
773+
ollama: [
774+
{ id: "valid-model", provider: "ollama" },
775+
{ provider: "ollama" },
776+
{ id: "", provider: "ollama" },
777+
null,
778+
"string-model"
779+
]
780+
},
781+
connected: ["ollama"]
782+
})
783+
784+
const result = await fetchAvailableModels(undefined, {
785+
connectedProviders: ["ollama"]
786+
})
787+
788+
expect(result.size).toBe(2)
789+
expect(result.has("ollama/valid-model")).toBe(true)
790+
expect(result.has("ollama/string-model")).toBe(true)
791+
})
726792
})
727793

728794
describe("isModelAvailable", () => {

src/shared/model-availability.ts

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -187,16 +187,23 @@ export async function fetchAvailableModels(
187187
if (providerCount === 0) {
188188
log("[fetchAvailableModels] provider-models cache empty, falling back to models.json")
189189
} else {
190-
log("[fetchAvailableModels] using provider-models cache (whitelist-filtered)")
191-
192-
for (const [providerId, modelIds] of Object.entries(providerModelsCache.models)) {
193-
if (!connectedSet.has(providerId)) {
194-
continue
195-
}
196-
for (const modelId of modelIds) {
190+
log("[fetchAvailableModels] using provider-models cache (whitelist-filtered)")
191+
192+
for (const [providerId, modelIds] of Object.entries(providerModelsCache.models)) {
193+
if (!connectedSet.has(providerId)) {
194+
continue
195+
}
196+
for (const modelItem of modelIds) {
197+
// Handle both string[] (legacy) and object[] (with metadata) formats
198+
const modelId = typeof modelItem === 'string'
199+
? modelItem
200+
: (modelItem as any)?.id
201+
202+
if (modelId) {
197203
modelSet.add(`${providerId}/${modelId}`)
198204
}
199205
}
206+
}
200207

201208
log("[fetchAvailableModels] parsed from provider-models cache", {
202209
count: modelSet.size,

0 commit comments

Comments
 (0)