From 6ba2bbc91c9499af3ddddcddda597a27e7e0109b Mon Sep 17 00:00:00 2001 From: Shoubhit Dash Date: Wed, 29 Apr 2026 17:10:19 +0530 Subject: [PATCH] feat(task): use small model for subagents --- packages/opencode/src/tool/task.ts | 7 ++- packages/opencode/test/tool/task.test.ts | 60 ++++++++++++++++++++++-- 2 files changed, 61 insertions(+), 6 deletions(-) diff --git a/packages/opencode/src/tool/task.ts b/packages/opencode/src/tool/task.ts index bd8645d3c1..46253a24ce 100644 --- a/packages/opencode/src/tool/task.ts +++ b/packages/opencode/src/tool/task.ts @@ -6,6 +6,7 @@ import { MessageV2 } from "../session/message-v2" import { Agent } from "../agent/agent" import type { SessionPrompt } from "../session/prompt" import { Config } from "@/config/config" +import { Provider } from "@/provider/provider" import { Effect, Schema } from "effect" export interface TaskPromptOps { @@ -32,6 +33,7 @@ export const TaskTool = Tool.define( Effect.gen(function* () { const agent = yield* Agent.Service const config = yield* Config.Service + const provider = yield* Provider.Service const sessions = yield* Session.Service const run = Effect.fn("TaskTool.execute")(function* ( @@ -99,9 +101,10 @@ export const TaskTool = Tool.define( const msg = yield* Effect.sync(() => MessageV2.get({ sessionID: ctx.sessionID, messageID: ctx.messageID })) if (msg.info.role !== "assistant") return yield* Effect.fail(new Error("Not an assistant message")) + const smallModel = next.model ? undefined : yield* provider.getSmallModel(msg.info.providerID) const model = next.model ?? { - modelID: msg.info.modelID, - providerID: msg.info.providerID, + modelID: smallModel?.id ?? msg.info.modelID, + providerID: smallModel?.providerID ?? msg.info.providerID, } yield* ctx.metadata({ diff --git a/packages/opencode/test/tool/task.test.ts b/packages/opencode/test/tool/task.test.ts index 147541f3d2..c2c0edf31d 100644 --- a/packages/opencode/test/tool/task.test.ts +++ b/packages/opencode/test/tool/task.test.ts @@ -2,6 +2,7 @@ import { afterEach, describe, expect } from "bun:test" import { Effect, Layer } from "effect" import { Agent } from "../../src/agent/agent" import { Config } from "@/config/config" +import { Provider } from "@/provider/provider" import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" import { Instance } from "../../src/project/instance" import { Session } from "@/session/session" @@ -28,6 +29,7 @@ const it = testEffect( Layer.mergeAll( Agent.defaultLayer, Config.defaultLayer, + Provider.defaultLayer, CrossSpawnSpawner.defaultLayer, Session.defaultLayer, Truncate.defaultLayer, @@ -35,7 +37,7 @@ const it = testEffect( ), ) -const seed = Effect.fn("TaskToolTest.seed")(function* (title = "Pinned") { +const seed = Effect.fn("TaskToolTest.seed")(function* (title = "Pinned", model = ref) { const session = yield* Session.Service const chat = yield* session.create({ title }) const user = yield* session.updateMessage({ @@ -43,7 +45,7 @@ const seed = Effect.fn("TaskToolTest.seed")(function* (title = "Pinned") { role: "user", sessionID: chat.id, agent: "build", - model: ref, + model, time: { created: Date.now() }, }) const assistant: MessageV2.Assistant = { @@ -56,8 +58,8 @@ const seed = Effect.fn("TaskToolTest.seed")(function* (title = "Pinned") { cost: 0, path: { cwd: "/tmp", root: "/tmp" }, tokens: { input: 0, output: 0, reasoning: 0, cache: { read: 0, write: 0 } }, - modelID: ref.modelID, - providerID: ref.providerID, + modelID: model.modelID, + providerID: model.providerID, time: { created: Date.now() }, } yield* session.updateMessage(assistant) @@ -274,6 +276,56 @@ describe("tool.task", () => { ), ) + it.live("execute defaults subagents to the provider small model", () => + provideTmpdirInstance( + () => + Effect.gen(function* () { + const current = { + providerID: ProviderID.openai, + modelID: ModelID.make("gpt-5"), + } + const { chat, assistant } = yield* seed("Pinned", current) + const tool = yield* TaskTool + const def = yield* tool.init() + let seen: SessionPrompt.PromptInput | undefined + const promptOps = stubOps({ onPrompt: (input) => (seen = input) }) + + const result = yield* def.execute( + { + description: "inspect bug", + prompt: "look into the cache key path", + subagent_type: "general", + }, + { + sessionID: chat.id, + messageID: assistant.id, + agent: "build", + abort: new AbortController().signal, + extra: { promptOps }, + messages: [], + metadata: () => Effect.void, + ask: () => Effect.void, + }, + ) + + expect(result.metadata.model).toEqual({ + providerID: ProviderID.openai, + modelID: ModelID.make("gpt-5.4-mini"), + }) + expect(seen?.model).toEqual(result.metadata.model) + }), + { + config: { + provider: { + openai: { + options: { apiKey: "test" }, + }, + }, + }, + }, + ), + ) + it.live("execute creates a child when task_id does not exist", () => provideTmpdirInstance(() => Effect.gen(function* () {