mirror of
https://gitee.com/wanwujie/sub2api
synced 2026-04-23 08:04:45 +08:00
chore: apply stashed changes
This commit is contained in:
@@ -64,6 +64,8 @@ type Group struct {
|
|||||||
ModelRoutingEnabled bool `json:"model_routing_enabled,omitempty"`
|
ModelRoutingEnabled bool `json:"model_routing_enabled,omitempty"`
|
||||||
// 是否注入 MCP XML 调用协议提示词(仅 antigravity 平台)
|
// 是否注入 MCP XML 调用协议提示词(仅 antigravity 平台)
|
||||||
McpXMLInject bool `json:"mcp_xml_inject,omitempty"`
|
McpXMLInject bool `json:"mcp_xml_inject,omitempty"`
|
||||||
|
// 支持的模型系列:claude, gemini_text, gemini_image
|
||||||
|
SupportedModelScopes []string `json:"supported_model_scopes,omitempty"`
|
||||||
// Edges holds the relations/edges for other nodes in the graph.
|
// Edges holds the relations/edges for other nodes in the graph.
|
||||||
// The values are being populated by the GroupQuery when eager-loading is set.
|
// The values are being populated by the GroupQuery when eager-loading is set.
|
||||||
Edges GroupEdges `json:"edges"`
|
Edges GroupEdges `json:"edges"`
|
||||||
@@ -170,7 +172,7 @@ func (*Group) 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 group.FieldModelRouting:
|
case group.FieldModelRouting, group.FieldSupportedModelScopes:
|
||||||
values[i] = new([]byte)
|
values[i] = new([]byte)
|
||||||
case group.FieldIsExclusive, group.FieldClaudeCodeOnly, group.FieldModelRoutingEnabled, group.FieldMcpXMLInject:
|
case group.FieldIsExclusive, group.FieldClaudeCodeOnly, group.FieldModelRoutingEnabled, group.FieldMcpXMLInject:
|
||||||
values[i] = new(sql.NullBool)
|
values[i] = new(sql.NullBool)
|
||||||
@@ -353,6 +355,14 @@ func (_m *Group) assignValues(columns []string, values []any) error {
|
|||||||
} else if value.Valid {
|
} else if value.Valid {
|
||||||
_m.McpXMLInject = value.Bool
|
_m.McpXMLInject = value.Bool
|
||||||
}
|
}
|
||||||
|
case group.FieldSupportedModelScopes:
|
||||||
|
if value, ok := values[i].(*[]byte); !ok {
|
||||||
|
return fmt.Errorf("unexpected type %T for field supported_model_scopes", values[i])
|
||||||
|
} else if value != nil && len(*value) > 0 {
|
||||||
|
if err := json.Unmarshal(*value, &_m.SupportedModelScopes); err != nil {
|
||||||
|
return fmt.Errorf("unmarshal field supported_model_scopes: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
_m.selectValues.Set(columns[i], values[i])
|
_m.selectValues.Set(columns[i], values[i])
|
||||||
}
|
}
|
||||||
@@ -517,6 +527,9 @@ func (_m *Group) String() string {
|
|||||||
builder.WriteString(", ")
|
builder.WriteString(", ")
|
||||||
builder.WriteString("mcp_xml_inject=")
|
builder.WriteString("mcp_xml_inject=")
|
||||||
builder.WriteString(fmt.Sprintf("%v", _m.McpXMLInject))
|
builder.WriteString(fmt.Sprintf("%v", _m.McpXMLInject))
|
||||||
|
builder.WriteString(", ")
|
||||||
|
builder.WriteString("supported_model_scopes=")
|
||||||
|
builder.WriteString(fmt.Sprintf("%v", _m.SupportedModelScopes))
|
||||||
builder.WriteByte(')')
|
builder.WriteByte(')')
|
||||||
return builder.String()
|
return builder.String()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,6 +61,8 @@ const (
|
|||||||
FieldModelRoutingEnabled = "model_routing_enabled"
|
FieldModelRoutingEnabled = "model_routing_enabled"
|
||||||
// FieldMcpXMLInject holds the string denoting the mcp_xml_inject field in the database.
|
// FieldMcpXMLInject holds the string denoting the mcp_xml_inject field in the database.
|
||||||
FieldMcpXMLInject = "mcp_xml_inject"
|
FieldMcpXMLInject = "mcp_xml_inject"
|
||||||
|
// FieldSupportedModelScopes holds the string denoting the supported_model_scopes field in the database.
|
||||||
|
FieldSupportedModelScopes = "supported_model_scopes"
|
||||||
// EdgeAPIKeys holds the string denoting the api_keys edge name in mutations.
|
// EdgeAPIKeys holds the string denoting the api_keys edge name in mutations.
|
||||||
EdgeAPIKeys = "api_keys"
|
EdgeAPIKeys = "api_keys"
|
||||||
// EdgeRedeemCodes holds the string denoting the redeem_codes edge name in mutations.
|
// EdgeRedeemCodes holds the string denoting the redeem_codes edge name in mutations.
|
||||||
@@ -159,6 +161,7 @@ var Columns = []string{
|
|||||||
FieldModelRouting,
|
FieldModelRouting,
|
||||||
FieldModelRoutingEnabled,
|
FieldModelRoutingEnabled,
|
||||||
FieldMcpXMLInject,
|
FieldMcpXMLInject,
|
||||||
|
FieldSupportedModelScopes,
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -220,6 +223,8 @@ var (
|
|||||||
DefaultModelRoutingEnabled bool
|
DefaultModelRoutingEnabled bool
|
||||||
// DefaultMcpXMLInject holds the default value on creation for the "mcp_xml_inject" field.
|
// DefaultMcpXMLInject holds the default value on creation for the "mcp_xml_inject" field.
|
||||||
DefaultMcpXMLInject bool
|
DefaultMcpXMLInject bool
|
||||||
|
// DefaultSupportedModelScopes holds the default value on creation for the "supported_model_scopes" field.
|
||||||
|
DefaultSupportedModelScopes []string
|
||||||
)
|
)
|
||||||
|
|
||||||
// OrderOption defines the ordering options for the Group queries.
|
// OrderOption defines the ordering options for the Group queries.
|
||||||
|
|||||||
@@ -334,6 +334,12 @@ func (_c *GroupCreate) SetNillableMcpXMLInject(v *bool) *GroupCreate {
|
|||||||
return _c
|
return _c
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetSupportedModelScopes sets the "supported_model_scopes" field.
|
||||||
|
func (_c *GroupCreate) SetSupportedModelScopes(v []string) *GroupCreate {
|
||||||
|
_c.mutation.SetSupportedModelScopes(v)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
// AddAPIKeyIDs adds the "api_keys" edge to the APIKey entity by IDs.
|
// AddAPIKeyIDs adds the "api_keys" edge to the APIKey entity by IDs.
|
||||||
func (_c *GroupCreate) AddAPIKeyIDs(ids ...int64) *GroupCreate {
|
func (_c *GroupCreate) AddAPIKeyIDs(ids ...int64) *GroupCreate {
|
||||||
_c.mutation.AddAPIKeyIDs(ids...)
|
_c.mutation.AddAPIKeyIDs(ids...)
|
||||||
@@ -511,6 +517,10 @@ func (_c *GroupCreate) defaults() error {
|
|||||||
v := group.DefaultMcpXMLInject
|
v := group.DefaultMcpXMLInject
|
||||||
_c.mutation.SetMcpXMLInject(v)
|
_c.mutation.SetMcpXMLInject(v)
|
||||||
}
|
}
|
||||||
|
if _, ok := _c.mutation.SupportedModelScopes(); !ok {
|
||||||
|
v := group.DefaultSupportedModelScopes
|
||||||
|
_c.mutation.SetSupportedModelScopes(v)
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -572,6 +582,9 @@ func (_c *GroupCreate) check() error {
|
|||||||
if _, ok := _c.mutation.McpXMLInject(); !ok {
|
if _, ok := _c.mutation.McpXMLInject(); !ok {
|
||||||
return &ValidationError{Name: "mcp_xml_inject", err: errors.New(`ent: missing required field "Group.mcp_xml_inject"`)}
|
return &ValidationError{Name: "mcp_xml_inject", err: errors.New(`ent: missing required field "Group.mcp_xml_inject"`)}
|
||||||
}
|
}
|
||||||
|
if _, ok := _c.mutation.SupportedModelScopes(); !ok {
|
||||||
|
return &ValidationError{Name: "supported_model_scopes", err: errors.New(`ent: missing required field "Group.supported_model_scopes"`)}
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -691,6 +704,10 @@ func (_c *GroupCreate) createSpec() (*Group, *sqlgraph.CreateSpec) {
|
|||||||
_spec.SetField(group.FieldMcpXMLInject, field.TypeBool, value)
|
_spec.SetField(group.FieldMcpXMLInject, field.TypeBool, value)
|
||||||
_node.McpXMLInject = value
|
_node.McpXMLInject = value
|
||||||
}
|
}
|
||||||
|
if value, ok := _c.mutation.SupportedModelScopes(); ok {
|
||||||
|
_spec.SetField(group.FieldSupportedModelScopes, field.TypeJSON, value)
|
||||||
|
_node.SupportedModelScopes = value
|
||||||
|
}
|
||||||
if nodes := _c.mutation.APIKeysIDs(); len(nodes) > 0 {
|
if nodes := _c.mutation.APIKeysIDs(); len(nodes) > 0 {
|
||||||
edge := &sqlgraph.EdgeSpec{
|
edge := &sqlgraph.EdgeSpec{
|
||||||
Rel: sqlgraph.O2M,
|
Rel: sqlgraph.O2M,
|
||||||
@@ -1237,6 +1254,18 @@ func (u *GroupUpsert) UpdateMcpXMLInject() *GroupUpsert {
|
|||||||
return u
|
return u
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetSupportedModelScopes sets the "supported_model_scopes" field.
|
||||||
|
func (u *GroupUpsert) SetSupportedModelScopes(v []string) *GroupUpsert {
|
||||||
|
u.Set(group.FieldSupportedModelScopes, v)
|
||||||
|
return u
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateSupportedModelScopes sets the "supported_model_scopes" field to the value that was provided on create.
|
||||||
|
func (u *GroupUpsert) UpdateSupportedModelScopes() *GroupUpsert {
|
||||||
|
u.SetExcluded(group.FieldSupportedModelScopes)
|
||||||
|
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:
|
||||||
//
|
//
|
||||||
@@ -1737,6 +1766,20 @@ func (u *GroupUpsertOne) UpdateMcpXMLInject() *GroupUpsertOne {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetSupportedModelScopes sets the "supported_model_scopes" field.
|
||||||
|
func (u *GroupUpsertOne) SetSupportedModelScopes(v []string) *GroupUpsertOne {
|
||||||
|
return u.Update(func(s *GroupUpsert) {
|
||||||
|
s.SetSupportedModelScopes(v)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateSupportedModelScopes sets the "supported_model_scopes" field to the value that was provided on create.
|
||||||
|
func (u *GroupUpsertOne) UpdateSupportedModelScopes() *GroupUpsertOne {
|
||||||
|
return u.Update(func(s *GroupUpsert) {
|
||||||
|
s.UpdateSupportedModelScopes()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// Exec executes the query.
|
// Exec executes the query.
|
||||||
func (u *GroupUpsertOne) Exec(ctx context.Context) error {
|
func (u *GroupUpsertOne) Exec(ctx context.Context) error {
|
||||||
if len(u.create.conflict) == 0 {
|
if len(u.create.conflict) == 0 {
|
||||||
@@ -2403,6 +2446,20 @@ func (u *GroupUpsertBulk) UpdateMcpXMLInject() *GroupUpsertBulk {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetSupportedModelScopes sets the "supported_model_scopes" field.
|
||||||
|
func (u *GroupUpsertBulk) SetSupportedModelScopes(v []string) *GroupUpsertBulk {
|
||||||
|
return u.Update(func(s *GroupUpsert) {
|
||||||
|
s.SetSupportedModelScopes(v)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateSupportedModelScopes sets the "supported_model_scopes" field to the value that was provided on create.
|
||||||
|
func (u *GroupUpsertBulk) UpdateSupportedModelScopes() *GroupUpsertBulk {
|
||||||
|
return u.Update(func(s *GroupUpsert) {
|
||||||
|
s.UpdateSupportedModelScopes()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// Exec executes the query.
|
// Exec executes the query.
|
||||||
func (u *GroupUpsertBulk) Exec(ctx context.Context) error {
|
func (u *GroupUpsertBulk) Exec(ctx context.Context) error {
|
||||||
if u.create.err != nil {
|
if u.create.err != nil {
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
|
|
||||||
"entgo.io/ent/dialect/sql"
|
"entgo.io/ent/dialect/sql"
|
||||||
"entgo.io/ent/dialect/sql/sqlgraph"
|
"entgo.io/ent/dialect/sql/sqlgraph"
|
||||||
|
"entgo.io/ent/dialect/sql/sqljson"
|
||||||
"entgo.io/ent/schema/field"
|
"entgo.io/ent/schema/field"
|
||||||
"github.com/Wei-Shaw/sub2api/ent/account"
|
"github.com/Wei-Shaw/sub2api/ent/account"
|
||||||
"github.com/Wei-Shaw/sub2api/ent/apikey"
|
"github.com/Wei-Shaw/sub2api/ent/apikey"
|
||||||
@@ -462,6 +463,18 @@ func (_u *GroupUpdate) SetNillableMcpXMLInject(v *bool) *GroupUpdate {
|
|||||||
return _u
|
return _u
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetSupportedModelScopes sets the "supported_model_scopes" field.
|
||||||
|
func (_u *GroupUpdate) SetSupportedModelScopes(v []string) *GroupUpdate {
|
||||||
|
_u.mutation.SetSupportedModelScopes(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// AppendSupportedModelScopes appends value to the "supported_model_scopes" field.
|
||||||
|
func (_u *GroupUpdate) AppendSupportedModelScopes(v []string) *GroupUpdate {
|
||||||
|
_u.mutation.AppendSupportedModelScopes(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
// AddAPIKeyIDs adds the "api_keys" edge to the APIKey entity by IDs.
|
// AddAPIKeyIDs adds the "api_keys" edge to the APIKey entity by IDs.
|
||||||
func (_u *GroupUpdate) AddAPIKeyIDs(ids ...int64) *GroupUpdate {
|
func (_u *GroupUpdate) AddAPIKeyIDs(ids ...int64) *GroupUpdate {
|
||||||
_u.mutation.AddAPIKeyIDs(ids...)
|
_u.mutation.AddAPIKeyIDs(ids...)
|
||||||
@@ -891,6 +904,14 @@ func (_u *GroupUpdate) sqlSave(ctx context.Context) (_node int, err error) {
|
|||||||
if value, ok := _u.mutation.McpXMLInject(); ok {
|
if value, ok := _u.mutation.McpXMLInject(); ok {
|
||||||
_spec.SetField(group.FieldMcpXMLInject, field.TypeBool, value)
|
_spec.SetField(group.FieldMcpXMLInject, field.TypeBool, value)
|
||||||
}
|
}
|
||||||
|
if value, ok := _u.mutation.SupportedModelScopes(); ok {
|
||||||
|
_spec.SetField(group.FieldSupportedModelScopes, field.TypeJSON, value)
|
||||||
|
}
|
||||||
|
if value, ok := _u.mutation.AppendedSupportedModelScopes(); ok {
|
||||||
|
_spec.AddModifier(func(u *sql.UpdateBuilder) {
|
||||||
|
sqljson.Append(u, group.FieldSupportedModelScopes, value)
|
||||||
|
})
|
||||||
|
}
|
||||||
if _u.mutation.APIKeysCleared() {
|
if _u.mutation.APIKeysCleared() {
|
||||||
edge := &sqlgraph.EdgeSpec{
|
edge := &sqlgraph.EdgeSpec{
|
||||||
Rel: sqlgraph.O2M,
|
Rel: sqlgraph.O2M,
|
||||||
@@ -1633,6 +1654,18 @@ func (_u *GroupUpdateOne) SetNillableMcpXMLInject(v *bool) *GroupUpdateOne {
|
|||||||
return _u
|
return _u
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetSupportedModelScopes sets the "supported_model_scopes" field.
|
||||||
|
func (_u *GroupUpdateOne) SetSupportedModelScopes(v []string) *GroupUpdateOne {
|
||||||
|
_u.mutation.SetSupportedModelScopes(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// AppendSupportedModelScopes appends value to the "supported_model_scopes" field.
|
||||||
|
func (_u *GroupUpdateOne) AppendSupportedModelScopes(v []string) *GroupUpdateOne {
|
||||||
|
_u.mutation.AppendSupportedModelScopes(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
// AddAPIKeyIDs adds the "api_keys" edge to the APIKey entity by IDs.
|
// AddAPIKeyIDs adds the "api_keys" edge to the APIKey entity by IDs.
|
||||||
func (_u *GroupUpdateOne) AddAPIKeyIDs(ids ...int64) *GroupUpdateOne {
|
func (_u *GroupUpdateOne) AddAPIKeyIDs(ids ...int64) *GroupUpdateOne {
|
||||||
_u.mutation.AddAPIKeyIDs(ids...)
|
_u.mutation.AddAPIKeyIDs(ids...)
|
||||||
@@ -2092,6 +2125,14 @@ func (_u *GroupUpdateOne) sqlSave(ctx context.Context) (_node *Group, err error)
|
|||||||
if value, ok := _u.mutation.McpXMLInject(); ok {
|
if value, ok := _u.mutation.McpXMLInject(); ok {
|
||||||
_spec.SetField(group.FieldMcpXMLInject, field.TypeBool, value)
|
_spec.SetField(group.FieldMcpXMLInject, field.TypeBool, value)
|
||||||
}
|
}
|
||||||
|
if value, ok := _u.mutation.SupportedModelScopes(); ok {
|
||||||
|
_spec.SetField(group.FieldSupportedModelScopes, field.TypeJSON, value)
|
||||||
|
}
|
||||||
|
if value, ok := _u.mutation.AppendedSupportedModelScopes(); ok {
|
||||||
|
_spec.AddModifier(func(u *sql.UpdateBuilder) {
|
||||||
|
sqljson.Append(u, group.FieldSupportedModelScopes, value)
|
||||||
|
})
|
||||||
|
}
|
||||||
if _u.mutation.APIKeysCleared() {
|
if _u.mutation.APIKeysCleared() {
|
||||||
edge := &sqlgraph.EdgeSpec{
|
edge := &sqlgraph.EdgeSpec{
|
||||||
Rel: sqlgraph.O2M,
|
Rel: sqlgraph.O2M,
|
||||||
|
|||||||
@@ -322,6 +322,7 @@ var (
|
|||||||
{Name: "model_routing", Type: field.TypeJSON, Nullable: true, SchemaType: map[string]string{"postgres": "jsonb"}},
|
{Name: "model_routing", Type: field.TypeJSON, Nullable: true, SchemaType: map[string]string{"postgres": "jsonb"}},
|
||||||
{Name: "model_routing_enabled", Type: field.TypeBool, Default: false},
|
{Name: "model_routing_enabled", Type: field.TypeBool, Default: false},
|
||||||
{Name: "mcp_xml_inject", Type: field.TypeBool, Default: true},
|
{Name: "mcp_xml_inject", Type: field.TypeBool, Default: true},
|
||||||
|
{Name: "supported_model_scopes", Type: field.TypeJSON, SchemaType: map[string]string{"postgres": "jsonb"}},
|
||||||
}
|
}
|
||||||
// GroupsTable holds the schema information for the "groups" table.
|
// GroupsTable holds the schema information for the "groups" table.
|
||||||
GroupsTable = &schema.Table{
|
GroupsTable = &schema.Table{
|
||||||
|
|||||||
@@ -5542,6 +5542,8 @@ type GroupMutation struct {
|
|||||||
model_routing *map[string][]int64
|
model_routing *map[string][]int64
|
||||||
model_routing_enabled *bool
|
model_routing_enabled *bool
|
||||||
mcp_xml_inject *bool
|
mcp_xml_inject *bool
|
||||||
|
supported_model_scopes *[]string
|
||||||
|
appendsupported_model_scopes []string
|
||||||
clearedFields map[string]struct{}
|
clearedFields map[string]struct{}
|
||||||
api_keys map[int64]struct{}
|
api_keys map[int64]struct{}
|
||||||
removedapi_keys map[int64]struct{}
|
removedapi_keys map[int64]struct{}
|
||||||
@@ -6843,6 +6845,57 @@ func (m *GroupMutation) ResetMcpXMLInject() {
|
|||||||
m.mcp_xml_inject = nil
|
m.mcp_xml_inject = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetSupportedModelScopes sets the "supported_model_scopes" field.
|
||||||
|
func (m *GroupMutation) SetSupportedModelScopes(s []string) {
|
||||||
|
m.supported_model_scopes = &s
|
||||||
|
m.appendsupported_model_scopes = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SupportedModelScopes returns the value of the "supported_model_scopes" field in the mutation.
|
||||||
|
func (m *GroupMutation) SupportedModelScopes() (r []string, exists bool) {
|
||||||
|
v := m.supported_model_scopes
|
||||||
|
if v == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return *v, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// OldSupportedModelScopes returns the old "supported_model_scopes" field's value of the Group entity.
|
||||||
|
// If the Group 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 *GroupMutation) OldSupportedModelScopes(ctx context.Context) (v []string, err error) {
|
||||||
|
if !m.op.Is(OpUpdateOne) {
|
||||||
|
return v, errors.New("OldSupportedModelScopes is only allowed on UpdateOne operations")
|
||||||
|
}
|
||||||
|
if m.id == nil || m.oldValue == nil {
|
||||||
|
return v, errors.New("OldSupportedModelScopes requires an ID field in the mutation")
|
||||||
|
}
|
||||||
|
oldValue, err := m.oldValue(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return v, fmt.Errorf("querying old value for OldSupportedModelScopes: %w", err)
|
||||||
|
}
|
||||||
|
return oldValue.SupportedModelScopes, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AppendSupportedModelScopes adds s to the "supported_model_scopes" field.
|
||||||
|
func (m *GroupMutation) AppendSupportedModelScopes(s []string) {
|
||||||
|
m.appendsupported_model_scopes = append(m.appendsupported_model_scopes, s...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AppendedSupportedModelScopes returns the list of values that were appended to the "supported_model_scopes" field in this mutation.
|
||||||
|
func (m *GroupMutation) AppendedSupportedModelScopes() ([]string, bool) {
|
||||||
|
if len(m.appendsupported_model_scopes) == 0 {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
return m.appendsupported_model_scopes, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResetSupportedModelScopes resets all changes to the "supported_model_scopes" field.
|
||||||
|
func (m *GroupMutation) ResetSupportedModelScopes() {
|
||||||
|
m.supported_model_scopes = nil
|
||||||
|
m.appendsupported_model_scopes = nil
|
||||||
|
}
|
||||||
|
|
||||||
// AddAPIKeyIDs adds the "api_keys" edge to the APIKey entity by ids.
|
// AddAPIKeyIDs adds the "api_keys" edge to the APIKey entity by ids.
|
||||||
func (m *GroupMutation) AddAPIKeyIDs(ids ...int64) {
|
func (m *GroupMutation) AddAPIKeyIDs(ids ...int64) {
|
||||||
if m.api_keys == nil {
|
if m.api_keys == nil {
|
||||||
@@ -7201,7 +7254,7 @@ func (m *GroupMutation) 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 *GroupMutation) Fields() []string {
|
func (m *GroupMutation) Fields() []string {
|
||||||
fields := make([]string, 0, 23)
|
fields := make([]string, 0, 24)
|
||||||
if m.created_at != nil {
|
if m.created_at != nil {
|
||||||
fields = append(fields, group.FieldCreatedAt)
|
fields = append(fields, group.FieldCreatedAt)
|
||||||
}
|
}
|
||||||
@@ -7271,6 +7324,9 @@ func (m *GroupMutation) Fields() []string {
|
|||||||
if m.mcp_xml_inject != nil {
|
if m.mcp_xml_inject != nil {
|
||||||
fields = append(fields, group.FieldMcpXMLInject)
|
fields = append(fields, group.FieldMcpXMLInject)
|
||||||
}
|
}
|
||||||
|
if m.supported_model_scopes != nil {
|
||||||
|
fields = append(fields, group.FieldSupportedModelScopes)
|
||||||
|
}
|
||||||
return fields
|
return fields
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -7325,6 +7381,8 @@ func (m *GroupMutation) Field(name string) (ent.Value, bool) {
|
|||||||
return m.ModelRoutingEnabled()
|
return m.ModelRoutingEnabled()
|
||||||
case group.FieldMcpXMLInject:
|
case group.FieldMcpXMLInject:
|
||||||
return m.McpXMLInject()
|
return m.McpXMLInject()
|
||||||
|
case group.FieldSupportedModelScopes:
|
||||||
|
return m.SupportedModelScopes()
|
||||||
}
|
}
|
||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
@@ -7380,6 +7438,8 @@ func (m *GroupMutation) OldField(ctx context.Context, name string) (ent.Value, e
|
|||||||
return m.OldModelRoutingEnabled(ctx)
|
return m.OldModelRoutingEnabled(ctx)
|
||||||
case group.FieldMcpXMLInject:
|
case group.FieldMcpXMLInject:
|
||||||
return m.OldMcpXMLInject(ctx)
|
return m.OldMcpXMLInject(ctx)
|
||||||
|
case group.FieldSupportedModelScopes:
|
||||||
|
return m.OldSupportedModelScopes(ctx)
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("unknown Group field %s", name)
|
return nil, fmt.Errorf("unknown Group field %s", name)
|
||||||
}
|
}
|
||||||
@@ -7550,6 +7610,13 @@ func (m *GroupMutation) SetField(name string, value ent.Value) error {
|
|||||||
}
|
}
|
||||||
m.SetMcpXMLInject(v)
|
m.SetMcpXMLInject(v)
|
||||||
return nil
|
return nil
|
||||||
|
case group.FieldSupportedModelScopes:
|
||||||
|
v, ok := value.([]string)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("unexpected type %T for field %s", value, name)
|
||||||
|
}
|
||||||
|
m.SetSupportedModelScopes(v)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
return fmt.Errorf("unknown Group field %s", name)
|
return fmt.Errorf("unknown Group field %s", name)
|
||||||
}
|
}
|
||||||
@@ -7860,6 +7927,9 @@ func (m *GroupMutation) ResetField(name string) error {
|
|||||||
case group.FieldMcpXMLInject:
|
case group.FieldMcpXMLInject:
|
||||||
m.ResetMcpXMLInject()
|
m.ResetMcpXMLInject()
|
||||||
return nil
|
return nil
|
||||||
|
case group.FieldSupportedModelScopes:
|
||||||
|
m.ResetSupportedModelScopes()
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
return fmt.Errorf("unknown Group field %s", name)
|
return fmt.Errorf("unknown Group field %s", name)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -341,6 +341,10 @@ func init() {
|
|||||||
groupDescMcpXMLInject := groupFields[19].Descriptor()
|
groupDescMcpXMLInject := groupFields[19].Descriptor()
|
||||||
// group.DefaultMcpXMLInject holds the default value on creation for the mcp_xml_inject field.
|
// group.DefaultMcpXMLInject holds the default value on creation for the mcp_xml_inject field.
|
||||||
group.DefaultMcpXMLInject = groupDescMcpXMLInject.Default.(bool)
|
group.DefaultMcpXMLInject = groupDescMcpXMLInject.Default.(bool)
|
||||||
|
// groupDescSupportedModelScopes is the schema descriptor for supported_model_scopes field.
|
||||||
|
groupDescSupportedModelScopes := groupFields[20].Descriptor()
|
||||||
|
// group.DefaultSupportedModelScopes holds the default value on creation for the supported_model_scopes field.
|
||||||
|
group.DefaultSupportedModelScopes = groupDescSupportedModelScopes.Default.([]string)
|
||||||
promocodeFields := schema.PromoCode{}.Fields()
|
promocodeFields := schema.PromoCode{}.Fields()
|
||||||
_ = promocodeFields
|
_ = promocodeFields
|
||||||
// promocodeDescCode is the schema descriptor for code field.
|
// promocodeDescCode is the schema descriptor for code field.
|
||||||
|
|||||||
@@ -115,6 +115,12 @@ func (Group) Fields() []ent.Field {
|
|||||||
field.Bool("mcp_xml_inject").
|
field.Bool("mcp_xml_inject").
|
||||||
Default(true).
|
Default(true).
|
||||||
Comment("是否注入 MCP XML 调用协议提示词(仅 antigravity 平台)"),
|
Comment("是否注入 MCP XML 调用协议提示词(仅 antigravity 平台)"),
|
||||||
|
|
||||||
|
// 支持的模型系列 (added by migration 046)
|
||||||
|
field.JSON("supported_model_scopes", []string{}).
|
||||||
|
Default([]string{"claude", "gemini_text", "gemini_image"}).
|
||||||
|
SchemaType(map[string]string{dialect.Postgres: "jsonb"}).
|
||||||
|
Comment("支持的模型系列:claude, gemini_text, gemini_image"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ go 1.25.6
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
entgo.io/ent v0.14.5
|
entgo.io/ent v0.14.5
|
||||||
|
github.com/DATA-DOG/go-sqlmock v1.5.2
|
||||||
|
github.com/dgraph-io/ristretto v0.2.0
|
||||||
github.com/gin-gonic/gin v1.9.1
|
github.com/gin-gonic/gin v1.9.1
|
||||||
github.com/golang-jwt/jwt/v5 v5.2.2
|
github.com/golang-jwt/jwt/v5 v5.2.2
|
||||||
github.com/google/uuid v1.6.0
|
github.com/google/uuid v1.6.0
|
||||||
@@ -11,7 +13,10 @@ require (
|
|||||||
github.com/gorilla/websocket v1.5.3
|
github.com/gorilla/websocket v1.5.3
|
||||||
github.com/imroc/req/v3 v3.57.0
|
github.com/imroc/req/v3 v3.57.0
|
||||||
github.com/lib/pq v1.10.9
|
github.com/lib/pq v1.10.9
|
||||||
|
github.com/pquerna/otp v1.5.0
|
||||||
github.com/redis/go-redis/v9 v9.17.2
|
github.com/redis/go-redis/v9 v9.17.2
|
||||||
|
github.com/refraction-networking/utls v1.8.1
|
||||||
|
github.com/robfig/cron/v3 v3.0.1
|
||||||
github.com/shirou/gopsutil/v4 v4.25.6
|
github.com/shirou/gopsutil/v4 v4.25.6
|
||||||
github.com/spf13/viper v1.18.2
|
github.com/spf13/viper v1.18.2
|
||||||
github.com/stretchr/testify v1.11.1
|
github.com/stretchr/testify v1.11.1
|
||||||
@@ -25,6 +30,7 @@ require (
|
|||||||
golang.org/x/sync v0.19.0
|
golang.org/x/sync v0.19.0
|
||||||
golang.org/x/term v0.38.0
|
golang.org/x/term v0.38.0
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
|
modernc.org/sqlite v1.44.3
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
@@ -36,6 +42,7 @@ require (
|
|||||||
github.com/andybalholm/brotli v1.2.0 // indirect
|
github.com/andybalholm/brotli v1.2.0 // indirect
|
||||||
github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect
|
github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect
|
||||||
github.com/bmatcuk/doublestar v1.3.4 // indirect
|
github.com/bmatcuk/doublestar v1.3.4 // indirect
|
||||||
|
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect
|
||||||
github.com/bytedance/sonic v1.9.1 // indirect
|
github.com/bytedance/sonic v1.9.1 // indirect
|
||||||
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
||||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||||
@@ -46,7 +53,6 @@ require (
|
|||||||
github.com/containerd/platforms v0.2.1 // indirect
|
github.com/containerd/platforms v0.2.1 // indirect
|
||||||
github.com/cpuguy83/dockercfg v0.3.2 // indirect
|
github.com/cpuguy83/dockercfg v0.3.2 // indirect
|
||||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||||
github.com/dgraph-io/ristretto v0.2.0 // indirect
|
|
||||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||||
github.com/distribution/reference v0.6.0 // indirect
|
github.com/distribution/reference v0.6.0 // indirect
|
||||||
github.com/docker/docker v28.5.1+incompatible // indirect
|
github.com/docker/docker v28.5.1+incompatible // indirect
|
||||||
@@ -97,6 +103,7 @@ require (
|
|||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
github.com/morikuni/aec v1.0.0 // indirect
|
github.com/morikuni/aec v1.0.0 // indirect
|
||||||
|
github.com/ncruces/go-strftime v1.0.0 // indirect
|
||||||
github.com/olekukonko/tablewriter v0.0.5 // indirect
|
github.com/olekukonko/tablewriter v0.0.5 // indirect
|
||||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||||
github.com/opencontainers/image-spec v1.1.1 // indirect
|
github.com/opencontainers/image-spec v1.1.1 // indirect
|
||||||
@@ -106,9 +113,8 @@ require (
|
|||||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
|
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
|
||||||
github.com/quic-go/qpack v0.6.0 // indirect
|
github.com/quic-go/qpack v0.6.0 // indirect
|
||||||
github.com/quic-go/quic-go v0.57.1 // indirect
|
github.com/quic-go/quic-go v0.57.1 // indirect
|
||||||
github.com/refraction-networking/utls v1.8.1 // indirect
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||||
github.com/rivo/uniseg v0.2.0 // indirect
|
github.com/rivo/uniseg v0.2.0 // indirect
|
||||||
github.com/robfig/cron/v3 v3.0.1 // indirect
|
|
||||||
github.com/sagikazarmark/locafero v0.4.0 // indirect
|
github.com/sagikazarmark/locafero v0.4.0 // indirect
|
||||||
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
|
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
|
||||||
github.com/sirupsen/logrus v1.9.3 // indirect
|
github.com/sirupsen/logrus v1.9.3 // indirect
|
||||||
@@ -139,13 +145,15 @@ require (
|
|||||||
go.uber.org/automaxprocs v1.6.0 // indirect
|
go.uber.org/automaxprocs v1.6.0 // indirect
|
||||||
go.uber.org/multierr v1.9.0 // indirect
|
go.uber.org/multierr v1.9.0 // indirect
|
||||||
golang.org/x/arch v0.3.0 // indirect
|
golang.org/x/arch v0.3.0 // indirect
|
||||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
|
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 // indirect
|
||||||
golang.org/x/mod v0.30.0 // indirect
|
golang.org/x/mod v0.30.0 // indirect
|
||||||
golang.org/x/sys v0.39.0 // indirect
|
golang.org/x/sys v0.39.0 // indirect
|
||||||
golang.org/x/text v0.32.0 // indirect
|
golang.org/x/text v0.32.0 // indirect
|
||||||
golang.org/x/tools v0.39.0 // indirect
|
golang.org/x/tools v0.39.0 // indirect
|
||||||
golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated // indirect
|
|
||||||
google.golang.org/grpc v1.75.1 // indirect
|
google.golang.org/grpc v1.75.1 // indirect
|
||||||
google.golang.org/protobuf v1.36.10 // indirect
|
google.golang.org/protobuf v1.36.10 // indirect
|
||||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||||
|
modernc.org/libc v1.67.6 // indirect
|
||||||
|
modernc.org/mathutil v1.7.1 // indirect
|
||||||
|
modernc.org/memory v1.11.0 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew
|
|||||||
github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4=
|
github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4=
|
||||||
github.com/bmatcuk/doublestar v1.3.4 h1:gPypJ5xD31uhX6Tf54sDPUOBXTqKH4c9aPY66CyQrS0=
|
github.com/bmatcuk/doublestar v1.3.4 h1:gPypJ5xD31uhX6Tf54sDPUOBXTqKH4c9aPY66CyQrS0=
|
||||||
github.com/bmatcuk/doublestar v1.3.4/go.mod h1:wiQtGV+rzVYxB7WIlirSN++5HPtPlXEo9MEoZQC/PmE=
|
github.com/bmatcuk/doublestar v1.3.4/go.mod h1:wiQtGV+rzVYxB7WIlirSN++5HPtPlXEo9MEoZQC/PmE=
|
||||||
|
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc h1:biVzkmvwrH8WK8raXaxBx6fRVTlJILwEwQGL1I/ByEI=
|
||||||
|
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
|
||||||
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
|
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
|
||||||
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
|
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
|
||||||
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
|
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
|
||||||
@@ -53,6 +55,8 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1
|
|||||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/dgraph-io/ristretto v0.2.0 h1:XAfl+7cmoUDWW/2Lx8TGZQjjxIQ2Ley9DSf52dru4WE=
|
github.com/dgraph-io/ristretto v0.2.0 h1:XAfl+7cmoUDWW/2Lx8TGZQjjxIQ2Ley9DSf52dru4WE=
|
||||||
github.com/dgraph-io/ristretto v0.2.0/go.mod h1:8uBHCU/PBV4Ag0CJrP47b9Ofby5dqWNh4FicAdoqFNU=
|
github.com/dgraph-io/ristretto v0.2.0/go.mod h1:8uBHCU/PBV4Ag0CJrP47b9Ofby5dqWNh4FicAdoqFNU=
|
||||||
|
github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 h1:fAjc9m62+UWV/WAFKLNi6ZS0675eEUC9y3AlwSbQu1Y=
|
||||||
|
github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
|
||||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
||||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||||
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
|
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
|
||||||
@@ -111,6 +115,8 @@ github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX
|
|||||||
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
|
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
|
||||||
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
|
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
|
||||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
|
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs=
|
||||||
|
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
|
||||||
github.com/google/subcommands v1.2.0 h1:vWQspBTo2nEqTUFita5/KeEWlUL8kQObDFbub/EN9oE=
|
github.com/google/subcommands v1.2.0 h1:vWQspBTo2nEqTUFita5/KeEWlUL8kQObDFbub/EN9oE=
|
||||||
github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk=
|
github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk=
|
||||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
@@ -121,6 +127,9 @@ github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aN
|
|||||||
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 h1:NmZ1PKzSTQbuGHw9DGPFomqkkLWMC+vZCkfs+FHv1Vg=
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 h1:NmZ1PKzSTQbuGHw9DGPFomqkkLWMC+vZCkfs+FHv1Vg=
|
||||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3/go.mod h1:zQrxl1YP88HQlA6i9c63DSVPFklWpGX4OWAc9bFuaH4=
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3/go.mod h1:zQrxl1YP88HQlA6i9c63DSVPFklWpGX4OWAc9bFuaH4=
|
||||||
|
github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc=
|
||||||
|
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
|
||||||
|
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
|
||||||
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
||||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||||
github.com/hashicorp/hcl/v2 v2.18.1 h1:6nxnOJFku1EuSawSD81fuviYUV8DxFr3fp2dUi3ZYSo=
|
github.com/hashicorp/hcl/v2 v2.18.1 h1:6nxnOJFku1EuSawSD81fuviYUV8DxFr3fp2dUi3ZYSo=
|
||||||
@@ -141,6 +150,7 @@ github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo
|
|||||||
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
||||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||||
|
github.com/kisielk/sqlstruct v0.0.0-20201105191214-5f3e10d3ab46/go.mod h1:yyMNCyc/Ib3bDTKd379tNMpB/7/H5TjM2Y9QJ5THLbE=
|
||||||
github.com/klauspost/compress v1.18.2 h1:iiPHWW0YrcFgpBYhsA6D1+fqHssJscY/Tm/y2Uqnapk=
|
github.com/klauspost/compress v1.18.2 h1:iiPHWW0YrcFgpBYhsA6D1+fqHssJscY/Tm/y2Uqnapk=
|
||||||
github.com/klauspost/compress v1.18.2/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
|
github.com/klauspost/compress v1.18.2/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
|
||||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||||
@@ -199,6 +209,8 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G
|
|||||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||||
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
|
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
|
||||||
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
|
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 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
|
||||||
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
|
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 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||||
@@ -214,6 +226,8 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRI
|
|||||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
|
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
|
||||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
||||||
|
github.com/pquerna/otp v1.5.0 h1:NMMR+WrmaqXU4EzdGJEE1aUUI0AMRzsp96fFFWNPwxs=
|
||||||
|
github.com/pquerna/otp v1.5.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg=
|
||||||
github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
|
github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
|
||||||
github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U=
|
github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U=
|
||||||
github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8=
|
github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8=
|
||||||
@@ -224,6 +238,8 @@ github.com/redis/go-redis/v9 v9.17.2 h1:P2EGsA4qVIM3Pp+aPocCJ7DguDHhqrXNhVcEp4Vi
|
|||||||
github.com/redis/go-redis/v9 v9.17.2/go.mod h1:u410H11HMLoB+TP67dz8rL9s6QW2j76l0//kSOd3370=
|
github.com/redis/go-redis/v9 v9.17.2/go.mod h1:u410H11HMLoB+TP67dz8rL9s6QW2j76l0//kSOd3370=
|
||||||
github.com/refraction-networking/utls v1.8.1 h1:yNY1kapmQU8JeM1sSw2H2asfTIwWxIkrMJI0pRUOCAo=
|
github.com/refraction-networking/utls v1.8.1 h1:yNY1kapmQU8JeM1sSw2H2asfTIwWxIkrMJI0pRUOCAo=
|
||||||
github.com/refraction-networking/utls v1.8.1/go.mod h1:jkSOEkLqn+S/jtpEHPOsVv/4V4EVnelwbMQl4vCWXAM=
|
github.com/refraction-networking/utls v1.8.1/go.mod h1:jkSOEkLqn+S/jtpEHPOsVv/4V4EVnelwbMQl4vCWXAM=
|
||||||
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||||
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||||
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
||||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||||
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
||||||
@@ -336,8 +352,8 @@ golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k=
|
|||||||
golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||||
golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU=
|
golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU=
|
||||||
golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0=
|
golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0=
|
||||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
|
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 h1:mgKeJMpvi0yx/sU5GsxQ7p6s2wtOnGAHZWCHUM4KGzY=
|
||||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
|
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546/go.mod h1:j/pmGrbnkbPtQfxEe5D0VQhZC6qKbfKifgD0oM7sR70=
|
||||||
golang.org/x/mod v0.30.0 h1:fDEXFVZ/fmCKProc/yAXXUijritrDzahmwwefnjoPFk=
|
golang.org/x/mod v0.30.0 h1:fDEXFVZ/fmCKProc/yAXXUijritrDzahmwwefnjoPFk=
|
||||||
golang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc=
|
golang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc=
|
||||||
golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
|
golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
|
||||||
@@ -363,8 +379,8 @@ golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
|
|||||||
golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
|
golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
|
||||||
golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ=
|
golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ=
|
||||||
golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ=
|
golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ=
|
||||||
golang.org/x/tools/go/expect v0.1.0-deprecated h1:jY2C5HGYR5lqex3gEniOQL0r7Dq5+VGVgY1nudX5lXY=
|
golang.org/x/tools/go/expect v0.1.1-deprecated h1:jpBZDwmgPhXsKZC6WhL20P4b/wmnpsEAGHaNy0n/rJM=
|
||||||
golang.org/x/tools/go/expect v0.1.0-deprecated/go.mod h1:eihoPOH+FgIqa3FpoTwguz/bVUSGBlGQU67vpBeOrBY=
|
golang.org/x/tools/go/expect v0.1.1-deprecated/go.mod h1:eihoPOH+FgIqa3FpoTwguz/bVUSGBlGQU67vpBeOrBY=
|
||||||
golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated h1:1h2MnaIAIXISqTFKdENegdpAgUXz6NrPEsbIeWaBRvM=
|
golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated h1:1h2MnaIAIXISqTFKdENegdpAgUXz6NrPEsbIeWaBRvM=
|
||||||
golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated/go.mod h1:RVAQXBGNv1ib0J382/DPCRS/BPnsGebyM1Gj5VSDpG8=
|
golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated/go.mod h1:RVAQXBGNv1ib0J382/DPCRS/BPnsGebyM1Gj5VSDpG8=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
@@ -387,4 +403,32 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
|||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q=
|
gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q=
|
||||||
gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA=
|
gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA=
|
||||||
|
modernc.org/cc/v4 v4.27.1 h1:9W30zRlYrefrDV2JE2O8VDtJ1yPGownxciz5rrbQZis=
|
||||||
|
modernc.org/cc/v4 v4.27.1/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
|
||||||
|
modernc.org/ccgo/v4 v4.30.1 h1:4r4U1J6Fhj98NKfSjnPUN7Ze2c6MnAdL0hWw6+LrJpc=
|
||||||
|
modernc.org/ccgo/v4 v4.30.1/go.mod h1:bIOeI1JL54Utlxn+LwrFyjCx2n2RDiYEaJVSrgdrRfM=
|
||||||
|
modernc.org/fileutil v1.3.40 h1:ZGMswMNc9JOCrcrakF1HrvmergNLAmxOPjizirpfqBA=
|
||||||
|
modernc.org/fileutil v1.3.40/go.mod h1:HxmghZSZVAz/LXcMNwZPA/DRrQZEVP9VX0V4LQGQFOc=
|
||||||
|
modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
|
||||||
|
modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
|
||||||
|
modernc.org/gc/v3 v3.1.1 h1:k8T3gkXWY9sEiytKhcgyiZ2L0DTyCQ/nvX+LoCljoRE=
|
||||||
|
modernc.org/gc/v3 v3.1.1/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY=
|
||||||
|
modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks=
|
||||||
|
modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI=
|
||||||
|
modernc.org/libc v1.67.6 h1:eVOQvpModVLKOdT+LvBPjdQqfrZq+pC39BygcT+E7OI=
|
||||||
|
modernc.org/libc v1.67.6/go.mod h1:JAhxUVlolfYDErnwiqaLvUqc8nfb2r6S6slAgZOnaiE=
|
||||||
|
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
|
||||||
|
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
|
||||||
|
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
|
||||||
|
modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
|
||||||
|
modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8=
|
||||||
|
modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
|
||||||
|
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
|
||||||
|
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
|
||||||
|
modernc.org/sqlite v1.44.3 h1:+39JvV/HWMcYslAwRxHb8067w+2zowvFOUrOWIy9PjY=
|
||||||
|
modernc.org/sqlite v1.44.3/go.mod h1:CzbrU2lSB1DKUusvwGz7rqEKIq+NUd8GWuBBZDs9/nA=
|
||||||
|
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
|
||||||
|
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
|
||||||
|
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
||||||
|
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
||||||
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ const (
|
|||||||
AccountTypeOAuth = "oauth" // OAuth类型账号(full scope: profile + inference)
|
AccountTypeOAuth = "oauth" // OAuth类型账号(full scope: profile + inference)
|
||||||
AccountTypeSetupToken = "setup-token" // Setup Token类型账号(inference only scope)
|
AccountTypeSetupToken = "setup-token" // Setup Token类型账号(inference only scope)
|
||||||
AccountTypeAPIKey = "apikey" // API Key类型账号
|
AccountTypeAPIKey = "apikey" // API Key类型账号
|
||||||
|
AccountTypeUpstream = "upstream" // 上游透传类型账号(通过 Base URL + API Key 连接上游)
|
||||||
)
|
)
|
||||||
|
|
||||||
// Redeem type constants
|
// Redeem type constants
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ type CreateAccountRequest struct {
|
|||||||
Name string `json:"name" binding:"required"`
|
Name string `json:"name" binding:"required"`
|
||||||
Notes *string `json:"notes"`
|
Notes *string `json:"notes"`
|
||||||
Platform string `json:"platform" binding:"required"`
|
Platform string `json:"platform" binding:"required"`
|
||||||
Type string `json:"type" binding:"required,oneof=oauth setup-token apikey"`
|
Type string `json:"type" binding:"required,oneof=oauth setup-token apikey upstream"`
|
||||||
Credentials map[string]any `json:"credentials" binding:"required"`
|
Credentials map[string]any `json:"credentials" binding:"required"`
|
||||||
Extra map[string]any `json:"extra"`
|
Extra map[string]any `json:"extra"`
|
||||||
ProxyID *int64 `json:"proxy_id"`
|
ProxyID *int64 `json:"proxy_id"`
|
||||||
@@ -102,7 +102,7 @@ type CreateAccountRequest struct {
|
|||||||
type UpdateAccountRequest struct {
|
type UpdateAccountRequest struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Notes *string `json:"notes"`
|
Notes *string `json:"notes"`
|
||||||
Type string `json:"type" binding:"omitempty,oneof=oauth setup-token apikey"`
|
Type string `json:"type" binding:"omitempty,oneof=oauth setup-token apikey upstream"`
|
||||||
Credentials map[string]any `json:"credentials"`
|
Credentials map[string]any `json:"credentials"`
|
||||||
Extra map[string]any `json:"extra"`
|
Extra map[string]any `json:"extra"`
|
||||||
ProxyID *int64 `json:"proxy_id"`
|
ProxyID *int64 `json:"proxy_id"`
|
||||||
|
|||||||
@@ -45,6 +45,8 @@ type CreateGroupRequest struct {
|
|||||||
ModelRouting map[string][]int64 `json:"model_routing"`
|
ModelRouting map[string][]int64 `json:"model_routing"`
|
||||||
ModelRoutingEnabled bool `json:"model_routing_enabled"`
|
ModelRoutingEnabled bool `json:"model_routing_enabled"`
|
||||||
MCPXMLInject *bool `json:"mcp_xml_inject"`
|
MCPXMLInject *bool `json:"mcp_xml_inject"`
|
||||||
|
// 支持的模型系列(仅 antigravity 平台使用)
|
||||||
|
SupportedModelScopes []string `json:"supported_model_scopes"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateGroupRequest represents update group request
|
// UpdateGroupRequest represents update group request
|
||||||
@@ -70,6 +72,8 @@ type UpdateGroupRequest struct {
|
|||||||
ModelRouting map[string][]int64 `json:"model_routing"`
|
ModelRouting map[string][]int64 `json:"model_routing"`
|
||||||
ModelRoutingEnabled *bool `json:"model_routing_enabled"`
|
ModelRoutingEnabled *bool `json:"model_routing_enabled"`
|
||||||
MCPXMLInject *bool `json:"mcp_xml_inject"`
|
MCPXMLInject *bool `json:"mcp_xml_inject"`
|
||||||
|
// 支持的模型系列(仅 antigravity 平台使用)
|
||||||
|
SupportedModelScopes *[]string `json:"supported_model_scopes"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// List handles listing all groups with pagination
|
// List handles listing all groups with pagination
|
||||||
@@ -177,6 +181,7 @@ func (h *GroupHandler) Create(c *gin.Context) {
|
|||||||
ModelRouting: req.ModelRouting,
|
ModelRouting: req.ModelRouting,
|
||||||
ModelRoutingEnabled: req.ModelRoutingEnabled,
|
ModelRoutingEnabled: req.ModelRoutingEnabled,
|
||||||
MCPXMLInject: req.MCPXMLInject,
|
MCPXMLInject: req.MCPXMLInject,
|
||||||
|
SupportedModelScopes: req.SupportedModelScopes,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
response.ErrorFrom(c, err)
|
response.ErrorFrom(c, err)
|
||||||
@@ -221,6 +226,7 @@ func (h *GroupHandler) Update(c *gin.Context) {
|
|||||||
ModelRouting: req.ModelRouting,
|
ModelRouting: req.ModelRouting,
|
||||||
ModelRoutingEnabled: req.ModelRoutingEnabled,
|
ModelRoutingEnabled: req.ModelRoutingEnabled,
|
||||||
MCPXMLInject: req.MCPXMLInject,
|
MCPXMLInject: req.MCPXMLInject,
|
||||||
|
SupportedModelScopes: req.SupportedModelScopes,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
response.ErrorFrom(c, err)
|
response.ErrorFrom(c, err)
|
||||||
|
|||||||
@@ -88,6 +88,8 @@ type AdminGroup struct {
|
|||||||
// MCP XML 协议注入(仅 antigravity 平台使用)
|
// MCP XML 协议注入(仅 antigravity 平台使用)
|
||||||
MCPXMLInject bool `json:"mcp_xml_inject"`
|
MCPXMLInject bool `json:"mcp_xml_inject"`
|
||||||
|
|
||||||
|
// 支持的模型系列(仅 antigravity 平台使用)
|
||||||
|
SupportedModelScopes []string `json:"supported_model_scopes"`
|
||||||
AccountGroups []AccountGroup `json:"account_groups,omitempty"`
|
AccountGroups []AccountGroup `json:"account_groups,omitempty"`
|
||||||
AccountCount int64 `json:"account_count,omitempty"`
|
AccountCount int64 `json:"account_count,omitempty"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -140,6 +140,7 @@ func (r *apiKeyRepository) GetByKeyForAuth(ctx context.Context, key string) (*se
|
|||||||
group.FieldModelRoutingEnabled,
|
group.FieldModelRoutingEnabled,
|
||||||
group.FieldModelRouting,
|
group.FieldModelRouting,
|
||||||
group.FieldMcpXMLInject,
|
group.FieldMcpXMLInject,
|
||||||
|
group.FieldSupportedModelScopes,
|
||||||
)
|
)
|
||||||
}).
|
}).
|
||||||
Only(ctx)
|
Only(ctx)
|
||||||
@@ -433,6 +434,7 @@ func groupEntityToService(g *dbent.Group) *service.Group {
|
|||||||
ModelRouting: g.ModelRouting,
|
ModelRouting: g.ModelRouting,
|
||||||
ModelRoutingEnabled: g.ModelRoutingEnabled,
|
ModelRoutingEnabled: g.ModelRoutingEnabled,
|
||||||
MCPXMLInject: g.McpXMLInject,
|
MCPXMLInject: g.McpXMLInject,
|
||||||
|
SupportedModelScopes: g.SupportedModelScopes,
|
||||||
CreatedAt: g.CreatedAt,
|
CreatedAt: g.CreatedAt,
|
||||||
UpdatedAt: g.UpdatedAt,
|
UpdatedAt: g.UpdatedAt,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,6 +59,9 @@ func (r *groupRepository) Create(ctx context.Context, groupIn *service.Group) er
|
|||||||
builder = builder.SetModelRouting(groupIn.ModelRouting)
|
builder = builder.SetModelRouting(groupIn.ModelRouting)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 设置支持的模型系列(始终设置,空数组表示不限制)
|
||||||
|
builder = builder.SetSupportedModelScopes(groupIn.SupportedModelScopes)
|
||||||
|
|
||||||
created, err := builder.Save(ctx)
|
created, err := builder.Save(ctx)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
groupIn.ID = created.ID
|
groupIn.ID = created.ID
|
||||||
@@ -89,7 +92,6 @@ func (r *groupRepository) GetByIDLite(ctx context.Context, id int64) (*service.G
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, translatePersistenceError(err, service.ErrGroupNotFound, nil)
|
return nil, translatePersistenceError(err, service.ErrGroupNotFound, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
return groupEntityToService(m), nil
|
return groupEntityToService(m), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,6 +135,9 @@ func (r *groupRepository) Update(ctx context.Context, groupIn *service.Group) er
|
|||||||
builder = builder.ClearModelRouting()
|
builder = builder.ClearModelRouting()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 处理 SupportedModelScopes(始终设置,空数组表示不限制)
|
||||||
|
builder = builder.SetSupportedModelScopes(groupIn.SupportedModelScopes)
|
||||||
|
|
||||||
updated, err := builder.Save(ctx)
|
updated, err := builder.Save(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return translatePersistenceError(err, service.ErrGroupNotFound, service.ErrGroupExists)
|
return translatePersistenceError(err, service.ErrGroupNotFound, service.ErrGroupExists)
|
||||||
|
|||||||
@@ -113,6 +113,8 @@ type CreateGroupInput struct {
|
|||||||
ModelRouting map[string][]int64
|
ModelRouting map[string][]int64
|
||||||
ModelRoutingEnabled bool // 是否启用模型路由
|
ModelRoutingEnabled bool // 是否启用模型路由
|
||||||
MCPXMLInject *bool
|
MCPXMLInject *bool
|
||||||
|
// 支持的模型系列(仅 antigravity 平台使用)
|
||||||
|
SupportedModelScopes []string
|
||||||
}
|
}
|
||||||
|
|
||||||
type UpdateGroupInput struct {
|
type UpdateGroupInput struct {
|
||||||
@@ -138,6 +140,8 @@ type UpdateGroupInput struct {
|
|||||||
ModelRouting map[string][]int64
|
ModelRouting map[string][]int64
|
||||||
ModelRoutingEnabled *bool // 是否启用模型路由
|
ModelRoutingEnabled *bool // 是否启用模型路由
|
||||||
MCPXMLInject *bool
|
MCPXMLInject *bool
|
||||||
|
// 支持的模型系列(仅 antigravity 平台使用)
|
||||||
|
SupportedModelScopes *[]string
|
||||||
}
|
}
|
||||||
|
|
||||||
type CreateAccountInput struct {
|
type CreateAccountInput struct {
|
||||||
@@ -613,6 +617,7 @@ func (s *adminServiceImpl) CreateGroup(ctx context.Context, input *CreateGroupIn
|
|||||||
FallbackGroupIDOnInvalidRequest: fallbackOnInvalidRequest,
|
FallbackGroupIDOnInvalidRequest: fallbackOnInvalidRequest,
|
||||||
ModelRouting: input.ModelRouting,
|
ModelRouting: input.ModelRouting,
|
||||||
MCPXMLInject: mcpXMLInject,
|
MCPXMLInject: mcpXMLInject,
|
||||||
|
SupportedModelScopes: input.SupportedModelScopes,
|
||||||
}
|
}
|
||||||
if err := s.groupRepo.Create(ctx, group); err != nil {
|
if err := s.groupRepo.Create(ctx, group); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -797,6 +802,11 @@ func (s *adminServiceImpl) UpdateGroup(ctx context.Context, id int64, input *Upd
|
|||||||
group.MCPXMLInject = *input.MCPXMLInject
|
group.MCPXMLInject = *input.MCPXMLInject
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 支持的模型系列(仅 antigravity 平台使用)
|
||||||
|
if input.SupportedModelScopes != nil {
|
||||||
|
group.SupportedModelScopes = *input.SupportedModelScopes
|
||||||
|
}
|
||||||
|
|
||||||
if err := s.groupRepo.Update(ctx, group); err != nil {
|
if err := s.groupRepo.Update(ctx, group); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -412,6 +412,11 @@ type TestConnectionResult struct {
|
|||||||
// TestConnection 测试 Antigravity 账号连接(非流式,无重试、无计费)
|
// TestConnection 测试 Antigravity 账号连接(非流式,无重试、无计费)
|
||||||
// 支持 Claude 和 Gemini 两种协议,根据 modelID 前缀自动选择
|
// 支持 Claude 和 Gemini 两种协议,根据 modelID 前缀自动选择
|
||||||
func (s *AntigravityGatewayService) TestConnection(ctx context.Context, account *Account, modelID string) (*TestConnectionResult, error) {
|
func (s *AntigravityGatewayService) TestConnection(ctx context.Context, account *Account, modelID string) (*TestConnectionResult, error) {
|
||||||
|
// 上游透传账号使用专用测试方法
|
||||||
|
if account.Type == AccountTypeUpstream {
|
||||||
|
return s.testUpstreamConnection(ctx, account, modelID)
|
||||||
|
}
|
||||||
|
|
||||||
// 获取 token
|
// 获取 token
|
||||||
if s.tokenProvider == nil {
|
if s.tokenProvider == nil {
|
||||||
return nil, errors.New("antigravity token provider not configured")
|
return nil, errors.New("antigravity token provider not configured")
|
||||||
@@ -506,6 +511,87 @@ func (s *AntigravityGatewayService) TestConnection(ctx context.Context, account
|
|||||||
return nil, lastErr
|
return nil, lastErr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// testUpstreamConnection 测试上游透传账号连接
|
||||||
|
func (s *AntigravityGatewayService) testUpstreamConnection(ctx context.Context, account *Account, modelID string) (*TestConnectionResult, error) {
|
||||||
|
baseURL := strings.TrimSpace(account.GetCredential("base_url"))
|
||||||
|
apiKey := strings.TrimSpace(account.GetCredential("api_key"))
|
||||||
|
if baseURL == "" || apiKey == "" {
|
||||||
|
return nil, errors.New("upstream account missing base_url or api_key")
|
||||||
|
}
|
||||||
|
baseURL = strings.TrimSuffix(baseURL, "/")
|
||||||
|
|
||||||
|
// 使用 Claude 模型进行测试
|
||||||
|
if modelID == "" {
|
||||||
|
modelID = "claude-sonnet-4-20250514"
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建最小测试请求
|
||||||
|
testReq := map[string]any{
|
||||||
|
"model": modelID,
|
||||||
|
"max_tokens": 1,
|
||||||
|
"messages": []map[string]any{
|
||||||
|
{"role": "user", "content": "."},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
requestBody, err := json.Marshal(testReq)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("构建请求失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建 HTTP 请求
|
||||||
|
upstreamURL := baseURL + "/v1/messages"
|
||||||
|
req, err := http.NewRequestWithContext(ctx, http.MethodPost, upstreamURL, bytes.NewReader(requestBody))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("创建请求失败: %w", err)
|
||||||
|
}
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
req.Header.Set("Authorization", "Bearer "+apiKey)
|
||||||
|
req.Header.Set("x-api-key", apiKey)
|
||||||
|
req.Header.Set("anthropic-version", "2023-06-01")
|
||||||
|
|
||||||
|
// 代理 URL
|
||||||
|
proxyURL := ""
|
||||||
|
if account.ProxyID != nil && account.Proxy != nil {
|
||||||
|
proxyURL = account.Proxy.URL()
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("[antigravity-Test-Upstream] account=%s url=%s", account.Name, upstreamURL)
|
||||||
|
|
||||||
|
// 发送请求
|
||||||
|
resp, err := s.httpUpstream.Do(req, proxyURL, account.ID, account.Concurrency)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("请求失败: %w", err)
|
||||||
|
}
|
||||||
|
defer func() { _ = resp.Body.Close() }()
|
||||||
|
|
||||||
|
respBody, err := io.ReadAll(io.LimitReader(resp.Body, 2<<20))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("读取响应失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode >= 400 {
|
||||||
|
return nil, fmt.Errorf("API 返回 %d: %s", resp.StatusCode, string(respBody))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提取响应文本
|
||||||
|
var respData map[string]any
|
||||||
|
text := ""
|
||||||
|
if json.Unmarshal(respBody, &respData) == nil {
|
||||||
|
if content, ok := respData["content"].([]any); ok && len(content) > 0 {
|
||||||
|
if block, ok := content[0].(map[string]any); ok {
|
||||||
|
if t, ok := block["text"].(string); ok {
|
||||||
|
text = t
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &TestConnectionResult{
|
||||||
|
Text: text,
|
||||||
|
MappedModel: modelID,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
// buildGeminiTestRequest 构建 Gemini 格式测试请求
|
// buildGeminiTestRequest 构建 Gemini 格式测试请求
|
||||||
// 使用最小 token 消耗:输入 "." + maxOutputTokens: 1
|
// 使用最小 token 消耗:输入 "." + maxOutputTokens: 1
|
||||||
func (s *AntigravityGatewayService) buildGeminiTestRequest(projectID, model string) ([]byte, error) {
|
func (s *AntigravityGatewayService) buildGeminiTestRequest(projectID, model string) ([]byte, error) {
|
||||||
@@ -728,6 +814,11 @@ func isModelNotFoundError(statusCode int, body []byte) bool {
|
|||||||
|
|
||||||
// Forward 转发 Claude 协议请求(Claude → Gemini 转换)
|
// Forward 转发 Claude 协议请求(Claude → Gemini 转换)
|
||||||
func (s *AntigravityGatewayService) Forward(ctx context.Context, c *gin.Context, account *Account, body []byte) (*ForwardResult, error) {
|
func (s *AntigravityGatewayService) Forward(ctx context.Context, c *gin.Context, account *Account, body []byte) (*ForwardResult, error) {
|
||||||
|
// 上游透传账号直接转发,不走 OAuth token 刷新
|
||||||
|
if account.Type == AccountTypeUpstream {
|
||||||
|
return s.ForwardUpstream(ctx, c, account, body)
|
||||||
|
}
|
||||||
|
|
||||||
startTime := time.Now()
|
startTime := time.Now()
|
||||||
sessionID := getSessionID(c)
|
sessionID := getSessionID(c)
|
||||||
prefix := logPrefix(sessionID, account.Name)
|
prefix := logPrefix(sessionID, account.Name)
|
||||||
@@ -1349,6 +1440,208 @@ func stripSignatureSensitiveBlocksFromClaudeRequest(req *antigravity.ClaudeReque
|
|||||||
return changed, nil
|
return changed, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ForwardUpstream 透传请求到上游 Antigravity 服务
|
||||||
|
// 用于 upstream 类型账号,直接使用 base_url + api_key 转发,不走 OAuth token
|
||||||
|
func (s *AntigravityGatewayService) ForwardUpstream(ctx context.Context, c *gin.Context, account *Account, body []byte) (*ForwardResult, error) {
|
||||||
|
startTime := time.Now()
|
||||||
|
sessionID := getSessionID(c)
|
||||||
|
prefix := logPrefix(sessionID, account.Name)
|
||||||
|
|
||||||
|
// 获取上游配置
|
||||||
|
baseURL := strings.TrimSpace(account.GetCredential("base_url"))
|
||||||
|
apiKey := strings.TrimSpace(account.GetCredential("api_key"))
|
||||||
|
if baseURL == "" || apiKey == "" {
|
||||||
|
return nil, fmt.Errorf("upstream account missing base_url or api_key")
|
||||||
|
}
|
||||||
|
baseURL = strings.TrimSuffix(baseURL, "/")
|
||||||
|
|
||||||
|
// 解析请求获取模型信息
|
||||||
|
var claudeReq antigravity.ClaudeRequest
|
||||||
|
if err := json.Unmarshal(body, &claudeReq); err != nil {
|
||||||
|
return nil, fmt.Errorf("parse claude request: %w", err)
|
||||||
|
}
|
||||||
|
if strings.TrimSpace(claudeReq.Model) == "" {
|
||||||
|
return nil, fmt.Errorf("missing model")
|
||||||
|
}
|
||||||
|
originalModel := claudeReq.Model
|
||||||
|
billingModel := originalModel
|
||||||
|
|
||||||
|
// 构建上游请求 URL
|
||||||
|
upstreamURL := baseURL + "/v1/messages"
|
||||||
|
|
||||||
|
// 创建请求
|
||||||
|
req, err := http.NewRequestWithContext(ctx, http.MethodPost, upstreamURL, bytes.NewReader(body))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("create upstream request: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置请求头
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
req.Header.Set("Authorization", "Bearer "+apiKey)
|
||||||
|
req.Header.Set("x-api-key", apiKey) // Claude API 兼容
|
||||||
|
|
||||||
|
// 透传 Claude 相关 headers
|
||||||
|
if v := c.GetHeader("anthropic-version"); v != "" {
|
||||||
|
req.Header.Set("anthropic-version", v)
|
||||||
|
}
|
||||||
|
if v := c.GetHeader("anthropic-beta"); v != "" {
|
||||||
|
req.Header.Set("anthropic-beta", v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 代理 URL
|
||||||
|
proxyURL := ""
|
||||||
|
if account.ProxyID != nil && account.Proxy != nil {
|
||||||
|
proxyURL = account.Proxy.URL()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发送请求
|
||||||
|
resp, err := s.httpUpstream.Do(req, proxyURL, account.ID, account.Concurrency)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("%s upstream request failed: %v", prefix, err)
|
||||||
|
return nil, fmt.Errorf("upstream request failed: %w", err)
|
||||||
|
}
|
||||||
|
defer func() { _ = resp.Body.Close() }()
|
||||||
|
|
||||||
|
// 处理错误响应
|
||||||
|
if resp.StatusCode >= 400 {
|
||||||
|
respBody, _ := io.ReadAll(io.LimitReader(resp.Body, 2<<20))
|
||||||
|
|
||||||
|
// 429 错误时标记账号限流
|
||||||
|
if resp.StatusCode == http.StatusTooManyRequests {
|
||||||
|
s.handleUpstreamError(ctx, prefix, account, resp.StatusCode, resp.Header, respBody, AntigravityQuotaScopeClaude)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 透传上游错误
|
||||||
|
c.Header("Content-Type", resp.Header.Get("Content-Type"))
|
||||||
|
c.Status(resp.StatusCode)
|
||||||
|
_, _ = c.Writer.Write(respBody)
|
||||||
|
|
||||||
|
return &ForwardResult{
|
||||||
|
Model: billingModel,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理成功响应(流式/非流式)
|
||||||
|
var usage *ClaudeUsage
|
||||||
|
var firstTokenMs *int
|
||||||
|
|
||||||
|
if claudeReq.Stream {
|
||||||
|
// 流式响应:透传
|
||||||
|
c.Header("Content-Type", "text/event-stream")
|
||||||
|
c.Header("Cache-Control", "no-cache")
|
||||||
|
c.Header("Connection", "keep-alive")
|
||||||
|
c.Header("X-Accel-Buffering", "no")
|
||||||
|
c.Status(http.StatusOK)
|
||||||
|
|
||||||
|
usage, firstTokenMs = s.streamUpstreamResponse(c, resp, startTime)
|
||||||
|
} else {
|
||||||
|
// 非流式响应:直接透传
|
||||||
|
respBody, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("read upstream response: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提取 usage
|
||||||
|
usage = s.extractClaudeUsage(respBody)
|
||||||
|
|
||||||
|
c.Header("Content-Type", resp.Header.Get("Content-Type"))
|
||||||
|
c.Status(http.StatusOK)
|
||||||
|
_, _ = c.Writer.Write(respBody)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建计费结果
|
||||||
|
duration := time.Since(startTime)
|
||||||
|
log.Printf("%s status=success duration_ms=%d", prefix, duration.Milliseconds())
|
||||||
|
|
||||||
|
return &ForwardResult{
|
||||||
|
Model: billingModel,
|
||||||
|
Stream: claudeReq.Stream,
|
||||||
|
Duration: duration,
|
||||||
|
FirstTokenMs: firstTokenMs,
|
||||||
|
Usage: ClaudeUsage{
|
||||||
|
InputTokens: usage.InputTokens,
|
||||||
|
OutputTokens: usage.OutputTokens,
|
||||||
|
CacheReadInputTokens: usage.CacheReadInputTokens,
|
||||||
|
CacheCreationInputTokens: usage.CacheCreationInputTokens,
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// streamUpstreamResponse 透传上游流式响应并提取 usage
|
||||||
|
func (s *AntigravityGatewayService) streamUpstreamResponse(c *gin.Context, resp *http.Response, startTime time.Time) (*ClaudeUsage, *int) {
|
||||||
|
usage := &ClaudeUsage{}
|
||||||
|
var firstTokenMs *int
|
||||||
|
var firstTokenRecorded bool
|
||||||
|
|
||||||
|
scanner := bufio.NewScanner(resp.Body)
|
||||||
|
buf := make([]byte, 0, 64*1024)
|
||||||
|
scanner.Buffer(buf, 1024*1024)
|
||||||
|
|
||||||
|
for scanner.Scan() {
|
||||||
|
line := scanner.Bytes()
|
||||||
|
|
||||||
|
// 记录首 token 时间
|
||||||
|
if !firstTokenRecorded && len(line) > 0 {
|
||||||
|
ms := int(time.Since(startTime).Milliseconds())
|
||||||
|
firstTokenMs = &ms
|
||||||
|
firstTokenRecorded = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 尝试从 message_delta 或 message_stop 事件提取 usage
|
||||||
|
if bytes.HasPrefix(line, []byte("data: ")) {
|
||||||
|
dataStr := bytes.TrimPrefix(line, []byte("data: "))
|
||||||
|
var event map[string]any
|
||||||
|
if json.Unmarshal(dataStr, &event) == nil {
|
||||||
|
if u, ok := event["usage"].(map[string]any); ok {
|
||||||
|
if v, ok := u["input_tokens"].(float64); ok && int(v) > 0 {
|
||||||
|
usage.InputTokens = int(v)
|
||||||
|
}
|
||||||
|
if v, ok := u["output_tokens"].(float64); ok && int(v) > 0 {
|
||||||
|
usage.OutputTokens = int(v)
|
||||||
|
}
|
||||||
|
if v, ok := u["cache_read_input_tokens"].(float64); ok && int(v) > 0 {
|
||||||
|
usage.CacheReadInputTokens = int(v)
|
||||||
|
}
|
||||||
|
if v, ok := u["cache_creation_input_tokens"].(float64); ok && int(v) > 0 {
|
||||||
|
usage.CacheCreationInputTokens = int(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 透传行
|
||||||
|
_, _ = c.Writer.Write(line)
|
||||||
|
_, _ = c.Writer.Write([]byte("\n"))
|
||||||
|
c.Writer.Flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
return usage, firstTokenMs
|
||||||
|
}
|
||||||
|
|
||||||
|
// extractClaudeUsage 从非流式 Claude 响应提取 usage
|
||||||
|
func (s *AntigravityGatewayService) extractClaudeUsage(body []byte) *ClaudeUsage {
|
||||||
|
usage := &ClaudeUsage{}
|
||||||
|
var resp map[string]any
|
||||||
|
if json.Unmarshal(body, &resp) != nil {
|
||||||
|
return usage
|
||||||
|
}
|
||||||
|
if u, ok := resp["usage"].(map[string]any); ok {
|
||||||
|
if v, ok := u["input_tokens"].(float64); ok {
|
||||||
|
usage.InputTokens = int(v)
|
||||||
|
}
|
||||||
|
if v, ok := u["output_tokens"].(float64); ok {
|
||||||
|
usage.OutputTokens = int(v)
|
||||||
|
}
|
||||||
|
if v, ok := u["cache_read_input_tokens"].(float64); ok {
|
||||||
|
usage.CacheReadInputTokens = int(v)
|
||||||
|
}
|
||||||
|
if v, ok := u["cache_creation_input_tokens"].(float64); ok {
|
||||||
|
usage.CacheCreationInputTokens = int(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return usage
|
||||||
|
}
|
||||||
|
|
||||||
// ForwardGemini 转发 Gemini 协议请求
|
// ForwardGemini 转发 Gemini 协议请求
|
||||||
func (s *AntigravityGatewayService) ForwardGemini(ctx context.Context, c *gin.Context, account *Account, originalModel string, action string, stream bool, body []byte) (*ForwardResult, error) {
|
func (s *AntigravityGatewayService) ForwardGemini(ctx context.Context, c *gin.Context, account *Account, originalModel string, action string, stream bool, body []byte) (*ForwardResult, error) {
|
||||||
startTime := time.Now()
|
startTime := time.Now()
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package service
|
package service
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
@@ -16,6 +17,21 @@ const (
|
|||||||
AntigravityQuotaScopeGeminiImage AntigravityQuotaScope = "gemini_image"
|
AntigravityQuotaScopeGeminiImage AntigravityQuotaScope = "gemini_image"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// IsScopeSupported 检查给定的 scope 是否在分组支持的 scope 列表中
|
||||||
|
func IsScopeSupported(supportedScopes []string, scope AntigravityQuotaScope) bool {
|
||||||
|
if len(supportedScopes) == 0 {
|
||||||
|
// 未配置时默认全部支持
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
supported := slices.Contains(supportedScopes, string(scope))
|
||||||
|
return supported
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResolveAntigravityQuotaScope 根据模型名称解析配额域(导出版本)
|
||||||
|
func ResolveAntigravityQuotaScope(requestedModel string) (AntigravityQuotaScope, bool) {
|
||||||
|
return resolveAntigravityQuotaScope(requestedModel)
|
||||||
|
}
|
||||||
|
|
||||||
// resolveAntigravityQuotaScope 根据模型名称解析配额域
|
// resolveAntigravityQuotaScope 根据模型名称解析配额域
|
||||||
func resolveAntigravityQuotaScope(requestedModel string) (AntigravityQuotaScope, bool) {
|
func resolveAntigravityQuotaScope(requestedModel string) (AntigravityQuotaScope, bool) {
|
||||||
model := normalizeAntigravityModelName(requestedModel)
|
model := normalizeAntigravityModelName(requestedModel)
|
||||||
|
|||||||
@@ -44,6 +44,9 @@ type APIKeyAuthGroupSnapshot struct {
|
|||||||
ModelRouting map[string][]int64 `json:"model_routing,omitempty"`
|
ModelRouting map[string][]int64 `json:"model_routing,omitempty"`
|
||||||
ModelRoutingEnabled bool `json:"model_routing_enabled"`
|
ModelRoutingEnabled bool `json:"model_routing_enabled"`
|
||||||
MCPXMLInject bool `json:"mcp_xml_inject"`
|
MCPXMLInject bool `json:"mcp_xml_inject"`
|
||||||
|
|
||||||
|
// 支持的模型系列(仅 antigravity 平台使用)
|
||||||
|
SupportedModelScopes []string `json:"supported_model_scopes,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// APIKeyAuthCacheEntry 缓存条目,支持负缓存
|
// APIKeyAuthCacheEntry 缓存条目,支持负缓存
|
||||||
|
|||||||
@@ -241,6 +241,7 @@ func (s *APIKeyService) snapshotFromAPIKey(apiKey *APIKey) *APIKeyAuthSnapshot {
|
|||||||
ModelRouting: apiKey.Group.ModelRouting,
|
ModelRouting: apiKey.Group.ModelRouting,
|
||||||
ModelRoutingEnabled: apiKey.Group.ModelRoutingEnabled,
|
ModelRoutingEnabled: apiKey.Group.ModelRoutingEnabled,
|
||||||
MCPXMLInject: apiKey.Group.MCPXMLInject,
|
MCPXMLInject: apiKey.Group.MCPXMLInject,
|
||||||
|
SupportedModelScopes: apiKey.Group.SupportedModelScopes,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return snapshot
|
return snapshot
|
||||||
@@ -287,6 +288,7 @@ func (s *APIKeyService) snapshotToAPIKey(key string, snapshot *APIKeyAuthSnapsho
|
|||||||
ModelRouting: snapshot.Group.ModelRouting,
|
ModelRouting: snapshot.Group.ModelRouting,
|
||||||
ModelRoutingEnabled: snapshot.Group.ModelRoutingEnabled,
|
ModelRoutingEnabled: snapshot.Group.ModelRoutingEnabled,
|
||||||
MCPXMLInject: snapshot.Group.MCPXMLInject,
|
MCPXMLInject: snapshot.Group.MCPXMLInject,
|
||||||
|
SupportedModelScopes: snapshot.Group.SupportedModelScopes,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return apiKey
|
return apiKey
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ const (
|
|||||||
AccountTypeOAuth = domain.AccountTypeOAuth // OAuth类型账号(full scope: profile + inference)
|
AccountTypeOAuth = domain.AccountTypeOAuth // OAuth类型账号(full scope: profile + inference)
|
||||||
AccountTypeSetupToken = domain.AccountTypeSetupToken // Setup Token类型账号(inference only scope)
|
AccountTypeSetupToken = domain.AccountTypeSetupToken // Setup Token类型账号(inference only scope)
|
||||||
AccountTypeAPIKey = domain.AccountTypeAPIKey // API Key类型账号
|
AccountTypeAPIKey = domain.AccountTypeAPIKey // API Key类型账号
|
||||||
|
AccountTypeUpstream = domain.AccountTypeUpstream // 上游透传类型账号(通过 Base URL + API Key 连接上游)
|
||||||
)
|
)
|
||||||
|
|
||||||
// Redeem type constants
|
// Redeem type constants
|
||||||
|
|||||||
@@ -92,6 +92,9 @@ var (
|
|||||||
// ErrClaudeCodeOnly 表示分组仅允许 Claude Code 客户端访问
|
// ErrClaudeCodeOnly 表示分组仅允许 Claude Code 客户端访问
|
||||||
var ErrClaudeCodeOnly = errors.New("this group only allows Claude Code clients")
|
var ErrClaudeCodeOnly = errors.New("this group only allows Claude Code clients")
|
||||||
|
|
||||||
|
// ErrModelScopeNotSupported 表示请求的模型系列不在分组支持的范围内
|
||||||
|
var ErrModelScopeNotSupported = errors.New("model scope not supported by this group")
|
||||||
|
|
||||||
// allowedHeaders 白名单headers(参考CRS项目)
|
// allowedHeaders 白名单headers(参考CRS项目)
|
||||||
var allowedHeaders = map[string]bool{
|
var allowedHeaders = map[string]bool{
|
||||||
"accept": true,
|
"accept": true,
|
||||||
@@ -582,6 +585,13 @@ func (s *GatewayService) SelectAccountWithLoadAwareness(ctx context.Context, gro
|
|||||||
log.Printf("[ModelRoutingDebug] load-aware enabled: group_id=%v model=%s session=%s platform=%s", derefGroupID(groupID), requestedModel, shortSessionHash(sessionHash), platform)
|
log.Printf("[ModelRoutingDebug] load-aware enabled: group_id=%v model=%s session=%s platform=%s", derefGroupID(groupID), requestedModel, shortSessionHash(sessionHash), platform)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Antigravity 模型系列检查(在账号选择前检查,确保所有代码路径都经过此检查)
|
||||||
|
if platform == PlatformAntigravity && groupID != nil && requestedModel != "" {
|
||||||
|
if err := s.checkAntigravityModelScope(ctx, *groupID, requestedModel); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
accounts, useMixed, err := s.listSchedulableAccounts(ctx, groupID, platform, hasForcePlatform)
|
accounts, useMixed, err := s.listSchedulableAccounts(ctx, groupID, platform, hasForcePlatform)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -1477,6 +1487,13 @@ func shuffleWithinPriority(accounts []*Account) {
|
|||||||
|
|
||||||
// selectAccountForModelWithPlatform 选择单平台账户(完全隔离)
|
// selectAccountForModelWithPlatform 选择单平台账户(完全隔离)
|
||||||
func (s *GatewayService) selectAccountForModelWithPlatform(ctx context.Context, groupID *int64, sessionHash string, requestedModel string, excludedIDs map[int64]struct{}, platform string) (*Account, error) {
|
func (s *GatewayService) selectAccountForModelWithPlatform(ctx context.Context, groupID *int64, sessionHash string, requestedModel string, excludedIDs map[int64]struct{}, platform string) (*Account, error) {
|
||||||
|
// 对 Antigravity 平台,检查请求的模型系列是否在分组支持范围内
|
||||||
|
if platform == PlatformAntigravity && groupID != nil && requestedModel != "" {
|
||||||
|
if err := s.checkAntigravityModelScope(ctx, *groupID, requestedModel); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
preferOAuth := platform == PlatformGemini
|
preferOAuth := platform == PlatformGemini
|
||||||
routingAccountIDs := s.routingAccountIDsForRequest(ctx, groupID, requestedModel, platform)
|
routingAccountIDs := s.routingAccountIDsForRequest(ctx, groupID, requestedModel, platform)
|
||||||
|
|
||||||
@@ -3898,6 +3915,27 @@ func (s *GatewayService) validateUpstreamBaseURL(raw string) (string, error) {
|
|||||||
return normalized, nil
|
return normalized, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// checkAntigravityModelScope 检查 Antigravity 平台的模型系列是否在分组支持范围内
|
||||||
|
func (s *GatewayService) checkAntigravityModelScope(ctx context.Context, groupID int64, requestedModel string) error {
|
||||||
|
scope, ok := ResolveAntigravityQuotaScope(requestedModel)
|
||||||
|
if !ok {
|
||||||
|
return nil // 无法解析 scope,跳过检查
|
||||||
|
}
|
||||||
|
|
||||||
|
group, err := s.resolveGroupByID(ctx, groupID)
|
||||||
|
if err != nil {
|
||||||
|
return nil // 查询失败时放行
|
||||||
|
}
|
||||||
|
if group == nil {
|
||||||
|
return nil // 分组不存在时放行
|
||||||
|
}
|
||||||
|
|
||||||
|
if !IsScopeSupported(group.SupportedModelScopes, scope) {
|
||||||
|
return ErrModelScopeNotSupported
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// GetAvailableModels returns the list of models available for a group
|
// GetAvailableModels returns the list of models available for a group
|
||||||
// It aggregates model_mapping keys from all schedulable accounts in the group
|
// It aggregates model_mapping keys from all schedulable accounts in the group
|
||||||
func (s *GatewayService) GetAvailableModels(ctx context.Context, groupID *int64, platform string) []string {
|
func (s *GatewayService) GetAvailableModels(ctx context.Context, groupID *int64, platform string) []string {
|
||||||
|
|||||||
@@ -41,6 +41,10 @@ type Group struct {
|
|||||||
// MCP XML 协议注入开关(仅 antigravity 平台使用)
|
// MCP XML 协议注入开关(仅 antigravity 平台使用)
|
||||||
MCPXMLInject bool
|
MCPXMLInject bool
|
||||||
|
|
||||||
|
// 支持的模型系列(仅 antigravity 平台使用)
|
||||||
|
// 可选值: claude, gemini_text, gemini_image
|
||||||
|
SupportedModelScopes []string
|
||||||
|
|
||||||
CreatedAt time.Time
|
CreatedAt time.Time
|
||||||
UpdatedAt time.Time
|
UpdatedAt time.Time
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,11 @@ type User struct {
|
|||||||
CreatedAt time.Time
|
CreatedAt time.Time
|
||||||
UpdatedAt time.Time
|
UpdatedAt time.Time
|
||||||
|
|
||||||
|
// TOTP 双因素认证字段
|
||||||
|
TotpSecretEncrypted *string // AES-256-GCM 加密的 TOTP 密钥
|
||||||
|
TotpEnabled bool // 是否启用 TOTP
|
||||||
|
TotpEnabledAt *time.Time // TOTP 启用时间
|
||||||
|
|
||||||
APIKeys []APIKey
|
APIKeys []APIKey
|
||||||
Subscriptions []UserSubscription
|
Subscriptions []UserSubscription
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ type UserRepository interface {
|
|||||||
ExistsByEmail(ctx context.Context, email string) (bool, error)
|
ExistsByEmail(ctx context.Context, email string) (bool, error)
|
||||||
RemoveGroupFromAllowedGroups(ctx context.Context, groupID int64) (int64, error)
|
RemoveGroupFromAllowedGroups(ctx context.Context, groupID int64) (int64, error)
|
||||||
|
|
||||||
// TOTP 相关方法
|
// TOTP 双因素认证
|
||||||
UpdateTotpSecret(ctx context.Context, userID int64, encryptedSecret *string) error
|
UpdateTotpSecret(ctx context.Context, userID int64, encryptedSecret *string) error
|
||||||
EnableTotp(ctx context.Context, userID int64) error
|
EnableTotp(ctx context.Context, userID int64) error
|
||||||
DisableTotp(ctx context.Context, userID int64) error
|
DisableTotp(ctx context.Context, userID int64) error
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
-- 添加分组支持的模型系列字段
|
||||||
|
ALTER TABLE groups
|
||||||
|
ADD COLUMN IF NOT EXISTS supported_model_scopes JSONB NOT NULL
|
||||||
|
DEFAULT '["claude", "gemini_text", "gemini_image"]'::jsonb;
|
||||||
|
|
||||||
|
COMMENT ON COLUMN groups.supported_model_scopes IS '支持的模型系列:claude, gemini_text, gemini_image';
|
||||||
27
docs/rename_local_migrations_20260202.sql
Normal file
27
docs/rename_local_migrations_20260202.sql
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
-- 修正 schema_migrations 中“本地改名”的迁移文件名
|
||||||
|
-- 适用场景:你已执行过旧文件名的迁移,合并后仅改了自己这边的文件名
|
||||||
|
|
||||||
|
BEGIN;
|
||||||
|
|
||||||
|
UPDATE schema_migrations
|
||||||
|
SET filename = '042b_add_ops_system_metrics_switch_count.sql'
|
||||||
|
WHERE filename = '042_add_ops_system_metrics_switch_count.sql'
|
||||||
|
AND NOT EXISTS (
|
||||||
|
SELECT 1 FROM schema_migrations WHERE filename = '042b_add_ops_system_metrics_switch_count.sql'
|
||||||
|
);
|
||||||
|
|
||||||
|
UPDATE schema_migrations
|
||||||
|
SET filename = '043b_add_group_invalid_request_fallback.sql'
|
||||||
|
WHERE filename = '043_add_group_invalid_request_fallback.sql'
|
||||||
|
AND NOT EXISTS (
|
||||||
|
SELECT 1 FROM schema_migrations WHERE filename = '043b_add_group_invalid_request_fallback.sql'
|
||||||
|
);
|
||||||
|
|
||||||
|
UPDATE schema_migrations
|
||||||
|
SET filename = '044b_add_group_mcp_xml_inject.sql'
|
||||||
|
WHERE filename = '044_add_group_mcp_xml_inject.sql'
|
||||||
|
AND NOT EXISTS (
|
||||||
|
SELECT 1 FROM schema_migrations WHERE filename = '044b_add_group_mcp_xml_inject.sql'
|
||||||
|
);
|
||||||
|
|
||||||
|
COMMIT;
|
||||||
@@ -614,21 +614,87 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Account Type Selection (Antigravity - OAuth only) -->
|
<!-- Account Type Selection (Antigravity - OAuth or Upstream) -->
|
||||||
<div v-if="form.platform === 'antigravity'">
|
<div v-if="form.platform === 'antigravity'">
|
||||||
<label class="input-label">{{ t('admin.accounts.accountType') }}</label>
|
<label class="input-label">{{ t('admin.accounts.accountType') }}</label>
|
||||||
<div class="mt-2">
|
<div class="mt-2 grid grid-cols-2 gap-3">
|
||||||
<div
|
<button
|
||||||
class="flex items-center gap-3 rounded-lg border-2 border-purple-500 bg-purple-50 p-3 dark:bg-purple-900/20"
|
type="button"
|
||||||
|
@click="antigravityAccountType = 'oauth'"
|
||||||
|
:class="[
|
||||||
|
'flex items-center gap-3 rounded-lg border-2 p-3 text-left transition-all',
|
||||||
|
antigravityAccountType === 'oauth'
|
||||||
|
? 'border-purple-500 bg-purple-50 dark:bg-purple-900/20'
|
||||||
|
: 'border-gray-200 hover:border-purple-300 dark:border-dark-600 dark:hover:border-purple-700'
|
||||||
|
]"
|
||||||
>
|
>
|
||||||
<div class="flex h-8 w-8 shrink-0 items-center justify-center rounded-lg bg-purple-500 text-white">
|
<div
|
||||||
|
:class="[
|
||||||
|
'flex h-8 w-8 shrink-0 items-center justify-center rounded-lg',
|
||||||
|
antigravityAccountType === 'oauth'
|
||||||
|
? 'bg-purple-500 text-white'
|
||||||
|
: 'bg-gray-100 text-gray-500 dark:bg-dark-600 dark:text-gray-400'
|
||||||
|
]"
|
||||||
|
>
|
||||||
<Icon name="key" size="sm" />
|
<Icon name="key" size="sm" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<span class="block text-sm font-medium text-gray-900 dark:text-white">OAuth</span>
|
<span class="block text-sm font-medium text-gray-900 dark:text-white">OAuth</span>
|
||||||
<span class="text-xs text-gray-500 dark:text-gray-400">{{ t('admin.accounts.types.antigravityOauth') }}</span>
|
<span class="text-xs text-gray-500 dark:text-gray-400">{{ t('admin.accounts.types.antigravityOauth') }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
@click="antigravityAccountType = 'upstream'"
|
||||||
|
:class="[
|
||||||
|
'flex items-center gap-3 rounded-lg border-2 p-3 text-left transition-all',
|
||||||
|
antigravityAccountType === 'upstream'
|
||||||
|
? 'border-purple-500 bg-purple-50 dark:bg-purple-900/20'
|
||||||
|
: 'border-gray-200 hover:border-purple-300 dark:border-dark-600 dark:hover:border-purple-700'
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
:class="[
|
||||||
|
'flex h-8 w-8 shrink-0 items-center justify-center rounded-lg',
|
||||||
|
antigravityAccountType === 'upstream'
|
||||||
|
? 'bg-purple-500 text-white'
|
||||||
|
: 'bg-gray-100 text-gray-500 dark:bg-dark-600 dark:text-gray-400'
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
<Icon name="cloud" size="sm" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span class="block text-sm font-medium text-gray-900 dark:text-white">{{ t('admin.accounts.types.upstream') }}</span>
|
||||||
|
<span class="text-xs text-gray-500 dark:text-gray-400">{{ t('admin.accounts.types.upstreamDesc') }}</span>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Upstream config (only for Antigravity upstream type) -->
|
||||||
|
<div v-if="form.platform === 'antigravity' && antigravityAccountType === 'upstream'" class="space-y-4">
|
||||||
|
<div>
|
||||||
|
<label class="input-label">{{ t('admin.accounts.upstream.baseUrl') }}</label>
|
||||||
|
<input
|
||||||
|
v-model="upstreamBaseUrl"
|
||||||
|
type="text"
|
||||||
|
required
|
||||||
|
class="input"
|
||||||
|
placeholder="https://upstream.example.com"
|
||||||
|
/>
|
||||||
|
<p class="input-hint">{{ t('admin.accounts.upstream.baseUrlHint') }}</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="input-label">{{ t('admin.accounts.upstream.apiKey') }}</label>
|
||||||
|
<input
|
||||||
|
v-model="upstreamApiKey"
|
||||||
|
type="password"
|
||||||
|
required
|
||||||
|
class="input font-mono"
|
||||||
|
placeholder="sk-..."
|
||||||
|
/>
|
||||||
|
<p class="input-hint">{{ t('admin.accounts.upstream.apiKeyHint') }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -1940,6 +2006,9 @@ const customErrorCodeInput = ref<number | null>(null)
|
|||||||
const interceptWarmupRequests = ref(false)
|
const interceptWarmupRequests = ref(false)
|
||||||
const autoPauseOnExpired = ref(true)
|
const autoPauseOnExpired = ref(true)
|
||||||
const mixedScheduling = ref(false) // For antigravity accounts: enable mixed scheduling
|
const mixedScheduling = ref(false) // For antigravity accounts: enable mixed scheduling
|
||||||
|
const antigravityAccountType = ref<'oauth' | 'upstream'>('oauth') // For antigravity: oauth or upstream
|
||||||
|
const upstreamBaseUrl = ref('') // For upstream type: base URL
|
||||||
|
const upstreamApiKey = ref('') // For upstream type: API key
|
||||||
const tempUnschedEnabled = ref(false)
|
const tempUnschedEnabled = ref(false)
|
||||||
const tempUnschedRules = ref<TempUnschedRuleForm[]>([])
|
const tempUnschedRules = ref<TempUnschedRuleForm[]>([])
|
||||||
const geminiOAuthType = ref<'code_assist' | 'google_one' | 'ai_studio'>('google_one')
|
const geminiOAuthType = ref<'code_assist' | 'google_one' | 'ai_studio'>('google_one')
|
||||||
@@ -2037,7 +2106,13 @@ const form = reactive({
|
|||||||
})
|
})
|
||||||
|
|
||||||
// Helper to check if current type needs OAuth flow
|
// Helper to check if current type needs OAuth flow
|
||||||
const isOAuthFlow = computed(() => accountCategory.value === 'oauth-based')
|
const isOAuthFlow = computed(() => {
|
||||||
|
// Antigravity upstream 类型不需要 OAuth 流程
|
||||||
|
if (form.platform === 'antigravity' && antigravityAccountType.value === 'upstream') {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return accountCategory.value === 'oauth-based'
|
||||||
|
})
|
||||||
|
|
||||||
const isManualInputMethod = computed(() => {
|
const isManualInputMethod = computed(() => {
|
||||||
return oauthFlowRef.value?.inputMethod === 'manual'
|
return oauthFlowRef.value?.inputMethod === 'manual'
|
||||||
@@ -2077,10 +2152,15 @@ watch(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
// Sync form.type based on accountCategory and addMethod
|
// Sync form.type based on accountCategory, addMethod, and antigravityAccountType
|
||||||
watch(
|
watch(
|
||||||
[accountCategory, addMethod],
|
[accountCategory, addMethod, antigravityAccountType],
|
||||||
([category, method]) => {
|
([category, method, agType]) => {
|
||||||
|
// Antigravity upstream 类型
|
||||||
|
if (form.platform === 'antigravity' && agType === 'upstream') {
|
||||||
|
form.type = 'upstream'
|
||||||
|
return
|
||||||
|
}
|
||||||
if (category === 'oauth-based') {
|
if (category === 'oauth-based') {
|
||||||
form.type = method as AccountType // 'oauth' or 'setup-token'
|
form.type = method as AccountType // 'oauth' or 'setup-token'
|
||||||
} else {
|
} else {
|
||||||
@@ -2108,9 +2188,10 @@ watch(
|
|||||||
if (newPlatform !== 'anthropic') {
|
if (newPlatform !== 'anthropic') {
|
||||||
interceptWarmupRequests.value = false
|
interceptWarmupRequests.value = false
|
||||||
}
|
}
|
||||||
// Antigravity only supports OAuth
|
// Antigravity: reset to OAuth by default, but allow upstream selection
|
||||||
if (newPlatform === 'antigravity') {
|
if (newPlatform === 'antigravity') {
|
||||||
accountCategory.value = 'oauth-based'
|
accountCategory.value = 'oauth-based'
|
||||||
|
antigravityAccountType.value = 'oauth'
|
||||||
}
|
}
|
||||||
// Reset OAuth states
|
// Reset OAuth states
|
||||||
oauth.resetState()
|
oauth.resetState()
|
||||||
@@ -2343,6 +2424,9 @@ const resetForm = () => {
|
|||||||
sessionIdleTimeout.value = null
|
sessionIdleTimeout.value = null
|
||||||
tlsFingerprintEnabled.value = false
|
tlsFingerprintEnabled.value = false
|
||||||
sessionIdMaskingEnabled.value = false
|
sessionIdMaskingEnabled.value = false
|
||||||
|
antigravityAccountType.value = 'oauth'
|
||||||
|
upstreamBaseUrl.value = ''
|
||||||
|
upstreamApiKey.value = ''
|
||||||
tempUnschedEnabled.value = false
|
tempUnschedEnabled.value = false
|
||||||
tempUnschedRules.value = []
|
tempUnschedRules.value = []
|
||||||
geminiOAuthType.value = 'code_assist'
|
geminiOAuthType.value = 'code_assist'
|
||||||
@@ -2371,6 +2455,36 @@ const handleSubmit = async () => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// For Antigravity upstream type, create directly
|
||||||
|
if (form.platform === 'antigravity' && antigravityAccountType.value === 'upstream') {
|
||||||
|
if (!form.name.trim()) {
|
||||||
|
appStore.showError(t('admin.accounts.pleaseEnterAccountName'))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!upstreamBaseUrl.value.trim()) {
|
||||||
|
appStore.showError(t('admin.accounts.upstream.pleaseEnterBaseUrl'))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!upstreamApiKey.value.trim()) {
|
||||||
|
appStore.showError(t('admin.accounts.upstream.pleaseEnterApiKey'))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
submitting.value = true
|
||||||
|
try {
|
||||||
|
const credentials: Record<string, unknown> = {
|
||||||
|
base_url: upstreamBaseUrl.value.trim(),
|
||||||
|
api_key: upstreamApiKey.value.trim()
|
||||||
|
}
|
||||||
|
await createAccountAndFinish(form.platform, 'upstream', credentials)
|
||||||
|
} catch (error: any) {
|
||||||
|
appStore.showError(error.response?.data?.detail || t('admin.accounts.failedToCreate'))
|
||||||
|
} finally {
|
||||||
|
submitting.value = false
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// For apikey type, create directly
|
// For apikey type, create directly
|
||||||
if (!apiKeyValue.value.trim()) {
|
if (!apiKeyValue.value.trim()) {
|
||||||
appStore.showError(t('admin.accounts.pleaseEnterApiKey'))
|
appStore.showError(t('admin.accounts.pleaseEnterApiKey'))
|
||||||
|
|||||||
@@ -1034,6 +1034,14 @@ export default {
|
|||||||
tooltip: 'When enabled, if the request contains MCP tools, an XML format call protocol prompt will be injected into the system prompt. Disable this to avoid interference with certain clients.',
|
tooltip: 'When enabled, if the request contains MCP tools, an XML format call protocol prompt will be injected into the system prompt. Disable this to avoid interference with certain clients.',
|
||||||
enabled: 'Enabled',
|
enabled: 'Enabled',
|
||||||
disabled: 'Disabled'
|
disabled: 'Disabled'
|
||||||
|
},
|
||||||
|
supportedScopes: {
|
||||||
|
title: 'Supported Model Families',
|
||||||
|
tooltip: 'Select the model families this group supports. Unchecked families will not be routed to this group.',
|
||||||
|
claude: 'Claude',
|
||||||
|
geminiText: 'Gemini Text',
|
||||||
|
geminiImage: 'Gemini Image',
|
||||||
|
hint: 'Select at least one model family'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -1173,7 +1181,9 @@ export default {
|
|||||||
responsesApi: 'Responses API',
|
responsesApi: 'Responses API',
|
||||||
googleOauth: 'Google OAuth',
|
googleOauth: 'Google OAuth',
|
||||||
codeAssist: 'Code Assist',
|
codeAssist: 'Code Assist',
|
||||||
antigravityOauth: 'Antigravity OAuth'
|
antigravityOauth: 'Antigravity OAuth',
|
||||||
|
upstream: 'Upstream',
|
||||||
|
upstreamDesc: 'Connect via Base URL + API Key'
|
||||||
},
|
},
|
||||||
status: {
|
status: {
|
||||||
active: 'Active',
|
active: 'Active',
|
||||||
@@ -1431,6 +1441,15 @@ export default {
|
|||||||
pleaseEnterApiKey: 'Please enter API Key',
|
pleaseEnterApiKey: 'Please enter API Key',
|
||||||
apiKeyIsRequired: 'API Key is required',
|
apiKeyIsRequired: 'API Key is required',
|
||||||
leaveEmptyToKeep: 'Leave empty to keep current key',
|
leaveEmptyToKeep: 'Leave empty to keep current key',
|
||||||
|
// Upstream type
|
||||||
|
upstream: {
|
||||||
|
baseUrl: 'Upstream Base URL',
|
||||||
|
baseUrlHint: 'The address of the upstream Antigravity service, e.g., https://upstream.example.com',
|
||||||
|
apiKey: 'Upstream API Key',
|
||||||
|
apiKeyHint: 'API Key for the upstream service',
|
||||||
|
pleaseEnterBaseUrl: 'Please enter upstream Base URL',
|
||||||
|
pleaseEnterApiKey: 'Please enter upstream API Key'
|
||||||
|
},
|
||||||
// OAuth flow
|
// OAuth flow
|
||||||
oauth: {
|
oauth: {
|
||||||
title: 'Claude Account Authorization',
|
title: 'Claude Account Authorization',
|
||||||
|
|||||||
@@ -1109,6 +1109,14 @@ export default {
|
|||||||
tooltip: '启用后,当请求包含 MCP 工具时,会在 system prompt 中注入 XML 格式调用协议提示词。关闭此选项可避免对某些客户端造成干扰。',
|
tooltip: '启用后,当请求包含 MCP 工具时,会在 system prompt 中注入 XML 格式调用协议提示词。关闭此选项可避免对某些客户端造成干扰。',
|
||||||
enabled: '已启用',
|
enabled: '已启用',
|
||||||
disabled: '已禁用'
|
disabled: '已禁用'
|
||||||
|
},
|
||||||
|
supportedScopes: {
|
||||||
|
title: '支持的模型系列',
|
||||||
|
tooltip: '选择此分组支持的模型系列。未勾选的系列将不会被路由到此分组。',
|
||||||
|
claude: 'Claude',
|
||||||
|
geminiText: 'Gemini Text',
|
||||||
|
geminiImage: 'Gemini Image',
|
||||||
|
hint: '至少选择一个模型系列'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -1294,6 +1302,8 @@ export default {
|
|||||||
googleOauth: 'Google OAuth',
|
googleOauth: 'Google OAuth',
|
||||||
codeAssist: 'Code Assist',
|
codeAssist: 'Code Assist',
|
||||||
antigravityOauth: 'Antigravity OAuth',
|
antigravityOauth: 'Antigravity OAuth',
|
||||||
|
upstream: '对接上游',
|
||||||
|
upstreamDesc: '通过 Base URL + API Key 连接上游',
|
||||||
api_key: 'API Key',
|
api_key: 'API Key',
|
||||||
cookie: 'Cookie'
|
cookie: 'Cookie'
|
||||||
},
|
},
|
||||||
@@ -1563,6 +1573,15 @@ export default {
|
|||||||
pleaseEnterApiKey: '请输入 API Key',
|
pleaseEnterApiKey: '请输入 API Key',
|
||||||
apiKeyIsRequired: 'API Key 是必需的',
|
apiKeyIsRequired: 'API Key 是必需的',
|
||||||
leaveEmptyToKeep: '留空以保持当前密钥',
|
leaveEmptyToKeep: '留空以保持当前密钥',
|
||||||
|
// Upstream type
|
||||||
|
upstream: {
|
||||||
|
baseUrl: '上游 Base URL',
|
||||||
|
baseUrlHint: '上游 Antigravity 服务的地址,例如:https://upstream.example.com',
|
||||||
|
apiKey: '上游 API Key',
|
||||||
|
apiKeyHint: '上游服务的 API Key',
|
||||||
|
pleaseEnterBaseUrl: '请输入上游 Base URL',
|
||||||
|
pleaseEnterApiKey: '请输入上游 API Key'
|
||||||
|
},
|
||||||
// OAuth flow
|
// OAuth flow
|
||||||
oauth: {
|
oauth: {
|
||||||
title: 'Claude 账号授权',
|
title: 'Claude 账号授权',
|
||||||
|
|||||||
@@ -365,6 +365,11 @@ export interface AdminGroup extends Group {
|
|||||||
|
|
||||||
// MCP XML 协议注入(仅 antigravity 平台使用)
|
// MCP XML 协议注入(仅 antigravity 平台使用)
|
||||||
mcp_xml_inject: boolean
|
mcp_xml_inject: boolean
|
||||||
|
|
||||||
|
// 支持的模型系列(仅 antigravity 平台使用)
|
||||||
|
supported_model_scopes?: string[]
|
||||||
|
|
||||||
|
// 分组下账号数量(仅管理员可见)
|
||||||
account_count?: number
|
account_count?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -414,6 +419,7 @@ export interface CreateGroupRequest {
|
|||||||
claude_code_only?: boolean
|
claude_code_only?: boolean
|
||||||
fallback_group_id?: number | null
|
fallback_group_id?: number | null
|
||||||
fallback_group_id_on_invalid_request?: number | null
|
fallback_group_id_on_invalid_request?: number | null
|
||||||
|
supported_model_scopes?: string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UpdateGroupRequest {
|
export interface UpdateGroupRequest {
|
||||||
@@ -433,12 +439,13 @@ export interface UpdateGroupRequest {
|
|||||||
claude_code_only?: boolean
|
claude_code_only?: boolean
|
||||||
fallback_group_id?: number | null
|
fallback_group_id?: number | null
|
||||||
fallback_group_id_on_invalid_request?: number | null
|
fallback_group_id_on_invalid_request?: number | null
|
||||||
|
supported_model_scopes?: string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==================== Account & Proxy Types ====================
|
// ==================== Account & Proxy Types ====================
|
||||||
|
|
||||||
export type AccountPlatform = 'anthropic' | 'openai' | 'gemini' | 'antigravity'
|
export type AccountPlatform = 'anthropic' | 'openai' | 'gemini' | 'antigravity'
|
||||||
export type AccountType = 'oauth' | 'setup-token' | 'apikey'
|
export type AccountType = 'oauth' | 'setup-token' | 'apikey' | 'upstream'
|
||||||
export type OAuthAddMethod = 'oauth' | 'setup-token'
|
export type OAuthAddMethod = 'oauth' | 'setup-token'
|
||||||
export type ProxyProtocol = 'http' | 'https' | 'socks5' | 'socks5h'
|
export type ProxyProtocol = 'http' | 'https' | 'socks5' | 'socks5h'
|
||||||
|
|
||||||
|
|||||||
@@ -404,6 +404,62 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 支持的模型系列(仅 antigravity 平台) -->
|
||||||
|
<div v-if="createForm.platform === 'antigravity'" class="border-t pt-4">
|
||||||
|
<div class="mb-1.5 flex items-center gap-1">
|
||||||
|
<label class="text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||||
|
{{ t('admin.groups.supportedScopes.title') }}
|
||||||
|
</label>
|
||||||
|
<!-- Help Tooltip -->
|
||||||
|
<div class="group relative inline-flex">
|
||||||
|
<Icon
|
||||||
|
name="questionCircle"
|
||||||
|
size="sm"
|
||||||
|
:stroke-width="2"
|
||||||
|
class="cursor-help text-gray-400 transition-colors hover:text-primary-500 dark:text-gray-500 dark:hover:text-primary-400"
|
||||||
|
/>
|
||||||
|
<div class="pointer-events-none absolute bottom-full left-0 z-50 mb-2 w-72 opacity-0 transition-all duration-200 group-hover:pointer-events-auto group-hover:opacity-100">
|
||||||
|
<div class="rounded-lg bg-gray-900 p-3 text-white shadow-lg dark:bg-gray-800">
|
||||||
|
<p class="text-xs leading-relaxed text-gray-300">
|
||||||
|
{{ t('admin.groups.supportedScopes.tooltip') }}
|
||||||
|
</p>
|
||||||
|
<div class="absolute -bottom-1.5 left-3 h-3 w-3 rotate-45 bg-gray-900 dark:bg-gray-800"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="space-y-2">
|
||||||
|
<label class="flex items-center gap-2 cursor-pointer">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
:checked="createForm.supported_model_scopes.includes('claude')"
|
||||||
|
@change="toggleCreateScope('claude')"
|
||||||
|
class="h-4 w-4 rounded border-gray-300 text-primary-600 focus:ring-primary-500 dark:border-dark-600 dark:bg-dark-700"
|
||||||
|
/>
|
||||||
|
<span class="text-sm text-gray-700 dark:text-gray-300">{{ t('admin.groups.supportedScopes.claude') }}</span>
|
||||||
|
</label>
|
||||||
|
<label class="flex items-center gap-2 cursor-pointer">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
:checked="createForm.supported_model_scopes.includes('gemini_text')"
|
||||||
|
@change="toggleCreateScope('gemini_text')"
|
||||||
|
class="h-4 w-4 rounded border-gray-300 text-primary-600 focus:ring-primary-500 dark:border-dark-600 dark:bg-dark-700"
|
||||||
|
/>
|
||||||
|
<span class="text-sm text-gray-700 dark:text-gray-300">{{ t('admin.groups.supportedScopes.geminiText') }}</span>
|
||||||
|
</label>
|
||||||
|
<label class="flex items-center gap-2 cursor-pointer">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
:checked="createForm.supported_model_scopes.includes('gemini_image')"
|
||||||
|
@change="toggleCreateScope('gemini_image')"
|
||||||
|
class="h-4 w-4 rounded border-gray-300 text-primary-600 focus:ring-primary-500 dark:border-dark-600 dark:bg-dark-700"
|
||||||
|
/>
|
||||||
|
<span class="text-sm text-gray-700 dark:text-gray-300">{{ t('admin.groups.supportedScopes.geminiImage') }}</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<p class="mt-2 text-xs text-gray-500 dark:text-gray-400">{{ t('admin.groups.supportedScopes.hint') }}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- MCP XML 协议注入(仅 antigravity 平台) -->
|
<!-- MCP XML 协议注入(仅 antigravity 平台) -->
|
||||||
<div v-if="createForm.platform === 'antigravity'" class="border-t pt-4">
|
<div v-if="createForm.platform === 'antigravity'" class="border-t pt-4">
|
||||||
<div class="mb-1.5 flex items-center gap-1">
|
<div class="mb-1.5 flex items-center gap-1">
|
||||||
@@ -907,6 +963,62 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 支持的模型系列(仅 antigravity 平台) -->
|
||||||
|
<div v-if="editForm.platform === 'antigravity'" class="border-t pt-4">
|
||||||
|
<div class="mb-1.5 flex items-center gap-1">
|
||||||
|
<label class="text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||||
|
{{ t('admin.groups.supportedScopes.title') }}
|
||||||
|
</label>
|
||||||
|
<!-- Help Tooltip -->
|
||||||
|
<div class="group relative inline-flex">
|
||||||
|
<Icon
|
||||||
|
name="questionCircle"
|
||||||
|
size="sm"
|
||||||
|
:stroke-width="2"
|
||||||
|
class="cursor-help text-gray-400 transition-colors hover:text-primary-500 dark:text-gray-500 dark:hover:text-primary-400"
|
||||||
|
/>
|
||||||
|
<div class="pointer-events-none absolute bottom-full left-0 z-50 mb-2 w-72 opacity-0 transition-all duration-200 group-hover:pointer-events-auto group-hover:opacity-100">
|
||||||
|
<div class="rounded-lg bg-gray-900 p-3 text-white shadow-lg dark:bg-gray-800">
|
||||||
|
<p class="text-xs leading-relaxed text-gray-300">
|
||||||
|
{{ t('admin.groups.supportedScopes.tooltip') }}
|
||||||
|
</p>
|
||||||
|
<div class="absolute -bottom-1.5 left-3 h-3 w-3 rotate-45 bg-gray-900 dark:bg-gray-800"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="space-y-2">
|
||||||
|
<label class="flex items-center gap-2 cursor-pointer">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
:checked="editForm.supported_model_scopes.includes('claude')"
|
||||||
|
@change="toggleEditScope('claude')"
|
||||||
|
class="h-4 w-4 rounded border-gray-300 text-primary-600 focus:ring-primary-500 dark:border-dark-600 dark:bg-dark-700"
|
||||||
|
/>
|
||||||
|
<span class="text-sm text-gray-700 dark:text-gray-300">{{ t('admin.groups.supportedScopes.claude') }}</span>
|
||||||
|
</label>
|
||||||
|
<label class="flex items-center gap-2 cursor-pointer">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
:checked="editForm.supported_model_scopes.includes('gemini_text')"
|
||||||
|
@change="toggleEditScope('gemini_text')"
|
||||||
|
class="h-4 w-4 rounded border-gray-300 text-primary-600 focus:ring-primary-500 dark:border-dark-600 dark:bg-dark-700"
|
||||||
|
/>
|
||||||
|
<span class="text-sm text-gray-700 dark:text-gray-300">{{ t('admin.groups.supportedScopes.geminiText') }}</span>
|
||||||
|
</label>
|
||||||
|
<label class="flex items-center gap-2 cursor-pointer">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
:checked="editForm.supported_model_scopes.includes('gemini_image')"
|
||||||
|
@change="toggleEditScope('gemini_image')"
|
||||||
|
class="h-4 w-4 rounded border-gray-300 text-primary-600 focus:ring-primary-500 dark:border-dark-600 dark:bg-dark-700"
|
||||||
|
/>
|
||||||
|
<span class="text-sm text-gray-700 dark:text-gray-300">{{ t('admin.groups.supportedScopes.geminiImage') }}</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<p class="mt-2 text-xs text-gray-500 dark:text-gray-400">{{ t('admin.groups.supportedScopes.hint') }}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- MCP XML 协议注入(仅 antigravity 平台) -->
|
<!-- MCP XML 协议注入(仅 antigravity 平台) -->
|
||||||
<div v-if="editForm.platform === 'antigravity'" class="border-t pt-4">
|
<div v-if="editForm.platform === 'antigravity'" class="border-t pt-4">
|
||||||
<div class="mb-1.5 flex items-center gap-1">
|
<div class="mb-1.5 flex items-center gap-1">
|
||||||
@@ -1402,6 +1514,9 @@ const createForm = reactive({
|
|||||||
fallback_group_id_on_invalid_request: null as number | null,
|
fallback_group_id_on_invalid_request: null as number | null,
|
||||||
// 模型路由开关
|
// 模型路由开关
|
||||||
model_routing_enabled: false,
|
model_routing_enabled: false,
|
||||||
|
// 支持的模型系列(仅 antigravity 平台)
|
||||||
|
supported_model_scopes: ['claude', 'gemini_text', 'gemini_image'] as string[],
|
||||||
|
// MCP XML 协议注入开关(仅 antigravity 平台)
|
||||||
mcp_xml_inject: true
|
mcp_xml_inject: true
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -1472,6 +1587,26 @@ const removeSelectedAccount = (ruleIndex: number, accountId: number, isEdit: boo
|
|||||||
rule.accounts = rule.accounts.filter(a => a.id !== accountId)
|
rule.accounts = rule.accounts.filter(a => a.id !== accountId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 切换创建表单的模型系列选择
|
||||||
|
const toggleCreateScope = (scope: string) => {
|
||||||
|
const idx = createForm.supported_model_scopes.indexOf(scope)
|
||||||
|
if (idx === -1) {
|
||||||
|
createForm.supported_model_scopes.push(scope)
|
||||||
|
} else {
|
||||||
|
createForm.supported_model_scopes.splice(idx, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 切换编辑表单的模型系列选择
|
||||||
|
const toggleEditScope = (scope: string) => {
|
||||||
|
const idx = editForm.supported_model_scopes.indexOf(scope)
|
||||||
|
if (idx === -1) {
|
||||||
|
editForm.supported_model_scopes.push(scope)
|
||||||
|
} else {
|
||||||
|
editForm.supported_model_scopes.splice(idx, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 处理账号搜索输入框聚焦
|
// 处理账号搜索输入框聚焦
|
||||||
const onAccountSearchFocus = (ruleIndex: number, isEdit: boolean = false) => {
|
const onAccountSearchFocus = (ruleIndex: number, isEdit: boolean = false) => {
|
||||||
const key = `${isEdit ? 'edit' : 'create'}-${ruleIndex}`
|
const key = `${isEdit ? 'edit' : 'create'}-${ruleIndex}`
|
||||||
@@ -1575,6 +1710,9 @@ const editForm = reactive({
|
|||||||
fallback_group_id_on_invalid_request: null as number | null,
|
fallback_group_id_on_invalid_request: null as number | null,
|
||||||
// 模型路由开关
|
// 模型路由开关
|
||||||
model_routing_enabled: false,
|
model_routing_enabled: false,
|
||||||
|
// 支持的模型系列(仅 antigravity 平台)
|
||||||
|
supported_model_scopes: ['claude', 'gemini_text', 'gemini_image'] as string[],
|
||||||
|
// MCP XML 协议注入开关(仅 antigravity 平台)
|
||||||
mcp_xml_inject: true
|
mcp_xml_inject: true
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -1658,6 +1796,7 @@ const closeCreateModal = () => {
|
|||||||
createForm.claude_code_only = false
|
createForm.claude_code_only = false
|
||||||
createForm.fallback_group_id = null
|
createForm.fallback_group_id = null
|
||||||
createForm.fallback_group_id_on_invalid_request = null
|
createForm.fallback_group_id_on_invalid_request = null
|
||||||
|
createForm.supported_model_scopes = ['claude', 'gemini_text', 'gemini_image']
|
||||||
createForm.mcp_xml_inject = true
|
createForm.mcp_xml_inject = true
|
||||||
createModelRoutingRules.value = []
|
createModelRoutingRules.value = []
|
||||||
}
|
}
|
||||||
@@ -1710,6 +1849,7 @@ const handleEdit = async (group: AdminGroup) => {
|
|||||||
editForm.fallback_group_id = group.fallback_group_id
|
editForm.fallback_group_id = group.fallback_group_id
|
||||||
editForm.fallback_group_id_on_invalid_request = group.fallback_group_id_on_invalid_request
|
editForm.fallback_group_id_on_invalid_request = group.fallback_group_id_on_invalid_request
|
||||||
editForm.model_routing_enabled = group.model_routing_enabled || false
|
editForm.model_routing_enabled = group.model_routing_enabled || false
|
||||||
|
editForm.supported_model_scopes = group.supported_model_scopes || ['claude', 'gemini_text', 'gemini_image']
|
||||||
editForm.mcp_xml_inject = group.mcp_xml_inject ?? true
|
editForm.mcp_xml_inject = group.mcp_xml_inject ?? true
|
||||||
// 加载模型路由规则(异步加载账号名称)
|
// 加载模型路由规则(异步加载账号名称)
|
||||||
editModelRoutingRules.value = await convertApiFormatToRoutingRules(group.model_routing)
|
editModelRoutingRules.value = await convertApiFormatToRoutingRules(group.model_routing)
|
||||||
|
|||||||
Reference in New Issue
Block a user