Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion core/mcp/toolmanager.go
Original file line number Diff line number Diff line change
Expand Up @@ -585,7 +585,7 @@ func (m *ToolsManager) executeToolInternal(ctx *schemas.BifrostContext, toolCall
return nil, "", "", fmt.Errorf("per-user OAuth requires an OAuth2Provider but none is configured")
}
virtualKeyID, _ := ctx.Value(schemas.BifrostContextKeyGovernanceVirtualKeyID).(string)
userID, _ := ctx.Value(schemas.BifrostContextKeyGovernanceUserID).(string)
userID, _ := ctx.Value(schemas.BifrostContextKeyUserID).(string)
sessionToken, _ := ctx.Value(schemas.BifrostContextKeyMCPUserSession).(string)

// Optional X-Bf-User-Id header overrides user identity; if absent, falls back to virtual key
Expand Down
4 changes: 2 additions & 2 deletions core/schemas/bifrost.go
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,6 @@ const (
BifrostContextKeyGovernanceTeamName BifrostContextKey = "bifrost-governance-team-name" // string (to store the team name (set by bifrost governance plugin - DO NOT SET THIS MANUALLY))
BifrostContextKeyGovernanceCustomerID BifrostContextKey = "bifrost-governance-customer-id" // string (to store the customer ID (set by bifrost governance plugin - DO NOT SET THIS MANUALLY))
BifrostContextKeyGovernanceCustomerName BifrostContextKey = "bifrost-governance-customer-name" // string (to store the customer name (set by bifrost governance plugin - DO NOT SET THIS MANUALLY))
BifrostContextKeyGovernanceUserID BifrostContextKey = "bifrost-governance-user-id" // string (to store the user ID (set by enterprise governance plugin - DO NOT SET THIS MANUALLY))
BifrostContextKeyGovernanceBusinessUnitID BifrostContextKey = "bifrost-governance-business-unit-id" // string (to store the business unit ID (set by enterprise governance plugin - DO NOT SET THIS MANUALLY))
BifrostContextKeyGovernanceBusinessUnitName BifrostContextKey = "bifrost-governance-business-unit-name" // string (to store the business unit name (set by enterprise governance plugin - DO NOT SET THIS MANUALLY))
BifrostContextKeyGovernanceRoutingRuleID BifrostContextKey = "bifrost-governance-routing-rule-id" // string (to store the routing rule ID (set by bifrost governance plugin - DO NOT SET THIS MANUALLY))
Expand Down Expand Up @@ -252,7 +251,8 @@ const (
BifrostContextKeyRequestHeaders BifrostContextKey = "bifrost-request-headers" // map[string]string (all request headers with lowercased keys)
BifrostContextKeySkipListModelsGovernanceFiltering BifrostContextKey = "bifrost-skip-list-models-governance-filtering" // bool (set by bifrost - DO NOT SET THIS MANUALLY))
BifrostContextKeySCIMClaims BifrostContextKey = "scim_claims"
BifrostContextKeyUserID BifrostContextKey = "user_id"
BifrostContextKeyUserID BifrostContextKey = "bifrost-user-id" // string (to store the user ID (set by enterprise auth middleware - DO NOT SET THIS MANUALLY))
BifrostContextKeyUserName BifrostContextKey = "bifrost-user-name" // string (to store the user name (set by enterprise auth middleware - DO NOT SET THIS MANUALLY))
BifrostContextKeyTargetUserID BifrostContextKey = "target_user_id"
BifrostContextKeyIsAzureUserAgent BifrostContextKey = "bifrost-is-azure-user-agent" // bool (set by bifrost - DO NOT SET THIS MANUALLY)) - whether the request is an Azure user agent (only used in gateway)
BifrostContextKeyVideoOutputRequested BifrostContextKey = "bifrost-video-output-requested"
Expand Down
37 changes: 37 additions & 0 deletions framework/logstore/migrations.go
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,9 @@ func triggerMigrations(ctx context.Context, db *gorm.DB) error {
if err := migrationAddSelectedPromptColumns(ctx, db); err != nil {
return err
}
if err := migrationAddUserNameColumn(ctx, db); err != nil {
return err
}
return nil
}

Expand Down Expand Up @@ -2357,6 +2360,40 @@ func migrationAddImageVariationInputColumn(ctx context.Context, db *gorm.DB) err
return nil
}

// migrationAddUserNameColumn adds the user_name column to the logs table.
// Adding a nullable column is instant in Postgres (metadata-only change, no table rewrite).
func migrationAddUserNameColumn(ctx context.Context, db *gorm.DB) error {
opts := *migrator.DefaultOptions
opts.UseTransaction = true
m := migrator.New(db, &opts, []*migrator.Migration{{
ID: "logs_add_user_name_column",
Migrate: func(tx *gorm.DB) error {
tx = tx.WithContext(ctx)
mig := tx.Migrator()
if !mig.HasColumn(&Log{}, "user_name") {
if err := mig.AddColumn(&Log{}, "user_name"); err != nil {
return err
}
}
return nil
},
Rollback: func(tx *gorm.DB) error {
tx = tx.WithContext(ctx)
mig := tx.Migrator()
if mig.HasColumn(&Log{}, "user_name") {
if err := mig.DropColumn(&Log{}, "user_name"); err != nil {
return err
}
}
return nil
},
}})
if err := m.Migrate(); err != nil {
return fmt.Errorf("error while adding user_name column: %s", err.Error())
}
return nil
}

// migrationAddGovernanceContextColumns adds user_id, team_id, team_name, customer_id, customer_name,
// business_unit_id, business_unit_name columns to the logs table.
func migrationAddGovernanceContextColumns(ctx context.Context, db *gorm.DB) error {
Expand Down
1 change: 1 addition & 0 deletions framework/logstore/tables.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ type Log struct {
SelectedPromptVersion *string `gorm:"type:varchar(64)" json:"selected_prompt_version"`
SelectedPromptID *string `gorm:"type:varchar(36)" json:"selected_prompt_id"`
UserID *string `gorm:"type:varchar(255);index:idx_logs_user_id" json:"user_id"`
UserName *string `gorm:"type:varchar(255)" json:"user_name"`
TeamID *string `gorm:"type:varchar(255);index:idx_logs_team_id" json:"team_id"`
TeamName *string `gorm:"type:varchar(255)" json:"team_name"`
CustomerID *string `gorm:"type:varchar(255);index:idx_logs_customer_id" json:"customer_id"`
Expand Down
2 changes: 1 addition & 1 deletion framework/oauth2/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -808,7 +808,7 @@ func (p *OAuth2Provider) InitiateUserOAuthFlow(ctx context.Context, oauthConfigI

// Propagate identity from context so the callback can link the token to the user
virtualKeyID, _ := ctx.Value(schemas.BifrostContextKeyGovernanceVirtualKeyID).(string)
userID, _ := ctx.Value(schemas.BifrostContextKeyGovernanceUserID).(string)
userID, _ := ctx.Value(schemas.BifrostContextKeyUserID).(string)
// For OSS: prefer X-Bf-User-Id header as user identity
if mcpUserID, _ := ctx.Value(schemas.BifrostContextKeyMCPUserID).(string); mcpUserID != "" {
userID = mcpUserID
Expand Down
8 changes: 4 additions & 4 deletions plugins/governance/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -1267,7 +1267,7 @@ func (p *GovernancePlugin) PreLLMHook(ctx *schemas.BifrostContext, req *schemas.
// Extract governance headers and virtual key using utility functions
virtualKeyValue := bifrost.GetStringFromContext(ctx, schemas.BifrostContextKeyVirtualKey)
// Extract user ID for enterprise user-level governance
userID := bifrost.GetStringFromContext(ctx, schemas.BifrostContextKeyGovernanceUserID)
userID := bifrost.GetStringFromContext(ctx, schemas.BifrostContextKeyUserID)
// Getting provider and mode from the request
provider, model, _ := req.GetRequestFields()
// Create request context for evaluation
Expand Down Expand Up @@ -1311,7 +1311,7 @@ func (p *GovernancePlugin) PostLLMHook(ctx *schemas.BifrostContext, result *sche
virtualKey := bifrost.GetStringFromContext(ctx, schemas.BifrostContextKeyVirtualKey)
requestID := bifrost.GetStringFromContext(ctx, schemas.BifrostContextKeyRequestID)
// Extract user ID for enterprise user-level governance
userID := bifrost.GetStringFromContext(ctx, schemas.BifrostContextKeyGovernanceUserID)
userID := bifrost.GetStringFromContext(ctx, schemas.BifrostContextKeyUserID)

if requestType == schemas.ListModelsRequest && result != nil && result.ListModelsResponse != nil && virtualKey != "" {
// filter models which are not supported on this virtual key
Expand Down Expand Up @@ -1368,7 +1368,7 @@ func (p *GovernancePlugin) PreMCPHook(ctx *schemas.BifrostContext, req *schemas.
// Extract governance headers and virtual key using utility functions
virtualKeyValue := bifrost.GetStringFromContext(ctx, schemas.BifrostContextKeyVirtualKey)
// Extract user ID for enterprise user-level governance
userID := bifrost.GetStringFromContext(ctx, schemas.BifrostContextKeyGovernanceUserID)
userID := bifrost.GetStringFromContext(ctx, schemas.BifrostContextKeyUserID)

// Create request context for evaluation (MCP requests don't have provider/model)
evaluationRequest := &EvaluationRequest{
Expand Down Expand Up @@ -1435,7 +1435,7 @@ func (p *GovernancePlugin) PostMCPHook(ctx *schemas.BifrostContext, resp *schema
// Extract governance information
virtualKey := bifrost.GetStringFromContext(ctx, schemas.BifrostContextKeyVirtualKey)
requestID := bifrost.GetStringFromContext(ctx, schemas.BifrostContextKeyRequestID)
userID := bifrost.GetStringFromContext(ctx, schemas.BifrostContextKeyGovernanceUserID)
userID := bifrost.GetStringFromContext(ctx, schemas.BifrostContextKeyUserID)

// When user auth is present, skip VK usage tracking to avoid double-counting
if userID != "" {
Expand Down
5 changes: 3 additions & 2 deletions plugins/logging/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -688,7 +688,8 @@ func (p *LoggerPlugin) PostLLMHook(ctx *schemas.BifrostContext, result *schemas.
teamName := bifrost.GetStringFromContext(ctx, schemas.BifrostContextKeyGovernanceTeamName)
customerID := bifrost.GetStringFromContext(ctx, schemas.BifrostContextKeyGovernanceCustomerID)
customerName := bifrost.GetStringFromContext(ctx, schemas.BifrostContextKeyGovernanceCustomerName)
userID := bifrost.GetStringFromContext(ctx, schemas.BifrostContextKeyGovernanceUserID)
userID := bifrost.GetStringFromContext(ctx, schemas.BifrostContextKeyUserID)
userName := bifrost.GetStringFromContext(ctx, schemas.BifrostContextKeyUserName)
businessUnitID := bifrost.GetStringFromContext(ctx, schemas.BifrostContextKeyGovernanceBusinessUnitID)
businessUnitName := bifrost.GetStringFromContext(ctx, schemas.BifrostContextKeyGovernanceBusinessUnitName)
numberOfRetries := bifrost.GetIntFromContext(ctx, schemas.BifrostContextKeyNumberOfRetries)
Expand Down Expand Up @@ -768,7 +769,7 @@ func (p *LoggerPlugin) PostLLMHook(ctx *schemas.BifrostContext, result *schemas.
if result != nil {
latency = result.GetExtraFields().Latency
}
applyOutputFieldsToEntry(entry, selectedKeyID, selectedKeyName, virtualKeyID, virtualKeyName, routingRuleID, routingRuleName, selectedPromptID, selectedPromptName, selectedPromptVersion, teamID, teamName, customerID, customerName, userID, businessUnitID, businessUnitName, numberOfRetries, latency, attemptTrail)
applyOutputFieldsToEntry(entry, selectedKeyID, selectedKeyName, virtualKeyID, virtualKeyName, routingRuleID, routingRuleName, selectedPromptID, selectedPromptName, selectedPromptVersion, teamID, teamName, customerID, customerName, userID, userName, businessUnitID, businessUnitName, numberOfRetries, latency, attemptTrail)
entry.MetadataParsed = pending.InitialData.Metadata
entry.MetadataParsed = mergeRealtimeMetadata(entry.MetadataParsed, ctx)
entry.RoutingEngineLogs = routingEngineLogs
Expand Down
5 changes: 4 additions & 1 deletion plugins/logging/writer.go
Original file line number Diff line number Diff line change
Expand Up @@ -356,7 +356,7 @@ func applyOutputFieldsToEntry(
selectedPromptID, selectedPromptName, selectedPromptVersion string,
teamID, teamName string,
customerID, customerName string,
userID string,
userID, userName string,
businessUnitID, businessUnitName string,
numberOfRetries int,
latency int64,
Expand Down Expand Up @@ -400,6 +400,9 @@ func applyOutputFieldsToEntry(
if userID != "" {
entry.UserID = &userID
}
if userName != "" {
entry.UserName = &userName
}
if businessUnitID != "" {
entry.BusinessUnitID = &businessUnitID
}
Expand Down
2 changes: 1 addition & 1 deletion transports/bifrost-http/handlers/mcpserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ func injectMCPSessionIdentity(bifrostCtx *schemas.BifrostContext, session *table
}
}
if session.UserID != nil && *session.UserID != "" {
bifrostCtx.SetValue(schemas.BifrostContextKeyGovernanceUserID, *session.UserID)
bifrostCtx.SetValue(schemas.BifrostContextKeyUserID, *session.UserID)
}
}
}
Expand Down
9 changes: 6 additions & 3 deletions transports/bifrost-http/handlers/realtime_client_secrets.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,8 +97,11 @@ func (h *RealtimeClientSecretsHandler) handleRequest(ctx *fasthttp.RequestCtx) {
if route.DefaultProvider == schemas.OpenAI {
bifrostCtx.SetValue(schemas.BifrostContextKeyIntegrationType, "openai")
}
if governanceUserID, ok := ctx.UserValue(schemas.BifrostContextKeyGovernanceUserID).(string); ok && governanceUserID != "" {
bifrostCtx.SetValue(schemas.BifrostContextKeyGovernanceUserID, governanceUserID)
if governanceUserID, ok := ctx.UserValue(schemas.BifrostContextKeyUserID).(string); ok && governanceUserID != "" {
bifrostCtx.SetValue(schemas.BifrostContextKeyUserID, governanceUserID)
}
if userName, ok := ctx.UserValue(schemas.BifrostContextKeyUserName).(string); ok && userName != "" {
bifrostCtx.SetValue(schemas.BifrostContextKeyUserName, userName)
}
if bifrostErr := h.evaluateMintingGovernance(bifrostCtx, providerKey, model); bifrostErr != nil {
SendBifrostError(ctx, bifrostErr)
Expand Down Expand Up @@ -179,7 +182,7 @@ func (h *RealtimeClientSecretsHandler) evaluateMintingGovernance(
VirtualKey: bifrost.GetStringFromContext(bifrostCtx, schemas.BifrostContextKeyVirtualKey),
Provider: providerKey,
Model: model,
UserID: bifrost.GetStringFromContext(bifrostCtx, schemas.BifrostContextKeyGovernanceUserID),
UserID: bifrost.GetStringFromContext(bifrostCtx, schemas.BifrostContextKeyUserID),
}, schemas.RealtimeRequest)
return bifrostErr
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -378,7 +378,7 @@ func TestRealtimeClientSecretsEvaluateMintingGovernance_PassesContext(t *testing
handler := NewRealtimeClientSecretsHandler(nil, config)
bifrostCtx := schemas.NewBifrostContext(context.Background(), schemas.NoDeadline)
defer bifrostCtx.Done()
bifrostCtx.SetValue(schemas.BifrostContextKeyGovernanceUserID, "user_123")
bifrostCtx.SetValue(schemas.BifrostContextKeyUserID, "user_123")
bifrostCtx.SetValue(schemas.BifrostContextKeyVirtualKey, "sk-bf-123")

if err := handler.evaluateMintingGovernance(bifrostCtx, schemas.OpenAI, "gpt-realtime"); err != nil {
Expand Down
3 changes: 2 additions & 1 deletion transports/bifrost-http/handlers/webrtc_realtime.go
Original file line number Diff line number Diff line change
Expand Up @@ -1062,7 +1062,8 @@ func newRealtimeRelayContext(requestCtx *schemas.BifrostContext) (*schemas.Bifro
schemas.BifrostContextKeyGovernanceCustomerName,
schemas.BifrostContextKeyGovernanceTeamID,
schemas.BifrostContextKeyGovernanceTeamName,
schemas.BifrostContextKeyGovernanceUserID,
schemas.BifrostContextKeyUserID,
schemas.BifrostContextKeyUserName,
schemas.BifrostContextKeyGovernanceIncludeOnlyKeys,
schemas.BifrostContextKeyGovernancePluginName,
schemas.BifrostContextKeySelectedKeyID,
Expand Down
18 changes: 18 additions & 0 deletions ui/app/_fallbacks/enterprise/lib/store/apis/accessProfileApi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { GetUserAccessProfilesResponse } from "@enterprise/lib/types/accessProfile";

// OSS build has no access-profile backend — return undefined data so consumers
// (e.g. useVirtualKeyUsage) fall back to VK-owned budget/rate-limit values.
export const useGetUserAccessProfilesQuery = (
_userId: string,
_opts?: { skip?: boolean; pollingInterval?: number },
): {
data: GetUserAccessProfilesResponse | undefined;
isLoading: boolean;
isError: boolean;
error: null;
} => ({
data: undefined,
isLoading: false,
isError: false,
error: null,
});
22 changes: 22 additions & 0 deletions ui/app/_fallbacks/enterprise/lib/store/apis/virtualKeyUsersApi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { User } from "@enterprise/lib/types/user";

export interface GetVirtualKeyUsersResponse {
users: User[];
}

// OSS build has no VK-user-attachment backend — return undefined data so the
// consumer treats the VK as unassigned (no AP-managed detection happens).
export const useGetVirtualKeyUsersQuery = (
_vkId: string,
_opts?: { skip?: boolean },
): {
data: GetVirtualKeyUsersResponse | undefined;
isLoading: boolean;
isError: boolean;
error: null;
} => ({
data: undefined,
isLoading: false,
isError: false,
error: null,
});
41 changes: 41 additions & 0 deletions ui/app/_fallbacks/enterprise/lib/types/accessProfile.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
export interface AccessProfileBudgetLine {
id: string;
scope: string;
max_limit: number;
reset_duration: string;
current_usage: number;
last_reset: string;
alert_thresholds?: number[];
}

export interface AccessProfileRateLimitLine {
token_max_limit?: number;
token_reset_duration?: string;
token_current_usage?: number;
token_last_reset?: string;
request_max_limit?: number;
request_reset_duration?: string;
request_current_usage?: number;
request_last_reset?: string;
}

export interface UserAccessProfile {
id: number;
user_id: string;
parent_profile_id?: number;
virtual_key_ids?: string[];
virtual_key_values?: Record<string, string>;
name: string;
is_active: boolean;
expires_at?: string;
provider_configs?: unknown[];
budget_lines?: AccessProfileBudgetLine[];
rate_limits?: AccessProfileRateLimitLine;
mcp_configs?: unknown;
created_at: string;
updated_at: string;
}

export interface GetUserAccessProfilesResponse {
access_profiles: UserAccessProfile[];
}
30 changes: 30 additions & 0 deletions ui/app/_fallbacks/enterprise/lib/types/user.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { UserAccessProfile } from "@enterprise/lib/types/accessProfile";

export interface User {
id: string;
name: string;
email: string;
role_id?: number;
role?: {
id: number;
name: string;
description: string;
is_system_role: boolean;
};
profile?: Record<string, unknown>;
config?: Record<string, unknown>;
claims?: Record<string, unknown>;
access_profile?: UserAccessProfile;
teams?: Array<{ id: string; name: string; business_unit_id?: string; business_unit_name?: string }>;
created_at: string;
updated_at: string;
}

export interface GetUsersResponse {
users: User[];
total: number;
page: number;
limit: number;
total_pages: number;
has_more: boolean;
}
9 changes: 1 addition & 8 deletions ui/app/workspace/dashboard/components/modelRankingsTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Skeleton } from "@/components/ui/skeleton";
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
import ProviderIcons, { type ProviderIconType, RenderProviderIcon } from "@/lib/constants/icons";
import type { ModelHistogramResponse, ModelRankingEntry, ModelRankingsResponse } from "@/lib/types/logs";
import { formatCompactNumber as formatNumber } from "@/lib/utils/governance";
import { ArrowDown, ArrowUp, ArrowUpDown, Minus } from "lucide-react";
import { useCallback, useMemo, useState } from "react";
import { Bar, BarChart, CartesianGrid, ResponsiveContainer, Tooltip, XAxis, YAxis } from "recharts";
Expand All @@ -22,14 +23,6 @@ interface ModelRankingsTabProps {
endTime: number;
}

function formatNumber(value: number): string {
if (value >= 1_000_000_000_000) return `${(value / 1_000_000_000_000).toFixed(2)}T`;
if (value >= 1_000_000_000) return `${(value / 1_000_000_000).toFixed(2)}B`;
if (value >= 1_000_000) return `${(value / 1_000_000).toFixed(1)}M`;
if (value >= 1_000) return `${(value / 1_000).toFixed(1)}K`;
return value.toLocaleString();
}

function formatCost(value: number): string {
if (value >= 1) return `$${value.toFixed(2)}`;
if (value >= 0.01) return `$${value.toFixed(3)}`;
Expand Down
Loading
Loading