Skip to content

Commit 9bb25a9

Browse files
thdxractions-user
andauthored
Session management and prompt handling improvements (anomalyco#2577)
Co-authored-by: GitHub Action <action@github.com>
1 parent 535230d commit 9bb25a9

9 files changed

Lines changed: 1755 additions & 1682 deletions

File tree

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { MessageV2 } from "../../session/message-v2"
1111
import { Identifier } from "../../id/id"
1212
import { Agent } from "../../agent/agent"
1313
import { Command } from "../../command"
14+
import { SessionPrompt } from "../../session/prompt"
1415

1516
const TOOL: Record<string, [string, string]> = {
1617
todowrite: ["Todo", UI.Style.TEXT_WARNING_BOLD],
@@ -185,7 +186,7 @@ export const RunCommand = cmd({
185186
})
186187

187188
if (args.command) {
188-
await Session.command({
189+
await SessionPrompt.command({
189190
messageID: Identifier.ascending("message"),
190191
sessionID: session.id,
191192
agent: agent.name,
@@ -197,7 +198,7 @@ export const RunCommand = cmd({
197198
}
198199

199200
const messageID = Identifier.ascending("message")
200-
const result = await Session.prompt({
201+
const result = await SessionPrompt.prompt({
201202
sessionID: session.id,
202203
messageID,
203204
model: {

packages/opencode/src/server/server.ts

Lines changed: 15 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ import { Global } from "../global"
2525
import { ProjectRoute } from "./project"
2626
import { ToolRegistry } from "../tool/registry"
2727
import { zodToJsonSchema } from "zod-to-json-schema"
28+
import { SessionPrompt } from "../session/prompt"
29+
import { SessionCompaction } from "../session/compaction"
30+
import { SessionRevert } from "../session/revert"
2831

2932
const ERRORS = {
3033
400: {
@@ -558,7 +561,7 @@ export namespace Server {
558561
}),
559562
),
560563
async (c) => {
561-
return c.json(Session.abort(c.req.valid("param").id))
564+
return c.json(SessionPrompt.abort(c.req.valid("param").id))
562565
},
563566
)
564567
.post(
@@ -651,7 +654,7 @@ export namespace Server {
651654
async (c) => {
652655
const id = c.req.valid("param").id
653656
const body = c.req.valid("json")
654-
await Session.summarize({ ...body, sessionID: id })
657+
await SessionCompaction.run({ ...body, sessionID: id })
655658
return c.json(true)
656659
},
657660
)
@@ -665,14 +668,7 @@ export namespace Server {
665668
description: "List of messages",
666669
content: {
667670
"application/json": {
668-
schema: resolver(
669-
z
670-
.object({
671-
info: MessageV2.Info,
672-
parts: MessageV2.Part.array(),
673-
})
674-
.array(),
675-
),
671+
schema: resolver(MessageV2.WithParts.array()),
676672
},
677673
},
678674
},
@@ -750,11 +746,11 @@ export namespace Server {
750746
id: z.string().openapi({ description: "Session ID" }),
751747
}),
752748
),
753-
zValidator("json", Session.PromptInput.omit({ sessionID: true })),
749+
zValidator("json", SessionPrompt.PromptInput.omit({ sessionID: true })),
754750
async (c) => {
755751
const sessionID = c.req.valid("param").id
756752
const body = c.req.valid("json")
757-
const msg = await Session.prompt({ ...body, sessionID })
753+
const msg = await SessionPrompt.prompt({ ...body, sessionID })
758754
return c.json(msg)
759755
},
760756
)
@@ -785,11 +781,11 @@ export namespace Server {
785781
id: z.string().openapi({ description: "Session ID" }),
786782
}),
787783
),
788-
zValidator("json", Session.CommandInput.omit({ sessionID: true })),
784+
zValidator("json", SessionPrompt.CommandInput.omit({ sessionID: true })),
789785
async (c) => {
790786
const sessionID = c.req.valid("param").id
791787
const body = c.req.valid("json")
792-
const msg = await Session.command({ ...body, sessionID })
788+
const msg = await SessionPrompt.command({ ...body, sessionID })
793789
return c.json(msg)
794790
},
795791
)
@@ -815,11 +811,11 @@ export namespace Server {
815811
id: z.string().openapi({ description: "Session ID" }),
816812
}),
817813
),
818-
zValidator("json", Session.ShellInput.omit({ sessionID: true })),
814+
zValidator("json", SessionPrompt.ShellInput.omit({ sessionID: true })),
819815
async (c) => {
820816
const sessionID = c.req.valid("param").id
821817
const body = c.req.valid("json")
822-
const msg = await Session.shell({ ...body, sessionID })
818+
const msg = await SessionPrompt.shell({ ...body, sessionID })
823819
return c.json(msg)
824820
},
825821
)
@@ -845,11 +841,11 @@ export namespace Server {
845841
id: z.string(),
846842
}),
847843
),
848-
zValidator("json", Session.RevertInput.omit({ sessionID: true })),
844+
zValidator("json", SessionRevert.RevertInput.omit({ sessionID: true })),
849845
async (c) => {
850846
const id = c.req.valid("param").id
851847
log.info("revert", c.req.valid("json"))
852-
const session = await Session.revert({ sessionID: id, ...c.req.valid("json") })
848+
const session = await SessionRevert.revert({ sessionID: id, ...c.req.valid("json") })
853849
return c.json(session)
854850
},
855851
)
@@ -877,7 +873,7 @@ export namespace Server {
877873
),
878874
async (c) => {
879875
const id = c.req.valid("param").id
880-
const session = await Session.unrevert({ sessionID: id })
876+
const session = await SessionRevert.unrevert({ sessionID: id })
881877
return c.json(session)
882878
},
883879
)
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
import { generateText, type ModelMessage } from "ai"
2+
import { Session } from "."
3+
import { Identifier } from "../id/id"
4+
import { Instance } from "../project/instance"
5+
import { Provider } from "../provider/provider"
6+
import { defer } from "../util/defer"
7+
import { MessageV2 } from "./message-v2"
8+
import { SystemPrompt } from "./system"
9+
import { Bus } from "../bus"
10+
import z from "zod"
11+
import type { ModelsDev } from "../provider/models"
12+
import { SessionPrompt } from "./prompt"
13+
14+
export namespace SessionCompaction {
15+
export const Event = {
16+
Compacted: Bus.event(
17+
"session.compacted",
18+
z.object({
19+
sessionID: z.string(),
20+
}),
21+
),
22+
}
23+
24+
export function isOverflow(input: { tokens: MessageV2.Assistant["tokens"]; model: ModelsDev.Model }) {
25+
const count = input.tokens.input + input.tokens.cache.read + input.tokens.output
26+
const output = Math.min(input.model.limit.output, SessionPrompt.OUTPUT_TOKEN_MAX) || SessionPrompt.OUTPUT_TOKEN_MAX
27+
const usable = input.model.limit.context - output
28+
return count > usable / 2
29+
}
30+
31+
export async function run(input: { sessionID: string; providerID: string; modelID: string }) {
32+
await Session.update(input.sessionID, (draft) => {
33+
draft.time.compacting = Date.now()
34+
})
35+
await using _ = defer(async () => {
36+
await Session.update(input.sessionID, (draft) => {
37+
draft.time.compacting = undefined
38+
})
39+
})
40+
const toSummarize = await Session.messages(input.sessionID).then(MessageV2.filterSummarized)
41+
const model = await Provider.getModel(input.providerID, input.modelID)
42+
const system = [
43+
...SystemPrompt.summarize(model.providerID),
44+
...(await SystemPrompt.environment()),
45+
...(await SystemPrompt.custom()),
46+
]
47+
48+
const msg = (await Session.updateMessage({
49+
id: Identifier.ascending("message"),
50+
role: "assistant",
51+
sessionID: input.sessionID,
52+
system,
53+
mode: "build",
54+
path: {
55+
cwd: Instance.directory,
56+
root: Instance.worktree,
57+
},
58+
cost: 0,
59+
tokens: {
60+
output: 0,
61+
input: 0,
62+
reasoning: 0,
63+
cache: { read: 0, write: 0 },
64+
},
65+
modelID: input.modelID,
66+
providerID: model.providerID,
67+
time: {
68+
created: Date.now(),
69+
},
70+
})) as MessageV2.Assistant
71+
const generated = await generateText({
72+
maxRetries: 10,
73+
model: model.language,
74+
messages: [
75+
...system.map(
76+
(x): ModelMessage => ({
77+
role: "system",
78+
content: x,
79+
}),
80+
),
81+
...MessageV2.toModelMessage(toSummarize),
82+
{
83+
role: "user",
84+
content: [
85+
{
86+
type: "text",
87+
text: "Provide a detailed but concise summary of our conversation above. Focus on information that would be helpful for continuing the conversation, including what we did, what we're doing, which files we're working on, and what we're going to do next.",
88+
},
89+
],
90+
},
91+
],
92+
})
93+
const usage = Session.getUsage(model.info, generated.usage, generated.providerMetadata)
94+
msg.cost += usage.cost
95+
msg.tokens = usage.tokens
96+
msg.summary = true
97+
msg.time.completed = Date.now()
98+
await Session.updateMessage(msg)
99+
const part = await Session.updatePart({
100+
type: "text",
101+
sessionID: input.sessionID,
102+
messageID: msg.id,
103+
id: Identifier.ascending("part"),
104+
text: generated.text,
105+
time: {
106+
start: Date.now(),
107+
end: Date.now(),
108+
},
109+
})
110+
111+
Bus.publish(Event.Compacted, {
112+
sessionID: input.sessionID,
113+
})
114+
115+
return {
116+
info: msg,
117+
parts: [part],
118+
}
119+
}
120+
}

0 commit comments

Comments
 (0)