Skip to content
Merged
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
11 changes: 7 additions & 4 deletions README.en.md
Original file line number Diff line number Diff line change
Expand Up @@ -191,15 +191,16 @@ Display settings in the header use a more comfortable type scale by default and

### API-equivalent cost

The Dashboard shows regular, Fast, and all tokens, with a mode filter. Raw Fast tokens are never multiplied. Fast cost uses Standard base rates multiplied by ChatGPT Codex credit factors: 2.5 for Astra, the GPT-5.6 family, and GPT-5.5; 2 for GPT-5.4. Models without a confirmed factor remain unpriced. History is classified only from explicit evidence for the same turn; unconfirmed usage is provisionally regular. See the [Fast accounting, backfill, and API guide (Chinese)](docs/fast-mode-accounting.md).
The Dashboard shows regular, Fast, and all tokens, with a mode filter. Raw Fast tokens are never multiplied. Fast cost uses Standard base rates multiplied by ChatGPT Codex credit factors: 2.5 for GPT-6.1 Sol, GPT-6 Astra/Sol/Luna, the GPT-5.6 family, and GPT-5.5; 2 for GPT-5.4. Models without a confirmed factor remain unpriced. History is classified only from explicit evidence for the same turn; unconfirmed usage is provisionally regular. See the [Fast accounting, backfill, and API guide (Chinese)](docs/fast-mode-accounting.md).

The estimator streams the normalized events that already passed source de-duplication and attribution filtering. It runs at query time, writes no cost data to SQLite, and leaves existing token totals unchanged. Arithmetic uses fixed-point nano-USD. Cached Input and Cache Write are removed from regular Input, and Reasoning is already included in Output, so neither is charged twice.

The bundled Standard text price catalog was updated on **2026-09-23**. All values are USD / 1M tokens:
The bundled Standard text price catalog was updated on **2026-10-07**. All values are USD / 1M tokens:

