mirror of
https://github.com/anomalyco/opencode.git
synced 2026-07-21 18:26:09 +00:00
refactor(core): let plugins pin small models via catalog draft
Move copilot utility-model preference out of core selection into the github-copilot plugin by exposing catalog.model.small set/get/remove on the transform draft.
This commit is contained in:
@@ -21,6 +21,7 @@ export const Event = Catalog.Event
|
||||
type Data = {
|
||||
providers: Map<ProviderV2.ID, ProviderRecord>
|
||||
defaultModel?: DefaultModel
|
||||
smallModels: Map<ProviderV2.ID, ModelV2.ID>
|
||||
}
|
||||
|
||||
export type Draft = {
|
||||
@@ -38,6 +39,11 @@ export type Draft = {
|
||||
get: () => DefaultModel | undefined
|
||||
set: (providerID: ProviderV2.ID, modelID: ModelV2.ID) => void
|
||||
}
|
||||
small: {
|
||||
get: (providerID: ProviderV2.ID) => ModelV2.ID | undefined
|
||||
set: (providerID: ProviderV2.ID, modelID: ModelV2.ID) => void
|
||||
remove: (providerID: ProviderV2.ID) => void
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,7 +89,7 @@ const layer = Layer.effect(
|
||||
|
||||
const state = State.create<Data, Draft>({
|
||||
name: "catalog",
|
||||
initial: () => ({ providers: new Map() }),
|
||||
initial: () => ({ providers: new Map(), smallModels: new Map() }),
|
||||
draft: (draft) => {
|
||||
const result: Draft = {
|
||||
provider: {
|
||||
@@ -131,6 +137,15 @@ const layer = Layer.effect(
|
||||
draft.defaultModel = { providerID, modelID }
|
||||
},
|
||||
},
|
||||
small: {
|
||||
get: (providerID) => draft.smallModels.get(providerID),
|
||||
set: (providerID, modelID) => {
|
||||
draft.smallModels.set(providerID, modelID)
|
||||
},
|
||||
remove: (providerID) => {
|
||||
draft.smallModels.delete(providerID)
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
return result
|
||||
@@ -217,13 +232,11 @@ const layer = Layer.effect(
|
||||
return
|
||||
}
|
||||
|
||||
// GitHub exposes utility models for title generation without including them in the picker.
|
||||
// They remain in the catalog with enabled=false, so prefer them before family selection.
|
||||
if (providerID.startsWith("github-copilot")) {
|
||||
for (const id of COPILOT_UTILITY_MODELS) {
|
||||
const model = record.models.get(ModelV2.ID.make(id))
|
||||
if (model?.status === "active") return projectModel(model, provider)
|
||||
}
|
||||
// Plugin transforms may pin a small model (for example picker-disabled utility models).
|
||||
const configured = state.get().smallModels.get(providerID)
|
||||
if (configured) {
|
||||
const model = record.models.get(configured)
|
||||
if (model?.status === "active") return projectModel(model, provider)
|
||||
}
|
||||
|
||||
const priority = providerID.startsWith("opencode")
|
||||
@@ -272,6 +285,5 @@ const layer = Layer.effect(
|
||||
)
|
||||
|
||||
const SMALL_MODEL_FAMILY_PRIORITY = ["gemini-flash", "gpt-nano", "claude-haiku"]
|
||||
const COPILOT_UTILITY_MODELS = ["gpt-5.4-nano", "gpt-4.1", "gpt-4o", "gpt-4o-mini"]
|
||||
|
||||
export const node = makeLocationNode({ service: Service, layer, deps: [EventV2.node, Integration.node] })
|
||||
|
||||
@@ -148,6 +148,12 @@ export const make = Effect.fn("PluginHost.make")(function* (plugin: PluginV2.Int
|
||||
set: (providerID, modelID) =>
|
||||
draft.model.default.set(ProviderV2.ID.make(providerID), ModelV2.ID.make(modelID)),
|
||||
},
|
||||
small: {
|
||||
get: (providerID) => draft.model.small.get(ProviderV2.ID.make(providerID)),
|
||||
set: (providerID, modelID) =>
|
||||
draft.model.small.set(ProviderV2.ID.make(providerID), ModelV2.ID.make(modelID)),
|
||||
remove: (providerID) => draft.model.small.remove(ProviderV2.ID.make(providerID)),
|
||||
},
|
||||
},
|
||||
})
|
||||
}),
|
||||
|
||||
@@ -119,6 +119,8 @@ function shouldUseResponses(modelID: string) {
|
||||
return Number(match[1]) >= 5 && !modelID.startsWith("gpt-5-mini")
|
||||
}
|
||||
|
||||
const UTILITY_MODELS = ["gpt-5.4-nano", "gpt-4.1", "gpt-4o", "gpt-4o-mini"]
|
||||
|
||||
export const GithubCopilotPlugin = define({
|
||||
id: "opencode.provider.github-copilot",
|
||||
effect: Effect.fn(function* (ctx) {
|
||||
@@ -192,6 +194,13 @@ export const GithubCopilotPlugin = define({
|
||||
model.enabled = false
|
||||
})
|
||||
}
|
||||
// GitHub exposes utility models for title generation without including them in the picker.
|
||||
const utility = UTILITY_MODELS.map((id) => ModelV2.ID.make(id)).find((id) => {
|
||||
const model = evt.model.get(item.provider.id, id)
|
||||
return model?.status === "active"
|
||||
})
|
||||
if (utility) evt.model.small.set(item.provider.id, utility)
|
||||
else evt.model.small.remove(item.provider.id)
|
||||
})
|
||||
const refresh = () => loading.withPermit(load().pipe(Effect.andThen(ctx.catalog.reload())))
|
||||
yield* events.subscribe(Integration.Event.ConnectionUpdated).pipe(
|
||||
|
||||
@@ -393,7 +393,7 @@ describe("CatalogV2", () => {
|
||||
}),
|
||||
)
|
||||
|
||||
it.effect("small model prefers picker-disabled copilot utility models", () =>
|
||||
it.effect("small model prefers a plugin-configured small model override", () =>
|
||||
Effect.gen(function* () {
|
||||
const catalog = yield* Catalog.Service
|
||||
const providerID = ProviderV2.ID.githubCopilot
|
||||
@@ -403,14 +403,11 @@ describe("CatalogV2", () => {
|
||||
model.family = ModelV2.Family.make("gpt-mini")
|
||||
model.time.released = Date.now()
|
||||
})
|
||||
catalog.model.update(providerID, ModelV2.ID.make("gpt-4o-mini"), (model) => {
|
||||
model.enabled = false
|
||||
model.time.released = Date.now() - 1000
|
||||
})
|
||||
catalog.model.update(providerID, ModelV2.ID.make("gpt-5.4-nano"), (model) => {
|
||||
model.enabled = false
|
||||
model.time.released = Date.now() - 2000
|
||||
})
|
||||
catalog.model.small.set(providerID, ModelV2.ID.make("gpt-5.4-nano"))
|
||||
})
|
||||
|
||||
expect((yield* catalog.model.small(providerID))?.id).toBe(ModelV2.ID.make("gpt-5.4-nano"))
|
||||
|
||||
@@ -178,6 +178,12 @@ export function catalogHost(catalog: Catalog.Interface): PluginContext["catalog"
|
||||
set: (providerID, modelID) =>
|
||||
draft.model.default.set(ProviderV2.ID.make(providerID), ModelV2.ID.make(modelID)),
|
||||
},
|
||||
small: {
|
||||
get: (providerID) => draft.model.small.get(ProviderV2.ID.make(providerID)),
|
||||
set: (providerID, modelID) =>
|
||||
draft.model.small.set(ProviderV2.ID.make(providerID), ModelV2.ID.make(modelID)),
|
||||
remove: (providerID) => draft.model.small.remove(ProviderV2.ID.make(providerID)),
|
||||
},
|
||||
},
|
||||
}),
|
||||
),
|
||||
|
||||
@@ -23,6 +23,11 @@ export interface CatalogDraft {
|
||||
get(): { providerID: string; modelID: string } | undefined
|
||||
set(providerID: string, modelID: string): void
|
||||
}
|
||||
readonly small: {
|
||||
get(providerID: string): string | undefined
|
||||
set(providerID: string, modelID: string): void
|
||||
remove(providerID: string): void
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user