fix(provider): default custom models to image input

This commit is contained in:
Aiden Cline 2026-06-22 17:08:44 -05:00
commit 628e886808
8 changed files with 243 additions and 10 deletions

View file

@ -116,6 +116,34 @@ describe("Config", () => {
}),
)
it.effect("migrates v1 custom model capability overrides without replacing unspecified fields", () =>
Effect.sync(() => {
const migrated = ConfigMigrateV1.migrate({
provider: {
custom: {
models: {
default: { tool_call: true },
text: { attachment: false },
explicit: { modalities: { input: ["audio"], output: ["audio"] } },
},
},
},
})
expect(migrated.providers?.custom?.models?.default?.capabilities).toEqual({
tools: true,
})
expect(migrated.providers?.custom?.models?.text?.capabilities).toEqual({
input: ["text"],
output: ["text"],
})
expect(migrated.providers?.custom?.models?.explicit?.capabilities).toEqual({
input: ["audio"],
output: ["audio"],
})
}),
)
it.effect("migrates v1 command configuration", () =>
Effect.sync(() => {
expect(

View file

@ -3,6 +3,7 @@ import { Effect, Schema } from "effect"
import { Catalog } from "@opencode-ai/core/catalog"
import { Config } from "@opencode-ai/core/config"
import { ConfigProviderPlugin } from "@opencode-ai/core/config/plugin/provider"
import { ConfigMigrateV1 } from "@opencode-ai/core/v1/config/migrate"
import { Integration } from "@opencode-ai/core/integration"
import { ModelV2 } from "@opencode-ai/core/model"
import { PluginV2 } from "@opencode-ai/core/plugin"
@ -244,7 +245,9 @@ describe("ConfigProviderPlugin.Plugin", () => {
const provider = required(yield* catalog.provider.get(providerID))
const model = required(yield* catalog.model.get(providerID, modelID))
expect((yield* catalog.model.default())?.id).toBe(ModelV2.ID.make("default"))
const defaultModel = required(yield* catalog.model.default())
expect(defaultModel.id).toBe(ModelV2.ID.make("default"))
expect(defaultModel.capabilities).toEqual({ tools: false, input: ["text", "image"], output: ["text"] })
expect(provider.name).toBe("Renamed")
expect((yield* integrations.get(Integration.ID.make("custom")))?.methods).toContainEqual({
type: "env",
@ -271,4 +274,67 @@ describe("ConfigProviderPlugin.Plugin", () => {
}),
),
)
it.effect("lowers a migrated attachment override into modalities", () =>
Effect.gen(function* () {
const catalog = yield* Catalog.Service
const providerID = ProviderV2.ID.make("custom")
const modelID = ModelV2.ID.make("chat")
yield* catalog.transform((catalog) =>
catalog.model.update(providerID, modelID, (model) => {
model.capabilities = { tools: true, input: ["text", "image", "audio"], output: ["text", "audio"] }
}),
)
const config = Config.Service.of({
entries: () =>
Effect.succeed([
new Config.Document({
type: "document",
info: decode(
ConfigMigrateV1.migrate({
provider: { custom: { models: { chat: { attachment: false } } } },
}),
),
}),
]),
})
yield* addPlugin(config)
expect(required(yield* catalog.model.get(providerID, modelID)).capabilities).toEqual({
tools: true,
input: ["text"],
output: ["text"],
})
}),
)
it.effect("defaults a migrated custom model with attachment disabled to text only", () =>
Effect.gen(function* () {
const catalog = yield* Catalog.Service
const providerID = ProviderV2.ID.make("custom")
const modelID = ModelV2.ID.make("chat")
const config = Config.Service.of({
entries: () =>
Effect.succeed([
new Config.Document({
type: "document",
info: decode(
ConfigMigrateV1.migrate({
provider: { custom: { models: { chat: { attachment: false } } } },
}),
),
}),
]),
})
yield* addPlugin(config)
expect(required(yield* catalog.model.get(providerID, modelID)).capabilities).toEqual({
tools: false,
input: ["text"],
output: ["text"],
})
}),
)
})

View file

@ -21,3 +21,11 @@ describe("ModelV2.Ref", () => {
})
})
})
describe("ModelV2.Info", () => {
test("creates an empty model without modalities", () => {
const model = ModelV2.Info.empty(ProviderV2.ID.make("custom"), ModelV2.ID.make("model"))
expect(model.capabilities).toEqual({ tools: false, input: [], output: [] })
})
})