| Model | Input | Cached | Cache Write | Output |
|---|---:|---:|---:|---:|
| [GPT-6 Astra](https://developers.openai.com/api/docs/models/gpt-6-astra) | 10.00 | 1.00 | 12.50 | 50.00 |
| [GPT-6.1 Sol](https://developers.openai.com/api/docs/models/gpt-6.1-sol) | 2.00 | 0.10 | 2.50 | 10.00 |
| [GPT-6 Sol](https://developers.openai.com/api/docs/models/gpt-6-sol) | 2.00 | 0.20 | 2.50 | 10.00 |
| [GPT-6 Luna](https://developers.openai.com/api/docs/models/gpt-6-luna) | 0.10 | 0.01 | 0.125 | 0.50 |
| [GPT-5.6 Sol](https://developers.openai.com/api/docs/models/gpt-5.6-sol) | 5.00 | 0.50 | 6.25 | 30.00 |
Expand All @@ -211,14 +212,16 @@ The bundled Standard text price catalog was updated on **2026-09-23**. All value
| [GPT-5.3-Codex](https://developers.openai.com/api/docs/models/gpt-5.3-codex) | 1.75 | 0.175 | not published | 14.00 |
| [GPT-5.2-Codex](https://developers.openai.com/api/docs/models/gpt-5.2-codex) | 1.75 | 0.175 | not published | 14.00 |

GPT-6 Astra/Sol/Luna and GPT-5.6 Cache Write use the official 1.25× regular Input rule. Local JSONL stores cumulative token activity and cannot reliably reconstruct the per-request boundaries used for API billing. The estimator uses the Standard base rates above, adjusts explicitly identified Fast usage, and does not infer long-context multipliers. The UI always shows estimated cost together with token pricing coverage; unknown models are never treated as zero-cost.
GPT-6.1 Sol, GPT-6 Astra/Sol/Luna and GPT-5.6 Cache Write use the official 1.25× regular Input rule. Local JSONL stores cumulative token activity and cannot reliably reconstruct the per-request boundaries used for API billing. The estimator uses the Standard base rates above, adjusts explicitly identified Fast usage, and does not infer long-context multipliers. The UI always shows estimated cost together with token pricing coverage; unknown models are never treated as zero-cost.

Separate `codex-auto-review` usage defaults to zero cost while retaining token counts. This local accounting policy follows the Tibo statement reported by the maintainer; as of 2026-10-07, an explicit official public free-pricing statement has not been verified. It is not a published API price. Existing manual overrides still take precedence; remove that override to restore zero pricing. Only this exact model ID is covered: enabling Auto-review does not make main-model usage free. Historical estimates also use the current policy.

Internal models can be explicitly mapped to one built-in public model or assigned custom rates in the Dashboard. Overrides take effect without restarting:

```json
{
"pricing_overrides": {
"codex-auto-review": { "alias_of": "gpt-5.6-luna" },
"internal-review": { "alias_of": "gpt-6.1-sol" },
"internal-model": {
"input_usd_per_million": "1.00",
"cached_input_usd_per_million": "0.10",
Expand Down
11 changes: 7 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -191,15 +191,16 @@ Dashboard 固定为“概览 / 每日 / 明细”三个一级视图。概览默

### 5. API 等价成本

Dashboard 展示常规、Fast 和全部 token,并提供模式筛选。Fast 原始 token 不乘倍率;费用按 Standard 基价乘 ChatGPT Codex 额度倍率:Astra、GPT-5.6 系列、GPT-5.5 为 2.5 倍,GPT-5.4 为 2 倍。未知倍率的型号保留为未定价。历史模式仅凭同一 turn 的明确证据补齐,未确认部分暂归常规。详见 [Fast 模式口径、历史补齐与接口说明](docs/fast-mode-accounting.md)。
Dashboard 展示常规、Fast 和全部 token,并提供模式筛选。Fast 原始 token 不乘倍率;费用按 Standard 基价乘 ChatGPT Codex 额度倍率:GPT-6.1 Sol、GPT-6 Astra/Sol/Luna、GPT-5.6 系列、GPT-5.5 为 2.5 倍,GPT-5.4 为 2 倍。未知倍率的型号保留为未定价。历史模式仅凭同一 turn 的明确证据补齐,未确认部分暂归常规。详见 [Fast 模式口径、历史补齐与接口说明](docs/fast-mode-accounting.md)。

费用在查询时流式读取已经过去重、归属规则筛选后的规范事件,不写入 SQLite,也不会改变原有 Token 统计。计算使用定点 nano-USD:Cached Input 与 Cache Write 从 Input 中扣除,Reasoning 已包含在 Output 中,不会重复收费。

内置 Standard 文本价格目录更新于 **2026-09-23**,单位均为 USD / 1M Token:
内置 Standard 文本价格目录更新于 **2026-10-07**,单位均为 USD / 1M Token:

| 模型 | Input | Cached | Cache Write | Output |
|---|---:|---:|---:|---:|
| [GPT-6 Astra](https://developers.openai.com/api/docs/models/gpt-6-astra) | 10.00 | 1.00 | 12.50 | 50.00 |
| [GPT-6.1 Sol](https://developers.openai.com/api/docs/models/gpt-6.1-sol) | 2.00 | 0.10 | 2.50 | 10.00 |
| [GPT-6 Sol](https://developers.openai.com/api/docs/models/gpt-6-sol) | 2.00 | 0.20 | 2.50 | 10.00 |
| [GPT-6 Luna](https://developers.openai.com/api/docs/models/gpt-6-luna) | 0.10 | 0.01 | 0.125 | 0.50 |
| [GPT-5.6 Sol](https://developers.openai.com/api/docs/models/gpt-5.6-sol) | 5.00 | 0.50 | 6.25 | 30.00 |
Expand All @@ -211,14 +212,16 @@ Dashboard 展示常规、Fast 和全部 token,并提供模式筛选。Fast 原
| [GPT-5.3-Codex](https://developers.openai.com/api/docs/models/gpt-5.3-codex) | 1.75 | 0.175 | 未公开 | 14.00 |
| [GPT-5.2-Codex](https://developers.openai.com/api/docs/models/gpt-5.2-codex) | 1.75 | 0.175 | 未公开 | 14.00 |

GPT-6 Astra/Sol/Luna 和 GPT-5.6 的 Cache Write 使用官方“普通 Input 的 1.25 倍”规则。本机 JSONL 保存的是累计 Token 活动,不能可靠还原 API 账单中的逐请求边界;估算采用上表 Standard 基价,并对明确 Fast 的用量折算,不推断长上下文倍率。页面始终同时展示费用和 Token 定价覆盖率,未知模型不会被当成零费用。
GPT-6.1 Sol、GPT-6 Astra/Sol/Luna 和 GPT-5.6 的 Cache Write 使用官方“普通 Input 的 1.25 倍”规则。本机 JSONL 保存的是累计 Token 活动,不能可靠还原 API 账单中的逐请求边界;估算采用上表 Standard 基价,并对明确 Fast 的用量折算,不推断长上下文倍率。页面始终同时展示费用和 Token 定价覆盖率,未知模型不会被当成零费用。

本软件将独立的 `codex-auto-review` 默认按零价估算,Token 仍正常统计。这是根据维护者收到的 Tibo 免费说明采用的本地计价规则;截至 2026-10-07,尚未找到明确的官方公开免费条款,不作为官方 API 报价。已有手动覆写继续优先,删除该项覆写后恢复默认零价。只匹配这一模型标识,不会使开启 Auto-review 的主模型免费;历史估算同样使用当前规则。

内部模型可以在 Dashboard 的“定价设置”中映射到一个明确的内置公开模型,或填写自定义单价。等价配置如下,保存后无需重启:

```json
{
"pricing_overrides": {
"codex-auto-review": { "alias_of": "gpt-5.6-luna" },
"internal-review": { "alias_of": "gpt-6.1-sol" },
"internal-model": {
"input_usd_per_million": "1.00",
"cached_input_usd_per_million": "0.10",
Expand Down
4 changes: 2 additions & 2 deletions docs/fast-mode-accounting.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@ schema v7 到 v8 为增量迁移,不清除账目或扫描游标,不要求全

## 费用口径

常规沿用既有 Standard API 价格及本机定价覆写。Fast 的相同 token 分类基础费用乘以 ChatGPT Codex 额度倍率:GPT-6 Astra/Sol/Luna、GPT-5.6 Sol/Terra/Luna、GPT-5.5 为 2.5;GPT-5.4 为 2。别名与日期快照通过模型目录解析,不将 mini、Spark、未知模型或未确认的自定义模型自动套入倍率。
常规沿用既有 Standard API 价格及本机定价覆写。Fast 的相同 token 分类基础费用乘以 ChatGPT Codex 额度倍率:GPT-6.1 Sol、GPT-6 Astra/Sol/Luna、GPT-5.6 Sol/Terra/Luna、GPT-5.5 为 2.5;GPT-5.4 为 2。别名与日期快照通过模型目录解析,不将 mini、Spark、未知模型或未确认的自定义模型自动套入倍率。

界面统一称为“API 等价成本”,以一句话显示 API 价格和 Fast 额度规则的更新日期。计算仍为额度倍率加权的估算,不是实际 API Priority 账单,也不是账号额度扣除记录。未知单价、缺失缓存写入价格、缺少 Fast 倍率的部分继续显示为未定价。

倍率核对于 2026-09-23:[OpenAI Fast 模式说明](https://learn.chatgpt.com/docs/agent-configuration/speed)。历史估算使用当前目录与倍率,不模拟历史账单。
GPT-6.1 Sol 倍率核对于 2026-10-07,既有型号规则保留;这里使用订阅内含额度倍率(2.5x),不是购买 credits / API 的 Fast 倍率(2x)。来源:[OpenAI Fast 模式说明](https://learn.chatgpt.com/docs/agent-configuration/speed)。历史估算使用当前目录与倍率,不模拟历史账单。

## API 与导出

Expand Down
31 changes: 31 additions & 0 deletions docs/releases/v2.7.2.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# codex-usage v2.7.2

新增 GPT-6.1 Sol 定价,并将独立的 `codex-auto-review` 审批用量默认按零费用估算。Token 统计保持不变,无需为此次定价更新重建历史数据。

| 模型 | Input | Cached Input | Cache Write | Output |
|---|---:|---:|---:|---:|
| [GPT-6.1 Sol](https://developers.openai.com/api/docs/models/gpt-6.1-sol) | 2.00 | 0.10 | 2.50 | 10.00 |

以上为 2026-10-07 核对的 Standard 短上下文文本价格,单位 USD / 1M Token。

- 支持 `gpt-6.1-sol`、有效日期后缀快照和本机模型映射。明确标记的 Fast 用量沿用 [Codex 订阅内含额度的 2.5× 倍率](https://learn.chatgpt.com/docs/agent-configuration/speed),与 API / 购买 credits 的 Fast 计费口径不同。
- `codex-auto-review` 默认 Input、Cached Input、Cache Write、Output 均为 $0;常规和 Fast 视图均保留原始 Token,并计入已定价覆盖率。只匹配这一模型标识,开启 Auto-review 不会使主模型免费。
- Auto-review 零价是根据维护者收到的 Tibo 免费说明采用的软件计价规则;截至 2026-10-07,尚未核实明确的官方公开免费条款,不作为官方 API 报价。
- **已有 Auto-review 手动覆写继续优先。** 在定价设置中删除该项覆写或选择“使用默认零价”并保存,即可恢复默认零价。
- Dashboard、synthetic Demo 和中英文文档同步更新;未知模型仍保持未定价,历史估算使用当前规则。
- 固定两个日期回归测试的时区,避免因运行电脑的时区不同产生误报。

验证:完整 Go 测试、`go vet`、27 项 Dashboard / Demo 浏览器测试,覆盖费用汇总、Session 明细、Fast、零价覆盖率和旧覆写恢复默认后的缓存刷新。

---

Add GPT-6.1 Sol pricing and default separate `codex-auto-review` approval usage to zero cost. Raw token counts are retained; this pricing update requires no historical rebuild.

- GPT-6.1 Sol Standard short-context prices per 1M tokens: **$2 input, $0.10 cached input, $2.50 cache write, $10 output**, verified on October 7, 2026. Supports dated snapshots, explicit aliases, and the existing 2.5× Codex included-subscription Fast weighting.
- Auto-review defaults to zero in both regular and Fast estimates, with tokens included in pricing coverage. This exact-model rule does not make main-model usage free.
- This is a local accounting policy based on a Tibo statement reported by the maintainer, not a verified published API price.
- **Existing manual Auto-review overrides still take precedence.** Remove the override or select “Use default zero pricing” and save to restore the default.
- Update the Dashboard, synthetic Demo, and bilingual documentation. Unknown models remain unpriced; historical estimates use current rules.
- Make two existing date regression tests independent of the host timezone.

Validation: full Go tests, `go vet`, and 27 browser regressions covering aggregate and session estimates, Fast weighting, zero-cost coverage, and cache invalidation after resetting an override.
2 changes: 1 addition & 1 deletion internal/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ import (
)

var (
Version = "2.7.1"
Version = "2.7.2"
Commit = "dev"
BuildDate = "unknown"
)
Expand Down
63 changes: 63 additions & 0 deletions internal/pricing/auto_review_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package pricing

import (
"testing"

"github.com/zJay26/codex-usage/internal/model"
)

func TestAutoReviewDefaultZeroPreservesUsage(t *testing.T) {
for _, basis := range []string{Basis, FastWeightedBasis} {
for _, tier := range []string{"default", "fast"} {
for _, usage := range []model.TokenUsage{
{Input: 1000, CachedInput: 200, CacheWriteInput: 100, Output: 100, ReasoningOutput: 50, Total: 1100},
{Total: 1100},
} {
b, err := NewBuilderForBasis(nil, basis)
if err != nil {
t.Fatal(err)
}
e := model.UsageEvent{Model: " CODEX-AUTO-REVIEW ", Usage: usage, ServiceMode: model.ModeFromTier(tier, "test")}
if err := b.Add(e); err != nil {
t.Fatal(err)
}
r := b.Report()
if r.Summary.USD != "0.000000000" || r.Summary.StandardBaseUSD != "0.000000000" || r.Summary.FastSurchargeUSD != "0.000000000" || r.Summary.PricedTokens != 1100 || r.Summary.UnpricedTokens != 0 || r.Summary.CoverageRatio != 1 || len(r.Summary.Reasons) != 0 {
t.Fatalf("%s/%s: %+v", basis, tier, r.Summary)
}
if len(r.Models) != 1 || !r.Models[0].Usage.Equal(usage) {
t.Fatalf("usage changed: %+v", r.Models)
}
}
}
}
}

func TestAutoReviewExplicitOverrideStillWins(t *testing.T) {
for _, override := range []Override{
{AliasOf: "gpt-6.1-sol"},
{InputUSDPerMillion: "2", CachedInputUSDPerMillion: "0.1", CacheWriteInputUSDPerMillion: "2.5", OutputUSDPerMillion: "10"},
} {
b, err := NewBuilder(map[string]Override{"codex-auto-review": override})
if err != nil {
t.Fatal(err)
}
if err := b.Add(model.UsageEvent{Model: "codex-auto-review", Usage: model.TokenUsage{Input: 1000000, Total: 1000000}}); err != nil {
t.Fatal(err)
}
if got := b.Report().Summary; got.USD != "2.000000000" || got.UnpricedTokens != 0 {
t.Fatalf("override lost: %+v", got)
}
}
}

func TestAutoReviewDoesNotHideInvalidUsageOrMakeMainModelFree(t *testing.T) {
bad, err := evaluateEvent(model.UsageEvent{Model: "codex-auto-review", Usage: model.TokenUsage{Input: 10, CachedInput: 11, Total: 10}}, nil)
if err != nil || bad.unpricedTokens != 10 || len(bad.reasons) != 1 || bad.reasons[0].Kind != "invalid_token_categories" {
t.Fatalf("invalid usage hidden: %+v %v", bad, err)
}
main, err := evaluateEvent(model.UsageEvent{Model: "gpt-6.1-sol", AgentType: "guardian", Usage: model.TokenUsage{Input: 1000000, Total: 1000000}}, nil)
if err != nil || main.regularNano != 2000000000 {
t.Fatalf("main model incorrectly free: %+v %v", main, err)
}
}
24 changes: 22 additions & 2 deletions internal/pricing/catalog.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (
)

const (
CatalogAsOf = "2026-09-23"
CatalogAsOf = "2026-10-07"
Currency = "USD"
Basis = "current_standard_api_text_token_prices"
)
Expand Down Expand Up @@ -45,6 +45,20 @@ type ResolvedRate struct {
}

var builtInCatalog = []CatalogEntry{
{
// Local accounting policy, not a published API model price. Keep exact
// matching and allow explicit overrides from older installations.
Model: "codex-auto-review", DisplayName: "Codex Auto-review",
InputUSDPerMillion: "0.00", CachedInputUSDPerMillion: "0.00",
CacheWriteInputUSDPerMillion: "0.00", OutputUSDPerMillion: "0.00",
},
{
Model: "gpt-6.1-sol", DisplayName: "GPT-6.1 Sol",
SnapshotPatterns: []string{"gpt-6.1-sol-YYYY-MM-DD"},
InputUSDPerMillion: "2.00", CachedInputUSDPerMillion: "0.10",
CacheWriteInputUSDPerMillion: "2.50", OutputUSDPerMillion: "10.00",
Source: "https://developers.openai.com/api/docs/models/gpt-6.1-sol",
},
{
Model: "gpt-6-astra", DisplayName: "GPT-6 Astra",
SnapshotPatterns: []string{"gpt-6-astra-YYYY-MM-DD"},
Expand Down Expand Up @@ -147,7 +161,7 @@ func NormalizeOverrides(input map[string]Override) (map[string]Override, error)
raw.CachedInputUSDPerMillion = strings.TrimSpace(raw.CachedInputUSDPerMillion)
raw.CacheWriteInputUSDPerMillion = strings.TrimSpace(raw.CacheWriteInputUSDPerMillion)
raw.OutputUSDPerMillion = strings.TrimSpace(raw.OutputUSDPerMillion)
if _, ok := resolveBuiltIn(model); ok {
if _, ok := resolveBuiltIn(model); ok && model != "codex-auto-review" {
return nil, fmt.Errorf("内置官方模型 %q 不能被本机覆写", model)
}
if raw.AliasOf != "" {
Expand Down Expand Up @@ -205,6 +219,12 @@ func Resolve(model string, overrides map[string]Override) (ResolvedRate, bool, e
func resolveBuiltIn(model string) (CatalogEntry, bool) {
model = strings.ToLower(strings.TrimSpace(model))
for _, entry := range builtInCatalog {
if len(entry.SnapshotPatterns) == 0 {
if model == entry.Model {
return entry, true
}
continue
}
if modelMatches(model, entry.Model) {
return entry, true
}
Expand Down
11 changes: 8 additions & 3 deletions internal/pricing/catalog_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ func TestResolveGPT6SolAndLunaPricing(t *testing.T) {
input, cached, cacheWrite, output int64
}{
{"gpt-6-sol", 2000, 200, 2500, 10000},
{"gpt-6.1-sol", 2000, 100, 2500, 10000},
{"gpt-6-luna", 100, 10, 125, 500},
} {
t.Run(tt.name, func(t *testing.T) {
Expand Down Expand Up @@ -66,13 +67,17 @@ func TestResolveBuiltInAndVersionedSnapshot(t *testing.T) {

func TestEveryCatalogModelAndSnapshotPatternResolvesExactly(t *testing.T) {
for _, entry := range Catalog() {
for _, name := range []string{entry.Model, entry.Model + "-2026-07-31"} {
names := []string{entry.Model}
if len(entry.SnapshotPatterns) > 0 {
names = append(names, entry.Model+"-2026-07-31")
}
for _, name := range names {
rate, ok, err := Resolve(name, nil)
if err != nil || !ok || rate.CanonicalModel != entry.Model {
t.Fatalf("%q did not resolve to %q: rate=%#v ok=%v err=%v", name, entry.Model, rate, ok, err)
}
}
if len(entry.SnapshotPatterns) == 0 {
if len(entry.SnapshotPatterns) == 0 && entry.Model != "codex-auto-review" {
t.Fatalf("catalog entry %q does not publish its snapshot pattern", entry.Model)
}
}
Expand Down Expand Up @@ -146,7 +151,7 @@ func TestNormalizeOverridesRejectsInvalidValues(t *testing.T) {
}

func TestModelMatchingDoesNotGuessFamilies(t *testing.T) {
for _, name := range []string{"gpt-6", "gpt-6-sol-preview", "gpt-6-luna-turbo", "gpt-6-luna-2026-02-30", "gpt-5.6-turbo", "gpt-5.4-pro", "codex-auto-review", ""} {
for _, name := range []string{"gpt-6", "gpt-6-sol-preview", "gpt-6-luna-turbo", "gpt-6-luna-2026-02-30", "gpt-5.6-turbo", "gpt-5.4-pro", "gpt-6.1-sol-preview", "codex-auto-review-2026-10-07", "internal-review", ""} {
if _, ok, err := Resolve(name, nil); err != nil || ok {
t.Fatalf("model %q should remain unpriced, ok=%v err=%v", name, ok, err)
}
Expand Down
Loading
Loading