Merge pull request #2585 from Arron196/feature/channel-monitor-openai-detection
优化渠道监控 OpenAI 检测协议与内置模板
This commit is contained in:
commit
03730fbcf3
@ -27,6 +27,8 @@ type ChannelMonitor struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
// Provider holds the value of the "provider" field.
|
||||
Provider channelmonitor.Provider `json:"provider,omitempty"`
|
||||
// OpenAI request protocol: chat_completions or responses; non-OpenAI uses chat_completions
|
||||
APIMode string `json:"api_mode,omitempty"`
|
||||
// Provider base origin, e.g. https://api.openai.com
|
||||
Endpoint string `json:"endpoint,omitempty"`
|
||||
// AES-256-GCM encrypted API key
|
||||
@ -112,7 +114,7 @@ func (*ChannelMonitor) scanValues(columns []string) ([]any, error) {
|
||||
values[i] = new(sql.NullBool)
|
||||
case channelmonitor.FieldID, channelmonitor.FieldIntervalSeconds, channelmonitor.FieldCreatedBy, channelmonitor.FieldTemplateID:
|
||||
values[i] = new(sql.NullInt64)
|
||||
case channelmonitor.FieldName, channelmonitor.FieldProvider, channelmonitor.FieldEndpoint, channelmonitor.FieldAPIKeyEncrypted, channelmonitor.FieldPrimaryModel, channelmonitor.FieldGroupName, channelmonitor.FieldBodyOverrideMode:
|
||||
case channelmonitor.FieldName, channelmonitor.FieldProvider, channelmonitor.FieldAPIMode, channelmonitor.FieldEndpoint, channelmonitor.FieldAPIKeyEncrypted, channelmonitor.FieldPrimaryModel, channelmonitor.FieldGroupName, channelmonitor.FieldBodyOverrideMode:
|
||||
values[i] = new(sql.NullString)
|
||||
case channelmonitor.FieldCreatedAt, channelmonitor.FieldUpdatedAt, channelmonitor.FieldLastCheckedAt:
|
||||
values[i] = new(sql.NullTime)
|
||||
@ -161,6 +163,12 @@ func (_m *ChannelMonitor) assignValues(columns []string, values []any) error {
|
||||
} else if value.Valid {
|
||||
_m.Provider = channelmonitor.Provider(value.String)
|
||||
}
|
||||
case channelmonitor.FieldAPIMode:
|
||||
if value, ok := values[i].(*sql.NullString); !ok {
|
||||
return fmt.Errorf("unexpected type %T for field api_mode", values[i])
|
||||
} else if value.Valid {
|
||||
_m.APIMode = value.String
|
||||
}
|
||||
case channelmonitor.FieldEndpoint:
|
||||
if value, ok := values[i].(*sql.NullString); !ok {
|
||||
return fmt.Errorf("unexpected type %T for field endpoint", values[i])
|
||||
@ -310,6 +318,9 @@ func (_m *ChannelMonitor) String() string {
|
||||
builder.WriteString("provider=")
|
||||
builder.WriteString(fmt.Sprintf("%v", _m.Provider))
|
||||
builder.WriteString(", ")
|
||||
builder.WriteString("api_mode=")
|
||||
builder.WriteString(_m.APIMode)
|
||||
builder.WriteString(", ")
|
||||
builder.WriteString("endpoint=")
|
||||
builder.WriteString(_m.Endpoint)
|
||||
builder.WriteString(", ")
|
||||
|
||||
@ -23,6 +23,8 @@ const (
|
||||
FieldName = "name"
|
||||
// FieldProvider holds the string denoting the provider field in the database.
|
||||
FieldProvider = "provider"
|
||||
// FieldAPIMode holds the string denoting the api_mode field in the database.
|
||||
FieldAPIMode = "api_mode"
|
||||
// FieldEndpoint holds the string denoting the endpoint field in the database.
|
||||
FieldEndpoint = "endpoint"
|
||||
// FieldAPIKeyEncrypted holds the string denoting the api_key_encrypted field in the database.
|
||||
@ -87,6 +89,7 @@ var Columns = []string{
|
||||
FieldUpdatedAt,
|
||||
FieldName,
|
||||
FieldProvider,
|
||||
FieldAPIMode,
|
||||
FieldEndpoint,
|
||||
FieldAPIKeyEncrypted,
|
||||
FieldPrimaryModel,
|
||||
@ -121,6 +124,10 @@ var (
|
||||
UpdateDefaultUpdatedAt func() time.Time
|
||||
// NameValidator is a validator for the "name" field. It is called by the builders before save.
|
||||
NameValidator func(string) error
|
||||
// DefaultAPIMode holds the default value on creation for the "api_mode" field.
|
||||
DefaultAPIMode string
|
||||
// APIModeValidator is a validator for the "api_mode" field. It is called by the builders before save.
|
||||
APIModeValidator func(string) error
|
||||
// EndpointValidator is a validator for the "endpoint" field. It is called by the builders before save.
|
||||
EndpointValidator func(string) error
|
||||
// APIKeyEncryptedValidator is a validator for the "api_key_encrypted" field. It is called by the builders before save.
|
||||
@ -197,6 +204,11 @@ func ByProvider(opts ...sql.OrderTermOption) OrderOption {
|
||||
return sql.OrderByField(FieldProvider, opts...).ToFunc()
|
||||
}
|
||||
|
||||
// ByAPIMode orders the results by the api_mode field.
|
||||
func ByAPIMode(opts ...sql.OrderTermOption) OrderOption {
|
||||
return sql.OrderByField(FieldAPIMode, opts...).ToFunc()
|
||||
}
|
||||
|
||||
// ByEndpoint orders the results by the endpoint field.
|
||||
func ByEndpoint(opts ...sql.OrderTermOption) OrderOption {
|
||||
return sql.OrderByField(FieldEndpoint, opts...).ToFunc()
|
||||
|
||||
@ -70,6 +70,11 @@ func Name(v string) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldEQ(FieldName, v))
|
||||
}
|
||||
|
||||
// APIMode applies equality check predicate on the "api_mode" field. It's identical to APIModeEQ.
|
||||
func APIMode(v string) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldEQ(FieldAPIMode, v))
|
||||
}
|
||||
|
||||
// Endpoint applies equality check predicate on the "endpoint" field. It's identical to EndpointEQ.
|
||||
func Endpoint(v string) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldEQ(FieldEndpoint, v))
|
||||
@ -285,6 +290,71 @@ func ProviderNotIn(vs ...Provider) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldNotIn(FieldProvider, vs...))
|
||||
}
|
||||
|
||||
// APIModeEQ applies the EQ predicate on the "api_mode" field.
|
||||
func APIModeEQ(v string) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldEQ(FieldAPIMode, v))
|
||||
}
|
||||
|
||||
// APIModeNEQ applies the NEQ predicate on the "api_mode" field.
|
||||
func APIModeNEQ(v string) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldNEQ(FieldAPIMode, v))
|
||||
}
|
||||
|
||||
// APIModeIn applies the In predicate on the "api_mode" field.
|
||||
func APIModeIn(vs ...string) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldIn(FieldAPIMode, vs...))
|
||||
}
|
||||
|
||||
// APIModeNotIn applies the NotIn predicate on the "api_mode" field.
|
||||
func APIModeNotIn(vs ...string) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldNotIn(FieldAPIMode, vs...))
|
||||
}
|
||||
|
||||
// APIModeGT applies the GT predicate on the "api_mode" field.
|
||||
func APIModeGT(v string) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldGT(FieldAPIMode, v))
|
||||
}
|
||||
|
||||
// APIModeGTE applies the GTE predicate on the "api_mode" field.
|
||||
func APIModeGTE(v string) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldGTE(FieldAPIMode, v))
|
||||
}
|
||||
|
||||
// APIModeLT applies the LT predicate on the "api_mode" field.
|
||||
func APIModeLT(v string) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldLT(FieldAPIMode, v))
|
||||
}
|
||||
|
||||
// APIModeLTE applies the LTE predicate on the "api_mode" field.
|
||||
func APIModeLTE(v string) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldLTE(FieldAPIMode, v))
|
||||
}
|
||||
|
||||
// APIModeContains applies the Contains predicate on the "api_mode" field.
|
||||
func APIModeContains(v string) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldContains(FieldAPIMode, v))
|
||||
}
|
||||
|
||||
// APIModeHasPrefix applies the HasPrefix predicate on the "api_mode" field.
|
||||
func APIModeHasPrefix(v string) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldHasPrefix(FieldAPIMode, v))
|
||||
}
|
||||
|
||||
// APIModeHasSuffix applies the HasSuffix predicate on the "api_mode" field.
|
||||
func APIModeHasSuffix(v string) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldHasSuffix(FieldAPIMode, v))
|
||||
}
|
||||
|
||||
// APIModeEqualFold applies the EqualFold predicate on the "api_mode" field.
|
||||
func APIModeEqualFold(v string) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldEqualFold(FieldAPIMode, v))
|
||||
}
|
||||
|
||||
// APIModeContainsFold applies the ContainsFold predicate on the "api_mode" field.
|
||||
func APIModeContainsFold(v string) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldContainsFold(FieldAPIMode, v))
|
||||
}
|
||||
|
||||
// EndpointEQ applies the EQ predicate on the "endpoint" field.
|
||||
func EndpointEQ(v string) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldEQ(FieldEndpoint, v))
|
||||
|
||||
@ -65,6 +65,20 @@ func (_c *ChannelMonitorCreate) SetProvider(v channelmonitor.Provider) *ChannelM
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetAPIMode sets the "api_mode" field.
|
||||
func (_c *ChannelMonitorCreate) SetAPIMode(v string) *ChannelMonitorCreate {
|
||||
_c.mutation.SetAPIMode(v)
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetNillableAPIMode sets the "api_mode" field if the given value is not nil.
|
||||
func (_c *ChannelMonitorCreate) SetNillableAPIMode(v *string) *ChannelMonitorCreate {
|
||||
if v != nil {
|
||||
_c.SetAPIMode(*v)
|
||||
}
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetEndpoint sets the "endpoint" field.
|
||||
func (_c *ChannelMonitorCreate) SetEndpoint(v string) *ChannelMonitorCreate {
|
||||
_c.mutation.SetEndpoint(v)
|
||||
@ -275,6 +289,10 @@ func (_c *ChannelMonitorCreate) defaults() {
|
||||
v := channelmonitor.DefaultUpdatedAt()
|
||||
_c.mutation.SetUpdatedAt(v)
|
||||
}
|
||||
if _, ok := _c.mutation.APIMode(); !ok {
|
||||
v := channelmonitor.DefaultAPIMode
|
||||
_c.mutation.SetAPIMode(v)
|
||||
}
|
||||
if _, ok := _c.mutation.ExtraModels(); !ok {
|
||||
v := channelmonitor.DefaultExtraModels
|
||||
_c.mutation.SetExtraModels(v)
|
||||
@ -321,6 +339,14 @@ func (_c *ChannelMonitorCreate) check() error {
|
||||
return &ValidationError{Name: "provider", err: fmt.Errorf(`ent: validator failed for field "ChannelMonitor.provider": %w`, err)}
|
||||
}
|
||||
}
|
||||
if _, ok := _c.mutation.APIMode(); !ok {
|
||||
return &ValidationError{Name: "api_mode", err: errors.New(`ent: missing required field "ChannelMonitor.api_mode"`)}
|
||||
}
|
||||
if v, ok := _c.mutation.APIMode(); ok {
|
||||
if err := channelmonitor.APIModeValidator(v); err != nil {
|
||||
return &ValidationError{Name: "api_mode", err: fmt.Errorf(`ent: validator failed for field "ChannelMonitor.api_mode": %w`, err)}
|
||||
}
|
||||
}
|
||||
if _, ok := _c.mutation.Endpoint(); !ok {
|
||||
return &ValidationError{Name: "endpoint", err: errors.New(`ent: missing required field "ChannelMonitor.endpoint"`)}
|
||||
}
|
||||
@ -421,6 +447,10 @@ func (_c *ChannelMonitorCreate) createSpec() (*ChannelMonitor, *sqlgraph.CreateS
|
||||
_spec.SetField(channelmonitor.FieldProvider, field.TypeEnum, value)
|
||||
_node.Provider = value
|
||||
}
|
||||
if value, ok := _c.mutation.APIMode(); ok {
|
||||
_spec.SetField(channelmonitor.FieldAPIMode, field.TypeString, value)
|
||||
_node.APIMode = value
|
||||
}
|
||||
if value, ok := _c.mutation.Endpoint(); ok {
|
||||
_spec.SetField(channelmonitor.FieldEndpoint, field.TypeString, value)
|
||||
_node.Endpoint = value
|
||||
@ -606,6 +636,18 @@ func (u *ChannelMonitorUpsert) UpdateProvider() *ChannelMonitorUpsert {
|
||||
return u
|
||||
}
|
||||
|
||||
// SetAPIMode sets the "api_mode" field.
|
||||
func (u *ChannelMonitorUpsert) SetAPIMode(v string) *ChannelMonitorUpsert {
|
||||
u.Set(channelmonitor.FieldAPIMode, v)
|
||||
return u
|
||||
}
|
||||
|
||||
// UpdateAPIMode sets the "api_mode" field to the value that was provided on create.
|
||||
func (u *ChannelMonitorUpsert) UpdateAPIMode() *ChannelMonitorUpsert {
|
||||
u.SetExcluded(channelmonitor.FieldAPIMode)
|
||||
return u
|
||||
}
|
||||
|
||||
// SetEndpoint sets the "endpoint" field.
|
||||
func (u *ChannelMonitorUpsert) SetEndpoint(v string) *ChannelMonitorUpsert {
|
||||
u.Set(channelmonitor.FieldEndpoint, v)
|
||||
@ -885,6 +927,20 @@ func (u *ChannelMonitorUpsertOne) UpdateProvider() *ChannelMonitorUpsertOne {
|
||||
})
|
||||
}
|
||||
|
||||
// SetAPIMode sets the "api_mode" field.
|
||||
func (u *ChannelMonitorUpsertOne) SetAPIMode(v string) *ChannelMonitorUpsertOne {
|
||||
return u.Update(func(s *ChannelMonitorUpsert) {
|
||||
s.SetAPIMode(v)
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateAPIMode sets the "api_mode" field to the value that was provided on create.
|
||||
func (u *ChannelMonitorUpsertOne) UpdateAPIMode() *ChannelMonitorUpsertOne {
|
||||
return u.Update(func(s *ChannelMonitorUpsert) {
|
||||
s.UpdateAPIMode()
|
||||
})
|
||||
}
|
||||
|
||||
// SetEndpoint sets the "endpoint" field.
|
||||
func (u *ChannelMonitorUpsertOne) SetEndpoint(v string) *ChannelMonitorUpsertOne {
|
||||
return u.Update(func(s *ChannelMonitorUpsert) {
|
||||
@ -1362,6 +1418,20 @@ func (u *ChannelMonitorUpsertBulk) UpdateProvider() *ChannelMonitorUpsertBulk {
|
||||
})
|
||||
}
|
||||
|
||||
// SetAPIMode sets the "api_mode" field.
|
||||
func (u *ChannelMonitorUpsertBulk) SetAPIMode(v string) *ChannelMonitorUpsertBulk {
|
||||
return u.Update(func(s *ChannelMonitorUpsert) {
|
||||
s.SetAPIMode(v)
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateAPIMode sets the "api_mode" field to the value that was provided on create.
|
||||
func (u *ChannelMonitorUpsertBulk) UpdateAPIMode() *ChannelMonitorUpsertBulk {
|
||||
return u.Update(func(s *ChannelMonitorUpsert) {
|
||||
s.UpdateAPIMode()
|
||||
})
|
||||
}
|
||||
|
||||
// SetEndpoint sets the "endpoint" field.
|
||||
func (u *ChannelMonitorUpsertBulk) SetEndpoint(v string) *ChannelMonitorUpsertBulk {
|
||||
return u.Update(func(s *ChannelMonitorUpsert) {
|
||||
|
||||
@ -66,6 +66,20 @@ func (_u *ChannelMonitorUpdate) SetNillableProvider(v *channelmonitor.Provider)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetAPIMode sets the "api_mode" field.
|
||||
func (_u *ChannelMonitorUpdate) SetAPIMode(v string) *ChannelMonitorUpdate {
|
||||
_u.mutation.SetAPIMode(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetNillableAPIMode sets the "api_mode" field if the given value is not nil.
|
||||
func (_u *ChannelMonitorUpdate) SetNillableAPIMode(v *string) *ChannelMonitorUpdate {
|
||||
if v != nil {
|
||||
_u.SetAPIMode(*v)
|
||||
}
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetEndpoint sets the "endpoint" field.
|
||||
func (_u *ChannelMonitorUpdate) SetEndpoint(v string) *ChannelMonitorUpdate {
|
||||
_u.mutation.SetEndpoint(v)
|
||||
@ -418,6 +432,11 @@ func (_u *ChannelMonitorUpdate) check() error {
|
||||
return &ValidationError{Name: "provider", err: fmt.Errorf(`ent: validator failed for field "ChannelMonitor.provider": %w`, err)}
|
||||
}
|
||||
}
|
||||
if v, ok := _u.mutation.APIMode(); ok {
|
||||
if err := channelmonitor.APIModeValidator(v); err != nil {
|
||||
return &ValidationError{Name: "api_mode", err: fmt.Errorf(`ent: validator failed for field "ChannelMonitor.api_mode": %w`, err)}
|
||||
}
|
||||
}
|
||||
if v, ok := _u.mutation.Endpoint(); ok {
|
||||
if err := channelmonitor.EndpointValidator(v); err != nil {
|
||||
return &ValidationError{Name: "endpoint", err: fmt.Errorf(`ent: validator failed for field "ChannelMonitor.endpoint": %w`, err)}
|
||||
@ -472,6 +491,9 @@ func (_u *ChannelMonitorUpdate) sqlSave(ctx context.Context) (_node int, err err
|
||||
if value, ok := _u.mutation.Provider(); ok {
|
||||
_spec.SetField(channelmonitor.FieldProvider, field.TypeEnum, value)
|
||||
}
|
||||
if value, ok := _u.mutation.APIMode(); ok {
|
||||
_spec.SetField(channelmonitor.FieldAPIMode, field.TypeString, value)
|
||||
}
|
||||
if value, ok := _u.mutation.Endpoint(); ok {
|
||||
_spec.SetField(channelmonitor.FieldEndpoint, field.TypeString, value)
|
||||
}
|
||||
@ -701,6 +723,20 @@ func (_u *ChannelMonitorUpdateOne) SetNillableProvider(v *channelmonitor.Provide
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetAPIMode sets the "api_mode" field.
|
||||
func (_u *ChannelMonitorUpdateOne) SetAPIMode(v string) *ChannelMonitorUpdateOne {
|
||||
_u.mutation.SetAPIMode(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetNillableAPIMode sets the "api_mode" field if the given value is not nil.
|
||||
func (_u *ChannelMonitorUpdateOne) SetNillableAPIMode(v *string) *ChannelMonitorUpdateOne {
|
||||
if v != nil {
|
||||
_u.SetAPIMode(*v)
|
||||
}
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetEndpoint sets the "endpoint" field.
|
||||
func (_u *ChannelMonitorUpdateOne) SetEndpoint(v string) *ChannelMonitorUpdateOne {
|
||||
_u.mutation.SetEndpoint(v)
|
||||
@ -1066,6 +1102,11 @@ func (_u *ChannelMonitorUpdateOne) check() error {
|
||||
return &ValidationError{Name: "provider", err: fmt.Errorf(`ent: validator failed for field "ChannelMonitor.provider": %w`, err)}
|
||||
}
|
||||
}
|
||||
if v, ok := _u.mutation.APIMode(); ok {
|
||||
if err := channelmonitor.APIModeValidator(v); err != nil {
|
||||
return &ValidationError{Name: "api_mode", err: fmt.Errorf(`ent: validator failed for field "ChannelMonitor.api_mode": %w`, err)}
|
||||
}
|
||||
}
|
||||
if v, ok := _u.mutation.Endpoint(); ok {
|
||||
if err := channelmonitor.EndpointValidator(v); err != nil {
|
||||
return &ValidationError{Name: "endpoint", err: fmt.Errorf(`ent: validator failed for field "ChannelMonitor.endpoint": %w`, err)}
|
||||
@ -1137,6 +1178,9 @@ func (_u *ChannelMonitorUpdateOne) sqlSave(ctx context.Context) (_node *ChannelM
|
||||
if value, ok := _u.mutation.Provider(); ok {
|
||||
_spec.SetField(channelmonitor.FieldProvider, field.TypeEnum, value)
|
||||
}
|
||||
if value, ok := _u.mutation.APIMode(); ok {
|
||||
_spec.SetField(channelmonitor.FieldAPIMode, field.TypeString, value)
|
||||
}
|
||||
if value, ok := _u.mutation.Endpoint(); ok {
|
||||
_spec.SetField(channelmonitor.FieldEndpoint, field.TypeString, value)
|
||||
}
|
||||
|
||||
@ -26,6 +26,8 @@ type ChannelMonitorRequestTemplate struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
// Provider holds the value of the "provider" field.
|
||||
Provider channelmonitorrequesttemplate.Provider `json:"provider,omitempty"`
|
||||
// OpenAI request protocol: chat_completions or responses; non-OpenAI uses chat_completions
|
||||
APIMode string `json:"api_mode,omitempty"`
|
||||
// Description holds the value of the "description" field.
|
||||
Description string `json:"description,omitempty"`
|
||||
// ExtraHeaders holds the value of the "extra_headers" field.
|
||||
@ -67,7 +69,7 @@ func (*ChannelMonitorRequestTemplate) scanValues(columns []string) ([]any, error
|
||||
values[i] = new([]byte)
|
||||
case channelmonitorrequesttemplate.FieldID:
|
||||
values[i] = new(sql.NullInt64)
|
||||
case channelmonitorrequesttemplate.FieldName, channelmonitorrequesttemplate.FieldProvider, channelmonitorrequesttemplate.FieldDescription, channelmonitorrequesttemplate.FieldBodyOverrideMode:
|
||||
case channelmonitorrequesttemplate.FieldName, channelmonitorrequesttemplate.FieldProvider, channelmonitorrequesttemplate.FieldAPIMode, channelmonitorrequesttemplate.FieldDescription, channelmonitorrequesttemplate.FieldBodyOverrideMode:
|
||||
values[i] = new(sql.NullString)
|
||||
case channelmonitorrequesttemplate.FieldCreatedAt, channelmonitorrequesttemplate.FieldUpdatedAt:
|
||||
values[i] = new(sql.NullTime)
|
||||
@ -116,6 +118,12 @@ func (_m *ChannelMonitorRequestTemplate) assignValues(columns []string, values [
|
||||
} else if value.Valid {
|
||||
_m.Provider = channelmonitorrequesttemplate.Provider(value.String)
|
||||
}
|
||||
case channelmonitorrequesttemplate.FieldAPIMode:
|
||||
if value, ok := values[i].(*sql.NullString); !ok {
|
||||
return fmt.Errorf("unexpected type %T for field api_mode", values[i])
|
||||
} else if value.Valid {
|
||||
_m.APIMode = value.String
|
||||
}
|
||||
case channelmonitorrequesttemplate.FieldDescription:
|
||||
if value, ok := values[i].(*sql.NullString); !ok {
|
||||
return fmt.Errorf("unexpected type %T for field description", values[i])
|
||||
@ -197,6 +205,9 @@ func (_m *ChannelMonitorRequestTemplate) String() string {
|
||||
builder.WriteString("provider=")
|
||||
builder.WriteString(fmt.Sprintf("%v", _m.Provider))
|
||||
builder.WriteString(", ")
|
||||
builder.WriteString("api_mode=")
|
||||
builder.WriteString(_m.APIMode)
|
||||
builder.WriteString(", ")
|
||||
builder.WriteString("description=")
|
||||
builder.WriteString(_m.Description)
|
||||
builder.WriteString(", ")
|
||||
|
||||
@ -23,6 +23,8 @@ const (
|
||||
FieldName = "name"
|
||||
// FieldProvider holds the string denoting the provider field in the database.
|
||||
FieldProvider = "provider"
|
||||
// FieldAPIMode holds the string denoting the api_mode field in the database.
|
||||
FieldAPIMode = "api_mode"
|
||||
// FieldDescription holds the string denoting the description field in the database.
|
||||
FieldDescription = "description"
|
||||
// FieldExtraHeaders holds the string denoting the extra_headers field in the database.
|
||||
@ -51,6 +53,7 @@ var Columns = []string{
|
||||
FieldUpdatedAt,
|
||||
FieldName,
|
||||
FieldProvider,
|
||||
FieldAPIMode,
|
||||
FieldDescription,
|
||||
FieldExtraHeaders,
|
||||
FieldBodyOverrideMode,
|
||||
@ -76,6 +79,10 @@ var (
|
||||
UpdateDefaultUpdatedAt func() time.Time
|
||||
// NameValidator is a validator for the "name" field. It is called by the builders before save.
|
||||
NameValidator func(string) error
|
||||
// DefaultAPIMode holds the default value on creation for the "api_mode" field.
|
||||
DefaultAPIMode string
|
||||
// APIModeValidator is a validator for the "api_mode" field. It is called by the builders before save.
|
||||
APIModeValidator func(string) error
|
||||
// DefaultDescription holds the default value on creation for the "description" field.
|
||||
DefaultDescription string
|
||||
// DescriptionValidator is a validator for the "description" field. It is called by the builders before save.
|
||||
@ -140,6 +147,11 @@ func ByProvider(opts ...sql.OrderTermOption) OrderOption {
|
||||
return sql.OrderByField(FieldProvider, opts...).ToFunc()
|
||||
}
|
||||
|
||||
// ByAPIMode orders the results by the api_mode field.
|
||||
func ByAPIMode(opts ...sql.OrderTermOption) OrderOption {
|
||||
return sql.OrderByField(FieldAPIMode, opts...).ToFunc()
|
||||
}
|
||||
|
||||
// ByDescription orders the results by the description field.
|
||||
func ByDescription(opts ...sql.OrderTermOption) OrderOption {
|
||||
return sql.OrderByField(FieldDescription, opts...).ToFunc()
|
||||
|
||||
@ -70,6 +70,11 @@ func Name(v string) predicate.ChannelMonitorRequestTemplate {
|
||||
return predicate.ChannelMonitorRequestTemplate(sql.FieldEQ(FieldName, v))
|
||||
}
|
||||
|
||||
// APIMode applies equality check predicate on the "api_mode" field. It's identical to APIModeEQ.
|
||||
func APIMode(v string) predicate.ChannelMonitorRequestTemplate {
|
||||
return predicate.ChannelMonitorRequestTemplate(sql.FieldEQ(FieldAPIMode, v))
|
||||
}
|
||||
|
||||
// Description applies equality check predicate on the "description" field. It's identical to DescriptionEQ.
|
||||
func Description(v string) predicate.ChannelMonitorRequestTemplate {
|
||||
return predicate.ChannelMonitorRequestTemplate(sql.FieldEQ(FieldDescription, v))
|
||||
@ -245,6 +250,71 @@ func ProviderNotIn(vs ...Provider) predicate.ChannelMonitorRequestTemplate {
|
||||
return predicate.ChannelMonitorRequestTemplate(sql.FieldNotIn(FieldProvider, vs...))
|
||||
}
|
||||
|
||||
// APIModeEQ applies the EQ predicate on the "api_mode" field.
|
||||
func APIModeEQ(v string) predicate.ChannelMonitorRequestTemplate {
|
||||
return predicate.ChannelMonitorRequestTemplate(sql.FieldEQ(FieldAPIMode, v))
|
||||
}
|
||||
|
||||
// APIModeNEQ applies the NEQ predicate on the "api_mode" field.
|
||||
func APIModeNEQ(v string) predicate.ChannelMonitorRequestTemplate {
|
||||
return predicate.ChannelMonitorRequestTemplate(sql.FieldNEQ(FieldAPIMode, v))
|
||||
}
|
||||
|
||||
// APIModeIn applies the In predicate on the "api_mode" field.
|
||||
func APIModeIn(vs ...string) predicate.ChannelMonitorRequestTemplate {
|
||||
return predicate.ChannelMonitorRequestTemplate(sql.FieldIn(FieldAPIMode, vs...))
|
||||
}
|
||||
|
||||
// APIModeNotIn applies the NotIn predicate on the "api_mode" field.
|
||||
func APIModeNotIn(vs ...string) predicate.ChannelMonitorRequestTemplate {
|
||||
return predicate.ChannelMonitorRequestTemplate(sql.FieldNotIn(FieldAPIMode, vs...))
|
||||
}
|
||||
|
||||
// APIModeGT applies the GT predicate on the "api_mode" field.
|
||||
func APIModeGT(v string) predicate.ChannelMonitorRequestTemplate {
|
||||
return predicate.ChannelMonitorRequestTemplate(sql.FieldGT(FieldAPIMode, v))
|
||||
}
|
||||
|
||||
// APIModeGTE applies the GTE predicate on the "api_mode" field.
|
||||
func APIModeGTE(v string) predicate.ChannelMonitorRequestTemplate {
|
||||
return predicate.ChannelMonitorRequestTemplate(sql.FieldGTE(FieldAPIMode, v))
|
||||
}
|
||||
|
||||
// APIModeLT applies the LT predicate on the "api_mode" field.
|
||||
func APIModeLT(v string) predicate.ChannelMonitorRequestTemplate {
|
||||
return predicate.ChannelMonitorRequestTemplate(sql.FieldLT(FieldAPIMode, v))
|
||||
}
|
||||
|
||||
// APIModeLTE applies the LTE predicate on the "api_mode" field.
|
||||
func APIModeLTE(v string) predicate.ChannelMonitorRequestTemplate {
|
||||
return predicate.ChannelMonitorRequestTemplate(sql.FieldLTE(FieldAPIMode, v))
|
||||
}
|
||||
|
||||
// APIModeContains applies the Contains predicate on the "api_mode" field.
|
||||
func APIModeContains(v string) predicate.ChannelMonitorRequestTemplate {
|
||||
return predicate.ChannelMonitorRequestTemplate(sql.FieldContains(FieldAPIMode, v))
|
||||
}
|
||||
|
||||
// APIModeHasPrefix applies the HasPrefix predicate on the "api_mode" field.
|
||||
func APIModeHasPrefix(v string) predicate.ChannelMonitorRequestTemplate {
|
||||
return predicate.ChannelMonitorRequestTemplate(sql.FieldHasPrefix(FieldAPIMode, v))
|
||||
}
|
||||
|
||||
// APIModeHasSuffix applies the HasSuffix predicate on the "api_mode" field.
|
||||
func APIModeHasSuffix(v string) predicate.ChannelMonitorRequestTemplate {
|
||||
return predicate.ChannelMonitorRequestTemplate(sql.FieldHasSuffix(FieldAPIMode, v))
|
||||
}
|
||||
|
||||
// APIModeEqualFold applies the EqualFold predicate on the "api_mode" field.
|
||||
func APIModeEqualFold(v string) predicate.ChannelMonitorRequestTemplate {
|
||||
return predicate.ChannelMonitorRequestTemplate(sql.FieldEqualFold(FieldAPIMode, v))
|
||||
}
|
||||
|
||||
// APIModeContainsFold applies the ContainsFold predicate on the "api_mode" field.
|
||||
func APIModeContainsFold(v string) predicate.ChannelMonitorRequestTemplate {
|
||||
return predicate.ChannelMonitorRequestTemplate(sql.FieldContainsFold(FieldAPIMode, v))
|
||||
}
|
||||
|
||||
// DescriptionEQ applies the EQ predicate on the "description" field.
|
||||
func DescriptionEQ(v string) predicate.ChannelMonitorRequestTemplate {
|
||||
return predicate.ChannelMonitorRequestTemplate(sql.FieldEQ(FieldDescription, v))
|
||||
|
||||
@ -63,6 +63,20 @@ func (_c *ChannelMonitorRequestTemplateCreate) SetProvider(v channelmonitorreque
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetAPIMode sets the "api_mode" field.
|
||||
func (_c *ChannelMonitorRequestTemplateCreate) SetAPIMode(v string) *ChannelMonitorRequestTemplateCreate {
|
||||
_c.mutation.SetAPIMode(v)
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetNillableAPIMode sets the "api_mode" field if the given value is not nil.
|
||||
func (_c *ChannelMonitorRequestTemplateCreate) SetNillableAPIMode(v *string) *ChannelMonitorRequestTemplateCreate {
|
||||
if v != nil {
|
||||
_c.SetAPIMode(*v)
|
||||
}
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetDescription sets the "description" field.
|
||||
func (_c *ChannelMonitorRequestTemplateCreate) SetDescription(v string) *ChannelMonitorRequestTemplateCreate {
|
||||
_c.mutation.SetDescription(v)
|
||||
@ -161,6 +175,10 @@ func (_c *ChannelMonitorRequestTemplateCreate) defaults() {
|
||||
v := channelmonitorrequesttemplate.DefaultUpdatedAt()
|
||||
_c.mutation.SetUpdatedAt(v)
|
||||
}
|
||||
if _, ok := _c.mutation.APIMode(); !ok {
|
||||
v := channelmonitorrequesttemplate.DefaultAPIMode
|
||||
_c.mutation.SetAPIMode(v)
|
||||
}
|
||||
if _, ok := _c.mutation.Description(); !ok {
|
||||
v := channelmonitorrequesttemplate.DefaultDescription
|
||||
_c.mutation.SetDescription(v)
|
||||
@ -199,6 +217,14 @@ func (_c *ChannelMonitorRequestTemplateCreate) check() error {
|
||||
return &ValidationError{Name: "provider", err: fmt.Errorf(`ent: validator failed for field "ChannelMonitorRequestTemplate.provider": %w`, err)}
|
||||
}
|
||||
}
|
||||
if _, ok := _c.mutation.APIMode(); !ok {
|
||||
return &ValidationError{Name: "api_mode", err: errors.New(`ent: missing required field "ChannelMonitorRequestTemplate.api_mode"`)}
|
||||
}
|
||||
if v, ok := _c.mutation.APIMode(); ok {
|
||||
if err := channelmonitorrequesttemplate.APIModeValidator(v); err != nil {
|
||||
return &ValidationError{Name: "api_mode", err: fmt.Errorf(`ent: validator failed for field "ChannelMonitorRequestTemplate.api_mode": %w`, err)}
|
||||
}
|
||||
}
|
||||
if v, ok := _c.mutation.Description(); ok {
|
||||
if err := channelmonitorrequesttemplate.DescriptionValidator(v); err != nil {
|
||||
return &ValidationError{Name: "description", err: fmt.Errorf(`ent: validator failed for field "ChannelMonitorRequestTemplate.description": %w`, err)}
|
||||
@ -258,6 +284,10 @@ func (_c *ChannelMonitorRequestTemplateCreate) createSpec() (*ChannelMonitorRequ
|
||||
_spec.SetField(channelmonitorrequesttemplate.FieldProvider, field.TypeEnum, value)
|
||||
_node.Provider = value
|
||||
}
|
||||
if value, ok := _c.mutation.APIMode(); ok {
|
||||
_spec.SetField(channelmonitorrequesttemplate.FieldAPIMode, field.TypeString, value)
|
||||
_node.APIMode = value
|
||||
}
|
||||
if value, ok := _c.mutation.Description(); ok {
|
||||
_spec.SetField(channelmonitorrequesttemplate.FieldDescription, field.TypeString, value)
|
||||
_node.Description = value
|
||||
@ -378,6 +408,18 @@ func (u *ChannelMonitorRequestTemplateUpsert) UpdateProvider() *ChannelMonitorRe
|
||||
return u
|
||||
}
|
||||
|
||||
// SetAPIMode sets the "api_mode" field.
|
||||
func (u *ChannelMonitorRequestTemplateUpsert) SetAPIMode(v string) *ChannelMonitorRequestTemplateUpsert {
|
||||
u.Set(channelmonitorrequesttemplate.FieldAPIMode, v)
|
||||
return u
|
||||
}
|
||||
|
||||
// UpdateAPIMode sets the "api_mode" field to the value that was provided on create.
|
||||
func (u *ChannelMonitorRequestTemplateUpsert) UpdateAPIMode() *ChannelMonitorRequestTemplateUpsert {
|
||||
u.SetExcluded(channelmonitorrequesttemplate.FieldAPIMode)
|
||||
return u
|
||||
}
|
||||
|
||||
// SetDescription sets the "description" field.
|
||||
func (u *ChannelMonitorRequestTemplateUpsert) SetDescription(v string) *ChannelMonitorRequestTemplateUpsert {
|
||||
u.Set(channelmonitorrequesttemplate.FieldDescription, v)
|
||||
@ -525,6 +567,20 @@ func (u *ChannelMonitorRequestTemplateUpsertOne) UpdateProvider() *ChannelMonito
|
||||
})
|
||||
}
|
||||
|
||||
// SetAPIMode sets the "api_mode" field.
|
||||
func (u *ChannelMonitorRequestTemplateUpsertOne) SetAPIMode(v string) *ChannelMonitorRequestTemplateUpsertOne {
|
||||
return u.Update(func(s *ChannelMonitorRequestTemplateUpsert) {
|
||||
s.SetAPIMode(v)
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateAPIMode sets the "api_mode" field to the value that was provided on create.
|
||||
func (u *ChannelMonitorRequestTemplateUpsertOne) UpdateAPIMode() *ChannelMonitorRequestTemplateUpsertOne {
|
||||
return u.Update(func(s *ChannelMonitorRequestTemplateUpsert) {
|
||||
s.UpdateAPIMode()
|
||||
})
|
||||
}
|
||||
|
||||
// SetDescription sets the "description" field.
|
||||
func (u *ChannelMonitorRequestTemplateUpsertOne) SetDescription(v string) *ChannelMonitorRequestTemplateUpsertOne {
|
||||
return u.Update(func(s *ChannelMonitorRequestTemplateUpsert) {
|
||||
@ -848,6 +904,20 @@ func (u *ChannelMonitorRequestTemplateUpsertBulk) UpdateProvider() *ChannelMonit
|
||||
})
|
||||
}
|
||||
|
||||
// SetAPIMode sets the "api_mode" field.
|
||||
func (u *ChannelMonitorRequestTemplateUpsertBulk) SetAPIMode(v string) *ChannelMonitorRequestTemplateUpsertBulk {
|
||||
return u.Update(func(s *ChannelMonitorRequestTemplateUpsert) {
|
||||
s.SetAPIMode(v)
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateAPIMode sets the "api_mode" field to the value that was provided on create.
|
||||
func (u *ChannelMonitorRequestTemplateUpsertBulk) UpdateAPIMode() *ChannelMonitorRequestTemplateUpsertBulk {
|
||||
return u.Update(func(s *ChannelMonitorRequestTemplateUpsert) {
|
||||
s.UpdateAPIMode()
|
||||
})
|
||||
}
|
||||
|
||||
// SetDescription sets the "description" field.
|
||||
func (u *ChannelMonitorRequestTemplateUpsertBulk) SetDescription(v string) *ChannelMonitorRequestTemplateUpsertBulk {
|
||||
return u.Update(func(s *ChannelMonitorRequestTemplateUpsert) {
|
||||
|
||||
@ -63,6 +63,20 @@ func (_u *ChannelMonitorRequestTemplateUpdate) SetNillableProvider(v *channelmon
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetAPIMode sets the "api_mode" field.
|
||||
func (_u *ChannelMonitorRequestTemplateUpdate) SetAPIMode(v string) *ChannelMonitorRequestTemplateUpdate {
|
||||
_u.mutation.SetAPIMode(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetNillableAPIMode sets the "api_mode" field if the given value is not nil.
|
||||
func (_u *ChannelMonitorRequestTemplateUpdate) SetNillableAPIMode(v *string) *ChannelMonitorRequestTemplateUpdate {
|
||||
if v != nil {
|
||||
_u.SetAPIMode(*v)
|
||||
}
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetDescription sets the "description" field.
|
||||
func (_u *ChannelMonitorRequestTemplateUpdate) SetDescription(v string) *ChannelMonitorRequestTemplateUpdate {
|
||||
_u.mutation.SetDescription(v)
|
||||
@ -204,6 +218,11 @@ func (_u *ChannelMonitorRequestTemplateUpdate) check() error {
|
||||
return &ValidationError{Name: "provider", err: fmt.Errorf(`ent: validator failed for field "ChannelMonitorRequestTemplate.provider": %w`, err)}
|
||||
}
|
||||
}
|
||||
if v, ok := _u.mutation.APIMode(); ok {
|
||||
if err := channelmonitorrequesttemplate.APIModeValidator(v); err != nil {
|
||||
return &ValidationError{Name: "api_mode", err: fmt.Errorf(`ent: validator failed for field "ChannelMonitorRequestTemplate.api_mode": %w`, err)}
|
||||
}
|
||||
}
|
||||
if v, ok := _u.mutation.Description(); ok {
|
||||
if err := channelmonitorrequesttemplate.DescriptionValidator(v); err != nil {
|
||||
return &ValidationError{Name: "description", err: fmt.Errorf(`ent: validator failed for field "ChannelMonitorRequestTemplate.description": %w`, err)}
|
||||
@ -238,6 +257,9 @@ func (_u *ChannelMonitorRequestTemplateUpdate) sqlSave(ctx context.Context) (_no
|
||||
if value, ok := _u.mutation.Provider(); ok {
|
||||
_spec.SetField(channelmonitorrequesttemplate.FieldProvider, field.TypeEnum, value)
|
||||
}
|
||||
if value, ok := _u.mutation.APIMode(); ok {
|
||||
_spec.SetField(channelmonitorrequesttemplate.FieldAPIMode, field.TypeString, value)
|
||||
}
|
||||
if value, ok := _u.mutation.Description(); ok {
|
||||
_spec.SetField(channelmonitorrequesttemplate.FieldDescription, field.TypeString, value)
|
||||
}
|
||||
@ -355,6 +377,20 @@ func (_u *ChannelMonitorRequestTemplateUpdateOne) SetNillableProvider(v *channel
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetAPIMode sets the "api_mode" field.
|
||||
func (_u *ChannelMonitorRequestTemplateUpdateOne) SetAPIMode(v string) *ChannelMonitorRequestTemplateUpdateOne {
|
||||
_u.mutation.SetAPIMode(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetNillableAPIMode sets the "api_mode" field if the given value is not nil.
|
||||
func (_u *ChannelMonitorRequestTemplateUpdateOne) SetNillableAPIMode(v *string) *ChannelMonitorRequestTemplateUpdateOne {
|
||||
if v != nil {
|
||||
_u.SetAPIMode(*v)
|
||||
}
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetDescription sets the "description" field.
|
||||
func (_u *ChannelMonitorRequestTemplateUpdateOne) SetDescription(v string) *ChannelMonitorRequestTemplateUpdateOne {
|
||||
_u.mutation.SetDescription(v)
|
||||
@ -509,6 +545,11 @@ func (_u *ChannelMonitorRequestTemplateUpdateOne) check() error {
|
||||
return &ValidationError{Name: "provider", err: fmt.Errorf(`ent: validator failed for field "ChannelMonitorRequestTemplate.provider": %w`, err)}
|
||||
}
|
||||
}
|
||||
if v, ok := _u.mutation.APIMode(); ok {
|
||||
if err := channelmonitorrequesttemplate.APIModeValidator(v); err != nil {
|
||||
return &ValidationError{Name: "api_mode", err: fmt.Errorf(`ent: validator failed for field "ChannelMonitorRequestTemplate.api_mode": %w`, err)}
|
||||
}
|
||||
}
|
||||
if v, ok := _u.mutation.Description(); ok {
|
||||
if err := channelmonitorrequesttemplate.DescriptionValidator(v); err != nil {
|
||||
return &ValidationError{Name: "description", err: fmt.Errorf(`ent: validator failed for field "ChannelMonitorRequestTemplate.description": %w`, err)}
|
||||
@ -560,6 +601,9 @@ func (_u *ChannelMonitorRequestTemplateUpdateOne) sqlSave(ctx context.Context) (
|
||||
if value, ok := _u.mutation.Provider(); ok {
|
||||
_spec.SetField(channelmonitorrequesttemplate.FieldProvider, field.TypeEnum, value)
|
||||
}
|
||||
if value, ok := _u.mutation.APIMode(); ok {
|
||||
_spec.SetField(channelmonitorrequesttemplate.FieldAPIMode, field.TypeString, value)
|
||||
}
|
||||
if value, ok := _u.mutation.Description(); ok {
|
||||
_spec.SetField(channelmonitorrequesttemplate.FieldDescription, field.TypeString, value)
|
||||
}
|
||||
|
||||
@ -428,6 +428,7 @@ var (
|
||||
{Name: "updated_at", Type: field.TypeTime, SchemaType: map[string]string{"postgres": "timestamptz"}},
|
||||
{Name: "name", Type: field.TypeString, Size: 100},
|
||||
{Name: "provider", Type: field.TypeEnum, Enums: []string{"openai", "anthropic", "gemini"}},
|
||||
{Name: "api_mode", Type: field.TypeString, Size: 32, Default: "chat_completions"},
|
||||
{Name: "endpoint", Type: field.TypeString, Size: 500},
|
||||
{Name: "api_key_encrypted", Type: field.TypeString},
|
||||
{Name: "primary_model", Type: field.TypeString, Size: 200},
|
||||
@ -450,7 +451,7 @@ var (
|
||||
ForeignKeys: []*schema.ForeignKey{
|
||||
{
|
||||
Symbol: "channel_monitors_channel_monitor_request_templates_request_template",
|
||||
Columns: []*schema.Column{ChannelMonitorsColumns[17]},
|
||||
Columns: []*schema.Column{ChannelMonitorsColumns[18]},
|
||||
RefColumns: []*schema.Column{ChannelMonitorRequestTemplatesColumns[0]},
|
||||
OnDelete: schema.SetNull,
|
||||
},
|
||||
@ -459,22 +460,27 @@ var (
|
||||
{
|
||||
Name: "channelmonitor_enabled_last_checked_at",
|
||||
Unique: false,
|
||||
Columns: []*schema.Column{ChannelMonitorsColumns[10], ChannelMonitorsColumns[12]},
|
||||
Columns: []*schema.Column{ChannelMonitorsColumns[11], ChannelMonitorsColumns[13]},
|
||||
},
|
||||
{
|
||||
Name: "channelmonitor_provider",
|
||||
Unique: false,
|
||||
Columns: []*schema.Column{ChannelMonitorsColumns[4]},
|
||||
},
|
||||
{
|
||||
Name: "channelmonitor_provider_api_mode",
|
||||
Unique: false,
|
||||
Columns: []*schema.Column{ChannelMonitorsColumns[4], ChannelMonitorsColumns[5]},
|
||||
},
|
||||
{
|
||||
Name: "channelmonitor_group_name",
|
||||
Unique: false,
|
||||
Columns: []*schema.Column{ChannelMonitorsColumns[9]},
|
||||
Columns: []*schema.Column{ChannelMonitorsColumns[10]},
|
||||
},
|
||||
{
|
||||
Name: "channelmonitor_template_id",
|
||||
Unique: false,
|
||||
Columns: []*schema.Column{ChannelMonitorsColumns[17]},
|
||||
Columns: []*schema.Column{ChannelMonitorsColumns[18]},
|
||||
},
|
||||
},
|
||||
}
|
||||
@ -566,6 +572,7 @@ var (
|
||||
{Name: "updated_at", Type: field.TypeTime, SchemaType: map[string]string{"postgres": "timestamptz"}},
|
||||
{Name: "name", Type: field.TypeString, Size: 100},
|
||||
{Name: "provider", Type: field.TypeEnum, Enums: []string{"openai", "anthropic", "gemini"}},
|
||||
{Name: "api_mode", Type: field.TypeString, Size: 32, Default: "chat_completions"},
|
||||
{Name: "description", Type: field.TypeString, Nullable: true, Size: 500, Default: ""},
|
||||
{Name: "extra_headers", Type: field.TypeJSON},
|
||||
{Name: "body_override_mode", Type: field.TypeString, Size: 10, Default: "off"},
|
||||
@ -582,6 +589,11 @@ var (
|
||||
Unique: true,
|
||||
Columns: []*schema.Column{ChannelMonitorRequestTemplatesColumns[4], ChannelMonitorRequestTemplatesColumns[3]},
|
||||
},
|
||||
{
|
||||
Name: "channelmonitorrequesttemplate_provider_api_mode",
|
||||
Unique: false,
|
||||
Columns: []*schema.Column{ChannelMonitorRequestTemplatesColumns[4], ChannelMonitorRequestTemplatesColumns[5]},
|
||||
},
|
||||
},
|
||||
}
|
||||
// ErrorPassthroughRulesColumns holds the columns for the "error_passthrough_rules" table.
|
||||
|
||||
@ -8752,6 +8752,7 @@ type ChannelMonitorMutation struct {
|
||||
updated_at *time.Time
|
||||
name *string
|
||||
provider *channelmonitor.Provider
|
||||
api_mode *string
|
||||
endpoint *string
|
||||
api_key_encrypted *string
|
||||
primary_model *string
|
||||
@ -9023,6 +9024,42 @@ func (m *ChannelMonitorMutation) ResetProvider() {
|
||||
m.provider = nil
|
||||
}
|
||||
|
||||
// SetAPIMode sets the "api_mode" field.
|
||||
func (m *ChannelMonitorMutation) SetAPIMode(s string) {
|
||||
m.api_mode = &s
|
||||
}
|
||||
|
||||
// APIMode returns the value of the "api_mode" field in the mutation.
|
||||
func (m *ChannelMonitorMutation) APIMode() (r string, exists bool) {
|
||||
v := m.api_mode
|
||||
if v == nil {
|
||||
return
|
||||
}
|
||||
return *v, true
|
||||
}
|
||||
|
||||
// OldAPIMode returns the old "api_mode" field's value of the ChannelMonitor entity.
|
||||
// If the ChannelMonitor 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 *ChannelMonitorMutation) OldAPIMode(ctx context.Context) (v string, err error) {
|
||||
if !m.op.Is(OpUpdateOne) {
|
||||
return v, errors.New("OldAPIMode is only allowed on UpdateOne operations")
|
||||
}
|
||||
if m.id == nil || m.oldValue == nil {
|
||||
return v, errors.New("OldAPIMode requires an ID field in the mutation")
|
||||
}
|
||||
oldValue, err := m.oldValue(ctx)
|
||||
if err != nil {
|
||||
return v, fmt.Errorf("querying old value for OldAPIMode: %w", err)
|
||||
}
|
||||
return oldValue.APIMode, nil
|
||||
}
|
||||
|
||||
// ResetAPIMode resets all changes to the "api_mode" field.
|
||||
func (m *ChannelMonitorMutation) ResetAPIMode() {
|
||||
m.api_mode = nil
|
||||
}
|
||||
|
||||
// SetEndpoint sets the "endpoint" field.
|
||||
func (m *ChannelMonitorMutation) SetEndpoint(s string) {
|
||||
m.endpoint = &s
|
||||
@ -9780,7 +9817,7 @@ func (m *ChannelMonitorMutation) Type() string {
|
||||
// order to get all numeric fields that were incremented/decremented, call
|
||||
// AddedFields().
|
||||
func (m *ChannelMonitorMutation) Fields() []string {
|
||||
fields := make([]string, 0, 17)
|
||||
fields := make([]string, 0, 18)
|
||||
if m.created_at != nil {
|
||||
fields = append(fields, channelmonitor.FieldCreatedAt)
|
||||
}
|
||||
@ -9793,6 +9830,9 @@ func (m *ChannelMonitorMutation) Fields() []string {
|
||||
if m.provider != nil {
|
||||
fields = append(fields, channelmonitor.FieldProvider)
|
||||
}
|
||||
if m.api_mode != nil {
|
||||
fields = append(fields, channelmonitor.FieldAPIMode)
|
||||
}
|
||||
if m.endpoint != nil {
|
||||
fields = append(fields, channelmonitor.FieldEndpoint)
|
||||
}
|
||||
@ -9848,6 +9888,8 @@ func (m *ChannelMonitorMutation) Field(name string) (ent.Value, bool) {
|
||||
return m.Name()
|
||||
case channelmonitor.FieldProvider:
|
||||
return m.Provider()
|
||||
case channelmonitor.FieldAPIMode:
|
||||
return m.APIMode()
|
||||
case channelmonitor.FieldEndpoint:
|
||||
return m.Endpoint()
|
||||
case channelmonitor.FieldAPIKeyEncrypted:
|
||||
@ -9891,6 +9933,8 @@ func (m *ChannelMonitorMutation) OldField(ctx context.Context, name string) (ent
|
||||
return m.OldName(ctx)
|
||||
case channelmonitor.FieldProvider:
|
||||
return m.OldProvider(ctx)
|
||||
case channelmonitor.FieldAPIMode:
|
||||
return m.OldAPIMode(ctx)
|
||||
case channelmonitor.FieldEndpoint:
|
||||
return m.OldEndpoint(ctx)
|
||||
case channelmonitor.FieldAPIKeyEncrypted:
|
||||
@ -9954,6 +9998,13 @@ func (m *ChannelMonitorMutation) SetField(name string, value ent.Value) error {
|
||||
}
|
||||
m.SetProvider(v)
|
||||
return nil
|
||||
case channelmonitor.FieldAPIMode:
|
||||
v, ok := value.(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("unexpected type %T for field %s", value, name)
|
||||
}
|
||||
m.SetAPIMode(v)
|
||||
return nil
|
||||
case channelmonitor.FieldEndpoint:
|
||||
v, ok := value.(string)
|
||||
if !ok {
|
||||
@ -10160,6 +10211,9 @@ func (m *ChannelMonitorMutation) ResetField(name string) error {
|
||||
case channelmonitor.FieldProvider:
|
||||
m.ResetProvider()
|
||||
return nil
|
||||
case channelmonitor.FieldAPIMode:
|
||||
m.ResetAPIMode()
|
||||
return nil
|
||||
case channelmonitor.FieldEndpoint:
|
||||
m.ResetEndpoint()
|
||||
return nil
|
||||
@ -12591,6 +12645,7 @@ type ChannelMonitorRequestTemplateMutation struct {
|
||||
updated_at *time.Time
|
||||
name *string
|
||||
provider *channelmonitorrequesttemplate.Provider
|
||||
api_mode *string
|
||||
description *string
|
||||
extra_headers *map[string]string
|
||||
body_override_mode *string
|
||||
@ -12846,6 +12901,42 @@ func (m *ChannelMonitorRequestTemplateMutation) ResetProvider() {
|
||||
m.provider = nil
|
||||
}
|
||||
|
||||
// SetAPIMode sets the "api_mode" field.
|
||||
func (m *ChannelMonitorRequestTemplateMutation) SetAPIMode(s string) {
|
||||
m.api_mode = &s
|
||||
}
|
||||
|
||||
// APIMode returns the value of the "api_mode" field in the mutation.
|
||||
func (m *ChannelMonitorRequestTemplateMutation) APIMode() (r string, exists bool) {
|
||||
v := m.api_mode
|
||||
if v == nil {
|
||||
return
|
||||
}
|
||||
return *v, true
|
||||
}
|
||||
|
||||
// OldAPIMode returns the old "api_mode" field's value of the ChannelMonitorRequestTemplate entity.
|
||||
// If the ChannelMonitorRequestTemplate 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 *ChannelMonitorRequestTemplateMutation) OldAPIMode(ctx context.Context) (v string, err error) {
|
||||
if !m.op.Is(OpUpdateOne) {
|
||||
return v, errors.New("OldAPIMode is only allowed on UpdateOne operations")
|
||||
}
|
||||
if m.id == nil || m.oldValue == nil {
|
||||
return v, errors.New("OldAPIMode requires an ID field in the mutation")
|
||||
}
|
||||
oldValue, err := m.oldValue(ctx)
|
||||
if err != nil {
|
||||
return v, fmt.Errorf("querying old value for OldAPIMode: %w", err)
|
||||
}
|
||||
return oldValue.APIMode, nil
|
||||
}
|
||||
|
||||
// ResetAPIMode resets all changes to the "api_mode" field.
|
||||
func (m *ChannelMonitorRequestTemplateMutation) ResetAPIMode() {
|
||||
m.api_mode = nil
|
||||
}
|
||||
|
||||
// SetDescription sets the "description" field.
|
||||
func (m *ChannelMonitorRequestTemplateMutation) SetDescription(s string) {
|
||||
m.description = &s
|
||||
@ -13104,7 +13195,7 @@ func (m *ChannelMonitorRequestTemplateMutation) Type() string {
|
||||
// order to get all numeric fields that were incremented/decremented, call
|
||||
// AddedFields().
|
||||
func (m *ChannelMonitorRequestTemplateMutation) Fields() []string {
|
||||
fields := make([]string, 0, 8)
|
||||
fields := make([]string, 0, 9)
|
||||
if m.created_at != nil {
|
||||
fields = append(fields, channelmonitorrequesttemplate.FieldCreatedAt)
|
||||
}
|
||||
@ -13117,6 +13208,9 @@ func (m *ChannelMonitorRequestTemplateMutation) Fields() []string {
|
||||
if m.provider != nil {
|
||||
fields = append(fields, channelmonitorrequesttemplate.FieldProvider)
|
||||
}
|
||||
if m.api_mode != nil {
|
||||
fields = append(fields, channelmonitorrequesttemplate.FieldAPIMode)
|
||||
}
|
||||
if m.description != nil {
|
||||
fields = append(fields, channelmonitorrequesttemplate.FieldDescription)
|
||||
}
|
||||
@ -13145,6 +13239,8 @@ func (m *ChannelMonitorRequestTemplateMutation) Field(name string) (ent.Value, b
|
||||
return m.Name()
|
||||
case channelmonitorrequesttemplate.FieldProvider:
|
||||
return m.Provider()
|
||||
case channelmonitorrequesttemplate.FieldAPIMode:
|
||||
return m.APIMode()
|
||||
case channelmonitorrequesttemplate.FieldDescription:
|
||||
return m.Description()
|
||||
case channelmonitorrequesttemplate.FieldExtraHeaders:
|
||||
@ -13170,6 +13266,8 @@ func (m *ChannelMonitorRequestTemplateMutation) OldField(ctx context.Context, na
|
||||
return m.OldName(ctx)
|
||||
case channelmonitorrequesttemplate.FieldProvider:
|
||||
return m.OldProvider(ctx)
|
||||
case channelmonitorrequesttemplate.FieldAPIMode:
|
||||
return m.OldAPIMode(ctx)
|
||||
case channelmonitorrequesttemplate.FieldDescription:
|
||||
return m.OldDescription(ctx)
|
||||
case channelmonitorrequesttemplate.FieldExtraHeaders:
|
||||
@ -13215,6 +13313,13 @@ func (m *ChannelMonitorRequestTemplateMutation) SetField(name string, value ent.
|
||||
}
|
||||
m.SetProvider(v)
|
||||
return nil
|
||||
case channelmonitorrequesttemplate.FieldAPIMode:
|
||||
v, ok := value.(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("unexpected type %T for field %s", value, name)
|
||||
}
|
||||
m.SetAPIMode(v)
|
||||
return nil
|
||||
case channelmonitorrequesttemplate.FieldDescription:
|
||||
v, ok := value.(string)
|
||||
if !ok {
|
||||
@ -13319,6 +13424,9 @@ func (m *ChannelMonitorRequestTemplateMutation) ResetField(name string) error {
|
||||
case channelmonitorrequesttemplate.FieldProvider:
|
||||
m.ResetProvider()
|
||||
return nil
|
||||
case channelmonitorrequesttemplate.FieldAPIMode:
|
||||
m.ResetAPIMode()
|
||||
return nil
|
||||
case channelmonitorrequesttemplate.FieldDescription:
|
||||
m.ResetDescription()
|
||||
return nil
|
||||
|
||||
@ -464,8 +464,14 @@ func init() {
|
||||
return nil
|
||||
}
|
||||
}()
|
||||
// channelmonitorDescAPIMode is the schema descriptor for api_mode field.
|
||||
channelmonitorDescAPIMode := channelmonitorFields[2].Descriptor()
|
||||
// channelmonitor.DefaultAPIMode holds the default value on creation for the api_mode field.
|
||||
channelmonitor.DefaultAPIMode = channelmonitorDescAPIMode.Default.(string)
|
||||
// channelmonitor.APIModeValidator is a validator for the "api_mode" field. It is called by the builders before save.
|
||||
channelmonitor.APIModeValidator = channelmonitorDescAPIMode.Validators[0].(func(string) error)
|
||||
// channelmonitorDescEndpoint is the schema descriptor for endpoint field.
|
||||
channelmonitorDescEndpoint := channelmonitorFields[2].Descriptor()
|
||||
channelmonitorDescEndpoint := channelmonitorFields[3].Descriptor()
|
||||
// channelmonitor.EndpointValidator is a validator for the "endpoint" field. It is called by the builders before save.
|
||||
channelmonitor.EndpointValidator = func() func(string) error {
|
||||
validators := channelmonitorDescEndpoint.Validators
|
||||
@ -483,11 +489,11 @@ func init() {
|
||||
}
|
||||
}()
|
||||
// channelmonitorDescAPIKeyEncrypted is the schema descriptor for api_key_encrypted field.
|
||||
channelmonitorDescAPIKeyEncrypted := channelmonitorFields[3].Descriptor()
|
||||
channelmonitorDescAPIKeyEncrypted := channelmonitorFields[4].Descriptor()
|
||||
// channelmonitor.APIKeyEncryptedValidator is a validator for the "api_key_encrypted" field. It is called by the builders before save.
|
||||
channelmonitor.APIKeyEncryptedValidator = channelmonitorDescAPIKeyEncrypted.Validators[0].(func(string) error)
|
||||
// channelmonitorDescPrimaryModel is the schema descriptor for primary_model field.
|
||||
channelmonitorDescPrimaryModel := channelmonitorFields[4].Descriptor()
|
||||
channelmonitorDescPrimaryModel := channelmonitorFields[5].Descriptor()
|
||||
// channelmonitor.PrimaryModelValidator is a validator for the "primary_model" field. It is called by the builders before save.
|
||||
channelmonitor.PrimaryModelValidator = func() func(string) error {
|
||||
validators := channelmonitorDescPrimaryModel.Validators
|
||||
@ -505,29 +511,29 @@ func init() {
|
||||
}
|
||||
}()
|
||||
// channelmonitorDescExtraModels is the schema descriptor for extra_models field.
|
||||
channelmonitorDescExtraModels := channelmonitorFields[5].Descriptor()
|
||||
channelmonitorDescExtraModels := channelmonitorFields[6].Descriptor()
|
||||
// channelmonitor.DefaultExtraModels holds the default value on creation for the extra_models field.
|
||||
channelmonitor.DefaultExtraModels = channelmonitorDescExtraModels.Default.([]string)
|
||||
// channelmonitorDescGroupName is the schema descriptor for group_name field.
|
||||
channelmonitorDescGroupName := channelmonitorFields[6].Descriptor()
|
||||
channelmonitorDescGroupName := channelmonitorFields[7].Descriptor()
|
||||
// channelmonitor.DefaultGroupName holds the default value on creation for the group_name field.
|
||||
channelmonitor.DefaultGroupName = channelmonitorDescGroupName.Default.(string)
|
||||
// channelmonitor.GroupNameValidator is a validator for the "group_name" field. It is called by the builders before save.
|
||||
channelmonitor.GroupNameValidator = channelmonitorDescGroupName.Validators[0].(func(string) error)
|
||||
// channelmonitorDescEnabled is the schema descriptor for enabled field.
|
||||
channelmonitorDescEnabled := channelmonitorFields[7].Descriptor()
|
||||
channelmonitorDescEnabled := channelmonitorFields[8].Descriptor()
|
||||
// channelmonitor.DefaultEnabled holds the default value on creation for the enabled field.
|
||||
channelmonitor.DefaultEnabled = channelmonitorDescEnabled.Default.(bool)
|
||||
// channelmonitorDescIntervalSeconds is the schema descriptor for interval_seconds field.
|
||||
channelmonitorDescIntervalSeconds := channelmonitorFields[8].Descriptor()
|
||||
channelmonitorDescIntervalSeconds := channelmonitorFields[9].Descriptor()
|
||||
// channelmonitor.IntervalSecondsValidator is a validator for the "interval_seconds" field. It is called by the builders before save.
|
||||
channelmonitor.IntervalSecondsValidator = channelmonitorDescIntervalSeconds.Validators[0].(func(int) error)
|
||||
// channelmonitorDescExtraHeaders is the schema descriptor for extra_headers field.
|
||||
channelmonitorDescExtraHeaders := channelmonitorFields[12].Descriptor()
|
||||
channelmonitorDescExtraHeaders := channelmonitorFields[13].Descriptor()
|
||||
// channelmonitor.DefaultExtraHeaders holds the default value on creation for the extra_headers field.
|
||||
channelmonitor.DefaultExtraHeaders = channelmonitorDescExtraHeaders.Default.(map[string]string)
|
||||
// channelmonitorDescBodyOverrideMode is the schema descriptor for body_override_mode field.
|
||||
channelmonitorDescBodyOverrideMode := channelmonitorFields[13].Descriptor()
|
||||
channelmonitorDescBodyOverrideMode := channelmonitorFields[14].Descriptor()
|
||||
// channelmonitor.DefaultBodyOverrideMode holds the default value on creation for the body_override_mode field.
|
||||
channelmonitor.DefaultBodyOverrideMode = channelmonitorDescBodyOverrideMode.Default.(string)
|
||||
// channelmonitor.BodyOverrideModeValidator is a validator for the "body_override_mode" field. It is called by the builders before save.
|
||||
@ -661,18 +667,24 @@ func init() {
|
||||
return nil
|
||||
}
|
||||
}()
|
||||
// channelmonitorrequesttemplateDescAPIMode is the schema descriptor for api_mode field.
|
||||
channelmonitorrequesttemplateDescAPIMode := channelmonitorrequesttemplateFields[2].Descriptor()
|
||||
// channelmonitorrequesttemplate.DefaultAPIMode holds the default value on creation for the api_mode field.
|
||||
channelmonitorrequesttemplate.DefaultAPIMode = channelmonitorrequesttemplateDescAPIMode.Default.(string)
|
||||
// channelmonitorrequesttemplate.APIModeValidator is a validator for the "api_mode" field. It is called by the builders before save.
|
||||
channelmonitorrequesttemplate.APIModeValidator = channelmonitorrequesttemplateDescAPIMode.Validators[0].(func(string) error)
|
||||
// channelmonitorrequesttemplateDescDescription is the schema descriptor for description field.
|
||||
channelmonitorrequesttemplateDescDescription := channelmonitorrequesttemplateFields[2].Descriptor()
|
||||
channelmonitorrequesttemplateDescDescription := channelmonitorrequesttemplateFields[3].Descriptor()
|
||||
// channelmonitorrequesttemplate.DefaultDescription holds the default value on creation for the description field.
|
||||
channelmonitorrequesttemplate.DefaultDescription = channelmonitorrequesttemplateDescDescription.Default.(string)
|
||||
// channelmonitorrequesttemplate.DescriptionValidator is a validator for the "description" field. It is called by the builders before save.
|
||||
channelmonitorrequesttemplate.DescriptionValidator = channelmonitorrequesttemplateDescDescription.Validators[0].(func(string) error)
|
||||
// channelmonitorrequesttemplateDescExtraHeaders is the schema descriptor for extra_headers field.
|
||||
channelmonitorrequesttemplateDescExtraHeaders := channelmonitorrequesttemplateFields[3].Descriptor()
|
||||
channelmonitorrequesttemplateDescExtraHeaders := channelmonitorrequesttemplateFields[4].Descriptor()
|
||||
// channelmonitorrequesttemplate.DefaultExtraHeaders holds the default value on creation for the extra_headers field.
|
||||
channelmonitorrequesttemplate.DefaultExtraHeaders = channelmonitorrequesttemplateDescExtraHeaders.Default.(map[string]string)
|
||||
// channelmonitorrequesttemplateDescBodyOverrideMode is the schema descriptor for body_override_mode field.
|
||||
channelmonitorrequesttemplateDescBodyOverrideMode := channelmonitorrequesttemplateFields[4].Descriptor()
|
||||
channelmonitorrequesttemplateDescBodyOverrideMode := channelmonitorrequesttemplateFields[5].Descriptor()
|
||||
// channelmonitorrequesttemplate.DefaultBodyOverrideMode holds the default value on creation for the body_override_mode field.
|
||||
channelmonitorrequesttemplate.DefaultBodyOverrideMode = channelmonitorrequesttemplateDescBodyOverrideMode.Default.(string)
|
||||
// channelmonitorrequesttemplate.BodyOverrideModeValidator is a validator for the "body_override_mode" field. It is called by the builders before save.
|
||||
|
||||
@ -36,6 +36,10 @@ func (ChannelMonitor) Fields() []ent.Field {
|
||||
MaxLen(100),
|
||||
field.Enum("provider").
|
||||
Values("openai", "anthropic", "gemini"),
|
||||
field.String("api_mode").
|
||||
Default("chat_completions").
|
||||
MaxLen(32).
|
||||
Comment("OpenAI request protocol: chat_completions or responses; non-OpenAI uses chat_completions"),
|
||||
field.String("endpoint").
|
||||
NotEmpty().
|
||||
MaxLen(500).
|
||||
@ -104,6 +108,7 @@ func (ChannelMonitor) Indexes() []ent.Index {
|
||||
return []ent.Index{
|
||||
index.Fields("enabled", "last_checked_at"),
|
||||
index.Fields("provider"),
|
||||
index.Fields("provider", "api_mode"),
|
||||
index.Fields("group_name"),
|
||||
index.Fields("template_id"),
|
||||
}
|
||||
|
||||
@ -40,6 +40,10 @@ func (ChannelMonitorRequestTemplate) Fields() []ent.Field {
|
||||
MaxLen(100),
|
||||
field.Enum("provider").
|
||||
Values("openai", "anthropic", "gemini"),
|
||||
field.String("api_mode").
|
||||
Default("chat_completions").
|
||||
MaxLen(32).
|
||||
Comment("OpenAI request protocol: chat_completions or responses; non-OpenAI uses chat_completions"),
|
||||
field.String("description").
|
||||
Optional().
|
||||
Default("").
|
||||
@ -76,5 +80,6 @@ func (ChannelMonitorRequestTemplate) Indexes() []ent.Index {
|
||||
return []ent.Index{
|
||||
// 同一 provider 内 name 唯一:允许 Anthropic + OpenAI 重名 "伪装官方客户端"。
|
||||
index.Fields("provider", "name").Unique(),
|
||||
index.Fields("provider", "api_mode"),
|
||||
}
|
||||
}
|
||||
|
||||
@ -38,6 +38,7 @@ func NewChannelMonitorHandler(monitorService *service.ChannelMonitorService) *Ch
|
||||
type channelMonitorCreateRequest struct {
|
||||
Name string `json:"name" binding:"required,max=100"`
|
||||
Provider string `json:"provider" binding:"required,oneof=openai anthropic gemini"`
|
||||
APIMode string `json:"api_mode" binding:"omitempty,oneof=chat_completions responses"`
|
||||
Endpoint string `json:"endpoint" binding:"required,max=500"`
|
||||
APIKey string `json:"api_key" binding:"required,max=2000"`
|
||||
PrimaryModel string `json:"primary_model" binding:"required,max=200"`
|
||||
@ -54,6 +55,7 @@ type channelMonitorCreateRequest struct {
|
||||
type channelMonitorUpdateRequest struct {
|
||||
Name *string `json:"name" binding:"omitempty,max=100"`
|
||||
Provider *string `json:"provider" binding:"omitempty,oneof=openai anthropic gemini"`
|
||||
APIMode *string `json:"api_mode" binding:"omitempty,oneof=chat_completions responses"`
|
||||
Endpoint *string `json:"endpoint" binding:"omitempty,max=500"`
|
||||
APIKey *string `json:"api_key" binding:"omitempty,max=2000"`
|
||||
PrimaryModel *string `json:"primary_model" binding:"omitempty,max=200"`
|
||||
@ -72,6 +74,7 @@ type channelMonitorResponse struct {
|
||||
ID int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Provider string `json:"provider"`
|
||||
APIMode string `json:"api_mode"`
|
||||
Endpoint string `json:"endpoint"`
|
||||
APIKeyMasked string `json:"api_key_masked"`
|
||||
APIKeyDecryptFailed bool `json:"api_key_decrypt_failed"`
|
||||
@ -138,6 +141,7 @@ func channelMonitorToResponse(m *service.ChannelMonitor) *channelMonitorResponse
|
||||
ID: m.ID,
|
||||
Name: m.Name,
|
||||
Provider: m.Provider,
|
||||
APIMode: m.APIMode,
|
||||
Endpoint: m.Endpoint,
|
||||
APIKeyMasked: maskAPIKey(m.APIKey),
|
||||
APIKeyDecryptFailed: m.APIKeyDecryptFailed,
|
||||
@ -303,6 +307,7 @@ func (h *ChannelMonitorHandler) Create(c *gin.Context) {
|
||||
m, err := h.monitorService.Create(c.Request.Context(), service.ChannelMonitorCreateParams{
|
||||
Name: req.Name,
|
||||
Provider: req.Provider,
|
||||
APIMode: req.APIMode,
|
||||
Endpoint: req.Endpoint,
|
||||
APIKey: req.APIKey,
|
||||
PrimaryModel: req.PrimaryModel,
|
||||
@ -338,6 +343,7 @@ func (h *ChannelMonitorHandler) Update(c *gin.Context) {
|
||||
m, err := h.monitorService.Update(c.Request.Context(), id, service.ChannelMonitorUpdateParams{
|
||||
Name: req.Name,
|
||||
Provider: req.Provider,
|
||||
APIMode: req.APIMode,
|
||||
Endpoint: req.Endpoint,
|
||||
APIKey: req.APIKey,
|
||||
PrimaryModel: req.PrimaryModel,
|
||||
|
||||
@ -27,6 +27,7 @@ func NewChannelMonitorRequestTemplateHandler(templateService *service.ChannelMon
|
||||
type channelMonitorTemplateCreateRequest struct {
|
||||
Name string `json:"name" binding:"required,max=100"`
|
||||
Provider string `json:"provider" binding:"required,oneof=openai anthropic gemini"`
|
||||
APIMode string `json:"api_mode" binding:"omitempty,oneof=chat_completions responses"`
|
||||
Description string `json:"description" binding:"max=500"`
|
||||
ExtraHeaders map[string]string `json:"extra_headers"`
|
||||
BodyOverrideMode string `json:"body_override_mode" binding:"omitempty,oneof=off merge replace"`
|
||||
@ -35,6 +36,7 @@ type channelMonitorTemplateCreateRequest struct {
|
||||
|
||||
type channelMonitorTemplateUpdateRequest struct {
|
||||
Name *string `json:"name" binding:"omitempty,max=100"`
|
||||
APIMode *string `json:"api_mode" binding:"omitempty,oneof=chat_completions responses"`
|
||||
Description *string `json:"description" binding:"omitempty,max=500"`
|
||||
ExtraHeaders *map[string]string `json:"extra_headers"`
|
||||
BodyOverrideMode *string `json:"body_override_mode" binding:"omitempty,oneof=off merge replace"`
|
||||
@ -45,6 +47,7 @@ type channelMonitorTemplateResponse struct {
|
||||
ID int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Provider string `json:"provider"`
|
||||
APIMode string `json:"api_mode"`
|
||||
Description string `json:"description"`
|
||||
ExtraHeaders map[string]string `json:"extra_headers"`
|
||||
BodyOverrideMode string `json:"body_override_mode"`
|
||||
@ -67,6 +70,7 @@ func (h *ChannelMonitorRequestTemplateHandler) toResponse(c *gin.Context, t *ser
|
||||
ID: t.ID,
|
||||
Name: t.Name,
|
||||
Provider: t.Provider,
|
||||
APIMode: t.APIMode,
|
||||
Description: t.Description,
|
||||
ExtraHeaders: headers,
|
||||
BodyOverrideMode: t.BodyOverrideMode,
|
||||
@ -93,6 +97,7 @@ func parseTemplateID(c *gin.Context) (int64, bool) {
|
||||
func (h *ChannelMonitorRequestTemplateHandler) List(c *gin.Context) {
|
||||
items, err := h.templateService.List(c.Request.Context(), service.ChannelMonitorRequestTemplateListParams{
|
||||
Provider: strings.TrimSpace(c.Query("provider")),
|
||||
APIMode: strings.TrimSpace(c.Query("api_mode")),
|
||||
})
|
||||
if err != nil {
|
||||
response.ErrorFrom(c, err)
|
||||
@ -129,6 +134,7 @@ func (h *ChannelMonitorRequestTemplateHandler) Create(c *gin.Context) {
|
||||
t, err := h.templateService.Create(c.Request.Context(), service.ChannelMonitorRequestTemplateCreateParams{
|
||||
Name: req.Name,
|
||||
Provider: req.Provider,
|
||||
APIMode: req.APIMode,
|
||||
Description: req.Description,
|
||||
ExtraHeaders: req.ExtraHeaders,
|
||||
BodyOverrideMode: req.BodyOverrideMode,
|
||||
@ -154,6 +160,7 @@ func (h *ChannelMonitorRequestTemplateHandler) Update(c *gin.Context) {
|
||||
}
|
||||
t, err := h.templateService.Update(c.Request.Context(), id, service.ChannelMonitorRequestTemplateUpdateParams{
|
||||
Name: req.Name,
|
||||
APIMode: req.APIMode,
|
||||
Description: req.Description,
|
||||
ExtraHeaders: req.ExtraHeaders,
|
||||
BodyOverrideMode: req.BodyOverrideMode,
|
||||
@ -209,6 +216,7 @@ type associatedMonitorBriefResponse struct {
|
||||
ID int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Provider string `json:"provider"`
|
||||
APIMode string `json:"api_mode"`
|
||||
Enabled bool `json:"enabled"`
|
||||
}
|
||||
|
||||
@ -227,7 +235,7 @@ func (h *ChannelMonitorRequestTemplateHandler) AssociatedMonitors(c *gin.Context
|
||||
out := make([]associatedMonitorBriefResponse, 0, len(items))
|
||||
for _, m := range items {
|
||||
out = append(out, associatedMonitorBriefResponse{
|
||||
ID: m.ID, Name: m.Name, Provider: m.Provider, Enabled: m.Enabled,
|
||||
ID: m.ID, Name: m.Name, Provider: m.Provider, APIMode: m.APIMode, Enabled: m.Enabled,
|
||||
})
|
||||
}
|
||||
response.Success(c, gin.H{"items": out})
|
||||
|
||||
@ -37,6 +37,7 @@ func (r *channelMonitorRepository) Create(ctx context.Context, m *service.Channe
|
||||
builder := client.ChannelMonitor.Create().
|
||||
SetName(m.Name).
|
||||
SetProvider(channelmonitor.Provider(m.Provider)).
|
||||
SetAPIMode(defaultAPIModeRepo(m.APIMode)).
|
||||
SetEndpoint(m.Endpoint).
|
||||
SetAPIKeyEncrypted(m.APIKey). // 调用方传入的已是密文
|
||||
SetPrimaryModel(m.PrimaryModel).
|
||||
@ -79,6 +80,7 @@ func (r *channelMonitorRepository) Update(ctx context.Context, m *service.Channe
|
||||
updater := client.ChannelMonitor.UpdateOneID(m.ID).
|
||||
SetName(m.Name).
|
||||
SetProvider(channelmonitor.Provider(m.Provider)).
|
||||
SetAPIMode(defaultAPIModeRepo(m.APIMode)).
|
||||
SetEndpoint(m.Endpoint).
|
||||
SetAPIKeyEncrypted(m.APIKey).
|
||||
SetPrimaryModel(m.PrimaryModel).
|
||||
@ -708,6 +710,7 @@ func entToServiceMonitor(row *dbent.ChannelMonitor) *service.ChannelMonitor {
|
||||
ID: row.ID,
|
||||
Name: row.Name,
|
||||
Provider: string(row.Provider),
|
||||
APIMode: defaultAPIModeRepo(row.APIMode),
|
||||
Endpoint: row.Endpoint,
|
||||
APIKey: row.APIKeyEncrypted, // 仍为密文,service 层负责解密
|
||||
PrimaryModel: row.PrimaryModel,
|
||||
@ -747,6 +750,13 @@ func defaultBodyModeRepo(mode string) string {
|
||||
return mode
|
||||
}
|
||||
|
||||
func defaultAPIModeRepo(apiMode string) string {
|
||||
if apiMode == "" {
|
||||
return "chat_completions"
|
||||
}
|
||||
return apiMode
|
||||
}
|
||||
|
||||
func emptySliceIfNil(in []string) []string {
|
||||
if in == nil {
|
||||
return []string{}
|
||||
|
||||
@ -30,6 +30,7 @@ func (r *channelMonitorRequestTemplateRepository) Create(ctx context.Context, t
|
||||
builder := client.ChannelMonitorRequestTemplate.Create().
|
||||
SetName(t.Name).
|
||||
SetProvider(channelmonitorrequesttemplate.Provider(t.Provider)).
|
||||
SetAPIMode(defaultAPIModeRepo(t.APIMode)).
|
||||
SetDescription(t.Description).
|
||||
SetExtraHeaders(emptyHeadersIfNilRepo(t.ExtraHeaders)).
|
||||
SetBodyOverrideMode(defaultBodyModeRepo(t.BodyOverrideMode))
|
||||
@ -61,6 +62,7 @@ func (r *channelMonitorRequestTemplateRepository) Update(ctx context.Context, t
|
||||
client := clientFromContext(ctx, r.client)
|
||||
updater := client.ChannelMonitorRequestTemplate.UpdateOneID(t.ID).
|
||||
SetName(t.Name).
|
||||
SetAPIMode(defaultAPIModeRepo(t.APIMode)).
|
||||
SetDescription(t.Description).
|
||||
SetExtraHeaders(emptyHeadersIfNilRepo(t.ExtraHeaders)).
|
||||
SetBodyOverrideMode(defaultBodyModeRepo(t.BodyOverrideMode))
|
||||
@ -90,8 +92,11 @@ func (r *channelMonitorRequestTemplateRepository) List(ctx context.Context, para
|
||||
if params.Provider != "" {
|
||||
q = q.Where(channelmonitorrequesttemplate.ProviderEQ(channelmonitorrequesttemplate.Provider(params.Provider)))
|
||||
}
|
||||
if params.APIMode != "" {
|
||||
q = q.Where(channelmonitorrequesttemplate.APIModeEQ(defaultAPIModeRepo(params.APIMode)))
|
||||
}
|
||||
rows, err := q.
|
||||
Order(dbent.Asc(channelmonitorrequesttemplate.FieldProvider), dbent.Asc(channelmonitorrequesttemplate.FieldName)).
|
||||
Order(dbent.Asc(channelmonitorrequesttemplate.FieldProvider), dbent.Asc(channelmonitorrequesttemplate.FieldAPIMode), dbent.Asc(channelmonitorrequesttemplate.FieldName)).
|
||||
All(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("list monitor templates: %w", err)
|
||||
@ -122,7 +127,10 @@ func (r *channelMonitorRequestTemplateRepository) ApplyToMonitors(ctx context.Co
|
||||
Where(
|
||||
channelmonitor.TemplateIDEQ(id),
|
||||
channelmonitor.IDIn(monitorIDs...),
|
||||
channelmonitor.ProviderEQ(channelmonitor.Provider(tpl.Provider)),
|
||||
channelmonitor.APIModeEQ(defaultAPIModeRepo(tpl.APIMode)),
|
||||
).
|
||||
SetAPIMode(defaultAPIModeRepo(tpl.APIMode)).
|
||||
SetExtraHeaders(emptyHeadersIfNilRepo(tpl.ExtraHeaders)).
|
||||
SetBodyOverrideMode(defaultBodyModeRepo(tpl.BodyOverrideMode))
|
||||
if tpl.BodyOverride != nil {
|
||||
@ -165,6 +173,7 @@ func (r *channelMonitorRequestTemplateRepository) ListAssociatedMonitors(ctx con
|
||||
ID: row.ID,
|
||||
Name: row.Name,
|
||||
Provider: string(row.Provider),
|
||||
APIMode: defaultAPIModeRepo(row.APIMode),
|
||||
Enabled: row.Enabled,
|
||||
})
|
||||
}
|
||||
@ -185,6 +194,7 @@ func entToServiceTemplate(row *dbent.ChannelMonitorRequestTemplate) *service.Cha
|
||||
ID: row.ID,
|
||||
Name: row.Name,
|
||||
Provider: string(row.Provider),
|
||||
APIMode: defaultAPIModeRepo(row.APIMode),
|
||||
Description: row.Description,
|
||||
ExtraHeaders: headers,
|
||||
BodyOverrideMode: row.BodyOverrideMode,
|
||||
|
||||
@ -40,6 +40,8 @@ func newSSRFSafeHTTPClient(timeout time.Duration) *http.Client {
|
||||
// CheckOptions 承载一次检测的自定义入参。
|
||||
// 所有字段都是可选(零值即等价于"用默认行为")。
|
||||
type CheckOptions struct {
|
||||
// APIMode 仅对 OpenAI provider 生效;空串等同 chat_completions。
|
||||
APIMode string
|
||||
// ExtraHeaders 用户自定义 HTTP 头(merge 到 adapter 默认 headers,用户优先)。
|
||||
ExtraHeaders map[string]string
|
||||
// BodyOverrideMode: off | merge | replace
|
||||
@ -164,21 +166,7 @@ type providerAdapter struct {
|
||||
//
|
||||
//nolint:gochecknoglobals // 适配器表是只读静态数据,初始化后不变更。
|
||||
var providerAdapters = map[string]providerAdapter{
|
||||
MonitorProviderOpenAI: {
|
||||
buildPath: func(string) string { return providerOpenAIPath },
|
||||
buildBody: func(model, prompt string) ([]byte, error) {
|
||||
return json.Marshal(map[string]any{
|
||||
"model": model,
|
||||
"messages": []map[string]string{{"role": "user", "content": prompt}},
|
||||
"max_tokens": monitorChallengeMaxTokens,
|
||||
"stream": false,
|
||||
})
|
||||
},
|
||||
buildHeaders: func(apiKey string) map[string]string {
|
||||
return map[string]string{"Authorization": "Bearer " + apiKey}
|
||||
},
|
||||
textPath: "choices.0.message.content",
|
||||
},
|
||||
MonitorProviderOpenAI: providerOpenAIChatAdapter,
|
||||
MonitorProviderAnthropic: {
|
||||
buildPath: func(string) string { return providerAnthropicPath },
|
||||
buildBody: func(model, prompt string) ([]byte, error) {
|
||||
@ -215,6 +203,50 @@ var providerAdapters = map[string]providerAdapter{
|
||||
},
|
||||
}
|
||||
|
||||
//nolint:gochecknoglobals // 适配器表是只读静态数据,初始化后不变更。
|
||||
var providerOpenAIChatAdapter = providerAdapter{
|
||||
buildPath: func(string) string { return providerOpenAIPath },
|
||||
buildBody: func(model, prompt string) ([]byte, error) {
|
||||
return json.Marshal(map[string]any{
|
||||
"model": model,
|
||||
"messages": []map[string]string{{"role": "user", "content": prompt}},
|
||||
"max_tokens": monitorChallengeMaxTokens,
|
||||
"stream": false,
|
||||
})
|
||||
},
|
||||
buildHeaders: func(apiKey string) map[string]string {
|
||||
return map[string]string{"Authorization": "Bearer " + apiKey}
|
||||
},
|
||||
textPath: "choices.0.message.content",
|
||||
}
|
||||
|
||||
//nolint:gochecknoglobals // 适配器表是只读静态数据,初始化后不变更。
|
||||
var providerOpenAIResponsesAdapter = providerAdapter{
|
||||
buildPath: func(string) string { return providerOpenAIResponsesPath },
|
||||
buildBody: func(model, prompt string) ([]byte, error) {
|
||||
return json.Marshal(map[string]any{
|
||||
"model": model,
|
||||
"instructions": "You are a channel health-check endpoint. Answer the arithmetic challenge exactly and briefly.",
|
||||
"input": prompt,
|
||||
"max_output_tokens": monitorChallengeMaxTokens,
|
||||
"stream": false,
|
||||
})
|
||||
},
|
||||
buildHeaders: func(apiKey string) map[string]string {
|
||||
return map[string]string{"Authorization": "Bearer " + apiKey}
|
||||
},
|
||||
textPath: "output.0.content.0.text",
|
||||
}
|
||||
|
||||
// providerAdapterFor 按 provider + api_mode 选择具体 adapter。
|
||||
func providerAdapterFor(provider, apiMode string) (providerAdapter, string, bool) {
|
||||
if provider == MonitorProviderOpenAI && defaultAPIMode(apiMode) == MonitorAPIModeResponses {
|
||||
return providerOpenAIResponsesAdapter, MonitorAPIModeResponses, true
|
||||
}
|
||||
adapter, ok := providerAdapters[provider]
|
||||
return adapter, MonitorAPIModeChatCompletions, ok
|
||||
}
|
||||
|
||||
// isSupportedProvider 校验 provider 字符串是否在 adapter 表中。
|
||||
// 供 validate.go 的 validateProvider 复用,避免两份 switch 漂移。
|
||||
func isSupportedProvider(p string) bool {
|
||||
@ -231,11 +263,15 @@ func isSupportedProvider(p string) bool {
|
||||
// - status: HTTP 状态码
|
||||
// - err: 网络 / 序列化错误
|
||||
func callProvider(ctx context.Context, provider, endpoint, apiKey, model, prompt string, opts *CheckOptions) (extractedText, rawBody string, status int, err error) {
|
||||
adapter, ok := providerAdapters[provider]
|
||||
requestedAPIMode := checkAPIMode(opts)
|
||||
if err := validateAPIMode(provider, requestedAPIMode); err != nil {
|
||||
return "", "", 0, err
|
||||
}
|
||||
adapter, apiMode, ok := providerAdapterFor(provider, requestedAPIMode)
|
||||
if !ok {
|
||||
return "", "", 0, fmt.Errorf("unsupported provider %q", provider)
|
||||
}
|
||||
body, err := buildRequestBody(adapter, provider, model, prompt, opts)
|
||||
body, err := buildRequestBody(adapter, provider, apiMode, model, prompt, opts)
|
||||
if err != nil {
|
||||
return "", "", 0, err
|
||||
}
|
||||
@ -275,13 +311,16 @@ func mergeHeaders(base map[string]string, opts *CheckOptions) map[string]string
|
||||
// - replace: 直接 marshal BodyOverride 作为完整 body
|
||||
//
|
||||
// 任何 mode 返回的 []byte 都已经是合法 JSON,可直接送入 postRawJSON。
|
||||
func buildRequestBody(adapter providerAdapter, provider, model, prompt string, opts *CheckOptions) ([]byte, error) {
|
||||
func buildRequestBody(adapter providerAdapter, provider, apiMode, model, prompt string, opts *CheckOptions) ([]byte, error) {
|
||||
mode := bodyOverrideMode(opts)
|
||||
|
||||
if mode == MonitorBodyOverrideModeReplace {
|
||||
if opts == nil || len(opts.BodyOverride) == 0 {
|
||||
return nil, fmt.Errorf("replace mode: body_override is empty")
|
||||
}
|
||||
if err := validateReplaceRequestBody(provider, apiMode, opts.BodyOverride); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
body, err := json.Marshal(opts.BodyOverride)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("marshal body_override (replace): %w", err)
|
||||
@ -301,7 +340,7 @@ func buildRequestBody(adapter providerAdapter, provider, model, prompt string, o
|
||||
if err := json.Unmarshal(defaultBody, &defaultMap); err != nil {
|
||||
return nil, fmt.Errorf("unmarshal default body for merge: %w", err)
|
||||
}
|
||||
deny := bodyMergeKeyDenyList[provider]
|
||||
deny := bodyMergeKeyDenyList[bodyMergeDenyKey(provider, apiMode)]
|
||||
for k, v := range opts.BodyOverride {
|
||||
if deny[k] {
|
||||
continue
|
||||
@ -321,9 +360,63 @@ func buildRequestBody(adapter providerAdapter, provider, model, prompt string, o
|
||||
//
|
||||
//nolint:gochecknoglobals // 静态查表,初始化后不变。
|
||||
var bodyMergeKeyDenyList = map[string]map[string]bool{
|
||||
MonitorProviderOpenAI: {"model": true, "messages": true, "stream": true},
|
||||
MonitorProviderAnthropic: {"model": true, "messages": true},
|
||||
MonitorProviderGemini: {"contents": true},
|
||||
MonitorProviderOpenAI + ":" + MonitorAPIModeChatCompletions: {"model": true, "messages": true, "stream": true},
|
||||
MonitorProviderOpenAI + ":" + MonitorAPIModeResponses: {"model": true, "instructions": true, "input": true, "stream": true},
|
||||
MonitorProviderAnthropic: {"model": true, "messages": true},
|
||||
MonitorProviderGemini: {"contents": true},
|
||||
}
|
||||
|
||||
func checkAPIMode(opts *CheckOptions) string {
|
||||
if opts == nil {
|
||||
return MonitorAPIModeChatCompletions
|
||||
}
|
||||
return defaultAPIMode(opts.APIMode)
|
||||
}
|
||||
|
||||
func bodyMergeDenyKey(provider, apiMode string) string {
|
||||
if provider == MonitorProviderOpenAI {
|
||||
return provider + ":" + defaultAPIMode(apiMode)
|
||||
}
|
||||
return provider
|
||||
}
|
||||
|
||||
func validateReplaceRequestBody(provider, apiMode string, body map[string]any) error {
|
||||
if provider != MonitorProviderOpenAI {
|
||||
return nil
|
||||
}
|
||||
switch defaultAPIMode(apiMode) {
|
||||
case MonitorAPIModeResponses:
|
||||
if strings.TrimSpace(stringFromAny(body["instructions"])) == "" || !hasNonEmptyBodyValue(body["input"]) {
|
||||
return fmt.Errorf("replace mode responses body: instructions and input are required")
|
||||
}
|
||||
case MonitorAPIModeChatCompletions:
|
||||
if !hasNonEmptyBodyValue(body["messages"]) {
|
||||
return fmt.Errorf("replace mode chat_completions body: messages are required")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func stringFromAny(v any) string {
|
||||
s, _ := v.(string)
|
||||
return s
|
||||
}
|
||||
|
||||
func hasNonEmptyBodyValue(v any) bool {
|
||||
switch val := v.(type) {
|
||||
case nil:
|
||||
return false
|
||||
case string:
|
||||
return strings.TrimSpace(val) != ""
|
||||
case []any:
|
||||
return len(val) > 0
|
||||
case []map[string]any:
|
||||
return len(val) > 0
|
||||
case []map[string]string:
|
||||
return len(val) > 0
|
||||
default:
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// postRawJSON 发送 POST + 已序列化好的 JSON 字节,限制响应体大小,返回响应字节、HTTP status、错误。
|
||||
|
||||
@ -7,6 +7,8 @@ import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
@ -57,6 +59,76 @@ func setupFakeAnthropic(t *testing.T, handler *captureHandler) string {
|
||||
return srv.URL
|
||||
}
|
||||
|
||||
type openAICaptureHandler struct {
|
||||
lastBody map[string]any
|
||||
lastHeaders http.Header
|
||||
lastPath string
|
||||
status int
|
||||
}
|
||||
|
||||
func (h *openAICaptureHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
h.lastHeaders = r.Header.Clone()
|
||||
h.lastPath = r.URL.Path
|
||||
defer func() { _ = r.Body.Close() }()
|
||||
var parsed map[string]any
|
||||
_ = json.NewDecoder(r.Body).Decode(&parsed)
|
||||
h.lastBody = parsed
|
||||
|
||||
if h.status == 0 {
|
||||
h.status = http.StatusOK
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(h.status)
|
||||
|
||||
answer := answerFromOpenAIRequest(parsed)
|
||||
if h.lastPath == providerOpenAIResponsesPath {
|
||||
_ = json.NewEncoder(w).Encode(map[string]any{
|
||||
"output": []map[string]any{{
|
||||
"content": []map[string]any{{"type": "output_text", "text": answer}},
|
||||
}},
|
||||
})
|
||||
return
|
||||
}
|
||||
_ = json.NewEncoder(w).Encode(map[string]any{
|
||||
"choices": []map[string]any{{"message": map[string]any{"content": answer}}},
|
||||
})
|
||||
}
|
||||
|
||||
func setupFakeOpenAI(t *testing.T, handler *openAICaptureHandler) string {
|
||||
t.Helper()
|
||||
swapMonitorHTTPClient(t)
|
||||
srv := httptest.NewServer(handler)
|
||||
t.Cleanup(srv.Close)
|
||||
return srv.URL
|
||||
}
|
||||
|
||||
func answerFromOpenAIRequest(body map[string]any) string {
|
||||
prompt, _ := body["input"].(string)
|
||||
if prompt == "" {
|
||||
if messages, ok := body["messages"].([]any); ok && len(messages) > 0 {
|
||||
if msg, ok := messages[0].(map[string]any); ok {
|
||||
prompt, _ = msg["content"].(string)
|
||||
}
|
||||
}
|
||||
}
|
||||
return answerFromChallengePrompt(prompt)
|
||||
}
|
||||
|
||||
var challengeQuestionRegex = regexp.MustCompile(`Q: (\d+) ([+-]) (\d+) = \?\nA:$`)
|
||||
|
||||
func answerFromChallengePrompt(prompt string) string {
|
||||
m := challengeQuestionRegex.FindStringSubmatch(prompt)
|
||||
if len(m) != 4 {
|
||||
return "0"
|
||||
}
|
||||
left, _ := strconv.Atoi(m[1])
|
||||
right, _ := strconv.Atoi(m[3])
|
||||
if m[2] == "+" {
|
||||
return strconv.Itoa(left + right)
|
||||
}
|
||||
return strconv.Itoa(left - right)
|
||||
}
|
||||
|
||||
func TestRunCheckForModel_OffMode_PreservesDefaultBody(t *testing.T) {
|
||||
h := &captureHandler{respondText: "the answer is 42"}
|
||||
endpoint := setupFakeAnthropic(t, h)
|
||||
@ -75,6 +147,95 @@ func TestRunCheckForModel_OffMode_PreservesDefaultBody(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunCheckForModel_OpenAI_DefaultChatRequest(t *testing.T) {
|
||||
h := &openAICaptureHandler{}
|
||||
endpoint := setupFakeOpenAI(t, h)
|
||||
|
||||
res := runCheckForModel(context.Background(), MonitorProviderOpenAI, endpoint, "sk-openai", "gpt-test", nil)
|
||||
|
||||
if res.Status != MonitorStatusOperational {
|
||||
t.Fatalf("default chat request should pass challenge, got status=%s message=%q", res.Status, res.Message)
|
||||
}
|
||||
if h.lastPath != providerOpenAIPath {
|
||||
t.Fatalf("expected chat completions path %q, got %q", providerOpenAIPath, h.lastPath)
|
||||
}
|
||||
if h.lastBody["model"] != "gpt-test" {
|
||||
t.Errorf("chat body should contain model=gpt-test, got %v", h.lastBody["model"])
|
||||
}
|
||||
if _, ok := h.lastBody["messages"]; !ok {
|
||||
t.Error("chat body should contain messages")
|
||||
}
|
||||
if _, ok := h.lastBody["instructions"]; ok {
|
||||
t.Error("chat body must not contain top-level instructions")
|
||||
}
|
||||
if h.lastBody["stream"] != false {
|
||||
t.Errorf("chat body should set stream=false, got %v", h.lastBody["stream"])
|
||||
}
|
||||
if h.lastHeaders.Get("Authorization") != "Bearer sk-openai" {
|
||||
t.Errorf("expected bearer auth header, got %q", h.lastHeaders.Get("Authorization"))
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunCheckForModel_OpenAIResponses_DefaultRequest(t *testing.T) {
|
||||
h := &openAICaptureHandler{}
|
||||
endpoint := setupFakeOpenAI(t, h)
|
||||
|
||||
res := runCheckForModel(context.Background(), MonitorProviderOpenAI, endpoint, "sk-openai", "gpt-test", &CheckOptions{
|
||||
APIMode: MonitorAPIModeResponses,
|
||||
})
|
||||
|
||||
if res.Status != MonitorStatusOperational {
|
||||
t.Fatalf("default responses request should pass challenge, got status=%s message=%q", res.Status, res.Message)
|
||||
}
|
||||
if h.lastPath != providerOpenAIResponsesPath {
|
||||
t.Fatalf("expected responses path %q, got %q", providerOpenAIResponsesPath, h.lastPath)
|
||||
}
|
||||
if h.lastBody["model"] != "gpt-test" {
|
||||
t.Errorf("responses body should contain model=gpt-test, got %v", h.lastBody["model"])
|
||||
}
|
||||
instructions, _ := h.lastBody["instructions"].(string)
|
||||
if strings.TrimSpace(instructions) == "" {
|
||||
t.Error("responses body should contain non-empty instructions")
|
||||
}
|
||||
input, _ := h.lastBody["input"].(string)
|
||||
if strings.TrimSpace(input) == "" {
|
||||
t.Error("responses body should contain non-empty input")
|
||||
}
|
||||
if _, ok := h.lastBody["messages"]; ok {
|
||||
t.Error("responses body must not contain chat messages")
|
||||
}
|
||||
if h.lastBody["stream"] != false {
|
||||
t.Errorf("responses body should set stream=false, got %v", h.lastBody["stream"])
|
||||
}
|
||||
if h.lastHeaders.Get("Authorization") != "Bearer sk-openai" {
|
||||
t.Errorf("expected bearer auth header, got %q", h.lastHeaders.Get("Authorization"))
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunCheckForModel_OpenAIResponsesReplaceMissingInstructionsFailsLocally(t *testing.T) {
|
||||
h := &openAICaptureHandler{}
|
||||
endpoint := setupFakeOpenAI(t, h)
|
||||
|
||||
res := runCheckForModel(context.Background(), MonitorProviderOpenAI, endpoint, "sk-openai", "gpt-test", &CheckOptions{
|
||||
APIMode: MonitorAPIModeResponses,
|
||||
BodyOverrideMode: MonitorBodyOverrideModeReplace,
|
||||
BodyOverride: map[string]any{
|
||||
"model": "gpt-test",
|
||||
"input": "hello",
|
||||
},
|
||||
})
|
||||
|
||||
if res.Status != MonitorStatusError {
|
||||
t.Fatalf("invalid responses replace body should fail locally as error, got status=%s", res.Status)
|
||||
}
|
||||
if !strings.Contains(res.Message, "instructions and input are required") {
|
||||
t.Errorf("expected local validation message about instructions/input, got %q", res.Message)
|
||||
}
|
||||
if h.lastPath != "" {
|
||||
t.Errorf("invalid replace body should fail before HTTP request, got path %q", h.lastPath)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunCheckForModel_MergeMode_UserFieldsWinButDenyListProtects(t *testing.T) {
|
||||
h := &captureHandler{respondText: "the answer is 42"}
|
||||
endpoint := setupFakeAnthropic(t, h)
|
||||
|
||||
@ -47,6 +47,8 @@ const (
|
||||
|
||||
// providerOpenAIPath OpenAI Chat Completions 路径。
|
||||
providerOpenAIPath = "/v1/chat/completions"
|
||||
// providerOpenAIResponsesPath OpenAI Responses API 路径。
|
||||
providerOpenAIResponsesPath = "/v1/responses"
|
||||
// providerAnthropicPath Anthropic Messages 路径。
|
||||
providerAnthropicPath = "/v1/messages"
|
||||
// providerGeminiPathTemplate Gemini generateContent 路径模板(含 model 占位)。
|
||||
@ -112,6 +114,12 @@ var (
|
||||
ErrChannelMonitorInvalidProvider = infraerrors.BadRequest(
|
||||
"CHANNEL_MONITOR_INVALID_PROVIDER", "provider must be one of openai/anthropic/gemini",
|
||||
)
|
||||
ErrChannelMonitorInvalidAPIMode = infraerrors.BadRequest(
|
||||
"CHANNEL_MONITOR_INVALID_API_MODE", "api_mode must be chat_completions or responses; responses is only supported for openai",
|
||||
)
|
||||
ErrChannelMonitorInvalidRequestBody = infraerrors.BadRequest(
|
||||
"CHANNEL_MONITOR_INVALID_REQUEST_BODY", "openai replace-mode body_override must include non-empty messages for chat_completions or non-empty instructions and input for responses",
|
||||
)
|
||||
ErrChannelMonitorInvalidInterval = infraerrors.BadRequest(
|
||||
"CHANNEL_MONITOR_INVALID_INTERVAL", "interval_seconds must be in [15, 3600]",
|
||||
)
|
||||
|
||||
@ -107,7 +107,7 @@ func (s *ChannelMonitorService) Create(ctx context.Context, p ChannelMonitorCrea
|
||||
if err := validateCreateParams(p); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := validateBodyModeParams(p.BodyOverrideMode, p.BodyOverride); err != nil {
|
||||
if err := validateBodyModeForProtocol(p.Provider, p.APIMode, p.BodyOverrideMode, p.BodyOverride); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := validateExtraHeaders(p.ExtraHeaders); err != nil {
|
||||
@ -120,6 +120,7 @@ func (s *ChannelMonitorService) Create(ctx context.Context, p ChannelMonitorCrea
|
||||
m := &ChannelMonitor{
|
||||
Name: strings.TrimSpace(p.Name),
|
||||
Provider: p.Provider,
|
||||
APIMode: defaultAPIMode(p.APIMode),
|
||||
Endpoint: normalizeEndpoint(p.Endpoint),
|
||||
APIKey: encrypted, // 注意:传入 repository 时该字段为密文
|
||||
PrimaryModel: strings.TrimSpace(p.PrimaryModel),
|
||||
@ -150,6 +151,9 @@ func validateCreateParams(p ChannelMonitorCreateParams) error {
|
||||
if err := validateProvider(p.Provider); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := validateAPIMode(p.Provider, p.APIMode); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := validateInterval(p.IntervalSeconds); err != nil {
|
||||
return err
|
||||
}
|
||||
@ -298,6 +302,7 @@ func (s *ChannelMonitorService) runChecksConcurrent(ctx context.Context, m *Chan
|
||||
|
||||
// 所有模型共用同一份 CheckOptions(来自监控的快照字段)。
|
||||
opts := &CheckOptions{
|
||||
APIMode: m.APIMode,
|
||||
ExtraHeaders: m.ExtraHeaders,
|
||||
BodyOverrideMode: m.BodyOverrideMode,
|
||||
BodyOverride: m.BodyOverride,
|
||||
@ -469,6 +474,7 @@ func (s *ChannelMonitorService) decryptInPlace(m *ChannelMonitor) {
|
||||
// 行数稍超过 30:这是逐字段平铺的 dispatcher,每个 if 都是 1-3 行的"非 nil 则覆盖"模式,
|
||||
// 拆分反而会增加跳转噪音、影响可读性,故保留为单函数。
|
||||
func applyMonitorUpdate(existing *ChannelMonitor, p ChannelMonitorUpdateParams) error {
|
||||
providerChanged := false
|
||||
if p.Name != nil {
|
||||
existing.Name = strings.TrimSpace(*p.Name)
|
||||
}
|
||||
@ -477,6 +483,7 @@ func applyMonitorUpdate(existing *ChannelMonitor, p ChannelMonitorUpdateParams)
|
||||
return err
|
||||
}
|
||||
existing.Provider = *p.Provider
|
||||
providerChanged = true
|
||||
}
|
||||
if p.Endpoint != nil {
|
||||
if err := validateEndpoint(*p.Endpoint); err != nil {
|
||||
@ -502,11 +509,11 @@ func applyMonitorUpdate(existing *ChannelMonitor, p ChannelMonitorUpdateParams)
|
||||
}
|
||||
existing.IntervalSeconds = *p.IntervalSeconds
|
||||
}
|
||||
return applyMonitorAdvancedUpdate(existing, p)
|
||||
return applyMonitorAdvancedUpdate(existing, p, providerChanged)
|
||||
}
|
||||
|
||||
// applyMonitorAdvancedUpdate 处理自定义请求快照相关字段,从 applyMonitorUpdate 拆出避免过长。
|
||||
func applyMonitorAdvancedUpdate(existing *ChannelMonitor, p ChannelMonitorUpdateParams) error {
|
||||
func applyMonitorAdvancedUpdate(existing *ChannelMonitor, p ChannelMonitorUpdateParams, providerChanged bool) error {
|
||||
if p.ClearTemplate {
|
||||
existing.TemplateID = nil
|
||||
} else if p.TemplateID != nil {
|
||||
@ -519,6 +526,15 @@ func applyMonitorAdvancedUpdate(existing *ChannelMonitor, p ChannelMonitorUpdate
|
||||
}
|
||||
existing.ExtraHeaders = emptyHeadersIfNil(*p.ExtraHeaders)
|
||||
}
|
||||
newAPIMode := defaultAPIMode(existing.APIMode)
|
||||
if p.APIMode != nil {
|
||||
newAPIMode = defaultAPIMode(*p.APIMode)
|
||||
} else if existing.Provider != MonitorProviderOpenAI {
|
||||
newAPIMode = MonitorAPIModeChatCompletions
|
||||
}
|
||||
if err := validateAPIMode(existing.Provider, newAPIMode); err != nil {
|
||||
return err
|
||||
}
|
||||
// BodyOverrideMode / BodyOverride 联合校验,和模板一致。
|
||||
newMode := existing.BodyOverrideMode
|
||||
newBody := existing.BodyOverride
|
||||
@ -528,12 +544,13 @@ func applyMonitorAdvancedUpdate(existing *ChannelMonitor, p ChannelMonitorUpdate
|
||||
if p.BodyOverride != nil {
|
||||
newBody = *p.BodyOverride
|
||||
}
|
||||
if p.BodyOverrideMode != nil || p.BodyOverride != nil {
|
||||
if err := validateBodyModeParams(newMode, newBody); err != nil {
|
||||
if providerChanged || p.APIMode != nil || p.BodyOverrideMode != nil || p.BodyOverride != nil {
|
||||
if err := validateBodyModeForProtocol(existing.Provider, newAPIMode, newMode, newBody); err != nil {
|
||||
return err
|
||||
}
|
||||
existing.BodyOverrideMode = defaultBodyMode(newMode)
|
||||
existing.BodyOverride = newBody
|
||||
}
|
||||
existing.APIMode = newAPIMode
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -14,14 +14,14 @@ type ChannelMonitorRequestTemplateRepository interface {
|
||||
Update(ctx context.Context, t *ChannelMonitorRequestTemplate) error
|
||||
Delete(ctx context.Context, id int64) error
|
||||
List(ctx context.Context, params ChannelMonitorRequestTemplateListParams) ([]*ChannelMonitorRequestTemplate, error)
|
||||
// ApplyToMonitors 把模板当前的 extra_headers / body_override_mode / body_override
|
||||
// ApplyToMonitors 把模板当前的 api_mode / extra_headers / body_override_mode / body_override
|
||||
// 批量覆盖到指定 monitorIDs 的监控上(同时还要求这些监控当前 template_id = id,
|
||||
// 防止误覆盖未关联的监控)。monitorIDs 必须非空;空列表直接返回 0 不写库。
|
||||
// 返回被覆盖的监控数量。
|
||||
ApplyToMonitors(ctx context.Context, id int64, monitorIDs []int64) (int64, error)
|
||||
// CountAssociatedMonitors 统计 template_id = id 的监控数(用于 UI 展示「应用到 N 个配置」)。
|
||||
CountAssociatedMonitors(ctx context.Context, id int64) (int64, error)
|
||||
// ListAssociatedMonitors 列出所有 template_id = id 的监控简略信息(id/name/provider/enabled)
|
||||
// ListAssociatedMonitors 列出所有 template_id = id 的监控简略信息(id/name/provider/api_mode/enabled)
|
||||
// 给 apply picker UI 用,避免前端再做一次 list+filter。
|
||||
ListAssociatedMonitors(ctx context.Context, id int64) ([]*AssociatedMonitorBrief, error)
|
||||
}
|
||||
@ -31,6 +31,7 @@ type AssociatedMonitorBrief struct {
|
||||
ID int64
|
||||
Name string
|
||||
Provider string
|
||||
APIMode string
|
||||
Enabled bool
|
||||
}
|
||||
|
||||
@ -53,6 +54,15 @@ func (s *ChannelMonitorRequestTemplateService) List(ctx context.Context, params
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if params.APIMode != "" {
|
||||
if params.Provider == "" {
|
||||
if err := validateAPIMode(MonitorProviderOpenAI, params.APIMode); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else if err := validateAPIMode(params.Provider, params.APIMode); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return s.repo.List(ctx, params)
|
||||
}
|
||||
|
||||
@ -69,6 +79,7 @@ func (s *ChannelMonitorRequestTemplateService) Create(ctx context.Context, p Cha
|
||||
t := &ChannelMonitorRequestTemplate{
|
||||
Name: strings.TrimSpace(p.Name),
|
||||
Provider: p.Provider,
|
||||
APIMode: defaultAPIMode(p.APIMode),
|
||||
Description: strings.TrimSpace(p.Description),
|
||||
ExtraHeaders: emptyHeadersIfNil(p.ExtraHeaders),
|
||||
BodyOverrideMode: defaultBodyMode(p.BodyOverrideMode),
|
||||
@ -144,7 +155,10 @@ func validateTemplateCreateParams(p ChannelMonitorRequestTemplateCreateParams) e
|
||||
if err := validateProvider(p.Provider); err != nil {
|
||||
return ErrChannelMonitorTemplateInvalidProvider
|
||||
}
|
||||
if err := validateBodyModeParams(p.BodyOverrideMode, p.BodyOverride); err != nil {
|
||||
if err := validateAPIMode(p.Provider, p.APIMode); err != nil {
|
||||
return ErrChannelMonitorTemplateInvalidAPIMode
|
||||
}
|
||||
if err := validateBodyModeForProtocol(p.Provider, p.APIMode, p.BodyOverrideMode, p.BodyOverride); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := validateExtraHeaders(p.ExtraHeaders); err != nil {
|
||||
@ -165,6 +179,13 @@ func applyTemplateUpdate(existing *ChannelMonitorRequestTemplate, p ChannelMonit
|
||||
if p.Description != nil {
|
||||
existing.Description = strings.TrimSpace(*p.Description)
|
||||
}
|
||||
newAPIMode := defaultAPIMode(existing.APIMode)
|
||||
if p.APIMode != nil {
|
||||
newAPIMode = defaultAPIMode(*p.APIMode)
|
||||
}
|
||||
if err := validateAPIMode(existing.Provider, newAPIMode); err != nil {
|
||||
return ErrChannelMonitorTemplateInvalidAPIMode
|
||||
}
|
||||
if p.ExtraHeaders != nil {
|
||||
if err := validateExtraHeaders(*p.ExtraHeaders); err != nil {
|
||||
return err
|
||||
@ -180,14 +201,29 @@ func applyTemplateUpdate(existing *ChannelMonitorRequestTemplate, p ChannelMonit
|
||||
if p.BodyOverride != nil {
|
||||
newBody = *p.BodyOverride
|
||||
}
|
||||
if err := validateBodyModeParams(newMode, newBody); err != nil {
|
||||
if err := validateBodyModeForProtocol(existing.Provider, newAPIMode, newMode, newBody); err != nil {
|
||||
return err
|
||||
}
|
||||
existing.APIMode = newAPIMode
|
||||
existing.BodyOverrideMode = defaultBodyMode(newMode)
|
||||
existing.BodyOverride = newBody
|
||||
return nil
|
||||
}
|
||||
|
||||
// validateBodyModeForProtocol 校验 body_override_mode 与 provider/api_mode 的协议特定要求。
|
||||
func validateBodyModeForProtocol(provider, apiMode, mode string, body map[string]any) error {
|
||||
if err := validateBodyModeParams(mode, body); err != nil {
|
||||
return err
|
||||
}
|
||||
if defaultBodyMode(mode) != MonitorBodyOverrideModeReplace {
|
||||
return nil
|
||||
}
|
||||
if err := validateReplaceRequestBody(provider, defaultAPIMode(apiMode), body); err != nil {
|
||||
return ErrChannelMonitorInvalidRequestBody
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// validateBodyModeParams 校验 body_override_mode 合法,且 merge/replace 模式下 body_override 非空。
|
||||
func validateBodyModeParams(mode string, body map[string]any) error {
|
||||
switch mode {
|
||||
|
||||
@ -12,6 +12,7 @@ type ChannelMonitorRequestTemplate struct {
|
||||
ID int64
|
||||
Name string
|
||||
Provider string
|
||||
APIMode string
|
||||
Description string
|
||||
ExtraHeaders map[string]string
|
||||
BodyOverrideMode string
|
||||
@ -23,12 +24,14 @@ type ChannelMonitorRequestTemplate struct {
|
||||
// ChannelMonitorRequestTemplateListParams 列表过滤。
|
||||
type ChannelMonitorRequestTemplateListParams struct {
|
||||
Provider string // 空 = 全部;非空则按 provider 过滤
|
||||
APIMode string // 空 = 全部;非空则按 api_mode 过滤
|
||||
}
|
||||
|
||||
// ChannelMonitorRequestTemplateCreateParams 创建参数。
|
||||
type ChannelMonitorRequestTemplateCreateParams struct {
|
||||
Name string
|
||||
Provider string
|
||||
APIMode string
|
||||
Description string
|
||||
ExtraHeaders map[string]string
|
||||
BodyOverrideMode string
|
||||
@ -39,6 +42,7 @@ type ChannelMonitorRequestTemplateCreateParams struct {
|
||||
// 注意 Provider 不可修改:改 provider 会让已关联监控的 body 黑名单语义错乱。
|
||||
type ChannelMonitorRequestTemplateUpdateParams struct {
|
||||
Name *string
|
||||
APIMode *string
|
||||
Description *string
|
||||
ExtraHeaders *map[string]string
|
||||
BodyOverrideMode *string
|
||||
@ -53,6 +57,9 @@ var (
|
||||
ErrChannelMonitorTemplateInvalidProvider = infraerrors.BadRequest(
|
||||
"CHANNEL_MONITOR_TEMPLATE_INVALID_PROVIDER", "template provider must be one of openai/anthropic/gemini",
|
||||
)
|
||||
ErrChannelMonitorTemplateInvalidAPIMode = infraerrors.BadRequest(
|
||||
"CHANNEL_MONITOR_TEMPLATE_INVALID_API_MODE", "template api_mode must be chat_completions or responses; responses is only supported for openai",
|
||||
)
|
||||
ErrChannelMonitorTemplateMissingName = infraerrors.BadRequest(
|
||||
"CHANNEL_MONITOR_TEMPLATE_MISSING_NAME", "template name is required",
|
||||
)
|
||||
@ -71,6 +78,9 @@ var (
|
||||
ErrChannelMonitorTemplateProviderMismatch = infraerrors.BadRequest(
|
||||
"CHANNEL_MONITOR_TEMPLATE_PROVIDER_MISMATCH", "monitor provider does not match template provider",
|
||||
)
|
||||
ErrChannelMonitorTemplateAPIModeMismatch = infraerrors.BadRequest(
|
||||
"CHANNEL_MONITOR_TEMPLATE_API_MODE_MISMATCH", "monitor api_mode does not match template api_mode",
|
||||
)
|
||||
ErrChannelMonitorTemplateApplyEmpty = infraerrors.BadRequest(
|
||||
"CHANNEL_MONITOR_TEMPLATE_APPLY_EMPTY", "monitor_ids must be a non-empty array",
|
||||
)
|
||||
|
||||
@ -15,11 +15,23 @@ const (
|
||||
MonitorBodyOverrideModeReplace = "replace"
|
||||
)
|
||||
|
||||
// MonitorAPIMode 描述 OpenAI provider 的请求协议。
|
||||
//
|
||||
// - chat_completions OpenAI-compatible Chat Completions: /v1/chat/completions + messages
|
||||
// - responses OpenAI Responses API: /v1/responses + instructions/input
|
||||
//
|
||||
// 非 OpenAI provider 固定使用 chat_completions 作为占位默认值,避免为每个 provider 单独扩表。
|
||||
const (
|
||||
MonitorAPIModeChatCompletions = "chat_completions"
|
||||
MonitorAPIModeResponses = "responses"
|
||||
)
|
||||
|
||||
// ChannelMonitor 渠道监控配置(service 层模型,不直接暴露 ent 类型)。
|
||||
type ChannelMonitor struct {
|
||||
ID int64
|
||||
Name string
|
||||
Provider string
|
||||
APIMode string
|
||||
Endpoint string
|
||||
APIKey string // 解密后的明文 API Key(仅在 service 内部使用,handler 层不应直接序列化返回)
|
||||
PrimaryModel string
|
||||
@ -56,6 +68,7 @@ type ChannelMonitorListParams struct {
|
||||
type ChannelMonitorCreateParams struct {
|
||||
Name string
|
||||
Provider string
|
||||
APIMode string
|
||||
Endpoint string
|
||||
APIKey string
|
||||
PrimaryModel string
|
||||
@ -74,6 +87,7 @@ type ChannelMonitorCreateParams struct {
|
||||
type ChannelMonitorUpdateParams struct {
|
||||
Name *string
|
||||
Provider *string
|
||||
APIMode *string
|
||||
Endpoint *string
|
||||
APIKey *string // 空字符串表示不修改;非空字符串覆盖
|
||||
PrimaryModel *string
|
||||
|
||||
@ -18,6 +18,23 @@ func validateProvider(p string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// validateAPIMode 校验 provider 与 api_mode 的组合。
|
||||
// responses 只对 OpenAI 有意义;其它 provider 使用 chat_completions 作为默认占位。
|
||||
func validateAPIMode(provider, apiMode string) error {
|
||||
apiMode = defaultAPIMode(apiMode)
|
||||
switch apiMode {
|
||||
case MonitorAPIModeChatCompletions:
|
||||
return nil
|
||||
case MonitorAPIModeResponses:
|
||||
if provider == "" || provider == MonitorProviderOpenAI {
|
||||
return nil
|
||||
}
|
||||
return ErrChannelMonitorInvalidAPIMode
|
||||
default:
|
||||
return ErrChannelMonitorInvalidAPIMode
|
||||
}
|
||||
}
|
||||
|
||||
// validateInterval 校验 interval_seconds 范围。
|
||||
func validateInterval(sec int) error {
|
||||
if sec < monitorMinIntervalSeconds || sec > monitorMaxIntervalSeconds {
|
||||
@ -97,3 +114,11 @@ func normalizeModels(in []string) []string {
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// defaultAPIMode 空串归一为 chat_completions,保证历史数据与旧客户端兼容。
|
||||
func defaultAPIMode(apiMode string) string {
|
||||
if strings.TrimSpace(apiMode) == "" {
|
||||
return MonitorAPIModeChatCompletions
|
||||
}
|
||||
return strings.TrimSpace(apiMode)
|
||||
}
|
||||
|
||||
40
backend/migrations/138_channel_monitor_openai_api_mode.sql
Normal file
40
backend/migrations/138_channel_monitor_openai_api_mode.sql
Normal file
@ -0,0 +1,40 @@
|
||||
-- Migration: 137_channel_monitor_openai_api_mode
|
||||
-- 为渠道监控和请求模板增加 OpenAI 协议模式:
|
||||
-- chat_completions -> /v1/chat/completions + messages
|
||||
-- responses -> /v1/responses + instructions/input
|
||||
-- 历史数据默认保持 chat_completions,避免改变现有监控行为。
|
||||
|
||||
ALTER TABLE channel_monitors
|
||||
ADD COLUMN IF NOT EXISTS api_mode VARCHAR(32) NOT NULL DEFAULT 'chat_completions';
|
||||
|
||||
ALTER TABLE channel_monitor_request_templates
|
||||
ADD COLUMN IF NOT EXISTS api_mode VARCHAR(32) NOT NULL DEFAULT 'chat_completions';
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM information_schema.table_constraints
|
||||
WHERE constraint_name = 'channel_monitors_api_mode_check'
|
||||
AND table_name = 'channel_monitors'
|
||||
) THEN
|
||||
ALTER TABLE channel_monitors
|
||||
ADD CONSTRAINT channel_monitors_api_mode_check
|
||||
CHECK (api_mode IN ('chat_completions', 'responses'));
|
||||
END IF;
|
||||
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM information_schema.table_constraints
|
||||
WHERE constraint_name = 'channel_monitor_request_templates_api_mode_check'
|
||||
AND table_name = 'channel_monitor_request_templates'
|
||||
) THEN
|
||||
ALTER TABLE channel_monitor_request_templates
|
||||
ADD CONSTRAINT channel_monitor_request_templates_api_mode_check
|
||||
CHECK (api_mode IN ('chat_completions', 'responses'));
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_channel_monitors_provider_api_mode
|
||||
ON channel_monitors (provider, api_mode);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_channel_monitor_templates_provider_api_mode
|
||||
ON channel_monitor_request_templates (provider, api_mode);
|
||||
47
backend/migrations/139_seed_openai_monitor_templates.sql
Normal file
47
backend/migrations/139_seed_openai_monitor_templates.sql
Normal file
@ -0,0 +1,47 @@
|
||||
-- Migration: 138_seed_openai_monitor_templates
|
||||
-- 内置 OpenAI 渠道监控模板。重点是把协议模式显式化:
|
||||
-- 1) OpenAI-compatible 使用 Chat Completions payload
|
||||
-- 2) Responses / 本站自检 使用 Responses payload,默认 body 由后端 adapter 填入 instructions + input
|
||||
-- 所有模板都可直接选择;ON CONFLICT 保证重复部署不覆盖用户编辑。
|
||||
|
||||
INSERT INTO channel_monitor_request_templates (
|
||||
name, provider, api_mode, description, extra_headers, body_override_mode, body_override
|
||||
)
|
||||
VALUES
|
||||
(
|
||||
'OpenAI Compatible 默认检测',
|
||||
'openai',
|
||||
'chat_completions',
|
||||
'适用于大多数 OpenAI-compatible 上游:POST /v1/chat/completions,后端自动生成 messages 数学 challenge。',
|
||||
'{}'::jsonb,
|
||||
'off',
|
||||
NULL
|
||||
),
|
||||
(
|
||||
'OpenAI Compatible 低 token 检测',
|
||||
'openai',
|
||||
'chat_completions',
|
||||
'仍走 /v1/chat/completions,仅把 max_tokens 调低;model/messages/stream 由后端保护,避免误伤 challenge。',
|
||||
'{}'::jsonb,
|
||||
'merge',
|
||||
'{"max_tokens": 20}'::jsonb
|
||||
),
|
||||
(
|
||||
'OpenAI Responses / 本站自检',
|
||||
'openai',
|
||||
'responses',
|
||||
'适用于本站或原生 Responses API:POST /v1/responses,默认 payload 自动带 instructions 与 input,避免 Instructions are required。',
|
||||
'{}'::jsonb,
|
||||
'off',
|
||||
NULL
|
||||
),
|
||||
(
|
||||
'OpenAI Responses 低 token 检测',
|
||||
'openai',
|
||||
'responses',
|
||||
'仍走 /v1/responses,仅把 max_output_tokens 调低;instructions/input/model/stream 由后端保护。',
|
||||
'{}'::jsonb,
|
||||
'merge',
|
||||
'{"max_output_tokens": 20}'::jsonb
|
||||
)
|
||||
ON CONFLICT (provider, name) DO NOTHING;
|
||||
@ -8,11 +8,13 @@ import { apiClient } from '../client'
|
||||
export type Provider = 'openai' | 'anthropic' | 'gemini'
|
||||
export type MonitorStatus = 'operational' | 'degraded' | 'failed' | 'error'
|
||||
export type BodyOverrideMode = 'off' | 'merge' | 'replace'
|
||||
export type APIMode = 'chat_completions' | 'responses'
|
||||
|
||||
export interface ChannelMonitor {
|
||||
id: number
|
||||
name: string
|
||||
provider: Provider
|
||||
api_mode: APIMode
|
||||
endpoint: string
|
||||
api_key_masked: string
|
||||
/**
|
||||
@ -70,6 +72,7 @@ export interface ListResponse {
|
||||
export interface CreateParams {
|
||||
name: string
|
||||
provider: Provider
|
||||
api_mode?: APIMode
|
||||
endpoint: string
|
||||
api_key: string
|
||||
primary_model: string
|
||||
|
||||
@ -6,12 +6,13 @@
|
||||
*/
|
||||
|
||||
import { apiClient } from '../client'
|
||||
import type { BodyOverrideMode, Provider } from './channelMonitor'
|
||||
import type { APIMode, BodyOverrideMode, Provider } from './channelMonitor'
|
||||
|
||||
export interface ChannelMonitorTemplate {
|
||||
id: number
|
||||
name: string
|
||||
provider: Provider
|
||||
api_mode: APIMode
|
||||
description: string
|
||||
extra_headers: Record<string, string>
|
||||
body_override_mode: BodyOverrideMode
|
||||
@ -24,6 +25,7 @@ export interface ChannelMonitorTemplate {
|
||||
|
||||
export interface ListParams {
|
||||
provider?: Provider
|
||||
api_mode?: APIMode
|
||||
}
|
||||
|
||||
export interface ListResponse {
|
||||
@ -33,6 +35,7 @@ export interface ListResponse {
|
||||
export interface CreateParams {
|
||||
name: string
|
||||
provider: Provider
|
||||
api_mode?: APIMode
|
||||
description?: string
|
||||
extra_headers?: Record<string, string>
|
||||
body_override_mode?: BodyOverrideMode
|
||||
@ -41,6 +44,7 @@ export interface CreateParams {
|
||||
|
||||
export interface UpdateParams {
|
||||
name?: string
|
||||
api_mode?: APIMode
|
||||
description?: string
|
||||
extra_headers?: Record<string, string>
|
||||
body_override_mode?: BodyOverrideMode
|
||||
@ -55,6 +59,7 @@ export interface AssociatedMonitorBrief {
|
||||
id: number
|
||||
name: string
|
||||
provider: Provider
|
||||
api_mode: APIMode
|
||||
enabled: boolean
|
||||
}
|
||||
|
||||
|
||||
@ -106,9 +106,15 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import type { BodyOverrideMode } from '@/api/admin/channelMonitor'
|
||||
import type { APIMode, BodyOverrideMode, Provider } from '@/api/admin/channelMonitor'
|
||||
import {
|
||||
API_MODE_RESPONSES,
|
||||
PROVIDER_OPENAI,
|
||||
} from '@/constants/channelMonitor'
|
||||
|
||||
const props = defineProps<{
|
||||
provider?: Provider
|
||||
apiMode?: APIMode
|
||||
extraHeaders: Record<string, string>
|
||||
bodyOverrideMode: BodyOverrideMode
|
||||
bodyOverride: Record<string, unknown> | null
|
||||
@ -293,6 +299,18 @@ const bodyModeHint = computed(() => {
|
||||
})
|
||||
|
||||
const bodyPlaceholder = computed(() => {
|
||||
if (props.provider === PROVIDER_OPENAI && props.apiMode === API_MODE_RESPONSES) {
|
||||
if (props.bodyOverrideMode === 'merge') {
|
||||
return '{\n "max_output_tokens": 20\n}'
|
||||
}
|
||||
return '{\n "model": "gpt-4o-mini",\n "instructions": "You are a health check endpoint. Reply briefly.",\n "input": "Reply with exactly: ok",\n "max_output_tokens": 20,\n "stream": false\n}'
|
||||
}
|
||||
if (props.provider === PROVIDER_OPENAI) {
|
||||
if (props.bodyOverrideMode === 'merge') {
|
||||
return '{\n "max_tokens": 20\n}'
|
||||
}
|
||||
return '{\n "model": "gpt-4o-mini",\n "messages": [{"role":"user","content":"Reply with exactly: ok"}],\n "max_tokens": 20,\n "stream": false\n}'
|
||||
}
|
||||
if (props.bodyOverrideMode === 'merge') {
|
||||
return '{\n "system": "You are Claude Code..."\n}'
|
||||
}
|
||||
|
||||
@ -29,6 +29,24 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="form.provider === PROVIDER_OPENAI" class="rounded-lg border border-blue-100 bg-blue-50/50 p-3 dark:border-blue-500/20 dark:bg-blue-500/10">
|
||||
<label class="input-label">{{ t('admin.channelMonitor.form.apiMode') }}</label>
|
||||
<div class="grid gap-3 sm:grid-cols-2">
|
||||
<button
|
||||
v-for="opt in apiModeOptions"
|
||||
:key="opt.value"
|
||||
type="button"
|
||||
:aria-pressed="form.api_mode === opt.value"
|
||||
class="rounded-lg border-2 px-3 py-2 text-left transition-colors"
|
||||
:class="apiModeButtonClass(opt.value)"
|
||||
@click="form.api_mode = opt.value"
|
||||
>
|
||||
<span class="block text-sm font-semibold">{{ opt.label }}</span>
|
||||
<span class="mt-0.5 block text-xs opacity-80">{{ opt.hint }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="input-label">{{ t('admin.channelMonitor.form.endpoint') }} <span class="text-red-500">*</span></label>
|
||||
<div class="flex gap-2">
|
||||
@ -115,6 +133,8 @@
|
||||
</div>
|
||||
|
||||
<MonitorAdvancedRequestConfig
|
||||
:provider="form.provider"
|
||||
:api-mode="form.api_mode"
|
||||
:extra-headers="form.extra_headers"
|
||||
:body-override-mode="form.body_override_mode"
|
||||
:body-override="form.body_override"
|
||||
@ -168,6 +188,7 @@ import type {
|
||||
BodyOverrideMode,
|
||||
ChannelMonitor,
|
||||
CreateParams,
|
||||
APIMode,
|
||||
Provider,
|
||||
UpdateParams,
|
||||
} from '@/api/admin/channelMonitor'
|
||||
@ -186,6 +207,8 @@ import {
|
||||
PROVIDER_OPENAI,
|
||||
PROVIDER_ANTHROPIC,
|
||||
PROVIDER_GEMINI,
|
||||
API_MODE_CHAT_COMPLETIONS,
|
||||
API_MODE_RESPONSES,
|
||||
DEFAULT_INTERVAL_SECONDS,
|
||||
} from '@/constants/channelMonitor'
|
||||
|
||||
@ -224,6 +247,7 @@ const userGroupRates = ref<Record<number, number>>({})
|
||||
interface MonitorForm {
|
||||
name: string
|
||||
provider: Provider
|
||||
api_mode: APIMode
|
||||
endpoint: string
|
||||
api_key: string
|
||||
primary_model: string
|
||||
@ -241,6 +265,7 @@ interface MonitorForm {
|
||||
const form = reactive<MonitorForm>({
|
||||
name: '',
|
||||
provider: PROVIDER_ANTHROPIC,
|
||||
api_mode: API_MODE_CHAT_COMPLETIONS,
|
||||
endpoint: '',
|
||||
api_key: '',
|
||||
primary_model: '',
|
||||
@ -254,15 +279,21 @@ const form = reactive<MonitorForm>({
|
||||
body_override: null,
|
||||
})
|
||||
|
||||
// 可用模板列表(进入 dialog 时一次性拉取 cache;按 provider 过滤)。
|
||||
let suppressFormWatchers = false
|
||||
|
||||
// 可用模板列表(进入 dialog 时一次性拉取 cache;按 provider / api mode 过滤)。
|
||||
const templatesCache = ref<ChannelMonitorTemplate[]>([])
|
||||
const templatesLoading = ref(false)
|
||||
|
||||
const templateOptions = computed(() => {
|
||||
const items = templatesCache.value.filter((t) => t.provider === form.provider)
|
||||
const items = templatesCache.value.filter((t) => {
|
||||
if (t.provider !== form.provider) return false
|
||||
if (form.provider !== PROVIDER_OPENAI) return true
|
||||
return normalizeAPIMode(t.api_mode) === form.api_mode
|
||||
})
|
||||
return [
|
||||
{ value: '', label: t('admin.channelMonitor.templateField.none') },
|
||||
...items.map((t) => ({ value: String(t.id), label: t.name })),
|
||||
...items.map((t) => ({ value: String(t.id), label: templateOptionLabel(t) })),
|
||||
]
|
||||
})
|
||||
|
||||
@ -294,13 +325,57 @@ const templateSelectValue = computed<string>({
|
||||
// 应用模板 = 拷贝快照
|
||||
const tpl = templatesCache.value.find((t) => t.id === id)
|
||||
if (tpl) {
|
||||
suppressFormWatchers = true
|
||||
form.api_mode = normalizeAPIMode(tpl.api_mode)
|
||||
form.template_id = id
|
||||
form.extra_headers = { ...(tpl.extra_headers || {}) }
|
||||
form.body_override_mode = tpl.body_override_mode
|
||||
form.body_override = tpl.body_override ? { ...tpl.body_override } : null
|
||||
suppressFormWatchers = false
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
const apiModeOptions = computed<{ value: APIMode; label: string; hint: string }[]>(() => [
|
||||
{
|
||||
value: API_MODE_CHAT_COMPLETIONS,
|
||||
label: t('admin.channelMonitor.form.apiModeChatCompletions'),
|
||||
hint: t('admin.channelMonitor.form.apiModeChatCompletionsHint'),
|
||||
},
|
||||
{
|
||||
value: API_MODE_RESPONSES,
|
||||
label: t('admin.channelMonitor.form.apiModeResponses'),
|
||||
hint: t('admin.channelMonitor.form.apiModeResponsesHint'),
|
||||
},
|
||||
])
|
||||
|
||||
function normalizeAPIMode(mode: APIMode | undefined | null): APIMode {
|
||||
return mode === API_MODE_RESPONSES ? API_MODE_RESPONSES : API_MODE_CHAT_COMPLETIONS
|
||||
}
|
||||
|
||||
function apiModeButtonClass(mode: APIMode): string {
|
||||
const active = form.api_mode === mode
|
||||
if (active) {
|
||||
return 'border-primary-500 bg-white text-primary-700 shadow-sm dark:border-primary-400 dark:bg-primary-500/15 dark:text-primary-300'
|
||||
}
|
||||
return 'border-blue-100 bg-white/70 text-gray-600 hover:border-primary-300 dark:border-dark-700 dark:bg-dark-800 dark:text-gray-400'
|
||||
}
|
||||
|
||||
function templateOptionLabel(tpl: ChannelMonitorTemplate): string {
|
||||
if (tpl.provider !== PROVIDER_OPENAI) return tpl.name
|
||||
const labelKey = normalizeAPIMode(tpl.api_mode) === API_MODE_RESPONSES
|
||||
? 'admin.channelMonitor.form.apiModeResponses'
|
||||
: 'admin.channelMonitor.form.apiModeChatCompletions'
|
||||
return `${tpl.name} · ${t(labelKey)}`
|
||||
}
|
||||
|
||||
function clearRequestSnapshot() {
|
||||
form.template_id = null
|
||||
form.extra_headers = {}
|
||||
form.body_override_mode = 'off'
|
||||
form.body_override = null
|
||||
}
|
||||
|
||||
interface ProviderOption {
|
||||
value: Provider
|
||||
label: string
|
||||
@ -318,13 +393,26 @@ const providerOptions = computed<ProviderOption[]>(() => [
|
||||
// picks a new key.
|
||||
// 同时清空 template_id(模板有 provider 归属,跨平台不通用)。
|
||||
watch(() => form.provider, () => {
|
||||
if (suppressFormWatchers) return
|
||||
form.api_key = ''
|
||||
form.template_id = null
|
||||
})
|
||||
if (form.provider !== PROVIDER_OPENAI) {
|
||||
form.api_mode = API_MODE_CHAT_COMPLETIONS
|
||||
}
|
||||
clearRequestSnapshot()
|
||||
}, { flush: 'sync' })
|
||||
|
||||
watch(() => form.api_mode, () => {
|
||||
if (suppressFormWatchers) return
|
||||
if (form.provider === PROVIDER_OPENAI) {
|
||||
clearRequestSnapshot()
|
||||
}
|
||||
}, { flush: 'sync' })
|
||||
|
||||
function resetForm() {
|
||||
suppressFormWatchers = true
|
||||
form.name = ''
|
||||
form.provider = PROVIDER_ANTHROPIC
|
||||
form.api_mode = API_MODE_CHAT_COMPLETIONS
|
||||
form.endpoint = ''
|
||||
form.api_key = ''
|
||||
form.primary_model = ''
|
||||
@ -336,11 +424,14 @@ function resetForm() {
|
||||
form.extra_headers = {}
|
||||
form.body_override_mode = 'off'
|
||||
form.body_override = null
|
||||
suppressFormWatchers = false
|
||||
}
|
||||
|
||||
function loadFromMonitor(m: ChannelMonitor) {
|
||||
suppressFormWatchers = true
|
||||
form.name = m.name
|
||||
form.provider = m.provider
|
||||
form.api_mode = normalizeAPIMode(m.api_mode)
|
||||
form.endpoint = m.endpoint
|
||||
form.api_key = ''
|
||||
form.primary_model = m.primary_model
|
||||
@ -352,6 +443,7 @@ function loadFromMonitor(m: ChannelMonitor) {
|
||||
form.extra_headers = { ...(m.extra_headers || {}) }
|
||||
form.body_override_mode = m.body_override_mode || 'off'
|
||||
form.body_override = m.body_override ? { ...m.body_override } : null
|
||||
suppressFormWatchers = false
|
||||
}
|
||||
|
||||
// Re-sync form whenever the dialog is opened or the target monitor changes.
|
||||
@ -404,6 +496,7 @@ function buildPayload(): CreateParams {
|
||||
return {
|
||||
name: form.name.trim(),
|
||||
provider: form.provider,
|
||||
api_mode: form.provider === PROVIDER_OPENAI ? form.api_mode : API_MODE_CHAT_COMPLETIONS,
|
||||
endpoint: form.endpoint.trim(),
|
||||
api_key: form.api_key.trim(),
|
||||
primary_model: form.primary_model.trim(),
|
||||
|
||||
@ -56,6 +56,7 @@
|
||||
/>
|
||||
<span class="font-medium text-gray-900 dark:text-white">{{ m.name }}</span>
|
||||
<span class="text-xs text-gray-400">{{ m.provider }}</span>
|
||||
<span v-if="m.provider === 'openai'" class="text-xs text-gray-400">{{ m.api_mode }}</span>
|
||||
<span
|
||||
v-if="!m.enabled"
|
||||
class="ml-auto rounded bg-gray-100 px-1.5 py-0.5 text-xs text-gray-500 dark:bg-dark-700 dark:text-gray-400"
|
||||
|
||||
@ -65,6 +65,13 @@
|
||||
>
|
||||
{{ modeLabel(tpl.body_override_mode) }}
|
||||
</span>
|
||||
<span
|
||||
v-if="tpl.provider === PROVIDER_OPENAI"
|
||||
class="inline-flex items-center rounded-md px-1.5 py-0.5 text-xs"
|
||||
:class="apiModeBadgeClass(tpl.api_mode)"
|
||||
>
|
||||
{{ apiModeLabel(tpl.api_mode) }}
|
||||
</span>
|
||||
<span
|
||||
v-if="tpl.associated_monitors > 0"
|
||||
class="text-xs text-gray-500 dark:text-gray-400"
|
||||
@ -137,6 +144,23 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="form.provider === PROVIDER_OPENAI" class="rounded-lg border border-blue-100 bg-blue-50/50 p-3 dark:border-blue-500/20 dark:bg-blue-500/10">
|
||||
<label class="input-label">{{ t('admin.channelMonitor.form.apiMode') }}</label>
|
||||
<div class="grid gap-3 sm:grid-cols-2">
|
||||
<button
|
||||
v-for="opt in apiModeOptions"
|
||||
:key="opt.value"
|
||||
type="button"
|
||||
class="rounded-lg border-2 px-3 py-2 text-left transition-colors"
|
||||
:class="apiModeButtonClass(opt.value)"
|
||||
@click="form.api_mode = opt.value"
|
||||
>
|
||||
<span class="block text-sm font-semibold">{{ opt.label }}</span>
|
||||
<span class="mt-0.5 block text-xs opacity-80">{{ opt.hint }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="input-label">
|
||||
{{ t('admin.channelMonitor.template.form.description') }}
|
||||
@ -150,6 +174,8 @@
|
||||
</div>
|
||||
|
||||
<MonitorAdvancedRequestConfig
|
||||
:provider="form.provider"
|
||||
:api-mode="form.api_mode"
|
||||
:extra-headers="form.extra_headers"
|
||||
:body-override-mode="form.body_override_mode"
|
||||
:body-override="form.body_override"
|
||||
@ -207,6 +233,7 @@ import { useAppStore } from '@/stores/app'
|
||||
import { extractApiErrorMessage } from '@/utils/apiError'
|
||||
import { adminAPI } from '@/api/admin'
|
||||
import type {
|
||||
APIMode,
|
||||
BodyOverrideMode,
|
||||
Provider,
|
||||
} from '@/api/admin/channelMonitor'
|
||||
@ -221,6 +248,8 @@ import {
|
||||
PROVIDER_ANTHROPIC,
|
||||
PROVIDER_OPENAI,
|
||||
PROVIDER_GEMINI,
|
||||
API_MODE_CHAT_COMPLETIONS,
|
||||
API_MODE_RESPONSES,
|
||||
} from '@/constants/channelMonitor'
|
||||
|
||||
const props = defineProps<{ show: boolean }>()
|
||||
@ -263,6 +292,7 @@ interface TemplateForm {
|
||||
id: number | null
|
||||
name: string
|
||||
provider: Provider
|
||||
api_mode: APIMode
|
||||
description: string
|
||||
extra_headers: Record<string, string>
|
||||
body_override_mode: BodyOverrideMode
|
||||
@ -278,6 +308,7 @@ function emptyForm(provider: Provider): TemplateForm {
|
||||
id: null,
|
||||
name: '',
|
||||
provider,
|
||||
api_mode: API_MODE_CHAT_COMPLETIONS,
|
||||
description: '',
|
||||
extra_headers: {},
|
||||
body_override_mode: 'off',
|
||||
@ -289,6 +320,7 @@ function loadForm(tpl: ChannelMonitorTemplate) {
|
||||
form.id = tpl.id
|
||||
form.name = tpl.name
|
||||
form.provider = tpl.provider
|
||||
form.api_mode = normalizeAPIMode(tpl.api_mode)
|
||||
form.description = tpl.description
|
||||
form.extra_headers = { ...(tpl.extra_headers || {}) }
|
||||
form.body_override_mode = tpl.body_override_mode
|
||||
@ -346,6 +378,7 @@ async function handleSubmit() {
|
||||
await adminAPI.channelMonitorTemplate.create({
|
||||
name: form.name.trim(),
|
||||
provider: form.provider,
|
||||
api_mode: form.provider === PROVIDER_OPENAI ? form.api_mode : API_MODE_CHAT_COMPLETIONS,
|
||||
description: form.description.trim(),
|
||||
extra_headers: form.extra_headers,
|
||||
body_override_mode: form.body_override_mode,
|
||||
@ -355,6 +388,7 @@ async function handleSubmit() {
|
||||
} else if (typeof editing.value === 'number') {
|
||||
await adminAPI.channelMonitorTemplate.update(editing.value, {
|
||||
name: form.name.trim(),
|
||||
api_mode: form.provider === PROVIDER_OPENAI ? form.api_mode : API_MODE_CHAT_COMPLETIONS,
|
||||
description: form.description.trim(),
|
||||
extra_headers: form.extra_headers,
|
||||
body_override_mode: form.body_override_mode,
|
||||
@ -444,4 +478,48 @@ function modeBadgeClass(mode: BodyOverrideMode): string {
|
||||
function modeLabel(mode: BodyOverrideMode): string {
|
||||
return t(`admin.channelMonitor.advanced.bodyMode${mode.charAt(0).toUpperCase()}${mode.slice(1)}`)
|
||||
}
|
||||
|
||||
const apiModeOptions = computed<{ value: APIMode; label: string; hint: string }[]>(() => [
|
||||
{
|
||||
value: API_MODE_CHAT_COMPLETIONS,
|
||||
label: t('admin.channelMonitor.form.apiModeChatCompletions'),
|
||||
hint: t('admin.channelMonitor.form.apiModeChatCompletionsHint'),
|
||||
},
|
||||
{
|
||||
value: API_MODE_RESPONSES,
|
||||
label: t('admin.channelMonitor.form.apiModeResponses'),
|
||||
hint: t('admin.channelMonitor.form.apiModeResponsesHint'),
|
||||
},
|
||||
])
|
||||
|
||||
watch(() => form.provider, (provider) => {
|
||||
if (provider !== PROVIDER_OPENAI) {
|
||||
form.api_mode = API_MODE_CHAT_COMPLETIONS
|
||||
}
|
||||
})
|
||||
|
||||
function normalizeAPIMode(mode: APIMode | undefined | null): APIMode {
|
||||
return mode === API_MODE_RESPONSES ? API_MODE_RESPONSES : API_MODE_CHAT_COMPLETIONS
|
||||
}
|
||||
|
||||
function apiModeButtonClass(mode: APIMode): string {
|
||||
const active = form.api_mode === mode
|
||||
if (active) {
|
||||
return 'border-primary-500 bg-white text-primary-700 shadow-sm dark:border-primary-400 dark:bg-primary-500/15 dark:text-primary-300'
|
||||
}
|
||||
return 'border-blue-100 bg-white/70 text-gray-600 hover:border-primary-300 dark:border-dark-700 dark:bg-dark-800 dark:text-gray-400'
|
||||
}
|
||||
|
||||
function apiModeLabel(mode: APIMode): string {
|
||||
return normalizeAPIMode(mode) === API_MODE_RESPONSES
|
||||
? t('admin.channelMonitor.form.apiModeResponses')
|
||||
: t('admin.channelMonitor.form.apiModeChatCompletions')
|
||||
}
|
||||
|
||||
function apiModeBadgeClass(mode: APIMode): string {
|
||||
if (normalizeAPIMode(mode) === API_MODE_RESPONSES) {
|
||||
return 'bg-blue-100 text-blue-700 dark:bg-blue-500/15 dark:text-blue-300'
|
||||
}
|
||||
return 'bg-emerald-100 text-emerald-700 dark:bg-emerald-500/15 dark:text-emerald-300'
|
||||
}
|
||||
</script>
|
||||
|
||||
@ -7,18 +7,26 @@
|
||||
* `useChannelMonitorFormat`.
|
||||
*/
|
||||
|
||||
import type { Provider, MonitorStatus } from '@/api/admin/channelMonitor'
|
||||
import type { APIMode, Provider, MonitorStatus } from '@/api/admin/channelMonitor'
|
||||
|
||||
export const PROVIDER_OPENAI: Provider = 'openai'
|
||||
export const PROVIDER_ANTHROPIC: Provider = 'anthropic'
|
||||
export const PROVIDER_GEMINI: Provider = 'gemini'
|
||||
|
||||
export const API_MODE_CHAT_COMPLETIONS: APIMode = 'chat_completions'
|
||||
export const API_MODE_RESPONSES: APIMode = 'responses'
|
||||
|
||||
export const PROVIDERS: readonly Provider[] = [
|
||||
PROVIDER_OPENAI,
|
||||
PROVIDER_ANTHROPIC,
|
||||
PROVIDER_GEMINI,
|
||||
]
|
||||
|
||||
export const API_MODES: readonly APIMode[] = [
|
||||
API_MODE_CHAT_COMPLETIONS,
|
||||
API_MODE_RESPONSES,
|
||||
]
|
||||
|
||||
export const STATUS_OPERATIONAL: MonitorStatus = 'operational'
|
||||
export const STATUS_DEGRADED: MonitorStatus = 'degraded'
|
||||
export const STATUS_FAILED: MonitorStatus = 'failed'
|
||||
|
||||
@ -2627,6 +2627,11 @@ export default {
|
||||
name: 'Name',
|
||||
namePlaceholder: 'Enter monitor name',
|
||||
provider: 'Platform',
|
||||
apiMode: 'OpenAI protocol',
|
||||
apiModeChatCompletions: 'OpenAI Compatible',
|
||||
apiModeChatCompletionsHint: 'Use /v1/chat/completions with messages; works for most compatible providers.',
|
||||
apiModeResponses: 'Responses API',
|
||||
apiModeResponsesHint: 'Use /v1/responses with default instructions + input; best for self-check/Codex paths.',
|
||||
endpoint: 'Endpoint',
|
||||
endpointPlaceholder: 'https://api.example.com',
|
||||
useCurrentDomain: 'Use current service',
|
||||
|
||||
@ -2704,6 +2704,11 @@ export default {
|
||||
name: '名称',
|
||||
namePlaceholder: '输入监控名称',
|
||||
provider: '平台',
|
||||
apiMode: 'OpenAI 协议',
|
||||
apiModeChatCompletions: 'OpenAI Compatible',
|
||||
apiModeChatCompletionsHint: '使用 /v1/chat/completions,发送 messages;适合大多数兼容站。',
|
||||
apiModeResponses: 'Responses API',
|
||||
apiModeResponsesHint: '使用 /v1/responses,默认带 instructions + input;适合本站自检/Codex。',
|
||||
endpoint: '上游地址',
|
||||
endpointPlaceholder: 'https://api.example.com',
|
||||
useCurrentDomain: '使用当前服务',
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user