Skip to content

Commit a454ba8

Browse files
committed
subagent
1 parent 5eae7ae commit a454ba8

6 files changed

Lines changed: 201 additions & 111 deletions

File tree

packages/opencode/src/provider/provider.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import { AuthAnthropic } from "../auth/anthropic"
2424
import { ModelsDev } from "./models"
2525
import { NamedError } from "../util/error"
2626
import { Auth } from "../auth"
27+
import { TaskTool } from "../tool/task"
2728

2829
export namespace Provider {
2930
const log = Log.create({ service: "provider" })
@@ -298,6 +299,7 @@ export namespace Provider {
298299
// MultiEditTool,
299300
WriteTool,
300301
TodoWriteTool,
302+
TaskTool,
301303
TodoReadTool,
302304
]
303305
const TOOL_MAPPING: Record<string, Tool.Info[]> = {

packages/opencode/src/session/index.ts

Lines changed: 78 additions & 110 deletions
Original file line numberDiff line numberDiff line change
@@ -12,31 +12,29 @@ import {
1212
tool,
1313
type Tool as AITool,
1414
type LanguageModelUsage,
15+
type UIMessage,
1516
} from "ai"
1617
import { z, ZodSchema } from "zod"
1718
import { Decimal } from "decimal.js"
1819

19-
import PROMPT_ANTHROPIC from "./prompt/anthropic.txt"
20-
import PROMPT_ANTHROPIC_SPOOF from "./prompt/anthropic_spoof.txt"
21-
import PROMPT_TITLE from "./prompt/title.txt"
22-
import PROMPT_SUMMARIZE from "./prompt/summarize.txt"
2320
import PROMPT_INITIALIZE from "../session/prompt/initialize.txt"
2421

2522
import { Share } from "../share/share"
2623
import { Message } from "./message"
2724
import { Bus } from "../bus"
2825
import { Provider } from "../provider/provider"
29-
import { SessionContext } from "./context"
30-
import { ListTool } from "../tool/ls"
3126
import { MCP } from "../mcp"
3227
import { NamedError } from "../util/error"
28+
import type { Tool } from "../tool/tool"
29+
import { SystemPrompt } from "./system"
3330

3431
export namespace Session {
3532
const log = Log.create({ service: "session" })
3633

3734
export const Info = z
3835
.object({
3936
id: Identifier.schema("session"),
37+
parentID: Identifier.schema("session").optional(),
4038
share: z
4139
.object({
4240
secret: z.string(),
@@ -79,10 +77,11 @@ export namespace Session {
7977
}
8078
})
8179

82-
export async function create() {
80+
export async function create(parentID?: string) {
8381
const result: Info = {
8482
id: Identifier.descending("session"),
85-
title: "New Session - " + new Date().toISOString(),
83+
parentID,
84+
title: "Child Session - " + new Date().toISOString(),
8685
time: {
8786
created: Date.now(),
8887
updated: Date.now(),
@@ -91,11 +90,12 @@ export namespace Session {
9190
log.info("created", result)
9291
state().sessions.set(result.id, result)
9392
await Storage.writeJSON("session/info/" + result.id, result)
94-
share(result.id).then((share) => {
95-
update(result.id, (draft) => {
96-
draft.share = share
93+
if (!result.parentID)
94+
share(result.id).then((share) => {
95+
update(result.id, (draft) => {
96+
draft.share = share
97+
})
9798
})
98-
})
9999
Bus.publish(Event.Updated, {
100100
info: result,
101101
})
@@ -186,12 +186,16 @@ export namespace Session {
186186
providerID: string
187187
modelID: string
188188
parts: Message.Part[]
189+
system?: string[]
190+
tools?: Tool.Info[]
189191
}) {
190192
const l = log.clone().tag("session", input.sessionID)
191193
l.info("chatting")
192194
const model = await Provider.getModel(input.providerID, input.modelID)
193195
let msgs = await messages(input.sessionID)
194196
const previous = msgs.at(-1)
197+
198+
// auto summarize if too long
195199
if (previous?.metadata.assistant) {
196200
const tokens =
197201
previous.metadata.assistant.tokens.input +
@@ -214,95 +218,25 @@ export namespace Session {
214218
const lastSummary = msgs.findLast(
215219
(msg) => msg.metadata.assistant?.summary === true,
216220
)
217-
if (lastSummary)
218-
msgs = msgs.filter(
219-
(msg) => msg.role === "system" || msg.id >= lastSummary.id,
220-
)
221+
if (lastSummary) msgs = msgs.filter((msg) => msg.id >= lastSummary.id)
221222

223+
const app = App.info()
222224
if (msgs.length === 0) {
223-
const app = App.info()
224-
if (input.providerID === "anthropic") {
225-
const claude: Message.Info = {
226-
id: Identifier.ascending("message"),
227-
role: "system",
228-
parts: [
229-
{
230-
type: "text",
231-
text: PROMPT_ANTHROPIC_SPOOF.trim(),
232-
},
233-
],
234-
metadata: {
235-
sessionID: input.sessionID,
236-
time: {
237-
created: Date.now(),
238-
},
239-
tool: {},
240-
},
241-
}
242-
await updateMessage(claude)
243-
msgs.push(claude)
244-
}
245-
const system: Message.Info = {
246-
id: Identifier.ascending("message"),
247-
role: "system",
248-
parts: [
249-
{
250-
type: "text",
251-
text: PROMPT_ANTHROPIC,
252-
},
253-
{
254-
type: "text",
255-
text: [
256-
`Here is some useful information about the environment you are running in:`,
257-
`<env>`,
258-
`Working directory: ${app.path.cwd}`,
259-
`Is directory a git repo: ${app.git ? "yes" : "no"}`,
260-
`Platform: ${process.platform}`,
261-
`Today's date: ${new Date().toISOString()}`,
262-
`</env>`,
263-
`<project>`,
264-
`${app.git ? await ListTool.execute({ path: app.path.cwd, ignore: [] }, { sessionID: input.sessionID, abort: abort.signal }).then((x) => x.output) : ""}`,
265-
`</project>`,
266-
].join("\n"),
267-
},
268-
],
269-
metadata: {
270-
sessionID: input.sessionID,
271-
time: {
272-
created: Date.now(),
273-
},
274-
tool: {},
275-
},
276-
}
277-
const context = await SessionContext.find()
278-
if (context) {
279-
system.parts.push({
280-
type: "text",
281-
text: context,
282-
})
283-
}
284-
msgs.push(system)
285225
generateText({
286226
maxOutputTokens: 20,
287227
messages: convertToModelMessages([
288-
{
289-
role: "system",
290-
parts: [
291-
{
292-
type: "text",
293-
text: PROMPT_ANTHROPIC_SPOOF.trim(),
294-
},
295-
],
296-
},
297-
{
298-
role: "system",
299-
parts: [
300-
{
301-
type: "text",
302-
text: PROMPT_TITLE,
303-
},
304-
],
305-
},
228+
...SystemPrompt.title(input.providerID).map(
229+
(x): UIMessage => ({
230+
id: Identifier.ascending("message"),
231+
role: "system",
232+
parts: [
233+
{
234+
type: "text",
235+
text: x,
236+
},
237+
],
238+
}),
239+
),
306240
{
307241
role: "user",
308242
parts: input.parts,
@@ -317,7 +251,6 @@ export namespace Session {
317251
})
318252
})
319253
.catch(() => {})
320-
await updateMessage(system)
321254
}
322255
const msg: Message.Info = {
323256
role: "user",
@@ -334,12 +267,21 @@ export namespace Session {
334267
await updateMessage(msg)
335268
msgs.push(msg)
336269

270+
const system = input.system ?? SystemPrompt.provider(input.providerID)
271+
system.push(...(await SystemPrompt.environment(input.sessionID)))
272+
system.push(...(await SystemPrompt.custom()))
273+
337274
const next: Message.Info = {
338275
id: Identifier.ascending("message"),
339276
role: "assistant",
340277
parts: [],
341278
metadata: {
342279
assistant: {
280+
system,
281+
path: {
282+
cwd: app.path.cwd,
283+
root: app.path.root,
284+
},
343285
cost: 0,
344286
tokens: {
345287
input: 0,
@@ -358,6 +300,7 @@ export namespace Session {
358300
}
359301
await updateMessage(next)
360302
const tools: Record<string, AITool> = {}
303+
361304
for (const item of await Provider.tools(input.providerID)) {
362305
tools[item.id.replaceAll(".", "_")] = tool({
363306
id: item.id as any,
@@ -369,6 +312,7 @@ export namespace Session {
369312
const result = await item.execute(args, {
370313
sessionID: input.sessionID,
371314
abort: abort.signal,
315+
messageID: next.id,
372316
})
373317
next.metadata!.tool![opts.toolCallId] = {
374318
...result.metadata,
@@ -395,6 +339,7 @@ export namespace Session {
395339
},
396340
})
397341
}
342+
398343
for (const [key, item] of Object.entries(await MCP.tools())) {
399344
const execute = item.execute
400345
if (!execute) continue
@@ -576,7 +521,21 @@ export namespace Session {
576521
toolCallStreaming: true,
577522
abortSignal: abort.signal,
578523
stopWhen: stepCountIs(1000),
579-
messages: convertToModelMessages(msgs),
524+
messages: convertToModelMessages([
525+
...system.map(
526+
(x): UIMessage => ({
527+
id: Identifier.ascending("message"),
528+
role: "system",
529+
parts: [
530+
{
531+
type: "text",
532+
text: x,
533+
},
534+
],
535+
}),
536+
),
537+
...msgs,
538+
]),
580539
temperature: model.info.id === "codex-mini-latest" ? undefined : 0,
581540
tools: {
582541
...(await MCP.tools()),
@@ -618,10 +577,11 @@ export namespace Session {
618577
const lastSummary = msgs.findLast(
619578
(msg) => msg.metadata.assistant?.summary === true,
620579
)?.id
621-
const filtered = msgs.filter(
622-
(msg) => msg.role !== "system" && (!lastSummary || msg.id >= lastSummary),
623-
)
580+
const filtered = msgs.filter((msg) => !lastSummary || msg.id >= lastSummary)
624581
const model = await Provider.getModel(input.providerID, input.modelID)
582+
const app = App.info()
583+
const system = SystemPrompt.summarize(input.providerID)
584+
625585
const next: Message.Info = {
626586
id: Identifier.ascending("message"),
627587
role: "assistant",
@@ -630,6 +590,11 @@ export namespace Session {
630590
tool: {},
631591
sessionID: input.sessionID,
632592
assistant: {
593+
system,
594+
path: {
595+
cwd: app.path.cwd,
596+
root: app.path.root,
597+
},
633598
summary: true,
634599
cost: 0,
635600
modelID: input.modelID,
@@ -650,15 +615,18 @@ export namespace Session {
650615
abortSignal: abort.signal,
651616
model: model.language,
652617
messages: convertToModelMessages([
653-
{
654-
role: "system",
655-
parts: [
656-
{
657-
type: "text",
658-
text: PROMPT_SUMMARIZE,
659-
},
660-
],
661-
},
618+
...system.map(
619+
(x): UIMessage => ({
620+
id: Identifier.ascending("message"),
621+
role: "system",
622+
parts: [
623+
{
624+
type: "text",
625+
text: x,
626+
},
627+
],
628+
}),
629+
),
662630
...filtered,
663631
{
664632
role: "user",

packages/opencode/src/session/message.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ export namespace Message {
133133
export const Info = z
134134
.object({
135135
id: z.string(),
136-
role: z.enum(["system", "user", "assistant"]),
136+
role: z.enum(["user", "assistant"]),
137137
parts: z.array(Part),
138138
metadata: z.object({
139139
time: z.object({
@@ -161,8 +161,13 @@ export namespace Message {
161161
),
162162
assistant: z
163163
.object({
164+
system: z.string().array(),
164165
modelID: z.string(),
165166
providerID: z.string(),
167+
path: z.object({
168+
cwd: z.string(),
169+
root: z.string(),
170+
}),
166171
cost: z.number(),
167172
summary: z.boolean().optional(),
168173
tokens: z.object({

0 commit comments

Comments
 (0)