diff --git a/packages/opencode/src/agent/agent.ts b/packages/opencode/src/agent/agent.ts index 53c655d1b373..208a39f55502 100644 --- a/packages/opencode/src/agent/agent.ts +++ b/packages/opencode/src/agent/agent.ts @@ -45,6 +45,19 @@ export namespace Agent { prompt: z.string().optional(), options: z.record(z.string(), z.any()), steps: z.number().int().positive().optional(), + modelTiers: z + .object({ + quick: z + .object({ modelID: ModelID.zod, providerID: ProviderID.zod, variant: z.string().optional() }) + .optional(), + standard: z + .object({ modelID: ModelID.zod, providerID: ProviderID.zod, variant: z.string().optional() }) + .optional(), + advanced: z + .object({ modelID: ModelID.zod, providerID: ProviderID.zod, variant: z.string().optional() }) + .optional(), + }) + .optional(), }) .meta({ ref: "Agent", @@ -259,6 +272,16 @@ export namespace Agent { item.steps = value.steps ?? item.steps item.options = mergeDeep(item.options, value.options ?? {}) item.permission = Permission.merge(item.permission, Permission.fromConfig(value.permission ?? {})) + + if (value.model_tiers) { + const modelTiers: Partial> = {} + + for (const [tier, tierConfig] of Object.entries(value.model_tiers)) { + modelTiers[tier as Provider.ModelTier] = Provider.parseTierConfig(tierConfig) + } + + item.modelTiers = modelTiers + } } // Ensure Truncate.GLOB is allowed unless explicitly configured @@ -415,4 +438,22 @@ export namespace Agent { export async function generate(input: { description: string; model?: { providerID: ProviderID; modelID: ModelID } }) { return runPromise((svc) => svc.generate(input)) } + + export async function resolveModel( + agent: Info, + tier?: Provider.ModelTier, + parentSessionModel?: Provider.ModelReference, + ): Promise { + const config = await Config.get() + + if (agent?.model) return agent.model + + if (tier) { + if (agent?.modelTiers?.[tier]) return agent.modelTiers[tier] + + if (config?.model_tiers?.[tier]) return Provider.parseTierConfig(config?.model_tiers?.[tier]) + } + + return parentSessionModel ?? (await Provider.defaultModel()) + } } diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts index 3cbb3416233d..9872779a8b2a 100644 --- a/packages/opencode/src/config/config.ts +++ b/packages/opencode/src/config/config.ts @@ -604,6 +604,14 @@ export namespace Config { .describe("Maximum number of agentic iterations before forcing text-only response"), maxSteps: z.number().int().positive().optional().describe("@deprecated Use 'steps' field instead."), permission: Permission.optional(), + model_tiers: z + .object({ + quick: z.object({ model: ModelId, variant: z.string().optional() }).optional(), + standard: z.object({ model: ModelId, variant: z.string().optional() }).optional(), + advanced: z.object({ model: ModelId, variant: z.string().optional() }).optional(), + }) + .optional() + .describe("Model tier mappings for this agent"), }) .catchall(z.any()) .transform((agent, ctx) => { @@ -624,6 +632,7 @@ export namespace Config { "permission", "disable", "tools", + "model_tiers", ]) // Extract unknown properties into options @@ -945,6 +954,14 @@ export namespace Config { small_model: ModelId.describe( "Small model to use for tasks like title generation in the format of provider/model", ).optional(), + model_tiers: z + .object({ + quick: z.object({ model: ModelId, variant: z.string().optional() }).optional(), + standard: z.object({ model: ModelId, variant: z.string().optional() }).optional(), + advanced: z.object({ model: ModelId, variant: z.string().optional() }).optional(), + }) + .optional() + .describe("Global model tier mappings"), default_agent: z .string() .optional() diff --git a/packages/opencode/src/provider/provider.ts b/packages/opencode/src/provider/provider.ts index 7fb3166284be..18c4344ddeb7 100644 --- a/packages/opencode/src/provider/provider.ts +++ b/packages/opencode/src/provider/provider.ts @@ -1518,6 +1518,10 @@ export namespace Provider { } } + export type ModelReference = { modelID: ModelID; providerID: ProviderID; variant?: string } + + export type ModelTier = "quick" | "standard" | "advanced" + export function parseModel(model: string) { const [providerID, ...rest] = model.split("/") return { @@ -1526,6 +1530,20 @@ export namespace Provider { } } + export const ModelTierConfig = z.object({ + model: z.string().describe("Model ID in format provider/model"), + variant: z.string().optional().describe("Variant for this tier"), + }) + + export type ModelTierConfig = z.infer + + export function parseTierConfig(tierConfig: ModelTierConfig): ModelReference { + return { + ...parseModel(tierConfig.model), + variant: tierConfig.variant, + } + } + export const ModelNotFoundError = NamedError.create( "ProviderModelNotFoundError", z.object({ diff --git a/packages/opencode/src/tool/task.ts b/packages/opencode/src/tool/task.ts index af130a70d919..951a4007fa9e 100644 --- a/packages/opencode/src/tool/task.ts +++ b/packages/opencode/src/tool/task.ts @@ -23,6 +23,12 @@ const parameters = z.object({ ) .optional(), command: z.string().describe("The command that triggered this task").optional(), + model_tier: z + .enum(["quick", "standard", "advanced"]) + .describe( + "Model tier for this subagent: 'quick' for fast and inexpensive model for simple tasks, 'standard' for balanced model for most tasks, 'advanced' for powerful model for complex tasks", + ) + .optional(), }) export const TaskTool = Tool.define("task", async (ctx) => { @@ -105,10 +111,10 @@ export const TaskTool = Tool.define("task", async (ctx) => { const msg = await MessageV2.get({ sessionID: ctx.sessionID, messageID: ctx.messageID }) if (msg.info.role !== "assistant") throw new Error("Not an assistant message") - const model = agent.model ?? { + const model = await Agent.resolveModel(agent, params.model_tier, { modelID: msg.info.modelID, providerID: msg.info.providerID, - } + }) ctx.metadata({ title: params.description, @@ -135,6 +141,7 @@ export const TaskTool = Tool.define("task", async (ctx) => { providerID: model.providerID, }, agent: agent.name, + variant: model.variant, tools: { ...(hasTodoWritePermission ? {} : { todowrite: false }), ...(hasTaskPermission ? {} : { task: false }), diff --git a/packages/opencode/src/tool/task.txt b/packages/opencode/src/tool/task.txt index 585cce8f9d0a..94222cbd1e48 100644 --- a/packages/opencode/src/tool/task.txt +++ b/packages/opencode/src/tool/task.txt @@ -5,8 +5,15 @@ Available agent types and the tools they have access to: When using the Task tool, you must specify a subagent_type parameter to select which agent type to use. -When to use the Task tool: -- When you are instructed to execute custom slash commands. Use the Task tool with the slash command invocation as the entire prompt. The slash command can take arguments. For example: Task(description="Check the file", prompt="/check-file path/to/file.py") +You can also specify a model_tier parameter to control which model the subagent uses and also optimize cost and performance. +- `quick` for fast and inexpensive model for simple tasks +- `standard` for balanced model for most tasks +- `advanced` for powerful model for complex tasks. + +Use this when spawning subagents in parallel with varying complexity. For example: Task(subagent_type="file-checker", description="Check the file", prompt="/check-file path/to/file.py", model_tier="quick") and Task(subagent_type="data-analyzer", description="Analyze the data", prompt="/analyze-data path/to/data.csv", model_tier="advanced"). If not specified, the agent's configured model or parent session's model is used. + +When to use Task tool: +- When you are instructed to execute custom slash commands. Use the Task tool with the slash command invocation as the entire prompt. The slash command can take arguments. For example: Task(subagent_type="file-checker", description="Check the file", prompt="/check-file path/to/file.py") When NOT to use the Task tool: - If you want to read a specific file path, use the Read or Glob tool instead of the Task tool, to find the match more quickly @@ -15,7 +22,7 @@ When NOT to use the Task tool: - Other tasks that are not related to the agent descriptions above -Usage notes: +Usage Notes: 1. Launch multiple agents concurrently whenever possible, to maximize performance; to do that, use a single message with multiple tool uses 2. When the agent is done, it will return a single message back to you. The result returned by the agent is not visible to the user. To show the user the result, you should send a text message back to the user with a concise summary of the result. The output includes a task_id you can reuse later to continue the same subagent session. 3. Each agent invocation starts with a fresh context unless you provide task_id to resume the same subagent session (which continues with its previous messages and tool outputs). When starting fresh, your prompt should contain a highly detailed task description for the agent to perform autonomously and you should specify exactly what information the agent should return back to you in its final and only message to you. @@ -28,7 +35,7 @@ Example usage (NOTE: The agents below are fictional examples for illustration on "code-reviewer": use this agent after you are done writing a significant piece of code "greeting-responder": use this agent when to respond to user greetings with a friendly joke - + user: "Please write a function that checks if a number is prime" diff --git a/packages/opencode/test/agent/agent.test.ts b/packages/opencode/test/agent/agent.test.ts index 98a0fd4c6ec6..d8b8d513a157 100644 --- a/packages/opencode/test/agent/agent.test.ts +++ b/packages/opencode/test/agent/agent.test.ts @@ -4,6 +4,7 @@ import { tmpdir } from "../fixture/fixture" import { Instance } from "../../src/project/instance" import { Agent } from "../../src/agent/agent" import { Permission } from "../../src/permission" +import { ModelID, ProviderID } from "../../src/provider/schema" // Helper to evaluate permission for a tool with wildcard pattern function evalPerm(agent: Agent.Info | undefined, permission: string): Permission.Action | undefined { @@ -592,6 +593,125 @@ description: Permission skill. } }) +test("resolveModel uses agent model as highest priority", async () => { + await using tmp = await tmpdir({ + config: { + agent: { + custom: { + model: "openai/gpt-5", + model_tiers: { + quick: { model: "anthropic/claude-haiku-4-5" }, + }, + }, + }, + }, + }) + + await Instance.provide({ + directory: tmp.path, + fn: async () => { + const agent = await Agent.get("custom") + const model = await Agent.resolveModel(agent, "quick", undefined) + + expect(model.providerID).toBe(ProviderID.make("openai")) + expect(model.modelID).toBe(ModelID.make("gpt-5")) + }, + }) +}) + +test("resolveModel falls back to parent session model when no tier", async () => { + await using tmp = await tmpdir() + await Instance.provide({ + directory: tmp.path, + fn: async () => { + const agent = await Agent.get("build") + const parentModel = { modelID: ModelID.make("gpt-5"), providerID: ProviderID.make("openai") } + const model = await Agent.resolveModel(agent!, undefined, parentModel) + + expect(model).toEqual(parentModel) + }, + }) +}) + +test("resolveModel hierarchy: agent tier > global tier > parent session", async () => { + await using tmp = await tmpdir({ + config: { + model_tiers: { + quick: { model: "anthropic/claude-haiku-4-5" }, + }, + agent: { + custom: { + model_tiers: { + quick: { model: "anthropic/claude-sonnet-4-5" }, + }, + }, + }, + }, + }) + + await Instance.provide({ + directory: tmp.path, + fn: async () => { + const agent = await Agent.get("custom") + const parentModel = { modelID: ModelID.make("gpt-5"), providerID: ProviderID.make("openai") } + const model = await Agent.resolveModel(agent!, "quick", parentModel) + + expect(model.providerID).toBe(ProviderID.make("anthropic")) + expect(model.modelID).toBe(ModelID.make("claude-sonnet-4-5")) + }, + }) +}) + +test("resolveModel uses global tier when agent tier not defined", async () => { + await using tmp = await tmpdir({ + config: { + model_tiers: { + quick: { model: "anthropic/claude-haiku-4-5" }, + }, + agent: { + custom: { + model_tiers: { + standard: { model: "anthropic/claude-sonnet-4-5" }, + }, + }, + }, + }, + }) + + await Instance.provide({ + directory: tmp.path, + fn: async () => { + const agent = await Agent.get("custom") + const parentModel = { modelID: ModelID.make("gpt-5"), providerID: ProviderID.make("openai") } + const model = await Agent.resolveModel(agent!, "quick", parentModel) + + expect(model.providerID).toBe(ProviderID.make("anthropic")) + expect(model.modelID).toBe(ModelID.make("claude-haiku-4-5")) + }, + }) +}) + +test("resolveModel falls back to parent session when tier not configured", async () => { + await using tmp = await tmpdir({ + config: { + agent: { + custom: {}, + }, + }, + }) + + await Instance.provide({ + directory: tmp.path, + fn: async () => { + const agent = await Agent.get("custom") + const parentModel = { modelID: ModelID.make("gpt-5"), providerID: ProviderID.make("openai") } + const model = await Agent.resolveModel(agent!, "quick", parentModel) + + expect(model).toEqual(parentModel) + }, + }) +}) + test("defaultAgent returns build when no default_agent config", async () => { await using tmp = await tmpdir() await Instance.provide({ @@ -715,3 +835,105 @@ test("defaultAgent throws when all primary agents are disabled", async () => { }, }) }) + +test("resolveModel extracts variant from agent tier", async () => { + await using tmp = await tmpdir({ + config: { + agent: { + custom: { + model_tiers: { + quick: { model: "openai/gpt-4o-mini", variant: "minimal" }, + }, + }, + }, + }, + }) + + await Instance.provide({ + directory: tmp.path, + fn: async () => { + const agent = await Agent.get("custom") + const model = await Agent.resolveModel(agent, "quick", undefined) + + expect(model.modelID).toBe(ModelID.make("gpt-4o-mini")) + expect(model.providerID).toBe(ProviderID.make("openai")) + expect(model.variant).toBe("minimal") + }, + }) +}) + +test("resolveModel extracts variant from global tier", async () => { + await using tmp = await tmpdir({ + config: { + model_tiers: { + quick: { model: "anthropic/claude-haiku-4-5", variant: "minimal" }, + }, + agent: { + custom: {}, + }, + }, + }) + + await Instance.provide({ + directory: tmp.path, + fn: async () => { + const agent = await Agent.get("custom") + const model = await Agent.resolveModel(agent, "quick", undefined) + + expect(model.modelID).toBe(ModelID.make("claude-haiku-4-5")) + expect(model.providerID).toBe(ProviderID.make("anthropic")) + expect(model.variant).toBe("minimal") + }, + }) +}) + +test("resolveModel uses agent tier variant over global tier variant", async () => { + await using tmp = await tmpdir({ + config: { + model_tiers: { + quick: { model: "openai/gpt-4", variant: "global-variant" }, + }, + agent: { + custom: { + model_tiers: { + quick: { model: "anthropic/claude-haiku", variant: "agent-variant" }, + }, + }, + }, + }, + }) + + await Instance.provide({ + directory: tmp.path, + fn: async () => { + const agent = await Agent.get("custom") + const model = await Agent.resolveModel(agent, "quick", undefined) + + expect(model.providerID).toBe(ProviderID.make("anthropic")) + expect(model.variant).toBe("agent-variant") + }, + }) +}) + +test("resolveModel returns no variant when using explicit agent model", async () => { + await using tmp = await tmpdir({ + config: { + agent: { + custom: { + model: "openai/gpt-4o", + }, + }, + }, + }) + + await Instance.provide({ + directory: tmp.path, + fn: async () => { + const agent = await Agent.get("custom") + const model = await Agent.resolveModel(agent, "quick", undefined) + + expect(model.modelID).toBe(ModelID.make("gpt-4o")) + expect(model.variant).toBeUndefined() + }, + }) +}) diff --git a/packages/opencode/test/config/config.test.ts b/packages/opencode/test/config/config.test.ts index ea0a54520004..0fe58a923168 100644 --- a/packages/opencode/test/config/config.test.ts +++ b/packages/opencode/test/config/config.test.ts @@ -7,6 +7,8 @@ import { Auth } from "../../src/auth" import { AccessToken, Account, AccountID, OrgID } from "../../src/account" import { AppFileSystem } from "../../src/filesystem" import { provideTmpdirInstance } from "../fixture/fixture" +import { Provider } from "../../src/provider/provider" +import { ModelID, ProviderID } from "../../src/provider/schema" import { tmpdir } from "../fixture/fixture" import * as CrossSpawnSpawner from "../../src/effect/cross-spawn-spawner" @@ -2185,3 +2187,115 @@ describe("OPENCODE_CONFIG_CONTENT token substitution", () => { } }) }) + +test("global model_tiers config is parsed correctly", async () => { + await using tmp = await tmpdir({ + config: { + model_tiers: { + quick: { model: "anthropic/claude-haiku-4-5" }, + standard: { model: "anthropic/claude-sonnet-4-5" }, + advanced: { model: "anthropic/claude-opus-4-5" }, + }, + }, + }) + + await Instance.provide({ + directory: tmp.path, + fn: async () => { + const config = await Config.get() + + expect(config.model_tiers).toBeDefined() + expect(config.model_tiers?.quick?.model).toBe("anthropic/claude-haiku-4-5") + expect(config.model_tiers?.standard?.model).toBe("anthropic/claude-sonnet-4-5") + expect(config.model_tiers?.advanced?.model).toBe("anthropic/claude-opus-4-5") + }, + }) +}) + +test("agent-level model_tiers config is parsed correctly", async () => { + await using tmp = await tmpdir({ + config: { + model_tiers: { + quick: { model: "anthropic/claude-haiku-4-5" }, + }, + agent: { + code_review: { + model_tiers: { + quick: { model: "anthropic/claude-sonnet-4-5" }, + }, + }, + }, + }, + }) + + await Instance.provide({ + directory: tmp.path, + fn: async () => { + const config = await Config.get() + + expect(config.agent?.code_review?.model_tiers?.quick?.model).toBe("anthropic/claude-sonnet-4-5") + }, + }) +}) + +test("global model_tiers config parses variant correctly", async () => { + await using tmp = await tmpdir({ + config: { + model_tiers: { + quick: { model: "anthropic/claude-haiku-4-5", variant: "minimal" }, + standard: { model: "anthropic/claude-sonnet-4-5", variant: "high" }, + advanced: { model: "anthropic/claude-opus-4-5", variant: "max" }, + }, + }, + }) + + await Instance.provide({ + directory: tmp.path, + fn: async () => { + const config = await Config.get() + + expect(config.model_tiers?.quick?.variant).toBe("minimal") + expect(config.model_tiers?.standard?.variant).toBe("high") + expect(config.model_tiers?.advanced?.variant).toBe("max") + }, + }) +}) + +test("agent-level model_tiers config parses variant correctly", async () => { + await using tmp = await tmpdir({ + config: { + agent: { + code_review: { + model_tiers: { + quick: { model: "anthropic/claude-sonnet-4-5", variant: "high" }, + }, + variant: "medium", + }, + }, + }, + }) + + await Instance.provide({ + directory: tmp.path, + fn: async () => { + const config = await Config.get() + + expect(config.agent?.code_review?.model_tiers?.quick?.variant).toBe("high") + expect(config.agent?.code_review?.variant).toBe("medium") + }, + }) +}) + +test("parseTierConfig extracts variant from tier config", () => { + const result = Provider.parseTierConfig({ model: "openai/gpt-4o-mini", variant: "minimal" }) + expect(result.modelID).toBe(ModelID.make("gpt-4o-mini")) + expect(result.providerID).toBe(ProviderID.make("openai")) + expect(result.variant).toBe("minimal") +}) + +test("parseTierConfig works without variant", () => { + const result = Provider.parseTierConfig({ model: "openai/gpt-4o-mini" }) + expect(result.modelID).toBe(ModelID.make("gpt-4o-mini")) + expect(result.providerID).toBe(ProviderID.make("openai")) + expect(result.variant).toBeUndefined() +}) diff --git a/packages/opencode/test/tool/task.test.ts b/packages/opencode/test/tool/task.test.ts index aae48a30ab3f..09f807457d5f 100644 --- a/packages/opencode/test/tool/task.test.ts +++ b/packages/opencode/test/tool/task.test.ts @@ -1,12 +1,12 @@ -import { afterEach, describe, expect, test } from "bun:test" -import { Agent } from "../../src/agent/agent" -import { Instance } from "../../src/project/instance" -import { TaskTool } from "../../src/tool/task" -import { tmpdir } from "../fixture/fixture" +import { afterEach, describe, expect, test } from "bun:test"; +import { Agent } from "../../src/agent/agent"; +import { Instance } from "../../src/project/instance"; +import { TaskTool } from "../../src/tool/task"; +import { tmpdir } from "../fixture/fixture"; afterEach(async () => { - await Instance.disposeAll() -}) + await Instance.disposeAll(); +}); describe("tool.task", () => { test("description sorts subagents by name and is stable across calls", async () => { @@ -23,27 +23,79 @@ describe("tool.task", () => { }, }, }, - }) + }); await Instance.provide({ directory: tmp.path, fn: async () => { - const build = await Agent.get("build") - const first = await TaskTool.init({ agent: build }) - const second = await TaskTool.init({ agent: build }) + const build = await Agent.get("build"); + const first = await TaskTool.init({ agent: build }); + const second = await TaskTool.init({ agent: build }); - expect(first.description).toBe(second.description) + expect(first.description).toBe(second.description); - const alpha = first.description.indexOf("- alpha: Alpha agent") - const explore = first.description.indexOf("- explore:") - const general = first.description.indexOf("- general:") - const zebra = first.description.indexOf("- zebra: Zebra agent") + const alpha = first.description.indexOf("- alpha: Alpha agent"); + const explore = first.description.indexOf("- explore:"); + const general = first.description.indexOf("- general:"); + const zebra = first.description.indexOf("- zebra: Zebra agent"); - expect(alpha).toBeGreaterThan(-1) - expect(explore).toBeGreaterThan(alpha) - expect(general).toBeGreaterThan(explore) - expect(zebra).toBeGreaterThan(general) + expect(alpha).toBeGreaterThan(-1); + expect(explore).toBeGreaterThan(alpha); + expect(general).toBeGreaterThan(explore); + expect(zebra).toBeGreaterThan(general); }, - }) - }) -}) + }); + }); +}); + +test("accepts valid model_tier values", async () => { + await using tmp = await tmpdir(); + await Instance.provide({ + directory: tmp.path, + fn: async () => { + const TaskInfo = await TaskTool.init(); + const schema = TaskInfo.parameters; + const result = schema.safeParse({ + description: "Test", + prompt: "Test prompt", + subagent_type: "build", + model_tier: "quick", + }); + + expect(result.success).toBe(true); + }, + }); +}); + +test("rejects invalid model_tier values", async () => { + await using tmp = await tmpdir(); + await Instance.provide({ + directory: tmp.path, + fn: async () => { + const TaskInfo = await TaskTool.init(); + const schema = TaskInfo.parameters; + const result = schema.safeParse({ + description: "Test", + prompt: "Test prompt", + subagent_type: "build", + model_tier: "invalid", + }); + + expect(result.success).toBe(false); + }, + }); +}); + +test("model_tier is optional", async () => { + await using tmp = await tmpdir(); + await Instance.provide({ + directory: tmp.path, + fn: async () => { + const TaskInfo = await TaskTool.init(); + const schema = TaskInfo.parameters; + const result = schema.safeParse({ description: "Test", prompt: "Test prompt", subagent_type: "build" }); + + expect(result.success).toBe(true); + }, + }); +}); diff --git a/packages/sdk/js/src/v2/gen/types.gen.ts b/packages/sdk/js/src/v2/gen/types.gen.ts index 318b8907a91d..0efbaf82cdaa 100644 --- a/packages/sdk/js/src/v2/gen/types.gen.ts +++ b/packages/sdk/js/src/v2/gen/types.gen.ts @@ -1218,6 +1218,23 @@ export type AgentConfig = { */ maxSteps?: number permission?: PermissionConfig + /** + * Model tier mappings for this agent + */ + model_tiers?: { + quick?: { + model: string + variant?: string + } + standard?: { + model: string + variant?: string + } + advanced?: { + model: string + variant?: string + } + } [key: string]: | unknown | string @@ -1242,6 +1259,20 @@ export type AgentConfig = { | "info" | number | PermissionConfig + | { + quick?: { + model: string + variant?: string + } + standard?: { + model: string + variant?: string + } + advanced?: { + model: string + variant?: string + } + } | undefined } @@ -1488,6 +1519,23 @@ export type Config = { * Small model to use for tasks like title generation in the format of provider/model */ small_model?: string + /** + * Global model tier mappings + */ + model_tiers?: { + quick?: { + model: string + variant?: string + } + standard?: { + model: string + variant?: string + } + advanced?: { + model: string + variant?: string + } + } /** * Default agent to use when none is specified. Must be a primary agent. Falls back to 'build' if not set or if the specified agent is invalid. */ @@ -2036,6 +2084,23 @@ export type Agent = { [key: string]: unknown } steps?: number + modelTiers?: { + quick?: { + modelID: string + providerID: string + variant?: string + } + standard?: { + modelID: string + providerID: string + variant?: string + } + advanced?: { + modelID: string + providerID: string + variant?: string + } + } } export type LspStatus = { diff --git a/packages/sdk/openapi.json b/packages/sdk/openapi.json index 5362e1daacae..84f49df49a6f 100644 --- a/packages/sdk/openapi.json +++ b/packages/sdk/openapi.json @@ -10521,6 +10521,48 @@ }, "permission": { "$ref": "#/components/schemas/PermissionConfig" + }, + "model_tiers": { + "description": "Model tier mappings for this agent", + "type": "object", + "properties": { + "quick": { + "type": "object", + "properties": { + "model": { + "type": "string" + }, + "variant": { + "type": "string" + } + }, + "required": ["model"] + }, + "standard": { + "type": "object", + "properties": { + "model": { + "type": "string" + }, + "variant": { + "type": "string" + } + }, + "required": ["model"] + }, + "advanced": { + "type": "object", + "properties": { + "model": { + "type": "string" + }, + "variant": { + "type": "string" + } + }, + "required": ["model"] + } + } } }, "additionalProperties": {} @@ -11033,6 +11075,48 @@ "description": "Small model to use for tasks like title generation in the format of provider/model", "type": "string" }, + "model_tiers": { + "description": "Global model tier mappings", + "type": "object", + "properties": { + "quick": { + "type": "object", + "properties": { + "model": { + "type": "string" + }, + "variant": { + "type": "string" + } + }, + "required": ["model"] + }, + "standard": { + "type": "object", + "properties": { + "model": { + "type": "string" + }, + "variant": { + "type": "string" + } + }, + "required": ["model"] + }, + "advanced": { + "type": "object", + "properties": { + "model": { + "type": "string" + }, + "variant": { + "type": "string" + } + }, + "required": ["model"] + } + } + }, "default_agent": { "description": "Default agent to use when none is specified. Must be a primary agent. Falls back to 'build' if not set or if the specified agent is invalid.", "type": "string" @@ -12604,6 +12688,56 @@ "type": "integer", "exclusiveMinimum": 0, "maximum": 9007199254740991 + }, + "modelTiers": { + "type": "object", + "properties": { + "quick": { + "type": "object", + "properties": { + "modelID": { + "type": "string" + }, + "providerID": { + "type": "string" + }, + "variant": { + "type": "string" + } + }, + "required": ["modelID", "providerID"] + }, + "standard": { + "type": "object", + "properties": { + "modelID": { + "type": "string" + }, + "providerID": { + "type": "string" + }, + "variant": { + "type": "string" + } + }, + "required": ["modelID", "providerID"] + }, + "advanced": { + "type": "object", + "properties": { + "modelID": { + "type": "string" + }, + "providerID": { + "type": "string" + }, + "variant": { + "type": "string" + } + }, + "required": ["modelID", "providerID"] + } + } } }, "required": ["name", "mode", "permission", "options"]