Skip to content

Commit c34aec0

Browse files
authored
Merge agent and mode into one (sst#1689)
The concept of mode has been deprecated, there is now only the agent field in the config. An agent can be cycled through as your primary agent with <tab> or you can spawn a subagent by @ mentioning it. if you include a description of when to use it, the primary agent will try to automatically use it Full docs here: https://opencode.ai/docs/agents/
1 parent 12f1ad5 commit c34aec0

File tree

42 files changed

+1746
-921
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+1746
-921
lines changed

.opencode/agent/docs.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
description: You MUST use this agent when writing documentation
3+
---
4+
5+
You are an expert technical documentation writer
6+
7+
You are not verbose
8+
9+
Every chunk of text should be followed by an example or something besides text
10+
to look at.
11+
12+
Chunks of text should not be more than 2 sentences long.

.opencode/agent/example-driven-docs-writer.md

Lines changed: 0 additions & 44 deletions
This file was deleted.

bun.lock

Lines changed: 348 additions & 0 deletions
Large diffs are not rendered by default.

packages/opencode/package.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,13 @@
3838
"@openauthjs/openauth": "0.4.3",
3939
"@opencode-ai/plugin": "workspace:*",
4040
"@opencode-ai/sdk": "workspace:*",
41+
"@opentelemetry/auto-instrumentations-node": "0.62.0",
42+
"@opentelemetry/exporter-jaeger": "2.0.1",
43+
"@opentelemetry/exporter-otlp-http": "0.26.0",
44+
"@opentelemetry/exporter-trace-otlp-http": "0.203.0",
45+
"@opentelemetry/instrumentation-fetch": "0.203.0",
46+
"@opentelemetry/sdk-node": "0.203.0",
47+
"@opentelemetry/sdk-trace-node": "2.0.1",
4148
"@standard-schema/spec": "1.0.0",
4249
"@zip.js/zip.js": "2.7.62",
4350
"ai": "catalog:",

packages/opencode/src/agent/agent.ts

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,20 +10,24 @@ export namespace Agent {
1010
export const Info = z
1111
.object({
1212
name: z.string(),
13+
description: z.string().optional(),
14+
mode: z.union([z.literal("subagent"), z.literal("primary"), z.literal("all")]),
15+
topP: z.number().optional(),
16+
temperature: z.number().optional(),
1317
model: z
1418
.object({
1519
modelID: z.string(),
1620
providerID: z.string(),
1721
})
1822
.optional(),
19-
description: z.string(),
2023
prompt: z.string().optional(),
2124
tools: z.record(z.boolean()),
2225
})
2326
.openapi({
2427
ref: "Agent",
2528
})
2629
export type Info = z.infer<typeof Info>
30+
2731
const state = App.state("agent", async () => {
2832
const cfg = await Config.get()
2933
const result: Record<string, Info> = {
@@ -35,6 +39,21 @@ export namespace Agent {
3539
todoread: false,
3640
todowrite: false,
3741
},
42+
mode: "subagent",
43+
},
44+
build: {
45+
name: "build",
46+
tools: {},
47+
mode: "primary",
48+
},
49+
plan: {
50+
name: "plan",
51+
tools: {
52+
write: false,
53+
edit: false,
54+
patch: false,
55+
},
56+
mode: "primary",
3857
},
3958
}
4059
for (const [key, value] of Object.entries(cfg.agent ?? {})) {
@@ -46,21 +65,20 @@ export namespace Agent {
4665
if (!item)
4766
item = result[key] = {
4867
name: key,
49-
description: "",
50-
tools: {
51-
todowrite: false,
52-
todoread: false,
53-
},
68+
mode: "all",
69+
tools: {},
5470
}
55-
const model = value.model ?? cfg.model
56-
if (model) item.model = Provider.parseModel(model)
71+
if (value.model) item.model = Provider.parseModel(value.model)
5772
if (value.prompt) item.prompt = value.prompt
5873
if (value.tools)
5974
item.tools = {
6075
...item.tools,
6176
...value.tools,
6277
}
6378
if (value.description) item.description = value.description
79+
if (value.temperature != undefined) item.temperature = value.temperature
80+
if (value.top_p != undefined) item.topP = value.top_p
81+
if (value.mode) item.mode = value.mode
6482
}
6583
return result
6684
})

packages/opencode/src/cli/cmd/github.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -641,7 +641,7 @@ export const GithubRunCommand = cmd({
641641
messageID: Identifier.ascending("message"),
642642
providerID,
643643
modelID,
644-
mode: "build",
644+
agent: "build",
645645
parts: [
646646
{
647647
id: Identifier.ascending("part"),

packages/opencode/src/cli/cmd/run.ts

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ import { Flag } from "../../flag/flag"
88
import { Config } from "../../config/config"
99
import { bootstrap } from "../bootstrap"
1010
import { MessageV2 } from "../../session/message-v2"
11-
import { Mode } from "../../session/mode"
1211
import { Identifier } from "../../id/id"
12+
import { Agent } from "../../agent/agent"
1313

1414
const TOOL: Record<string, [string, string]> = {
1515
todowrite: ["Todo", UI.Style.TEXT_WARNING_BOLD],
@@ -54,9 +54,9 @@ export const RunCommand = cmd({
5454
alias: ["m"],
5555
describe: "model to use in the format of provider/model",
5656
})
57-
.option("mode", {
57+
.option("agent", {
5858
type: "string",
59-
describe: "mode to use",
59+
describe: "agent to use",
6060
})
6161
},
6262
handler: async (args) => {
@@ -103,8 +103,19 @@ export const RunCommand = cmd({
103103
}
104104
UI.empty()
105105

106-
const mode = args.mode ? await Mode.get(args.mode) : await Mode.list().then((x) => x[0])
107-
const { providerID, modelID } = args.model ? Provider.parseModel(args.model) : mode.model ?? await Provider.defaultModel()
106+
const agent = await (async () => {
107+
if (args.agent) return Agent.get(args.agent)
108+
const build = Agent.get("build")
109+
if (build) return build
110+
return Agent.list().then((x) => x[0])
111+
})()
112+
113+
const { providerID, modelID } = await (() => {
114+
if (args.model) return Provider.parseModel(args.model)
115+
if (agent.model) return agent.model
116+
return Provider.defaultModel()
117+
})()
118+
108119
UI.println(UI.Style.TEXT_NORMAL_BOLD + "@ ", UI.Style.TEXT_NORMAL + `${providerID}/${modelID}`)
109120
UI.empty()
110121

@@ -157,14 +168,17 @@ export const RunCommand = cmd({
157168
UI.error(err)
158169
})
159170

160-
161171
const messageID = Identifier.ascending("message")
162172
const result = await Session.chat({
163173
sessionID: session.id,
164174
messageID,
165-
providerID,
166-
modelID,
167-
mode: mode.name,
175+
...(agent.model
176+
? agent.model
177+
: {
178+
providerID,
179+
modelID,
180+
}),
181+
agent: agent.name,
168182
parts: [
169183
{
170184
id: Identifier.ascending("part"),

packages/opencode/src/cli/cmd/tui.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ import { Config } from "../../config/config"
1111
import { Bus } from "../../bus"
1212
import { Log } from "../../util/log"
1313
import { FileWatcher } from "../../file/watch"
14-
import { Mode } from "../../session/mode"
1514
import { Ide } from "../../ide"
15+
import { Agent } from "../../agent/agent"
1616

1717
declare global {
1818
const OPENCODE_TUI_PATH: string
@@ -115,7 +115,7 @@ export const TuiCommand = cmd({
115115
CGO_ENABLED: "0",
116116
OPENCODE_SERVER: server.url.toString(),
117117
OPENCODE_APP_INFO: JSON.stringify(app),
118-
OPENCODE_MODES: JSON.stringify(await Mode.list()),
118+
OPENCODE_AGENTS: JSON.stringify(await Agent.list()),
119119
},
120120
onExit: () => {
121121
server.stop()

packages/opencode/src/config/config.ts

Lines changed: 36 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ export namespace Config {
8383
...md.data,
8484
prompt: md.content.trim(),
8585
}
86-
const parsed = Mode.safeParse(config)
86+
const parsed = Agent.safeParse(config)
8787
if (parsed.success) {
8888
result.mode = mergeDeep(result.mode, {
8989
[config.name]: parsed.data,
@@ -92,6 +92,15 @@ export namespace Config {
9292
}
9393
throw new InvalidError({ path: item }, { cause: parsed.error })
9494
}
95+
// Migrate deprecated mode field to agent field
96+
for (const [name, mode] of Object.entries(result.mode)) {
97+
result.agent = mergeDeep(result.agent ?? {}, {
98+
[name]: {
99+
...mode,
100+
mode: "primary" as const,
101+
},
102+
})
103+
}
95104

96105
result.plugin = result.plugin || []
97106
result.plugin.push(
@@ -108,6 +117,12 @@ export namespace Config {
108117
if (result.keybinds?.messages_revert && !result.keybinds.messages_undo) {
109118
result.keybinds.messages_undo = result.keybinds.messages_revert
110119
}
120+
if (result.keybinds?.switch_mode && !result.keybinds.switch_agent) {
121+
result.keybinds.switch_agent = result.keybinds.switch_mode
122+
}
123+
if (result.keybinds?.switch_mode_reverse && !result.keybinds.switch_agent_reverse) {
124+
result.keybinds.switch_agent_reverse = result.keybinds.switch_mode_reverse
125+
}
111126

112127
if (!result.username) {
113128
const os = await import("os")
@@ -149,32 +164,34 @@ export namespace Config {
149164
export const Mcp = z.discriminatedUnion("type", [McpLocal, McpRemote])
150165
export type Mcp = z.infer<typeof Mcp>
151166

152-
export const Mode = z
167+
export const Agent = z
153168
.object({
154169
model: z.string().optional(),
155170
temperature: z.number().optional(),
156171
top_p: z.number().optional(),
157172
prompt: z.string().optional(),
158173
tools: z.record(z.string(), z.boolean()).optional(),
159174
disable: z.boolean().optional(),
175+
description: z.string().optional().describe("Description of when to use the agent"),
176+
mode: z.union([z.literal("subagent"), z.literal("primary"), z.literal("all")]).optional(),
160177
})
161178
.openapi({
162-
ref: "ModeConfig",
179+
ref: "AgentConfig",
163180
})
164-
export type Mode = z.infer<typeof Mode>
165-
166-
export const Agent = Mode.extend({
167-
description: z.string(),
168-
}).openapi({
169-
ref: "AgentConfig",
170-
})
181+
export type Agent = z.infer<typeof Agent>
171182

172183
export const Keybinds = z
173184
.object({
174185
leader: z.string().optional().default("ctrl+x").describe("Leader key for keybind combinations"),
175186
app_help: z.string().optional().default("<leader>h").describe("Show help dialog"),
176-
switch_mode: z.string().optional().default("tab").describe("Next mode"),
177-
switch_mode_reverse: z.string().optional().default("shift+tab").describe("Previous Mode"),
187+
switch_mode: z.string().optional().default("none").describe("@deprecated use switch_agent. Next mode"),
188+
switch_mode_reverse: z
189+
.string()
190+
.optional()
191+
.default("none")
192+
.describe("@deprecated use switch_agent_reverse. Previous mode"),
193+
switch_agent: z.string().optional().default("tab").describe("Next agent"),
194+
switch_agent_reverse: z.string().optional().default("shift+tab").describe("Previous agent"),
178195
editor_open: z.string().optional().default("<leader>e").describe("Open external editor"),
179196
session_export: z.string().optional().default("<leader>x").describe("Export session to editor"),
180197
session_new: z.string().optional().default("<leader>n").describe("Create a new session"),
@@ -257,19 +274,21 @@ export namespace Config {
257274
.describe("Custom username to display in conversations instead of system username"),
258275
mode: z
259276
.object({
260-
build: Mode.optional(),
261-
plan: Mode.optional(),
277+
build: Agent.optional(),
278+
plan: Agent.optional(),
262279
})
263-
.catchall(Mode)
280+
.catchall(Agent)
264281
.optional()
265-
.describe("Modes configuration, see https://opencode.ai/docs/modes"),
282+
.describe("@deprecated Use `agent` field instead."),
266283
agent: z
267284
.object({
285+
plan: Agent.optional(),
286+
build: Agent.optional(),
268287
general: Agent.optional(),
269288
})
270289
.catchall(Agent)
271290
.optional()
272-
.describe("Modes configuration, see https://opencode.ai/docs/modes"),
291+
.describe("Agent configuration, see https://opencode.ai/docs/agent"),
273292
provider: z
274293
.record(
275294
ModelsDev.Provider.partial()

packages/opencode/src/index.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import "zod-openapi/extend"
2+
import { Trace } from "./trace"
3+
Trace.init()
24
import yargs from "yargs"
35
import { hideBin } from "yargs/helpers"
46
import { RunCommand } from "./cli/cmd/run"
@@ -18,9 +20,6 @@ import { DebugCommand } from "./cli/cmd/debug"
1820
import { StatsCommand } from "./cli/cmd/stats"
1921
import { McpCommand } from "./cli/cmd/mcp"
2022
import { GithubCommand } from "./cli/cmd/github"
21-
import { Trace } from "./trace"
22-
23-
Trace.init()
2423

2524
const cancel = new AbortController()
2625

0 commit comments

Comments
 (0)