Skip to content

Commit 723af93

Browse files
committed
core: capture codebase snapshots before and after each AI step for review
1 parent 1e21d04 commit 723af93

5 files changed

Lines changed: 53 additions & 1 deletion

File tree

packages/opencode/src/session/processor.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -432,6 +432,7 @@ export const layer: Layer.Layer<
432432
providerID: ctx.model.providerID,
433433
variant: input.assistantMessage.variant,
434434
},
435+
snapshot: ctx.snapshot,
435436
timestamp: DateTime.makeUnsafe(Date.now()),
436437
})
437438
yield* session.updatePart({
@@ -444,6 +445,7 @@ export const layer: Layer.Layer<
444445
return
445446

446447
case "finish-step": {
448+
const completedSnapshot = yield* snapshot.track()
447449
const usage = Session.getUsage({
448450
model: ctx.model,
449451
usage: value.usage,
@@ -454,6 +456,7 @@ export const layer: Layer.Layer<
454456
reason: value.finishReason,
455457
cost: usage.cost,
456458
tokens: usage.tokens,
459+
snapshot: completedSnapshot,
457460
timestamp: DateTime.makeUnsafe(Date.now()),
458461
})
459462
ctx.assistantMessage.finish = value.finishReason
@@ -462,7 +465,7 @@ export const layer: Layer.Layer<
462465
yield* session.updatePart({
463466
id: PartID.ascending(),
464467
reason: value.finishReason,
465-
snapshot: yield* snapshot.track(),
468+
snapshot: completedSnapshot,
466469
messageID: ctx.assistantMessage.id,
467470
sessionID: ctx.assistantMessage.sessionID,
468471
type: "step-finish",

packages/opencode/src/v2/session-event.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ export namespace Step {
5454
providerID: Schema.String,
5555
variant: Schema.String.pipe(Schema.optional),
5656
}),
57+
snapshot: Schema.String.pipe(Schema.optional),
5758
},
5859
})
5960
export type Started = Schema.Schema.Type<typeof Started>
@@ -74,6 +75,7 @@ export namespace Step {
7475
write: Schema.Number,
7576
}),
7677
}),
78+
snapshot: Schema.String.pipe(Schema.optional),
7779
},
7880
})
7981
export type Ended = Schema.Schema.Type<typeof Ended>

packages/opencode/src/v2/session-message-updater.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ export function update<Result>(adapter: Adapter<Result>, event: SessionEvent.Eve
9494
draft.time.completed = event.data.timestamp
9595
draft.cost = event.data.cost
9696
draft.tokens = event.data.tokens
97+
if (event.data.snapshot) draft.snapshot = { ...draft.snapshot, end: event.data.snapshot }
9798
}),
9899
)
99100
}

packages/opencode/src/v2/session-message.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,10 @@ export class Assistant extends Schema.Class<Assistant>("Session.Message.Assistan
152152
type: Schema.Literal("assistant"),
153153
content: AssistantContent.pipe(Schema.Array),
154154
retries: AssistantRetry.pipe(Schema.Array, Schema.optional),
155+
snapshot: Schema.Struct({
156+
start: Schema.String.pipe(Schema.optional),
157+
end: Schema.String.pipe(Schema.optional),
158+
}).pipe(Schema.optional),
155159
cost: Schema.Number.pipe(Schema.optional),
156160
tokens: Schema.Struct({
157161
input: Schema.Number,
@@ -177,6 +181,7 @@ export class Assistant extends Schema.Class<Assistant>("Session.Message.Assistan
177181
},
178182
content: [],
179183
retries: [],
184+
snapshot: event.data.snapshot ? { start: event.data.snapshot } : undefined,
180185
})
181186
}
182187
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { expect, test } from "bun:test"
2+
import * as DateTime from "effect/DateTime"
3+
import { SessionID } from "../../src/session/schema"
4+
import { SessionEvent } from "../../src/v2/session-event"
5+
import { SessionMessageUpdater } from "../../src/v2/session-message-updater"
6+
7+
test("step snapshots carry over to assistant messages", () => {
8+
const state: SessionMessageUpdater.MemoryState = { messages: [], pending: [] }
9+
const sessionID = SessionID.make("session")
10+
11+
SessionMessageUpdater.update(SessionMessageUpdater.memory(state), {
12+
type: "session.next.step.started",
13+
data: {
14+
sessionID,
15+
timestamp: DateTime.makeUnsafe(1),
16+
model: { id: "model", providerID: "provider" },
17+
snapshot: "before",
18+
},
19+
} satisfies SessionEvent.Event)
20+
21+
SessionMessageUpdater.update(SessionMessageUpdater.memory(state), {
22+
type: "session.next.step.ended",
23+
data: {
24+
sessionID,
25+
timestamp: DateTime.makeUnsafe(2),
26+
reason: "stop",
27+
cost: 0,
28+
tokens: {
29+
input: 1,
30+
output: 2,
31+
reasoning: 0,
32+
cache: { read: 0, write: 0 },
33+
},
34+
snapshot: "after",
35+
},
36+
} satisfies SessionEvent.Event)
37+
38+
expect(state.messages[0]?.type).toBe("assistant")
39+
if (state.messages[0]?.type !== "assistant") return
40+
expect(state.messages[0].snapshot).toEqual({ start: "before", end: "after" })
41+
})

0 commit comments

Comments
 (0)