diff --git a/core/providers/anthropic/chat.go b/core/providers/anthropic/chat.go index bda53b3f03c..bac88fe1a70 100644 --- a/core/providers/anthropic/chat.go +++ b/core/providers/anthropic/chat.go @@ -984,6 +984,10 @@ func (response *AnthropicMessageResponse) ToBifrostChatResponse(ctx *schemas.Bif mapped := MapAnthropicServiceTierToBifrost(*response.Usage.ServiceTier) bifrostResponse.ServiceTier = &mapped } + // Forward the speed actually served (fast mode) — drives fast-mode billing. + if response.Usage.Speed != nil { + bifrostResponse.Speed = response.Usage.Speed + } } return bifrostResponse @@ -1031,6 +1035,10 @@ func ToAnthropicChatResponse(bifrostResp *schemas.BifrostChatResponse) *Anthropi mapped := MapBifrostServiceTierToAnthropicResponse(*bifrostResp.ServiceTier) anthropicResp.Usage.ServiceTier = &mapped } + // Forward the speed actually served (fast mode) + if bifrostResp.Speed != nil { + anthropicResp.Usage.Speed = bifrostResp.Speed + } } // Convert choices to content diff --git a/core/providers/anthropic/passthrough_usage.go b/core/providers/anthropic/passthrough_usage.go index 1996e4521e2..bddf96dd42d 100644 --- a/core/providers/anthropic/passthrough_usage.go +++ b/core/providers/anthropic/passthrough_usage.go @@ -74,6 +74,9 @@ func buildAnthropicPassthroughUsage(au *AnthropicUsage) *schemas.BifrostPassthro t := MapAnthropicServiceTierToBifrost(*au.ServiceTier) u.ServiceTier = &t } + if au.Speed != nil { + u.Speed = au.Speed + } return u } @@ -136,6 +139,9 @@ func (a *AnthropicPassthroughStreamUsage) ObserveEvent(event []byte) *schemas.Bi if u.ServiceTier != nil { c.ServiceTier = u.ServiceTier } + if u.Speed != nil { + c.Speed = u.Speed + } return a.usage() } diff --git a/core/providers/anthropic/responses.go b/core/providers/anthropic/responses.go index a5d43478e7b..f9d399cd12a 100644 --- a/core/providers/anthropic/responses.go +++ b/core/providers/anthropic/responses.go @@ -2866,6 +2866,11 @@ func (response *AnthropicMessageResponse) ToBifrostResponsesResponse(ctx *schema bifrostResp.ServiceTier = &mapped } + // Forward the speed actually served (fast mode) — drives fast-mode billing. + if response.Usage != nil && response.Usage.Speed != nil { + bifrostResp.Speed = response.Usage.Speed + } + return bifrostResp } @@ -2928,6 +2933,13 @@ func ToAnthropicResponsesResponse(ctx *schemas.BifrostContext, bifrostResp *sche anthropicResp.Usage.ServiceTier = &mapped } + if bifrostResp.Speed != nil { + if anthropicResp.Usage == nil { + anthropicResp.Usage = &AnthropicUsage{} + } + anthropicResp.Usage.Speed = bifrostResp.Speed + } + return anthropicResp } diff --git a/core/providers/anthropic/types.go b/core/providers/anthropic/types.go index 7c196d2d514..fc84fdd9afb 100644 --- a/core/providers/anthropic/types.go +++ b/core/providers/anthropic/types.go @@ -1429,6 +1429,7 @@ type AnthropicUsage struct { OutputTokens int `json:"output_tokens"` ServerToolUse *AnthropicServerToolUseUsage `json:"server_tool_use,omitempty"` // Server tool use statistics (e.g., web search) ServiceTier *string `json:"service_tier,omitempty"` // "standard", "priority", or "batch" + Speed *string `json:"speed,omitempty"` // "fast" or "standard" — which speed was actually served (fast mode research preview) InferenceGeo *string `json:"inference_geo,omitempty"` // the geographic region for inference processing. If not specified, the workspace's default_inference_geo is used. Iterations []AnthropicUsage `json:"iterations,omitempty"` // Iterations statistics } diff --git a/core/providers/anthropic/utils.go b/core/providers/anthropic/utils.go index 160ed3af729..d161275fe71 100644 --- a/core/providers/anthropic/utils.go +++ b/core/providers/anthropic/utils.go @@ -1079,7 +1079,7 @@ func AddMissingBetaHeadersToContext(ctx *schemas.BifrostContext, req *AnthropicM // Check for fast mode. Only add the beta header when both the provider // supports fast mode AND the model does (Opus 4.6 only per // SupportsFastMode); otherwise sending the header guarantees a 400. - if req.Speed != nil && *req.Speed == "fast" { + if req.Speed != nil { if (!hasProvider || features.FastMode) && SupportsFastMode(req.Model) { headers = appendUniqueHeader(headers, AnthropicFastModeBetaHeader) } diff --git a/core/schemas/chatcompletions.go b/core/schemas/chatcompletions.go index c6b483c16bf..555b00d55b7 100644 --- a/core/schemas/chatcompletions.go +++ b/core/schemas/chatcompletions.go @@ -40,6 +40,7 @@ type BifrostChatResponse struct { Model string `json:"model"` Object string `json:"object"` // "chat.completion" or "chat.completion.chunk" ServiceTier *BifrostServiceTier `json:"service_tier,omitempty"` + Speed *string `json:"speed,omitempty"` // "fast" | "standard" — speed actually served (Anthropic fast mode); drives fast-mode billing SystemFingerprint string `json:"system_fingerprint"` Usage *BifrostLLMUsage `json:"usage"` ExtraFields BifrostResponseExtraFields `json:"extra_fields"` diff --git a/core/schemas/passthrough.go b/core/schemas/passthrough.go index ed743da3865..12ef96508bb 100644 --- a/core/schemas/passthrough.go +++ b/core/schemas/passthrough.go @@ -17,6 +17,7 @@ type BifrostPassthroughUsage struct { // Text / chat / responses / embeddings LLMUsage *BifrostLLMUsage ServiceTier *BifrostServiceTier // "priority" | "flex" | nil (default) + Speed *string // "fast" | "standard" — speed actually served (Anthropic fast mode); drives fast-mode billing // Image generation / edit / variation ImageUsage *ImageUsage diff --git a/core/schemas/responses.go b/core/schemas/responses.go index 6b987608ceb..f9545f5c8e4 100644 --- a/core/schemas/responses.go +++ b/core/schemas/responses.go @@ -129,6 +129,7 @@ type BifrostResponsesResponse struct { Reasoning *ResponsesParametersReasoning `json:"reasoning"` // Configuration options for reasoning models SafetyIdentifier *string `json:"safety_identifier"` // Safety identifier ServiceTier *BifrostServiceTier `json:"service_tier"` + Speed *string `json:"speed,omitempty"` // "fast" | "standard" — speed actually served (Anthropic fast mode); drives fast-mode billing Status *string `json:"status,omitempty"` // completed, failed, in_progress, cancelled, queued, or incomplete StreamOptions *ResponsesStreamOptions `json:"stream_options,omitempty"` StopReason *string `json:"stop_reason,omitempty"` // Not in OpenAI's spec, but sent by other providers diff --git a/framework/configstore/migrations.go b/framework/configstore/migrations.go index e6b84cc0903..3a849a5c7cb 100644 --- a/framework/configstore/migrations.go +++ b/framework/configstore/migrations.go @@ -188,7 +188,6 @@ type legacyBudgetTeam struct { // TableName returns the governance_teams table name for legacyBudgetTeam. func (legacyBudgetTeam) TableName() string { return "governance_teams" } - // sqliteColumnInfo holds the information about a SQLite column. type sqliteColumnInfo struct { Name string `gorm:"column:name"` @@ -875,6 +874,9 @@ func triggerMigrations(ctx context.Context, db *gorm.DB) error { if err := migrationAddMCPLibrarySourceColumns(ctx, db); err != nil { return err } + if err := migrationAddFastModePricingColumns(ctx, db); err != nil { + return err + } return nil } @@ -7829,6 +7831,54 @@ func migrationAddFlexTierPricingColumns(ctx context.Context, db *gorm.DB) error return nil } +// migrationAddFastModePricingColumns adds pricing columns for Anthropic fast mode +// (research preview, speed:"fast" on Opus 4.6/4.7/4.8). +func migrationAddFastModePricingColumns(ctx context.Context, db *gorm.DB) error { + m := migrator.New(db, migrator.DefaultOptions, []*migrator.Migration{{ + ID: "add_fast_mode_pricing_columns", + Migrate: func(tx *gorm.DB) error { + tx = tx.WithContext(ctx) + mg := tx.Migrator() + + columns := []string{ + "input_cost_per_token_fast", + "output_cost_per_token_fast", + } + + for _, field := range columns { + if !mg.HasColumn(&tables.TableModelPricing{}, field) { + if err := mg.AddColumn(&tables.TableModelPricing{}, field); err != nil { + return fmt.Errorf("failed to add column %s: %w", field, err) + } + } + } + return nil + }, + Rollback: func(tx *gorm.DB) error { + tx = tx.WithContext(ctx) + mg := tx.Migrator() + + columns := []string{ + "input_cost_per_token_fast", + "output_cost_per_token_fast", + } + + for _, field := range columns { + if mg.HasColumn(&tables.TableModelPricing{}, field) { + if err := mg.DropColumn(&tables.TableModelPricing{}, field); err != nil { + return fmt.Errorf("failed to drop column %s: %w", field, err) + } + } + } + return nil + }, + }}) + if err := m.Migrate(); err != nil { + return fmt.Errorf("error while running fast mode pricing columns migration: %s", err.Error()) + } + return nil +} + // migrationAddWhitelistedRoutesJSONColumn adds the whitelisted_routes_json column to the config_client table func migrationAddWhitelistedRoutesJSONColumn(ctx context.Context, db *gorm.DB) error { m := migrator.New(db, migrator.DefaultOptions, []*migrator.Migration{{ diff --git a/framework/configstore/rdb.go b/framework/configstore/rdb.go index 485d83cee06..1198d20a4ae 100644 --- a/framework/configstore/rdb.go +++ b/framework/configstore/rdb.go @@ -2332,6 +2332,8 @@ var pricingSyncUpdateColumns = []string{ "output_cost_per_token_priority", "input_cost_per_token_flex", "output_cost_per_token_flex", + "input_cost_per_token_fast", + "output_cost_per_token_fast", "input_cost_per_character", // Costs - 128k Tier "input_cost_per_token_above_128k_tokens", diff --git a/framework/configstore/tables/modelpricing.go b/framework/configstore/tables/modelpricing.go index 7c99b6fba2c..2b015daf901 100644 --- a/framework/configstore/tables/modelpricing.go +++ b/framework/configstore/tables/modelpricing.go @@ -28,7 +28,11 @@ type TableModelPricing struct { OutputCostPerTokenPriority *float64 `gorm:"default:null;column:output_cost_per_token_priority" json:"output_cost_per_token_priority,omitempty"` InputCostPerTokenFlex *float64 `gorm:"default:null;column:input_cost_per_token_flex" json:"input_cost_per_token_flex,omitempty"` OutputCostPerTokenFlex *float64 `gorm:"default:null;column:output_cost_per_token_flex" json:"output_cost_per_token_flex,omitempty"` - InputCostPerCharacter *float64 `gorm:"default:null;column:input_cost_per_character" json:"input_cost_per_character,omitempty"` + // Fast mode (Anthropic research preview, speed:"fast" on Opus 4.6/4.7/4.8). + // Flat rate across the full context window; cache tokens bill at standard cache rates. + InputCostPerTokenFast *float64 `gorm:"default:null;column:input_cost_per_token_fast" json:"input_cost_per_token_fast,omitempty"` + OutputCostPerTokenFast *float64 `gorm:"default:null;column:output_cost_per_token_fast" json:"output_cost_per_token_fast,omitempty"` + InputCostPerCharacter *float64 `gorm:"default:null;column:input_cost_per_character" json:"input_cost_per_character,omitempty"` // Costs - 128k Tier InputCostPerTokenAbove128kTokens *float64 `gorm:"default:null;column:input_cost_per_token_above_128k_tokens" json:"input_cost_per_token_above_128k_tokens,omitempty"` InputCostPerImageAbove128kTokens *float64 `gorm:"default:null;column:input_cost_per_image_above_128k_tokens" json:"input_cost_per_image_above_128k_tokens,omitempty"` diff --git a/framework/modelcatalog/datasheet/cost.go b/framework/modelcatalog/datasheet/cost.go index 5d82e48703c..f3e65c692c1 100644 --- a/framework/modelcatalog/datasheet/cost.go +++ b/framework/modelcatalog/datasheet/cost.go @@ -187,18 +187,18 @@ func extractCostInput(result *schemas.BifrostResponse) costInput { case result.ChatResponse != nil && result.ChatResponse.Usage != nil: input.usage = result.ChatResponse.Usage - input.tier = tierFromString(result.ChatResponse.ServiceTier) + input.tier = tierFromResponse(result.ChatResponse.ServiceTier, result.ChatResponse.Speed) case result.ResponsesResponse != nil && result.ResponsesResponse.Usage != nil: input.usage = responsesUsageToBifrostUsage(result.ResponsesResponse.Usage) - input.tier = tierFromString(result.ResponsesResponse.ServiceTier) + input.tier = tierFromResponse(result.ResponsesResponse.ServiceTier, result.ResponsesResponse.Speed) case result.CompactionResponse != nil && result.CompactionResponse.Usage != nil: input.usage = responsesUsageToBifrostUsage(result.CompactionResponse.Usage) case result.ResponsesStreamResponse != nil && result.ResponsesStreamResponse.Response != nil && result.ResponsesStreamResponse.Response.Usage != nil: input.usage = responsesUsageToBifrostUsage(result.ResponsesStreamResponse.Response.Usage) - input.tier = tierFromString(result.ResponsesStreamResponse.Response.ServiceTier) + input.tier = tierFromResponse(result.ResponsesStreamResponse.Response.ServiceTier, result.ResponsesStreamResponse.Response.Speed) case result.EmbeddingResponse != nil && result.EmbeddingResponse.Usage != nil: input.usage = result.EmbeddingResponse.Usage @@ -730,24 +730,33 @@ func computeOCRCost(pricing *configstoreTables.TableModelPricing, ocrProcessedPa // Helpers // --------------------------------------------------------------------------- -// tierFromString constructs a serviceTier from an OpenAI service_tier response value. -func tierFromString(s *schemas.BifrostServiceTier) serviceTier { - if s == nil { - return serviceTier{} - } - switch *s { - case schemas.BifrostServiceTierPriority: - return serviceTier{isPriority: true} - case schemas.BifrostServiceTierFlex: - return serviceTier{isFlex: true} - default: - return serviceTier{} +// tierFromResponse builds a serviceTier from a response's billing-relevant +// fields: the OpenAI service_tier (priority/flex) and the Anthropic speed +// (fast mode). speed == "fast" means fast mode was actually served — the +// provider echoes the served speed, so stripped/fell-back requests report +// "standard" and bill at standard rates. +func tierFromResponse(s *schemas.BifrostServiceTier, speed *string) serviceTier { + var tier serviceTier + if s != nil { + switch *s { + case schemas.BifrostServiceTierPriority: + tier.isPriority = true + case schemas.BifrostServiceTierFlex: + tier.isFlex = true + } } + tier.isFast = speed != nil && *speed == "fast" + return tier } // tieredInputRate returns the effective per-token input rate based on total token count. // Flex applies a flat rate. Priority-specific tier rates are preferred where available. func tieredInputRate(pricing *configstoreTables.TableModelPricing, totalTokens int, tier serviceTier) float64 { + // Fast mode (Anthropic) is a flat rate across the full context window — it + // takes precedence over the token-count tiers below. + if tier.isFast && pricing.InputCostPerTokenFast != nil { + return *pricing.InputCostPerTokenFast + } if tier.isFlex && pricing.InputCostPerTokenFlex != nil { return *pricing.InputCostPerTokenFlex } @@ -782,6 +791,11 @@ func tieredInputRate(pricing *configstoreTables.TableModelPricing, totalTokens i // tieredOutputRate returns the effective per-token output rate based on total token count. // Flex applies a flat rate. Priority-specific tier rates are preferred where available. func tieredOutputRate(pricing *configstoreTables.TableModelPricing, totalTokens int, tier serviceTier) float64 { + // Fast mode (Anthropic) is a flat rate across the full context window — it + // takes precedence over the token-count tiers below. + if tier.isFast && pricing.OutputCostPerTokenFast != nil { + return *pricing.OutputCostPerTokenFast + } if tier.isFlex && pricing.OutputCostPerTokenFlex != nil { return *pricing.OutputCostPerTokenFlex } @@ -1298,9 +1312,7 @@ func passthroughUsageToCostInput(su *schemas.BifrostPassthroughUsage) costInput if su.LLMUsage != nil { input.usage = su.LLMUsage } - if su.ServiceTier != nil { - input.tier = tierFromString(su.ServiceTier) - } + input.tier = tierFromResponse(su.ServiceTier, su.Speed) if su.ImageUsage != nil { input.imageUsage = su.ImageUsage input.imageSize = su.ImageSize diff --git a/framework/modelcatalog/datasheet/cost_test.go b/framework/modelcatalog/datasheet/cost_test.go index e39885c1564..a407df145de 100644 --- a/framework/modelcatalog/datasheet/cost_test.go +++ b/framework/modelcatalog/datasheet/cost_test.go @@ -153,6 +153,90 @@ func TestComputeTextCost_WithCachedPromptTokens(t *testing.T) { assert.InDelta(t, 0.0096, cost, 1e-12) } +func TestComputeTextCost_FastMode(t *testing.T) { + // Opus 4.8: standard $5/$25, fast $10/$50 per MTok. + p := chatPricing(0.000005, 0.000025) + p.InputCostPerTokenFast = bifrost.Ptr(0.00001) + p.OutputCostPerTokenFast = bifrost.Ptr(0.00005) + + usage := &schemas.BifrostLLMUsage{ + PromptTokens: 1000, + CompletionTokens: 500, + TotalTokens: 1500, + } + + // Standard speed → standard rates. + standard := computeTextCost(&p, usage, serviceTier{}) + assert.InDelta(t, 1000*0.000005+500*0.000025, standard, 1e-12) + + // Fast speed → fast rates. + fast := computeTextCost(&p, usage, serviceTier{isFast: true}) + assert.InDelta(t, 1000*0.00001+500*0.00005, fast, 1e-12) +} + +func TestComputeTextCost_FastMode_FlatAcrossContextWindow(t *testing.T) { + // Fast mode is flat across the full window — it must ignore the 200k tier rate. + p := chatPricing(0.000005, 0.000025) + p.InputCostPerTokenFast = bifrost.Ptr(0.00001) + p.OutputCostPerTokenFast = bifrost.Ptr(0.00005) + p.InputCostPerTokenAbove200kTokens = bifrost.Ptr(0.0000075) + p.OutputCostPerTokenAbove200kTokens = bifrost.Ptr(0.0000375) + + usage := &schemas.BifrostLLMUsage{ + PromptTokens: 250000, + CompletionTokens: 1000, + TotalTokens: 251000, // above the 200k tier + } + + fast := computeTextCost(&p, usage, serviceTier{isFast: true}) + // Flat fast rate, not the above-200k rate. + assert.InDelta(t, 250000*0.00001+1000*0.00005, fast, 1e-9) +} + +func TestComputeTextCost_FastMode_FallsBackWhenUnconfigured(t *testing.T) { + // Model without fast columns (e.g. non-Opus) → fast flag is a no-op, standard rates apply. + p := chatPricing(0.000005, 0.000025) + usage := &schemas.BifrostLLMUsage{ + PromptTokens: 1000, + CompletionTokens: 500, + TotalTokens: 1500, + } + fast := computeTextCost(&p, usage, serviceTier{isFast: true}) + assert.InDelta(t, 1000*0.000005+500*0.000025, fast, 1e-12) +} + +func TestComputeTextCost_FastMode_CacheBillsAtStandardRates(t *testing.T) { + // Per design: cache tokens on a fast request bill at standard cache rates; + // only the non-cached input and the output use the fast rate. + p := chatPricing(0.000005, 0.000025) + p.InputCostPerTokenFast = bifrost.Ptr(0.00001) + p.OutputCostPerTokenFast = bifrost.Ptr(0.00005) + p.CacheReadInputTokenCost = bifrost.Ptr(0.0000005) // standard read + p.CacheCreationInputTokenCost = bifrost.Ptr(0.00000625) // standard 5m write + + usage := &schemas.BifrostLLMUsage{ + PromptTokens: 2000, + CompletionTokens: 500, + TotalTokens: 2500, + PromptTokensDetails: &schemas.ChatPromptTokensDetails{ + CachedReadTokens: 1500, + CachedWriteTokens: 200, + }, + } + + fast := computeTextCost(&p, usage, serviceTier{isFast: true}) + // Input: non-cached (2000-1500-200)*fast + read 1500*stdRead + write 200*stdWrite + // = 300*0.00001 + 1500*0.0000005 + 200*0.00000625 = 0.003 + 0.00075 + 0.00125 = 0.0019(? recompute) + expected := 300*0.00001 + 1500*0.0000005 + 200*0.00000625 + 500*0.00005 + assert.InDelta(t, expected, fast, 1e-12) +} + +func TestTierFromResponse_Speed(t *testing.T) { + assert.False(t, tierFromResponse(nil, nil).isFast) + assert.False(t, tierFromResponse(nil, bifrost.Ptr("standard")).isFast) + assert.True(t, tierFromResponse(nil, bifrost.Ptr("fast")).isFast) +} + func TestComputeTextCost_With1hrCacheCreationTokens(t *testing.T) { // claude-3-5-sonnet-20241022-v2:0 on Bedrock: // input=$3/M, output=$15/M, cache_creation=$3.75/M, cache_creation_1hr=$7.50/M, cache_read=$0.3/M @@ -1998,33 +2082,33 @@ func TestTieredCacheReadRate_FallbackOrder(t *testing.T) { } // ========================================================================= -// tierFromString tests +// tierFromResponse tests // ========================================================================= -func TestTierFromString_Priority(t *testing.T) { +func TestTierFromResponse_Priority(t *testing.T) { s := schemas.BifrostServiceTierPriority - tier := tierFromString(&s) + tier := tierFromResponse(&s, nil) assert.True(t, tier.isPriority) assert.False(t, tier.isFlex) } -func TestTierFromString_Flex(t *testing.T) { +func TestTierFromResponse_Flex(t *testing.T) { s := schemas.BifrostServiceTierFlex - tier := tierFromString(&s) + tier := tierFromResponse(&s, nil) assert.False(t, tier.isPriority) assert.True(t, tier.isFlex) } -func TestTierFromString_Default(t *testing.T) { +func TestTierFromResponse_Default(t *testing.T) { for _, s := range []schemas.BifrostServiceTier{schemas.BifrostServiceTierAuto, schemas.BifrostServiceTierDefault, ""} { - tier := tierFromString(&s) + tier := tierFromResponse(&s, nil) assert.False(t, tier.isPriority, "expected no priority for %q", s) assert.False(t, tier.isFlex, "expected no flex for %q", s) } } -func TestTierFromString_Nil(t *testing.T) { - tier := tierFromString(nil) +func TestTierFromResponse_Nil(t *testing.T) { + tier := tierFromResponse(nil, nil) assert.False(t, tier.isPriority) assert.False(t, tier.isFlex) } diff --git a/framework/modelcatalog/datasheet/overrides.go b/framework/modelcatalog/datasheet/overrides.go index 8543924bc49..67c335e4b7e 100644 --- a/framework/modelcatalog/datasheet/overrides.go +++ b/framework/modelcatalog/datasheet/overrides.go @@ -254,6 +254,8 @@ func patchPricing(pricing configstoreTables.TableModelPricing, override Options) {dst: &patched.OutputCostPerTokenPriority, src: override.OutputCostPerTokenPriority}, {dst: &patched.InputCostPerTokenFlex, src: override.InputCostPerTokenFlex}, {dst: &patched.OutputCostPerTokenFlex, src: override.OutputCostPerTokenFlex}, + {dst: &patched.InputCostPerTokenFast, src: override.InputCostPerTokenFast}, + {dst: &patched.OutputCostPerTokenFast, src: override.OutputCostPerTokenFast}, {dst: &patched.InputCostPerVideoPerSecond, src: override.InputCostPerVideoPerSecond}, {dst: &patched.OutputCostPerVideoPerSecond, src: override.OutputCostPerVideoPerSecond}, {dst: &patched.OutputCostPerSecond, src: override.OutputCostPerSecond}, diff --git a/framework/modelcatalog/datasheet/types.go b/framework/modelcatalog/datasheet/types.go index fb4e20757fe..b1a520252be 100644 --- a/framework/modelcatalog/datasheet/types.go +++ b/framework/modelcatalog/datasheet/types.go @@ -102,7 +102,11 @@ type Options struct { OutputCostPerTokenPriority *float64 `json:"output_cost_per_token_priority,omitempty"` InputCostPerTokenFlex *float64 `json:"input_cost_per_token_flex,omitempty"` OutputCostPerTokenFlex *float64 `json:"output_cost_per_token_flex,omitempty"` - InputCostPerCharacter *float64 `json:"input_cost_per_character,omitempty"` + // Fast mode (Anthropic research preview, speed:"fast" on Opus 4.6/4.7/4.8). + // Flat rate across the full context window — no 128k/200k/272k tiering. + InputCostPerTokenFast *float64 `json:"input_cost_per_token_fast,omitempty"` + OutputCostPerTokenFast *float64 `json:"output_cost_per_token_fast,omitempty"` + InputCostPerCharacter *float64 `json:"input_cost_per_character,omitempty"` // Costs - 128k Tier InputCostPerTokenAbove128kTokens *float64 `json:"input_cost_per_token_above_128k_tokens,omitempty"` InputCostPerImageAbove128kTokens *float64 `json:"input_cost_per_image_above_128k_tokens,omitempty"` @@ -246,6 +250,7 @@ type Override struct { type serviceTier struct { isPriority bool // true when service_tier == "priority" isFlex bool // true when service_tier == "flex" + isFast bool // true when usage.speed == "fast" (Anthropic fast mode) } // costInput holds the extracted usage data from a BifrostResponse, @@ -544,6 +549,8 @@ func convertEntryToTablePricing(modelKey string, entry Entry) configstoreTables. OutputCostPerTokenPriority: entry.OutputCostPerTokenPriority, InputCostPerTokenFlex: entry.InputCostPerTokenFlex, OutputCostPerTokenFlex: entry.OutputCostPerTokenFlex, + InputCostPerTokenFast: entry.InputCostPerTokenFast, + OutputCostPerTokenFast: entry.OutputCostPerTokenFast, InputCostPerTokenAbove200kTokens: entry.InputCostPerTokenAbove200kTokens, InputCostPerTokenAbove200kTokensPriority: entry.InputCostPerTokenAbove200kTokensPriority, OutputCostPerTokenAbove200kTokens: entry.OutputCostPerTokenAbove200kTokens, @@ -619,6 +626,8 @@ func convertTablePricingToEntry(pricing *configstoreTables.TableModelPricing) *E OutputCostPerTokenPriority: pricing.OutputCostPerTokenPriority, InputCostPerTokenFlex: pricing.InputCostPerTokenFlex, OutputCostPerTokenFlex: pricing.OutputCostPerTokenFlex, + InputCostPerTokenFast: pricing.InputCostPerTokenFast, + OutputCostPerTokenFast: pricing.OutputCostPerTokenFast, InputCostPerTokenAbove200kTokens: pricing.InputCostPerTokenAbove200kTokens, InputCostPerTokenAbove200kTokensPriority: pricing.InputCostPerTokenAbove200kTokensPriority, OutputCostPerTokenAbove200kTokens: pricing.OutputCostPerTokenAbove200kTokens, diff --git a/ui/app/workspace/custom-pricing/overrides/pricingOverrideSheet.tsx b/ui/app/workspace/custom-pricing/overrides/pricingOverrideSheet.tsx index 1f0801ade1f..4e4fb5ee703 100644 --- a/ui/app/workspace/custom-pricing/overrides/pricingOverrideSheet.tsx +++ b/ui/app/workspace/custom-pricing/overrides/pricingOverrideSheet.tsx @@ -90,6 +90,8 @@ export const PRICING_FIELDS = [ { key: "output_cost_per_token_priority", label: "Output / token (priority)", group: "chat", requestTypeGroups: ["chat"] }, { key: "input_cost_per_token_flex", label: "Input / token (flex)", group: "chat", requestTypeGroups: ["chat"] }, { key: "output_cost_per_token_flex", label: "Output / token (flex)", group: "chat", requestTypeGroups: ["chat"] }, + { key: "input_cost_per_token_fast", label: "Input / token (fast)", group: "chat", requestTypeGroups: ["chat"] }, + { key: "output_cost_per_token_fast", label: "Output / token (fast)", group: "chat", requestTypeGroups: ["chat"] }, { key: "input_cost_per_token_above_128k_tokens", label: "Input / token (>128k)", diff --git a/ui/lib/types/governance.ts b/ui/lib/types/governance.ts index a8892b62a2f..69e24a87d06 100644 --- a/ui/lib/types/governance.ts +++ b/ui/lib/types/governance.ts @@ -420,6 +420,8 @@ export interface PricingOverridePatch { input_cost_per_token_flex?: number; output_cost_per_token_flex?: number; input_cost_per_character?: number; + input_cost_per_token_fast?: number; + output_cost_per_token_fast?:number; // 128k tier input_cost_per_token_above_128k_tokens?: number; output_cost_per_token_above_128k_tokens?: number;