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
2 changes: 1 addition & 1 deletion copilot.go
Original file line number Diff line number Diff line change
Expand Up @@ -558,7 +558,7 @@ func (CopilotHarness) DefaultModels() []ModelDefault {
// entry as the default. Dotted Anthropic IDs are distinct from Claude Code's
// hyphenated IDs.
return []ModelDefault{
{Name: "GPT-5.6 Sol", ID: "gpt-5.6-sol"},
{Name: "GPT-5.6 Sol", ID: modelGPT56SolID},
{Name: "Claude Sonnet 5", ID: modelClaudeSonnet5ID},
{Name: "Claude Opus 5", ID: modelClaudeOpus5ID, Tier: modelTierMax},
{Name: "Claude Opus 4.8", ID: "claude-opus-4.8"},
Expand Down
2 changes: 2 additions & 0 deletions harness.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ const (
modelGPT54MiniID = "gpt-5.4-mini"
modelGPT54ID = "gpt-5.4"
modelGPT55ID = "gpt-5.5"
modelGPT56SolID = "gpt-5.6-sol"
modelDaybreakBlueID = "gpt-daybreak-blue-latest"

modelTierMid = "mid"
modelTierHigh = "high"
Expand Down
14 changes: 11 additions & 3 deletions pricing.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ type modelPrice struct {
// no dollar amount. Rows for backends that report cost directly remain here so
// tests can require every default model to have a known price.
//
// Prices are USD rates per million tokens as of 2026-08. Copilot-only rows use
// Prices are USD rates per million tokens. Copilot-only rows use
// the token prices returned by CLI 1.0.80's models.list, converting one AI
// credit to $0.01. Update this table with each backend's DefaultModels list.
//
Expand All @@ -38,7 +38,9 @@ var modelPricing = map[string]modelPrice{
"claude-haiku-4.5": {In: 1.00, Out: 5.00, CachedIn: 0.10, CacheWrite: 1.25},

// OpenAI
"gpt-5.6-sol": {In: 2.00, Out: 10.00, CachedIn: 0.20, CacheWrite: 2.50},
// Sol's promotional standard rates apply at least through 2026-11-21; recheck then.
// https://developers.openai.com/api/docs/pricing
modelGPT56SolID: {In: 4.00, Out: 20.00, CachedIn: 0.40, CacheWrite: 5.00},
"gpt-5.6-terra": {In: 2.00, Out: 12.00, CachedIn: 0.20, CacheWrite: 2.50},
"gpt-5.6-luna": {In: 0.20, Out: 1.20, CachedIn: 0.02, CacheWrite: 0.25},
modelGPT55ID: {In: 5.00, Out: 30.00, CachedIn: 0.50},
Expand Down Expand Up @@ -68,7 +70,13 @@ const perMillion = 1e6
// subset. CacheWriteTokens is separate only for models with a dedicated write
// rate; it remains ordinary input when CacheWrite is zero.
func CostFromUsage(model string, usage Usage) float64 {
price, ok := modelPricing[normalizeModelID(model)]
model = normalizeModelID(model)
// Daybreak Blue currently aliases Sol and shares its pricing.
// https://developers.openai.com/api/docs/models/gpt-daybreak-blue-latest
if model == modelDaybreakBlueID {
model = modelGPT56SolID
}
price, ok := modelPricing[model]
if !ok {
return 0
}
Expand Down
39 changes: 39 additions & 0 deletions pricing_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package harness

import (
"math"
"testing"
)

func TestCostFromUsage_gpt56SolAndDaybreakBasePricing(t *testing.T) {
t.Parallel()

tests := []struct {
name string
usage Usage
want float64
}{
{"uncached input", Usage{InputTokens: 100_000}, 0.40},
{"output", Usage{OutputTokens: 10_000}, 0.20},
{"cache reads", Usage{InputTokens: 100_000, CacheReadTokens: 100_000}, 0.04},
{"cache writes", Usage{InputTokens: 100_000, CacheWriteTokens: 100_000}, 0.50},
{"standard scan", Usage{InputTokens: 100_000, OutputTokens: 10_000}, 0.60},
{"mixed", Usage{InputTokens: 100_000, OutputTokens: 10_000, CacheReadTokens: 10_000, CacheWriteTokens: 20_000}, 0.584},
}
for _, model := range []struct{ name, id string }{
{"sol", "gpt-5.6-sol"},
{"sol_normalized", "openai/gpt-5.6-sol[1m]"},
{"daybreak", "gpt-daybreak-blue-latest"},
{"daybreak_normalized", "openai/gpt-daybreak-blue-latest[1m]"},
} {
t.Run(model.name, func(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := CostFromUsage(model.id, tt.usage); math.Abs(got-tt.want) > 1e-9 {
t.Errorf("CostFromUsage(%q, %+v) = %v, want %v", model.id, tt.usage, got, tt.want)
}
})
}
})
}
}