feat(config): add model variant support

Allow optional model variant config for agents and categories.
Propagate category variants into task model payloads so
category-driven runs inherit provider-specific variants.

Closes: #647
This commit is contained in:
Jason Kölker
2026-01-10 21:44:20 +00:00
parent f9fce50144
commit 2b8853cbac
18 changed files with 452 additions and 12 deletions
+1
View File
@@ -76,6 +76,7 @@ export type AgentName = BuiltinAgentName
export type AgentOverrideConfig = Partial<AgentConfig> & {
prompt_append?: string
variant?: string
}
export type AgentOverrides = Partial<Record<OverridableAgentName, AgentOverrideConfig>>
+25
View File
@@ -127,6 +127,31 @@ describe("buildAgent with category and skills", () => {
expect(agent.temperature).toBe(0.7)
})
test("agent with category inherits variant", () => {
// #given
const source = {
"test-agent": () =>
({
description: "Test agent",
category: "custom-category",
}) as AgentConfig,
}
const categories = {
"custom-category": {
model: "openai/gpt-5.2",
variant: "xhigh",
},
}
// #when
const agent = buildAgent(source["test-agent"], undefined, categories)
// #then
expect(agent.model).toBe("openai/gpt-5.2")
expect(agent.variant).toBe("xhigh")
})
test("agent with skills has content prepended to prompt", () => {
// #given
const source = {
+21 -5
View File
@@ -1,5 +1,6 @@
import type { AgentConfig } from "@opencode-ai/sdk"
import type { BuiltinAgentName, AgentOverrideConfig, AgentOverrides, AgentFactory, AgentPromptMetadata } from "./types"
import type { CategoriesConfig, CategoryConfig } from "../config/schema"
import { createSisyphusAgent } from "./sisyphus"
import { createOracleAgent, ORACLE_PROMPT_METADATA } from "./oracle"
import { createLibrarianAgent, LIBRARIAN_PROMPT_METADATA } from "./librarian"
@@ -47,12 +48,19 @@ function isFactory(source: AgentSource): source is AgentFactory {
return typeof source === "function"
}
export function buildAgent(source: AgentSource, model?: string): AgentConfig {
export function buildAgent(
source: AgentSource,
model?: string,
categories?: CategoriesConfig
): AgentConfig {
const base = isFactory(source) ? source(model) : source
const categoryConfigs: Record<string, CategoryConfig> = categories
? { ...DEFAULT_CATEGORIES, ...categories }
: DEFAULT_CATEGORIES
const agentWithCategory = base as AgentConfig & { category?: string; skills?: string[] }
const agentWithCategory = base as AgentConfig & { category?: string; skills?: string[]; variant?: string }
if (agentWithCategory.category) {
const categoryConfig = DEFAULT_CATEGORIES[agentWithCategory.category]
const categoryConfig = categoryConfigs[agentWithCategory.category]
if (categoryConfig) {
if (!base.model) {
base.model = categoryConfig.model
@@ -60,6 +68,9 @@ export function buildAgent(source: AgentSource, model?: string): AgentConfig {
if (base.temperature === undefined && categoryConfig.temperature !== undefined) {
base.temperature = categoryConfig.temperature
}
if (base.variant === undefined && categoryConfig.variant !== undefined) {
base.variant = categoryConfig.variant
}
}
}
@@ -118,11 +129,16 @@ export function createBuiltinAgents(
disabledAgents: BuiltinAgentName[] = [],
agentOverrides: AgentOverrides = {},
directory?: string,
systemDefaultModel?: string
systemDefaultModel?: string,
categories?: CategoriesConfig
): Record<string, AgentConfig> {
const result: Record<string, AgentConfig> = {}
const availableAgents: AvailableAgent[] = []
const mergedCategories = categories
? { ...DEFAULT_CATEGORIES, ...categories }
: DEFAULT_CATEGORIES
for (const [name, source] of Object.entries(agentSources)) {
const agentName = name as BuiltinAgentName
@@ -133,7 +149,7 @@ export function createBuiltinAgents(
const override = agentOverrides[agentName]
const model = override?.model
let config = buildAgent(source, model)
let config = buildAgent(source, model, mergedCategories)
if (agentName === "librarian" && directory && config.prompt) {
const envContext = createEnvContext()