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
1 change: 1 addition & 0 deletions core/mcp/agentadaptors.go
Original file line number Diff line number Diff line change
Expand Up @@ -449,6 +449,7 @@ func createResponsesResponseWithExecutedToolsAndNonAutoExecutableCalls(
Prompt: originalResponse.Prompt,
PromptCacheKey: originalResponse.PromptCacheKey,
PromptCacheRetention: originalResponse.PromptCacheRetention,
PromptCacheOptions: originalResponse.PromptCacheOptions,
Reasoning: originalResponse.Reasoning,
SafetyIdentifier: originalResponse.SafetyIdentifier,
ServiceTier: originalResponse.ServiceTier,
Expand Down
73 changes: 73 additions & 0 deletions core/providers/anthropic/anthropic.go
Original file line number Diff line number Diff line change
Expand Up @@ -621,6 +621,26 @@ func accumulateAnthropicResponsesUsage(usage *schemas.ResponsesResponseUsage, bi
if usage == nil || usageToProcess == nil {
return
}
// Web search request count → billed as search queries (server tool use). The
// terminal chunk overwrites Response.Usage with this accumulator, so the count
// must live here (not only on the per-event message_delta usage).
if usageToProcess.ServerToolUse != nil && usageToProcess.ServerToolUse.WebSearchRequests > 0 {
n := usageToProcess.ServerToolUse.WebSearchRequests
if usage.OutputTokensDetails == nil {
usage.OutputTokensDetails = &schemas.ResponsesResponseOutputTokens{}
}
if usage.OutputTokensDetails.NumSearchQueries == nil || n > *usage.OutputTokensDetails.NumSearchQueries {
usage.OutputTokensDetails.NumSearchQueries = schemas.Ptr(n)
}
if billedUsage != nil {
if billedUsage.CompletionTokensDetails == nil {
billedUsage.CompletionTokensDetails = &schemas.ChatCompletionTokensDetails{}
}
if billedUsage.CompletionTokensDetails.NumSearchQueries == nil || n > *billedUsage.CompletionTokensDetails.NumSearchQueries {
billedUsage.CompletionTokensDetails.NumSearchQueries = schemas.Ptr(n)
}
}
}
if usageToProcess.InputTokens > usage.InputTokens {
usage.InputTokens = usageToProcess.InputTokens
if billedUsage != nil {
Expand Down Expand Up @@ -863,6 +883,10 @@ func HandleAnthropicChatCompletionStreaming(
var finishReason *string

usage := &schemas.BifrostLLMUsage{}
// Served billing modifiers (top-level response fields, not usage) captured
// across events and set on the final chunk: fast mode and data residency.
var servedSpeed *string
var servedInferenceGeo *string
// Register the accumulating usage handle so a mid-stream cancel/timeout
// can bill for tokens already processed Mutated in place below;
// the deferred HandleStreamCancellation/Timeout reads it from context.
Expand Down Expand Up @@ -938,6 +962,26 @@ func HandleAnthropicChatCompletionStreaming(
usageToProcess = event.Message.Usage
}
if usageToProcess != nil {
// Web search request count → billed as search queries (server tool use).
if usageToProcess.ServerToolUse != nil && usageToProcess.ServerToolUse.WebSearchRequests > 0 {
if usage.CompletionTokensDetails == nil {
usage.CompletionTokensDetails = &schemas.ChatCompletionTokensDetails{}
}
if n := usageToProcess.ServerToolUse.WebSearchRequests; usage.CompletionTokensDetails.NumSearchQueries == nil || n > *usage.CompletionTokensDetails.NumSearchQueries {
usage.CompletionTokensDetails.NumSearchQueries = &n
}
}
// Capture served fast mode + inference geography (top-level response fields).
// Mirror onto the billing usage handle so a mid-stream cancel/timeout can
// still apply the served-tier multiplier (billed usage is otherwise bare).
if usageToProcess.Speed != nil {
servedSpeed = usageToProcess.Speed
usage.Speed = usageToProcess.Speed
}
if usageToProcess.InferenceGeo != nil {
servedInferenceGeo = usageToProcess.InferenceGeo
usage.InferenceGeo = usageToProcess.InferenceGeo
}
// Collect usage information and send at the end of the stream
// Here in some cases usage comes before final message
// So we need to check if the response.Usage is nil and then if usage != nil
Expand Down Expand Up @@ -1099,6 +1143,13 @@ func HandleAnthropicChatCompletionStreaming(
return
}
}
// Forward served fast mode + data residency so the final chunk bills correctly.
if servedSpeed != nil {
response.Speed = servedSpeed
}
if servedInferenceGeo != nil {
response.InferenceGeo = servedInferenceGeo
}
// Set raw request if enabled
if sendBackRawRequest {
providerUtils.ParseAndSetRawRequest(&response.ExtraFields, jsonBody)
Expand Down Expand Up @@ -1455,6 +1506,8 @@ func HandleAnthropicResponsesStream(
}

var modelName string
var servedSpeed *string
var servedInferenceGeo *string

for {
// If context was cancelled/timed out, let defer handle it
Expand Down Expand Up @@ -1503,6 +1556,20 @@ func HandleAnthropicResponsesStream(
// Also mirror it into billedUsage so cancellation/timeout paths can
// charge for provider-reported usage before the final chunk arrives.
accumulateAnthropicResponsesUsage(usage, billedUsage, usageToProcess)
// Mirror served tier onto billedUsage so a mid-stream cancel/timeout can
// still apply the served-tier multiplier (billed usage is otherwise bare).
if usageToProcess.Speed != nil {
servedSpeed = usageToProcess.Speed
if billedUsage != nil {
billedUsage.Speed = usageToProcess.Speed
}
}
if usageToProcess.InferenceGeo != nil {
servedInferenceGeo = usageToProcess.InferenceGeo
if billedUsage != nil {
billedUsage.InferenceGeo = usageToProcess.InferenceGeo
}
}
}

responses, bifrostErr, isLastChunk := event.ToBifrostResponsesStream(ctx, chunkIndex, streamState)
Expand Down Expand Up @@ -1561,6 +1628,12 @@ func HandleAnthropicResponsesStream(
usage.TotalTokens = usage.TotalTokens + usage.InputTokensDetails.CachedReadTokens + usage.InputTokensDetails.CachedWriteTokens
}
response.Response.Usage = usage
if servedSpeed != nil {
response.Response.Speed = servedSpeed
}
if servedInferenceGeo != nil {
response.Response.InferenceGeo = servedInferenceGeo
}
// Set raw request if enabled
if sendBackRawRequest {
providerUtils.ParseAndSetRawRequest(&response.ExtraFields, jsonBody)
Expand Down
11 changes: 11 additions & 0 deletions core/providers/anthropic/chat.go
Original file line number Diff line number Diff line change
Expand Up @@ -1033,6 +1033,13 @@ func (response *AnthropicMessageResponse) ToBifrostChatResponse(ctx *schemas.Bif
PromptTokensDetails: promptTokensDetails,
CompletionTokens: response.Usage.OutputTokens,
}
// Forward web search request count so server-tool use is billed.
if response.Usage.ServerToolUse != nil && response.Usage.ServerToolUse.WebSearchRequests > 0 {
n := response.Usage.ServerToolUse.WebSearchRequests
bifrostResponse.Usage.CompletionTokensDetails = &schemas.ChatCompletionTokensDetails{
NumSearchQueries: &n,
}
}
bifrostResponse.Usage.TotalTokens = bifrostResponse.Usage.PromptTokens + bifrostResponse.Usage.CompletionTokens
// Forward service tier from usage to response
if response.Usage.ServiceTier != nil {
Expand All @@ -1043,6 +1050,10 @@ func (response *AnthropicMessageResponse) ToBifrostChatResponse(ctx *schemas.Bif
if response.Usage.Speed != nil {
bifrostResponse.Speed = response.Usage.Speed
}
// Forward the inference geography served — drives the data-residency multiplier.
if response.Usage.InferenceGeo != nil {
bifrostResponse.InferenceGeo = response.Usage.InferenceGeo
}
}

// Forward cache diagnostics (cache-diagnosis-2026-04-07) — top-level on the
Expand Down
6 changes: 6 additions & 0 deletions core/providers/anthropic/passthrough_usage.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,9 @@ func buildAnthropicPassthroughUsage(au *AnthropicUsage) *schemas.BifrostPassthro
if au.Speed != nil {
u.Speed = au.Speed
}
if au.InferenceGeo != nil {
u.InferenceGeo = au.InferenceGeo
}
return u
}

Expand Down Expand Up @@ -142,6 +145,9 @@ func (a *AnthropicPassthroughStreamUsage) ObserveEvent(event []byte) *schemas.Bi
if u.Speed != nil {
c.Speed = u.Speed
}
if u.InferenceGeo != nil {
c.InferenceGeo = u.InferenceGeo
}
return a.usage()
}

Expand Down
9 changes: 8 additions & 1 deletion core/providers/anthropic/responses.go
Original file line number Diff line number Diff line change
Expand Up @@ -1988,6 +1988,8 @@ func (chunk *AnthropicStreamEvent) ToBifrostResponsesStream(ctx context.Context,
}
if bifrostUsage != nil {
response.Usage = bifrostUsage
response.Speed = chunk.Usage.Speed
response.InferenceGeo = chunk.Usage.InferenceGeo
}
// Carry the sandbox container on the message_delta event so the reverse
// converter can re-emit it (Anthropic delivers it here, not earlier).
Expand Down Expand Up @@ -3702,6 +3704,11 @@ func (response *AnthropicMessageResponse) ToBifrostResponsesResponse(ctx *schema
bifrostResp.Speed = response.Usage.Speed
}

// Forward the inference geography served — drives the data-residency multiplier.
if response.Usage != nil && response.Usage.InferenceGeo != nil {
bifrostResp.InferenceGeo = response.Usage.InferenceGeo
}

// Forward cache diagnostics (cache-diagnosis-2026-04-07) to the client.
if response.Diagnostics != nil {
bifrostResp.Diagnostics = response.Diagnostics
Expand Down Expand Up @@ -7816,4 +7823,4 @@ func generateSyntheticInputJSONDeltas(argumentsJSON string, contentIndex *int) [
}

return events
}
}
Loading
Loading