Compare commits

..

4 Commits

Author SHA1 Message Date
Luis Pater 3b4d6d359b Merge pull request #1049 from router-for-me/codex
docker-image / docker (push) Has been cancelled
goreleaser / goreleaser (push) Has been cancelled
feat(codex): add config toggle for codex instructions injection
2026-01-16 12:38:35 +08:00
hkfires 48cba39a12 feat(codex): add config toggle for codex instructions injection 2026-01-16 12:30:12 +08:00
Luis Pater cec4e251bd feat(translator): preserve text field in serialized output during chat completions processing
docker-image / docker (push) Has been cancelled
goreleaser / goreleaser (push) Has been cancelled
2026-01-16 11:35:34 +08:00
Luis Pater 526dd866ba refactor(gemini): replace static model handling with dynamic model registry lookup 2026-01-16 10:39:16 +08:00
6 changed files with 69 additions and 87 deletions
+4
View File
@@ -85,6 +85,10 @@ nonstream-keepalive-interval: 0
# keepalive-seconds: 15 # Default: 0 (disabled). <= 0 disables keep-alives. # keepalive-seconds: 15 # Default: 0 (disabled). <= 0 disables keep-alives.
# bootstrap-retries: 1 # Default: 0 (disabled). Retries before first byte is sent. # bootstrap-retries: 1 # Default: 0 (disabled). Retries before first byte is sent.
# When true, enable custom Codex instructions injection for Codex API requests.
# When false (default), CodexInstructionsForModel returns immediately without modification.
codex-instructions-enabled: false
# Gemini API keys # Gemini API keys
# gemini-api-key: # gemini-api-key:
# - api-key: "AIzaSy...01" # - api-key: "AIzaSy...01"
+12
View File
@@ -26,6 +26,7 @@ import (
"github.com/router-for-me/CLIProxyAPI/v6/internal/config" "github.com/router-for-me/CLIProxyAPI/v6/internal/config"
"github.com/router-for-me/CLIProxyAPI/v6/internal/logging" "github.com/router-for-me/CLIProxyAPI/v6/internal/logging"
"github.com/router-for-me/CLIProxyAPI/v6/internal/managementasset" "github.com/router-for-me/CLIProxyAPI/v6/internal/managementasset"
"github.com/router-for-me/CLIProxyAPI/v6/internal/misc"
"github.com/router-for-me/CLIProxyAPI/v6/internal/usage" "github.com/router-for-me/CLIProxyAPI/v6/internal/usage"
"github.com/router-for-me/CLIProxyAPI/v6/internal/util" "github.com/router-for-me/CLIProxyAPI/v6/internal/util"
sdkaccess "github.com/router-for-me/CLIProxyAPI/v6/sdk/access" sdkaccess "github.com/router-for-me/CLIProxyAPI/v6/sdk/access"
@@ -254,6 +255,7 @@ func NewServer(cfg *config.Config, authManager *auth.Manager, accessManager *sdk
} }
managementasset.SetCurrentConfig(cfg) managementasset.SetCurrentConfig(cfg)
auth.SetQuotaCooldownDisabled(cfg.DisableCooling) auth.SetQuotaCooldownDisabled(cfg.DisableCooling)
misc.SetCodexInstructionsEnabled(cfg.CodexInstructionsEnabled)
// Initialize management handler // Initialize management handler
s.mgmt = managementHandlers.NewHandler(cfg, configFilePath, authManager) s.mgmt = managementHandlers.NewHandler(cfg, configFilePath, authManager)
if optionState.localPassword != "" { if optionState.localPassword != "" {
@@ -912,6 +914,16 @@ func (s *Server) UpdateClients(cfg *config.Config) {
log.Debugf("disable_cooling toggled to %t", cfg.DisableCooling) log.Debugf("disable_cooling toggled to %t", cfg.DisableCooling)
} }
} }
if oldCfg == nil || oldCfg.CodexInstructionsEnabled != cfg.CodexInstructionsEnabled {
misc.SetCodexInstructionsEnabled(cfg.CodexInstructionsEnabled)
if oldCfg != nil {
log.Debugf("codex_instructions_enabled updated from %t to %t", oldCfg.CodexInstructionsEnabled, cfg.CodexInstructionsEnabled)
} else {
log.Debugf("codex_instructions_enabled toggled to %t", cfg.CodexInstructionsEnabled)
}
}
if s.handlers != nil && s.handlers.AuthManager != nil { if s.handlers != nil && s.handlers.AuthManager != nil {
s.handlers.AuthManager.SetRetryConfig(cfg.RequestRetry, time.Duration(cfg.MaxRetryInterval)*time.Second) s.handlers.AuthManager.SetRetryConfig(cfg.RequestRetry, time.Duration(cfg.MaxRetryInterval)*time.Second)
} }
+5
View File
@@ -71,6 +71,11 @@ type Config struct {
// WebsocketAuth enables or disables authentication for the WebSocket API. // WebsocketAuth enables or disables authentication for the WebSocket API.
WebsocketAuth bool `yaml:"ws-auth" json:"ws-auth"` WebsocketAuth bool `yaml:"ws-auth" json:"ws-auth"`
// CodexInstructionsEnabled controls whether custom Codex instructions are injected.
// When false (default), CodexInstructionsForModel returns immediately without modification.
// When true, the original instruction injection logic is used.
CodexInstructionsEnabled bool `yaml:"codex-instructions-enabled" json:"codex-instructions-enabled"`
// GeminiKey defines Gemini API key configurations with optional routing overrides. // GeminiKey defines Gemini API key configurations with optional routing overrides.
GeminiKey []GeminiKey `yaml:"gemini-api-key" json:"gemini-api-key"` GeminiKey []GeminiKey `yaml:"gemini-api-key" json:"gemini-api-key"`
+19
View File
@@ -7,11 +7,27 @@ import (
"embed" "embed"
_ "embed" _ "embed"
"strings" "strings"
"sync/atomic"
"github.com/tidwall/gjson" "github.com/tidwall/gjson"
"github.com/tidwall/sjson" "github.com/tidwall/sjson"
) )
// codexInstructionsEnabled controls whether CodexInstructionsForModel returns custom instructions.
// When false (default), CodexInstructionsForModel returns (true, "") immediately.
// Set via SetCodexInstructionsEnabled from config.
var codexInstructionsEnabled atomic.Bool
// SetCodexInstructionsEnabled sets whether codex instructions processing is enabled.
func SetCodexInstructionsEnabled(enabled bool) {
codexInstructionsEnabled.Store(enabled)
}
// GetCodexInstructionsEnabled returns whether codex instructions processing is enabled.
func GetCodexInstructionsEnabled() bool {
return codexInstructionsEnabled.Load()
}
//go:embed codex_instructions //go:embed codex_instructions
var codexInstructionsDir embed.FS var codexInstructionsDir embed.FS
@@ -124,6 +140,9 @@ func codexInstructionsForCodex(modelName, systemInstructions string) (bool, stri
} }
func CodexInstructionsForModel(modelName, systemInstructions, userAgent string) (bool, string) { func CodexInstructionsForModel(modelName, systemInstructions, userAgent string) (bool, string) {
if !GetCodexInstructionsEnabled() {
return true, ""
}
if IsOpenCodeUserAgent(userAgent) { if IsOpenCodeUserAgent(userAgent) {
return codexInstructionsForOpenCode(systemInstructions) return codexInstructionsForOpenCode(systemInstructions)
} }
@@ -209,6 +209,7 @@ func ConvertOpenAIRequestToAntigravity(modelName string, inputRawJSON []byte, _
for _, item := range content.Array() { for _, item := range content.Array() {
switch item.Get("type").String() { switch item.Get("type").String() {
case "text": case "text":
node, _ = sjson.SetBytes(node, "parts."+itoa(p)+".text", item.Get("text").String())
p++ p++
case "image_url": case "image_url":
// If the assistant returned an inline data URL, preserve it for history fidelity. // If the assistant returned an inline data URL, preserve it for history fidelity.
+28 -87
View File
@@ -85,94 +85,35 @@ func (h *GeminiAPIHandler) GeminiGetHandler(c *gin.Context) {
return return
} }
action := strings.TrimPrefix(request.Action, "/") action := strings.TrimPrefix(request.Action, "/")
switch action {
case "gemini-3-pro-preview": // Get dynamic models from the global registry and find the matching one
c.JSON(http.StatusOK, gin.H{ availableModels := h.Models()
"name": "models/gemini-3-pro-preview", var targetModel map[string]any
"version": "3",
"displayName": "Gemini 3 Pro Preview", for _, model := range availableModels {
"description": "Gemini 3 Pro Preview", name, _ := model["name"].(string)
"inputTokenLimit": 1048576, // Match name with or without 'models/' prefix
"outputTokenLimit": 65536, if name == action || name == "models/"+action {
"supportedGenerationMethods": []string{ targetModel = model
"generateContent", break
"countTokens", }
"createCachedContent",
"batchGenerateContent",
},
"temperature": 1,
"topP": 0.95,
"topK": 64,
"maxTemperature": 2,
"thinking": true,
},
)
case "gemini-2.5-pro":
c.JSON(http.StatusOK, gin.H{
"name": "models/gemini-2.5-pro",
"version": "2.5",
"displayName": "Gemini 2.5 Pro",
"description": "Stable release (June 17th, 2025) of Gemini 2.5 Pro",
"inputTokenLimit": 1048576,
"outputTokenLimit": 65536,
"supportedGenerationMethods": []string{
"generateContent",
"countTokens",
"createCachedContent",
"batchGenerateContent",
},
"temperature": 1,
"topP": 0.95,
"topK": 64,
"maxTemperature": 2,
"thinking": true,
},
)
case "gemini-2.5-flash":
c.JSON(http.StatusOK, gin.H{
"name": "models/gemini-2.5-flash",
"version": "001",
"displayName": "Gemini 2.5 Flash",
"description": "Stable version of Gemini 2.5 Flash, our mid-size multimodal model that supports up to 1 million tokens, released in June of 2025.",
"inputTokenLimit": 1048576,
"outputTokenLimit": 65536,
"supportedGenerationMethods": []string{
"generateContent",
"countTokens",
"createCachedContent",
"batchGenerateContent",
},
"temperature": 1,
"topP": 0.95,
"topK": 64,
"maxTemperature": 2,
"thinking": true,
})
case "gpt-5":
c.JSON(http.StatusOK, gin.H{
"name": "gpt-5",
"version": "001",
"displayName": "GPT 5",
"description": "Stable version of GPT 5, The best model for coding and agentic tasks across domains.",
"inputTokenLimit": 400000,
"outputTokenLimit": 128000,
"supportedGenerationMethods": []string{
"generateContent",
},
"temperature": 1,
"topP": 0.95,
"topK": 64,
"maxTemperature": 2,
"thinking": true,
})
default:
c.JSON(http.StatusNotFound, handlers.ErrorResponse{
Error: handlers.ErrorDetail{
Message: "Not Found",
Type: "not_found",
},
})
} }
if targetModel != nil {
// Ensure the name has 'models/' prefix in the output if it's a Gemini model
if name, ok := targetModel["name"].(string); ok && name != "" && !strings.HasPrefix(name, "models/") {
targetModel["name"] = "models/" + name
}
c.JSON(http.StatusOK, targetModel)
return
}
c.JSON(http.StatusNotFound, handlers.ErrorResponse{
Error: handlers.ErrorDetail{
Message: "Not Found",
Type: "not_found",
},
})
} }
// GeminiHandler handles POST requests for Gemini API operations. // GeminiHandler handles POST requests for Gemini API operations.