Documentation
¶
Overview ¶
Package pricing owns the pricing/model-parameters catalog (compartments A + B + E): canonical pricing rows fetched from the upstream datasheet, per-provider datasheet-derived model views, supported request types and parameters, and scoped pricing overrides. It also computes per-response cost.
The package performs no list-models I/O — that's the live store's domain. The hourly sync ticker lives on the composer (ModelCatalog), not here; the composer calls SyncFromURL / LoadFromDB / Sync*ModelParams*. Reads are hot-path and lock-free where possible.
Index ¶
- Constants
- Variables
- func FilePathFromURL(parsed *url.URL) string
- func IsEmptyModelCapabilities(ov *schemas.ModelCapabilities) bool
- type BatchCostDetails
- type CatalogPricingOverrides
- type Config
- type Entry
- type LookupScopes
- type MatchType
- type Options
- type Override
- type PeakHoursSchedule
- type PeakHoursWindow
- type ScopeKind
- type Store
- func (s *Store) BaseModelName(model string) string
- func (s *Store) CalculateBatchCostDetailsForUsage(usage *schemas.BifrostLLMUsage, provider schemas.ModelProvider, model string, ...) BatchCostDetails
- func (s *Store) CalculateCacheEmbeddingCost(cacheMetadata *schemas.BifrostCacheMetadata, scopes *LookupScopes) float64
- func (s *Store) CalculateCost(result *schemas.BifrostResponse, scopes *LookupScopes) float64
- func (s *Store) CalculateCostBreakdown(result *schemas.BifrostResponse, scopes *LookupScopes) *schemas.BifrostCost
- func (s *Store) CalculateCostBreakdownForUsage(usage *schemas.BifrostLLMUsage, provider schemas.ModelProvider, model string, ...) *schemas.BifrostCost
- func (s *Store) CalculateCostForUsage(usage *schemas.BifrostLLMUsage, provider schemas.ModelProvider, model string, ...) float64
- func (s *Store) CalculateGuardrailCost(metadata *schemas.BifrostGuardrailMetadata, scopes *LookupScopes) float64
- func (s *Store) CalculateVideoCostDetails(dims VideoPricingDimensions, provider schemas.ModelProvider, ...) VideoCostDetails
- func (s *Store) CatalogPricingOverrides(model string, provider schemas.ModelProvider, mode string) CatalogPricingOverrides
- func (s *Store) DatasheetModelsForProvider(provider schemas.ModelProvider) []string
- func (s *Store) DatasheetProviders() []schemas.ModelProvider
- func (s *Store) DeleteOverride(id string)
- func (s *Store) DeprecatedDatasheetModelsForProvider(provider schemas.ModelProvider) []string
- func (s *Store) DistinctBaseModelNames() []string
- func (s *Store) Get(model string, provider schemas.ModelProvider, requestType schemas.RequestType) *configstoreTables.TableModelPricing
- func (s *Store) GetCapabilityEntry(model string, provider schemas.ModelProvider) *Entry
- func (s *Store) GetModelParametersByModel(ctx context.Context, model string) (*configstoreTables.TableModelParameters, error)
- func (s *Store) GetPricingEntryForModel(model string, provider schemas.ModelProvider) *Entry
- func (s *Store) GetSupportedParameters(model string) []string
- func (s *Store) IsProviderCostIgnored(provider schemas.ModelProvider) bool
- func (s *Store) IsRequestTypeSupported(model string, requestType schemas.RequestType) bool
- func (s *Store) IsSameModel(model1, model2 string) bool
- func (s *Store) IsTextCompletionSupported(model string, provider schemas.ModelProvider) bool
- func (s *Store) LastSyncedAt() time.Time
- func (s *Store) LoadFromDB(ctx context.Context) error
- func (s *Store) LoadFromURLIntoMemory(ctx context.Context) error
- func (s *Store) LoadModelCapabilities(ctx context.Context, provider schemas.ModelProvider, model string) (*schemas.ModelCapabilities, error)
- func (s *Store) LoadModelParamsFromDB(ctx context.Context) (int, error)
- func (s *Store) LoadModelParamsFromURLIntoMemory(ctx context.Context) error
- func (s *Store) LoadOverridesFromStore(ctx context.Context) error
- func (s *Store) MarkSynced(t time.Time)
- func (s *Store) ModelParametersURL() string
- func (s *Store) ReplaceIgnoreProviderCost(providers []schemas.ModelProvider)
- func (s *Store) ResolveModelParameters(ctx context.Context, model string) (*configstoreTables.TableModelParameters, error)
- func (s *Store) RoutingCallCost(call schemas.BifrostRoutingCall, scopes *LookupScopes) float64
- func (s *Store) SetIgnoreProviderCost(provider schemas.ModelProvider, ignore bool)
- func (s *Store) SetOnModelParametersApplied(fn func())
- func (s *Store) SetOverrides(rows []configstoreTables.TablePricingOverride) error
- func (s *Store) SetPricingRowsForTest(rows []configstoreTables.TableModelPricing)
- func (s *Store) SetSupportedParamsForTest(params map[string][]string)
- func (s *Store) SyncFromURL(ctx context.Context) error
- func (s *Store) SyncInterval() time.Duration
- func (s *Store) SyncModelParamsFromURL(ctx context.Context) error
- func (s *Store) URL() string
- func (s *Store) UpdateSyncConfig(cfg Config)
- func (s *Store) UpsertModelPricingAttributes(ctx context.Context, model string, provider schemas.ModelProvider, ...) (int64, error)
- func (s *Store) UpsertOverrides(rows ...*configstoreTables.TablePricingOverride) error
- func (s *Store) WriteGen() uint64
- type VideoCostDetails
- type VideoPricingDimensions
Constants ¶
const ( DefaultURL = "https://getbifrost.ai/datasheet" DefaultModelParametersURL = "https://getbifrost.ai/datasheet/model-parameters" DefaultSyncInterval = 24 * time.Hour DefaultPricingTimeout = 60 * time.Second DefaultModelParametersTimeout = 45 * time.Second )
Defaults for sync configuration and timeouts. Exposed so the composer can fall back to these when the framework Config leaves fields nil.
const ( TokenTierAbove272K = 272000 TokenTierAbove200K = 200000 TokenTierAbove128K = 128000 TokenTierAbove100K = 100000 )
Tier boundaries for tiered token pricing. Matches the upstream datasheet keys (input_cost_per_token_above_<N>k_tokens).
Variables ¶
var NewHTTPClient = network.NewPrivateNetworkHTTPClient
NewHTTPClient builds the client for every catalog feed download (pricing, model parameters, MCP library). It is a var, not a direct call to network.NewPrivateNetworkHTTPClient, only so tests can wrap the transport: the guarded client builds its own *http.Transport, so swapping http.DefaultTransport no longer intercepts these fetches. Without this hook every LoadConfig in transports/bifrost-http/lib tests downloads the full public datasheets (~29MB), which took that package from minutes to ~35 min under -race in CI. Production code must not reassign it.
Functions ¶
func FilePathFromURL ¶ added in v1.6.0
FilePathFromURL resolves a parsed file:// URL to a filesystem path, supporting both absolute and relative references. Go's url.Parse scatters a relative path across different fields depending on its form, so we reassemble it here:
- file:./x.json or file:x.json -> Opaque ("./x.json")
- file://./x.json -> Host (".") + Path ("/x.json") = "./x.json"
- file:///abs/x.json -> Path ("/abs/x.json")
- file://localhost/abs/x.json -> Path ("/abs/x.json")
Relative paths resolve against the process working directory, matching how the sqlite config store treats a relative "path" value.
Exported because every air-gapped source in the catalog resolves file:// URLs the same way: the pricing datasheet, the model parameters datasheet, and the MCP library catalog (modelcatalog.fetchMCPLibrary).
func IsEmptyModelCapabilities ¶ added in v1.6.0
func IsEmptyModelCapabilities(ov *schemas.ModelCapabilities) bool
IsEmptyModelCapabilities reports whether no field on the override struct is set. Compared against the zero value so new fields need no maintenance here.
Types ¶
type BatchCostDetails ¶ added in v1.6.0
type BatchCostDetails struct {
Cost float64
Priced bool
ProviderCostUsed bool
InputCostPerTokenBatches *float64
OutputCostPerTokenBatches *float64
}
BatchCostDetails captures the rate inputs used to price a batch result row. InputCostPerTokenBatches/OutputCostPerTokenBatches are the rates actually applied — the catalog's explicit batch rate when set, otherwise defaultBatchPricingRatio of the standard rate. There is no field marking which case occurred; callers that need to distinguish an authoritative catalog rate from an assumed default must compare against the standard rate themselves.
type CatalogPricingOverrides ¶ added in v1.5.9
type CatalogPricingOverrides struct {
// AppliedID is the override that wins under the global/provider scopes
// only — the scopes that apply to every request regardless of caller.
// Empty when nothing global/provider-scoped matches.
AppliedID string
// AppliedPatch is that winner's option patch; nil when AppliedID is empty.
AppliedPatch *Options
// Matching lists every override matching (model, provider) regardless of
// scope or mode, most-specific-first, for informational display. Includes
// the applied one.
Matching []Override
}
CatalogPricingOverrides summarizes how scoped overrides affect one (model, provider) row of the management model catalog.
type Config ¶
Config groups the values the composer hands to New / UpdateSyncConfig. Zero values fall back to the Default* constants.
type Entry ¶
type Entry struct {
BaseModel string `json:"base_model,omitempty"`
Provider string `json:"provider"`
Mode string `json:"mode"`
ContextLength *int `json:"context_length,omitempty"`
MaxInputTokens *int `json:"max_input_tokens,omitempty"`
MaxOutputTokens *int `json:"max_output_tokens,omitempty"`
Architecture *schemas.Architecture `json:"architecture,omitempty"`
IsDeprecated bool `json:"is_deprecated,omitempty"`
// AdditionalAttributes carries editorial metadata stored on the pricing
// row (e.g. description). Populated from the DB read path only; the
// json:"-" tag prevents URL datasheet payloads from ever feeding into
// this field via json.Unmarshal.
AdditionalAttributes map[string]string `json:"-"`
Options
}
Entry represents a single model's pricing information. Field names and JSON tags match the datasheet schema exactly. AdditionalAttributes carries editorial metadata stored on the pricing row — never populated from the URL datasheet, only from DB reads via the management API.
func (*Entry) UnmarshalJSON ¶
UnmarshalJSON handles the special case where search_context_cost_per_query may arrive as either a plain float64 or a tiered object {"search_context_size_low":…, "search_context_size_medium":…, "search_context_size_high":…}.
type LookupScopes ¶
type LookupScopes struct {
UserID string
VirtualKeyID string
SelectedKeyID string
Provider string
// BilledAt is the instant used to decide peak vs off-peak for models that
// carry a PeakHours schedule. It is the request's START time, not the
// completion time: that keeps pricing deterministic and reproducible, makes
// streaming and non-streaming agree, and matches the timestamp users see in
// logs. A long stream that crosses a window boundary bills entirely at its
// start-time rate. The zero value means "unknown" and falls back to the
// wall clock at evaluation time.
BilledAt time.Time
}
LookupScopes carries the runtime identifiers used to resolve scoped pricing overrides during cost calculation.
func LookupScopesFromContext ¶
func LookupScopesFromContext(ctx *schemas.BifrostContext, provider string) *LookupScopes
LookupScopesFromContext builds a LookupScopes from a BifrostContext. Reads the governance virtual key ID (not the raw VK token), the selected key ID, and the resolved calling user ID. provider should be the provider name string (e.g. "openai"); pass "" if unavailable. Returns nil only when ctx is nil. An empty scopes value is still returned when all fields are empty so global-scope overrides remain evaluable.
NOT SAFE in a goroutine — reads from ctx which is cancelled when the request ends. Call synchronously in PostHooks and pass the result by value to anything that may outlive the request.
type MatchType ¶
type MatchType string
MatchType controls how an override pattern is matched against model names.
type Options ¶
type Options struct {
// Costs - Text
InputCostPerToken *float64 `json:"input_cost_per_token,omitempty"`
OutputCostPerToken *float64 `json:"output_cost_per_token,omitempty"`
InputCostPerTokenBatches *float64 `json:"input_cost_per_token_batches,omitempty"`
OutputCostPerTokenBatches *float64 `json:"output_cost_per_token_batches,omitempty"`
InputCostPerTokenPriority *float64 `json:"input_cost_per_token_priority,omitempty"`
OutputCostPerTokenPriority *float64 `json:"output_cost_per_token_priority,omitempty"`
InputCostPerTokenUltrafast *float64 `json:"input_cost_per_token_ultrafast,omitempty"`
OutputCostPerTokenUltrafast *float64 `json:"output_cost_per_token_ultrafast,omitempty"`
InputCostPerTokenFlex *float64 `json:"input_cost_per_token_flex,omitempty"`
OutputCostPerTokenFlex *float64 `json:"output_cost_per_token_flex,omitempty"`
// Decision requests on a model whose row is another mode (OpenAI's gpt-6-luna
// is a chat row served natively on /v1/decisions). See computeDecisionCost.
InputCostPerTokenDecisions *float64 `json:"input_cost_per_token_decisions,omitempty"`
OutputCostPerTokenDecisions *float64 `json:"output_cost_per_token_decisions,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"`
InputCostPerVideoPerSecondAbove128kTokens *float64 `json:"input_cost_per_video_per_second_above_128k_tokens,omitempty"`
InputCostPerAudioPerSecondAbove128kTokens *float64 `json:"input_cost_per_audio_per_second_above_128k_tokens,omitempty"`
OutputCostPerTokenAbove128kTokens *float64 `json:"output_cost_per_token_above_128k_tokens,omitempty"`
// Costs - 100k Tier
InputCostPerTokenAbove100kTokens *float64 `json:"input_cost_per_token_above_100k_tokens,omitempty"`
OutputCostPerTokenAbove100kTokens *float64 `json:"output_cost_per_token_above_100k_tokens,omitempty"`
// Costs - 200k Tier
InputCostPerTokenAbove200kTokens *float64 `json:"input_cost_per_token_above_200k_tokens,omitempty"`
InputCostPerTokenAbove200kTokensPriority *float64 `json:"input_cost_per_token_above_200k_tokens_priority,omitempty"`
OutputCostPerTokenAbove200kTokens *float64 `json:"output_cost_per_token_above_200k_tokens,omitempty"`
OutputCostPerTokenAbove200kTokensPriority *float64 `json:"output_cost_per_token_above_200k_tokens_priority,omitempty"`
// Costs - 272k Tier
InputCostPerTokenAbove272kTokens *float64 `json:"input_cost_per_token_above_272k_tokens,omitempty"`
InputCostPerTokenAbove272kTokensPriority *float64 `json:"input_cost_per_token_above_272k_tokens_priority,omitempty"`
InputCostPerTokenFlexAbove272kTokens *float64 `json:"input_cost_per_token_flex_above_272k_tokens,omitempty"`
OutputCostPerTokenAbove272kTokens *float64 `json:"output_cost_per_token_above_272k_tokens,omitempty"`
OutputCostPerTokenAbove272kTokensPriority *float64 `json:"output_cost_per_token_above_272k_tokens_priority,omitempty"`
OutputCostPerTokenFlexAbove272kTokens *float64 `json:"output_cost_per_token_flex_above_272k_tokens,omitempty"`
InputCostPerTokenAbove272kTokensUltrafast *float64 `json:"input_cost_per_token_above_272k_tokens_ultrafast,omitempty"`
OutputCostPerTokenAbove272kTokensUltrafast *float64 `json:"output_cost_per_token_above_272k_tokens_ultrafast,omitempty"`
// Costs - Cache
CacheCreationInputTokenCost *float64 `json:"cache_creation_input_token_cost,omitempty"`
CacheReadInputTokenCost *float64 `json:"cache_read_input_token_cost,omitempty"`
CacheCreationInputTokenCostAbove200kTokens *float64 `json:"cache_creation_input_token_cost_above_200k_tokens,omitempty"`
CacheReadInputTokenCostAbove200kTokens *float64 `json:"cache_read_input_token_cost_above_200k_tokens,omitempty"`
CacheReadInputTokenCostAbove200kTokensPriority *float64 `json:"cache_read_input_token_cost_above_200k_tokens_priority,omitempty"`
CacheCreationInputTokenCostAbove1hr *float64 `json:"cache_creation_input_token_cost_above_1hr,omitempty"`
CacheCreationInputTokenCostAbove1hrAbove200kTokens *float64 `json:"cache_creation_input_token_cost_above_1hr_above_200k_tokens,omitempty"`
CacheCreationInputTokenCostAbove100kTokens *float64 `json:"cache_creation_input_token_cost_above_100k_tokens,omitempty"`
CacheReadInputTokenCostAbove100kTokens *float64 `json:"cache_read_input_token_cost_above_100k_tokens,omitempty"`
CacheCreationInputTokenCostAbove1hrAbove100kTokens *float64 `json:"cache_creation_input_token_cost_above_1hr_above_100k_tokens,omitempty"`
CacheCreationInputAudioTokenCost *float64 `json:"cache_creation_input_audio_token_cost,omitempty"`
CacheReadInputTokenCostPriority *float64 `json:"cache_read_input_token_cost_priority,omitempty"`
CacheReadInputTokenCostUltrafast *float64 `json:"cache_read_input_token_cost_ultrafast,omitempty"`
CacheReadInputTokenCostFlex *float64 `json:"cache_read_input_token_cost_flex,omitempty"`
CacheReadInputImageTokenCost *float64 `json:"cache_read_input_image_token_cost,omitempty"`
CacheReadInputTokenCostAbove272kTokens *float64 `json:"cache_read_input_token_cost_above_272k_tokens,omitempty"`
CacheReadInputTokenCostAbove272kTokensPriority *float64 `json:"cache_read_input_token_cost_above_272k_tokens_priority,omitempty"`
CacheReadInputTokenCostFlexAbove272kTokens *float64 `json:"cache_read_input_token_cost_flex_above_272k_tokens,omitempty"`
CacheReadInputTokenCostAbove272kTokensUltrafast *float64 `json:"cache_read_input_token_cost_above_272k_tokens_ultrafast,omitempty"`
// OpenAI cache-write (cache-creation) tiered rates, added with gpt-5.6.
CacheCreationInputTokenCostAbove272kTokens *float64 `json:"cache_creation_input_token_cost_above_272k_tokens,omitempty"`
CacheCreationInputTokenCostFlex *float64 `json:"cache_creation_input_token_cost_flex,omitempty"`
CacheCreationInputTokenCostFlexAbove272kTokens *float64 `json:"cache_creation_input_token_cost_flex_above_272k_tokens,omitempty"`
CacheCreationInputTokenCostPriority *float64 `json:"cache_creation_input_token_cost_priority,omitempty"`
CacheCreationInputTokenCostAbove272kTokensPriority *float64 `json:"cache_creation_input_token_cost_above_272k_tokens_priority,omitempty"`
CacheCreationInputTokenCostUltrafast *float64 `json:"cache_creation_input_token_cost_ultrafast,omitempty"`
CacheCreationInputTokenCostAbove272kTokensUltrafast *float64 `json:"cache_creation_input_token_cost_above_272k_tokens_ultrafast,omitempty"`
// Fast mode (Anthropic) cache rates — flat across the full context window, no tiering.
CacheCreationInputTokenCostFast *float64 `json:"cache_creation_input_token_cost_fast,omitempty"`
CacheCreationInputTokenCostAbove1hrFast *float64 `json:"cache_creation_input_token_cost_above_1hr_fast,omitempty"`
CacheReadInputTokenCostFast *float64 `json:"cache_read_input_token_cost_fast,omitempty"`
// Costs - Image
InputCostPerImage *float64 `json:"input_cost_per_image,omitempty"`
InputCostPerPixel *float64 `json:"input_cost_per_pixel,omitempty"`
OutputCostPerImage *float64 `json:"output_cost_per_image,omitempty"`
OutputCostPerPixel *float64 `json:"output_cost_per_pixel,omitempty"`
OutputCostPerImagePremiumImage *float64 `json:"output_cost_per_image_premium_image,omitempty"`
OutputCostPerImageAbove512x512Pixels *float64 `json:"output_cost_per_image_above_512_and_512_pixels,omitempty"`
OutputCostPerImageAbove512x512PixelsPremium *float64 `json:"output_cost_per_image_above_512_and_512_pixels_and_premium_image,omitempty"`
OutputCostPerImageAbove1024x1024Pixels *float64 `json:"output_cost_per_image_above_1024_and_1024_pixels,omitempty"`
OutputCostPerImageAbove1024x1024PixelsPremium *float64 `json:"output_cost_per_image_above_1024_and_1024_pixels_and_premium_image,omitempty"`
OutputCostPerImageAbove1024x1536Pixels *float64 `json:"output_cost_per_image_above_1024_and_1536_pixels,omitempty"`
OutputCostPerImageAbove1536x1024Pixels *float64 `json:"output_cost_per_image_above_1536_and_1024_pixels,omitempty"`
OutputCostPerImageAbove2048x2048Pixels *float64 `json:"output_cost_per_image_above_2048_and_2048_pixels,omitempty"`
OutputCostPerImageAbove4096x4096Pixels *float64 `json:"output_cost_per_image_above_4096_and_4096_pixels,omitempty"`
OutputCostPerImageAbove4Megapixels *float64 `json:"output_cost_per_image_above_4_megapixels,omitempty"`
OutputCostPerImageAbove8Megapixels *float64 `json:"output_cost_per_image_above_8_megapixels,omitempty"`
OutputCostPerImageAbove16Megapixels *float64 `json:"output_cost_per_image_above_16_megapixels,omitempty"`
OutputCostPerImageAbove32Megapixels *float64 `json:"output_cost_per_image_above_32_megapixels,omitempty"`
OutputCostPerImageAbove64Megapixels *float64 `json:"output_cost_per_image_above_64_megapixels,omitempty"`
OutputCostPerImageLowQuality *float64 `json:"output_cost_per_image_low_quality,omitempty"`
OutputCostPerImageMediumQuality *float64 `json:"output_cost_per_image_medium_quality,omitempty"`
OutputCostPerImageHighQuality *float64 `json:"output_cost_per_image_high_quality,omitempty"`
OutputCostPerImageAutoQuality *float64 `json:"output_cost_per_image_auto_quality,omitempty"`
InputCostPerImageToken *float64 `json:"input_cost_per_image_token,omitempty"`
OutputCostPerImageToken *float64 `json:"output_cost_per_image_token,omitempty"`
// Costs - Image, joint size and quality. These are the most specific
// per-image rates: they win over the quality-only and size-only rates
// above, since upstream prices size and quality together.
OutputCostPerImageAbove1024x1024PixelsLowQuality *float64 `json:"output_cost_per_image_above_1024_and_1024_pixels_low_quality,omitempty"`
OutputCostPerImageAbove1024x1536PixelsLowQuality *float64 `json:"output_cost_per_image_above_1024_and_1536_pixels_low_quality,omitempty"`
OutputCostPerImageAbove1536x1024PixelsLowQuality *float64 `json:"output_cost_per_image_above_1536_and_1024_pixels_low_quality,omitempty"`
OutputCostPerImageAbove1024x1024PixelsMediumQuality *float64 `json:"output_cost_per_image_above_1024_and_1024_pixels_medium_quality,omitempty"`
OutputCostPerImageAbove1024x1536PixelsMediumQuality *float64 `json:"output_cost_per_image_above_1024_and_1536_pixels_medium_quality,omitempty"`
OutputCostPerImageAbove1536x1024PixelsMediumQuality *float64 `json:"output_cost_per_image_above_1536_and_1024_pixels_medium_quality,omitempty"`
OutputCostPerImageAbove1024x1024PixelsHighQuality *float64 `json:"output_cost_per_image_above_1024_and_1024_pixels_high_quality,omitempty"`
OutputCostPerImageAbove1024x1536PixelsHighQuality *float64 `json:"output_cost_per_image_above_1024_and_1536_pixels_high_quality,omitempty"`
OutputCostPerImageAbove1536x1024PixelsHighQuality *float64 `json:"output_cost_per_image_above_1536_and_1024_pixels_high_quality,omitempty"`
OutputCostPerImageAbove1024x1024PixelsStandardQuality *float64 `json:"output_cost_per_image_above_1024_and_1024_pixels_standard_quality,omitempty"`
OutputCostPerImageAbove1024x1536PixelsStandardQuality *float64 `json:"output_cost_per_image_above_1024_and_1536_pixels_standard_quality,omitempty"`
OutputCostPerImageAbove1536x1024PixelsStandardQuality *float64 `json:"output_cost_per_image_above_1536_and_1024_pixels_standard_quality,omitempty"`
// Costs - Audio/Video
InputCostPerAudioToken *float64 `json:"input_cost_per_audio_token,omitempty"`
InputCostPerAudioPerSecond *float64 `json:"input_cost_per_audio_per_second,omitempty"`
InputCostPerSecond *float64 `json:"input_cost_per_second,omitempty"`
InputCostPerVideoPerSecond *float64 `json:"input_cost_per_video_per_second,omitempty"`
OutputCostPerAudioToken *float64 `json:"output_cost_per_audio_token,omitempty"`
OutputCostPerVideoPerSecond *float64 `json:"output_cost_per_video_per_second,omitempty"`
OutputCostPerSecond *float64 `json:"output_cost_per_second,omitempty"`
// Azure Voice Live rates, used on Voice Live turns in place of the matching rates above when set. The
// speech rates price the audio Azure Speech handled.
InputCostPerTokenVoiceLive *float64 `json:"input_cost_per_token_voice_live,omitempty"`
OutputCostPerTokenVoiceLive *float64 `json:"output_cost_per_token_voice_live,omitempty"`
CacheReadInputTokenCostVoiceLive *float64 `json:"cache_read_input_token_cost_voice_live,omitempty"`
InputCostPerAudioTokenVoiceLive *float64 `json:"input_cost_per_audio_token_voice_live,omitempty"`
OutputCostPerAudioTokenVoiceLive *float64 `json:"output_cost_per_audio_token_voice_live,omitempty"`
InputCostPerStandardSpeechAudioTokenVoiceLive *float64 `json:"input_cost_per_standard_speech_audio_token_voice_live,omitempty"`
OutputCostPerStandardSpeechAudioTokenVoiceLive *float64 `json:"output_cost_per_standard_speech_audio_token_voice_live,omitempty"`
InputCostPerCustomSpeechAudioTokenVoiceLive *float64 `json:"input_cost_per_custom_speech_audio_token_voice_live,omitempty"`
OutputCostPerCustomSpeechAudioTokenVoiceLive *float64 `json:"output_cost_per_custom_speech_audio_token_voice_live,omitempty"`
// Resolution-banded video output rates, matched on the short edge of the response's
// size. Absent band falls back to OutputCostPerVideoPerSecond, then OutputCostPerSecond.
OutputCostPerVideoPerSecond480p *float64 `json:"output_cost_per_video_per_second_480p,omitempty"`
OutputCostPerVideoPerSecond720p *float64 `json:"output_cost_per_video_per_second_720p,omitempty"`
OutputCostPerVideoPerSecond1024p *float64 `json:"output_cost_per_video_per_second_1024p,omitempty"`
OutputCostPerVideoPerSecond1080p *float64 `json:"output_cost_per_video_per_second_1080p,omitempty"`
OutputCostPerVideoPerSecond4k *float64 `json:"output_cost_per_video_per_second_4k,omitempty"`
// Per-token video output rate, for providers that meter video in tokens (BytePlus Seedance).
// Takes precedence over OutputCostPerToken for video output tokens.
OutputCostPerVideoToken *float64 `json:"output_cost_per_video_token,omitempty"`
// Costs - Other.
//
// SearchContextCostPerQuery is stored as a single float64, but the upstream datasheet
// represents it as a tiered object. See Entry.UnmarshalJSON.
SearchContextCostPerQuery *float64 `json:"search_context_cost_per_query,omitempty"`
// WebSearchCostPerRequest prices each server-side web search call (usage.tool_usage.web_search.num_requests).
WebSearchCostPerRequest *float64 `json:"web_search_cost_per_request,omitempty"`
CodeInterpreterCostPerSession *float64 `json:"code_interpreter_cost_per_session,omitempty"`
// InputCostPerQuery is the per-query rate rerank models bill on. Cohere and Bedrock both
// define a query (a "search unit") as one query against up to 100 document chunks, so a
// request over that many chunks bills as several queries. It is unrelated to
// SearchContextCostPerQuery, which prices web-search context on chat models.
//
// Not applicable to Vertex: its Ranking API bills "ranking units" derived from record count
// and title/content size, so a flat per-query rate would misprice every call.
InputCostPerQuery *float64 `json:"input_cost_per_query,omitempty"`
InferenceGeoUSMultiplier *float64 `json:"inference_geo_us_multiplier,omitempty"`
// CostPerRequest is a flat fee added once per billed request, on top of
// whatever usage-based cost the request otherwise computes to.
CostPerRequest *float64 `json:"cost_per_request,omitempty"`
// Costs - OCR
OCRCostPerPage *float64 `json:"ocr_cost_per_page,omitempty"`
AnnotationCostPerPage *float64 `json:"annotation_cost_per_page,omitempty"`
// Costs - Time of day
//
// OffPeakCostMultiplier scales every usage-based charge when the request
// falls outside the PeakHours windows. Every other rate on this struct is
// the PEAK price, so the multiplier is expected to be in (0, 1] — 0.5 for
// DeepSeek's 50% off-peak discount. Both fields must be present for a
// discount to apply; either one alone bills at peak.
OffPeakCostMultiplier *float64 `json:"off_peak_cost_multiplier,omitempty"`
PeakHours *PeakHoursSchedule `json:"peak_hours,omitempty"`
}
Options holds every individual cost field. Embedded into Entry and reused as the patch shape for Override.
type Override ¶
type Override struct {
ID string `json:"id"`
Name string `json:"name"`
ScopeKind ScopeKind `json:"scope_kind"`
UserID *string `json:"user_id,omitempty"`
VirtualKeyID *string `json:"virtual_key_id,omitempty"`
ProviderID *string `json:"provider_id,omitempty"`
ProviderKeyID *string `json:"provider_key_id,omitempty"`
MatchType MatchType `json:"match_type"`
Pattern string `json:"pattern"`
RequestTypes []schemas.RequestType `json:"request_types,omitempty"`
Options Options `json:"options"`
}
Override describes a scoped pricing override shared across config storage, model catalog compilation, and governance APIs.
type PeakHoursSchedule ¶ added in v1.7.0
type PeakHoursSchedule = configstoreTables.PeakHoursSchedule
PeakHoursSchedule and PeakHoursWindow are defined in the configstore tables package (TableModelPricing has to reference them and datasheet already imports tables, so the dependency can only run that way). Aliased here so the datasheet JSON shape reads as one self-contained type set.
type PeakHoursWindow ¶ added in v1.7.0
type PeakHoursWindow = configstoreTables.PeakHoursWindow
PeakHoursSchedule and PeakHoursWindow are defined in the configstore tables package (TableModelPricing has to reference them and datasheet already imports tables, so the dependency can only run that way). Aliased here so the datasheet JSON shape reads as one self-contained type set.
type ScopeKind ¶
type ScopeKind string
ScopeKind identifies which governance scope an override applies to.
const ( ScopeKindGlobal ScopeKind = "global" ScopeKindProvider ScopeKind = "provider" ScopeKindProviderKey ScopeKind = "provider_key" ScopeKindVirtualKey ScopeKind = "virtual_key" ScopeKindVirtualKeyProvider ScopeKind = "virtual_key_provider" ScopeKindVirtualKeyProviderKey ScopeKind = "virtual_key_provider_key" // The user scope family targets the resolved calling user. It ranks // below the virtual-key family and above the provider scopes during // resolution: virtual-key pricing is checked first, then the user's. // Within the family, more identifiers = more specific. ScopeKindUser ScopeKind = "user" ScopeKindUserProvider ScopeKind = "user_provider" ScopeKindUserProviderKey ScopeKind = "user_provider_key" )
type Store ¶
type Store struct {
// contains filtered or unexported fields
}
Store owns the pricing catalog (canonical rows, base-model index, derived datasheet view, supported request types/parameters) and pricing overrides.
All I/O is driven by the composer — Store does not own a ticker or the distributed lock; it exposes SyncFromURL / LoadFromDB / UpdateSyncConfig as the surface the composer calls.
func New ¶
func New(configStore configstore.ConfigStore, logger schemas.Logger, cfg Config) *Store
New constructs a Store with the given config. The store is empty; callers (composer) drive bootstrap via LoadFromDB or LoadFromURLIntoMemory.
func NewTestStore ¶
NewTestStore constructs a minimal Store for unit tests without I/O. Optionally seed baseModelIndex so BaseModelName lookups resolve. A no-op logger is wired so cost / pricing paths (which assume Store.logger is non-nil) don't panic from external test code.
func (*Store) BaseModelName ¶
BaseModelName returns the canonical base model name. Uses the pre-computed base_model from the pricing catalog when present, falling back to algorithmic date/version stripping for unknown models.
"gpt-4o" → "gpt-4o" "openai/gpt-4o" → "gpt-4o" "gpt-4o-2024-08-06" → "gpt-4o" (algorithmic fallback)
func (*Store) CalculateBatchCostDetailsForUsage ¶ added in v1.6.0
func (s *Store) CalculateBatchCostDetailsForUsage(usage *schemas.BifrostLLMUsage, provider schemas.ModelProvider, model string, requestType schemas.RequestType, scopes *LookupScopes) BatchCostDetails
CalculateBatchCostDetailsForUsage computes batch result cost and returns the explicit batch rates used so aggregate logs can explain historical pricing. When the catalog has no batch-specific rate, it defaults to defaultBatchPricingRatio of the standard rate rather than refusing to price — only a model with no pricing at all (neither batch nor standard) is unpriced.
func (*Store) CalculateCacheEmbeddingCost ¶ added in v1.5.9
func (s *Store) CalculateCacheEmbeddingCost(cacheMetadata *schemas.BifrostCacheMetadata, scopes *LookupScopes) float64
CalculateCacheEmbeddingCost computes the semantic-cache embedding lookup cost.
func (*Store) CalculateCost ¶
func (s *Store) CalculateCost(result *schemas.BifrostResponse, scopes *LookupScopes) float64
CalculateCost calculates the cost of a Bifrost response. It handles all request types, cache, guardrail, and routing billing, and tiered pricing. If scopes is nil, an empty LookupScopes is used; global and provider-scoped overrides may still apply since the provider is derived from the response.
func (*Store) CalculateCostBreakdown ¶ added in v1.6.0
func (s *Store) CalculateCostBreakdown(result *schemas.BifrostResponse, scopes *LookupScopes) *schemas.BifrostCost
CalculateCostBreakdown mirrors CalculateCost but returns the full per-category cost breakdown (input / output / cache) instead of only the total. Returns nil when there is no cost to record. CalculateCost is a thin wrapper over this that returns breakdown.TotalCost, so both paths compute cost identically.
func (*Store) CalculateCostBreakdownForUsage ¶ added in v1.6.0
func (s *Store) CalculateCostBreakdownForUsage(usage *schemas.BifrostLLMUsage, provider schemas.ModelProvider, model string, requestType schemas.RequestType, scopes *LookupScopes) *schemas.BifrostCost
CalculateCostBreakdownForUsage mirrors CalculateCostForUsage but returns the full per-category breakdown instead of only the total, so callers billing a bare usage object (failed/cancelled requests) can denormalize the input / output / additional split, not just the scalar total. Returns nil when there is no cost to record.
func (*Store) CalculateCostForUsage ¶ added in v1.3.22
func (s *Store) CalculateCostForUsage(usage *schemas.BifrostLLMUsage, provider schemas.ModelProvider, model string, requestType schemas.RequestType, scopes *LookupScopes) float64
CalculateCostForUsage computes the dollar cost from a bare usage object plus provider / model / request type, for cases where no full BifrostResponse exists. The primary use is billing partial usage carried on a failed or cancelled request via BifrostError.ExtraFields.BilledUsage: the provider consumed tokens, so we must charge for them even though there is no success response to read. It mirrors CalculateCost's compute path so success and failure billing use identical rates. Returns 0 when usage is nil. A thin wrapper over CalculateCostBreakdownForUsage returning only the total, so both paths compute cost identically.
func (*Store) CalculateGuardrailCost ¶ added in v1.5.9
func (s *Store) CalculateGuardrailCost(metadata *schemas.BifrostGuardrailMetadata, scopes *LookupScopes) float64
CalculateGuardrailCost computes judge cost when no parent response is available.
CalculateCost uses this for normal responses. Logging also calls it directly for input guardrail blocks, where the main provider call never produced a BifrostResponse.
func (*Store) CalculateVideoCostDetails ¶ added in v1.6.1
func (s *Store) CalculateVideoCostDetails(dims VideoPricingDimensions, provider schemas.ModelProvider, scopes *LookupScopes) VideoCostDetails
CalculateVideoCostDetails prices a video job from its merged dimensions.
Resolution order is provider-reported cost, then the catalog rate, then nothing. A provider that hands back an exact figure is always right about its own bill, so no estimate is allowed to override it, unless the provider is configured to ignore its reported cost.
func (*Store) CatalogPricingOverrides ¶ added in v1.5.9
func (s *Store) CatalogPricingOverrides(model string, provider schemas.ModelProvider, mode string) CatalogPricingOverrides
CatalogPricingOverrides returns the overrides relevant to one management catalog row. mode is the pricing mode of the base row being displayed ("chat", "embedding", …).
The management catalog has no virtual-key/user/selected-key context, so AppliedPatch is resolved with only the provider set — scopePriorityOrder then naturally yields [provider, global], the two scopes that hold for every caller. Overrides in the virtual-key/user/provider-key families are still returned in Matching so the UI can show them as informational.
func (*Store) DatasheetModelsForProvider ¶
func (s *Store) DatasheetModelsForProvider(provider schemas.ModelProvider) []string
DatasheetModelsForProvider returns the per-provider model slice derived from pricing data on the last load/sync. Composer unions this with live.ModelsForProvider on read.
func (*Store) DatasheetProviders ¶
func (s *Store) DatasheetProviders() []schemas.ModelProvider
DatasheetProviders returns every provider that has at least one pricing row in the datasheet view. Composer unions this with live + keyconfig to enumerate "all known providers" for GetProvidersForModel.
func (*Store) DeleteOverride ¶
DeleteOverride removes an override by ID.
func (*Store) DeprecatedDatasheetModelsForProvider ¶ added in v1.4.3
func (s *Store) DeprecatedDatasheetModelsForProvider(provider schemas.ModelProvider) []string
DeprecatedDatasheetModelsForProvider returns deprecated models from the datasheet view for provider. Deprecated models may disappear from provider list-models APIs but must remain visible in Bifrost catalog listings.
func (*Store) DistinctBaseModelNames ¶
DistinctBaseModelNames returns every unique base name from the catalog. Used by governance for cross-provider model selection.
func (*Store) Get ¶
func (s *Store) Get(model string, provider schemas.ModelProvider, requestType schemas.RequestType) *configstoreTables.TableModelPricing
Get returns the raw pricing row for (model, provider, requestType) or nil. Useful for callers that need exact pricing without override resolution.
func (*Store) GetCapabilityEntry ¶
func (s *Store) GetCapabilityEntry(model string, provider schemas.ModelProvider) *Entry
GetCapabilityEntry returns capability metadata (context length, supported modes, etc.) for a (model, provider) pair. Prefers chat → responses → text-completion entries; falls back to the lexicographically first mode if none of the preferred modes match. Tries the exact model first, then the canonical base model.
func (*Store) GetModelParametersByModel ¶
func (s *Store) GetModelParametersByModel(ctx context.Context, model string) (*configstoreTables.TableModelParameters, error)
GetModelParametersByModel reads a single model-parameter row from the DB. Used by the composer's cache-miss handler — installed via providerUtils.SetCacheMissHandler in the composer's Init.
func (*Store) GetPricingEntryForModel ¶
func (s *Store) GetPricingEntryForModel(model string, provider schemas.ModelProvider) *Entry
GetPricingEntryForModel returns the first pricing entry found across known modes. Preserved for callers (inference handler) that want any pricing row for the model without specifying a request type.
func (*Store) GetSupportedParameters ¶
GetSupportedParameters returns the list of OpenAI-compatible parameter names a model accepts (e.g. temperature, top_p, tools). nil for unknown.
func (*Store) IsProviderCostIgnored ¶ added in v1.9.0
func (s *Store) IsProviderCostIgnored(provider schemas.ModelProvider) bool
IsProviderCostIgnored reports whether the provider's self-reported cost should be ignored.
func (*Store) IsRequestTypeSupported ¶
func (s *Store) IsRequestTypeSupported(model string, requestType schemas.RequestType) bool
IsRequestTypeSupported checks whether a model declares support for the given request type via the model-parameters datasheet.
func (*Store) IsSameModel ¶
IsSameModel reports whether two model strings refer to the same underlying model after normalization.
func (*Store) IsTextCompletionSupported ¶
func (s *Store) IsTextCompletionSupported(model string, provider schemas.ModelProvider) bool
IsTextCompletionSupported checks whether a model has a text_completion pricing entry — used by litellmcompat to decide whether to convert text completion requests into chat completion requests.
func (*Store) LastSyncedAt ¶
LastSyncedAt returns the last successful URL→DB sync timestamp; zero before any sync has completed.
func (*Store) LoadFromDB ¶
LoadFromDB reloads the in-memory pricing cache + datasheet view from the config store. Used by the composer at bootstrap and as the gossip ReloadFromDB handler on non-leader pods.
func (*Store) LoadFromURLIntoMemory ¶
LoadFromURLIntoMemory loads pricing from the URL directly into memory (no DB). Used when the composer was built without a config store.
func (*Store) LoadModelCapabilities ¶ added in v1.6.0
func (s *Store) LoadModelCapabilities(ctx context.Context, provider schemas.ModelProvider, model string) (*schemas.ModelCapabilities, error)
LoadModelCapabilities resolves a runtime (provider, model) pair to its capability record by reading datasheet rows out of the DB. It is the loader behind the provider-utils capability cache, and it owns the key translation that cache deliberately knows nothing about: the datasheet keys rows by a raw, often provider-prefixed name ("azure/gpt-5.1-chat") while callers ask by bare model plus provider.
A row only answers for a model when its own provider matches, so an OpenAI request for "o1" can never be served the "azure/o1" row.
Returns (nil, nil) when nothing matches — the cache remembers that, so an unknown model is looked up once rather than on every capability check. A store failure returns an error instead, which keeps the caller from caching "no capabilities" for a model whose row exists but was unreachable.
func (*Store) LoadModelParamsFromDB ¶
LoadModelParamsFromDB bulk-loads model parameters from the DB into the provider-utils cache and the in-memory supportedResponseTypes / supportedParams indexes. Returns the row count so the composer can decide whether to background-sync from URL afterwards.
The provider-utils cache-miss handler in the composer still loads one row at a time when an unknown model is queried; both paths use the same JSON shape stored in the table.
func (*Store) LoadModelParamsFromURLIntoMemory ¶
LoadModelParamsFromURLIntoMemory fetches model parameters from the URL and applies them in-memory only. Used when there's no config store.
func (*Store) LoadOverridesFromStore ¶
LoadOverridesFromStore reloads all overrides from the config store. Called at bootstrap and after force-reload paths.
func (*Store) MarkSynced ¶
MarkSynced records the timestamp of a successful sync — called by the composer's ticker after a successful tick.
func (*Store) ModelParametersURL ¶
ModelParametersURL returns a snapshot of the model-parameters URL.
func (*Store) ReplaceIgnoreProviderCost ¶ added in v1.9.0
func (s *Store) ReplaceIgnoreProviderCost(providers []schemas.ModelProvider)
ReplaceIgnoreProviderCost resets the set of providers whose reported cost is ignored.
func (*Store) ResolveModelParameters ¶ added in v1.5.5
func (s *Store) ResolveModelParameters(ctx context.Context, model string) (*configstoreTables.TableModelParameters, error)
ResolveModelParameters looks up a model-parameter row for model, falling back through equivalent identifiers when the exact key has no row. The model-parameters datasheet keys models inconsistently — some bare ("gpt-5.5"), some provider-qualified ("openrouter/moonshotai/kimi-k2.5") — so clients querying with the IDs returned by /v1/models need this resolution to land on the stored key. Mirrors GetCapabilityEntry's exact → stripped → base-model fallback chain.
func (*Store) RoutingCallCost ¶ added in v1.6.1
func (s *Store) RoutingCallCost(call schemas.BifrostRoutingCall, scopes *LookupScopes) float64
RoutingCallCost calculates the cost of one routing-classification call — a semantic embed, LLM completion, or decision-model call. Exported so telemetry can price each call independently and unconditionally, while CalculateCost folds a call's cost into the request's cost only when that call's CountTowardBudgets is set. If scopes is nil, an empty LookupScopes is used.
func (*Store) SetIgnoreProviderCost ¶ added in v1.9.0
func (s *Store) SetIgnoreProviderCost(provider schemas.ModelProvider, ignore bool)
SetIgnoreProviderCost records whether a provider's self-reported usage.cost is ignored in favour of catalog pricing.
func (*Store) SetOnModelParametersApplied ¶ added in v1.6.0
func (s *Store) SetOnModelParametersApplied(fn func())
SetOnModelParametersApplied registers a callback fired after a model-parameters reload lands. The catalog uses it to drop capability records built from the previous sheet; keeping it a hook means the sync path does not need to know what caches exist above it.
func (*Store) SetOverrides ¶
func (s *Store) SetOverrides(rows []configstoreTables.TablePricingOverride) error
SetOverrides replaces the full in-memory override set. Duplicate IDs in the input keep the last-seen entry (matching today's behavior).
func (*Store) SetPricingRowsForTest ¶ added in v1.8.1
func (s *Store) SetPricingRowsForTest(rows []configstoreTables.TableModelPricing)
SetPricingRowsForTest replaces pricingData with rows and rebuilds the derived indexes. Test-only seam for packages outside datasheet (e.g. the compat plugin) that need capability lookups such as max_output_tokens without running a sync.
func (*Store) SetSupportedParamsForTest ¶ added in v1.5.7
SetSupportedParamsForTest replaces the supported-parameter index. Test-only seam for packages outside datasheet (e.g. the compat plugin) that need a catalog answering GetSupportedParameters without running a sync.
func (*Store) SyncFromURL ¶
SyncFromURL fetches the upstream pricing datasheet, persists it to the DB (when configStore != nil), and refreshes the in-memory cache + derived datasheet view. On URL failure it falls back to existing DB records when any exist, otherwise propagates the error.
The composer owns the distributed lock, the ticker, and the after-sync gossip hook — none of that lives here. SyncFromURL is the pure "URL → DB → memory" step.
func (*Store) SyncInterval ¶
SyncInterval returns the minimum elapsed time between background syncs.
func (*Store) SyncModelParamsFromURL ¶
SyncModelParamsFromURL fetches model parameters from the configured URL, persists to DB (when configStore != nil), and refreshes the in-memory indexes. On URL failure it falls back to DB records when any exist.
func (*Store) UpdateSyncConfig ¶
UpdateSyncConfig replaces URL / params URL / interval atomically. The composer is responsible for triggering a fresh sync after this returns.
func (*Store) UpsertModelPricingAttributes ¶
func (s *Store) UpsertModelPricingAttributes(ctx context.Context, model string, provider schemas.ModelProvider, attrs map[string]string) (int64, error)
UpsertModelPricingAttributes writes the additional_attributes column for every pricing row that matches (model, provider), then reloads the pricing cache so the new values are immediately visible to list-models. Returns the number of rows updated (0 = no such pricing row, which callers must surface as a validation error). An empty/nil attrs map clears the column.
func (*Store) UpsertOverrides ¶
func (s *Store) UpsertOverrides(rows ...*configstoreTables.TablePricingOverride) error
UpsertOverrides inserts or replaces one or more overrides, rebuilding the lookup map exactly once at the end.
type VideoCostDetails ¶ added in v1.6.1
type VideoCostDetails struct {
Cost float64
Priced bool
ProviderCostUsed bool
Breakdown *schemas.BifrostCost
}
VideoCostDetails is the outcome of pricing one video job. Priced separates "this costs nothing" from "no rate could be found", which the settlement engine treats as entirely different states.
type VideoPricingDimensions ¶ added in v1.6.1
type VideoPricingDimensions struct {
// Model is the resolved model, which matters because several providers never
// echo it back on retrieve.
Model string `json:"model,omitempty"`
// RequestType distinguishes generation from remix and edit. They can price on
// different bases even for the same model.
RequestType schemas.RequestType `json:"request_type,omitempty"`
// Seconds and Size are the two dimensions every published video rate card uses
// today. Size is "WxH"; the rate is banded on its short edge.
Seconds *int `json:"seconds,omitempty"`
Size string `json:"size,omitempty"`
// Type is the operation selector ("upscale", "3d"). These do not bill per
// second of output at all, so a rate keyed only on duration is wrong for them.
Type *string `json:"type,omitempty"`
// UpscaleFactor and TargetMegapixels are the upscale operation's own basis;
// they are mutually exclusive.
UpscaleFactor *int `json:"upscale_factor,omitempty"`
TargetMegapixels *int `json:"target_megapixels,omitempty"`
// Audio: Veo publishes distinct with- and without-audio rates.
Audio *bool `json:"audio,omitempty"`
// OutputCount is how many clips the job actually returned. It is 0 until the
// job produces them, and a job that has not finished still owes one clip's
// worth rather than nothing — see computeVideoCost.
OutputCount int `json:"output_count,omitempty"`
// ProviderCost is the provider's own figure for the job, when it reports one
// (Runware does). It wins over every rate below.
ProviderCost *float64 `json:"provider_cost,omitempty"`
// OutputTokens is the provider's metered output for the job (Seedance's
// completion_tokens). Only the terminal response reports it.
OutputTokens int `json:"output_tokens,omitempty"`
// Extra carries request knobs that are not modeled above yet. It is the
// research hatch: capture a provider-specific parameter here as soon as it
// looks price-relevant, and promote it to a typed field once its rate is
// actually published. Nothing reads it during pricing today.
Extra map[string]any `json:"extra,omitempty"`
}
VideoPricingDimensions is every factor that can move the price of one video job.
It exists because a video is billed long after it is asked for. The request says what was ordered, the terminal response says what was actually produced, and neither alone is enough: most providers' retrieve response reports little beyond "done", so a dimension not captured at submission is usually unrecoverable by the time settlement runs.
Adding a dimension is deliberately cheap. The whole struct is persisted as JSON in a single TEXT column, so a new field needs no migration and no capture-side change beyond setting it — which is the point: the provider rate cards are not fully mapped yet, and a factor worth capturing should not have to wait for one. Fields that no rate reads yet are still worth recording.
func VideoDimensionsFromResponse ¶ added in v1.6.1
func VideoDimensionsFromResponse(resp *schemas.BifrostVideoGenerationResponse) VideoPricingDimensions
VideoDimensionsFromResponse reads back everything a terminal video response actually reports. What that amounts to varies enormously by provider — OpenAI echoes model, duration and size; several others report only a status and a URL — which is precisely why the submitted request is kept as the fallback.
func (VideoPricingDimensions) MergedWith ¶ added in v1.6.1
func (d VideoPricingDimensions) MergedWith(observed VideoPricingDimensions) VideoPricingDimensions
MergedWith layers observed dimensions over captured ones: whatever the argument actually knows wins, and the receiver fills every remaining gap.
The direction matters at settlement. The request is a statement of intent — a provider may clamp a duration, downscale a resolution, or return fewer clips than asked for — so the terminal response is authoritative wherever it says anything at all, and the submitted request is the fallback for everything it stays silent about (which, for most providers, is nearly everything).