mirror of
https://github.com/stablyai/orca.git
synced 2026-11-15 10:27:33 +00:00
* fix(usage): price Codex long context per request, not per aggregate OpenAI bills a request whose prompt exceeds 272K input tokens at the long-context rates for that whole request. Orca applied 272K as a marginal threshold to daily, per-range and per-session token totals, so ordinary days of short requests were billed mostly at long-context rates and the summary disagreed with the model breakdown. Classify each token_count at parse time and carry long-context token counts through every rollup; the estimate bills that subset at the long-context rates and the rest at base. Bump the cache schema to 6 so existing caches rescan and pick up the split. * fix(usage): never class a total-only Codex delta as long context A token_count with only running totals yields the increase since the previous total, which can span several short requests; classing that aggregate as one >272K request would bill short requests at long-context rates. Only a last_token_usage record is one response. Also note that OpenAI's GPT-5.4/5.5 pages say "full session" where 5.6/6 say "full request"; the per-request reading is the stated assumption. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
37 lines
1.4 KiB
TypeScript
37 lines
1.4 KiB
TypeScript
import { createUsageEventAggregation } from '../usage/usage-event-aggregation'
|
|
import type { CodexLongContextTokens, CodexUsageAttributedEvent } from './types'
|
|
|
|
type CodexUsageMetric = { hasInferredPricing: boolean } & CodexLongContextTokens
|
|
|
|
export const codexUsageAggregation = createUsageEventAggregation<
|
|
CodexUsageAttributedEvent,
|
|
CodexUsageMetric
|
|
>({
|
|
metric: {
|
|
empty: () => ({
|
|
hasInferredPricing: false,
|
|
longContextInputTokens: 0,
|
|
longContextCachedInputTokens: 0,
|
|
longContextOutputTokens: 0
|
|
}),
|
|
fromEvent: (event) => ({
|
|
hasInferredPricing: event.hasInferredPricing,
|
|
longContextInputTokens: event.longContextInputTokens,
|
|
longContextCachedInputTokens: event.longContextCachedInputTokens,
|
|
longContextOutputTokens: event.longContextOutputTokens
|
|
}),
|
|
fold: (target, source) => {
|
|
target.hasInferredPricing ||= source.hasInferredPricing
|
|
target.longContextInputTokens += source.longContextInputTokens
|
|
target.longContextCachedInputTokens += source.longContextCachedInputTokens
|
|
target.longContextOutputTokens += source.longContextOutputTokens
|
|
}
|
|
},
|
|
cloneSessionForMerge: (session) => ({
|
|
...session,
|
|
locationBreakdown: session.locationBreakdown.map((entry) => ({ ...entry })),
|
|
modelBreakdown: session.modelBreakdown.map((entry) => ({ ...entry })),
|
|
locationModelBreakdown: session.locationModelBreakdown.map((entry) => ({ ...entry }))
|
|
})
|
|
})
|