Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions packages/opencode/src/tool/task.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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,
Expand Down
1 change: 1 addition & 0 deletions packages/opencode/src/tool/task.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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.
54 changes: 54 additions & 0 deletions packages/opencode/test/tool/task.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(`<task id="${result.metadata.sessionId}" state="completed">`)
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,
})
}),
)
})
Loading