Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in / Register
Toggle navigation
Menu
Open sidebar
陈曦
sub2api
Commits
08c4e514
Commit
08c4e514
authored
Mar 24, 2026
by
InCerry
Browse files
Merge branch 'main' of github.com:InCerryGit/sub2api
# Conflicts: # backend/internal/service/billing_service.go
parents
73708da6
995bee14
Changes
59
Hide whitespace changes
Inline
Side-by-side
backend/ent/migrate/schema.go
View file @
08c4e514
...
...
@@ -716,6 +716,7 @@ var (
{
Name
:
"id"
,
Type
:
field
.
TypeInt64
,
Increment
:
true
},
{
Name
:
"request_id"
,
Type
:
field
.
TypeString
,
Size
:
64
},
{
Name
:
"model"
,
Type
:
field
.
TypeString
,
Size
:
100
},
{
Name
:
"requested_model"
,
Type
:
field
.
TypeString
,
Nullable
:
true
,
Size
:
100
},
{
Name
:
"upstream_model"
,
Type
:
field
.
TypeString
,
Nullable
:
true
,
Size
:
100
},
{
Name
:
"input_tokens"
,
Type
:
field
.
TypeInt
,
Default
:
0
},
{
Name
:
"output_tokens"
,
Type
:
field
.
TypeInt
,
Default
:
0
},
...
...
@@ -756,31 +757,31 @@ var (
ForeignKeys
:
[]
*
schema
.
ForeignKey
{
{
Symbol
:
"usage_logs_api_keys_usage_logs"
,
Columns
:
[]
*
schema
.
Column
{
UsageLogsColumns
[
29
]},
Columns
:
[]
*
schema
.
Column
{
UsageLogsColumns
[
30
]},
RefColumns
:
[]
*
schema
.
Column
{
APIKeysColumns
[
0
]},
OnDelete
:
schema
.
NoAction
,
},
{
Symbol
:
"usage_logs_accounts_usage_logs"
,
Columns
:
[]
*
schema
.
Column
{
UsageLogsColumns
[
3
0
]},
Columns
:
[]
*
schema
.
Column
{
UsageLogsColumns
[
3
1
]},
RefColumns
:
[]
*
schema
.
Column
{
AccountsColumns
[
0
]},
OnDelete
:
schema
.
NoAction
,
},
{
Symbol
:
"usage_logs_groups_usage_logs"
,
Columns
:
[]
*
schema
.
Column
{
UsageLogsColumns
[
3
1
]},
Columns
:
[]
*
schema
.
Column
{
UsageLogsColumns
[
3
2
]},
RefColumns
:
[]
*
schema
.
Column
{
GroupsColumns
[
0
]},
OnDelete
:
schema
.
SetNull
,
},
{
Symbol
:
"usage_logs_users_usage_logs"
,
Columns
:
[]
*
schema
.
Column
{
UsageLogsColumns
[
3
2
]},
Columns
:
[]
*
schema
.
Column
{
UsageLogsColumns
[
3
3
]},
RefColumns
:
[]
*
schema
.
Column
{
UsersColumns
[
0
]},
OnDelete
:
schema
.
NoAction
,
},
{
Symbol
:
"usage_logs_user_subscriptions_usage_logs"
,
Columns
:
[]
*
schema
.
Column
{
UsageLogsColumns
[
3
3
]},
Columns
:
[]
*
schema
.
Column
{
UsageLogsColumns
[
3
4
]},
RefColumns
:
[]
*
schema
.
Column
{
UserSubscriptionsColumns
[
0
]},
OnDelete
:
schema
.
SetNull
,
},
...
...
@@ -789,38 +790,43 @@ var (
{
Name
:
"usagelog_user_id"
,
Unique
:
false
,
Columns
:
[]
*
schema
.
Column
{
UsageLogsColumns
[
3
2
]},
Columns
:
[]
*
schema
.
Column
{
UsageLogsColumns
[
3
3
]},
},
{
Name
:
"usagelog_api_key_id"
,
Unique
:
false
,
Columns
:
[]
*
schema
.
Column
{
UsageLogsColumns
[
29
]},
Columns
:
[]
*
schema
.
Column
{
UsageLogsColumns
[
30
]},
},
{
Name
:
"usagelog_account_id"
,
Unique
:
false
,
Columns
:
[]
*
schema
.
Column
{
UsageLogsColumns
[
3
0
]},
Columns
:
[]
*
schema
.
Column
{
UsageLogsColumns
[
3
1
]},
},
{
Name
:
"usagelog_group_id"
,
Unique
:
false
,
Columns
:
[]
*
schema
.
Column
{
UsageLogsColumns
[
3
1
]},
Columns
:
[]
*
schema
.
Column
{
UsageLogsColumns
[
3
2
]},
},
{
Name
:
"usagelog_subscription_id"
,
Unique
:
false
,
Columns
:
[]
*
schema
.
Column
{
UsageLogsColumns
[
3
3
]},
Columns
:
[]
*
schema
.
Column
{
UsageLogsColumns
[
3
4
]},
},
{
Name
:
"usagelog_created_at"
,
Unique
:
false
,
Columns
:
[]
*
schema
.
Column
{
UsageLogsColumns
[
2
8
]},
Columns
:
[]
*
schema
.
Column
{
UsageLogsColumns
[
2
9
]},
},
{
Name
:
"usagelog_model"
,
Unique
:
false
,
Columns
:
[]
*
schema
.
Column
{
UsageLogsColumns
[
2
]},
},
{
Name
:
"usagelog_requested_model"
,
Unique
:
false
,
Columns
:
[]
*
schema
.
Column
{
UsageLogsColumns
[
3
]},
},
{
Name
:
"usagelog_request_id"
,
Unique
:
false
,
...
...
@@ -829,17 +835,17 @@ var (
{
Name
:
"usagelog_user_id_created_at"
,
Unique
:
false
,
Columns
:
[]
*
schema
.
Column
{
UsageLogsColumns
[
3
2
],
UsageLogsColumns
[
2
8
]},
Columns
:
[]
*
schema
.
Column
{
UsageLogsColumns
[
3
3
],
UsageLogsColumns
[
2
9
]},
},
{
Name
:
"usagelog_api_key_id_created_at"
,
Unique
:
false
,
Columns
:
[]
*
schema
.
Column
{
UsageLogsColumns
[
29
],
UsageLogsColumns
[
2
8
]},
Columns
:
[]
*
schema
.
Column
{
UsageLogsColumns
[
30
],
UsageLogsColumns
[
2
9
]},
},
{
Name
:
"usagelog_group_id_created_at"
,
Unique
:
false
,
Columns
:
[]
*
schema
.
Column
{
UsageLogsColumns
[
3
1
],
UsageLogsColumns
[
2
8
]},
Columns
:
[]
*
schema
.
Column
{
UsageLogsColumns
[
3
2
],
UsageLogsColumns
[
2
9
]},
},
},
}
...
...
backend/ent/mutation.go
View file @
08c4e514
...
...
@@ -18239,6 +18239,7 @@ type UsageLogMutation struct {
id *int64
request_id *string
model *string
requested_model *string
upstream_model *string
input_tokens *int
addinput_tokens *int
...
...
@@ -18577,6 +18578,55 @@ func (m *UsageLogMutation) ResetModel() {
m.model = nil
}
// SetRequestedModel sets the "requested_model" field.
func (m *UsageLogMutation) SetRequestedModel(s string) {
m.requested_model = &s
}
// RequestedModel returns the value of the "requested_model" field in the mutation.
func (m *UsageLogMutation) RequestedModel() (r string, exists bool) {
v := m.requested_model
if v == nil {
return
}
return *v, true
}
// OldRequestedModel returns the old "requested_model" field's value of the UsageLog entity.
// If the UsageLog object wasn't provided to the builder, the object is fetched from the database.
// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
func (m *UsageLogMutation) OldRequestedModel(ctx context.Context) (v *string, err error) {
if !m.op.Is(OpUpdateOne) {
return v, errors.New("OldRequestedModel is only allowed on UpdateOne operations")
}
if m.id == nil || m.oldValue == nil {
return v, errors.New("OldRequestedModel requires an ID field in the mutation")
}
oldValue, err := m.oldValue(ctx)
if err != nil {
return v, fmt.Errorf("querying old value for OldRequestedModel: %w", err)
}
return oldValue.RequestedModel, nil
}
// ClearRequestedModel clears the value of the "requested_model" field.
func (m *UsageLogMutation) ClearRequestedModel() {
m.requested_model = nil
m.clearedFields[usagelog.FieldRequestedModel] = struct{}{}
}
// RequestedModelCleared returns if the "requested_model" field was cleared in this mutation.
func (m *UsageLogMutation) RequestedModelCleared() bool {
_, ok := m.clearedFields[usagelog.FieldRequestedModel]
return ok
}
// ResetRequestedModel resets all changes to the "requested_model" field.
func (m *UsageLogMutation) ResetRequestedModel() {
m.requested_model = nil
delete(m.clearedFields, usagelog.FieldRequestedModel)
}
// SetUpstreamModel sets the "upstream_model" field.
func (m *UsageLogMutation) SetUpstreamModel(s string) {
m.upstream_model = &s
...
...
@@ -20247,7 +20297,7 @@ func (m *UsageLogMutation) Type() string {
// order to get all numeric fields that were incremented/decremented, call
// AddedFields().
func (m *UsageLogMutation) Fields() []string {
fields := make([]string, 0, 3
3
)
fields := make([]string, 0, 3
4
)
if m.user != nil {
fields = append(fields, usagelog.FieldUserID)
}
...
...
@@ -20263,6 +20313,9 @@ func (m *UsageLogMutation) Fields() []string {
if m.model != nil {
fields = append(fields, usagelog.FieldModel)
}
if m.requested_model != nil {
fields = append(fields, usagelog.FieldRequestedModel)
}
if m.upstream_model != nil {
fields = append(fields, usagelog.FieldUpstreamModel)
}
...
...
@@ -20365,6 +20418,8 @@ func (m *UsageLogMutation) Field(name string) (ent.Value, bool) {
return m.RequestID()
case usagelog.FieldModel:
return m.Model()
case usagelog.FieldRequestedModel:
return m.RequestedModel()
case usagelog.FieldUpstreamModel:
return m.UpstreamModel()
case usagelog.FieldGroupID:
...
...
@@ -20440,6 +20495,8 @@ func (m *UsageLogMutation) OldField(ctx context.Context, name string) (ent.Value
return m.OldRequestID(ctx)
case usagelog.FieldModel:
return m.OldModel(ctx)
case usagelog.FieldRequestedModel:
return m.OldRequestedModel(ctx)
case usagelog.FieldUpstreamModel:
return m.OldUpstreamModel(ctx)
case usagelog.FieldGroupID:
...
...
@@ -20540,6 +20597,13 @@ func (m *UsageLogMutation) SetField(name string, value ent.Value) error {
}
m.SetModel(v)
return nil
case usagelog.FieldRequestedModel:
v, ok := value.(string)
if !ok {
return fmt.Errorf("unexpected type %T for field %s", value, name)
}
m.SetRequestedModel(v)
return nil
case usagelog.FieldUpstreamModel:
v, ok := value.(string)
if !ok {
...
...
@@ -20985,6 +21049,9 @@ func (m *UsageLogMutation) AddField(name string, value ent.Value) error {
// mutation.
func (m *UsageLogMutation) ClearedFields() []string {
var fields []string
if m.FieldCleared(usagelog.FieldRequestedModel) {
fields = append(fields, usagelog.FieldRequestedModel)
}
if m.FieldCleared(usagelog.FieldUpstreamModel) {
fields = append(fields, usagelog.FieldUpstreamModel)
}
...
...
@@ -21029,6 +21096,9 @@ func (m *UsageLogMutation) FieldCleared(name string) bool {
// error if the field is not defined in the schema.
func (m *UsageLogMutation) ClearField(name string) error {
switch name {
case usagelog.FieldRequestedModel:
m.ClearRequestedModel()
return nil
case usagelog.FieldUpstreamModel:
m.ClearUpstreamModel()
return nil
...
...
@@ -21082,6 +21152,9 @@ func (m *UsageLogMutation) ResetField(name string) error {
case usagelog.FieldModel:
m.ResetModel()
return nil
case usagelog.FieldRequestedModel:
m.ResetRequestedModel()
return nil
case usagelog.FieldUpstreamModel:
m.ResetUpstreamModel()
return nil
...
...
backend/ent/runtime/runtime.go
View file @
08c4e514
...
...
@@ -821,96 +821,100 @@ func init() {
return
nil
}
}()
// usagelogDescRequestedModel is the schema descriptor for requested_model field.
usagelogDescRequestedModel
:=
usagelogFields
[
5
]
.
Descriptor
()
// usagelog.RequestedModelValidator is a validator for the "requested_model" field. It is called by the builders before save.
usagelog
.
RequestedModelValidator
=
usagelogDescRequestedModel
.
Validators
[
0
]
.
(
func
(
string
)
error
)
// usagelogDescUpstreamModel is the schema descriptor for upstream_model field.
usagelogDescUpstreamModel
:=
usagelogFields
[
5
]
.
Descriptor
()
usagelogDescUpstreamModel
:=
usagelogFields
[
6
]
.
Descriptor
()
// usagelog.UpstreamModelValidator is a validator for the "upstream_model" field. It is called by the builders before save.
usagelog
.
UpstreamModelValidator
=
usagelogDescUpstreamModel
.
Validators
[
0
]
.
(
func
(
string
)
error
)
// usagelogDescInputTokens is the schema descriptor for input_tokens field.
usagelogDescInputTokens
:=
usagelogFields
[
8
]
.
Descriptor
()
usagelogDescInputTokens
:=
usagelogFields
[
9
]
.
Descriptor
()
// usagelog.DefaultInputTokens holds the default value on creation for the input_tokens field.
usagelog
.
DefaultInputTokens
=
usagelogDescInputTokens
.
Default
.
(
int
)
// usagelogDescOutputTokens is the schema descriptor for output_tokens field.
usagelogDescOutputTokens
:=
usagelogFields
[
9
]
.
Descriptor
()
usagelogDescOutputTokens
:=
usagelogFields
[
10
]
.
Descriptor
()
// usagelog.DefaultOutputTokens holds the default value on creation for the output_tokens field.
usagelog
.
DefaultOutputTokens
=
usagelogDescOutputTokens
.
Default
.
(
int
)
// usagelogDescCacheCreationTokens is the schema descriptor for cache_creation_tokens field.
usagelogDescCacheCreationTokens
:=
usagelogFields
[
1
0
]
.
Descriptor
()
usagelogDescCacheCreationTokens
:=
usagelogFields
[
1
1
]
.
Descriptor
()
// usagelog.DefaultCacheCreationTokens holds the default value on creation for the cache_creation_tokens field.
usagelog
.
DefaultCacheCreationTokens
=
usagelogDescCacheCreationTokens
.
Default
.
(
int
)
// usagelogDescCacheReadTokens is the schema descriptor for cache_read_tokens field.
usagelogDescCacheReadTokens
:=
usagelogFields
[
1
1
]
.
Descriptor
()
usagelogDescCacheReadTokens
:=
usagelogFields
[
1
2
]
.
Descriptor
()
// usagelog.DefaultCacheReadTokens holds the default value on creation for the cache_read_tokens field.
usagelog
.
DefaultCacheReadTokens
=
usagelogDescCacheReadTokens
.
Default
.
(
int
)
// usagelogDescCacheCreation5mTokens is the schema descriptor for cache_creation_5m_tokens field.
usagelogDescCacheCreation5mTokens
:=
usagelogFields
[
1
2
]
.
Descriptor
()
usagelogDescCacheCreation5mTokens
:=
usagelogFields
[
1
3
]
.
Descriptor
()
// usagelog.DefaultCacheCreation5mTokens holds the default value on creation for the cache_creation_5m_tokens field.
usagelog
.
DefaultCacheCreation5mTokens
=
usagelogDescCacheCreation5mTokens
.
Default
.
(
int
)
// usagelogDescCacheCreation1hTokens is the schema descriptor for cache_creation_1h_tokens field.
usagelogDescCacheCreation1hTokens
:=
usagelogFields
[
1
3
]
.
Descriptor
()
usagelogDescCacheCreation1hTokens
:=
usagelogFields
[
1
4
]
.
Descriptor
()
// usagelog.DefaultCacheCreation1hTokens holds the default value on creation for the cache_creation_1h_tokens field.
usagelog
.
DefaultCacheCreation1hTokens
=
usagelogDescCacheCreation1hTokens
.
Default
.
(
int
)
// usagelogDescInputCost is the schema descriptor for input_cost field.
usagelogDescInputCost
:=
usagelogFields
[
1
4
]
.
Descriptor
()
usagelogDescInputCost
:=
usagelogFields
[
1
5
]
.
Descriptor
()
// usagelog.DefaultInputCost holds the default value on creation for the input_cost field.
usagelog
.
DefaultInputCost
=
usagelogDescInputCost
.
Default
.
(
float64
)
// usagelogDescOutputCost is the schema descriptor for output_cost field.
usagelogDescOutputCost
:=
usagelogFields
[
1
5
]
.
Descriptor
()
usagelogDescOutputCost
:=
usagelogFields
[
1
6
]
.
Descriptor
()
// usagelog.DefaultOutputCost holds the default value on creation for the output_cost field.
usagelog
.
DefaultOutputCost
=
usagelogDescOutputCost
.
Default
.
(
float64
)
// usagelogDescCacheCreationCost is the schema descriptor for cache_creation_cost field.
usagelogDescCacheCreationCost
:=
usagelogFields
[
1
6
]
.
Descriptor
()
usagelogDescCacheCreationCost
:=
usagelogFields
[
1
7
]
.
Descriptor
()
// usagelog.DefaultCacheCreationCost holds the default value on creation for the cache_creation_cost field.
usagelog
.
DefaultCacheCreationCost
=
usagelogDescCacheCreationCost
.
Default
.
(
float64
)
// usagelogDescCacheReadCost is the schema descriptor for cache_read_cost field.
usagelogDescCacheReadCost
:=
usagelogFields
[
1
7
]
.
Descriptor
()
usagelogDescCacheReadCost
:=
usagelogFields
[
1
8
]
.
Descriptor
()
// usagelog.DefaultCacheReadCost holds the default value on creation for the cache_read_cost field.
usagelog
.
DefaultCacheReadCost
=
usagelogDescCacheReadCost
.
Default
.
(
float64
)
// usagelogDescTotalCost is the schema descriptor for total_cost field.
usagelogDescTotalCost
:=
usagelogFields
[
1
8
]
.
Descriptor
()
usagelogDescTotalCost
:=
usagelogFields
[
1
9
]
.
Descriptor
()
// usagelog.DefaultTotalCost holds the default value on creation for the total_cost field.
usagelog
.
DefaultTotalCost
=
usagelogDescTotalCost
.
Default
.
(
float64
)
// usagelogDescActualCost is the schema descriptor for actual_cost field.
usagelogDescActualCost
:=
usagelogFields
[
19
]
.
Descriptor
()
usagelogDescActualCost
:=
usagelogFields
[
20
]
.
Descriptor
()
// usagelog.DefaultActualCost holds the default value on creation for the actual_cost field.
usagelog
.
DefaultActualCost
=
usagelogDescActualCost
.
Default
.
(
float64
)
// usagelogDescRateMultiplier is the schema descriptor for rate_multiplier field.
usagelogDescRateMultiplier
:=
usagelogFields
[
2
0
]
.
Descriptor
()
usagelogDescRateMultiplier
:=
usagelogFields
[
2
1
]
.
Descriptor
()
// usagelog.DefaultRateMultiplier holds the default value on creation for the rate_multiplier field.
usagelog
.
DefaultRateMultiplier
=
usagelogDescRateMultiplier
.
Default
.
(
float64
)
// usagelogDescBillingType is the schema descriptor for billing_type field.
usagelogDescBillingType
:=
usagelogFields
[
2
2
]
.
Descriptor
()
usagelogDescBillingType
:=
usagelogFields
[
2
3
]
.
Descriptor
()
// usagelog.DefaultBillingType holds the default value on creation for the billing_type field.
usagelog
.
DefaultBillingType
=
usagelogDescBillingType
.
Default
.
(
int8
)
// usagelogDescStream is the schema descriptor for stream field.
usagelogDescStream
:=
usagelogFields
[
2
3
]
.
Descriptor
()
usagelogDescStream
:=
usagelogFields
[
2
4
]
.
Descriptor
()
// usagelog.DefaultStream holds the default value on creation for the stream field.
usagelog
.
DefaultStream
=
usagelogDescStream
.
Default
.
(
bool
)
// usagelogDescUserAgent is the schema descriptor for user_agent field.
usagelogDescUserAgent
:=
usagelogFields
[
2
6
]
.
Descriptor
()
usagelogDescUserAgent
:=
usagelogFields
[
2
7
]
.
Descriptor
()
// usagelog.UserAgentValidator is a validator for the "user_agent" field. It is called by the builders before save.
usagelog
.
UserAgentValidator
=
usagelogDescUserAgent
.
Validators
[
0
]
.
(
func
(
string
)
error
)
// usagelogDescIPAddress is the schema descriptor for ip_address field.
usagelogDescIPAddress
:=
usagelogFields
[
2
7
]
.
Descriptor
()
usagelogDescIPAddress
:=
usagelogFields
[
2
8
]
.
Descriptor
()
// usagelog.IPAddressValidator is a validator for the "ip_address" field. It is called by the builders before save.
usagelog
.
IPAddressValidator
=
usagelogDescIPAddress
.
Validators
[
0
]
.
(
func
(
string
)
error
)
// usagelogDescImageCount is the schema descriptor for image_count field.
usagelogDescImageCount
:=
usagelogFields
[
2
8
]
.
Descriptor
()
usagelogDescImageCount
:=
usagelogFields
[
2
9
]
.
Descriptor
()
// usagelog.DefaultImageCount holds the default value on creation for the image_count field.
usagelog
.
DefaultImageCount
=
usagelogDescImageCount
.
Default
.
(
int
)
// usagelogDescImageSize is the schema descriptor for image_size field.
usagelogDescImageSize
:=
usagelogFields
[
29
]
.
Descriptor
()
usagelogDescImageSize
:=
usagelogFields
[
30
]
.
Descriptor
()
// usagelog.ImageSizeValidator is a validator for the "image_size" field. It is called by the builders before save.
usagelog
.
ImageSizeValidator
=
usagelogDescImageSize
.
Validators
[
0
]
.
(
func
(
string
)
error
)
// usagelogDescMediaType is the schema descriptor for media_type field.
usagelogDescMediaType
:=
usagelogFields
[
3
0
]
.
Descriptor
()
usagelogDescMediaType
:=
usagelogFields
[
3
1
]
.
Descriptor
()
// usagelog.MediaTypeValidator is a validator for the "media_type" field. It is called by the builders before save.
usagelog
.
MediaTypeValidator
=
usagelogDescMediaType
.
Validators
[
0
]
.
(
func
(
string
)
error
)
// usagelogDescCacheTTLOverridden is the schema descriptor for cache_ttl_overridden field.
usagelogDescCacheTTLOverridden
:=
usagelogFields
[
3
1
]
.
Descriptor
()
usagelogDescCacheTTLOverridden
:=
usagelogFields
[
3
2
]
.
Descriptor
()
// usagelog.DefaultCacheTTLOverridden holds the default value on creation for the cache_ttl_overridden field.
usagelog
.
DefaultCacheTTLOverridden
=
usagelogDescCacheTTLOverridden
.
Default
.
(
bool
)
// usagelogDescCreatedAt is the schema descriptor for created_at field.
usagelogDescCreatedAt
:=
usagelogFields
[
3
2
]
.
Descriptor
()
usagelogDescCreatedAt
:=
usagelogFields
[
3
3
]
.
Descriptor
()
// usagelog.DefaultCreatedAt holds the default value on creation for the created_at field.
usagelog
.
DefaultCreatedAt
=
usagelogDescCreatedAt
.
Default
.
(
func
()
time
.
Time
)
userMixin
:=
schema
.
User
{}
.
Mixin
()
...
...
backend/ent/schema/usage_log.go
View file @
08c4e514
...
...
@@ -41,6 +41,12 @@ func (UsageLog) Fields() []ent.Field {
field
.
String
(
"model"
)
.
MaxLen
(
100
)
.
NotEmpty
(),
// RequestedModel stores the client-requested model name for stable display and analytics.
// NULL means historical rows written before requested_model dual-write was introduced.
field
.
String
(
"requested_model"
)
.
MaxLen
(
100
)
.
Optional
()
.
Nillable
(),
// UpstreamModel stores the actual upstream model name when model mapping
// is applied. NULL means no mapping — the requested model was used as-is.
field
.
String
(
"upstream_model"
)
.
...
...
@@ -181,6 +187,7 @@ func (UsageLog) Indexes() []ent.Index {
index
.
Fields
(
"subscription_id"
),
index
.
Fields
(
"created_at"
),
index
.
Fields
(
"model"
),
index
.
Fields
(
"requested_model"
),
index
.
Fields
(
"request_id"
),
// 复合索引用于时间范围查询
index
.
Fields
(
"user_id"
,
"created_at"
),
...
...
backend/ent/usagelog.go
View file @
08c4e514
...
...
@@ -32,6 +32,8 @@ type UsageLog struct {
RequestID
string
`json:"request_id,omitempty"`
// Model holds the value of the "model" field.
Model
string
`json:"model,omitempty"`
// RequestedModel holds the value of the "requested_model" field.
RequestedModel
*
string
`json:"requested_model,omitempty"`
// UpstreamModel holds the value of the "upstream_model" field.
UpstreamModel
*
string
`json:"upstream_model,omitempty"`
// GroupID holds the value of the "group_id" field.
...
...
@@ -177,7 +179,7 @@ func (*UsageLog) scanValues(columns []string) ([]any, error) {
values
[
i
]
=
new
(
sql
.
NullFloat64
)
case
usagelog
.
FieldID
,
usagelog
.
FieldUserID
,
usagelog
.
FieldAPIKeyID
,
usagelog
.
FieldAccountID
,
usagelog
.
FieldGroupID
,
usagelog
.
FieldSubscriptionID
,
usagelog
.
FieldInputTokens
,
usagelog
.
FieldOutputTokens
,
usagelog
.
FieldCacheCreationTokens
,
usagelog
.
FieldCacheReadTokens
,
usagelog
.
FieldCacheCreation5mTokens
,
usagelog
.
FieldCacheCreation1hTokens
,
usagelog
.
FieldBillingType
,
usagelog
.
FieldDurationMs
,
usagelog
.
FieldFirstTokenMs
,
usagelog
.
FieldImageCount
:
values
[
i
]
=
new
(
sql
.
NullInt64
)
case
usagelog
.
FieldRequestID
,
usagelog
.
FieldModel
,
usagelog
.
FieldUpstreamModel
,
usagelog
.
FieldUserAgent
,
usagelog
.
FieldIPAddress
,
usagelog
.
FieldImageSize
,
usagelog
.
FieldMediaType
:
case
usagelog
.
FieldRequestID
,
usagelog
.
FieldModel
,
usagelog
.
FieldRequestedModel
,
usagelog
.
FieldUpstreamModel
,
usagelog
.
FieldUserAgent
,
usagelog
.
FieldIPAddress
,
usagelog
.
FieldImageSize
,
usagelog
.
FieldMediaType
:
values
[
i
]
=
new
(
sql
.
NullString
)
case
usagelog
.
FieldCreatedAt
:
values
[
i
]
=
new
(
sql
.
NullTime
)
...
...
@@ -232,6 +234,13 @@ func (_m *UsageLog) assignValues(columns []string, values []any) error {
}
else
if
value
.
Valid
{
_m
.
Model
=
value
.
String
}
case
usagelog
.
FieldRequestedModel
:
if
value
,
ok
:=
values
[
i
]
.
(
*
sql
.
NullString
);
!
ok
{
return
fmt
.
Errorf
(
"unexpected type %T for field requested_model"
,
values
[
i
])
}
else
if
value
.
Valid
{
_m
.
RequestedModel
=
new
(
string
)
*
_m
.
RequestedModel
=
value
.
String
}
case
usagelog
.
FieldUpstreamModel
:
if
value
,
ok
:=
values
[
i
]
.
(
*
sql
.
NullString
);
!
ok
{
return
fmt
.
Errorf
(
"unexpected type %T for field upstream_model"
,
values
[
i
])
...
...
@@ -486,6 +495,11 @@ func (_m *UsageLog) String() string {
builder
.
WriteString
(
"model="
)
builder
.
WriteString
(
_m
.
Model
)
builder
.
WriteString
(
", "
)
if
v
:=
_m
.
RequestedModel
;
v
!=
nil
{
builder
.
WriteString
(
"requested_model="
)
builder
.
WriteString
(
*
v
)
}
builder
.
WriteString
(
", "
)
if
v
:=
_m
.
UpstreamModel
;
v
!=
nil
{
builder
.
WriteString
(
"upstream_model="
)
builder
.
WriteString
(
*
v
)
...
...
backend/ent/usagelog/usagelog.go
View file @
08c4e514
...
...
@@ -24,6 +24,8 @@ const (
FieldRequestID
=
"request_id"
// FieldModel holds the string denoting the model field in the database.
FieldModel
=
"model"
// FieldRequestedModel holds the string denoting the requested_model field in the database.
FieldRequestedModel
=
"requested_model"
// FieldUpstreamModel holds the string denoting the upstream_model field in the database.
FieldUpstreamModel
=
"upstream_model"
// FieldGroupID holds the string denoting the group_id field in the database.
...
...
@@ -137,6 +139,7 @@ var Columns = []string{
FieldAccountID
,
FieldRequestID
,
FieldModel
,
FieldRequestedModel
,
FieldUpstreamModel
,
FieldGroupID
,
FieldSubscriptionID
,
...
...
@@ -182,6 +185,8 @@ var (
RequestIDValidator
func
(
string
)
error
// ModelValidator is a validator for the "model" field. It is called by the builders before save.
ModelValidator
func
(
string
)
error
// RequestedModelValidator is a validator for the "requested_model" field. It is called by the builders before save.
RequestedModelValidator
func
(
string
)
error
// UpstreamModelValidator is a validator for the "upstream_model" field. It is called by the builders before save.
UpstreamModelValidator
func
(
string
)
error
// DefaultInputTokens holds the default value on creation for the "input_tokens" field.
...
...
@@ -263,6 +268,11 @@ func ByModel(opts ...sql.OrderTermOption) OrderOption {
return
sql
.
OrderByField
(
FieldModel
,
opts
...
)
.
ToFunc
()
}
// ByRequestedModel orders the results by the requested_model field.
func
ByRequestedModel
(
opts
...
sql
.
OrderTermOption
)
OrderOption
{
return
sql
.
OrderByField
(
FieldRequestedModel
,
opts
...
)
.
ToFunc
()
}
// ByUpstreamModel orders the results by the upstream_model field.
func
ByUpstreamModel
(
opts
...
sql
.
OrderTermOption
)
OrderOption
{
return
sql
.
OrderByField
(
FieldUpstreamModel
,
opts
...
)
.
ToFunc
()
...
...
backend/ent/usagelog/where.go
View file @
08c4e514
...
...
@@ -80,6 +80,11 @@ func Model(v string) predicate.UsageLog {
return
predicate
.
UsageLog
(
sql
.
FieldEQ
(
FieldModel
,
v
))
}
// RequestedModel applies equality check predicate on the "requested_model" field. It's identical to RequestedModelEQ.
func
RequestedModel
(
v
string
)
predicate
.
UsageLog
{
return
predicate
.
UsageLog
(
sql
.
FieldEQ
(
FieldRequestedModel
,
v
))
}
// UpstreamModel applies equality check predicate on the "upstream_model" field. It's identical to UpstreamModelEQ.
func
UpstreamModel
(
v
string
)
predicate
.
UsageLog
{
return
predicate
.
UsageLog
(
sql
.
FieldEQ
(
FieldUpstreamModel
,
v
))
...
...
@@ -410,6 +415,81 @@ func ModelContainsFold(v string) predicate.UsageLog {
return
predicate
.
UsageLog
(
sql
.
FieldContainsFold
(
FieldModel
,
v
))
}
// RequestedModelEQ applies the EQ predicate on the "requested_model" field.
func
RequestedModelEQ
(
v
string
)
predicate
.
UsageLog
{
return
predicate
.
UsageLog
(
sql
.
FieldEQ
(
FieldRequestedModel
,
v
))
}
// RequestedModelNEQ applies the NEQ predicate on the "requested_model" field.
func
RequestedModelNEQ
(
v
string
)
predicate
.
UsageLog
{
return
predicate
.
UsageLog
(
sql
.
FieldNEQ
(
FieldRequestedModel
,
v
))
}
// RequestedModelIn applies the In predicate on the "requested_model" field.
func
RequestedModelIn
(
vs
...
string
)
predicate
.
UsageLog
{
return
predicate
.
UsageLog
(
sql
.
FieldIn
(
FieldRequestedModel
,
vs
...
))
}
// RequestedModelNotIn applies the NotIn predicate on the "requested_model" field.
func
RequestedModelNotIn
(
vs
...
string
)
predicate
.
UsageLog
{
return
predicate
.
UsageLog
(
sql
.
FieldNotIn
(
FieldRequestedModel
,
vs
...
))
}
// RequestedModelGT applies the GT predicate on the "requested_model" field.
func
RequestedModelGT
(
v
string
)
predicate
.
UsageLog
{
return
predicate
.
UsageLog
(
sql
.
FieldGT
(
FieldRequestedModel
,
v
))
}
// RequestedModelGTE applies the GTE predicate on the "requested_model" field.
func
RequestedModelGTE
(
v
string
)
predicate
.
UsageLog
{
return
predicate
.
UsageLog
(
sql
.
FieldGTE
(
FieldRequestedModel
,
v
))
}
// RequestedModelLT applies the LT predicate on the "requested_model" field.
func
RequestedModelLT
(
v
string
)
predicate
.
UsageLog
{
return
predicate
.
UsageLog
(
sql
.
FieldLT
(
FieldRequestedModel
,
v
))
}
// RequestedModelLTE applies the LTE predicate on the "requested_model" field.
func
RequestedModelLTE
(
v
string
)
predicate
.
UsageLog
{
return
predicate
.
UsageLog
(
sql
.
FieldLTE
(
FieldRequestedModel
,
v
))
}
// RequestedModelContains applies the Contains predicate on the "requested_model" field.
func
RequestedModelContains
(
v
string
)
predicate
.
UsageLog
{
return
predicate
.
UsageLog
(
sql
.
FieldContains
(
FieldRequestedModel
,
v
))
}
// RequestedModelHasPrefix applies the HasPrefix predicate on the "requested_model" field.
func
RequestedModelHasPrefix
(
v
string
)
predicate
.
UsageLog
{
return
predicate
.
UsageLog
(
sql
.
FieldHasPrefix
(
FieldRequestedModel
,
v
))
}
// RequestedModelHasSuffix applies the HasSuffix predicate on the "requested_model" field.
func
RequestedModelHasSuffix
(
v
string
)
predicate
.
UsageLog
{
return
predicate
.
UsageLog
(
sql
.
FieldHasSuffix
(
FieldRequestedModel
,
v
))
}
// RequestedModelIsNil applies the IsNil predicate on the "requested_model" field.
func
RequestedModelIsNil
()
predicate
.
UsageLog
{
return
predicate
.
UsageLog
(
sql
.
FieldIsNull
(
FieldRequestedModel
))
}
// RequestedModelNotNil applies the NotNil predicate on the "requested_model" field.
func
RequestedModelNotNil
()
predicate
.
UsageLog
{
return
predicate
.
UsageLog
(
sql
.
FieldNotNull
(
FieldRequestedModel
))
}
// RequestedModelEqualFold applies the EqualFold predicate on the "requested_model" field.
func
RequestedModelEqualFold
(
v
string
)
predicate
.
UsageLog
{
return
predicate
.
UsageLog
(
sql
.
FieldEqualFold
(
FieldRequestedModel
,
v
))
}
// RequestedModelContainsFold applies the ContainsFold predicate on the "requested_model" field.
func
RequestedModelContainsFold
(
v
string
)
predicate
.
UsageLog
{
return
predicate
.
UsageLog
(
sql
.
FieldContainsFold
(
FieldRequestedModel
,
v
))
}
// UpstreamModelEQ applies the EQ predicate on the "upstream_model" field.
func
UpstreamModelEQ
(
v
string
)
predicate
.
UsageLog
{
return
predicate
.
UsageLog
(
sql
.
FieldEQ
(
FieldUpstreamModel
,
v
))
...
...
backend/ent/usagelog_create.go
View file @
08c4e514
...
...
@@ -57,6 +57,20 @@ func (_c *UsageLogCreate) SetModel(v string) *UsageLogCreate {
return
_c
}
// SetRequestedModel sets the "requested_model" field.
func
(
_c
*
UsageLogCreate
)
SetRequestedModel
(
v
string
)
*
UsageLogCreate
{
_c
.
mutation
.
SetRequestedModel
(
v
)
return
_c
}
// SetNillableRequestedModel sets the "requested_model" field if the given value is not nil.
func
(
_c
*
UsageLogCreate
)
SetNillableRequestedModel
(
v
*
string
)
*
UsageLogCreate
{
if
v
!=
nil
{
_c
.
SetRequestedModel
(
*
v
)
}
return
_c
}
// SetUpstreamModel sets the "upstream_model" field.
func
(
_c
*
UsageLogCreate
)
SetUpstreamModel
(
v
string
)
*
UsageLogCreate
{
_c
.
mutation
.
SetUpstreamModel
(
v
)
...
...
@@ -610,6 +624,11 @@ func (_c *UsageLogCreate) check() error {
return
&
ValidationError
{
Name
:
"model"
,
err
:
fmt
.
Errorf
(
`ent: validator failed for field "UsageLog.model": %w`
,
err
)}
}
}
if
v
,
ok
:=
_c
.
mutation
.
RequestedModel
();
ok
{
if
err
:=
usagelog
.
RequestedModelValidator
(
v
);
err
!=
nil
{
return
&
ValidationError
{
Name
:
"requested_model"
,
err
:
fmt
.
Errorf
(
`ent: validator failed for field "UsageLog.requested_model": %w`
,
err
)}
}
}
if
v
,
ok
:=
_c
.
mutation
.
UpstreamModel
();
ok
{
if
err
:=
usagelog
.
UpstreamModelValidator
(
v
);
err
!=
nil
{
return
&
ValidationError
{
Name
:
"upstream_model"
,
err
:
fmt
.
Errorf
(
`ent: validator failed for field "UsageLog.upstream_model": %w`
,
err
)}
...
...
@@ -733,6 +752,10 @@ func (_c *UsageLogCreate) createSpec() (*UsageLog, *sqlgraph.CreateSpec) {
_spec
.
SetField
(
usagelog
.
FieldModel
,
field
.
TypeString
,
value
)
_node
.
Model
=
value
}
if
value
,
ok
:=
_c
.
mutation
.
RequestedModel
();
ok
{
_spec
.
SetField
(
usagelog
.
FieldRequestedModel
,
field
.
TypeString
,
value
)
_node
.
RequestedModel
=
&
value
}
if
value
,
ok
:=
_c
.
mutation
.
UpstreamModel
();
ok
{
_spec
.
SetField
(
usagelog
.
FieldUpstreamModel
,
field
.
TypeString
,
value
)
_node
.
UpstreamModel
=
&
value
...
...
@@ -1034,6 +1057,24 @@ func (u *UsageLogUpsert) UpdateModel() *UsageLogUpsert {
return
u
}
// SetRequestedModel sets the "requested_model" field.
func
(
u
*
UsageLogUpsert
)
SetRequestedModel
(
v
string
)
*
UsageLogUpsert
{
u
.
Set
(
usagelog
.
FieldRequestedModel
,
v
)
return
u
}
// UpdateRequestedModel sets the "requested_model" field to the value that was provided on create.
func
(
u
*
UsageLogUpsert
)
UpdateRequestedModel
()
*
UsageLogUpsert
{
u
.
SetExcluded
(
usagelog
.
FieldRequestedModel
)
return
u
}
// ClearRequestedModel clears the value of the "requested_model" field.
func
(
u
*
UsageLogUpsert
)
ClearRequestedModel
()
*
UsageLogUpsert
{
u
.
SetNull
(
usagelog
.
FieldRequestedModel
)
return
u
}
// SetUpstreamModel sets the "upstream_model" field.
func
(
u
*
UsageLogUpsert
)
SetUpstreamModel
(
v
string
)
*
UsageLogUpsert
{
u
.
Set
(
usagelog
.
FieldUpstreamModel
,
v
)
...
...
@@ -1641,6 +1682,27 @@ func (u *UsageLogUpsertOne) UpdateModel() *UsageLogUpsertOne {
})
}
// SetRequestedModel sets the "requested_model" field.
func
(
u
*
UsageLogUpsertOne
)
SetRequestedModel
(
v
string
)
*
UsageLogUpsertOne
{
return
u
.
Update
(
func
(
s
*
UsageLogUpsert
)
{
s
.
SetRequestedModel
(
v
)
})
}
// UpdateRequestedModel sets the "requested_model" field to the value that was provided on create.
func
(
u
*
UsageLogUpsertOne
)
UpdateRequestedModel
()
*
UsageLogUpsertOne
{
return
u
.
Update
(
func
(
s
*
UsageLogUpsert
)
{
s
.
UpdateRequestedModel
()
})
}
// ClearRequestedModel clears the value of the "requested_model" field.
func
(
u
*
UsageLogUpsertOne
)
ClearRequestedModel
()
*
UsageLogUpsertOne
{
return
u
.
Update
(
func
(
s
*
UsageLogUpsert
)
{
s
.
ClearRequestedModel
()
})
}
// SetUpstreamModel sets the "upstream_model" field.
func
(
u
*
UsageLogUpsertOne
)
SetUpstreamModel
(
v
string
)
*
UsageLogUpsertOne
{
return
u
.
Update
(
func
(
s
*
UsageLogUpsert
)
{
...
...
@@ -2496,6 +2558,27 @@ func (u *UsageLogUpsertBulk) UpdateModel() *UsageLogUpsertBulk {
})
}
// SetRequestedModel sets the "requested_model" field.
func
(
u
*
UsageLogUpsertBulk
)
SetRequestedModel
(
v
string
)
*
UsageLogUpsertBulk
{
return
u
.
Update
(
func
(
s
*
UsageLogUpsert
)
{
s
.
SetRequestedModel
(
v
)
})
}
// UpdateRequestedModel sets the "requested_model" field to the value that was provided on create.
func
(
u
*
UsageLogUpsertBulk
)
UpdateRequestedModel
()
*
UsageLogUpsertBulk
{
return
u
.
Update
(
func
(
s
*
UsageLogUpsert
)
{
s
.
UpdateRequestedModel
()
})
}
// ClearRequestedModel clears the value of the "requested_model" field.
func
(
u
*
UsageLogUpsertBulk
)
ClearRequestedModel
()
*
UsageLogUpsertBulk
{
return
u
.
Update
(
func
(
s
*
UsageLogUpsert
)
{
s
.
ClearRequestedModel
()
})
}
// SetUpstreamModel sets the "upstream_model" field.
func
(
u
*
UsageLogUpsertBulk
)
SetUpstreamModel
(
v
string
)
*
UsageLogUpsertBulk
{
return
u
.
Update
(
func
(
s
*
UsageLogUpsert
)
{
...
...
backend/ent/usagelog_update.go
View file @
08c4e514
...
...
@@ -102,6 +102,26 @@ func (_u *UsageLogUpdate) SetNillableModel(v *string) *UsageLogUpdate {
return
_u
}
// SetRequestedModel sets the "requested_model" field.
func
(
_u
*
UsageLogUpdate
)
SetRequestedModel
(
v
string
)
*
UsageLogUpdate
{
_u
.
mutation
.
SetRequestedModel
(
v
)
return
_u
}
// SetNillableRequestedModel sets the "requested_model" field if the given value is not nil.
func
(
_u
*
UsageLogUpdate
)
SetNillableRequestedModel
(
v
*
string
)
*
UsageLogUpdate
{
if
v
!=
nil
{
_u
.
SetRequestedModel
(
*
v
)
}
return
_u
}
// ClearRequestedModel clears the value of the "requested_model" field.
func
(
_u
*
UsageLogUpdate
)
ClearRequestedModel
()
*
UsageLogUpdate
{
_u
.
mutation
.
ClearRequestedModel
()
return
_u
}
// SetUpstreamModel sets the "upstream_model" field.
func
(
_u
*
UsageLogUpdate
)
SetUpstreamModel
(
v
string
)
*
UsageLogUpdate
{
_u
.
mutation
.
SetUpstreamModel
(
v
)
...
...
@@ -765,6 +785,11 @@ func (_u *UsageLogUpdate) check() error {
return
&
ValidationError
{
Name
:
"model"
,
err
:
fmt
.
Errorf
(
`ent: validator failed for field "UsageLog.model": %w`
,
err
)}
}
}
if
v
,
ok
:=
_u
.
mutation
.
RequestedModel
();
ok
{
if
err
:=
usagelog
.
RequestedModelValidator
(
v
);
err
!=
nil
{
return
&
ValidationError
{
Name
:
"requested_model"
,
err
:
fmt
.
Errorf
(
`ent: validator failed for field "UsageLog.requested_model": %w`
,
err
)}
}
}
if
v
,
ok
:=
_u
.
mutation
.
UpstreamModel
();
ok
{
if
err
:=
usagelog
.
UpstreamModelValidator
(
v
);
err
!=
nil
{
return
&
ValidationError
{
Name
:
"upstream_model"
,
err
:
fmt
.
Errorf
(
`ent: validator failed for field "UsageLog.upstream_model": %w`
,
err
)}
...
...
@@ -820,6 +845,12 @@ func (_u *UsageLogUpdate) sqlSave(ctx context.Context) (_node int, err error) {
if
value
,
ok
:=
_u
.
mutation
.
Model
();
ok
{
_spec
.
SetField
(
usagelog
.
FieldModel
,
field
.
TypeString
,
value
)
}
if
value
,
ok
:=
_u
.
mutation
.
RequestedModel
();
ok
{
_spec
.
SetField
(
usagelog
.
FieldRequestedModel
,
field
.
TypeString
,
value
)
}
if
_u
.
mutation
.
RequestedModelCleared
()
{
_spec
.
ClearField
(
usagelog
.
FieldRequestedModel
,
field
.
TypeString
)
}
if
value
,
ok
:=
_u
.
mutation
.
UpstreamModel
();
ok
{
_spec
.
SetField
(
usagelog
.
FieldUpstreamModel
,
field
.
TypeString
,
value
)
}
...
...
@@ -1208,6 +1239,26 @@ func (_u *UsageLogUpdateOne) SetNillableModel(v *string) *UsageLogUpdateOne {
return
_u
}
// SetRequestedModel sets the "requested_model" field.
func
(
_u
*
UsageLogUpdateOne
)
SetRequestedModel
(
v
string
)
*
UsageLogUpdateOne
{
_u
.
mutation
.
SetRequestedModel
(
v
)
return
_u
}
// SetNillableRequestedModel sets the "requested_model" field if the given value is not nil.
func
(
_u
*
UsageLogUpdateOne
)
SetNillableRequestedModel
(
v
*
string
)
*
UsageLogUpdateOne
{
if
v
!=
nil
{
_u
.
SetRequestedModel
(
*
v
)
}
return
_u
}
// ClearRequestedModel clears the value of the "requested_model" field.
func
(
_u
*
UsageLogUpdateOne
)
ClearRequestedModel
()
*
UsageLogUpdateOne
{
_u
.
mutation
.
ClearRequestedModel
()
return
_u
}
// SetUpstreamModel sets the "upstream_model" field.
func
(
_u
*
UsageLogUpdateOne
)
SetUpstreamModel
(
v
string
)
*
UsageLogUpdateOne
{
_u
.
mutation
.
SetUpstreamModel
(
v
)
...
...
@@ -1884,6 +1935,11 @@ func (_u *UsageLogUpdateOne) check() error {
return
&
ValidationError
{
Name
:
"model"
,
err
:
fmt
.
Errorf
(
`ent: validator failed for field "UsageLog.model": %w`
,
err
)}
}
}
if
v
,
ok
:=
_u
.
mutation
.
RequestedModel
();
ok
{
if
err
:=
usagelog
.
RequestedModelValidator
(
v
);
err
!=
nil
{
return
&
ValidationError
{
Name
:
"requested_model"
,
err
:
fmt
.
Errorf
(
`ent: validator failed for field "UsageLog.requested_model": %w`
,
err
)}
}
}
if
v
,
ok
:=
_u
.
mutation
.
UpstreamModel
();
ok
{
if
err
:=
usagelog
.
UpstreamModelValidator
(
v
);
err
!=
nil
{
return
&
ValidationError
{
Name
:
"upstream_model"
,
err
:
fmt
.
Errorf
(
`ent: validator failed for field "UsageLog.upstream_model": %w`
,
err
)}
...
...
@@ -1956,6 +2012,12 @@ func (_u *UsageLogUpdateOne) sqlSave(ctx context.Context) (_node *UsageLog, err
if
value
,
ok
:=
_u
.
mutation
.
Model
();
ok
{
_spec
.
SetField
(
usagelog
.
FieldModel
,
field
.
TypeString
,
value
)
}
if
value
,
ok
:=
_u
.
mutation
.
RequestedModel
();
ok
{
_spec
.
SetField
(
usagelog
.
FieldRequestedModel
,
field
.
TypeString
,
value
)
}
if
_u
.
mutation
.
RequestedModelCleared
()
{
_spec
.
ClearField
(
usagelog
.
FieldRequestedModel
,
field
.
TypeString
)
}
if
value
,
ok
:=
_u
.
mutation
.
UpstreamModel
();
ok
{
_spec
.
SetField
(
usagelog
.
FieldUpstreamModel
,
field
.
TypeString
,
value
)
}
...
...
backend/go.sum
View file @
08c4e514
...
...
@@ -94,6 +94,10 @@ github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XL
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams=
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
github.com/clipperhouse/stringish v0.1.1 h1:+NSqMOr3GR6k1FdRhhnXrLfztGzuG+VuFDfatpWHKCs=
github.com/clipperhouse/stringish v0.1.1/go.mod h1:v/WhFtE1q0ovMta2+m+UbpZ+2/HEXNWYXQgCt4hdOzA=
github.com/clipperhouse/uax29/v2 v2.5.0 h1:x7T0T4eTHDONxFJsL94uKNKPHrclyFI0lm7+w94cO8U=
github.com/clipperhouse/uax29/v2 v2.5.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsVRgg6W7ihQeh4g=
github.com/coder/websocket v1.8.14 h1:9L0p0iKiNOibykf283eHkKUHHrpG7f65OE3BhhO7v9g=
github.com/coder/websocket v1.8.14/go.mod h1:NX3SzP+inril6yawo5CQXx8+fk145lPDC6pumgx0mVg=
github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI=
...
...
@@ -195,6 +199,8 @@ github.com/icholy/digest v1.1.0 h1:HfGg9Irj7i+IX1o1QAmPfIBNu/Q5A5Tu3n/MED9k9H4=
github.com/icholy/digest v1.1.0/go.mod h1:QNrsSGQ5v7v9cReDI0+eyjsXGUoRSUZQHeQ5C4XLa0Y=
github.com/imroc/req/v3 v3.57.0 h1:LMTUjNRUybUkTPn8oJDq8Kg3JRBOBTcnDhKu7mzupKI=
github.com/imroc/req/v3 v3.57.0/go.mod h1:JL62ey1nvSLq81HORNcosvlf7SxZStONNqOprg0Pz00=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
...
...
@@ -230,6 +236,8 @@ github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovk
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw=
github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM=
github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/mdelapenya/tlscert v0.2.0 h1:7H81W6Z/4weDvZBNOfQte5GpIMo0lGYEeWbkGp5LJHI=
...
...
@@ -263,6 +271,8 @@ github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w=
github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=
...
...
@@ -314,6 +324,8 @@ github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0=
github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I=
github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ=
...
...
backend/internal/handler/admin/setting_handler.go
View file @
08c4e514
...
...
@@ -110,6 +110,7 @@ func (h *SettingHandler) GetSettings(c *gin.Context) {
PurchaseSubscriptionURL
:
settings
.
PurchaseSubscriptionURL
,
SoraClientEnabled
:
settings
.
SoraClientEnabled
,
CustomMenuItems
:
dto
.
ParseCustomMenuItems
(
settings
.
CustomMenuItems
),
CustomEndpoints
:
dto
.
ParseCustomEndpoints
(
settings
.
CustomEndpoints
),
DefaultConcurrency
:
settings
.
DefaultConcurrency
,
DefaultBalance
:
settings
.
DefaultBalance
,
DefaultSubscriptions
:
defaultSubscriptions
,
...
...
@@ -176,6 +177,7 @@ type UpdateSettingsRequest struct {
PurchaseSubscriptionURL
*
string
`json:"purchase_subscription_url"`
SoraClientEnabled
bool
`json:"sora_client_enabled"`
CustomMenuItems
*
[]
dto
.
CustomMenuItem
`json:"custom_menu_items"`
CustomEndpoints
*
[]
dto
.
CustomEndpoint
`json:"custom_endpoints"`
// 默认配置
DefaultConcurrency
int
`json:"default_concurrency"`
...
...
@@ -417,6 +419,55 @@ func (h *SettingHandler) UpdateSettings(c *gin.Context) {
customMenuJSON
=
string
(
menuBytes
)
}
// 自定义端点验证
const
(
maxCustomEndpoints
=
10
maxEndpointNameLen
=
50
maxEndpointURLLen
=
2048
maxEndpointDescriptionLen
=
200
)
customEndpointsJSON
:=
previousSettings
.
CustomEndpoints
if
req
.
CustomEndpoints
!=
nil
{
endpoints
:=
*
req
.
CustomEndpoints
if
len
(
endpoints
)
>
maxCustomEndpoints
{
response
.
BadRequest
(
c
,
"Too many custom endpoints (max 10)"
)
return
}
for
_
,
ep
:=
range
endpoints
{
if
strings
.
TrimSpace
(
ep
.
Name
)
==
""
{
response
.
BadRequest
(
c
,
"Custom endpoint name is required"
)
return
}
if
len
(
ep
.
Name
)
>
maxEndpointNameLen
{
response
.
BadRequest
(
c
,
"Custom endpoint name is too long (max 50 characters)"
)
return
}
if
strings
.
TrimSpace
(
ep
.
Endpoint
)
==
""
{
response
.
BadRequest
(
c
,
"Custom endpoint URL is required"
)
return
}
if
len
(
ep
.
Endpoint
)
>
maxEndpointURLLen
{
response
.
BadRequest
(
c
,
"Custom endpoint URL is too long (max 2048 characters)"
)
return
}
if
err
:=
config
.
ValidateAbsoluteHTTPURL
(
strings
.
TrimSpace
(
ep
.
Endpoint
));
err
!=
nil
{
response
.
BadRequest
(
c
,
"Custom endpoint URL must be an absolute http(s) URL"
)
return
}
if
len
(
ep
.
Description
)
>
maxEndpointDescriptionLen
{
response
.
BadRequest
(
c
,
"Custom endpoint description is too long (max 200 characters)"
)
return
}
}
endpointBytes
,
err
:=
json
.
Marshal
(
endpoints
)
if
err
!=
nil
{
response
.
BadRequest
(
c
,
"Failed to serialize custom endpoints"
)
return
}
customEndpointsJSON
=
string
(
endpointBytes
)
}
// Ops metrics collector interval validation (seconds).
if
req
.
OpsMetricsIntervalSeconds
!=
nil
{
v
:=
*
req
.
OpsMetricsIntervalSeconds
...
...
@@ -495,6 +546,7 @@ func (h *SettingHandler) UpdateSettings(c *gin.Context) {
PurchaseSubscriptionURL
:
purchaseURL
,
SoraClientEnabled
:
req
.
SoraClientEnabled
,
CustomMenuItems
:
customMenuJSON
,
CustomEndpoints
:
customEndpointsJSON
,
DefaultConcurrency
:
req
.
DefaultConcurrency
,
DefaultBalance
:
req
.
DefaultBalance
,
DefaultSubscriptions
:
defaultSubscriptions
,
...
...
@@ -592,6 +644,7 @@ func (h *SettingHandler) UpdateSettings(c *gin.Context) {
PurchaseSubscriptionURL
:
updatedSettings
.
PurchaseSubscriptionURL
,
SoraClientEnabled
:
updatedSettings
.
SoraClientEnabled
,
CustomMenuItems
:
dto
.
ParseCustomMenuItems
(
updatedSettings
.
CustomMenuItems
),
CustomEndpoints
:
dto
.
ParseCustomEndpoints
(
updatedSettings
.
CustomEndpoints
),
DefaultConcurrency
:
updatedSettings
.
DefaultConcurrency
,
DefaultBalance
:
updatedSettings
.
DefaultBalance
,
DefaultSubscriptions
:
updatedDefaultSubscriptions
,
...
...
backend/internal/handler/dto/mappers.go
View file @
08c4e514
...
...
@@ -276,11 +276,17 @@ func AccountFromServiceShallow(a *service.Account) *Account {
if
limit
:=
a
.
GetQuotaDailyLimit
();
limit
>
0
{
out
.
QuotaDailyLimit
=
&
limit
used
:=
a
.
GetQuotaDailyUsed
()
if
a
.
IsDailyQuotaPeriodExpired
()
{
used
=
0
}
out
.
QuotaDailyUsed
=
&
used
}
if
limit
:=
a
.
GetQuotaWeeklyLimit
();
limit
>
0
{
out
.
QuotaWeeklyLimit
=
&
limit
used
:=
a
.
GetQuotaWeeklyUsed
()
if
a
.
IsWeeklyQuotaPeriodExpired
()
{
used
=
0
}
out
.
QuotaWeeklyUsed
=
&
used
}
// 固定时间重置配置
...
...
@@ -516,14 +522,17 @@ func usageLogFromServiceUser(l *service.UsageLog) UsageLog {
// 普通用户 DTO:严禁包含管理员字段(例如 account_rate_multiplier、ip_address、account)。
requestType
:=
l
.
EffectiveRequestType
()
stream
,
openAIWSMode
:=
service
.
ApplyLegacyRequestFields
(
requestType
,
l
.
Stream
,
l
.
OpenAIWSMode
)
requestedModel
:=
l
.
RequestedModel
if
requestedModel
==
""
{
requestedModel
=
l
.
Model
}
return
UsageLog
{
ID
:
l
.
ID
,
UserID
:
l
.
UserID
,
APIKeyID
:
l
.
APIKeyID
,
AccountID
:
l
.
AccountID
,
RequestID
:
l
.
RequestID
,
Model
:
l
.
Model
,
UpstreamModel
:
l
.
UpstreamModel
,
Model
:
requestedModel
,
ServiceTier
:
l
.
ServiceTier
,
ReasoningEffort
:
l
.
ReasoningEffort
,
InboundEndpoint
:
l
.
InboundEndpoint
,
...
...
@@ -580,6 +589,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
),
...
...
backend/internal/handler/dto/mappers_usage_test.go
View file @
08c4e514
package
dto
import
(
"encoding/json"
"testing"
"github.com/Wei-Shaw/sub2api/internal/service"
...
...
@@ -106,6 +107,47 @@ 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
TestUsageLogFromService_FallsBackToLegacyModelWhenRequestedModelMissing
(
t
*
testing
.
T
)
{
t
.
Parallel
()
log
:=
&
service
.
UsageLog
{
RequestID
:
"req_legacy"
,
Model
:
"claude-3"
,
}
userDTO
:=
UsageLogFromService
(
log
)
adminDTO
:=
UsageLogFromServiceAdmin
(
log
)
require
.
Equal
(
t
,
"claude-3"
,
userDTO
.
Model
)
require
.
Equal
(
t
,
"claude-3"
,
adminDTO
.
Model
)
}
func
f64Ptr
(
value
float64
)
*
float64
{
return
&
value
}
backend/internal/handler/dto/settings.go
View file @
08c4e514
...
...
@@ -15,6 +15,13 @@ type CustomMenuItem struct {
SortOrder
int
`json:"sort_order"`
}
// CustomEndpoint represents an admin-configured API endpoint for quick copy.
type
CustomEndpoint
struct
{
Name
string
`json:"name"`
Endpoint
string
`json:"endpoint"`
Description
string
`json:"description"`
}
// SystemSettings represents the admin settings API response payload.
type
SystemSettings
struct
{
RegistrationEnabled
bool
`json:"registration_enabled"`
...
...
@@ -56,6 +63,7 @@ type SystemSettings struct {
PurchaseSubscriptionURL
string
`json:"purchase_subscription_url"`
SoraClientEnabled
bool
`json:"sora_client_enabled"`
CustomMenuItems
[]
CustomMenuItem
`json:"custom_menu_items"`
CustomEndpoints
[]
CustomEndpoint
`json:"custom_endpoints"`
DefaultConcurrency
int
`json:"default_concurrency"`
DefaultBalance
float64
`json:"default_balance"`
...
...
@@ -114,6 +122,7 @@ type PublicSettings struct {
PurchaseSubscriptionEnabled
bool
`json:"purchase_subscription_enabled"`
PurchaseSubscriptionURL
string
`json:"purchase_subscription_url"`
CustomMenuItems
[]
CustomMenuItem
`json:"custom_menu_items"`
CustomEndpoints
[]
CustomEndpoint
`json:"custom_endpoints"`
LinuxDoOAuthEnabled
bool
`json:"linuxdo_oauth_enabled"`
SoraClientEnabled
bool
`json:"sora_client_enabled"`
BackendModeEnabled
bool
`json:"backend_mode_enabled"`
...
...
@@ -218,3 +227,17 @@ func ParseUserVisibleMenuItems(raw string) []CustomMenuItem {
}
return
filtered
}
// ParseCustomEndpoints parses a JSON string into a slice of CustomEndpoint.
// Returns empty slice on empty/invalid input.
func
ParseCustomEndpoints
(
raw
string
)
[]
CustomEndpoint
{
raw
=
strings
.
TrimSpace
(
raw
)
if
raw
==
""
||
raw
==
"[]"
{
return
[]
CustomEndpoint
{}
}
var
items
[]
CustomEndpoint
if
err
:=
json
.
Unmarshal
([]
byte
(
raw
),
&
items
);
err
!=
nil
{
return
[]
CustomEndpoint
{}
}
return
items
}
backend/internal/handler/dto/types.go
View file @
08c4e514
...
...
@@ -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"`
...
...
backend/internal/handler/openai_chat_completions.go
View file @
08c4e514
...
...
@@ -181,7 +181,7 @@ func (h *OpenAIGatewayHandler) ChatCompletions(c *gin.Context) {
service
.
SetOpsLatencyMs
(
c
,
service
.
OpsRoutingLatencyMsKey
,
time
.
Since
(
routingStart
)
.
Milliseconds
())
forwardStart
:=
time
.
Now
()
defaultMappedModel
:=
c
.
GetString
(
"openai_chat_completions_fallback_model"
)
defaultMappedModel
:=
resolveOpenAIForwardDefaultMappedModel
(
apiKey
,
c
.
GetString
(
"openai_chat_completions_fallback_model"
)
)
result
,
err
:=
h
.
gatewayService
.
ForwardAsChatCompletions
(
c
.
Request
.
Context
(),
c
,
account
,
body
,
promptCacheKey
,
defaultMappedModel
)
forwardDurationMs
:=
time
.
Since
(
forwardStart
)
.
Milliseconds
()
...
...
backend/internal/handler/openai_gateway_handler.go
View file @
08c4e514
...
...
@@ -37,6 +37,16 @@ type OpenAIGatewayHandler struct {
cfg
*
config
.
Config
}
func
resolveOpenAIForwardDefaultMappedModel
(
apiKey
*
service
.
APIKey
,
fallbackModel
string
)
string
{
if
fallbackModel
=
strings
.
TrimSpace
(
fallbackModel
);
fallbackModel
!=
""
{
return
fallbackModel
}
if
apiKey
==
nil
||
apiKey
.
Group
==
nil
{
return
""
}
return
strings
.
TrimSpace
(
apiKey
.
Group
.
DefaultMappedModel
)
}
// NewOpenAIGatewayHandler creates a new OpenAIGatewayHandler
func
NewOpenAIGatewayHandler
(
gatewayService
*
service
.
OpenAIGatewayService
,
...
...
@@ -657,9 +667,9 @@ func (h *OpenAIGatewayHandler) Messages(c *gin.Context) {
service
.
SetOpsLatencyMs
(
c
,
service
.
OpsRoutingLatencyMsKey
,
time
.
Since
(
routingStart
)
.
Milliseconds
())
forwardStart
:=
time
.
Now
()
//
仅在调度时实际触发了降级(原模型无可用账号、改用默认模型重试成功)时,
//
才将降级模型传给 Forward 层做模型替换;否则保持用户请求的原始模型
。
defaultMappedModel
:=
c
.
GetString
(
"openai_messages_fallback_model"
)
//
Forward 层需要始终拿到 group 默认映射模型,这样未命中账号级映射的
//
Claude 兼容模型才不会在后续 Codex 规范化中意外退化到 gpt-5.1
。
defaultMappedModel
:=
resolveOpenAIForwardDefaultMappedModel
(
apiKey
,
c
.
GetString
(
"openai_messages_fallback_model"
)
)
result
,
err
:=
h
.
gatewayService
.
ForwardAsAnthropic
(
c
.
Request
.
Context
(),
c
,
account
,
body
,
promptCacheKey
,
defaultMappedModel
)
forwardDurationMs
:=
time
.
Since
(
forwardStart
)
.
Milliseconds
()
...
...
backend/internal/handler/openai_gateway_handler_test.go
View file @
08c4e514
...
...
@@ -352,6 +352,30 @@ func TestOpenAIEnsureResponsesDependencies(t *testing.T) {
})
}
func
TestResolveOpenAIForwardDefaultMappedModel
(
t
*
testing
.
T
)
{
t
.
Run
(
"prefers_explicit_fallback_model"
,
func
(
t
*
testing
.
T
)
{
apiKey
:=
&
service
.
APIKey
{
Group
:
&
service
.
Group
{
DefaultMappedModel
:
"gpt-5.4"
},
}
require
.
Equal
(
t
,
"gpt-5.2"
,
resolveOpenAIForwardDefaultMappedModel
(
apiKey
,
" gpt-5.2 "
))
})
t
.
Run
(
"uses_group_default_on_normal_path"
,
func
(
t
*
testing
.
T
)
{
apiKey
:=
&
service
.
APIKey
{
Group
:
&
service
.
Group
{
DefaultMappedModel
:
"gpt-5.4"
},
}
require
.
Equal
(
t
,
"gpt-5.4"
,
resolveOpenAIForwardDefaultMappedModel
(
apiKey
,
""
))
})
t
.
Run
(
"returns_empty_without_group_default"
,
func
(
t
*
testing
.
T
)
{
require
.
Empty
(
t
,
resolveOpenAIForwardDefaultMappedModel
(
nil
,
""
))
require
.
Empty
(
t
,
resolveOpenAIForwardDefaultMappedModel
(
&
service
.
APIKey
{},
""
))
require
.
Empty
(
t
,
resolveOpenAIForwardDefaultMappedModel
(
&
service
.
APIKey
{
Group
:
&
service
.
Group
{},
},
""
))
})
}
func
TestOpenAIResponses_MissingDependencies_ReturnsServiceUnavailable
(
t
*
testing
.
T
)
{
gin
.
SetMode
(
gin
.
TestMode
)
...
...
backend/internal/handler/setting_handler.go
View file @
08c4e514
...
...
@@ -52,6 +52,7 @@ func (h *SettingHandler) GetPublicSettings(c *gin.Context) {
PurchaseSubscriptionEnabled
:
settings
.
PurchaseSubscriptionEnabled
,
PurchaseSubscriptionURL
:
settings
.
PurchaseSubscriptionURL
,
CustomMenuItems
:
dto
.
ParseUserVisibleMenuItems
(
settings
.
CustomMenuItems
),
CustomEndpoints
:
dto
.
ParseCustomEndpoints
(
settings
.
CustomEndpoints
),
LinuxDoOAuthEnabled
:
settings
.
LinuxDoOAuthEnabled
,
SoraClientEnabled
:
settings
.
SoraClientEnabled
,
BackendModeEnabled
:
settings
.
BackendModeEnabled
,
...
...
backend/internal/pkg/apicompat/anthropic_responses_test.go
View file @
08c4e514
...
...
@@ -632,8 +632,8 @@ func TestAnthropicToResponses_ThinkingEnabled(t *testing.T) {
resp
,
err
:=
AnthropicToResponses
(
req
)
require
.
NoError
(
t
,
err
)
require
.
NotNil
(
t
,
resp
.
Reasoning
)
// thinking.type is ignored for effort; default
x
high applies.
assert
.
Equal
(
t
,
"
x
high"
,
resp
.
Reasoning
.
Effort
)
// thinking.type is ignored for effort; default high applies.
assert
.
Equal
(
t
,
"high"
,
resp
.
Reasoning
.
Effort
)
assert
.
Equal
(
t
,
"auto"
,
resp
.
Reasoning
.
Summary
)
assert
.
Contains
(
t
,
resp
.
Include
,
"reasoning.encrypted_content"
)
assert
.
NotContains
(
t
,
resp
.
Include
,
"reasoning.summary"
)
...
...
@@ -650,8 +650,8 @@ func TestAnthropicToResponses_ThinkingAdaptive(t *testing.T) {
resp
,
err
:=
AnthropicToResponses
(
req
)
require
.
NoError
(
t
,
err
)
require
.
NotNil
(
t
,
resp
.
Reasoning
)
// thinking.type is ignored for effort; default
x
high applies.
assert
.
Equal
(
t
,
"
x
high"
,
resp
.
Reasoning
.
Effort
)
// thinking.type is ignored for effort; default high applies.
assert
.
Equal
(
t
,
"high"
,
resp
.
Reasoning
.
Effort
)
assert
.
Equal
(
t
,
"auto"
,
resp
.
Reasoning
.
Summary
)
assert
.
NotContains
(
t
,
resp
.
Include
,
"reasoning.summary"
)
}
...
...
@@ -666,9 +666,9 @@ func TestAnthropicToResponses_ThinkingDisabled(t *testing.T) {
resp
,
err
:=
AnthropicToResponses
(
req
)
require
.
NoError
(
t
,
err
)
// Default effort applies (high →
x
high) even when thinking is disabled.
// Default effort applies (high → high) even when thinking is disabled.
require
.
NotNil
(
t
,
resp
.
Reasoning
)
assert
.
Equal
(
t
,
"
x
high"
,
resp
.
Reasoning
.
Effort
)
assert
.
Equal
(
t
,
"high"
,
resp
.
Reasoning
.
Effort
)
}
func
TestAnthropicToResponses_NoThinking
(
t
*
testing
.
T
)
{
...
...
@@ -680,9 +680,9 @@ func TestAnthropicToResponses_NoThinking(t *testing.T) {
resp
,
err
:=
AnthropicToResponses
(
req
)
require
.
NoError
(
t
,
err
)
// Default effort applies (high →
x
high) when no thinking/output_config is set.
// Default effort applies (high → high) when no thinking/output_config is set.
require
.
NotNil
(
t
,
resp
.
Reasoning
)
assert
.
Equal
(
t
,
"
x
high"
,
resp
.
Reasoning
.
Effort
)
assert
.
Equal
(
t
,
"high"
,
resp
.
Reasoning
.
Effort
)
}
// ---------------------------------------------------------------------------
...
...
@@ -690,7 +690,7 @@ func TestAnthropicToResponses_NoThinking(t *testing.T) {
// ---------------------------------------------------------------------------
func
TestAnthropicToResponses_OutputConfigOverridesDefault
(
t
*
testing
.
T
)
{
// Default is
x
high, but output_config.effort="low" overrides. low→low after mapping.
// Default is high, but output_config.effort="low" overrides. low→low after mapping.
req
:=
&
AnthropicRequest
{
Model
:
"gpt-5.2"
,
MaxTokens
:
1024
,
...
...
@@ -708,7 +708,7 @@ func TestAnthropicToResponses_OutputConfigOverridesDefault(t *testing.T) {
func
TestAnthropicToResponses_OutputConfigWithoutThinking
(
t
*
testing
.
T
)
{
// No thinking field, but output_config.effort="medium" → creates reasoning.
// medium→
high
after mapping.
// medium→
medium
after
1:1
mapping.
req
:=
&
AnthropicRequest
{
Model
:
"gpt-5.2"
,
MaxTokens
:
1024
,
...
...
@@ -719,12 +719,12 @@ func TestAnthropicToResponses_OutputConfigWithoutThinking(t *testing.T) {
resp
,
err
:=
AnthropicToResponses
(
req
)
require
.
NoError
(
t
,
err
)
require
.
NotNil
(
t
,
resp
.
Reasoning
)
assert
.
Equal
(
t
,
"
high
"
,
resp
.
Reasoning
.
Effort
)
assert
.
Equal
(
t
,
"
medium
"
,
resp
.
Reasoning
.
Effort
)
assert
.
Equal
(
t
,
"auto"
,
resp
.
Reasoning
.
Summary
)
}
func
TestAnthropicToResponses_OutputConfigHigh
(
t
*
testing
.
T
)
{
// output_config.effort="high" → mapped to "
x
high".
// output_config.effort="high" → mapped to "high"
(1:1, both sides' default)
.
req
:=
&
AnthropicRequest
{
Model
:
"gpt-5.2"
,
MaxTokens
:
1024
,
...
...
@@ -732,6 +732,22 @@ func TestAnthropicToResponses_OutputConfigHigh(t *testing.T) {
OutputConfig
:
&
AnthropicOutputConfig
{
Effort
:
"high"
},
}
resp
,
err
:=
AnthropicToResponses
(
req
)
require
.
NoError
(
t
,
err
)
require
.
NotNil
(
t
,
resp
.
Reasoning
)
assert
.
Equal
(
t
,
"high"
,
resp
.
Reasoning
.
Effort
)
assert
.
Equal
(
t
,
"auto"
,
resp
.
Reasoning
.
Summary
)
}
func
TestAnthropicToResponses_OutputConfigMax
(
t
*
testing
.
T
)
{
// output_config.effort="max" → mapped to OpenAI's highest supported level "xhigh".
req
:=
&
AnthropicRequest
{
Model
:
"gpt-5.2"
,
MaxTokens
:
1024
,
Messages
:
[]
AnthropicMessage
{{
Role
:
"user"
,
Content
:
json
.
RawMessage
(
`"Hello"`
)}},
OutputConfig
:
&
AnthropicOutputConfig
{
Effort
:
"max"
},
}
resp
,
err
:=
AnthropicToResponses
(
req
)
require
.
NoError
(
t
,
err
)
require
.
NotNil
(
t
,
resp
.
Reasoning
)
...
...
@@ -740,7 +756,7 @@ func TestAnthropicToResponses_OutputConfigHigh(t *testing.T) {
}
func
TestAnthropicToResponses_NoOutputConfig
(
t
*
testing
.
T
)
{
// No output_config → default
x
high regardless of thinking.type.
// No output_config → default high regardless of thinking.type.
req
:=
&
AnthropicRequest
{
Model
:
"gpt-5.2"
,
MaxTokens
:
1024
,
...
...
@@ -751,11 +767,11 @@ func TestAnthropicToResponses_NoOutputConfig(t *testing.T) {
resp
,
err
:=
AnthropicToResponses
(
req
)
require
.
NoError
(
t
,
err
)
require
.
NotNil
(
t
,
resp
.
Reasoning
)
assert
.
Equal
(
t
,
"
x
high"
,
resp
.
Reasoning
.
Effort
)
assert
.
Equal
(
t
,
"high"
,
resp
.
Reasoning
.
Effort
)
}
func
TestAnthropicToResponses_OutputConfigWithoutEffort
(
t
*
testing
.
T
)
{
// output_config present but effort empty (e.g. only format set) → default
x
high.
// output_config present but effort empty (e.g. only format set) → default high.
req
:=
&
AnthropicRequest
{
Model
:
"gpt-5.2"
,
MaxTokens
:
1024
,
...
...
@@ -766,7 +782,7 @@ func TestAnthropicToResponses_OutputConfigWithoutEffort(t *testing.T) {
resp
,
err
:=
AnthropicToResponses
(
req
)
require
.
NoError
(
t
,
err
)
require
.
NotNil
(
t
,
resp
.
Reasoning
)
assert
.
Equal
(
t
,
"
x
high"
,
resp
.
Reasoning
.
Effort
)
assert
.
Equal
(
t
,
"high"
,
resp
.
Reasoning
.
Effort
)
}
// ---------------------------------------------------------------------------
...
...
Prev
1
2
3
Next
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment