Skip to content
Open
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
2 changes: 2 additions & 0 deletions packages/opencode/src/command/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export namespace Command {
model: z.string().optional(),
template: z.string(),
subtask: z.boolean().optional(),
returnPrompt: z.string().optional(),
})
.meta({
ref: "Command",
Expand All @@ -49,6 +50,7 @@ export namespace Command {
description: command.description,
template: command.template,
subtask: command.subtask,
returnPrompt: command.returnPrompt,
}
}

Expand Down
1 change: 1 addition & 0 deletions packages/opencode/src/config/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,7 @@ export namespace Config {
agent: z.string().optional(),
model: z.string().optional(),
subtask: z.boolean().optional(),
returnPrompt: z.string().optional(),
})
export type Command = z.infer<typeof Command>

Expand Down
2 changes: 2 additions & 0 deletions packages/opencode/src/session/message-v2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,8 @@ export namespace MessageV2 {
prompt: z.string(),
description: z.string(),
agent: z.string(),
returnPrompt: z.string().optional(),
model: z.string().optional(),
})
export type SubtaskPart = z.infer<typeof SubtaskPart>

Expand Down
33 changes: 15 additions & 18 deletions packages/opencode/src/session/prompt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,7 @@ export namespace SessionPrompt {
// TODO: centralize "invoke tool" logic
if (task?.type === "subtask") {
const taskTool = await TaskTool.init()
const taskModel = task.model ? Provider.parseModel(task.model) : model
const assistantMessage = (await Session.updateMessage({
id: Identifier.ascending("message"),
role: "assistant",
Expand All @@ -308,8 +309,8 @@ export namespace SessionPrompt {
reasoning: 0,
cache: { read: 0, write: 0 },
},
modelID: model.modelID,
providerID: model.providerID,
modelID: taskModel.modelID,
providerID: taskModel.providerID,
time: {
created: Date.now(),
},
Expand All @@ -327,6 +328,8 @@ export namespace SessionPrompt {
prompt: task.prompt,
description: task.description,
subagent_type: task.agent,
returnPrompt: task.returnPrompt,
model: task.model,
},
time: {
start: Date.now(),
Expand All @@ -339,6 +342,8 @@ export namespace SessionPrompt {
prompt: task.prompt,
description: task.description,
subagent_type: task.agent,
returnPrompt: task.returnPrompt,
model: task.model,
},
{
agent: task.agent,
Expand Down Expand Up @@ -1311,7 +1316,7 @@ export namespace SessionPrompt {
export async function command(input: CommandInput) {
log.info("command", input)
const command = await Command.get(input.command)
const agentName = command.agent ?? input.agent ?? "build"
const agentName = input.agent ?? "build"

const raw = input.arguments.match(argsRegex) ?? []
const args = raw.map((arg) => arg.replace(quoteTrimRegex, ""))
Expand Down Expand Up @@ -1349,28 +1354,20 @@ export namespace SessionPrompt {
}
template = template.trim()

const model = await (async () => {
if (command.model) {
return Provider.parseModel(command.model)
}
if (command.agent) {
const cmdAgent = await Agent.get(command.agent)
if (cmdAgent.model) {
return cmdAgent.model
}
}
if (input.model) return Provider.parseModel(input.model)
return await lastModel(input.sessionID)
})()
const agent = await Agent.get(agentName)
const model = input.model ? Provider.parseModel(input.model) : (agent.model ?? (await lastModel(input.sessionID)))

const cmdAgent = command.agent ? await Agent.get(command.agent) : agent

const parts =
(agent.mode === "subagent" && command.subtask !== false) || command.subtask === true
(cmdAgent.mode === "subagent" && command.subtask !== false) || command.subtask === true
? [
{
type: "subtask" as const,
agent: agent.name,
agent: cmdAgent.name,
description: command.description ?? "",
returnPrompt: command.returnPrompt,
model: command.model,
// TODO: how can we make task tool accept a more complex input?
prompt: await resolvePromptParts(template).then((x) => x.find((y) => y.type === "text")?.text ?? ""),
},
Expand Down
17 changes: 12 additions & 5 deletions packages/opencode/src/tool/task.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { MessageV2 } from "../session/message-v2"
import { Identifier } from "../id/id"
import { Agent } from "../agent/agent"
import { SessionPrompt } from "../session/prompt"
import { Provider } from "../provider/provider"
import { iife } from "@/util/iife"
import { defer } from "@/util/defer"

Expand All @@ -25,6 +26,8 @@ export const TaskTool = Tool.define("task", async () => {
prompt: z.string().describe("The task for the agent to perform"),
subagent_type: z.string().describe("The type of specialized agent to use for this task"),
session_id: z.string().describe("Existing Task session to continue").optional(),
returnPrompt: z.string().describe("Optional instructions for resuming in the parent session").optional(),
model: z.string().describe("Optional model override in provider/model form").optional(),
}),
async execute(params, ctx) {
const agent = await Agent.get(params.subagent_type)
Expand Down Expand Up @@ -66,10 +69,12 @@ export const TaskTool = Tool.define("task", async () => {
})
})

const model = agent.model ?? {
modelID: msg.info.modelID,
providerID: msg.info.providerID,
}
const model = params.model
? Provider.parseModel(params.model)
: (agent.model ?? {
modelID: msg.info.modelID,
providerID: msg.info.providerID,
})

function cancel() {
SessionPrompt.cancel(session.id)
Expand Down Expand Up @@ -100,7 +105,9 @@ export const TaskTool = Tool.define("task", async () => {
all = all.flatMap((msg) => msg.parts.filter((x: any) => x.type === "tool") as MessageV2.ToolPart[])
const text = result.parts.findLast((x) => x.type === "text")?.text ?? ""

const output = text + "\n\n" + ["<task_metadata>", `session_id: ${session.id}`, "</task_metadata>"].join("\n")
const exit = params.returnPrompt ? `\n\n${params.returnPrompt}` : ""
const output =
text + exit + "\n\n" + ["<task_metadata>", `session_id: ${session.id}`, "</task_metadata>"].join("\n")

return {
title: params.description,
Expand Down
2 changes: 1 addition & 1 deletion packages/plugin/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,4 @@
"typescript": "catalog:",
"@typescript/native-preview": "catalog:"
}
}
}
2 changes: 1 addition & 1 deletion packages/sdk/js/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,4 @@
"publishConfig": {
"directory": "dist"
}
}
}
6 changes: 6 additions & 0 deletions packages/sdk/js/src/gen/types.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -383,6 +383,8 @@ export type Part =
prompt: string
description: string
agent: string
returnPrompt?: string
model?: string
}
| ReasoningPart
| FilePart
Expand Down Expand Up @@ -1005,6 +1007,7 @@ export type Config = {
agent?: string
model?: string
subtask?: boolean
returnPrompt?: string
}
}
watcher?: {
Expand Down Expand Up @@ -1294,6 +1297,8 @@ export type SubtaskPartInput = {
prompt: string
description: string
agent: string
returnPrompt?: string
model?: string
}

export type Command = {
Expand All @@ -1303,6 +1308,7 @@ export type Command = {
model?: string
template: string
subtask?: boolean
returnPrompt?: string
}

export type Model = {
Expand Down
33 changes: 33 additions & 0 deletions packages/web/src/content/docs/commands.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,39 @@ This is an **optional** config option.

---

### Return Prompt

Use the `returnPrompt` option to provide instructions that are appended to the subagent's response when it returns to the parent session. This is useful for guiding what the parent agent should do with the subtask results and keeps the agent loop going.

```json title="opencode.json"
{
"command": {
"research": {
"agent": "general",
"subtask": true,
"returnPrompt": "Challenge, verify and validate the plan before implementing it."
}
}
}
```

Or in markdown format:

```md title=".opencode/command/research.md"
---
description: Research and implement
agent: general
subtask: true
returnPrompt: Challenge, verify and validate the plan before implementing it.
---

Research how to implement $ARGUMENTS and provide a detailed plan.
```

This is an **optional** config option. Only applies when the command runs as a subtask.

---

## Built-in

opencode includes several built-in commands like `/init`, `/undo`, `/redo`, `/share`, `/help`; [learn more](/docs/tui#commands).
Expand Down
Loading