From 4ca36aea3f1b60f82da029684b0369eeb23b505b Mon Sep 17 00:00:00 2001 From: CSWNDQTJ-Kenny Date: Wed, 29 Jul 2026 07:58:27 +0800 Subject: [PATCH] feat(task): add images parameter for subagent image passthrough Add support for passing images to subagents via the task tool. This allows visual analysis tasks by providing image attachments that will be included in the subagent's prompt. Changes: - Add images parameter to BaseParameterFields in task.ts - Modify runTask to convert and pass images to subagent prompt - Update task.txt documentation with images parameter usage - Add test case for images parameter passing --- packages/opencode/src/tool/task.ts | 21 +++++++++ packages/opencode/src/tool/task.txt | 1 + packages/opencode/test/tool/task.test.ts | 54 ++++++++++++++++++++++++ 3 files changed, 76 insertions(+) diff --git a/packages/opencode/src/tool/task.ts b/packages/opencode/src/tool/task.ts index 1384e5d19725..b39ad735fa9f 100644 --- a/packages/opencode/src/tool/task.ts +++ b/packages/opencode/src/tool/task.ts @@ -49,6 +49,15 @@ const BaseParameterFields = { "This should only be set if you mean to resume a previous task (you can pass a prior task_id and the task will continue the same subagent session as before instead of creating a fresh one)", }), command: Schema.optional(Schema.String).annotate({ description: "The command that triggered this task" }), + images: Schema.optional( + Schema.Array( + Schema.Struct({ + mime: Schema.String.annotate({ description: "The MIME type of the image (e.g., image/png, image/jpeg)" }), + url: Schema.String.annotate({ description: "The URL of the image file" }), + filename: Schema.optional(Schema.String).annotate({ description: "Optional filename for the image" }), + }), + ), + ).annotate({ description: "Optional array of images to pass to the subagent for analysis" }), } const BaseParameters = Schema.Struct(BaseParameterFields) @@ -199,6 +208,18 @@ export const TaskTool = Tool.define( const runTask = Effect.fn("TaskTool.runTask")(function* () { const parts = yield* ops.resolvePromptParts(params.prompt) + + // Add images to parts if provided + if (params.images && params.images.length > 0) { + const imageParts: SessionV1.FilePartInput[] = params.images.map((image) => ({ + type: "file", + mime: image.mime, + url: image.url, + filename: image.filename, + })) + parts.push(...imageParts) + } + const result = yield* ops.prompt({ messageID: MessageID.ascending(), sessionID: nextSession.id, diff --git a/packages/opencode/src/tool/task.txt b/packages/opencode/src/tool/task.txt index c5e412f409d9..6cfb249fe537 100644 --- a/packages/opencode/src/tool/task.txt +++ b/packages/opencode/src/tool/task.txt @@ -17,3 +17,4 @@ Usage notes: 5. The agent's outputs should generally be trusted 6. Clearly tell the agent whether you expect it to write code or just to do research (search, file reads, web fetches, etc.), since it is not aware of the user's intent. Tell it how to verify its work if possible (e.g., relevant test commands). 7. If the agent description mentions that it should be used proactively, then you should try your best to use it without the user having to ask for it first. Use your judgement. +8. You can pass images to the subagent using the images parameter for visual analysis tasks. The images parameter accepts an array of image objects with mime type, url, and optional filename. diff --git a/packages/opencode/test/tool/task.test.ts b/packages/opencode/test/tool/task.test.ts index 2bcf05a2a49b..14a1afd95687 100644 --- a/packages/opencode/test/tool/task.test.ts +++ b/packages/opencode/test/tool/task.test.ts @@ -982,4 +982,58 @@ describe("tool.task", () => { expect((yield* jobs.get(grandchild.id))?.status).toBe("cancelled") }), ) + + it.instance("execute passes images to subagent when provided", () => + Effect.gen(function* () { + const { chat, assistant } = yield* seed() + const tool = yield* TaskTool + const def = yield* tool.init() + let seen: SessionPrompt.PromptInput | undefined + const promptOps = stubOps({ text: "analyzed", onPrompt: (input) => (seen = input) }) + + const images = [ + { mime: "image/png", url: "https://example.com/image1.png", filename: "test1.png" }, + { mime: "image/jpeg", url: "https://example.com/image2.jpg" }, + ] + + const result = yield* def.execute( + { + description: "analyze images", + prompt: "analyze the provided images", + subagent_type: "general", + images, + }, + { + sessionID: chat.id, + messageID: assistant.id, + agent: "build", + abort: new AbortController().signal, + extra: { promptOps }, + messages: [], + metadata: () => Effect.void, + ask: () => Effect.void, + }, + ) + + expect(result.output).toContain(``) + expect(seen?.parts).toBeDefined() + expect(seen?.parts).toHaveLength(3) // 1 text part + 2 image parts + + // Check image parts + const imageParts = seen?.parts.filter((p) => p.type === "file") + expect(imageParts).toHaveLength(2) + expect(imageParts?.[0]).toEqual({ + type: "file", + mime: "image/png", + url: "https://example.com/image1.png", + filename: "test1.png", + }) + expect(imageParts?.[1]).toEqual({ + type: "file", + mime: "image/jpeg", + url: "https://example.com/image2.jpg", + filename: undefined, + }) + }), + ) })