mirror of
https://github.com/router-for-me/CLIProxyAPIPlus.git
synced 2026-05-13 22:31:40 +00:00
Merge branch 'pr-529'
# Conflicts: # README.md # README_CN.md # README_JA.md # cmd/server/main.go # config.example.yaml # internal/api/handlers/management/auth_files.go # internal/api/server.go # internal/auth/iflow/iflow_auth.go # internal/cmd/auth_manager.go # internal/config/config.go # sdk/cliproxy/auth/oauth_model_alias.go # sdk/cliproxy/auth/types.go # test/thinking_conversion_test.go
This commit is contained in:
@@ -232,8 +232,6 @@ func NormalizeOAuthProvider(provider string) (string, error) {
|
||||
return "gitlab", nil
|
||||
case "gemini", "google":
|
||||
return "gemini", nil
|
||||
case "iflow", "i-flow":
|
||||
return "iflow", nil
|
||||
case "antigravity", "anti-gravity":
|
||||
return "antigravity", nil
|
||||
case "kiro":
|
||||
|
||||
@@ -437,20 +437,6 @@ func (s *Server) setupRoutes() {
|
||||
c.String(http.StatusOK, oauthCallbackSuccessHTML)
|
||||
})
|
||||
|
||||
s.engine.GET("/iflow/callback", func(c *gin.Context) {
|
||||
code := c.Query("code")
|
||||
state := c.Query("state")
|
||||
errStr := c.Query("error")
|
||||
if errStr == "" {
|
||||
errStr = c.Query("error_description")
|
||||
}
|
||||
if state != "" {
|
||||
_, _ = managementHandlers.WriteOAuthCallbackFileForPendingSession(s.cfg.AuthDir, "iflow", state, code, errStr)
|
||||
}
|
||||
c.Header("Content-Type", "text/html; charset=utf-8")
|
||||
c.String(http.StatusOK, oauthCallbackSuccessHTML)
|
||||
})
|
||||
|
||||
s.engine.GET("/antigravity/callback", func(c *gin.Context) {
|
||||
code := c.Query("code")
|
||||
state := c.Query("state")
|
||||
@@ -683,18 +669,18 @@ func (s *Server) registerManagementRoutes() {
|
||||
mgmt.GET("/gitlab-auth-url", s.mgmt.RequestGitLabToken)
|
||||
mgmt.POST("/gitlab-auth-url", s.mgmt.RequestGitLabPATToken)
|
||||
mgmt.GET("/gemini-cli-auth-url", s.mgmt.RequestGeminiCLIToken)
|
||||
mgmt.GET("/antigravity-auth-url", s.mgmt.RequestAntigravityToken)
|
||||
mgmt.GET("/kilo-auth-url", s.mgmt.RequestKiloToken)
|
||||
mgmt.GET("/kimi-auth-url", s.mgmt.RequestKimiToken)
|
||||
mgmt.GET("/iflow-auth-url", s.mgmt.RequestIFlowToken)
|
||||
mgmt.POST("/iflow-auth-url", s.mgmt.RequestIFlowCookieToken)
|
||||
mgmt.GET("/kiro-auth-url", s.mgmt.RequestKiroToken)
|
||||
mgmt.GET("/cursor-auth-url", s.mgmt.RequestCursorToken)
|
||||
mgmt.GET("/github-auth-url", s.mgmt.RequestGitHubToken)
|
||||
mgmt.POST("/oauth-callback", s.mgmt.PostOAuthCallback)
|
||||
mgmt.GET("/get-auth-status", s.mgmt.GetAuthStatus)
|
||||
mgmt.GET("/antigravity-auth-url", s.mgmt.RequestAntigravityToken)
|
||||
mgmt.GET("/kilo-auth-url", s.mgmt.RequestKiloToken)
|
||||
mgmt.GET("/kimi-auth-url", s.mgmt.RequestKimiToken)
|
||||
mgmt.GET("/iflow-auth-url", s.mgmt.RequestIFlowToken)
|
||||
mgmt.POST("/iflow-auth-url", s.mgmt.RequestIFlowCookieToken)
|
||||
mgmt.GET("/kiro-auth-url", s.mgmt.RequestKiroToken)
|
||||
mgmt.GET("/cursor-auth-url", s.mgmt.RequestCursorToken)
|
||||
mgmt.GET("/github-auth-url", s.mgmt.RequestGitHubToken)
|
||||
mgmt.POST("/oauth-callback", s.mgmt.PostOAuthCallback)
|
||||
mgmt.GET("/get-auth-status", s.mgmt.GetAuthStatus)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) managementAvailabilityMiddleware() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
|
||||
@@ -59,10 +59,30 @@ type ClaudeAuth struct {
|
||||
// Returns:
|
||||
// - *ClaudeAuth: A new Claude authentication service instance
|
||||
func NewClaudeAuth(cfg *config.Config) *ClaudeAuth {
|
||||
return NewClaudeAuthWithProxyURL(cfg, "")
|
||||
}
|
||||
|
||||
// NewClaudeAuthWithProxyURL creates a new Anthropic authentication service with a proxy override.
|
||||
// proxyURL takes precedence over cfg.ProxyURL when non-empty.
|
||||
func NewClaudeAuthWithProxyURL(cfg *config.Config, proxyURL string) *ClaudeAuth {
|
||||
effectiveProxyURL := strings.TrimSpace(proxyURL)
|
||||
var sdkCfg *config.SDKConfig
|
||||
if cfg != nil {
|
||||
sdkCfgCopy := cfg.SDKConfig
|
||||
if effectiveProxyURL == "" {
|
||||
effectiveProxyURL = strings.TrimSpace(cfg.ProxyURL)
|
||||
}
|
||||
sdkCfgCopy.ProxyURL = effectiveProxyURL
|
||||
sdkCfg = &sdkCfgCopy
|
||||
} else if effectiveProxyURL != "" {
|
||||
sdkCfgCopy := config.SDKConfig{ProxyURL: effectiveProxyURL}
|
||||
sdkCfg = &sdkCfgCopy
|
||||
}
|
||||
|
||||
// Use custom HTTP client with Firefox TLS fingerprint to bypass
|
||||
// Cloudflare's bot detection on Anthropic domains
|
||||
return &ClaudeAuth{
|
||||
httpClient: NewAnthropicHttpClient(&cfg.SDKConfig),
|
||||
httpClient: NewAnthropicHttpClient(sdkCfg),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
33
internal/auth/claude/anthropic_auth_proxy_test.go
Normal file
33
internal/auth/claude/anthropic_auth_proxy_test.go
Normal file
@@ -0,0 +1,33 @@
|
||||
package claude
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/router-for-me/CLIProxyAPI/v6/internal/config"
|
||||
"golang.org/x/net/proxy"
|
||||
)
|
||||
|
||||
func TestNewClaudeAuthWithProxyURL_OverrideDirectTakesPrecedence(t *testing.T) {
|
||||
cfg := &config.Config{SDKConfig: config.SDKConfig{ProxyURL: "socks5://proxy.example.com:1080"}}
|
||||
auth := NewClaudeAuthWithProxyURL(cfg, "direct")
|
||||
|
||||
transport, ok := auth.httpClient.Transport.(*utlsRoundTripper)
|
||||
if !ok || transport == nil {
|
||||
t.Fatalf("expected utlsRoundTripper, got %T", auth.httpClient.Transport)
|
||||
}
|
||||
if transport.dialer != proxy.Direct {
|
||||
t.Fatalf("expected proxy.Direct, got %T", transport.dialer)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewClaudeAuthWithProxyURL_OverrideProxyAppliedWithoutConfig(t *testing.T) {
|
||||
auth := NewClaudeAuthWithProxyURL(nil, "socks5://proxy.example.com:1080")
|
||||
|
||||
transport, ok := auth.httpClient.Transport.(*utlsRoundTripper)
|
||||
if !ok || transport == nil {
|
||||
t.Fatalf("expected utlsRoundTripper, got %T", auth.httpClient.Transport)
|
||||
}
|
||||
if transport.dialer == proxy.Direct {
|
||||
t.Fatalf("expected proxy dialer, got %T", transport.dialer)
|
||||
}
|
||||
}
|
||||
@@ -37,8 +37,23 @@ type CodexAuth struct {
|
||||
// NewCodexAuth creates a new CodexAuth service instance.
|
||||
// It initializes an HTTP client with proxy settings from the provided configuration.
|
||||
func NewCodexAuth(cfg *config.Config) *CodexAuth {
|
||||
return NewCodexAuthWithProxyURL(cfg, "")
|
||||
}
|
||||
|
||||
// NewCodexAuthWithProxyURL creates a new CodexAuth service instance.
|
||||
// proxyURL takes precedence over cfg.ProxyURL when non-empty.
|
||||
func NewCodexAuthWithProxyURL(cfg *config.Config, proxyURL string) *CodexAuth {
|
||||
effectiveProxyURL := strings.TrimSpace(proxyURL)
|
||||
var sdkCfg config.SDKConfig
|
||||
if cfg != nil {
|
||||
sdkCfg = cfg.SDKConfig
|
||||
if effectiveProxyURL == "" {
|
||||
effectiveProxyURL = strings.TrimSpace(cfg.ProxyURL)
|
||||
}
|
||||
}
|
||||
sdkCfg.ProxyURL = effectiveProxyURL
|
||||
return &CodexAuth{
|
||||
httpClient: util.SetProxy(&cfg.SDKConfig, &http.Client{}),
|
||||
httpClient: util.SetProxy(&sdkCfg, &http.Client{}),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,8 @@ import (
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
|
||||
"github.com/router-for-me/CLIProxyAPI/v6/internal/config"
|
||||
)
|
||||
|
||||
type roundTripFunc func(*http.Request) (*http.Response, error)
|
||||
@@ -42,3 +44,37 @@ func TestRefreshTokensWithRetry_NonRetryableOnlyAttemptsOnce(t *testing.T) {
|
||||
t.Fatalf("expected 1 refresh attempt, got %d", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewCodexAuthWithProxyURL_OverrideDirectDisablesProxy(t *testing.T) {
|
||||
cfg := &config.Config{SDKConfig: config.SDKConfig{ProxyURL: "http://proxy.example.com:8080"}}
|
||||
auth := NewCodexAuthWithProxyURL(cfg, "direct")
|
||||
|
||||
transport, ok := auth.httpClient.Transport.(*http.Transport)
|
||||
if !ok || transport == nil {
|
||||
t.Fatalf("expected http.Transport, got %T", auth.httpClient.Transport)
|
||||
}
|
||||
if transport.Proxy != nil {
|
||||
t.Fatal("expected direct transport to disable proxy function")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewCodexAuthWithProxyURL_OverrideProxyTakesPrecedence(t *testing.T) {
|
||||
cfg := &config.Config{SDKConfig: config.SDKConfig{ProxyURL: "http://global.example.com:8080"}}
|
||||
auth := NewCodexAuthWithProxyURL(cfg, "http://override.example.com:8081")
|
||||
|
||||
transport, ok := auth.httpClient.Transport.(*http.Transport)
|
||||
if !ok || transport == nil {
|
||||
t.Fatalf("expected http.Transport, got %T", auth.httpClient.Transport)
|
||||
}
|
||||
req, errReq := http.NewRequest(http.MethodGet, "https://example.com", nil)
|
||||
if errReq != nil {
|
||||
t.Fatalf("new request: %v", errReq)
|
||||
}
|
||||
proxyURL, errProxy := transport.Proxy(req)
|
||||
if errProxy != nil {
|
||||
t.Fatalf("proxy func: %v", errProxy)
|
||||
}
|
||||
if proxyURL == nil || proxyURL.String() != "http://override.example.com:8081" {
|
||||
t.Fatalf("proxy URL = %v, want http://override.example.com:8081", proxyURL)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,10 +102,24 @@ func NewDeviceFlowClient(cfg *config.Config) *DeviceFlowClient {
|
||||
|
||||
// NewDeviceFlowClientWithDeviceID creates a new device flow client with the specified device ID.
|
||||
func NewDeviceFlowClientWithDeviceID(cfg *config.Config, deviceID string) *DeviceFlowClient {
|
||||
return NewDeviceFlowClientWithDeviceIDAndProxyURL(cfg, deviceID, "")
|
||||
}
|
||||
|
||||
// NewDeviceFlowClientWithDeviceIDAndProxyURL creates a new device flow client with a proxy override.
|
||||
// proxyURL takes precedence over cfg.ProxyURL when non-empty.
|
||||
func NewDeviceFlowClientWithDeviceIDAndProxyURL(cfg *config.Config, deviceID string, proxyURL string) *DeviceFlowClient {
|
||||
client := &http.Client{Timeout: 30 * time.Second}
|
||||
effectiveProxyURL := strings.TrimSpace(proxyURL)
|
||||
var sdkCfg config.SDKConfig
|
||||
if cfg != nil {
|
||||
client = util.SetProxy(&cfg.SDKConfig, client)
|
||||
sdkCfg = cfg.SDKConfig
|
||||
if effectiveProxyURL == "" {
|
||||
effectiveProxyURL = strings.TrimSpace(cfg.ProxyURL)
|
||||
}
|
||||
}
|
||||
sdkCfg.ProxyURL = effectiveProxyURL
|
||||
client = util.SetProxy(&sdkCfg, client)
|
||||
|
||||
resolvedDeviceID := strings.TrimSpace(deviceID)
|
||||
if resolvedDeviceID == "" {
|
||||
resolvedDeviceID = getOrCreateDeviceID()
|
||||
|
||||
42
internal/auth/kimi/kimi_proxy_test.go
Normal file
42
internal/auth/kimi/kimi_proxy_test.go
Normal file
@@ -0,0 +1,42 @@
|
||||
package kimi
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/router-for-me/CLIProxyAPI/v6/internal/config"
|
||||
)
|
||||
|
||||
func TestNewDeviceFlowClientWithDeviceIDAndProxyURL_OverrideDirectDisablesProxy(t *testing.T) {
|
||||
cfg := &config.Config{SDKConfig: config.SDKConfig{ProxyURL: "http://proxy.example.com:8080"}}
|
||||
client := NewDeviceFlowClientWithDeviceIDAndProxyURL(cfg, "device-1", "direct")
|
||||
|
||||
transport, ok := client.httpClient.Transport.(*http.Transport)
|
||||
if !ok || transport == nil {
|
||||
t.Fatalf("expected http.Transport, got %T", client.httpClient.Transport)
|
||||
}
|
||||
if transport.Proxy != nil {
|
||||
t.Fatal("expected direct transport to disable proxy function")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewDeviceFlowClientWithDeviceIDAndProxyURL_OverrideProxyTakesPrecedence(t *testing.T) {
|
||||
cfg := &config.Config{SDKConfig: config.SDKConfig{ProxyURL: "http://global.example.com:8080"}}
|
||||
client := NewDeviceFlowClientWithDeviceIDAndProxyURL(cfg, "device-1", "http://override.example.com:8081")
|
||||
|
||||
transport, ok := client.httpClient.Transport.(*http.Transport)
|
||||
if !ok || transport == nil {
|
||||
t.Fatalf("expected http.Transport, got %T", client.httpClient.Transport)
|
||||
}
|
||||
req, errReq := http.NewRequest(http.MethodGet, "https://example.com", nil)
|
||||
if errReq != nil {
|
||||
t.Fatalf("new request: %v", errReq)
|
||||
}
|
||||
proxyURL, errProxy := transport.Proxy(req)
|
||||
if errProxy != nil {
|
||||
t.Fatalf("proxy func: %v", errProxy)
|
||||
}
|
||||
if proxyURL == nil || proxyURL.String() != "http://override.example.com:8081" {
|
||||
t.Fatalf("proxy URL = %v, want http://override.example.com:8081", proxyURL)
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,6 @@ func newAuthManager() *sdkAuth.Manager {
|
||||
sdkAuth.NewGeminiAuthenticator(),
|
||||
sdkAuth.NewCodexAuthenticator(),
|
||||
sdkAuth.NewClaudeAuthenticator(),
|
||||
sdkAuth.NewIFlowAuthenticator(),
|
||||
sdkAuth.NewAntigravityAuthenticator(),
|
||||
sdkAuth.NewKimiAuthenticator(),
|
||||
sdkAuth.NewKiroAuthenticator(),
|
||||
|
||||
@@ -135,12 +135,12 @@ type Config struct {
|
||||
AmpCode AmpCode `yaml:"ampcode" json:"ampcode"`
|
||||
|
||||
// OAuthExcludedModels defines per-provider global model exclusions applied to OAuth/file-backed auth entries.
|
||||
// Supported channels: gemini-cli, vertex, aistudio, antigravity, claude, codex, iflow, kiro, github-copilot.
|
||||
// Supported channels: gemini-cli, vertex, aistudio, antigravity, claude, codex, iflow, kiro, github-copilot, kimi.
|
||||
OAuthExcludedModels map[string][]string `yaml:"oauth-excluded-models,omitempty" json:"oauth-excluded-models,omitempty"`
|
||||
|
||||
// OAuthModelAlias defines global model name aliases for OAuth/file-backed auth channels.
|
||||
// These aliases affect both model listing and model routing for supported channels:
|
||||
// gemini-cli, vertex, aistudio, antigravity, claude, codex, iflow, kiro, github-copilot.
|
||||
// gemini-cli, vertex, aistudio, antigravity, claude, codex, iflow, kiro, github-copilot, kimi.
|
||||
//
|
||||
// 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.
|
||||
|
||||
@@ -17,7 +17,6 @@ type staticModelsJSON struct {
|
||||
CodexTeam []*ModelInfo `json:"codex-team"`
|
||||
CodexPlus []*ModelInfo `json:"codex-plus"`
|
||||
CodexPro []*ModelInfo `json:"codex-pro"`
|
||||
IFlow []*ModelInfo `json:"iflow"`
|
||||
Kimi []*ModelInfo `json:"kimi"`
|
||||
Antigravity []*ModelInfo `json:"antigravity"`
|
||||
}
|
||||
@@ -67,11 +66,6 @@ func GetCodexProModels() []*ModelInfo {
|
||||
return cloneModelInfos(getModels().CodexPro)
|
||||
}
|
||||
|
||||
// GetIFlowModels returns the standard iFlow model definitions.
|
||||
func GetIFlowModels() []*ModelInfo {
|
||||
return cloneModelInfos(getModels().IFlow)
|
||||
}
|
||||
|
||||
// GetKimiModels returns the standard Kimi (Moonshot AI) model definitions.
|
||||
func GetKimiModels() []*ModelInfo {
|
||||
return cloneModelInfos(getModels().Kimi)
|
||||
@@ -233,7 +227,6 @@ func cloneModelInfos(models []*ModelInfo) []*ModelInfo {
|
||||
// - gemini-cli
|
||||
// - aistudio
|
||||
// - codex
|
||||
// - iflow
|
||||
// - kimi
|
||||
// - kilo
|
||||
// - github-copilot
|
||||
@@ -254,8 +247,6 @@ func GetStaticModelDefinitionsByChannel(channel string) []*ModelInfo {
|
||||
return GetAIStudioModels()
|
||||
case "codex":
|
||||
return GetCodexProModels()
|
||||
case "iflow":
|
||||
return GetIFlowModels()
|
||||
case "kimi":
|
||||
return GetKimiModels()
|
||||
case "github-copilot":
|
||||
@@ -304,7 +295,6 @@ func LookupStaticModelInfo(modelID string) *ModelInfo {
|
||||
data.GeminiCLI,
|
||||
data.AIStudio,
|
||||
data.CodexPro,
|
||||
data.IFlow,
|
||||
data.Kimi,
|
||||
data.Antigravity,
|
||||
GetGitHubCopilotModels(),
|
||||
|
||||
@@ -213,7 +213,6 @@ func detectChangedProviders(oldData, newData *staticModelsJSON) []string {
|
||||
{"codex", oldData.CodexTeam, newData.CodexTeam},
|
||||
{"codex", oldData.CodexPlus, newData.CodexPlus},
|
||||
{"codex", oldData.CodexPro, newData.CodexPro},
|
||||
{"iflow", oldData.IFlow, newData.IFlow},
|
||||
{"kimi", oldData.Kimi, newData.Kimi},
|
||||
{"antigravity", oldData.Antigravity, newData.Antigravity},
|
||||
}
|
||||
@@ -334,7 +333,6 @@ func validateModelsCatalog(data *staticModelsJSON) error {
|
||||
{name: "codex-team", models: data.CodexTeam},
|
||||
{name: "codex-plus", models: data.CodexPlus},
|
||||
{name: "codex-pro", models: data.CodexPro},
|
||||
{name: "iflow", models: data.IFlow},
|
||||
{name: "kimi", models: data.Kimi},
|
||||
{name: "antigravity", models: data.Antigravity},
|
||||
}
|
||||
|
||||
@@ -72,6 +72,29 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "claude-opus-4-7",
|
||||
"object": "model",
|
||||
"created": 1776297600,
|
||||
"owned_by": "anthropic",
|
||||
"type": "claude",
|
||||
"display_name": "Claude Opus 4.7",
|
||||
"description": "Premium model combining maximum intelligence with practical performance",
|
||||
"context_length": 1000000,
|
||||
"max_completion_tokens": 128000,
|
||||
"thinking": {
|
||||
"min": 1024,
|
||||
"max": 128000,
|
||||
"zero_allowed": true,
|
||||
"levels": [
|
||||
"low",
|
||||
"medium",
|
||||
"high",
|
||||
"xhigh",
|
||||
"max"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "claude-opus-4-5-20251101",
|
||||
"object": "model",
|
||||
@@ -1602,187 +1625,6 @@
|
||||
}
|
||||
}
|
||||
],
|
||||
"iflow": [
|
||||
{
|
||||
"id": "qwen3-coder-plus",
|
||||
"object": "model",
|
||||
"created": 1753228800,
|
||||
"owned_by": "iflow",
|
||||
"type": "iflow",
|
||||
"display_name": "Qwen3-Coder-Plus",
|
||||
"description": "Qwen3 Coder Plus code generation"
|
||||
},
|
||||
{
|
||||
"id": "qwen3-max",
|
||||
"object": "model",
|
||||
"created": 1758672000,
|
||||
"owned_by": "iflow",
|
||||
"type": "iflow",
|
||||
"display_name": "Qwen3-Max",
|
||||
"description": "Qwen3 flagship model"
|
||||
},
|
||||
{
|
||||
"id": "qwen3-vl-plus",
|
||||
"object": "model",
|
||||
"created": 1758672000,
|
||||
"owned_by": "iflow",
|
||||
"type": "iflow",
|
||||
"display_name": "Qwen3-VL-Plus",
|
||||
"description": "Qwen3 multimodal vision-language"
|
||||
},
|
||||
{
|
||||
"id": "qwen3-max-preview",
|
||||
"object": "model",
|
||||
"created": 1757030400,
|
||||
"owned_by": "iflow",
|
||||
"type": "iflow",
|
||||
"display_name": "Qwen3-Max-Preview",
|
||||
"description": "Qwen3 Max preview build",
|
||||
"thinking": {
|
||||
"levels": [
|
||||
"none",
|
||||
"auto",
|
||||
"minimal",
|
||||
"low",
|
||||
"medium",
|
||||
"high",
|
||||
"xhigh"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "glm-4.6",
|
||||
"object": "model",
|
||||
"created": 1759190400,
|
||||
"owned_by": "iflow",
|
||||
"type": "iflow",
|
||||
"display_name": "GLM-4.6",
|
||||
"description": "Zhipu GLM 4.6 general model",
|
||||
"thinking": {
|
||||
"levels": [
|
||||
"none",
|
||||
"auto",
|
||||
"minimal",
|
||||
"low",
|
||||
"medium",
|
||||
"high",
|
||||
"xhigh"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "kimi-k2",
|
||||
"object": "model",
|
||||
"created": 1752192000,
|
||||
"owned_by": "iflow",
|
||||
"type": "iflow",
|
||||
"display_name": "Kimi-K2",
|
||||
"description": "Moonshot Kimi K2 general model"
|
||||
},
|
||||
{
|
||||
"id": "deepseek-v3.2",
|
||||
"object": "model",
|
||||
"created": 1759104000,
|
||||
"owned_by": "iflow",
|
||||
"type": "iflow",
|
||||
"display_name": "DeepSeek-V3.2-Exp",
|
||||
"description": "DeepSeek V3.2 experimental",
|
||||
"thinking": {
|
||||
"levels": [
|
||||
"none",
|
||||
"auto",
|
||||
"minimal",
|
||||
"low",
|
||||
"medium",
|
||||
"high",
|
||||
"xhigh"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "deepseek-v3.1",
|
||||
"object": "model",
|
||||
"created": 1756339200,
|
||||
"owned_by": "iflow",
|
||||
"type": "iflow",
|
||||
"display_name": "DeepSeek-V3.1-Terminus",
|
||||
"description": "DeepSeek V3.1 Terminus",
|
||||
"thinking": {
|
||||
"levels": [
|
||||
"none",
|
||||
"auto",
|
||||
"minimal",
|
||||
"low",
|
||||
"medium",
|
||||
"high",
|
||||
"xhigh"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "deepseek-r1",
|
||||
"object": "model",
|
||||
"created": 1737331200,
|
||||
"owned_by": "iflow",
|
||||
"type": "iflow",
|
||||
"display_name": "DeepSeek-R1",
|
||||
"description": "DeepSeek reasoning model R1"
|
||||
},
|
||||
{
|
||||
"id": "deepseek-v3",
|
||||
"object": "model",
|
||||
"created": 1734307200,
|
||||
"owned_by": "iflow",
|
||||
"type": "iflow",
|
||||
"display_name": "DeepSeek-V3-671B",
|
||||
"description": "DeepSeek V3 671B"
|
||||
},
|
||||
{
|
||||
"id": "qwen3-32b",
|
||||
"object": "model",
|
||||
"created": 1747094400,
|
||||
"owned_by": "iflow",
|
||||
"type": "iflow",
|
||||
"display_name": "Qwen3-32B",
|
||||
"description": "Qwen3 32B"
|
||||
},
|
||||
{
|
||||
"id": "qwen3-235b-a22b-thinking-2507",
|
||||
"object": "model",
|
||||
"created": 1753401600,
|
||||
"owned_by": "iflow",
|
||||
"type": "iflow",
|
||||
"display_name": "Qwen3-235B-A22B-Thinking",
|
||||
"description": "Qwen3 235B A22B Thinking (2507)"
|
||||
},
|
||||
{
|
||||
"id": "qwen3-235b-a22b-instruct",
|
||||
"object": "model",
|
||||
"created": 1753401600,
|
||||
"owned_by": "iflow",
|
||||
"type": "iflow",
|
||||
"display_name": "Qwen3-235B-A22B-Instruct",
|
||||
"description": "Qwen3 235B A22B Instruct"
|
||||
},
|
||||
{
|
||||
"id": "qwen3-235b",
|
||||
"object": "model",
|
||||
"created": 1753401600,
|
||||
"owned_by": "iflow",
|
||||
"type": "iflow",
|
||||
"display_name": "Qwen3-235B-A22B",
|
||||
"description": "Qwen3 235B A22B"
|
||||
},
|
||||
{
|
||||
"id": "iflow-rome-30ba3b",
|
||||
"object": "model",
|
||||
"created": 1736899200,
|
||||
"owned_by": "iflow",
|
||||
"type": "iflow",
|
||||
"display_name": "iFlow-ROME",
|
||||
"description": "iFlow Rome 30BA3B model"
|
||||
}
|
||||
],
|
||||
"kimi": [
|
||||
{
|
||||
"id": "kimi-k2",
|
||||
@@ -2022,4 +1864,4 @@
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -659,7 +659,7 @@ func (e *ClaudeExecutor) Refresh(ctx context.Context, auth *cliproxyauth.Auth) (
|
||||
if refreshToken == "" {
|
||||
return auth, nil
|
||||
}
|
||||
svc := claudeauth.NewClaudeAuth(e.cfg)
|
||||
svc := claudeauth.NewClaudeAuthWithProxyURL(e.cfg, auth.ProxyURL)
|
||||
td, err := svc.RefreshTokens(ctx, refreshToken)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -612,7 +612,7 @@ func (e *CodexExecutor) Refresh(ctx context.Context, auth *cliproxyauth.Auth) (*
|
||||
if refreshToken == "" {
|
||||
return auth, nil
|
||||
}
|
||||
svc := codexauth.NewCodexAuth(e.cfg)
|
||||
svc := codexauth.NewCodexAuthWithProxyURL(e.cfg, auth.ProxyURL)
|
||||
td, err := svc.RefreshTokensWithRetry(ctx, refreshToken, 3)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -6,7 +6,6 @@ import (
|
||||
_ "github.com/router-for-me/CLIProxyAPI/v6/internal/thinking/provider/codex"
|
||||
_ "github.com/router-for-me/CLIProxyAPI/v6/internal/thinking/provider/gemini"
|
||||
_ "github.com/router-for-me/CLIProxyAPI/v6/internal/thinking/provider/geminicli"
|
||||
_ "github.com/router-for-me/CLIProxyAPI/v6/internal/thinking/provider/iflow"
|
||||
_ "github.com/router-for-me/CLIProxyAPI/v6/internal/thinking/provider/kimi"
|
||||
_ "github.com/router-for-me/CLIProxyAPI/v6/internal/thinking/provider/openai"
|
||||
)
|
||||
|
||||
@@ -472,7 +472,7 @@ func (e *KimiExecutor) Refresh(ctx context.Context, auth *cliproxyauth.Auth) (*c
|
||||
return auth, nil
|
||||
}
|
||||
|
||||
client := kimiauth.NewDeviceFlowClientWithDeviceID(e.cfg, resolveKimiDeviceID(auth))
|
||||
client := kimiauth.NewDeviceFlowClientWithDeviceIDAndProxyURL(e.cfg, resolveKimiDeviceID(auth), auth.ProxyURL)
|
||||
td, err := client.RefreshToken(ctx, refreshToken)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -16,7 +16,6 @@ var providerAppliers = map[string]ProviderApplier{
|
||||
"claude": nil,
|
||||
"openai": nil,
|
||||
"codex": nil,
|
||||
"iflow": nil,
|
||||
"antigravity": nil,
|
||||
"kimi": nil,
|
||||
}
|
||||
@@ -63,7 +62,7 @@ func IsUserDefinedModel(modelInfo *registry.ModelInfo) bool {
|
||||
// - body: Original request body JSON
|
||||
// - model: Model name, optionally with thinking suffix (e.g., "claude-sonnet-4-5(16384)")
|
||||
// - fromFormat: Source request format (e.g., openai, codex, gemini)
|
||||
// - toFormat: Target provider format for the request body (gemini, gemini-cli, antigravity, claude, openai, codex, iflow)
|
||||
// - toFormat: Target provider format for the request body (gemini, gemini-cli, antigravity, claude, openai, codex, kimi)
|
||||
// - providerKey: Provider identifier used for registry model lookups (may differ from toFormat, e.g., openrouter -> openai)
|
||||
//
|
||||
// Returns:
|
||||
@@ -327,12 +326,6 @@ func extractThinkingConfig(body []byte, provider string) ThinkingConfig {
|
||||
return extractOpenAIConfig(body)
|
||||
case "codex":
|
||||
return extractCodexConfig(body)
|
||||
case "iflow":
|
||||
config := extractIFlowConfig(body)
|
||||
if hasThinkingConfig(config) {
|
||||
return config
|
||||
}
|
||||
return extractOpenAIConfig(body)
|
||||
case "kimi":
|
||||
// Kimi uses OpenAI-compatible reasoning_effort format
|
||||
return extractOpenAIConfig(body)
|
||||
@@ -494,34 +487,3 @@ func extractCodexConfig(body []byte) ThinkingConfig {
|
||||
|
||||
return ThinkingConfig{}
|
||||
}
|
||||
|
||||
// extractIFlowConfig extracts thinking configuration from iFlow format request body.
|
||||
//
|
||||
// iFlow API format (supports multiple model families):
|
||||
// - GLM format: chat_template_kwargs.enable_thinking (boolean)
|
||||
// - MiniMax format: reasoning_split (boolean)
|
||||
//
|
||||
// Returns ModeBudget with Budget=1 as a sentinel value indicating "enabled".
|
||||
// The actual budget/configuration is determined by the iFlow applier based on model capabilities.
|
||||
// Budget=1 is used because iFlow models don't use numeric budgets; they only support on/off.
|
||||
func extractIFlowConfig(body []byte) ThinkingConfig {
|
||||
// GLM format: chat_template_kwargs.enable_thinking
|
||||
if enabled := gjson.GetBytes(body, "chat_template_kwargs.enable_thinking"); enabled.Exists() {
|
||||
if enabled.Bool() {
|
||||
// Budget=1 is a sentinel meaning "enabled" (iFlow doesn't use numeric budgets)
|
||||
return ThinkingConfig{Mode: ModeBudget, Budget: 1}
|
||||
}
|
||||
return ThinkingConfig{Mode: ModeNone, Budget: 0}
|
||||
}
|
||||
|
||||
// MiniMax format: reasoning_split
|
||||
if split := gjson.GetBytes(body, "reasoning_split"); split.Exists() {
|
||||
if split.Bool() {
|
||||
// Budget=1 is a sentinel meaning "enabled" (iFlow doesn't use numeric budgets)
|
||||
return ThinkingConfig{Mode: ModeBudget, Budget: 1}
|
||||
}
|
||||
return ThinkingConfig{Mode: ModeNone, Budget: 0}
|
||||
}
|
||||
|
||||
return ThinkingConfig{}
|
||||
}
|
||||
|
||||
@@ -155,7 +155,7 @@ const (
|
||||
// It analyzes the model's ThinkingSupport configuration to classify the model:
|
||||
// - CapabilityNone: modelInfo.Thinking is nil (model doesn't support thinking)
|
||||
// - CapabilityBudgetOnly: Has Min/Max but no Levels (Claude, Gemini 2.5)
|
||||
// - CapabilityLevelOnly: Has Levels but no Min/Max (OpenAI, iFlow)
|
||||
// - CapabilityLevelOnly: Has Levels but no Min/Max (OpenAI, Codex, Kimi)
|
||||
// - CapabilityHybrid: Has both Min/Max and Levels (Gemini 3)
|
||||
//
|
||||
// Note: Returns a special sentinel value when modelInfo itself is nil (unknown model).
|
||||
|
||||
@@ -44,13 +44,6 @@ func StripThinkingConfig(body []byte, provider string) []byte {
|
||||
}
|
||||
case "codex":
|
||||
paths = []string{"reasoning.effort"}
|
||||
case "iflow":
|
||||
paths = []string{
|
||||
"chat_template_kwargs.enable_thinking",
|
||||
"chat_template_kwargs.clear_thinking",
|
||||
"reasoning_split",
|
||||
"reasoning_effort",
|
||||
}
|
||||
default:
|
||||
return body
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Package thinking provides unified thinking configuration processing.
|
||||
//
|
||||
// This package offers a unified interface for parsing, validating, and applying
|
||||
// thinking configurations across various AI providers (Claude, Gemini, OpenAI, iFlow).
|
||||
// thinking configurations across various AI providers (Claude, Gemini, OpenAI, Codex, Antigravity, Kimi).
|
||||
package thinking
|
||||
|
||||
import "github.com/router-for-me/CLIProxyAPI/v6/internal/registry"
|
||||
|
||||
@@ -24,7 +24,6 @@ var oauthProviders = []oauthProvider{
|
||||
{"Codex (OpenAI)", "codex-auth-url", "🟩"},
|
||||
{"Antigravity", "antigravity-auth-url", "🟪"},
|
||||
{"Kimi", "kimi-auth-url", "🟫"},
|
||||
{"IFlow", "iflow-auth-url", "⬜"},
|
||||
}
|
||||
|
||||
// oauthTabModel handles OAuth login flows.
|
||||
@@ -281,8 +280,6 @@ func (m oauthTabModel) submitCallback(callbackURL string) tea.Cmd {
|
||||
providerKey = "antigravity"
|
||||
case "kimi-auth-url":
|
||||
providerKey = "kimi"
|
||||
case "iflow-auth-url":
|
||||
providerKey = "iflow"
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user