diff --git a/backend/internal/handler/dto/mappers.go b/backend/internal/handler/dto/mappers.go index 8c68b819..489c24da 100644 --- a/backend/internal/handler/dto/mappers.go +++ b/backend/internal/handler/dto/mappers.go @@ -528,8 +528,7 @@ func usageLogFromServiceUser(l *service.UsageLog) UsageLog { APIKeyID: l.APIKeyID, AccountID: l.AccountID, RequestID: l.RequestID, - Model: l.Model, - UpstreamModel: l.UpstreamModel, + Model: l.RequestedModel, ServiceTier: l.ServiceTier, ReasoningEffort: l.ReasoningEffort, InboundEndpoint: l.InboundEndpoint, @@ -586,6 +585,7 @@ func UsageLogFromServiceAdmin(l *service.UsageLog) *AdminUsageLog { } return &AdminUsageLog{ UsageLog: usageLogFromServiceUser(l), + UpstreamModel: l.UpstreamModel, AccountRateMultiplier: l.AccountRateMultiplier, IPAddress: l.IPAddress, Account: AccountSummaryFromService(l.Account), diff --git a/backend/internal/handler/dto/mappers_usage_test.go b/backend/internal/handler/dto/mappers_usage_test.go index e4031970..8fa980b4 100644 --- a/backend/internal/handler/dto/mappers_usage_test.go +++ b/backend/internal/handler/dto/mappers_usage_test.go @@ -1,6 +1,7 @@ package dto import ( + "encoding/json" "testing" "github.com/Wei-Shaw/sub2api/internal/service" @@ -106,6 +107,32 @@ func TestUsageLogFromService_IncludesServiceTierForUserAndAdmin(t *testing.T) { require.InDelta(t, 1.5, *adminDTO.AccountRateMultiplier, 1e-12) } +func TestUsageLogFromService_UsesRequestedModelAndKeepsUpstreamAdminOnly(t *testing.T) { + t.Parallel() + + upstreamModel := "claude-sonnet-4-20250514" + log := &service.UsageLog{ + RequestID: "req_4", + Model: upstreamModel, + RequestedModel: "claude-sonnet-4", + UpstreamModel: &upstreamModel, + } + + userDTO := UsageLogFromService(log) + adminDTO := UsageLogFromServiceAdmin(log) + + require.Equal(t, "claude-sonnet-4", userDTO.Model) + require.Equal(t, "claude-sonnet-4", adminDTO.Model) + + userJSON, err := json.Marshal(userDTO) + require.NoError(t, err) + require.NotContains(t, string(userJSON), "upstream_model") + + adminJSON, err := json.Marshal(adminDTO) + require.NoError(t, err) + require.Contains(t, string(adminJSON), `"upstream_model":"claude-sonnet-4-20250514"`) +} + func f64Ptr(value float64) *float64 { return &value } diff --git a/backend/internal/handler/dto/types.go b/backend/internal/handler/dto/types.go index 7b3443be..d4a24e10 100644 --- a/backend/internal/handler/dto/types.go +++ b/backend/internal/handler/dto/types.go @@ -334,9 +334,6 @@ type UsageLog struct { AccountID int64 `json:"account_id"` RequestID string `json:"request_id"` Model string `json:"model"` - // UpstreamModel is the actual model sent to the upstream provider after mapping. - // Omitted when no mapping was applied (requested model was used as-is). - UpstreamModel *string `json:"upstream_model,omitempty"` // ServiceTier records the OpenAI service tier used for billing, e.g. "priority" / "flex". ServiceTier *string `json:"service_tier,omitempty"` // ReasoningEffort is the request's reasoning effort level. @@ -396,6 +393,10 @@ type UsageLog struct { type AdminUsageLog struct { UsageLog + // UpstreamModel is the actual model sent to the upstream provider after mapping. + // Omitted when no mapping was applied (requested model was used as-is). + UpstreamModel *string `json:"upstream_model,omitempty"` + // AccountRateMultiplier 账号计费倍率快照(nil 表示按 1.0 处理) AccountRateMultiplier *float64 `json:"account_rate_multiplier"`