Skip to content
Open
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
73 changes: 58 additions & 15 deletions packages/opencode/src/session/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { Command } from "../command"
import { Snapshot } from "@/snapshot"

import type { Provider } from "@/provider/provider"
import type { JSONObject } from "@ai-sdk/provider"
import { PermissionNext } from "@/permission/next"
import { Global } from "@/global"

Expand Down Expand Up @@ -409,6 +410,58 @@ export namespace Session {
return part
})

function extractProviderCost(metadata?: ProviderMetadata): number | null {
if (!metadata) return null

if (typeof metadata.openrouter?.usage == "object") {
const usage = metadata.openrouter?.usage as JSONObject;
if (typeof usage.cost == "number") {
return usage.cost;
}
}

return null
}

type UsageTokens = {
input: number;
output: number;
reasoning: number;
cache: {
write: number;
read: number;
};
}

const getCost = fn(z.object({
model: z.custom<Provider.Model>(),
tokens: z.custom<UsageTokens>(),
metadata: z.custom<ProviderMetadata>().optional(),
}), (input) => {
const {tokens, metadata} = input;
const providerCost = extractProviderCost(metadata);
if (providerCost) {
return providerCost;
}

// Approximated cost
const costInfo =
input.model.cost?.experimentalOver200K && tokens.input + tokens.cache.read > 200_000
? input.model.cost.experimentalOver200K
: input.model.cost;
const appoximatedCost = new Decimal(0)
.add(new Decimal(tokens.input).mul(costInfo?.input ?? 0).div(1_000_000))
.add(new Decimal(tokens.output).mul(costInfo?.output ?? 0).div(1_000_000))
.add(new Decimal(tokens.cache.read).mul(costInfo?.cache?.read ?? 0).div(1_000_000))
.add(new Decimal(tokens.cache.write).mul(costInfo?.cache?.write ?? 0).div(1_000_000))
// TODO: update models.dev to have better pricing model, for now:
// charge reasoning tokens at the same rate as output tokens
.add(new Decimal(tokens.reasoning).mul(costInfo?.output ?? 0).div(1_000_000))
.toNumber();

return Number.isFinite(appoximatedCost) ? appoximatedCost : 0;
})

export const getUsage = fn(
z.object({
model: z.custom<Provider.Model>(),
Expand Down Expand Up @@ -441,23 +494,13 @@ export namespace Session {
},
}

const costInfo =
input.model.cost?.experimentalOver200K && tokens.input + tokens.cache.read > 200_000
? input.model.cost.experimentalOver200K
: input.model.cost
return {
cost: safe(
new Decimal(0)
.add(new Decimal(tokens.input).mul(costInfo?.input ?? 0).div(1_000_000))
.add(new Decimal(tokens.output).mul(costInfo?.output ?? 0).div(1_000_000))
.add(new Decimal(tokens.cache.read).mul(costInfo?.cache?.read ?? 0).div(1_000_000))
.add(new Decimal(tokens.cache.write).mul(costInfo?.cache?.write ?? 0).div(1_000_000))
// TODO: update models.dev to have better pricing model, for now:
// charge reasoning tokens at the same rate as output tokens
.add(new Decimal(tokens.reasoning).mul(costInfo?.output ?? 0).div(1_000_000))
.toNumber(),
),
tokens,
cost: getCost({
model: input.model,
tokens: tokens,
metadata: input.metadata
}),
}
},
)
Expand Down