Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export function Sidebar(props: { sessionID: string; overlay?: boolean }) {
)

const cost = createMemo(() => {
const total = messages().reduce((sum, x) => sum + (x.role === "assistant" ? x.cost : 0), 0)
const total = session().cost ?? messages().reduce((sum, x) => sum + (x.role === "assistant" ? x.cost : 0), 0)
return new Intl.NumberFormat("en-US", {
style: "currency",
currency: "USD",
Expand Down
14 changes: 14 additions & 0 deletions packages/opencode/src/session/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ export namespace Session {
.optional(),
title: z.string(),
version: z.string(),
cost: z.number().optional(),
time: z.object({
created: z.number(),
updated: z.number(),
Expand Down Expand Up @@ -199,6 +200,7 @@ export namespace Session {
directory: input.directory,
parentID: input.parentID,
title: input.title ?? createDefaultTitle(!!input.parentID),
cost: 0,
permission: input.permission,
time: {
created: Date.now(),
Expand Down Expand Up @@ -236,6 +238,11 @@ export namespace Session {
return Storage.read<ShareInfo>(["share", id])
})

export const getCost = fn(Identifier.schema("session"), async (id) => {
const read = await Storage.read<Info>(["session", Instance.project.id, id])
return read.cost
})

export const share = fn(Identifier.schema("session"), async (id) => {
const cfg = await Config.get()
if (cfg.share === "disabled") {
Expand Down Expand Up @@ -272,6 +279,13 @@ export namespace Session {
return result
}

export async function addCost(sessionID: string, amount: number) {
if (amount === 0) return
await update(sessionID, (draft) => {
draft.cost = (draft.cost ?? 0) + amount
})
}

export const diff = fn(Identifier.schema("session"), async (sessionID) => {
const diffs = await Storage.read<Snapshot.FileDiff[]>(["session_diff", sessionID])
return diffs ?? []
Expand Down
1 change: 1 addition & 0 deletions packages/opencode/src/session/processor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,7 @@ export namespace SessionProcessor {
cost: usage.cost,
})
await Session.updateMessage(input.assistantMessage)
await Session.addCost(input.sessionID, usage.cost)
if (snapshot) {
const patch = await Snapshot.patch(snapshot)
if (patch.files.length) {
Expand Down
46 changes: 46 additions & 0 deletions packages/opencode/src/storage/storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,52 @@ export namespace Storage {
)
}
},
async (dir) => {
log.info("migrating session costs")
const startTime = Date.now()
let migratedCount = 0

for await (const sessionPath of new Bun.Glob("session/*/*.json").scan({
cwd: dir,
absolute: true,
})) {
Comment on lines +141 to +149
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we have to do a migration?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's fine if only new things are updated, some people have a LOT of sessions

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would be awesome to have a way to run the migration manually if its not automatic.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rekram1-node with the fallback to deriving cost from messages present in the memory (from commit affc11a), migration is not strictly required in a sense that it will break the functionality but for older sessions the cost reporting will still be bugged.

from my local testing the migration was fine but again I don't know the extent of how many sessions people have, it could be a concern.

as @maharshi365 mentioned I can also add lazy migration, that only runs when a session is loaded, checks if the session has cost field, and run the migration if needed. I am currently not aware if users can manually run migrations in OC or not, also if it's fine from maintainers perspective to only updated new sessions I am fine with that as well, let me know @rekram1-node .

const session = await Bun.file(sessionPath).json()
if (!session) continue
if (session.cost != null) continue

let totalCost = 0
const messageDir = path.join(dir, "message", session.id)

if (await fs.exists(messageDir)) {
for await (const messagePath of new Bun.Glob("*.json").scan({
cwd: messageDir,
absolute: true,
})) {
const message = await Bun.file(messagePath).json()
if (message.role !== "assistant") continue
totalCost += message.cost ?? 0
}
}

await Bun.file(sessionPath).write(
JSON.stringify(
{
...session,
cost: totalCost,
},
null,
2,
),
)

migratedCount++
}

log.info("cost migration complete", {
sessionsMigrated: migratedCount,
elapsedMs: Date.now() - startTime,
})
},
]

const state = lazy(async () => {
Expand Down