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
17 changes: 15 additions & 2 deletions cmd/genprices/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,16 @@ func buildBlock(table map[string]otel.Pricing) string {
}
fmt.Fprintf(&b, " %-*s %s%s\n", pad, entry, fmtPrice(table[m].OutputUSDPer1M/1000), comma)
}
b.WriteString(" },\n")
b.WriteString(" \"cache_read\": {\n")
for i, m := range models {
entry := fmt.Sprintf("%q:", m)
comma := ","
if i == len(models)-1 {
comma = ""
}
fmt.Fprintf(&b, " %-*s %s%s\n", pad, entry, fmtCatalogPrice(table[m].EffectiveCacheReadMultiplier()), comma)
}
b.WriteString(" }\n")
b.WriteString("}'")
return b.String()
Expand All @@ -105,17 +115,19 @@ func buildTypeScript(table map[string]otel.Pricing) string {
b.WriteString("export interface ModelPricing {\n")
b.WriteString("\tinputUsdPerMillion: number;\n")
b.WriteString("\toutputUsdPerMillion: number;\n")
b.WriteString("\tcacheReadMultiplier: number;\n")
b.WriteString("}\n\n")
fmt.Fprintf(&b, "export const PRICING_VERSION = %q;\n\n", version)
b.WriteString("export const MODEL_PRICING: Readonly<Record<string, ModelPricing>> = Object.freeze({\n")
for _, model := range models {
price := table[model]
fmt.Fprintf(
&b,
"\t%q: { inputUsdPerMillion: %s, outputUsdPerMillion: %s },\n",
"\t%q: { inputUsdPerMillion: %s, outputUsdPerMillion: %s, cacheReadMultiplier: %s },\n",
model,
fmtCatalogPrice(price.InputUSDPer1M),
fmtCatalogPrice(price.OutputUSDPer1M),
fmtCatalogPrice(price.EffectiveCacheReadMultiplier()),
)
}
b.WriteString("});\n")
Expand All @@ -137,10 +149,11 @@ func pricingVersion(table map[string]otel.Pricing, models []string) string {
price := table[model]
fmt.Fprintf(
&canonical,
"%s\x00%s\x00%s\n",
"%s\x00%s\x00%s\x00%s\n",
model,
fmtCatalogPrice(price.InputUSDPer1M),
fmtCatalogPrice(price.OutputUSDPer1M),
fmtCatalogPrice(price.EffectiveCacheReadMultiplier()),
)
}
sum := sha256.Sum256([]byte(canonical.String()))
Expand Down
2 changes: 1 addition & 1 deletion cmd/genprices/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ func TestBuildTypeScriptIsSortedAndVersioned(t *testing.T) {
if strings.Index(got, `"a-model"`) > strings.Index(got, `"z-model"`) {
t.Fatalf("generated TypeScript models are not sorted:\n%s", got)
}
if !strings.Contains(got, `"a-model": { inputUsdPerMillion: 0.071, outputUsdPerMillion: 0.463 }`) {
if !strings.Contains(got, `"a-model": { inputUsdPerMillion: 0.071, outputUsdPerMillion: 0.463, cacheReadMultiplier: 0.5 }`) {
t.Fatalf("generated TypeScript changed catalog units or decimal formatting:\n%s", got)
}

Expand Down
109 changes: 93 additions & 16 deletions install/cc-statusline.sh
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@
# Renders:
# WEAVE ROUTER — claude-sonnet-4-5 ← claude-opus-4-7 · saved $1.23 · 12.4k in / 3.1k out / 45.2k cached
#
# Pricing source of truth: router/eval/pricing.py. Keep these maps in lockstep
# when prices change. Cache multipliers (1.25× / 0.1×) follow Anthropic's
# published cache pricing and are stable across the Claude family.
# Pricing source of truth: internal/router/catalog. Input/output prices and
# cache-read multipliers are generated by cmd/genprices. Cache creation remains
# at 1.25× input pending TTL-aware pricing.

set -euo pipefail

Expand Down Expand Up @@ -609,6 +609,83 @@ prices='{
"z-ai/glm-5.2": 0.0044,
"z-ai/glm-5.3": 0.0044,
"z-ai/glm-5.3-flash": 0.0005
},
"cache_read": {
"claude-fable-5": 0.1,
"claude-haiku-4-5": 0.1,
"claude-opus-4-0": 0.1,
"claude-opus-4-1": 0.1,
"claude-opus-4-5": 0.1,
"claude-opus-4-6": 0.1,
"claude-opus-4-7": 0.1,
"claude-opus-4-8": 0.1,
"claude-opus-5": 0.1,
"claude-sonnet-4-5": 0.1,
"claude-sonnet-4-6": 0.1,
"claude-sonnet-5": 0.1,
"deepseek/deepseek-v4-flash": 0.2,
"deepseek/deepseek-v4-pro": 0.11494252873563218,
"deepseek/deepseek-v4-pro-0813": 0.11494252873563218,
"gemini-2.0-flash": 0.25,
"gemini-2.0-flash-lite": 0.25,
"gemini-2.5-flash": 0.1,
"gemini-2.5-flash-lite": 0.1,
"gemini-2.5-pro": 0.1,
"gemini-3-flash-preview": 0.1,
"gemini-3-pro-preview": 0.1,
"gemini-3.1-flash-lite-preview": 0.1,
"gemini-3.1-pro-preview": 0.1,
"gemini-3.5-flash": 0.1,
"gemini-3.5-flash-lite": 0.1,
"gemini-3.6-flash": 0.1,
"gemini-3.7-flash": 0.1,
"google/gemma-4-26b-a4b-it": 0.1,
"gpt-4.1": 0.25,
"gpt-4.1-mini": 0.25,
"gpt-4.1-nano": 0.25,
"gpt-4o": 0.5,
"gpt-4o-mini": 0.5,
"gpt-5": 0.1,
"gpt-5-chat": 0.1,
"gpt-5-mini": 0.1,
"gpt-5-nano": 0.1,
"gpt-5.4": 0.1,
"gpt-5.4-mini": 0.1,
"gpt-5.4-nano": 0.1,
"gpt-5.4-pro": 1,
"gpt-5.5": 0.1,
"gpt-5.5-mini": 0.1,
"gpt-5.5-nano": 0.1,
"gpt-5.5-pro": 1,
"gpt-5.6-luna": 0.1,
"gpt-5.6-luna-pro": 0.1,
"gpt-5.6-sol": 0.1,
"gpt-5.6-sol-pro": 0.1,
"gpt-5.6-terra": 0.1,
"grok-4.5": 0.25,
"grok-4.6": 0.25,
"minimax/minimax-m2.7": 0.2,
"minimax/minimax-m3": 0.2,
"mistralai/mistral-small-2603": 0.1,
"moonshotai/kimi-k2.5": 0.5,
"moonshotai/kimi-k2.6": 0.1684,
"moonshotai/kimi-k2.7": 0.2,
"moonshotai/kimi-k3": 0.1,
"qwen/qwen3-235b-a22b-2507": 0.5,
"qwen/qwen3-30b-a3b-instruct-2507": 0.1684,
"qwen/qwen3-coder": 0.1684,
"qwen/qwen3-coder-next": 0.5,
"qwen/qwen3-next-80b-a3b-instruct": 0.5,
"qwen/qwen3.5-flash-02-23": 0.1,
"qwen/qwen3.6-35b-a3b": 0.1,
"qwen/qwen3.7-plus": 0.2,
"qwen/qwen3.8-max": 0.125,
"xiaomi/mimo-v2.5-pro": 0.1,
"z-ai/glm-5": 0.2,
"z-ai/glm-5.1": 0.18571428571428572,
"z-ai/glm-5.2": 0.18571428571428572,
"z-ai/glm-5.3": 0.18571428571428572,
"z-ai/glm-5.3-flash": 0.2
}
}'
# END_GENERATED_PRICES
Expand Down Expand Up @@ -693,12 +770,9 @@ if [[ -n "$transcript_path" && -f "$transcript_path" ]]; then
# Compute a session running total: savings across every assistant turn
# whose marker reports a requested ≠ routed swap, plus cumulative token
# counts across every assistant turn (rerouted or not — total work the
# session has done). cache_creation is priced at 1.25× input, cache_read
# at 0.1× — both ratios are stable across the Claude family and a no-op
# when the provider doesn't return those fields. Cache reads ARE included
# in the savings comparison: both costs apply the same 0.1× weight to
# cache_read_input_tokens, so the delta reflects the model-price
# difference on the cached portion as well.
# session has done). cache_creation is priced at 1.25× input; cache_read
# uses each model's generated catalog multiplier. Both are no-ops when the
# provider does not return those fields.
#
# The marker regex tolerates the optional "(<provider>)" segment and a
# `[1m]` / `-YYYYMMDD` suffix on either model name so transcripts written
Expand Down Expand Up @@ -731,14 +805,17 @@ if [[ -n "$transcript_path" && -f "$transcript_path" ]]; then
} as $t |
(if $requested == "" or $requested == $rm then 0
else
($p.input[$rm] // null) as $rin | ($p.output[$rm] // null) as $rout |
($p.input[$requested] // null) as $sin | ($p.output[$requested] // null) as $sout |
if ($rin == null or $rout == null or $sin == null or $sout == null) then 0
($p.input[$rm] // null) as $rin | ($p.output[$rm] // null) as $rout |
($p.cache_read[$rm] // null) as $rcr |
($p.input[$requested] // null) as $sin | ($p.output[$requested] // null) as $sout |
($p.cache_read[$requested] // null) as $scr |
if ($rin == null or $rout == null or $rcr == null or $sin == null or $sout == null or $scr == null) then 0
else
(($t.in + 1.25 * $t.cwrt + 0.1 * $t.crd) / 1000) as $input_units |
($t.out / 1000) as $output_units |
($input_units * $rin + $output_units * $rout) as $routed_cost |
($input_units * $sin + $output_units * $sout) as $requested_cost |
(($t.in + 1.25 * $t.cwrt + $rcr * $t.crd) / 1000) as $routed_input_units |
(($t.in + 1.25 * $t.cwrt + $scr * $t.crd) / 1000) as $requested_input_units |
($t.out / 1000) as $output_units |
($routed_input_units * $rin + $output_units * $rout) as $routed_cost |
($requested_input_units * $sin + $output_units * $sout) as $requested_cost |
($requested_cost - $routed_cost)
end
end) as $savings |
Expand Down
109 changes: 93 additions & 16 deletions install/install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3514,9 +3514,9 @@ cat > "$statusline_file" << 'STATUSLINE_EOF'
# Renders:
# WEAVE ROUTER — claude-sonnet-4-5 ← claude-opus-4-7 · saved $1.23 · 12.4k in / 3.1k out / 45.2k cached
#
# Pricing source of truth: router/eval/pricing.py. Keep these maps in lockstep
# when prices change. Cache multipliers (1.25× / 0.1×) follow Anthropic's
# published cache pricing and are stable across the Claude family.
# Pricing source of truth: internal/router/catalog. Input/output prices and
# cache-read multipliers are generated by cmd/genprices. Cache creation remains
# at 1.25× input pending TTL-aware pricing.

set -euo pipefail

Expand Down Expand Up @@ -4107,6 +4107,83 @@ prices='{
"z-ai/glm-5.2": 0.0044,
"z-ai/glm-5.3": 0.0044,
"z-ai/glm-5.3-flash": 0.0005
},
"cache_read": {
"claude-fable-5": 0.1,
"claude-haiku-4-5": 0.1,
"claude-opus-4-0": 0.1,
"claude-opus-4-1": 0.1,
"claude-opus-4-5": 0.1,
"claude-opus-4-6": 0.1,
"claude-opus-4-7": 0.1,
"claude-opus-4-8": 0.1,
"claude-opus-5": 0.1,
"claude-sonnet-4-5": 0.1,
"claude-sonnet-4-6": 0.1,
"claude-sonnet-5": 0.1,
"deepseek/deepseek-v4-flash": 0.2,
"deepseek/deepseek-v4-pro": 0.11494252873563218,
"deepseek/deepseek-v4-pro-0813": 0.11494252873563218,
"gemini-2.0-flash": 0.25,
"gemini-2.0-flash-lite": 0.25,
"gemini-2.5-flash": 0.1,
"gemini-2.5-flash-lite": 0.1,
"gemini-2.5-pro": 0.1,
"gemini-3-flash-preview": 0.1,
"gemini-3-pro-preview": 0.1,
"gemini-3.1-flash-lite-preview": 0.1,
"gemini-3.1-pro-preview": 0.1,
"gemini-3.5-flash": 0.1,
"gemini-3.5-flash-lite": 0.1,
"gemini-3.6-flash": 0.1,
"gemini-3.7-flash": 0.1,
"google/gemma-4-26b-a4b-it": 0.1,
"gpt-4.1": 0.25,
"gpt-4.1-mini": 0.25,
"gpt-4.1-nano": 0.25,
"gpt-4o": 0.5,
"gpt-4o-mini": 0.5,
"gpt-5": 0.1,
"gpt-5-chat": 0.1,
"gpt-5-mini": 0.1,
"gpt-5-nano": 0.1,
"gpt-5.4": 0.1,
"gpt-5.4-mini": 0.1,
"gpt-5.4-nano": 0.1,
"gpt-5.4-pro": 1,
"gpt-5.5": 0.1,
"gpt-5.5-mini": 0.1,
"gpt-5.5-nano": 0.1,
"gpt-5.5-pro": 1,
"gpt-5.6-luna": 0.1,
"gpt-5.6-luna-pro": 0.1,
"gpt-5.6-sol": 0.1,
"gpt-5.6-sol-pro": 0.1,
"gpt-5.6-terra": 0.1,
"grok-4.5": 0.25,
"grok-4.6": 0.25,
"minimax/minimax-m2.7": 0.2,
"minimax/minimax-m3": 0.2,
"mistralai/mistral-small-2603": 0.1,
"moonshotai/kimi-k2.5": 0.5,
"moonshotai/kimi-k2.6": 0.1684,
"moonshotai/kimi-k2.7": 0.2,
"moonshotai/kimi-k3": 0.1,
"qwen/qwen3-235b-a22b-2507": 0.5,
"qwen/qwen3-30b-a3b-instruct-2507": 0.1684,
"qwen/qwen3-coder": 0.1684,
"qwen/qwen3-coder-next": 0.5,
"qwen/qwen3-next-80b-a3b-instruct": 0.5,
"qwen/qwen3.5-flash-02-23": 0.1,
"qwen/qwen3.6-35b-a3b": 0.1,
"qwen/qwen3.7-plus": 0.2,
"qwen/qwen3.8-max": 0.125,
"xiaomi/mimo-v2.5-pro": 0.1,
"z-ai/glm-5": 0.2,
"z-ai/glm-5.1": 0.18571428571428572,
"z-ai/glm-5.2": 0.18571428571428572,
"z-ai/glm-5.3": 0.18571428571428572,
"z-ai/glm-5.3-flash": 0.2
}
}'
# END_GENERATED_PRICES
Expand Down Expand Up @@ -4191,12 +4268,9 @@ if [[ -n "$transcript_path" && -f "$transcript_path" ]]; then
# Compute a session running total: savings across every assistant turn
# whose marker reports a requested ≠ routed swap, plus cumulative token
# counts across every assistant turn (rerouted or not — total work the
# session has done). cache_creation is priced at 1.25× input, cache_read
# at 0.1× — both ratios are stable across the Claude family and a no-op
# when the provider doesn't return those fields. Cache reads ARE included
# in the savings comparison: both costs apply the same 0.1× weight to
# cache_read_input_tokens, so the delta reflects the model-price
# difference on the cached portion as well.
# session has done). cache_creation is priced at 1.25× input; cache_read
# uses each model's generated catalog multiplier. Both are no-ops when the
# provider does not return those fields.
#
# The marker regex tolerates the optional "(<provider>)" segment and a
# `[1m]` / `-YYYYMMDD` suffix on either model name so transcripts written
Expand Down Expand Up @@ -4229,14 +4303,17 @@ if [[ -n "$transcript_path" && -f "$transcript_path" ]]; then
} as $t |
(if $requested == "" or $requested == $rm then 0
else
($p.input[$rm] // null) as $rin | ($p.output[$rm] // null) as $rout |
($p.input[$requested] // null) as $sin | ($p.output[$requested] // null) as $sout |
if ($rin == null or $rout == null or $sin == null or $sout == null) then 0
($p.input[$rm] // null) as $rin | ($p.output[$rm] // null) as $rout |
($p.cache_read[$rm] // null) as $rcr |
($p.input[$requested] // null) as $sin | ($p.output[$requested] // null) as $sout |
($p.cache_read[$requested] // null) as $scr |
if ($rin == null or $rout == null or $rcr == null or $sin == null or $sout == null or $scr == null) then 0
else
(($t.in + 1.25 * $t.cwrt + 0.1 * $t.crd) / 1000) as $input_units |
($t.out / 1000) as $output_units |
($input_units * $rin + $output_units * $rout) as $routed_cost |
($input_units * $sin + $output_units * $sout) as $requested_cost |
(($t.in + 1.25 * $t.cwrt + $rcr * $t.crd) / 1000) as $routed_input_units |
(($t.in + 1.25 * $t.cwrt + $scr * $t.crd) / 1000) as $requested_input_units |
($t.out / 1000) as $output_units |
($routed_input_units * $rin + $output_units * $rout) as $routed_cost |
($requested_input_units * $sin + $output_units * $sout) as $requested_cost |
($requested_cost - $routed_cost)
end
end) as $savings |
Expand Down
Loading
Loading