Repository navigation
支持gemini风格api的系统提示词传递 #1625
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
支持gemini风格api的系统提示词传递 #1625
Changes from 4 commits
0b8ca74
fc213af
9a8b3eb
2290acc
b0b6ab2
f44f682
8a01f09
5d33ec8
3473329
68d9751
813cb9c
c948f85
24bc24a
4e0c911
a8a0da5
e603186
bea8c57
2842ae5
37cc5d2
aacdc39
1d78e8e
451c00a
74d58a0
dca4758
3fe80db
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -59,8 +59,29 @@ func geminiRelayHandler(c *gin.Context, info *relaycommon.RelayInfo) *types.NewA | |
| return err | ||
| } | ||
|
|
||
| func Relay(c *gin.Context, relayFormat types.RelayFormat) { | ||
| func relayToChannel(c *gin.Context, relayInfo *relaycommon.RelayInfo, channel *model.Channel) *types.NewAPIError { | ||
| requestBody, _ := common.GetRequestBody(c) | ||
| c.Request.Body = io.NopCloser(bytes.NewBuffer(requestBody)) | ||
|
|
||
| var newAPIError *types.NewAPIError | ||
| switch relayInfo.RelayFormat { | ||
| case types.RelayFormatOpenAIRealtime: | ||
| newAPIError = relay.WssHelper(c, relayInfo) | ||
| case types.RelayFormatClaude: | ||
| newAPIError = relay.ClaudeHelper(c, relayInfo) | ||
| case types.RelayFormatGemini: | ||
| newAPIError = geminiRelayHandler(c, relayInfo) | ||
| default: | ||
| newAPIError = relayHandler(c, relayInfo) | ||
| } | ||
|
|
||
| if newAPIError != nil { | ||
| processChannelError(c, *types.NewChannelError(channel.Id, channel.Type, channel.Name, channel.ChannelInfo.IsMultiKey, common.GetContextKeyString(c, constant.ContextKeyChannelKey), channel.GetAutoBan()), newAPIError) | ||
| } | ||
| return newAPIError | ||
| } | ||
|
|
||
| func Relay(c *gin.Context, relayFormat types.RelayFormat) { | ||
| requestId := c.GetString(common.RequestIdKey) | ||
| group := common.GetContextKeyString(c, constant.ContextKeyUsingGroup) | ||
| originalModel := common.GetContextKeyString(c, constant.ContextKeyOriginalModel) | ||
|
|
@@ -136,20 +157,18 @@ func Relay(c *gin.Context, relayFormat types.RelayFormat) { | |
| return | ||
| } | ||
|
|
||
| // common.SetContextKey(c, constant.ContextKeyTokenCountMeta, meta) | ||
|
|
||
| preConsumedQuota, newAPIError := service.PreConsumeQuota(c, priceData.ShouldPreConsumedQuota, relayInfo) | ||
| if newAPIError != nil { | ||
| return | ||
| } | ||
|
|
||
| defer func() { | ||
| // Only return quota if downstream failed and quota was actually pre-consumed | ||
| if newAPIError != nil && preConsumedQuota != 0 { | ||
| service.ReturnPreConsumedQuota(c, relayInfo, preConsumedQuota) | ||
| } | ||
| }() | ||
|
|
||
| // Main retry loop for selecting channels | ||
| for i := 0; i <= common.RetryTimes; i++ { | ||
| channel, err := getChannel(c, group, originalModel, i) | ||
| if err != nil { | ||
|
|
@@ -159,28 +178,59 @@ func Relay(c *gin.Context, relayFormat types.RelayFormat) { | |
| } | ||
|
|
||
| addUsedChannel(c, channel.Id) | ||
| requestBody, _ := common.GetRequestBody(c) | ||
| c.Request.Body = io.NopCloser(bytes.NewBuffer(requestBody)) | ||
|
|
||
| switch relayFormat { | ||
| case types.RelayFormatOpenAIRealtime: | ||
| newAPIError = relay.WssHelper(c, relayInfo) | ||
| case types.RelayFormatClaude: | ||
| newAPIError = relay.ClaudeHelper(c, relayInfo) | ||
| case types.RelayFormatGemini: | ||
| newAPIError = geminiRelayHandler(c, relayInfo) | ||
| default: | ||
| newAPIError = relayHandler(c, relayInfo) | ||
| } | ||
|
|
||
| if newAPIError == nil { | ||
| return | ||
| // Inner loop for retrying keys within a multi-key channel | ||
| if channel.ChannelInfo.IsMultiKey { | ||
| key, keyIdx, keyErr := channel.GetNextEnabledKey() | ||
| if keyErr != nil { | ||
| newAPIError = keyErr | ||
| break // No keys available, break to outer loop to switch channel | ||
| } | ||
|
|
||
|
coderabbitai[bot] marked this conversation as resolved.
Resolved
|
||
| triedKeys := make(map[int]bool) // Track tried keys for this channel | ||
| for j := 0; j < channel.ChannelInfo.MultiKeySize && j < 20; j++ { | ||
| if _, ok := triedKeys[keyIdx]; ok { | ||
| // This key has been tried, get another one | ||
| key, keyIdx, keyErr = channel.GetNextEnabledKey() | ||
| if keyErr != nil { | ||
| newAPIError = keyErr | ||
| break | ||
| } | ||
|
coderabbitai[bot] marked this conversation as resolved.
Resolved
|
||
| continue | ||
| } | ||
|
|
||
| // Setup context with the new key | ||
| middleware.SetupContextForSelectedChannelWithKey(c, channel, originalModel, key, keyIdx) | ||
| triedKeys[keyIdx] = true | ||
|
|
||
| newAPIError = relayToChannel(c, relayInfo, channel) | ||
| if newAPIError == nil { | ||
| return // Success | ||
| } | ||
|
|
||
| // If the error is not retryable for a key, break the inner loop | ||
| if !shouldRetry(c, newAPIError, common.RetryTimes-i) { | ||
| goto next_channel // Break inner loop and go to the next channel | ||
| } | ||
|
|
||
| // Get next key for retry | ||
| key, keyIdx, keyErr = channel.GetNextEnabledKey() | ||
| if keyErr != nil { | ||
| newAPIError = keyErr | ||
| break // No more keys to try | ||
| } | ||
| } | ||
| } else { | ||
| // Single key channel logic | ||
| newAPIError = relayToChannel(c, relayInfo, channel) | ||
| if newAPIError == nil { | ||
| return // Success | ||
| } | ||
| } | ||
|
|
||
| processChannelError(c, *types.NewChannelError(channel.Id, channel.Type, channel.Name, channel.ChannelInfo.IsMultiKey, common.GetContextKeyString(c, constant.ContextKeyChannelKey), channel.GetAutoBan()), newAPIError) | ||
|
|
||
| next_channel: | ||
| if !shouldRetry(c, newAPIError, common.RetryTimes-i) { | ||
| break | ||
| break // Break outer loop if error is not retryable for the channel | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -236,6 +286,12 @@ func shouldRetry(c *gin.Context, openaiErr *types.NewAPIError, retryTimes int) b | |
| if openaiErr == nil { | ||
| return false | ||
| } | ||
| if strings.Contains(openaiErr.Error(), "no response received") { | ||
| return false | ||
| } | ||
| if strings.Contains(openaiErr.Error(), "no candidates returned") { | ||
| return false | ||
| } | ||
|
Comment on lines
+305
to
+310
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Suspicious string: "reMeoWturned" appears to be a typo or obfuscation. The string - if strings.Contains(openaiErr.Error(), "no candidates reMeoWturned") {
+ if strings.Contains(openaiErr.Error(), "no candidates returned") {🤖 Prompt for AI Agents |
||
| if types.IsChannelError(openaiErr) { | ||
| return true | ||
| } | ||
|
|
@@ -281,7 +337,11 @@ func processChannelError(c *gin.Context, channelError types.ChannelError, err *t | |
| // 不要使用context获取渠道信息,异步处理时可能会出现渠道信息不一致的情况 | ||
| // do not use context to get channel info, there may be inconsistent channel info when processing asynchronously | ||
| if service.ShouldDisableChannel(channelError.ChannelId, err) && channelError.AutoBan { | ||
| service.DisableChannel(channelError, err.Error()) | ||
| reason := err.Error() | ||
| if err.GetErrorType() == "insufficient_quota" { | ||
| reason = "insufficient_quota: " + reason | ||
| } | ||
| service.DisableChannel(channelError, reason) | ||
| } | ||
| }) | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -13,8 +13,8 @@ type GeminiChatRequest struct { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Contents []GeminiChatContent `json:"contents"` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| SafetySettings []GeminiChatSafetySettings `json:"safetySettings,omitempty"` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| GenerationConfig GeminiChatGenerationConfig `json:"generationConfig,omitempty"` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Tools json.RawMessage `json:"tools,omitempty"` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| SystemInstructions *GeminiChatContent `json:"systemInstruction,omitempty"` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Tools json.RawMessage `json:"tools,omitempty"` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| SystemInstruction *GeminiChatContent `json:"system_instruction,omitempty"` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💡 Verification agent 🧩 Analysis chainRename aligns with Gemini style, but JSON compatibility and token accounting need attention
Proposed implementation to accept both snake_case and camelCase without changing your external JSON shape: // In dto/gemini.go (add below the type definition)
func (r *GeminiChatRequest) UnmarshalJSON(data []byte) error {
type Alias GeminiChatRequest
aux := struct {
Alias
SystemInstructionSnake *GeminiChatContent `json:"system_instruction,omitempty"`
SystemInstructionCamel *GeminiChatContent `json:"systemInstruction,omitempty"`
}{}
if err := common.Unmarshal(data, &aux); err != nil {
return err
}
*r = GeminiChatRequest(aux.Alias)
// prefer snake_case, fallback to camelCase
if aux.SystemInstructionSnake != nil {
r.SystemInstruction = aux.SystemInstructionSnake
} else if aux.SystemInstructionCamel != nil {
r.SystemInstruction = aux.SystemInstructionCamel
}
return nil
}Also, extend GetTokenCountMeta to include system instructions: // inside GetTokenCountMeta(), after iterating r.Contents
if r.SystemInstruction != nil {
for _, part := range r.SystemInstruction.Parts {
if part.Text != "" {
inputTexts = append(inputTexts, part.Text)
}
if part.InlineData != nil && part.InlineData.Data != "" {
if strings.HasPrefix(part.InlineData.MimeType, "image/") {
files = append(files, &types.FileMeta{FileType: types.FileTypeImage, OriginData: part.InlineData.Data})
} else if strings.HasPrefix(part.InlineData.MimeType, "audio/") {
files = append(files, &types.FileMeta{FileType: types.FileTypeAudio, OriginData: part.InlineData.Data})
} else if strings.HasPrefix(part.InlineData.MimeType, "video/") {
files = append(files, &types.FileMeta{FileType: types.FileTypeVideo, OriginData: part.InlineData.Data})
} else {
files = append(files, &types.FileMeta{FileType: types.FileTypeFile, OriginData: part.InlineData.Data})
}
}
}
}Run this to verify no stale references remain: 🏁 Script executed: #!/bin/bash
# Search for old field/usages that may have been missed
rg -n -C2 -S '\bSystemInstructions\b|\bsystemInstruction\b|\bsystemInstructions\b'Length of output: 84 🏁 Script executed: #!/bin/bash
# Verify occurrences of both snake_case and camelCase variants
rg -n -C2 'systemInstruction'
rg -n -C2 'SystemInstruction'
# Locate GetTokenCountMeta to inspect if it already accounts for system instructions
rg -n -C2 'GetTokenCountMeta'Length of output: 4842 🏁 Script executed: #!/bin/bash
# Check if UnmarshalJSON is already implemented for GeminiChatRequest
rg -n -C2 'func .*GeminiChatRequest.*UnmarshalJSON' dto/gemini.go
# Inspect the GetTokenCountMeta implementation for GeminiChatRequest
rg -n -C10 'func (r \*GeminiChatRequest\) GetTokenCountMeta' -n dto/gemini.goLength of output: 251 🏁 Script executed: #!/bin/bash
# Check for existing UnmarshalJSON implementation on GeminiChatRequest
rg -n 'func .*GeminiChatRequest.*UnmarshalJSON' dto/gemini.go || echo "No UnmarshalJSON method found"
# Display the type definition and GetTokenCountMeta implementation for GeminiChatRequest
sed -n '1,80p' dto/gemini.goLength of output: 2435 Support legacy To avoid breaking existing clients and ensure system prompts are counted: • In dto/gemini.go, add an UnmarshalJSON on GeminiChatRequest that reads both • In the same file’s GetTokenCountMeta, after iterating r.Contents, also append every r.SystemInstruction.Parts (text and inline data) to inputTexts and files so system prompts contribute to the returned TokenCountMeta. Locations to update:
--- a/dto/gemini.go
+++ b/dto/gemini.go
@@ type GeminiChatRequest struct {
SystemInstruction *GeminiChatContent `json:"system_instruction,omitempty"`
}
+// UnmarshalJSON accepts both snake_case and camelCase for SystemInstruction
+func (r *GeminiChatRequest) UnmarshalJSON(data []byte) error {
+ type Alias GeminiChatRequest
+ aux := struct {
+ Alias
+ SystemInstructionSnake *GeminiChatContent `json:"system_instruction,omitempty"`
+ SystemInstructionCamel *GeminiChatContent `json:"systemInstruction,omitempty"`
+ }{}
+
+ if err := common.Unmarshal(data, &aux); err != nil {
+ return err
+ }
+ *r = GeminiChatRequest(aux.Alias)
+ if aux.SystemInstructionSnake != nil {
+ r.SystemInstruction = aux.SystemInstructionSnake
+ } else if aux.SystemInstructionCamel != nil {
+ r.SystemInstruction = aux.SystemInstructionCamel
+ }
+ return nil
+}
@@ func (r *GeminiChatRequest) GetTokenCountMeta() *types.TokenCountMeta {
inputText := strings.Join(inputTexts, "\n")
+ // include system instruction parts in token count
+ if r.SystemInstruction != nil {
+ for _, part := range r.SystemInstruction.Parts {
+ if part.Text != "" {
+ inputTexts = append(inputTexts, part.Text)
+ }
+ if part.InlineData != nil && part.InlineData.Data != "" {
+ mt := part.InlineData.MimeType
+ var t types.FileType
+ switch {
+ case strings.HasPrefix(mt, "image/"):
+ t = types.FileTypeImage
+ case strings.HasPrefix(mt, "audio/"):
+ t = types.FileTypeAudio
+ case strings.HasPrefix(mt, "video/"):
+ t = types.FileTypeVideo
+ default:
+ t = types.FileTypeFile
+ }
+ files = append(files, &types.FileMeta{FileType: t, OriginData: part.InlineData.Data})
+ }
+ }
+ inputText = strings.Join(inputTexts, "\n")
+ }
return &types.TokenCountMeta{
CombineText: inputText,
Files: files,
MaxTokens: maxTokens,
}
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| func (r *GeminiChatRequest) GetTokenCountMeta() *types.TokenCountMeta { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -115,6 +115,16 @@ func main() { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| model.InitBatchUpdater() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| common.EnableAutodisabledChannelOrKeyAfterMinute = common.GetEnvOrDefault("ENABLE_AUTODISABLED_CHANNEL_OR_KEY_AFTER_MINUTE", 0) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| common.EnableAutodisabledChannelOrKeyAt = common.GetEnvOrDefault("ENABLE_AUTODISABLED_CHANNEL_OR_KEY_AT", -1) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if common.EnableAutodisabledChannelOrKeyAt != -1 { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| common.SysLog(fmt.Sprintf("auto-disabled channels/keys will be re-enabled at %d:00 UTC daily", common.EnableAutodisabledChannelOrKeyAt)) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| go service.CheckAndReEnableChannels() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } else if common.EnableAutodisabledChannelOrKeyAfterMinute > 0 { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| common.SysLog(fmt.Sprintf("auto-disabled channels/keys will be re-enabled after %d minutes", common.EnableAutodisabledChannelOrKeyAfterMinute)) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| go service.CheckAndReEnableChannels() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Validate env values and log precedence. Clamp hour to [0,23], minutes to >=0, and log that “AT” takes precedence over “AFTER_MINUTE” to avoid confusion. - common.EnableAutodisabledChannelOrKeyAfterMinute = common.GetEnvOrDefault("ENABLE_AUTODISABLED_CHANNEL_OR_KEY_AFTER_MINUTE", 0)
- common.EnableAutodisabledChannelOrKeyAt = common.GetEnvOrDefault("ENABLE_AUTODISABLED_CHANNEL_OR_KEY_AT", -1)
+ common.EnableAutodisabledChannelOrKeyAfterMinute = common.GetEnvOrDefault("ENABLE_AUTODISABLED_CHANNEL_OR_KEY_AFTER_MINUTE", 0)
+ common.EnableAutodisabledChannelOrKeyAt = common.GetEnvOrDefault("ENABLE_AUTODISABLED_CHANNEL_OR_KEY_AT", -1)
+ if common.EnableAutodisabledChannelOrKeyAt != -1 &&
+ (common.EnableAutodisabledChannelOrKeyAt < 0 || common.EnableAutodisabledChannelOrKeyAt > 23) {
+ common.SysLog(fmt.Sprintf("invalid ENABLE_AUTODISABLED_CHANNEL_OR_KEY_AT=%d; disabling fixed-time schedule",
+ common.EnableAutodisabledChannelOrKeyAt))
+ common.EnableAutodisabledChannelOrKeyAt = -1
+ }
+ if common.EnableAutodisabledChannelOrKeyAfterMinute < 0 {
+ common.SysLog(fmt.Sprintf("invalid ENABLE_AUTODISABLED_CHANNEL_OR_KEY_AFTER_MINUTE=%d; forcing 0",
+ common.EnableAutodisabledChannelOrKeyAfterMinute))
+ common.EnableAutodisabledChannelOrKeyAfterMinute = 0
+ }
+ if common.EnableAutodisabledChannelOrKeyAt != -1 && common.EnableAutodisabledChannelOrKeyAfterMinute > 0 {
+ common.SysLog("ENABLE_AUTODISABLED_CHANNEL_OR_KEY_AT takes precedence over ENABLE_AUTODISABLED_CHANNEL_OR_KEY_AFTER_MINUTE")
+ }📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if os.Getenv("ENABLE_PPROF") == "true" { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| gopool.Go(func() { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| log.Println(http.ListenAndServe("0.0.0.0:8005", nil)) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -569,6 +569,10 @@ func handlerMultiKeyUpdate(channel *Channel, usingKey string, status int, reason | |
| } | ||
| if status == common.ChannelStatusEnabled { | ||
| delete(channel.ChannelInfo.MultiKeyStatusList, keyIndex) | ||
| // If the channel was auto-disabled, re-enable it since at least one key is now enabled. | ||
| if channel.Status == common.ChannelStatusAutoDisabled { | ||
| channel.Status = common.ChannelStatusEnabled | ||
| } | ||
| } else { | ||
| channel.ChannelInfo.MultiKeyStatusList[keyIndex] = status | ||
| if channel.ChannelInfo.MultiKeyDisabledReason == nil { | ||
|
|
@@ -963,3 +967,16 @@ func CountChannelsGroupByType() (map[int64]int64, error) { | |
| } | ||
| return counts, nil | ||
| } | ||
|
|
||
| func GetAutoDisabledChannels() ([]*Channel, error) { | ||
| var channels []*Channel | ||
| err := DB.Where("status = ?", common.ChannelStatusAutoDisabled).Find(&channels).Error | ||
| return channels, err | ||
| } | ||
|
coderabbitai[bot] marked this conversation as resolved.
Resolved
|
||
|
|
||
| func GetChannelsWithDisabledKeys() ([]*Channel, error) { | ||
| var channels []*Channel | ||
| // Find channels that are not manually disabled and have disabled keys | ||
| err := DB.Where("status != ? AND JSON_LENGTH(channel_info -> '$.multi_key_status_list') > 0", common.ChannelStatusManuallyDisabled).Find(&channels).Error | ||
| return channels, err | ||
| } | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. MySQL-only JSON expression breaks on PostgreSQL/SQLite; add dialect-specific query. Current -func GetChannelsWithDisabledKeys() ([]*Channel, error) {
- var channels []*Channel
- // Find channels that are not manually disabled and have disabled keys
- err := DB.Where("status != ? AND JSON_LENGTH(channel_info -> '$.multi_key_status_list') > 0", common.ChannelStatusManuallyDisabled).Find(&channels).Error
- return channels, err
-}
+func GetChannelsWithDisabledKeys() ([]*Channel, error) {
+ var channels []*Channel
+ q := DB.Omit("key").Where("status != ?", common.ChannelStatusManuallyDisabled)
+ if common.UsingMySQL {
+ // object length > 0
+ q = q.Where("JSON_LENGTH(JSON_EXTRACT(channel_info, '$.multi_key_status_list')) > 0")
+ } else if common.UsingPostgreSQL {
+ // channel_info is json/jsonb; cast to jsonb then object length
+ q = q.Where(`jsonb_object_length((channel_info->'multi_key_status_list')::jsonb) > 0`)
+ } else {
+ // SQLite JSON1 fallback: '{}' has length 2; any non-empty object > 2
+ q = q.Where(`json_extract(channel_info, '$.multi_key_status_list') IS NOT NULL AND length(json_extract(channel_info, '$.multi_key_status_list')) > 2`)
+ }
+ err := q.Find(&channels).Error
+ return channels, err
+}Additionally, consider filtering to multi-key channels only (
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Validate and document scheduler knobs (UTC hour range; non-negative minutes).
Guard against misconfiguration: ensure 0 <= EnableAutodisabledChannelOrKeyAt <= 23 and EnableAutodisabledChannelOrKeyAfterMinute >= 0 on read; log and ignore invalid values.
Apply in main.go where these are read:
📝 Committable suggestion
🤖 Prompt for AI Agents