mirror of
https://gitee.com/wanwujie/sub2api
synced 2026-04-08 01:00:21 +08:00
Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
074bd0dfda | ||
|
|
b41fa5e15f | ||
|
|
beceb45d23 | ||
|
|
9450edf462 | ||
|
|
785a7397f8 | ||
|
|
3d1f03c286 | ||
|
|
8ff40f52e0 | ||
|
|
ab14df043a |
@@ -1 +1 @@
|
|||||||
0.1.76
|
0.1.83
|
||||||
@@ -650,6 +650,7 @@ var (
|
|||||||
{Name: "ip_address", Type: field.TypeString, Nullable: true, Size: 45},
|
{Name: "ip_address", Type: field.TypeString, Nullable: true, Size: 45},
|
||||||
{Name: "image_count", Type: field.TypeInt, Default: 0},
|
{Name: "image_count", Type: field.TypeInt, Default: 0},
|
||||||
{Name: "image_size", Type: field.TypeString, Nullable: true, Size: 10},
|
{Name: "image_size", Type: field.TypeString, Nullable: true, Size: 10},
|
||||||
|
{Name: "cache_ttl_overridden", Type: field.TypeBool, Default: false},
|
||||||
{Name: "created_at", Type: field.TypeTime, SchemaType: map[string]string{"postgres": "timestamptz"}},
|
{Name: "created_at", Type: field.TypeTime, SchemaType: map[string]string{"postgres": "timestamptz"}},
|
||||||
{Name: "api_key_id", Type: field.TypeInt64},
|
{Name: "api_key_id", Type: field.TypeInt64},
|
||||||
{Name: "account_id", Type: field.TypeInt64},
|
{Name: "account_id", Type: field.TypeInt64},
|
||||||
@@ -665,31 +666,31 @@ var (
|
|||||||
ForeignKeys: []*schema.ForeignKey{
|
ForeignKeys: []*schema.ForeignKey{
|
||||||
{
|
{
|
||||||
Symbol: "usage_logs_api_keys_usage_logs",
|
Symbol: "usage_logs_api_keys_usage_logs",
|
||||||
Columns: []*schema.Column{UsageLogsColumns[26]},
|
Columns: []*schema.Column{UsageLogsColumns[27]},
|
||||||
RefColumns: []*schema.Column{APIKeysColumns[0]},
|
RefColumns: []*schema.Column{APIKeysColumns[0]},
|
||||||
OnDelete: schema.NoAction,
|
OnDelete: schema.NoAction,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Symbol: "usage_logs_accounts_usage_logs",
|
Symbol: "usage_logs_accounts_usage_logs",
|
||||||
Columns: []*schema.Column{UsageLogsColumns[27]},
|
Columns: []*schema.Column{UsageLogsColumns[28]},
|
||||||
RefColumns: []*schema.Column{AccountsColumns[0]},
|
RefColumns: []*schema.Column{AccountsColumns[0]},
|
||||||
OnDelete: schema.NoAction,
|
OnDelete: schema.NoAction,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Symbol: "usage_logs_groups_usage_logs",
|
Symbol: "usage_logs_groups_usage_logs",
|
||||||
Columns: []*schema.Column{UsageLogsColumns[28]},
|
Columns: []*schema.Column{UsageLogsColumns[29]},
|
||||||
RefColumns: []*schema.Column{GroupsColumns[0]},
|
RefColumns: []*schema.Column{GroupsColumns[0]},
|
||||||
OnDelete: schema.SetNull,
|
OnDelete: schema.SetNull,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Symbol: "usage_logs_users_usage_logs",
|
Symbol: "usage_logs_users_usage_logs",
|
||||||
Columns: []*schema.Column{UsageLogsColumns[29]},
|
Columns: []*schema.Column{UsageLogsColumns[30]},
|
||||||
RefColumns: []*schema.Column{UsersColumns[0]},
|
RefColumns: []*schema.Column{UsersColumns[0]},
|
||||||
OnDelete: schema.NoAction,
|
OnDelete: schema.NoAction,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Symbol: "usage_logs_user_subscriptions_usage_logs",
|
Symbol: "usage_logs_user_subscriptions_usage_logs",
|
||||||
Columns: []*schema.Column{UsageLogsColumns[30]},
|
Columns: []*schema.Column{UsageLogsColumns[31]},
|
||||||
RefColumns: []*schema.Column{UserSubscriptionsColumns[0]},
|
RefColumns: []*schema.Column{UserSubscriptionsColumns[0]},
|
||||||
OnDelete: schema.SetNull,
|
OnDelete: schema.SetNull,
|
||||||
},
|
},
|
||||||
@@ -698,32 +699,32 @@ var (
|
|||||||
{
|
{
|
||||||
Name: "usagelog_user_id",
|
Name: "usagelog_user_id",
|
||||||
Unique: false,
|
Unique: false,
|
||||||
Columns: []*schema.Column{UsageLogsColumns[29]},
|
Columns: []*schema.Column{UsageLogsColumns[30]},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "usagelog_api_key_id",
|
Name: "usagelog_api_key_id",
|
||||||
Unique: false,
|
Unique: false,
|
||||||
Columns: []*schema.Column{UsageLogsColumns[26]},
|
Columns: []*schema.Column{UsageLogsColumns[27]},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "usagelog_account_id",
|
Name: "usagelog_account_id",
|
||||||
Unique: false,
|
Unique: false,
|
||||||
Columns: []*schema.Column{UsageLogsColumns[27]},
|
Columns: []*schema.Column{UsageLogsColumns[28]},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "usagelog_group_id",
|
Name: "usagelog_group_id",
|
||||||
Unique: false,
|
Unique: false,
|
||||||
Columns: []*schema.Column{UsageLogsColumns[28]},
|
Columns: []*schema.Column{UsageLogsColumns[29]},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "usagelog_subscription_id",
|
Name: "usagelog_subscription_id",
|
||||||
Unique: false,
|
Unique: false,
|
||||||
Columns: []*schema.Column{UsageLogsColumns[30]},
|
Columns: []*schema.Column{UsageLogsColumns[31]},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "usagelog_created_at",
|
Name: "usagelog_created_at",
|
||||||
Unique: false,
|
Unique: false,
|
||||||
Columns: []*schema.Column{UsageLogsColumns[25]},
|
Columns: []*schema.Column{UsageLogsColumns[26]},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "usagelog_model",
|
Name: "usagelog_model",
|
||||||
@@ -738,12 +739,12 @@ var (
|
|||||||
{
|
{
|
||||||
Name: "usagelog_user_id_created_at",
|
Name: "usagelog_user_id_created_at",
|
||||||
Unique: false,
|
Unique: false,
|
||||||
Columns: []*schema.Column{UsageLogsColumns[29], UsageLogsColumns[25]},
|
Columns: []*schema.Column{UsageLogsColumns[30], UsageLogsColumns[26]},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "usagelog_api_key_id_created_at",
|
Name: "usagelog_api_key_id_created_at",
|
||||||
Unique: false,
|
Unique: false,
|
||||||
Columns: []*schema.Column{UsageLogsColumns[26], UsageLogsColumns[25]},
|
Columns: []*schema.Column{UsageLogsColumns[27], UsageLogsColumns[26]},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15061,6 +15061,7 @@ type UsageLogMutation struct {
|
|||||||
image_count *int
|
image_count *int
|
||||||
addimage_count *int
|
addimage_count *int
|
||||||
image_size *string
|
image_size *string
|
||||||
|
cache_ttl_overridden *bool
|
||||||
created_at *time.Time
|
created_at *time.Time
|
||||||
clearedFields map[string]struct{}
|
clearedFields map[string]struct{}
|
||||||
user *int64
|
user *int64
|
||||||
@@ -16687,6 +16688,42 @@ func (m *UsageLogMutation) ResetImageSize() {
|
|||||||
delete(m.clearedFields, usagelog.FieldImageSize)
|
delete(m.clearedFields, usagelog.FieldImageSize)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetCacheTTLOverridden sets the "cache_ttl_overridden" field.
|
||||||
|
func (m *UsageLogMutation) SetCacheTTLOverridden(b bool) {
|
||||||
|
m.cache_ttl_overridden = &b
|
||||||
|
}
|
||||||
|
|
||||||
|
// CacheTTLOverridden returns the value of the "cache_ttl_overridden" field in the mutation.
|
||||||
|
func (m *UsageLogMutation) CacheTTLOverridden() (r bool, exists bool) {
|
||||||
|
v := m.cache_ttl_overridden
|
||||||
|
if v == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return *v, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// OldCacheTTLOverridden returns the old "cache_ttl_overridden" 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) OldCacheTTLOverridden(ctx context.Context) (v bool, err error) {
|
||||||
|
if !m.op.Is(OpUpdateOne) {
|
||||||
|
return v, errors.New("OldCacheTTLOverridden is only allowed on UpdateOne operations")
|
||||||
|
}
|
||||||
|
if m.id == nil || m.oldValue == nil {
|
||||||
|
return v, errors.New("OldCacheTTLOverridden requires an ID field in the mutation")
|
||||||
|
}
|
||||||
|
oldValue, err := m.oldValue(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return v, fmt.Errorf("querying old value for OldCacheTTLOverridden: %w", err)
|
||||||
|
}
|
||||||
|
return oldValue.CacheTTLOverridden, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResetCacheTTLOverridden resets all changes to the "cache_ttl_overridden" field.
|
||||||
|
func (m *UsageLogMutation) ResetCacheTTLOverridden() {
|
||||||
|
m.cache_ttl_overridden = nil
|
||||||
|
}
|
||||||
|
|
||||||
// SetCreatedAt sets the "created_at" field.
|
// SetCreatedAt sets the "created_at" field.
|
||||||
func (m *UsageLogMutation) SetCreatedAt(t time.Time) {
|
func (m *UsageLogMutation) SetCreatedAt(t time.Time) {
|
||||||
m.created_at = &t
|
m.created_at = &t
|
||||||
@@ -16892,7 +16929,7 @@ func (m *UsageLogMutation) Type() string {
|
|||||||
// order to get all numeric fields that were incremented/decremented, call
|
// order to get all numeric fields that were incremented/decremented, call
|
||||||
// AddedFields().
|
// AddedFields().
|
||||||
func (m *UsageLogMutation) Fields() []string {
|
func (m *UsageLogMutation) Fields() []string {
|
||||||
fields := make([]string, 0, 30)
|
fields := make([]string, 0, 31)
|
||||||
if m.user != nil {
|
if m.user != nil {
|
||||||
fields = append(fields, usagelog.FieldUserID)
|
fields = append(fields, usagelog.FieldUserID)
|
||||||
}
|
}
|
||||||
@@ -16980,6 +17017,9 @@ func (m *UsageLogMutation) Fields() []string {
|
|||||||
if m.image_size != nil {
|
if m.image_size != nil {
|
||||||
fields = append(fields, usagelog.FieldImageSize)
|
fields = append(fields, usagelog.FieldImageSize)
|
||||||
}
|
}
|
||||||
|
if m.cache_ttl_overridden != nil {
|
||||||
|
fields = append(fields, usagelog.FieldCacheTTLOverridden)
|
||||||
|
}
|
||||||
if m.created_at != nil {
|
if m.created_at != nil {
|
||||||
fields = append(fields, usagelog.FieldCreatedAt)
|
fields = append(fields, usagelog.FieldCreatedAt)
|
||||||
}
|
}
|
||||||
@@ -17049,6 +17089,8 @@ func (m *UsageLogMutation) Field(name string) (ent.Value, bool) {
|
|||||||
return m.ImageCount()
|
return m.ImageCount()
|
||||||
case usagelog.FieldImageSize:
|
case usagelog.FieldImageSize:
|
||||||
return m.ImageSize()
|
return m.ImageSize()
|
||||||
|
case usagelog.FieldCacheTTLOverridden:
|
||||||
|
return m.CacheTTLOverridden()
|
||||||
case usagelog.FieldCreatedAt:
|
case usagelog.FieldCreatedAt:
|
||||||
return m.CreatedAt()
|
return m.CreatedAt()
|
||||||
}
|
}
|
||||||
@@ -17118,6 +17160,8 @@ func (m *UsageLogMutation) OldField(ctx context.Context, name string) (ent.Value
|
|||||||
return m.OldImageCount(ctx)
|
return m.OldImageCount(ctx)
|
||||||
case usagelog.FieldImageSize:
|
case usagelog.FieldImageSize:
|
||||||
return m.OldImageSize(ctx)
|
return m.OldImageSize(ctx)
|
||||||
|
case usagelog.FieldCacheTTLOverridden:
|
||||||
|
return m.OldCacheTTLOverridden(ctx)
|
||||||
case usagelog.FieldCreatedAt:
|
case usagelog.FieldCreatedAt:
|
||||||
return m.OldCreatedAt(ctx)
|
return m.OldCreatedAt(ctx)
|
||||||
}
|
}
|
||||||
@@ -17332,6 +17376,13 @@ func (m *UsageLogMutation) SetField(name string, value ent.Value) error {
|
|||||||
}
|
}
|
||||||
m.SetImageSize(v)
|
m.SetImageSize(v)
|
||||||
return nil
|
return nil
|
||||||
|
case usagelog.FieldCacheTTLOverridden:
|
||||||
|
v, ok := value.(bool)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("unexpected type %T for field %s", value, name)
|
||||||
|
}
|
||||||
|
m.SetCacheTTLOverridden(v)
|
||||||
|
return nil
|
||||||
case usagelog.FieldCreatedAt:
|
case usagelog.FieldCreatedAt:
|
||||||
v, ok := value.(time.Time)
|
v, ok := value.(time.Time)
|
||||||
if !ok {
|
if !ok {
|
||||||
@@ -17745,6 +17796,9 @@ func (m *UsageLogMutation) ResetField(name string) error {
|
|||||||
case usagelog.FieldImageSize:
|
case usagelog.FieldImageSize:
|
||||||
m.ResetImageSize()
|
m.ResetImageSize()
|
||||||
return nil
|
return nil
|
||||||
|
case usagelog.FieldCacheTTLOverridden:
|
||||||
|
m.ResetCacheTTLOverridden()
|
||||||
|
return nil
|
||||||
case usagelog.FieldCreatedAt:
|
case usagelog.FieldCreatedAt:
|
||||||
m.ResetCreatedAt()
|
m.ResetCreatedAt()
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -779,8 +779,12 @@ func init() {
|
|||||||
usagelogDescImageSize := usagelogFields[28].Descriptor()
|
usagelogDescImageSize := usagelogFields[28].Descriptor()
|
||||||
// usagelog.ImageSizeValidator is a validator for the "image_size" field. It is called by the builders before save.
|
// 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)
|
usagelog.ImageSizeValidator = usagelogDescImageSize.Validators[0].(func(string) error)
|
||||||
|
// usagelogDescCacheTTLOverridden is the schema descriptor for cache_ttl_overridden field.
|
||||||
|
usagelogDescCacheTTLOverridden := usagelogFields[29].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 is the schema descriptor for created_at field.
|
||||||
usagelogDescCreatedAt := usagelogFields[29].Descriptor()
|
usagelogDescCreatedAt := usagelogFields[30].Descriptor()
|
||||||
// usagelog.DefaultCreatedAt holds the default value on creation for the created_at field.
|
// usagelog.DefaultCreatedAt holds the default value on creation for the created_at field.
|
||||||
usagelog.DefaultCreatedAt = usagelogDescCreatedAt.Default.(func() time.Time)
|
usagelog.DefaultCreatedAt = usagelogDescCreatedAt.Default.(func() time.Time)
|
||||||
userMixin := schema.User{}.Mixin()
|
userMixin := schema.User{}.Mixin()
|
||||||
|
|||||||
@@ -119,6 +119,10 @@ func (UsageLog) Fields() []ent.Field {
|
|||||||
Optional().
|
Optional().
|
||||||
Nillable(),
|
Nillable(),
|
||||||
|
|
||||||
|
// Cache TTL Override 标记(管理员强制替换了缓存 TTL 计费)
|
||||||
|
field.Bool("cache_ttl_overridden").
|
||||||
|
Default(false),
|
||||||
|
|
||||||
// 时间戳(只有 created_at,日志不可修改)
|
// 时间戳(只有 created_at,日志不可修改)
|
||||||
field.Time("created_at").
|
field.Time("created_at").
|
||||||
Default(time.Now).
|
Default(time.Now).
|
||||||
|
|||||||
@@ -80,6 +80,8 @@ type UsageLog struct {
|
|||||||
ImageCount int `json:"image_count,omitempty"`
|
ImageCount int `json:"image_count,omitempty"`
|
||||||
// ImageSize holds the value of the "image_size" field.
|
// ImageSize holds the value of the "image_size" field.
|
||||||
ImageSize *string `json:"image_size,omitempty"`
|
ImageSize *string `json:"image_size,omitempty"`
|
||||||
|
// CacheTTLOverridden holds the value of the "cache_ttl_overridden" field.
|
||||||
|
CacheTTLOverridden bool `json:"cache_ttl_overridden,omitempty"`
|
||||||
// CreatedAt holds the value of the "created_at" field.
|
// CreatedAt holds the value of the "created_at" field.
|
||||||
CreatedAt time.Time `json:"created_at,omitempty"`
|
CreatedAt time.Time `json:"created_at,omitempty"`
|
||||||
// Edges holds the relations/edges for other nodes in the graph.
|
// Edges holds the relations/edges for other nodes in the graph.
|
||||||
@@ -165,7 +167,7 @@ func (*UsageLog) scanValues(columns []string) ([]any, error) {
|
|||||||
values := make([]any, len(columns))
|
values := make([]any, len(columns))
|
||||||
for i := range columns {
|
for i := range columns {
|
||||||
switch columns[i] {
|
switch columns[i] {
|
||||||
case usagelog.FieldStream:
|
case usagelog.FieldStream, usagelog.FieldCacheTTLOverridden:
|
||||||
values[i] = new(sql.NullBool)
|
values[i] = new(sql.NullBool)
|
||||||
case usagelog.FieldInputCost, usagelog.FieldOutputCost, usagelog.FieldCacheCreationCost, usagelog.FieldCacheReadCost, usagelog.FieldTotalCost, usagelog.FieldActualCost, usagelog.FieldRateMultiplier, usagelog.FieldAccountRateMultiplier:
|
case usagelog.FieldInputCost, usagelog.FieldOutputCost, usagelog.FieldCacheCreationCost, usagelog.FieldCacheReadCost, usagelog.FieldTotalCost, usagelog.FieldActualCost, usagelog.FieldRateMultiplier, usagelog.FieldAccountRateMultiplier:
|
||||||
values[i] = new(sql.NullFloat64)
|
values[i] = new(sql.NullFloat64)
|
||||||
@@ -378,6 +380,12 @@ func (_m *UsageLog) assignValues(columns []string, values []any) error {
|
|||||||
_m.ImageSize = new(string)
|
_m.ImageSize = new(string)
|
||||||
*_m.ImageSize = value.String
|
*_m.ImageSize = value.String
|
||||||
}
|
}
|
||||||
|
case usagelog.FieldCacheTTLOverridden:
|
||||||
|
if value, ok := values[i].(*sql.NullBool); !ok {
|
||||||
|
return fmt.Errorf("unexpected type %T for field cache_ttl_overridden", values[i])
|
||||||
|
} else if value.Valid {
|
||||||
|
_m.CacheTTLOverridden = value.Bool
|
||||||
|
}
|
||||||
case usagelog.FieldCreatedAt:
|
case usagelog.FieldCreatedAt:
|
||||||
if value, ok := values[i].(*sql.NullTime); !ok {
|
if value, ok := values[i].(*sql.NullTime); !ok {
|
||||||
return fmt.Errorf("unexpected type %T for field created_at", values[i])
|
return fmt.Errorf("unexpected type %T for field created_at", values[i])
|
||||||
@@ -548,6 +556,9 @@ func (_m *UsageLog) String() string {
|
|||||||
builder.WriteString(*v)
|
builder.WriteString(*v)
|
||||||
}
|
}
|
||||||
builder.WriteString(", ")
|
builder.WriteString(", ")
|
||||||
|
builder.WriteString("cache_ttl_overridden=")
|
||||||
|
builder.WriteString(fmt.Sprintf("%v", _m.CacheTTLOverridden))
|
||||||
|
builder.WriteString(", ")
|
||||||
builder.WriteString("created_at=")
|
builder.WriteString("created_at=")
|
||||||
builder.WriteString(_m.CreatedAt.Format(time.ANSIC))
|
builder.WriteString(_m.CreatedAt.Format(time.ANSIC))
|
||||||
builder.WriteByte(')')
|
builder.WriteByte(')')
|
||||||
|
|||||||
@@ -72,6 +72,8 @@ const (
|
|||||||
FieldImageCount = "image_count"
|
FieldImageCount = "image_count"
|
||||||
// FieldImageSize holds the string denoting the image_size field in the database.
|
// FieldImageSize holds the string denoting the image_size field in the database.
|
||||||
FieldImageSize = "image_size"
|
FieldImageSize = "image_size"
|
||||||
|
// FieldCacheTTLOverridden holds the string denoting the cache_ttl_overridden field in the database.
|
||||||
|
FieldCacheTTLOverridden = "cache_ttl_overridden"
|
||||||
// FieldCreatedAt holds the string denoting the created_at field in the database.
|
// FieldCreatedAt holds the string denoting the created_at field in the database.
|
||||||
FieldCreatedAt = "created_at"
|
FieldCreatedAt = "created_at"
|
||||||
// EdgeUser holds the string denoting the user edge name in mutations.
|
// EdgeUser holds the string denoting the user edge name in mutations.
|
||||||
@@ -155,6 +157,7 @@ var Columns = []string{
|
|||||||
FieldIPAddress,
|
FieldIPAddress,
|
||||||
FieldImageCount,
|
FieldImageCount,
|
||||||
FieldImageSize,
|
FieldImageSize,
|
||||||
|
FieldCacheTTLOverridden,
|
||||||
FieldCreatedAt,
|
FieldCreatedAt,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -211,6 +214,8 @@ var (
|
|||||||
DefaultImageCount int
|
DefaultImageCount int
|
||||||
// ImageSizeValidator is a validator for the "image_size" field. It is called by the builders before save.
|
// ImageSizeValidator is a validator for the "image_size" field. It is called by the builders before save.
|
||||||
ImageSizeValidator func(string) error
|
ImageSizeValidator func(string) error
|
||||||
|
// DefaultCacheTTLOverridden holds the default value on creation for the "cache_ttl_overridden" field.
|
||||||
|
DefaultCacheTTLOverridden bool
|
||||||
// DefaultCreatedAt holds the default value on creation for the "created_at" field.
|
// DefaultCreatedAt holds the default value on creation for the "created_at" field.
|
||||||
DefaultCreatedAt func() time.Time
|
DefaultCreatedAt func() time.Time
|
||||||
)
|
)
|
||||||
@@ -368,6 +373,11 @@ func ByImageSize(opts ...sql.OrderTermOption) OrderOption {
|
|||||||
return sql.OrderByField(FieldImageSize, opts...).ToFunc()
|
return sql.OrderByField(FieldImageSize, opts...).ToFunc()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ByCacheTTLOverridden orders the results by the cache_ttl_overridden field.
|
||||||
|
func ByCacheTTLOverridden(opts ...sql.OrderTermOption) OrderOption {
|
||||||
|
return sql.OrderByField(FieldCacheTTLOverridden, opts...).ToFunc()
|
||||||
|
}
|
||||||
|
|
||||||
// ByCreatedAt orders the results by the created_at field.
|
// ByCreatedAt orders the results by the created_at field.
|
||||||
func ByCreatedAt(opts ...sql.OrderTermOption) OrderOption {
|
func ByCreatedAt(opts ...sql.OrderTermOption) OrderOption {
|
||||||
return sql.OrderByField(FieldCreatedAt, opts...).ToFunc()
|
return sql.OrderByField(FieldCreatedAt, opts...).ToFunc()
|
||||||
|
|||||||
@@ -200,6 +200,11 @@ func ImageSize(v string) predicate.UsageLog {
|
|||||||
return predicate.UsageLog(sql.FieldEQ(FieldImageSize, v))
|
return predicate.UsageLog(sql.FieldEQ(FieldImageSize, v))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CacheTTLOverridden applies equality check predicate on the "cache_ttl_overridden" field. It's identical to CacheTTLOverriddenEQ.
|
||||||
|
func CacheTTLOverridden(v bool) predicate.UsageLog {
|
||||||
|
return predicate.UsageLog(sql.FieldEQ(FieldCacheTTLOverridden, v))
|
||||||
|
}
|
||||||
|
|
||||||
// CreatedAt applies equality check predicate on the "created_at" field. It's identical to CreatedAtEQ.
|
// CreatedAt applies equality check predicate on the "created_at" field. It's identical to CreatedAtEQ.
|
||||||
func CreatedAt(v time.Time) predicate.UsageLog {
|
func CreatedAt(v time.Time) predicate.UsageLog {
|
||||||
return predicate.UsageLog(sql.FieldEQ(FieldCreatedAt, v))
|
return predicate.UsageLog(sql.FieldEQ(FieldCreatedAt, v))
|
||||||
@@ -1440,6 +1445,16 @@ func ImageSizeContainsFold(v string) predicate.UsageLog {
|
|||||||
return predicate.UsageLog(sql.FieldContainsFold(FieldImageSize, v))
|
return predicate.UsageLog(sql.FieldContainsFold(FieldImageSize, v))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CacheTTLOverriddenEQ applies the EQ predicate on the "cache_ttl_overridden" field.
|
||||||
|
func CacheTTLOverriddenEQ(v bool) predicate.UsageLog {
|
||||||
|
return predicate.UsageLog(sql.FieldEQ(FieldCacheTTLOverridden, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// CacheTTLOverriddenNEQ applies the NEQ predicate on the "cache_ttl_overridden" field.
|
||||||
|
func CacheTTLOverriddenNEQ(v bool) predicate.UsageLog {
|
||||||
|
return predicate.UsageLog(sql.FieldNEQ(FieldCacheTTLOverridden, v))
|
||||||
|
}
|
||||||
|
|
||||||
// CreatedAtEQ applies the EQ predicate on the "created_at" field.
|
// CreatedAtEQ applies the EQ predicate on the "created_at" field.
|
||||||
func CreatedAtEQ(v time.Time) predicate.UsageLog {
|
func CreatedAtEQ(v time.Time) predicate.UsageLog {
|
||||||
return predicate.UsageLog(sql.FieldEQ(FieldCreatedAt, v))
|
return predicate.UsageLog(sql.FieldEQ(FieldCreatedAt, v))
|
||||||
|
|||||||
@@ -393,6 +393,20 @@ func (_c *UsageLogCreate) SetNillableImageSize(v *string) *UsageLogCreate {
|
|||||||
return _c
|
return _c
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetCacheTTLOverridden sets the "cache_ttl_overridden" field.
|
||||||
|
func (_c *UsageLogCreate) SetCacheTTLOverridden(v bool) *UsageLogCreate {
|
||||||
|
_c.mutation.SetCacheTTLOverridden(v)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNillableCacheTTLOverridden sets the "cache_ttl_overridden" field if the given value is not nil.
|
||||||
|
func (_c *UsageLogCreate) SetNillableCacheTTLOverridden(v *bool) *UsageLogCreate {
|
||||||
|
if v != nil {
|
||||||
|
_c.SetCacheTTLOverridden(*v)
|
||||||
|
}
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
// SetCreatedAt sets the "created_at" field.
|
// SetCreatedAt sets the "created_at" field.
|
||||||
func (_c *UsageLogCreate) SetCreatedAt(v time.Time) *UsageLogCreate {
|
func (_c *UsageLogCreate) SetCreatedAt(v time.Time) *UsageLogCreate {
|
||||||
_c.mutation.SetCreatedAt(v)
|
_c.mutation.SetCreatedAt(v)
|
||||||
@@ -531,6 +545,10 @@ func (_c *UsageLogCreate) defaults() {
|
|||||||
v := usagelog.DefaultImageCount
|
v := usagelog.DefaultImageCount
|
||||||
_c.mutation.SetImageCount(v)
|
_c.mutation.SetImageCount(v)
|
||||||
}
|
}
|
||||||
|
if _, ok := _c.mutation.CacheTTLOverridden(); !ok {
|
||||||
|
v := usagelog.DefaultCacheTTLOverridden
|
||||||
|
_c.mutation.SetCacheTTLOverridden(v)
|
||||||
|
}
|
||||||
if _, ok := _c.mutation.CreatedAt(); !ok {
|
if _, ok := _c.mutation.CreatedAt(); !ok {
|
||||||
v := usagelog.DefaultCreatedAt()
|
v := usagelog.DefaultCreatedAt()
|
||||||
_c.mutation.SetCreatedAt(v)
|
_c.mutation.SetCreatedAt(v)
|
||||||
@@ -627,6 +645,9 @@ func (_c *UsageLogCreate) check() error {
|
|||||||
return &ValidationError{Name: "image_size", err: fmt.Errorf(`ent: validator failed for field "UsageLog.image_size": %w`, err)}
|
return &ValidationError{Name: "image_size", err: fmt.Errorf(`ent: validator failed for field "UsageLog.image_size": %w`, err)}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if _, ok := _c.mutation.CacheTTLOverridden(); !ok {
|
||||||
|
return &ValidationError{Name: "cache_ttl_overridden", err: errors.New(`ent: missing required field "UsageLog.cache_ttl_overridden"`)}
|
||||||
|
}
|
||||||
if _, ok := _c.mutation.CreatedAt(); !ok {
|
if _, ok := _c.mutation.CreatedAt(); !ok {
|
||||||
return &ValidationError{Name: "created_at", err: errors.New(`ent: missing required field "UsageLog.created_at"`)}
|
return &ValidationError{Name: "created_at", err: errors.New(`ent: missing required field "UsageLog.created_at"`)}
|
||||||
}
|
}
|
||||||
@@ -762,6 +783,10 @@ func (_c *UsageLogCreate) createSpec() (*UsageLog, *sqlgraph.CreateSpec) {
|
|||||||
_spec.SetField(usagelog.FieldImageSize, field.TypeString, value)
|
_spec.SetField(usagelog.FieldImageSize, field.TypeString, value)
|
||||||
_node.ImageSize = &value
|
_node.ImageSize = &value
|
||||||
}
|
}
|
||||||
|
if value, ok := _c.mutation.CacheTTLOverridden(); ok {
|
||||||
|
_spec.SetField(usagelog.FieldCacheTTLOverridden, field.TypeBool, value)
|
||||||
|
_node.CacheTTLOverridden = value
|
||||||
|
}
|
||||||
if value, ok := _c.mutation.CreatedAt(); ok {
|
if value, ok := _c.mutation.CreatedAt(); ok {
|
||||||
_spec.SetField(usagelog.FieldCreatedAt, field.TypeTime, value)
|
_spec.SetField(usagelog.FieldCreatedAt, field.TypeTime, value)
|
||||||
_node.CreatedAt = value
|
_node.CreatedAt = value
|
||||||
@@ -1407,6 +1432,18 @@ func (u *UsageLogUpsert) ClearImageSize() *UsageLogUpsert {
|
|||||||
return u
|
return u
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetCacheTTLOverridden sets the "cache_ttl_overridden" field.
|
||||||
|
func (u *UsageLogUpsert) SetCacheTTLOverridden(v bool) *UsageLogUpsert {
|
||||||
|
u.Set(usagelog.FieldCacheTTLOverridden, v)
|
||||||
|
return u
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateCacheTTLOverridden sets the "cache_ttl_overridden" field to the value that was provided on create.
|
||||||
|
func (u *UsageLogUpsert) UpdateCacheTTLOverridden() *UsageLogUpsert {
|
||||||
|
u.SetExcluded(usagelog.FieldCacheTTLOverridden)
|
||||||
|
return u
|
||||||
|
}
|
||||||
|
|
||||||
// UpdateNewValues updates the mutable fields using the new values that were set on create.
|
// UpdateNewValues updates the mutable fields using the new values that were set on create.
|
||||||
// Using this option is equivalent to using:
|
// Using this option is equivalent to using:
|
||||||
//
|
//
|
||||||
@@ -2040,6 +2077,20 @@ func (u *UsageLogUpsertOne) ClearImageSize() *UsageLogUpsertOne {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetCacheTTLOverridden sets the "cache_ttl_overridden" field.
|
||||||
|
func (u *UsageLogUpsertOne) SetCacheTTLOverridden(v bool) *UsageLogUpsertOne {
|
||||||
|
return u.Update(func(s *UsageLogUpsert) {
|
||||||
|
s.SetCacheTTLOverridden(v)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateCacheTTLOverridden sets the "cache_ttl_overridden" field to the value that was provided on create.
|
||||||
|
func (u *UsageLogUpsertOne) UpdateCacheTTLOverridden() *UsageLogUpsertOne {
|
||||||
|
return u.Update(func(s *UsageLogUpsert) {
|
||||||
|
s.UpdateCacheTTLOverridden()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// Exec executes the query.
|
// Exec executes the query.
|
||||||
func (u *UsageLogUpsertOne) Exec(ctx context.Context) error {
|
func (u *UsageLogUpsertOne) Exec(ctx context.Context) error {
|
||||||
if len(u.create.conflict) == 0 {
|
if len(u.create.conflict) == 0 {
|
||||||
@@ -2839,6 +2890,20 @@ func (u *UsageLogUpsertBulk) ClearImageSize() *UsageLogUpsertBulk {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetCacheTTLOverridden sets the "cache_ttl_overridden" field.
|
||||||
|
func (u *UsageLogUpsertBulk) SetCacheTTLOverridden(v bool) *UsageLogUpsertBulk {
|
||||||
|
return u.Update(func(s *UsageLogUpsert) {
|
||||||
|
s.SetCacheTTLOverridden(v)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateCacheTTLOverridden sets the "cache_ttl_overridden" field to the value that was provided on create.
|
||||||
|
func (u *UsageLogUpsertBulk) UpdateCacheTTLOverridden() *UsageLogUpsertBulk {
|
||||||
|
return u.Update(func(s *UsageLogUpsert) {
|
||||||
|
s.UpdateCacheTTLOverridden()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// Exec executes the query.
|
// Exec executes the query.
|
||||||
func (u *UsageLogUpsertBulk) Exec(ctx context.Context) error {
|
func (u *UsageLogUpsertBulk) Exec(ctx context.Context) error {
|
||||||
if u.create.err != nil {
|
if u.create.err != nil {
|
||||||
|
|||||||
@@ -612,6 +612,20 @@ func (_u *UsageLogUpdate) ClearImageSize() *UsageLogUpdate {
|
|||||||
return _u
|
return _u
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetCacheTTLOverridden sets the "cache_ttl_overridden" field.
|
||||||
|
func (_u *UsageLogUpdate) SetCacheTTLOverridden(v bool) *UsageLogUpdate {
|
||||||
|
_u.mutation.SetCacheTTLOverridden(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNillableCacheTTLOverridden sets the "cache_ttl_overridden" field if the given value is not nil.
|
||||||
|
func (_u *UsageLogUpdate) SetNillableCacheTTLOverridden(v *bool) *UsageLogUpdate {
|
||||||
|
if v != nil {
|
||||||
|
_u.SetCacheTTLOverridden(*v)
|
||||||
|
}
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
// SetUser sets the "user" edge to the User entity.
|
// SetUser sets the "user" edge to the User entity.
|
||||||
func (_u *UsageLogUpdate) SetUser(v *User) *UsageLogUpdate {
|
func (_u *UsageLogUpdate) SetUser(v *User) *UsageLogUpdate {
|
||||||
return _u.SetUserID(v.ID)
|
return _u.SetUserID(v.ID)
|
||||||
@@ -894,6 +908,9 @@ func (_u *UsageLogUpdate) sqlSave(ctx context.Context) (_node int, err error) {
|
|||||||
if _u.mutation.ImageSizeCleared() {
|
if _u.mutation.ImageSizeCleared() {
|
||||||
_spec.ClearField(usagelog.FieldImageSize, field.TypeString)
|
_spec.ClearField(usagelog.FieldImageSize, field.TypeString)
|
||||||
}
|
}
|
||||||
|
if value, ok := _u.mutation.CacheTTLOverridden(); ok {
|
||||||
|
_spec.SetField(usagelog.FieldCacheTTLOverridden, field.TypeBool, value)
|
||||||
|
}
|
||||||
if _u.mutation.UserCleared() {
|
if _u.mutation.UserCleared() {
|
||||||
edge := &sqlgraph.EdgeSpec{
|
edge := &sqlgraph.EdgeSpec{
|
||||||
Rel: sqlgraph.M2O,
|
Rel: sqlgraph.M2O,
|
||||||
@@ -1639,6 +1656,20 @@ func (_u *UsageLogUpdateOne) ClearImageSize() *UsageLogUpdateOne {
|
|||||||
return _u
|
return _u
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetCacheTTLOverridden sets the "cache_ttl_overridden" field.
|
||||||
|
func (_u *UsageLogUpdateOne) SetCacheTTLOverridden(v bool) *UsageLogUpdateOne {
|
||||||
|
_u.mutation.SetCacheTTLOverridden(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNillableCacheTTLOverridden sets the "cache_ttl_overridden" field if the given value is not nil.
|
||||||
|
func (_u *UsageLogUpdateOne) SetNillableCacheTTLOverridden(v *bool) *UsageLogUpdateOne {
|
||||||
|
if v != nil {
|
||||||
|
_u.SetCacheTTLOverridden(*v)
|
||||||
|
}
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
// SetUser sets the "user" edge to the User entity.
|
// SetUser sets the "user" edge to the User entity.
|
||||||
func (_u *UsageLogUpdateOne) SetUser(v *User) *UsageLogUpdateOne {
|
func (_u *UsageLogUpdateOne) SetUser(v *User) *UsageLogUpdateOne {
|
||||||
return _u.SetUserID(v.ID)
|
return _u.SetUserID(v.ID)
|
||||||
@@ -1951,6 +1982,9 @@ func (_u *UsageLogUpdateOne) sqlSave(ctx context.Context) (_node *UsageLog, err
|
|||||||
if _u.mutation.ImageSizeCleared() {
|
if _u.mutation.ImageSizeCleared() {
|
||||||
_spec.ClearField(usagelog.FieldImageSize, field.TypeString)
|
_spec.ClearField(usagelog.FieldImageSize, field.TypeString)
|
||||||
}
|
}
|
||||||
|
if value, ok := _u.mutation.CacheTTLOverridden(); ok {
|
||||||
|
_spec.SetField(usagelog.FieldCacheTTLOverridden, field.TypeBool, value)
|
||||||
|
}
|
||||||
if _u.mutation.UserCleared() {
|
if _u.mutation.UserCleared() {
|
||||||
edge := &sqlgraph.EdgeSpec{
|
edge := &sqlgraph.EdgeSpec{
|
||||||
Rel: sqlgraph.M2O,
|
Rel: sqlgraph.M2O,
|
||||||
|
|||||||
@@ -211,6 +211,13 @@ func AccountFromServiceShallow(a *service.Account) *Account {
|
|||||||
enabled := true
|
enabled := true
|
||||||
out.EnableSessionIDMasking = &enabled
|
out.EnableSessionIDMasking = &enabled
|
||||||
}
|
}
|
||||||
|
// 缓存 TTL 强制替换
|
||||||
|
if a.IsCacheTTLOverrideEnabled() {
|
||||||
|
enabled := true
|
||||||
|
out.CacheTTLOverrideEnabled = &enabled
|
||||||
|
target := a.GetCacheTTLOverrideTarget()
|
||||||
|
out.CacheTTLOverrideTarget = &target
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return out
|
return out
|
||||||
@@ -398,6 +405,7 @@ func usageLogFromServiceUser(l *service.UsageLog) UsageLog {
|
|||||||
ImageCount: l.ImageCount,
|
ImageCount: l.ImageCount,
|
||||||
ImageSize: l.ImageSize,
|
ImageSize: l.ImageSize,
|
||||||
UserAgent: l.UserAgent,
|
UserAgent: l.UserAgent,
|
||||||
|
CacheTTLOverridden: l.CacheTTLOverridden,
|
||||||
CreatedAt: l.CreatedAt,
|
CreatedAt: l.CreatedAt,
|
||||||
User: UserFromServiceShallow(l.User),
|
User: UserFromServiceShallow(l.User),
|
||||||
APIKey: APIKeyFromService(l.APIKey),
|
APIKey: APIKeyFromService(l.APIKey),
|
||||||
|
|||||||
@@ -150,6 +150,11 @@ type Account struct {
|
|||||||
// 从 extra 字段提取,方便前端显示和编辑
|
// 从 extra 字段提取,方便前端显示和编辑
|
||||||
EnableSessionIDMasking *bool `json:"session_id_masking_enabled,omitempty"`
|
EnableSessionIDMasking *bool `json:"session_id_masking_enabled,omitempty"`
|
||||||
|
|
||||||
|
// 缓存 TTL 强制替换(仅 Anthropic OAuth/SetupToken 账号有效)
|
||||||
|
// 启用后将所有 cache creation tokens 归入指定的 TTL 类型计费
|
||||||
|
CacheTTLOverrideEnabled *bool `json:"cache_ttl_override_enabled,omitempty"`
|
||||||
|
CacheTTLOverrideTarget *string `json:"cache_ttl_override_target,omitempty"`
|
||||||
|
|
||||||
Proxy *Proxy `json:"proxy,omitempty"`
|
Proxy *Proxy `json:"proxy,omitempty"`
|
||||||
AccountGroups []AccountGroup `json:"account_groups,omitempty"`
|
AccountGroups []AccountGroup `json:"account_groups,omitempty"`
|
||||||
|
|
||||||
@@ -273,6 +278,9 @@ type UsageLog struct {
|
|||||||
// User-Agent
|
// User-Agent
|
||||||
UserAgent *string `json:"user_agent"`
|
UserAgent *string `json:"user_agent"`
|
||||||
|
|
||||||
|
// Cache TTL Override 标记
|
||||||
|
CacheTTLOverridden bool `json:"cache_ttl_overridden"`
|
||||||
|
|
||||||
CreatedAt time.Time `json:"created_at"`
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
|
||||||
User *User `json:"user,omitempty"`
|
User *User `json:"user,omitempty"`
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ const (
|
|||||||
BetaInterleavedThinking = "interleaved-thinking-2025-05-14"
|
BetaInterleavedThinking = "interleaved-thinking-2025-05-14"
|
||||||
BetaFineGrainedToolStreaming = "fine-grained-tool-streaming-2025-05-14"
|
BetaFineGrainedToolStreaming = "fine-grained-tool-streaming-2025-05-14"
|
||||||
BetaTokenCounting = "token-counting-2024-11-01"
|
BetaTokenCounting = "token-counting-2024-11-01"
|
||||||
|
BetaContext1M = "context-1m-2025-08-07"
|
||||||
)
|
)
|
||||||
|
|
||||||
// DefaultBetaHeader Claude Code 客户端默认的 anthropic-beta header
|
// DefaultBetaHeader Claude Code 客户端默认的 anthropic-beta header
|
||||||
@@ -77,6 +78,12 @@ var DefaultModels = []Model{
|
|||||||
DisplayName: "Claude Opus 4.6",
|
DisplayName: "Claude Opus 4.6",
|
||||||
CreatedAt: "2026-02-06T00:00:00Z",
|
CreatedAt: "2026-02-06T00:00:00Z",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
ID: "claude-sonnet-4-6",
|
||||||
|
Type: "model",
|
||||||
|
DisplayName: "Claude Sonnet 4.6",
|
||||||
|
CreatedAt: "2026-02-18T00:00:00Z",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
ID: "claude-sonnet-4-5-20250929",
|
ID: "claude-sonnet-4-5-20250929",
|
||||||
Type: "model",
|
Type: "model",
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ import (
|
|||||||
"github.com/lib/pq"
|
"github.com/lib/pq"
|
||||||
)
|
)
|
||||||
|
|
||||||
const usageLogSelectColumns = "id, user_id, api_key_id, account_id, request_id, model, group_id, subscription_id, input_tokens, output_tokens, cache_creation_tokens, cache_read_tokens, cache_creation_5m_tokens, cache_creation_1h_tokens, input_cost, output_cost, cache_creation_cost, cache_read_cost, total_cost, actual_cost, rate_multiplier, account_rate_multiplier, billing_type, stream, duration_ms, first_token_ms, user_agent, ip_address, image_count, image_size, reasoning_effort, created_at"
|
const usageLogSelectColumns = "id, user_id, api_key_id, account_id, request_id, model, group_id, subscription_id, input_tokens, output_tokens, cache_creation_tokens, cache_read_tokens, cache_creation_5m_tokens, cache_creation_1h_tokens, input_cost, output_cost, cache_creation_cost, cache_read_cost, total_cost, actual_cost, rate_multiplier, account_rate_multiplier, billing_type, stream, duration_ms, first_token_ms, user_agent, ip_address, image_count, image_size, reasoning_effort, cache_ttl_overridden, created_at"
|
||||||
|
|
||||||
type usageLogRepository struct {
|
type usageLogRepository struct {
|
||||||
client *dbent.Client
|
client *dbent.Client
|
||||||
@@ -115,6 +115,7 @@ func (r *usageLogRepository) Create(ctx context.Context, log *service.UsageLog)
|
|||||||
image_count,
|
image_count,
|
||||||
image_size,
|
image_size,
|
||||||
reasoning_effort,
|
reasoning_effort,
|
||||||
|
cache_ttl_overridden,
|
||||||
created_at
|
created_at
|
||||||
) VALUES (
|
) VALUES (
|
||||||
$1, $2, $3, $4, $5,
|
$1, $2, $3, $4, $5,
|
||||||
@@ -122,7 +123,7 @@ func (r *usageLogRepository) Create(ctx context.Context, log *service.UsageLog)
|
|||||||
$8, $9, $10, $11,
|
$8, $9, $10, $11,
|
||||||
$12, $13,
|
$12, $13,
|
||||||
$14, $15, $16, $17, $18, $19,
|
$14, $15, $16, $17, $18, $19,
|
||||||
$20, $21, $22, $23, $24, $25, $26, $27, $28, $29, $30, $31
|
$20, $21, $22, $23, $24, $25, $26, $27, $28, $29, $30, $31, $32
|
||||||
)
|
)
|
||||||
ON CONFLICT (request_id, api_key_id) DO NOTHING
|
ON CONFLICT (request_id, api_key_id) DO NOTHING
|
||||||
RETURNING id, created_at
|
RETURNING id, created_at
|
||||||
@@ -173,6 +174,7 @@ func (r *usageLogRepository) Create(ctx context.Context, log *service.UsageLog)
|
|||||||
log.ImageCount,
|
log.ImageCount,
|
||||||
imageSize,
|
imageSize,
|
||||||
reasoningEffort,
|
reasoningEffort,
|
||||||
|
log.CacheTTLOverridden,
|
||||||
createdAt,
|
createdAt,
|
||||||
}
|
}
|
||||||
if err := scanSingleRow(ctx, sqlq, query, args, &log.ID, &log.CreatedAt); err != nil {
|
if err := scanSingleRow(ctx, sqlq, query, args, &log.ID, &log.CreatedAt); err != nil {
|
||||||
@@ -2195,6 +2197,7 @@ func scanUsageLog(scanner interface{ Scan(...any) error }) (*service.UsageLog, e
|
|||||||
imageCount int
|
imageCount int
|
||||||
imageSize sql.NullString
|
imageSize sql.NullString
|
||||||
reasoningEffort sql.NullString
|
reasoningEffort sql.NullString
|
||||||
|
cacheTTLOverridden bool
|
||||||
createdAt time.Time
|
createdAt time.Time
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -2230,6 +2233,7 @@ func scanUsageLog(scanner interface{ Scan(...any) error }) (*service.UsageLog, e
|
|||||||
&imageCount,
|
&imageCount,
|
||||||
&imageSize,
|
&imageSize,
|
||||||
&reasoningEffort,
|
&reasoningEffort,
|
||||||
|
&cacheTTLOverridden,
|
||||||
&createdAt,
|
&createdAt,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -2258,6 +2262,7 @@ func scanUsageLog(scanner interface{ Scan(...any) error }) (*service.UsageLog, e
|
|||||||
BillingType: int8(billingType),
|
BillingType: int8(billingType),
|
||||||
Stream: stream,
|
Stream: stream,
|
||||||
ImageCount: imageCount,
|
ImageCount: imageCount,
|
||||||
|
CacheTTLOverridden: cacheTTLOverridden,
|
||||||
CreatedAt: createdAt,
|
CreatedAt: createdAt,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -401,6 +401,7 @@ func TestAPIContracts(t *testing.T) {
|
|||||||
"first_token_ms": 50,
|
"first_token_ms": 50,
|
||||||
"image_count": 0,
|
"image_count": 0,
|
||||||
"image_size": null,
|
"image_size": null,
|
||||||
|
"cache_ttl_overridden": false,
|
||||||
"created_at": "2025-01-02T03:04:05Z",
|
"created_at": "2025-01-02T03:04:05Z",
|
||||||
"user_agent": null
|
"user_agent": null
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -70,7 +70,15 @@ func CORS(cfg config.CORSConfig) gin.HandlerFunc {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, accept, origin, Cache-Control, X-Requested-With, X-API-Key")
|
allowHeaders := []string{"Content-Type", "Content-Length", "Accept-Encoding", "X-CSRF-Token", "Authorization", "accept", "origin", "Cache-Control", "X-Requested-With", "X-API-Key"}
|
||||||
|
|
||||||
|
// openai node sdk
|
||||||
|
openAIProperties := []string{"lang", "package-version", "os", "arch", "retry-count", "runtime", "runtime-version", "async", "helper-method", "poll-helper", "custom-poll-interval", "timeout"}
|
||||||
|
for _, prop := range openAIProperties {
|
||||||
|
allowHeaders = append(allowHeaders, "x-stainless-"+prop)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Writer.Header().Set("Access-Control-Allow-Headers", strings.Join(allowHeaders, ", "))
|
||||||
c.Writer.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS, GET, PUT, DELETE, PATCH")
|
c.Writer.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS, GET, PUT, DELETE, PATCH")
|
||||||
|
|
||||||
// 处理预检请求
|
// 处理预检请求
|
||||||
|
|||||||
@@ -752,6 +752,38 @@ func (a *Account) IsSessionIDMaskingEnabled() bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsCacheTTLOverrideEnabled 检查是否启用缓存 TTL 强制替换
|
||||||
|
// 仅适用于 Anthropic OAuth/SetupToken 类型账号
|
||||||
|
// 启用后将所有 cache creation tokens 归入指定的 TTL 类型(5m 或 1h)
|
||||||
|
func (a *Account) IsCacheTTLOverrideEnabled() bool {
|
||||||
|
if !a.IsAnthropicOAuthOrSetupToken() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if a.Extra == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if v, ok := a.Extra["cache_ttl_override_enabled"]; ok {
|
||||||
|
if enabled, ok := v.(bool); ok {
|
||||||
|
return enabled
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCacheTTLOverrideTarget 获取缓存 TTL 强制替换的目标类型
|
||||||
|
// 返回 "5m" 或 "1h",默认 "5m"
|
||||||
|
func (a *Account) GetCacheTTLOverrideTarget() string {
|
||||||
|
if a.Extra == nil {
|
||||||
|
return "5m"
|
||||||
|
}
|
||||||
|
if v, ok := a.Extra["cache_ttl_override_target"]; ok {
|
||||||
|
if target, ok := v.(string); ok && (target == "5m" || target == "1h") {
|
||||||
|
return target
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "5m"
|
||||||
|
}
|
||||||
|
|
||||||
// GetWindowCostLimit 获取 5h 窗口费用阈值(美元)
|
// GetWindowCostLimit 获取 5h 窗口费用阈值(美元)
|
||||||
// 返回 0 表示未启用
|
// 返回 0 表示未启用
|
||||||
func (a *Account) GetWindowCostLimit() float64 {
|
func (a *Account) GetWindowCostLimit() float64 {
|
||||||
|
|||||||
@@ -21,3 +21,72 @@ func TestMergeAnthropicBeta_EmptyIncoming(t *testing.T) {
|
|||||||
)
|
)
|
||||||
require.Equal(t, "oauth-2025-04-20,interleaved-thinking-2025-05-14", got)
|
require.Equal(t, "oauth-2025-04-20,interleaved-thinking-2025-05-14", got)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestStripBetaToken(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
header string
|
||||||
|
token string
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "token in middle",
|
||||||
|
header: "oauth-2025-04-20,context-1m-2025-08-07,interleaved-thinking-2025-05-14",
|
||||||
|
token: "context-1m-2025-08-07",
|
||||||
|
want: "oauth-2025-04-20,interleaved-thinking-2025-05-14",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "token at start",
|
||||||
|
header: "context-1m-2025-08-07,oauth-2025-04-20,interleaved-thinking-2025-05-14",
|
||||||
|
token: "context-1m-2025-08-07",
|
||||||
|
want: "oauth-2025-04-20,interleaved-thinking-2025-05-14",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "token at end",
|
||||||
|
header: "oauth-2025-04-20,interleaved-thinking-2025-05-14,context-1m-2025-08-07",
|
||||||
|
token: "context-1m-2025-08-07",
|
||||||
|
want: "oauth-2025-04-20,interleaved-thinking-2025-05-14",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "token not present",
|
||||||
|
header: "oauth-2025-04-20,interleaved-thinking-2025-05-14",
|
||||||
|
token: "context-1m-2025-08-07",
|
||||||
|
want: "oauth-2025-04-20,interleaved-thinking-2025-05-14",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty header",
|
||||||
|
header: "",
|
||||||
|
token: "context-1m-2025-08-07",
|
||||||
|
want: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "with spaces",
|
||||||
|
header: "oauth-2025-04-20, context-1m-2025-08-07 , interleaved-thinking-2025-05-14",
|
||||||
|
token: "context-1m-2025-08-07",
|
||||||
|
want: "oauth-2025-04-20,interleaved-thinking-2025-05-14",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "only token",
|
||||||
|
header: "context-1m-2025-08-07",
|
||||||
|
token: "context-1m-2025-08-07",
|
||||||
|
want: "",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got := stripBetaToken(tt.header, tt.token)
|
||||||
|
require.Equal(t, tt.want, got)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMergeAnthropicBetaDropping_Context1M(t *testing.T) {
|
||||||
|
required := []string{"oauth-2025-04-20", "interleaved-thinking-2025-05-14"}
|
||||||
|
incoming := "context-1m-2025-08-07,foo-beta,oauth-2025-04-20"
|
||||||
|
drop := map[string]struct{}{"context-1m-2025-08-07": {}}
|
||||||
|
|
||||||
|
got := mergeAnthropicBetaDropping(required, incoming, drop)
|
||||||
|
require.Equal(t, "oauth-2025-04-20,interleaved-thinking-2025-05-14,foo-beta", got)
|
||||||
|
require.NotContains(t, got, "context-1m-2025-08-07")
|
||||||
|
}
|
||||||
|
|||||||
@@ -3553,12 +3553,12 @@ func (s *GatewayService) buildUpstreamRequest(ctx context.Context, c *gin.Contex
|
|||||||
// messages requests typically use only oauth + interleaved-thinking.
|
// messages requests typically use only oauth + interleaved-thinking.
|
||||||
// Also drop claude-code beta if a downstream client added it.
|
// Also drop claude-code beta if a downstream client added it.
|
||||||
requiredBetas := []string{claude.BetaOAuth, claude.BetaInterleavedThinking}
|
requiredBetas := []string{claude.BetaOAuth, claude.BetaInterleavedThinking}
|
||||||
drop := map[string]struct{}{claude.BetaClaudeCode: {}}
|
drop := map[string]struct{}{claude.BetaClaudeCode: {}, claude.BetaContext1M: {}}
|
||||||
req.Header.Set("anthropic-beta", mergeAnthropicBetaDropping(requiredBetas, incomingBeta, drop))
|
req.Header.Set("anthropic-beta", mergeAnthropicBetaDropping(requiredBetas, incomingBeta, drop))
|
||||||
} else {
|
} else {
|
||||||
// Claude Code 客户端:尽量透传原始 header,仅补齐 oauth beta
|
// Claude Code 客户端:尽量透传原始 header,仅补齐 oauth beta
|
||||||
clientBetaHeader := req.Header.Get("anthropic-beta")
|
clientBetaHeader := req.Header.Get("anthropic-beta")
|
||||||
req.Header.Set("anthropic-beta", s.getBetaHeader(modelID, clientBetaHeader))
|
req.Header.Set("anthropic-beta", stripBetaToken(s.getBetaHeader(modelID, clientBetaHeader), claude.BetaContext1M))
|
||||||
}
|
}
|
||||||
} else if s.cfg != nil && s.cfg.Gateway.InjectBetaForAPIKey && req.Header.Get("anthropic-beta") == "" {
|
} else if s.cfg != nil && s.cfg.Gateway.InjectBetaForAPIKey && req.Header.Get("anthropic-beta") == "" {
|
||||||
// API-key:仅在请求显式使用 beta 特性且客户端未提供时,按需补齐(默认关闭)
|
// API-key:仅在请求显式使用 beta 特性且客户端未提供时,按需补齐(默认关闭)
|
||||||
@@ -3712,6 +3712,23 @@ func mergeAnthropicBetaDropping(required []string, incoming string, drop map[str
|
|||||||
return strings.Join(out, ",")
|
return strings.Join(out, ",")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// stripBetaToken removes a single beta token from a comma-separated header value.
|
||||||
|
// It short-circuits when the token is not present to avoid unnecessary allocations.
|
||||||
|
func stripBetaToken(header, token string) string {
|
||||||
|
if !strings.Contains(header, token) {
|
||||||
|
return header
|
||||||
|
}
|
||||||
|
out := make([]string, 0, 8)
|
||||||
|
for _, p := range strings.Split(header, ",") {
|
||||||
|
p = strings.TrimSpace(p)
|
||||||
|
if p == "" || p == token {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
out = append(out, p)
|
||||||
|
}
|
||||||
|
return strings.Join(out, ",")
|
||||||
|
}
|
||||||
|
|
||||||
// applyClaudeCodeMimicHeaders forces "Claude Code-like" request headers.
|
// applyClaudeCodeMimicHeaders forces "Claude Code-like" request headers.
|
||||||
// This mirrors opencode-anthropic-auth behavior: do not trust downstream
|
// This mirrors opencode-anthropic-auth behavior: do not trust downstream
|
||||||
// headers when using Claude Code-scoped OAuth credentials.
|
// headers when using Claude Code-scoped OAuth credentials.
|
||||||
@@ -4276,6 +4293,23 @@ func (s *GatewayService) handleStreamingResponse(ctx context.Context, resp *http
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Cache TTL Override: 重写 SSE 事件中的 cache_creation 分类
|
||||||
|
if account.IsCacheTTLOverrideEnabled() {
|
||||||
|
overrideTarget := account.GetCacheTTLOverrideTarget()
|
||||||
|
if eventType == "message_start" {
|
||||||
|
if msg, ok := event["message"].(map[string]any); ok {
|
||||||
|
if u, ok := msg["usage"].(map[string]any); ok {
|
||||||
|
rewriteCacheCreationJSON(u, overrideTarget)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if eventType == "message_delta" {
|
||||||
|
if u, ok := event["usage"].(map[string]any); ok {
|
||||||
|
rewriteCacheCreationJSON(u, overrideTarget)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if needModelReplace {
|
if needModelReplace {
|
||||||
if msg, ok := event["message"].(map[string]any); ok {
|
if msg, ok := event["message"].(map[string]any); ok {
|
||||||
if model, ok := msg["model"].(string); ok && model == mappedModel {
|
if model, ok := msg["model"].(string); ok && model == mappedModel {
|
||||||
@@ -4450,6 +4484,58 @@ func (s *GatewayService) parseSSEUsage(data string, usage *ClaudeUsage) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// applyCacheTTLOverride 将所有 cache creation tokens 归入指定的 TTL 类型。
|
||||||
|
// target 为 "5m" 或 "1h"。返回 true 表示发生了变更。
|
||||||
|
func applyCacheTTLOverride(usage *ClaudeUsage, target string) bool {
|
||||||
|
// Fallback: 如果只有聚合字段但无 5m/1h 明细,将聚合字段归入 5m 默认类别
|
||||||
|
if usage.CacheCreation5mTokens == 0 && usage.CacheCreation1hTokens == 0 && usage.CacheCreationInputTokens > 0 {
|
||||||
|
usage.CacheCreation5mTokens = usage.CacheCreationInputTokens
|
||||||
|
}
|
||||||
|
|
||||||
|
total := usage.CacheCreation5mTokens + usage.CacheCreation1hTokens
|
||||||
|
if total == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
switch target {
|
||||||
|
case "1h":
|
||||||
|
if usage.CacheCreation1hTokens == total {
|
||||||
|
return false // 已经全是 1h
|
||||||
|
}
|
||||||
|
usage.CacheCreation1hTokens = total
|
||||||
|
usage.CacheCreation5mTokens = 0
|
||||||
|
default: // "5m"
|
||||||
|
if usage.CacheCreation5mTokens == total {
|
||||||
|
return false // 已经全是 5m
|
||||||
|
}
|
||||||
|
usage.CacheCreation5mTokens = total
|
||||||
|
usage.CacheCreation1hTokens = 0
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// rewriteCacheCreationJSON 在 JSON usage 对象中重写 cache_creation 嵌套对象的 TTL 分类。
|
||||||
|
// usageObj 是 usage JSON 对象(map[string]any)。
|
||||||
|
func rewriteCacheCreationJSON(usageObj map[string]any, target string) {
|
||||||
|
ccObj, ok := usageObj["cache_creation"].(map[string]any)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
v5m, _ := ccObj["ephemeral_5m_input_tokens"].(float64)
|
||||||
|
v1h, _ := ccObj["ephemeral_1h_input_tokens"].(float64)
|
||||||
|
total := v5m + v1h
|
||||||
|
if total == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch target {
|
||||||
|
case "1h":
|
||||||
|
ccObj["ephemeral_1h_input_tokens"] = total
|
||||||
|
ccObj["ephemeral_5m_input_tokens"] = float64(0)
|
||||||
|
default: // "5m"
|
||||||
|
ccObj["ephemeral_5m_input_tokens"] = total
|
||||||
|
ccObj["ephemeral_1h_input_tokens"] = float64(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (s *GatewayService) handleNonStreamingResponse(ctx context.Context, resp *http.Response, c *gin.Context, account *Account, originalModel, mappedModel string) (*ClaudeUsage, error) {
|
func (s *GatewayService) handleNonStreamingResponse(ctx context.Context, resp *http.Response, c *gin.Context, account *Account, originalModel, mappedModel string) (*ClaudeUsage, error) {
|
||||||
// 更新5h窗口状态
|
// 更新5h窗口状态
|
||||||
s.rateLimitService.UpdateSessionWindow(ctx, account, resp.Header)
|
s.rateLimitService.UpdateSessionWindow(ctx, account, resp.Header)
|
||||||
@@ -4486,6 +4572,20 @@ func (s *GatewayService) handleNonStreamingResponse(ctx context.Context, resp *h
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Cache TTL Override: 重写 non-streaming 响应中的 cache_creation 分类
|
||||||
|
if account.IsCacheTTLOverrideEnabled() {
|
||||||
|
overrideTarget := account.GetCacheTTLOverrideTarget()
|
||||||
|
if applyCacheTTLOverride(&response.Usage, overrideTarget) {
|
||||||
|
// 同步更新 body JSON 中的嵌套 cache_creation 对象
|
||||||
|
if newBody, err := sjson.SetBytes(body, "usage.cache_creation.ephemeral_5m_input_tokens", response.Usage.CacheCreation5mTokens); err == nil {
|
||||||
|
body = newBody
|
||||||
|
}
|
||||||
|
if newBody, err := sjson.SetBytes(body, "usage.cache_creation.ephemeral_1h_input_tokens", response.Usage.CacheCreation1hTokens); err == nil {
|
||||||
|
body = newBody
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 如果有模型映射,替换响应中的model字段
|
// 如果有模型映射,替换响应中的model字段
|
||||||
if originalModel != mappedModel {
|
if originalModel != mappedModel {
|
||||||
body = s.replaceModelInResponseBody(body, mappedModel, originalModel)
|
body = s.replaceModelInResponseBody(body, mappedModel, originalModel)
|
||||||
@@ -4562,6 +4662,13 @@ func (s *GatewayService) RecordUsage(ctx context.Context, input *RecordUsageInpu
|
|||||||
result.Usage.InputTokens = 0
|
result.Usage.InputTokens = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Cache TTL Override: 确保计费时 token 分类与账号设置一致
|
||||||
|
cacheTTLOverridden := false
|
||||||
|
if account.IsCacheTTLOverrideEnabled() {
|
||||||
|
applyCacheTTLOverride(&result.Usage, account.GetCacheTTLOverrideTarget())
|
||||||
|
cacheTTLOverridden = (result.Usage.CacheCreation5mTokens + result.Usage.CacheCreation1hTokens) > 0
|
||||||
|
}
|
||||||
|
|
||||||
// 获取费率倍数(优先级:用户专属 > 分组默认 > 系统默认)
|
// 获取费率倍数(优先级:用户专属 > 分组默认 > 系统默认)
|
||||||
multiplier := s.cfg.Default.RateMultiplier
|
multiplier := s.cfg.Default.RateMultiplier
|
||||||
if apiKey.GroupID != nil && apiKey.Group != nil {
|
if apiKey.GroupID != nil && apiKey.Group != nil {
|
||||||
@@ -4647,6 +4754,7 @@ func (s *GatewayService) RecordUsage(ctx context.Context, input *RecordUsageInpu
|
|||||||
FirstTokenMs: result.FirstTokenMs,
|
FirstTokenMs: result.FirstTokenMs,
|
||||||
ImageCount: result.ImageCount,
|
ImageCount: result.ImageCount,
|
||||||
ImageSize: imageSize,
|
ImageSize: imageSize,
|
||||||
|
CacheTTLOverridden: cacheTTLOverridden,
|
||||||
CreatedAt: time.Now(),
|
CreatedAt: time.Now(),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4747,6 +4855,13 @@ func (s *GatewayService) RecordUsageWithLongContext(ctx context.Context, input *
|
|||||||
result.Usage.InputTokens = 0
|
result.Usage.InputTokens = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Cache TTL Override: 确保计费时 token 分类与账号设置一致
|
||||||
|
cacheTTLOverridden := false
|
||||||
|
if account.IsCacheTTLOverrideEnabled() {
|
||||||
|
applyCacheTTLOverride(&result.Usage, account.GetCacheTTLOverrideTarget())
|
||||||
|
cacheTTLOverridden = (result.Usage.CacheCreation5mTokens + result.Usage.CacheCreation1hTokens) > 0
|
||||||
|
}
|
||||||
|
|
||||||
// 获取费率倍数(优先级:用户专属 > 分组默认 > 系统默认)
|
// 获取费率倍数(优先级:用户专属 > 分组默认 > 系统默认)
|
||||||
multiplier := s.cfg.Default.RateMultiplier
|
multiplier := s.cfg.Default.RateMultiplier
|
||||||
if apiKey.GroupID != nil && apiKey.Group != nil {
|
if apiKey.GroupID != nil && apiKey.Group != nil {
|
||||||
@@ -4832,6 +4947,7 @@ func (s *GatewayService) RecordUsageWithLongContext(ctx context.Context, input *
|
|||||||
FirstTokenMs: result.FirstTokenMs,
|
FirstTokenMs: result.FirstTokenMs,
|
||||||
ImageCount: result.ImageCount,
|
ImageCount: result.ImageCount,
|
||||||
ImageSize: imageSize,
|
ImageSize: imageSize,
|
||||||
|
CacheTTLOverridden: cacheTTLOverridden,
|
||||||
CreatedAt: time.Now(),
|
CreatedAt: time.Now(),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -5137,7 +5253,8 @@ func (s *GatewayService) buildCountTokensRequest(ctx context.Context, c *gin.Con
|
|||||||
|
|
||||||
incomingBeta := req.Header.Get("anthropic-beta")
|
incomingBeta := req.Header.Get("anthropic-beta")
|
||||||
requiredBetas := []string{claude.BetaClaudeCode, claude.BetaOAuth, claude.BetaInterleavedThinking, claude.BetaTokenCounting}
|
requiredBetas := []string{claude.BetaClaudeCode, claude.BetaOAuth, claude.BetaInterleavedThinking, claude.BetaTokenCounting}
|
||||||
req.Header.Set("anthropic-beta", mergeAnthropicBeta(requiredBetas, incomingBeta))
|
drop := map[string]struct{}{claude.BetaContext1M: {}}
|
||||||
|
req.Header.Set("anthropic-beta", mergeAnthropicBetaDropping(requiredBetas, incomingBeta, drop))
|
||||||
} else {
|
} else {
|
||||||
clientBetaHeader := req.Header.Get("anthropic-beta")
|
clientBetaHeader := req.Header.Get("anthropic-beta")
|
||||||
if clientBetaHeader == "" {
|
if clientBetaHeader == "" {
|
||||||
@@ -5147,7 +5264,7 @@ func (s *GatewayService) buildCountTokensRequest(ctx context.Context, c *gin.Con
|
|||||||
if !strings.Contains(beta, claude.BetaTokenCounting) {
|
if !strings.Contains(beta, claude.BetaTokenCounting) {
|
||||||
beta = beta + "," + claude.BetaTokenCounting
|
beta = beta + "," + claude.BetaTokenCounting
|
||||||
}
|
}
|
||||||
req.Header.Set("anthropic-beta", beta)
|
req.Header.Set("anthropic-beta", stripBetaToken(beta, claude.BetaContext1M))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if s.cfg != nil && s.cfg.Gateway.InjectBetaForAPIKey && req.Header.Get("anthropic-beta") == "" {
|
} else if s.cfg != nil && s.cfg.Gateway.InjectBetaForAPIKey && req.Header.Get("anthropic-beta") == "" {
|
||||||
|
|||||||
@@ -112,13 +112,19 @@ func applyCodexOAuthTransform(reqBody map[string]any, isCodexCLI bool) codexTran
|
|||||||
result.Modified = true
|
result.Modified = true
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, ok := reqBody["max_output_tokens"]; ok {
|
// Strip parameters unsupported by codex models via the Responses API.
|
||||||
delete(reqBody, "max_output_tokens")
|
for _, key := range []string{
|
||||||
result.Modified = true
|
"max_output_tokens",
|
||||||
}
|
"max_completion_tokens",
|
||||||
if _, ok := reqBody["max_completion_tokens"]; ok {
|
"temperature",
|
||||||
delete(reqBody, "max_completion_tokens")
|
"top_p",
|
||||||
result.Modified = true
|
"frequency_penalty",
|
||||||
|
"presence_penalty",
|
||||||
|
} {
|
||||||
|
if _, ok := reqBody[key]; ok {
|
||||||
|
delete(reqBody, key)
|
||||||
|
result.Modified = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if normalizeCodexTools(reqBody) {
|
if normalizeCodexTools(reqBody) {
|
||||||
|
|||||||
@@ -46,6 +46,9 @@ type UsageLog struct {
|
|||||||
UserAgent *string
|
UserAgent *string
|
||||||
IPAddress *string
|
IPAddress *string
|
||||||
|
|
||||||
|
// Cache TTL Override 标记(管理员强制替换了缓存 TTL 计费)
|
||||||
|
CacheTTLOverridden bool
|
||||||
|
|
||||||
// 图片生成字段
|
// 图片生成字段
|
||||||
ImageCount int
|
ImageCount int
|
||||||
ImageSize *string
|
ImageSize *string
|
||||||
|
|||||||
2
backend/migrations/055_add_cache_ttl_overridden.sql
Normal file
2
backend/migrations/055_add_cache_ttl_overridden.sql
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
-- Add cache_ttl_overridden flag to usage_logs for tracking cache TTL override per account.
|
||||||
|
ALTER TABLE usage_logs ADD COLUMN IF NOT EXISTS cache_ttl_overridden BOOLEAN NOT NULL DEFAULT FALSE;
|
||||||
@@ -708,6 +708,7 @@ const groupIds = ref<number[]>([])
|
|||||||
// All models list (combined Anthropic + OpenAI)
|
// All models list (combined Anthropic + OpenAI)
|
||||||
const allModels = [
|
const allModels = [
|
||||||
{ value: 'claude-opus-4-6', label: 'Claude Opus 4.6' },
|
{ value: 'claude-opus-4-6', label: 'Claude Opus 4.6' },
|
||||||
|
{ value: 'claude-sonnet-4-6', label: 'Claude Sonnet 4.6' },
|
||||||
{ value: 'claude-opus-4-5-20251101', label: 'Claude Opus 4.5' },
|
{ value: 'claude-opus-4-5-20251101', label: 'Claude Opus 4.5' },
|
||||||
{ value: 'claude-sonnet-4-20250514', label: 'Claude Sonnet 4' },
|
{ value: 'claude-sonnet-4-20250514', label: 'Claude Sonnet 4' },
|
||||||
{ value: 'claude-sonnet-4-5-20250929', label: 'Claude Sonnet 4.5' },
|
{ value: 'claude-sonnet-4-5-20250929', label: 'Claude Sonnet 4.5' },
|
||||||
@@ -754,6 +755,13 @@ const presetMappings = [
|
|||||||
color:
|
color:
|
||||||
'bg-purple-100 text-purple-700 hover:bg-purple-200 dark:bg-purple-900/30 dark:text-purple-400'
|
'bg-purple-100 text-purple-700 hover:bg-purple-200 dark:bg-purple-900/30 dark:text-purple-400'
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: 'Sonnet 4.6',
|
||||||
|
from: 'claude-sonnet-4-6',
|
||||||
|
to: 'claude-sonnet-4-6',
|
||||||
|
color:
|
||||||
|
'bg-purple-100 text-purple-700 hover:bg-purple-200 dark:bg-purple-900/30 dark:text-purple-400'
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: 'Opus->Sonnet',
|
label: 'Opus->Sonnet',
|
||||||
from: 'claude-opus-4-5-20251101',
|
from: 'claude-opus-4-5-20251101',
|
||||||
|
|||||||
@@ -1527,6 +1527,46 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Cache TTL Override -->
|
||||||
|
<div class="rounded-lg border border-gray-200 p-4 dark:border-dark-600">
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<div>
|
||||||
|
<label class="input-label mb-0">{{ t('admin.accounts.quotaControl.cacheTTLOverride.label') }}</label>
|
||||||
|
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">
|
||||||
|
{{ t('admin.accounts.quotaControl.cacheTTLOverride.hint') }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
@click="cacheTTLOverrideEnabled = !cacheTTLOverrideEnabled"
|
||||||
|
:class="[
|
||||||
|
'relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2',
|
||||||
|
cacheTTLOverrideEnabled ? 'bg-primary-600' : 'bg-gray-200 dark:bg-dark-600'
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
:class="[
|
||||||
|
'pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out',
|
||||||
|
cacheTTLOverrideEnabled ? 'translate-x-5' : 'translate-x-0'
|
||||||
|
]"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div v-if="cacheTTLOverrideEnabled" class="mt-3">
|
||||||
|
<label class="input-label text-xs">{{ t('admin.accounts.quotaControl.cacheTTLOverride.target') }}</label>
|
||||||
|
<select
|
||||||
|
v-model="cacheTTLOverrideTarget"
|
||||||
|
class="mt-1 block w-full rounded-md border border-gray-300 bg-white px-3 py-2 text-sm shadow-sm focus:border-primary-500 focus:outline-none focus:ring-1 focus:ring-primary-500 dark:border-dark-500 dark:bg-dark-700 dark:text-white"
|
||||||
|
>
|
||||||
|
<option value="5m">5m</option>
|
||||||
|
<option value="1h">1h</option>
|
||||||
|
</select>
|
||||||
|
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">
|
||||||
|
{{ t('admin.accounts.quotaControl.cacheTTLOverride.targetHint') }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
@@ -2146,6 +2186,8 @@ const maxSessions = ref<number | null>(null)
|
|||||||
const sessionIdleTimeout = ref<number | null>(null)
|
const sessionIdleTimeout = ref<number | null>(null)
|
||||||
const tlsFingerprintEnabled = ref(false)
|
const tlsFingerprintEnabled = ref(false)
|
||||||
const sessionIdMaskingEnabled = ref(false)
|
const sessionIdMaskingEnabled = ref(false)
|
||||||
|
const cacheTTLOverrideEnabled = ref(false)
|
||||||
|
const cacheTTLOverrideTarget = ref<string>('5m')
|
||||||
|
|
||||||
// Gemini tier selection (used as fallback when auto-detection is unavailable/fails)
|
// Gemini tier selection (used as fallback when auto-detection is unavailable/fails)
|
||||||
const geminiTierGoogleOne = ref<'google_one_free' | 'google_ai_pro' | 'google_ai_ultra'>('google_one_free')
|
const geminiTierGoogleOne = ref<'google_one_free' | 'google_ai_pro' | 'google_ai_ultra'>('google_one_free')
|
||||||
@@ -2597,6 +2639,8 @@ const resetForm = () => {
|
|||||||
sessionIdleTimeout.value = null
|
sessionIdleTimeout.value = null
|
||||||
tlsFingerprintEnabled.value = false
|
tlsFingerprintEnabled.value = false
|
||||||
sessionIdMaskingEnabled.value = false
|
sessionIdMaskingEnabled.value = false
|
||||||
|
cacheTTLOverrideEnabled.value = false
|
||||||
|
cacheTTLOverrideTarget.value = '5m'
|
||||||
antigravityAccountType.value = 'oauth'
|
antigravityAccountType.value = 'oauth'
|
||||||
upstreamBaseUrl.value = ''
|
upstreamBaseUrl.value = ''
|
||||||
upstreamApiKey.value = ''
|
upstreamApiKey.value = ''
|
||||||
@@ -3174,6 +3218,12 @@ const handleAnthropicExchange = async (authCode: string) => {
|
|||||||
extra.session_id_masking_enabled = true
|
extra.session_id_masking_enabled = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add cache TTL override settings
|
||||||
|
if (cacheTTLOverrideEnabled.value) {
|
||||||
|
extra.cache_ttl_override_enabled = true
|
||||||
|
extra.cache_ttl_override_target = cacheTTLOverrideTarget.value
|
||||||
|
}
|
||||||
|
|
||||||
const credentials = {
|
const credentials = {
|
||||||
...tokenInfo,
|
...tokenInfo,
|
||||||
...(interceptWarmupRequests.value ? { intercept_warmup_requests: true } : {})
|
...(interceptWarmupRequests.value ? { intercept_warmup_requests: true } : {})
|
||||||
@@ -3267,6 +3317,12 @@ const handleCookieAuth = async (sessionKey: string) => {
|
|||||||
extra.session_id_masking_enabled = true
|
extra.session_id_masking_enabled = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add cache TTL override settings
|
||||||
|
if (cacheTTLOverrideEnabled.value) {
|
||||||
|
extra.cache_ttl_override_enabled = true
|
||||||
|
extra.cache_ttl_override_target = cacheTTLOverrideTarget.value
|
||||||
|
}
|
||||||
|
|
||||||
const accountName = keys.length > 1 ? `${form.name} #${i + 1}` : form.name
|
const accountName = keys.length > 1 ? `${form.name} #${i + 1}` : form.name
|
||||||
|
|
||||||
// Merge interceptWarmupRequests into credentials
|
// Merge interceptWarmupRequests into credentials
|
||||||
|
|||||||
@@ -904,6 +904,46 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Cache TTL Override -->
|
||||||
|
<div class="rounded-lg border border-gray-200 p-4 dark:border-dark-600">
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<div>
|
||||||
|
<label class="input-label mb-0">{{ t('admin.accounts.quotaControl.cacheTTLOverride.label') }}</label>
|
||||||
|
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">
|
||||||
|
{{ t('admin.accounts.quotaControl.cacheTTLOverride.hint') }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
@click="cacheTTLOverrideEnabled = !cacheTTLOverrideEnabled"
|
||||||
|
:class="[
|
||||||
|
'relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2',
|
||||||
|
cacheTTLOverrideEnabled ? 'bg-primary-600' : 'bg-gray-200 dark:bg-dark-600'
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
:class="[
|
||||||
|
'pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out',
|
||||||
|
cacheTTLOverrideEnabled ? 'translate-x-5' : 'translate-x-0'
|
||||||
|
]"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div v-if="cacheTTLOverrideEnabled" class="mt-3">
|
||||||
|
<label class="input-label text-xs">{{ t('admin.accounts.quotaControl.cacheTTLOverride.target') }}</label>
|
||||||
|
<select
|
||||||
|
v-model="cacheTTLOverrideTarget"
|
||||||
|
class="mt-1 block w-full rounded-md border border-gray-300 bg-white px-3 py-2 text-sm shadow-sm focus:border-primary-500 focus:outline-none focus:ring-1 focus:ring-primary-500 dark:border-dark-500 dark:bg-dark-700 dark:text-white"
|
||||||
|
>
|
||||||
|
<option value="5m">5m</option>
|
||||||
|
<option value="1h">1h</option>
|
||||||
|
</select>
|
||||||
|
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">
|
||||||
|
{{ t('admin.accounts.quotaControl.cacheTTLOverride.targetHint') }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="border-t border-gray-200 pt-4 dark:border-dark-600">
|
<div class="border-t border-gray-200 pt-4 dark:border-dark-600">
|
||||||
@@ -1102,6 +1142,8 @@ const maxSessions = ref<number | null>(null)
|
|||||||
const sessionIdleTimeout = ref<number | null>(null)
|
const sessionIdleTimeout = ref<number | null>(null)
|
||||||
const tlsFingerprintEnabled = ref(false)
|
const tlsFingerprintEnabled = ref(false)
|
||||||
const sessionIdMaskingEnabled = ref(false)
|
const sessionIdMaskingEnabled = ref(false)
|
||||||
|
const cacheTTLOverrideEnabled = ref(false)
|
||||||
|
const cacheTTLOverrideTarget = ref<string>('5m')
|
||||||
|
|
||||||
// Computed: current preset mappings based on platform
|
// Computed: current preset mappings based on platform
|
||||||
const presetMappings = computed(() => getPresetMappingsByPlatform(props.account?.platform || 'anthropic'))
|
const presetMappings = computed(() => getPresetMappingsByPlatform(props.account?.platform || 'anthropic'))
|
||||||
@@ -1489,6 +1531,8 @@ function loadQuotaControlSettings(account: Account) {
|
|||||||
sessionIdleTimeout.value = null
|
sessionIdleTimeout.value = null
|
||||||
tlsFingerprintEnabled.value = false
|
tlsFingerprintEnabled.value = false
|
||||||
sessionIdMaskingEnabled.value = false
|
sessionIdMaskingEnabled.value = false
|
||||||
|
cacheTTLOverrideEnabled.value = false
|
||||||
|
cacheTTLOverrideTarget.value = '5m'
|
||||||
|
|
||||||
// Only applies to Anthropic OAuth/SetupToken accounts
|
// Only applies to Anthropic OAuth/SetupToken accounts
|
||||||
if (account.platform !== 'anthropic' || (account.type !== 'oauth' && account.type !== 'setup-token')) {
|
if (account.platform !== 'anthropic' || (account.type !== 'oauth' && account.type !== 'setup-token')) {
|
||||||
@@ -1517,6 +1561,12 @@ function loadQuotaControlSettings(account: Account) {
|
|||||||
if (account.session_id_masking_enabled === true) {
|
if (account.session_id_masking_enabled === true) {
|
||||||
sessionIdMaskingEnabled.value = true
|
sessionIdMaskingEnabled.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Load cache TTL override setting
|
||||||
|
if (account.cache_ttl_override_enabled === true) {
|
||||||
|
cacheTTLOverrideEnabled.value = true
|
||||||
|
cacheTTLOverrideTarget.value = account.cache_ttl_override_target || '5m'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatTempUnschedKeywords(value: unknown) {
|
function formatTempUnschedKeywords(value: unknown) {
|
||||||
@@ -1723,6 +1773,15 @@ const handleSubmit = async () => {
|
|||||||
delete newExtra.session_id_masking_enabled
|
delete newExtra.session_id_masking_enabled
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Cache TTL override setting
|
||||||
|
if (cacheTTLOverrideEnabled.value) {
|
||||||
|
newExtra.cache_ttl_override_enabled = true
|
||||||
|
newExtra.cache_ttl_override_target = cacheTTLOverrideTarget.value
|
||||||
|
} else {
|
||||||
|
delete newExtra.cache_ttl_override_enabled
|
||||||
|
delete newExtra.cache_ttl_override_target
|
||||||
|
}
|
||||||
|
|
||||||
updatePayload.extra = newExtra
|
updatePayload.extra = newExtra
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -71,6 +71,7 @@
|
|||||||
<svg class="h-3.5 w-3.5 text-amber-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" /></svg>
|
<svg class="h-3.5 w-3.5 text-amber-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" /></svg>
|
||||||
<span class="font-medium text-amber-600 dark:text-amber-400">{{ formatCacheTokens(row.cache_creation_tokens) }}</span>
|
<span class="font-medium text-amber-600 dark:text-amber-400">{{ formatCacheTokens(row.cache_creation_tokens) }}</span>
|
||||||
<span v-if="row.cache_creation_1h_tokens > 0" class="inline-flex items-center rounded px-1 py-px text-[10px] font-medium leading-tight bg-orange-100 text-orange-600 ring-1 ring-inset ring-orange-200 dark:bg-orange-500/20 dark:text-orange-400 dark:ring-orange-500/30">1h</span>
|
<span v-if="row.cache_creation_1h_tokens > 0" class="inline-flex items-center rounded px-1 py-px text-[10px] font-medium leading-tight bg-orange-100 text-orange-600 ring-1 ring-inset ring-orange-200 dark:bg-orange-500/20 dark:text-orange-400 dark:ring-orange-500/30">1h</span>
|
||||||
|
<span v-if="row.cache_ttl_overridden" :title="t('usage.cacheTtlOverriddenHint')" class="inline-flex items-center rounded px-1 py-px text-[10px] font-medium leading-tight bg-rose-100 text-rose-600 ring-1 ring-inset ring-rose-200 dark:bg-rose-500/20 dark:text-rose-400 dark:ring-rose-500/30 cursor-help">R</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -182,6 +183,13 @@
|
|||||||
<span class="font-medium text-white">{{ tokenTooltipData.cache_creation_tokens.toLocaleString() }}</span>
|
<span class="font-medium text-white">{{ tokenTooltipData.cache_creation_tokens.toLocaleString() }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div v-if="tokenTooltipData && tokenTooltipData.cache_ttl_overridden" class="flex items-center justify-between gap-4">
|
||||||
|
<span class="text-gray-400 flex items-center gap-1.5">
|
||||||
|
{{ t('usage.cacheTtlOverriddenLabel') }}
|
||||||
|
<span class="inline-flex items-center rounded px-1 py-px text-[10px] font-medium leading-tight bg-rose-500/20 text-rose-400 ring-1 ring-inset ring-rose-500/30">R-{{ tokenTooltipData.cache_creation_1h_tokens > 0 ? '5m' : '1H' }}</span>
|
||||||
|
</span>
|
||||||
|
<span class="font-medium text-rose-400">{{ tokenTooltipData.cache_creation_1h_tokens > 0 ? t('usage.cacheTtlOverridden1h') : t('usage.cacheTtlOverridden5m') }}</span>
|
||||||
|
</div>
|
||||||
<div v-if="tokenTooltipData && tokenTooltipData.cache_read_tokens > 0" class="flex items-center justify-between gap-4">
|
<div v-if="tokenTooltipData && tokenTooltipData.cache_read_tokens > 0" class="flex items-center justify-between gap-4">
|
||||||
<span class="text-gray-400">{{ t('admin.usage.cacheReadTokens') }}</span>
|
<span class="text-gray-400">{{ t('admin.usage.cacheReadTokens') }}</span>
|
||||||
<span class="font-medium text-white">{{ tokenTooltipData.cache_read_tokens.toLocaleString() }}</span>
|
<span class="font-medium text-white">{{ tokenTooltipData.cache_read_tokens.toLocaleString() }}</span>
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ export const claudeModels = [
|
|||||||
'claude-sonnet-4-5-20250929', 'claude-haiku-4-5-20251001',
|
'claude-sonnet-4-5-20250929', 'claude-haiku-4-5-20251001',
|
||||||
'claude-opus-4-5-20251101',
|
'claude-opus-4-5-20251101',
|
||||||
'claude-opus-4-6',
|
'claude-opus-4-6',
|
||||||
|
'claude-sonnet-4-6',
|
||||||
'claude-2.1', 'claude-2.0', 'claude-instant-1.2'
|
'claude-2.1', 'claude-2.0', 'claude-instant-1.2'
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -233,6 +234,7 @@ export const allModels = allModelsList.map(m => ({ value: m, label: m }))
|
|||||||
const anthropicPresetMappings = [
|
const anthropicPresetMappings = [
|
||||||
{ label: 'Sonnet 4', from: 'claude-sonnet-4-20250514', to: 'claude-sonnet-4-20250514', color: 'bg-blue-100 text-blue-700 hover:bg-blue-200 dark:bg-blue-900/30 dark:text-blue-400' },
|
{ label: 'Sonnet 4', from: 'claude-sonnet-4-20250514', to: 'claude-sonnet-4-20250514', color: 'bg-blue-100 text-blue-700 hover:bg-blue-200 dark:bg-blue-900/30 dark:text-blue-400' },
|
||||||
{ label: 'Sonnet 4.5', from: 'claude-sonnet-4-5-20250929', to: 'claude-sonnet-4-5-20250929', color: 'bg-indigo-100 text-indigo-700 hover:bg-indigo-200 dark:bg-indigo-900/30 dark:text-indigo-400' },
|
{ label: 'Sonnet 4.5', from: 'claude-sonnet-4-5-20250929', to: 'claude-sonnet-4-5-20250929', color: 'bg-indigo-100 text-indigo-700 hover:bg-indigo-200 dark:bg-indigo-900/30 dark:text-indigo-400' },
|
||||||
|
{ label: 'Sonnet 4.6', from: 'claude-sonnet-4-6', to: 'claude-sonnet-4-6', color: 'bg-indigo-100 text-indigo-700 hover:bg-indigo-200 dark:bg-indigo-900/30 dark:text-indigo-400' },
|
||||||
{ label: 'Opus 4.5', from: 'claude-opus-4-5-20251101', to: 'claude-opus-4-5-20251101', color: 'bg-purple-100 text-purple-700 hover:bg-purple-200 dark:bg-purple-900/30 dark:text-purple-400' },
|
{ label: 'Opus 4.5', from: 'claude-opus-4-5-20251101', to: 'claude-opus-4-5-20251101', color: 'bg-purple-100 text-purple-700 hover:bg-purple-200 dark:bg-purple-900/30 dark:text-purple-400' },
|
||||||
{ label: 'Opus 4.6', from: 'claude-opus-4-6', to: 'claude-opus-4-6', color: 'bg-purple-100 text-purple-700 hover:bg-purple-200 dark:bg-purple-900/30 dark:text-purple-400' },
|
{ label: 'Opus 4.6', from: 'claude-opus-4-6', to: 'claude-opus-4-6', color: 'bg-purple-100 text-purple-700 hover:bg-purple-200 dark:bg-purple-900/30 dark:text-purple-400' },
|
||||||
{ label: 'Haiku 3.5', from: 'claude-3-5-haiku-20241022', to: 'claude-3-5-haiku-20241022', color: 'bg-green-100 text-green-700 hover:bg-green-200 dark:bg-green-900/30 dark:text-green-400' },
|
{ label: 'Haiku 3.5', from: 'claude-3-5-haiku-20241022', to: 'claude-3-5-haiku-20241022', color: 'bg-green-100 text-green-700 hover:bg-green-200 dark:bg-green-900/30 dark:text-green-400' },
|
||||||
|
|||||||
@@ -576,6 +576,10 @@ export default {
|
|||||||
description: 'View and analyze your API usage history',
|
description: 'View and analyze your API usage history',
|
||||||
costDetails: 'Cost Breakdown',
|
costDetails: 'Cost Breakdown',
|
||||||
tokenDetails: 'Token Breakdown',
|
tokenDetails: 'Token Breakdown',
|
||||||
|
cacheTtlOverriddenHint: 'Cache TTL Override enabled',
|
||||||
|
cacheTtlOverriddenLabel: 'TTL Override',
|
||||||
|
cacheTtlOverridden5m: 'Billed as 5m',
|
||||||
|
cacheTtlOverridden1h: 'Billed as 1h',
|
||||||
totalRequests: 'Total Requests',
|
totalRequests: 'Total Requests',
|
||||||
totalTokens: 'Total Tokens',
|
totalTokens: 'Total Tokens',
|
||||||
totalCost: 'Total Cost',
|
totalCost: 'Total Cost',
|
||||||
@@ -1595,6 +1599,12 @@ export default {
|
|||||||
sessionIdMasking: {
|
sessionIdMasking: {
|
||||||
label: 'Session ID Masking',
|
label: 'Session ID Masking',
|
||||||
hint: 'When enabled, fixes the session ID in metadata.user_id for 15 minutes, making upstream think requests come from the same session'
|
hint: 'When enabled, fixes the session ID in metadata.user_id for 15 minutes, making upstream think requests come from the same session'
|
||||||
|
},
|
||||||
|
cacheTTLOverride: {
|
||||||
|
label: 'Cache TTL Override',
|
||||||
|
hint: 'Force all cache creation tokens to be billed as the selected TTL tier (5m or 1h)',
|
||||||
|
target: 'Target TTL',
|
||||||
|
targetHint: 'Select the TTL tier for billing'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
expired: 'Expired',
|
expired: 'Expired',
|
||||||
|
|||||||
@@ -582,6 +582,10 @@ export default {
|
|||||||
description: '查看和分析您的 API 使用历史',
|
description: '查看和分析您的 API 使用历史',
|
||||||
costDetails: '成本明细',
|
costDetails: '成本明细',
|
||||||
tokenDetails: 'Token 明细',
|
tokenDetails: 'Token 明细',
|
||||||
|
cacheTtlOverriddenHint: '缓存 TTL Override 已启用',
|
||||||
|
cacheTtlOverriddenLabel: 'TTL 替换',
|
||||||
|
cacheTtlOverridden5m: '按 5m 计费',
|
||||||
|
cacheTtlOverridden1h: '按 1h 计费',
|
||||||
totalRequests: '总请求数',
|
totalRequests: '总请求数',
|
||||||
totalTokens: '总 Token',
|
totalTokens: '总 Token',
|
||||||
totalCost: '总消费',
|
totalCost: '总消费',
|
||||||
@@ -1741,6 +1745,12 @@ export default {
|
|||||||
sessionIdMasking: {
|
sessionIdMasking: {
|
||||||
label: '会话 ID 伪装',
|
label: '会话 ID 伪装',
|
||||||
hint: '启用后将在 15 分钟内固定 metadata.user_id 中的 session ID,使上游认为请求来自同一会话'
|
hint: '启用后将在 15 分钟内固定 metadata.user_id 中的 session ID,使上游认为请求来自同一会话'
|
||||||
|
},
|
||||||
|
cacheTTLOverride: {
|
||||||
|
label: '缓存 TTL 强制替换',
|
||||||
|
hint: '将所有缓存创建 token 强制按指定的 TTL 类型(5分钟或1小时)计费',
|
||||||
|
target: '目标 TTL',
|
||||||
|
targetHint: '选择计费使用的 TTL 类型'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
expired: '已过期',
|
expired: '已过期',
|
||||||
|
|||||||
@@ -614,6 +614,10 @@ export interface Account {
|
|||||||
// 启用后将在15分钟内固定 metadata.user_id 中的 session ID
|
// 启用后将在15分钟内固定 metadata.user_id 中的 session ID
|
||||||
session_id_masking_enabled?: boolean | null
|
session_id_masking_enabled?: boolean | null
|
||||||
|
|
||||||
|
// 缓存 TTL 强制替换(仅 Anthropic OAuth/SetupToken 账号有效)
|
||||||
|
cache_ttl_override_enabled?: boolean | null
|
||||||
|
cache_ttl_override_target?: string | null
|
||||||
|
|
||||||
// 运行时状态(仅当启用对应限制时返回)
|
// 运行时状态(仅当启用对应限制时返回)
|
||||||
current_window_cost?: number | null // 当前窗口费用
|
current_window_cost?: number | null // 当前窗口费用
|
||||||
active_sessions?: number | null // 当前活跃会话数
|
active_sessions?: number | null // 当前活跃会话数
|
||||||
@@ -827,6 +831,9 @@ export interface UsageLog {
|
|||||||
// User-Agent
|
// User-Agent
|
||||||
user_agent: string | null
|
user_agent: string | null
|
||||||
|
|
||||||
|
// Cache TTL Override
|
||||||
|
cache_ttl_overridden: boolean
|
||||||
|
|
||||||
created_at: string
|
created_at: string
|
||||||
|
|
||||||
user?: User
|
user?: User
|
||||||
|
|||||||
@@ -234,6 +234,7 @@
|
|||||||
formatCacheTokens(row.cache_creation_tokens)
|
formatCacheTokens(row.cache_creation_tokens)
|
||||||
}}</span>
|
}}</span>
|
||||||
<span v-if="row.cache_creation_1h_tokens > 0" class="inline-flex items-center rounded px-1 py-px text-[10px] font-medium leading-tight bg-orange-100 text-orange-600 ring-1 ring-inset ring-orange-200 dark:bg-orange-500/20 dark:text-orange-400 dark:ring-orange-500/30">1h</span>
|
<span v-if="row.cache_creation_1h_tokens > 0" class="inline-flex items-center rounded px-1 py-px text-[10px] font-medium leading-tight bg-orange-100 text-orange-600 ring-1 ring-inset ring-orange-200 dark:bg-orange-500/20 dark:text-orange-400 dark:ring-orange-500/30">1h</span>
|
||||||
|
<span v-if="row.cache_ttl_overridden" :title="t('usage.cacheTtlOverriddenHint')" class="inline-flex items-center rounded px-1 py-px text-[10px] font-medium leading-tight bg-rose-100 text-rose-600 ring-1 ring-inset ring-rose-200 dark:bg-rose-500/20 dark:text-rose-400 dark:ring-rose-500/30 cursor-help">R</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -375,6 +376,13 @@
|
|||||||
<span class="font-medium text-white">{{ tokenTooltipData.cache_creation_tokens.toLocaleString() }}</span>
|
<span class="font-medium text-white">{{ tokenTooltipData.cache_creation_tokens.toLocaleString() }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div v-if="tokenTooltipData && tokenTooltipData.cache_ttl_overridden" class="flex items-center justify-between gap-4">
|
||||||
|
<span class="text-gray-400 flex items-center gap-1.5">
|
||||||
|
{{ t('usage.cacheTtlOverriddenLabel') }}
|
||||||
|
<span class="inline-flex items-center rounded px-1 py-px text-[10px] font-medium leading-tight bg-rose-500/20 text-rose-400 ring-1 ring-inset ring-rose-500/30">R-{{ tokenTooltipData.cache_creation_1h_tokens > 0 ? '5m' : '1H' }}</span>
|
||||||
|
</span>
|
||||||
|
<span class="font-medium text-rose-400">{{ tokenTooltipData.cache_creation_1h_tokens > 0 ? t('usage.cacheTtlOverridden1h') : t('usage.cacheTtlOverridden5m') }}</span>
|
||||||
|
</div>
|
||||||
<div v-if="tokenTooltipData && tokenTooltipData.cache_read_tokens > 0" class="flex items-center justify-between gap-4">
|
<div v-if="tokenTooltipData && tokenTooltipData.cache_read_tokens > 0" class="flex items-center justify-between gap-4">
|
||||||
<span class="text-gray-400">{{ t('admin.usage.cacheReadTokens') }}</span>
|
<span class="text-gray-400">{{ t('admin.usage.cacheReadTokens') }}</span>
|
||||||
<span class="font-medium text-white">{{ tokenTooltipData.cache_read_tokens.toLocaleString() }}</span>
|
<span class="font-medium text-white">{{ tokenTooltipData.cache_read_tokens.toLocaleString() }}</span>
|
||||||
|
|||||||
Reference in New Issue
Block a user