Skip to content

Commit 16efa5c

Browse files
committed
fix(opencode): update stats command to filter messages by creation time
1 parent d2d5d84 commit 16efa5c

2 files changed

Lines changed: 107 additions & 1 deletion

File tree

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,7 @@ export async function aggregateSessionStats(days?: number, projectFilter?: strin
170170
const messages = await Session.messages({ sessionID: session.id })
171171

172172
let sessionCost = 0
173+
let sessionMessageCount = 0
173174
let sessionTokens = { input: 0, output: 0, reasoning: 0, cache: { read: 0, write: 0 } }
174175
let sessionToolUsage: Record<string, number> = {}
175176
let sessionModelUsage: Record<
@@ -189,6 +190,8 @@ export async function aggregateSessionStats(days?: number, projectFilter?: strin
189190
> = {}
190191

191192
for (const message of messages) {
193+
if (cutoffTime > 0 && message.info.time.created < cutoffTime) continue
194+
sessionMessageCount++
192195
if (message.info.role === "assistant") {
193196
sessionCost += message.info.cost || 0
194197

@@ -226,7 +229,7 @@ export async function aggregateSessionStats(days?: number, projectFilter?: strin
226229
}
227230

228231
return {
229-
messageCount: messages.length,
232+
messageCount: sessionMessageCount,
230233
sessionCost,
231234
sessionTokens,
232235
sessionTotalTokens:
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
import { describe, expect, test } from "bun:test"
2+
import path from "path"
3+
import { Instance } from "../../../src/project/instance"
4+
import { Session } from "../../../src/session"
5+
import { MessageID, type SessionID } from "../../../src/session/schema"
6+
import { MessageV2 } from "../../../src/session/message-v2"
7+
import { ModelID, ProviderID } from "../../../src/provider/schema"
8+
import { aggregateSessionStats } from "../../../src/cli/cmd/stats"
9+
import { Log } from "../../../src/util/log"
10+
import { Database, eq } from "../../../src/storage/db"
11+
import { SessionTable } from "../../../src/session/session.sql"
12+
13+
const projectRoot = path.join(__dirname, "../../..")
14+
Log.init({ print: false })
15+
16+
const DAY = 24 * 60 * 60 * 1000
17+
18+
async function addMsg(sessionID: SessionID, created: number, cost: number) {
19+
await Session.updateMessage({
20+
id: MessageID.ascending(),
21+
sessionID,
22+
role: "assistant",
23+
parentID: MessageID.ascending(),
24+
modelID: ModelID.make("test"),
25+
providerID: ProviderID.make("test"),
26+
mode: "",
27+
agent: "default",
28+
path: { cwd: "/", root: "/" },
29+
time: { created },
30+
cost,
31+
tokens: { input: 0, output: 0, reasoning: 0, cache: { read: 0, write: 0 } },
32+
} as unknown as MessageV2.Info)
33+
}
34+
35+
function touch(sessionID: SessionID) {
36+
Database.use((db) =>
37+
db.update(SessionTable).set({ time_updated: Date.now() }).where(eq(SessionTable.id, sessionID)).run(),
38+
)
39+
}
40+
41+
describe("aggregateSessionStats", () => {
42+
test("excludes messages older than the day window even when session was touched today", async () => {
43+
await Instance.provide({
44+
directory: projectRoot,
45+
fn: async () => {
46+
const session = await Session.create({})
47+
const now = Date.now()
48+
49+
await addMsg(session.id, now - 10 * DAY, 5.0)
50+
await addMsg(session.id, now, 1.0)
51+
touch(session.id)
52+
53+
const stats = await aggregateSessionStats(1, "")
54+
55+
expect(stats.totalCost).toBeCloseTo(1.0, 1)
56+
expect(stats.totalMessages).toBe(1)
57+
58+
await Session.remove(session.id)
59+
},
60+
})
61+
})
62+
63+
test("counts new messages added today to old sessions", async () => {
64+
await Instance.provide({
65+
directory: projectRoot,
66+
fn: async () => {
67+
const session = await Session.create({})
68+
const now = Date.now()
69+
70+
await addMsg(session.id, now - 5 * DAY, 3.0)
71+
await addMsg(session.id, now, 2.0)
72+
touch(session.id)
73+
74+
const stats = await aggregateSessionStats(1, "")
75+
76+
expect(stats.totalCost).toBeCloseTo(2.0, 1)
77+
expect(stats.totalMessages).toBe(1)
78+
79+
await Session.remove(session.id)
80+
},
81+
})
82+
})
83+
84+
test("counts all messages when no day filter is set", async () => {
85+
await Instance.provide({
86+
directory: projectRoot,
87+
fn: async () => {
88+
const session = await Session.create({})
89+
const now = Date.now()
90+
91+
await addMsg(session.id, now - 10 * DAY, 5.0)
92+
await addMsg(session.id, now, 1.0)
93+
94+
const stats = await aggregateSessionStats(undefined, "")
95+
96+
expect(stats.totalCost).toBeCloseTo(6.0, 1)
97+
expect(stats.totalMessages).toBe(2)
98+
99+
await Session.remove(session.id)
100+
},
101+
})
102+
})
103+
})

0 commit comments

Comments
 (0)