mirror of
https://github.com/router-for-me/CLIProxyAPIPlus.git
synced 2026-04-24 05:10:27 +00:00
Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3c4e7997c3 | ||
|
|
1afc3a5f65 | ||
|
|
ea3d22831e | ||
|
|
3b4d6d359b | ||
|
|
48cba39a12 | ||
|
|
bca244df67 | ||
|
|
cec4e251bd | ||
|
|
526dd866ba |
@@ -90,6 +90,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 official 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"
|
||||||
@@ -218,7 +222,7 @@ nonstream-keepalive-interval: 0
|
|||||||
|
|
||||||
# Global OAuth model name aliases (per channel)
|
# Global OAuth model name aliases (per channel)
|
||||||
# These aliases rename model IDs for both model listing and request routing.
|
# These aliases rename model IDs for both model listing and request routing.
|
||||||
# Supported channels: gemini-cli, vertex, aistudio, antigravity, claude, codex, qwen, iflow.
|
# Supported channels: gemini-cli, vertex, aistudio, antigravity, claude, codex, qwen, iflow, kiro, github-copilot.
|
||||||
# NOTE: Aliases do not apply to gemini-api-key, codex-api-key, claude-api-key, openai-compatibility, vertex-api-key, or ampcode.
|
# NOTE: Aliases do not apply to gemini-api-key, codex-api-key, claude-api-key, openai-compatibility, vertex-api-key, or ampcode.
|
||||||
# You can repeat the same name with different aliases to expose multiple client model names.
|
# You can repeat the same name with different aliases to expose multiple client model names.
|
||||||
#oauth-model-alias:
|
#oauth-model-alias:
|
||||||
|
|||||||
@@ -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 != "" {
|
||||||
@@ -933,6 +935,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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"`
|
||||||
|
|
||||||
@@ -103,7 +108,7 @@ type Config struct {
|
|||||||
|
|
||||||
// OAuthModelAlias defines global model name aliases for OAuth/file-backed auth channels.
|
// OAuthModelAlias defines global model name aliases for OAuth/file-backed auth channels.
|
||||||
// These aliases affect both model listing and model routing for supported channels:
|
// These aliases affect both model listing and model routing for supported channels:
|
||||||
// gemini-cli, vertex, aistudio, antigravity, claude, codex, qwen, iflow.
|
// gemini-cli, vertex, aistudio, antigravity, claude, codex, qwen, iflow, kiro, github-copilot.
|
||||||
//
|
//
|
||||||
// NOTE: This does not apply to existing per-credential model alias features under:
|
// NOTE: This does not apply to existing per-credential model alias features under:
|
||||||
// gemini-api-key, codex-api-key, claude-api-key, openai-compatibility, vertex-api-key, and ampcode.
|
// gemini-api-key, codex-api-key, claude-api-key, openai-compatibility, vertex-api-key, and ampcode.
|
||||||
|
|||||||
@@ -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 official 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.
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -221,7 +221,7 @@ func modelAliasChannel(auth *Auth) string {
|
|||||||
// and auth kind. Returns empty string if the provider/authKind combination doesn't support
|
// and auth kind. Returns empty string if the provider/authKind combination doesn't support
|
||||||
// OAuth model alias (e.g., API key authentication).
|
// OAuth model alias (e.g., API key authentication).
|
||||||
//
|
//
|
||||||
// Supported channels: gemini-cli, vertex, aistudio, antigravity, claude, codex, qwen, iflow.
|
// Supported channels: gemini-cli, vertex, aistudio, antigravity, claude, codex, qwen, iflow, kiro, github-copilot.
|
||||||
func OAuthModelAliasChannel(provider, authKind string) string {
|
func OAuthModelAliasChannel(provider, authKind string) string {
|
||||||
provider = strings.ToLower(strings.TrimSpace(provider))
|
provider = strings.ToLower(strings.TrimSpace(provider))
|
||||||
authKind = strings.ToLower(strings.TrimSpace(authKind))
|
authKind = strings.ToLower(strings.TrimSpace(authKind))
|
||||||
@@ -245,7 +245,7 @@ func OAuthModelAliasChannel(provider, authKind string) string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
return "codex"
|
return "codex"
|
||||||
case "gemini-cli", "aistudio", "antigravity", "qwen", "iflow":
|
case "gemini-cli", "aistudio", "antigravity", "qwen", "iflow", "kiro", "github-copilot":
|
||||||
return provider
|
return provider
|
||||||
default:
|
default:
|
||||||
return ""
|
return ""
|
||||||
|
|||||||
@@ -43,6 +43,15 @@ func TestResolveOAuthUpstreamModel_SuffixPreservation(t *testing.T) {
|
|||||||
input: "gemini-2.5-pro",
|
input: "gemini-2.5-pro",
|
||||||
want: "gemini-2.5-pro-exp-03-25",
|
want: "gemini-2.5-pro-exp-03-25",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "kiro alias resolves",
|
||||||
|
aliases: map[string][]internalconfig.OAuthModelAlias{
|
||||||
|
"kiro": {{Name: "kiro-claude-sonnet-4-5", Alias: "sonnet"}},
|
||||||
|
},
|
||||||
|
channel: "kiro",
|
||||||
|
input: "sonnet",
|
||||||
|
want: "kiro-claude-sonnet-4-5",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "config suffix takes priority",
|
name: "config suffix takes priority",
|
||||||
aliases: map[string][]internalconfig.OAuthModelAlias{
|
aliases: map[string][]internalconfig.OAuthModelAlias{
|
||||||
@@ -152,6 +161,8 @@ func createAuthForChannel(channel string) *Auth {
|
|||||||
return &Auth{Provider: "qwen"}
|
return &Auth{Provider: "qwen"}
|
||||||
case "iflow":
|
case "iflow":
|
||||||
return &Auth{Provider: "iflow"}
|
return &Auth{Provider: "iflow"}
|
||||||
|
case "kiro":
|
||||||
|
return &Auth{Provider: "kiro"}
|
||||||
default:
|
default:
|
||||||
return &Auth{Provider: channel}
|
return &Auth{Provider: channel}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user