summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorRyan Skidmore <[email protected]>2026-03-02 21:52:43 -0600
committerGitHub <[email protected]>2026-03-02 22:52:43 -0500
commitfd6f7133c556f1b4f2cc9769cc18ddae61ab8de7 (patch)
tree217ab4b8bfe3c1d64bb4413d025087f1bc90c89d
parent98c75be7e1ab72c48985be033862d96209d4069b (diff)
downloadopencode-fd6f7133c556f1b4f2cc9769cc18ddae61ab8de7.tar.gz
opencode-fd6f7133c556f1b4f2cc9769cc18ddae61ab8de7.zip
fix(opencode): clone part data in Bus event to preserve token values (#15780)
-rw-r--r--packages/opencode/src/session/index.ts2
-rw-r--r--packages/opencode/test/session/session.test.ts67
2 files changed, 68 insertions, 1 deletions
diff --git a/packages/opencode/src/session/index.ts b/packages/opencode/src/session/index.ts
index e8db405fd..b11763205 100644
--- a/packages/opencode/src/session/index.ts
+++ b/packages/opencode/src/session/index.ts
@@ -761,7 +761,7 @@ export namespace Session {
.run()
Database.effect(() =>
Bus.publish(MessageV2.Event.PartUpdated, {
- part,
+ part: structuredClone(part),
}),
)
})
diff --git a/packages/opencode/test/session/session.test.ts b/packages/opencode/test/session/session.test.ts
index 219cef127..2de94ee7e 100644
--- a/packages/opencode/test/session/session.test.ts
+++ b/packages/opencode/test/session/session.test.ts
@@ -4,6 +4,8 @@ import { Session } from "../../src/session"
import { Bus } from "../../src/bus"
import { Log } from "../../src/util/log"
import { Instance } from "../../src/project/instance"
+import { MessageV2 } from "../../src/session/message-v2"
+import { Identifier } from "../../src/id/id"
const projectRoot = path.join(__dirname, "../..")
Log.init({ print: false })
@@ -69,3 +71,68 @@ describe("session.started event", () => {
})
})
})
+
+describe("step-finish token propagation via Bus event", () => {
+ test("non-zero tokens propagate through PartUpdated event", async () => {
+ await Instance.provide({
+ directory: projectRoot,
+ fn: async () => {
+ const session = await Session.create({})
+
+ const messageID = Identifier.ascending("message")
+ await Session.updateMessage({
+ id: messageID,
+ sessionID: session.id,
+ role: "user",
+ time: { created: Date.now() },
+ agent: "user",
+ model: { providerID: "test", modelID: "test" },
+ tools: {},
+ mode: "",
+ } as unknown as MessageV2.Info)
+
+ let received: MessageV2.Part | undefined
+ const unsub = Bus.subscribe(MessageV2.Event.PartUpdated, (event) => {
+ received = event.properties.part
+ })
+
+ const tokens = {
+ total: 1500,
+ input: 500,
+ output: 800,
+ reasoning: 200,
+ cache: { read: 100, write: 50 },
+ }
+
+ const partInput = {
+ id: Identifier.ascending("part"),
+ messageID,
+ sessionID: session.id,
+ type: "step-finish" as const,
+ reason: "stop",
+ cost: 0.005,
+ tokens,
+ }
+
+ await Session.updatePart(partInput)
+
+ await new Promise((resolve) => setTimeout(resolve, 100))
+
+ expect(received).toBeDefined()
+ expect(received!.type).toBe("step-finish")
+ const finish = received as MessageV2.StepFinishPart
+ expect(finish.tokens.input).toBe(500)
+ expect(finish.tokens.output).toBe(800)
+ expect(finish.tokens.reasoning).toBe(200)
+ expect(finish.tokens.total).toBe(1500)
+ expect(finish.tokens.cache.read).toBe(100)
+ expect(finish.tokens.cache.write).toBe(50)
+ expect(finish.cost).toBe(0.005)
+ expect(received).not.toBe(partInput)
+
+ unsub()
+ await Session.remove(session.id)
+ },
+ })
+ }, { timeout: 30000 })
+})