Skip to content

Commit 9979e8f

Browse files
committed
core: persist v2 tool metadata
1 parent aded014 commit 9979e8f

3 files changed

Lines changed: 73 additions & 6 deletions

File tree

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

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,15 +49,15 @@ export function update<Result>(adapter: Adapter<Result>, event: SessionEvent.Eve
4949

5050
const latestTool = (assistant: DraftAssistant | undefined, callID?: string) =>
5151
assistant?.content.findLast(
52-
(item): item is DraftTool => item.type === "tool" && (callID === undefined || item.callID === callID),
52+
(item): item is DraftTool => item.type === "tool" && (callID === undefined || item.id === callID),
5353
)
5454

5555
const latestText = (assistant: DraftAssistant | undefined) =>
5656
assistant?.content.findLast((item): item is DraftText => item.type === "text")
5757

5858
const latestReasoning = (assistant: DraftAssistant | undefined, reasoningID: string) =>
5959
assistant?.content.findLast(
60-
(item): item is DraftReasoning => item.type === "reasoning" && item.reasoningID === reasoningID,
60+
(item): item is DraftReasoning => item.type === "reasoning" && item.id === reasoningID,
6161
)
6262

6363
SessionEvent.All.match(event, {
@@ -128,7 +128,7 @@ export function update<Result>(adapter: Adapter<Result>, event: SessionEvent.Eve
128128
produce(currentAssistant, (draft) => {
129129
draft.content.push({
130130
type: "tool",
131-
callID: event.data.callID,
131+
id: event.data.callID,
132132
name: event.data.name,
133133
time: {
134134
created: event.data.timestamp,
@@ -160,6 +160,7 @@ export function update<Result>(adapter: Adapter<Result>, event: SessionEvent.Eve
160160
produce(currentAssistant, (draft) => {
161161
const match = latestTool(draft, event.data.callID)
162162
if (match) {
163+
match.provider = event.data.provider
163164
match.time.ran = event.data.timestamp
164165
match.state = {
165166
status: "running",
@@ -191,6 +192,8 @@ export function update<Result>(adapter: Adapter<Result>, event: SessionEvent.Eve
191192
produce(currentAssistant, (draft) => {
192193
const match = latestTool(draft, event.data.callID)
193194
if (match && match.state.status === "running") {
195+
match.provider = event.data.provider
196+
match.time.completed = event.data.timestamp
194197
match.state = {
195198
status: "completed",
196199
input: match.state.input,
@@ -208,6 +211,8 @@ export function update<Result>(adapter: Adapter<Result>, event: SessionEvent.Eve
208211
produce(currentAssistant, (draft) => {
209212
const match = latestTool(draft, event.data.callID)
210213
if (match && match.state.status === "running") {
214+
match.provider = event.data.provider
215+
match.time.completed = event.data.timestamp
211216
match.state = {
212217
status: "error",
213218
error: event.data.error,
@@ -226,7 +231,7 @@ export function update<Result>(adapter: Adapter<Result>, event: SessionEvent.Eve
226231
produce(currentAssistant, (draft) => {
227232
draft.content.push({
228233
type: "reasoning",
229-
reasoningID: event.data.reasoningID,
234+
id: event.data.reasoningID,
230235
text: "",
231236
})
232237
}),

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

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,8 +93,12 @@ export type ToolState = Schema.Schema.Type<typeof ToolState>
9393

9494
export class AssistantTool extends Schema.Class<AssistantTool>("Session.Message.Assistant.Tool")({
9595
type: Schema.Literal("tool"),
96-
callID: Schema.String,
96+
id: Schema.String,
9797
name: Schema.String,
98+
provider: Schema.Struct({
99+
executed: Schema.Boolean,
100+
metadata: Schema.Record(Schema.String, Schema.Unknown).pipe(Schema.optional),
101+
}).pipe(Schema.optional),
98102
state: ToolState,
99103
time: Schema.Struct({
100104
created: Schema.DateTimeUtcFromMillis,
@@ -111,7 +115,7 @@ export class AssistantText extends Schema.Class<AssistantText>("Session.Message.
111115

112116
export class AssistantReasoning extends Schema.Class<AssistantReasoning>("Session.Message.Assistant.Reasoning")({
113117
type: Schema.Literal("reasoning"),
114-
reasoningID: Schema.String,
118+
id: Schema.String,
115119
text: Schema.String,
116120
}) {}
117121

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

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,3 +79,61 @@ test("text ended populates assistant text content", () => {
7979
if (state.messages[0]?.type !== "assistant") return
8080
expect(state.messages[0].content).toEqual([{ type: "text", text: "hello assistant" }])
8181
})
82+
83+
test("tool completion stores completed timestamp", () => {
84+
const state: SessionMessageUpdater.MemoryState = { messages: [] }
85+
const sessionID = SessionID.make("session")
86+
const callID = "call"
87+
88+
SessionMessageUpdater.update(SessionMessageUpdater.memory(state), {
89+
type: "session.next.step.started",
90+
data: {
91+
id: SessionEvent.ID.create(),
92+
sessionID,
93+
timestamp: DateTime.makeUnsafe(1),
94+
agent: "build",
95+
model: { id: "model", providerID: "provider" },
96+
},
97+
} satisfies SessionEvent.Event)
98+
99+
SessionMessageUpdater.update(SessionMessageUpdater.memory(state), {
100+
type: "session.next.tool.input.started",
101+
data: {
102+
sessionID,
103+
timestamp: DateTime.makeUnsafe(2),
104+
callID,
105+
name: "bash",
106+
},
107+
} satisfies SessionEvent.Event)
108+
109+
SessionMessageUpdater.update(SessionMessageUpdater.memory(state), {
110+
type: "session.next.tool.called",
111+
data: {
112+
sessionID,
113+
timestamp: DateTime.makeUnsafe(3),
114+
callID,
115+
tool: "bash",
116+
input: { command: "pwd" },
117+
provider: { executed: true, metadata: { source: "provider" } },
118+
},
119+
} satisfies SessionEvent.Event)
120+
121+
SessionMessageUpdater.update(SessionMessageUpdater.memory(state), {
122+
type: "session.next.tool.success",
123+
data: {
124+
sessionID,
125+
timestamp: DateTime.makeUnsafe(4),
126+
callID,
127+
structured: {},
128+
content: [{ type: "text", text: "/tmp" }],
129+
provider: { executed: true, metadata: { status: "done" } },
130+
},
131+
} satisfies SessionEvent.Event)
132+
133+
expect(state.messages[0]?.type).toBe("assistant")
134+
if (state.messages[0]?.type !== "assistant") return
135+
expect(state.messages[0].content[0]?.type).toBe("tool")
136+
if (state.messages[0].content[0]?.type !== "tool") return
137+
expect(state.messages[0].content[0].time.completed).toEqual(DateTime.makeUnsafe(4))
138+
expect(state.messages[0].content[0].provider).toEqual({ executed: true, metadata: { status: "done" } })
139+
})

0 commit comments

Comments
 (0)