mirror of
https://github.com/router-for-me/CLIProxyAPIPlus.git
synced 2026-04-24 07:02:33 +00:00
Compare commits
17 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b93cce5412 | ||
|
|
c6cb24039d | ||
|
|
5382408489 | ||
|
|
67669196ed | ||
|
|
58fd9bf964 | ||
|
|
7b3dfc67bc | ||
|
|
cdd24052d3 | ||
|
|
733fd8edab | ||
|
|
af27f2b8bc | ||
|
|
2e1925d762 | ||
|
|
77254bd074 | ||
|
|
3960c93d51 | ||
|
|
339a81b650 | ||
|
|
560c020477 | ||
|
|
aec65e3be3 | ||
|
|
f44f0702f8 | ||
|
|
b76b79068f |
@@ -244,11 +244,11 @@ nonstream-keepalive-interval: 0
|
|||||||
# - name: "kimi-k2.5"
|
# - name: "kimi-k2.5"
|
||||||
# alias: "claude-opus-4.66"
|
# alias: "claude-opus-4.66"
|
||||||
|
|
||||||
# Vertex API keys (Vertex-compatible endpoints, use API key + base URL)
|
# Vertex API keys (Vertex-compatible endpoints, base-url is optional)
|
||||||
# vertex-api-key:
|
# vertex-api-key:
|
||||||
# - api-key: "vk-123..." # x-goog-api-key header
|
# - api-key: "vk-123..." # x-goog-api-key header
|
||||||
# prefix: "test" # optional: require calls like "test/vertex-pro" to target this credential
|
# prefix: "test" # optional: require calls like "test/vertex-pro" to target this credential
|
||||||
# base-url: "https://example.com/api" # e.g. https://zenmux.ai/api
|
# base-url: "https://example.com/api" # optional, e.g. https://zenmux.ai/api; falls back to Google Vertex when omitted
|
||||||
# proxy-url: "socks5://proxy.example.com:1080" # optional per-key proxy override
|
# proxy-url: "socks5://proxy.example.com:1080" # optional per-key proxy override
|
||||||
# # proxy-url: "direct" # optional: explicit direct connect for this credential
|
# # proxy-url: "direct" # optional: explicit direct connect for this credential
|
||||||
# headers:
|
# headers:
|
||||||
|
|||||||
@@ -2438,17 +2438,20 @@ func (h *Handler) RequestGitHubToken(c *gin.Context) {
|
|||||||
if label == "" {
|
if label == "" {
|
||||||
label = username
|
label = username
|
||||||
}
|
}
|
||||||
|
metadata, errMeta := copilotTokenMetadata(tokenStorage)
|
||||||
|
if errMeta != nil {
|
||||||
|
log.Errorf("Failed to build token metadata: %v", errMeta)
|
||||||
|
SetOAuthSessionError(state, "Failed to build token metadata")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
record := &coreauth.Auth{
|
record := &coreauth.Auth{
|
||||||
ID: fileName,
|
ID: fileName,
|
||||||
Provider: "github-copilot",
|
Provider: "github-copilot",
|
||||||
Label: label,
|
Label: label,
|
||||||
FileName: fileName,
|
FileName: fileName,
|
||||||
Storage: tokenStorage,
|
Storage: tokenStorage,
|
||||||
Metadata: map[string]any{
|
Metadata: metadata,
|
||||||
"email": userInfo.Email,
|
|
||||||
"username": username,
|
|
||||||
"name": userInfo.Name,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
savedPath, errSave := h.saveTokenRecord(ctx, record)
|
savedPath, errSave := h.saveTokenRecord(ctx, record)
|
||||||
@@ -2473,6 +2476,21 @@ func (h *Handler) RequestGitHubToken(c *gin.Context) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func copilotTokenMetadata(storage *copilot.CopilotTokenStorage) (map[string]any, error) {
|
||||||
|
if storage == nil {
|
||||||
|
return nil, fmt.Errorf("token storage is nil")
|
||||||
|
}
|
||||||
|
payload, errMarshal := json.Marshal(storage)
|
||||||
|
if errMarshal != nil {
|
||||||
|
return nil, fmt.Errorf("marshal token storage: %w", errMarshal)
|
||||||
|
}
|
||||||
|
metadata := make(map[string]any)
|
||||||
|
if errUnmarshal := json.Unmarshal(payload, &metadata); errUnmarshal != nil {
|
||||||
|
return nil, fmt.Errorf("unmarshal token storage: %w", errUnmarshal)
|
||||||
|
}
|
||||||
|
return metadata, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (h *Handler) RequestIFlowCookieToken(c *gin.Context) {
|
func (h *Handler) RequestIFlowCookieToken(c *gin.Context) {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
|
|||||||
@@ -509,8 +509,12 @@ func (h *Handler) PutVertexCompatKeys(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
for i := range arr {
|
for i := range arr {
|
||||||
normalizeVertexCompatKey(&arr[i])
|
normalizeVertexCompatKey(&arr[i])
|
||||||
|
if arr[i].APIKey == "" {
|
||||||
|
c.JSON(400, gin.H{"error": fmt.Sprintf("vertex-api-key[%d].api-key is required", i)})
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
h.cfg.VertexCompatAPIKey = arr
|
h.cfg.VertexCompatAPIKey = append([]config.VertexCompatKey(nil), arr...)
|
||||||
h.cfg.SanitizeVertexCompatKeys()
|
h.cfg.SanitizeVertexCompatKeys()
|
||||||
h.persist(c)
|
h.persist(c)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -685,7 +685,7 @@ func LoadConfigOptional(configFile string, optional bool) (*Config, error) {
|
|||||||
// Sanitize Gemini API key configuration and migrate legacy entries.
|
// Sanitize Gemini API key configuration and migrate legacy entries.
|
||||||
cfg.SanitizeGeminiKeys()
|
cfg.SanitizeGeminiKeys()
|
||||||
|
|
||||||
// Sanitize Vertex-compatible API keys: drop entries without base-url
|
// Sanitize Vertex-compatible API keys.
|
||||||
cfg.SanitizeVertexCompatKeys()
|
cfg.SanitizeVertexCompatKeys()
|
||||||
|
|
||||||
// Sanitize Codex keys: drop entries without base-url
|
// Sanitize Codex keys: drop entries without base-url
|
||||||
|
|||||||
@@ -20,9 +20,9 @@ type VertexCompatKey struct {
|
|||||||
// Prefix optionally namespaces model aliases for this credential (e.g., "teamA/vertex-pro").
|
// Prefix optionally namespaces model aliases for this credential (e.g., "teamA/vertex-pro").
|
||||||
Prefix string `yaml:"prefix,omitempty" json:"prefix,omitempty"`
|
Prefix string `yaml:"prefix,omitempty" json:"prefix,omitempty"`
|
||||||
|
|
||||||
// BaseURL is the base URL for the Vertex-compatible API endpoint.
|
// BaseURL optionally overrides the Vertex-compatible API endpoint.
|
||||||
// The executor will append "/v1/publishers/google/models/{model}:action" to this.
|
// The executor will append "/v1/publishers/google/models/{model}:action" to this.
|
||||||
// Example: "https://zenmux.ai/api" becomes "https://zenmux.ai/api/v1/publishers/google/models/..."
|
// When empty, requests fall back to the default Vertex API base URL.
|
||||||
BaseURL string `yaml:"base-url,omitempty" json:"base-url,omitempty"`
|
BaseURL string `yaml:"base-url,omitempty" json:"base-url,omitempty"`
|
||||||
|
|
||||||
// ProxyURL optionally overrides the global proxy for this API key.
|
// ProxyURL optionally overrides the global proxy for this API key.
|
||||||
@@ -71,10 +71,6 @@ func (cfg *Config) SanitizeVertexCompatKeys() {
|
|||||||
}
|
}
|
||||||
entry.Prefix = normalizeModelPrefix(entry.Prefix)
|
entry.Prefix = normalizeModelPrefix(entry.Prefix)
|
||||||
entry.BaseURL = strings.TrimSpace(entry.BaseURL)
|
entry.BaseURL = strings.TrimSpace(entry.BaseURL)
|
||||||
if entry.BaseURL == "" {
|
|
||||||
// BaseURL is required for Vertex API key entries
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
entry.ProxyURL = strings.TrimSpace(entry.ProxyURL)
|
entry.ProxyURL = strings.TrimSpace(entry.ProxyURL)
|
||||||
entry.Headers = NormalizeHeaders(entry.Headers)
|
entry.Headers = NormalizeHeaders(entry.Headers)
|
||||||
entry.ExcludedModels = NormalizeExcludedModels(entry.ExcludedModels)
|
entry.ExcludedModels = NormalizeExcludedModels(entry.ExcludedModels)
|
||||||
|
|||||||
@@ -205,6 +205,10 @@ func (e *OpenAICompatExecutor) ExecuteStream(ctx context.Context, auth *cliproxy
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Request usage data in the final streaming chunk so that token statistics
|
||||||
|
// are captured even when the upstream is an OpenAI-compatible provider.
|
||||||
|
translated, _ = sjson.SetBytes(translated, "stream_options.include_usage", true)
|
||||||
|
|
||||||
url := strings.TrimSuffix(baseURL, "/") + "/chat/completions"
|
url := strings.TrimSuffix(baseURL, "/") + "/chat/completions"
|
||||||
httpReq, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(translated))
|
httpReq, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(translated))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -6,10 +6,10 @@
|
|||||||
package claude
|
package claude
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/router-for-me/CLIProxyAPI/v6/internal/translator/gemini/common"
|
"github.com/router-for-me/CLIProxyAPI/v6/internal/translator/gemini/common"
|
||||||
|
"github.com/router-for-me/CLIProxyAPI/v6/internal/util"
|
||||||
"github.com/tidwall/gjson"
|
"github.com/tidwall/gjson"
|
||||||
"github.com/tidwall/sjson"
|
"github.com/tidwall/sjson"
|
||||||
)
|
)
|
||||||
@@ -36,7 +36,6 @@ const geminiCLIClaudeThoughtSignature = "skip_thought_signature_validator"
|
|||||||
// - []byte: The transformed request data in Gemini CLI API format
|
// - []byte: The transformed request data in Gemini CLI API format
|
||||||
func ConvertClaudeRequestToCLI(modelName string, inputRawJSON []byte, _ bool) []byte {
|
func ConvertClaudeRequestToCLI(modelName string, inputRawJSON []byte, _ bool) []byte {
|
||||||
rawJSON := inputRawJSON
|
rawJSON := inputRawJSON
|
||||||
rawJSON = bytes.Replace(rawJSON, []byte(`"url":{"type":"string","format":"uri",`), []byte(`"url":{"type":"string",`), -1)
|
|
||||||
|
|
||||||
// Build output Gemini CLI request JSON
|
// Build output Gemini CLI request JSON
|
||||||
out := `{"model":"","request":{"contents":[]}}`
|
out := `{"model":"","request":{"contents":[]}}`
|
||||||
@@ -149,7 +148,7 @@ func ConvertClaudeRequestToCLI(modelName string, inputRawJSON []byte, _ bool) []
|
|||||||
toolsResult.ForEach(func(_, toolResult gjson.Result) bool {
|
toolsResult.ForEach(func(_, toolResult gjson.Result) bool {
|
||||||
inputSchemaResult := toolResult.Get("input_schema")
|
inputSchemaResult := toolResult.Get("input_schema")
|
||||||
if inputSchemaResult.Exists() && inputSchemaResult.IsObject() {
|
if inputSchemaResult.Exists() && inputSchemaResult.IsObject() {
|
||||||
inputSchema := inputSchemaResult.Raw
|
inputSchema := util.CleanJSONSchemaForGemini(inputSchemaResult.Raw)
|
||||||
tool, _ := sjson.Delete(toolResult.Raw, "input_schema")
|
tool, _ := sjson.Delete(toolResult.Raw, "input_schema")
|
||||||
tool, _ = sjson.SetRaw(tool, "parametersJsonSchema", inputSchema)
|
tool, _ = sjson.SetRaw(tool, "parametersJsonSchema", inputSchema)
|
||||||
tool, _ = sjson.Delete(tool, "strict")
|
tool, _ = sjson.Delete(tool, "strict")
|
||||||
@@ -157,6 +156,7 @@ func ConvertClaudeRequestToCLI(modelName string, inputRawJSON []byte, _ bool) []
|
|||||||
tool, _ = sjson.Delete(tool, "type")
|
tool, _ = sjson.Delete(tool, "type")
|
||||||
tool, _ = sjson.Delete(tool, "cache_control")
|
tool, _ = sjson.Delete(tool, "cache_control")
|
||||||
tool, _ = sjson.Delete(tool, "defer_loading")
|
tool, _ = sjson.Delete(tool, "defer_loading")
|
||||||
|
tool, _ = sjson.Delete(tool, "eager_input_streaming")
|
||||||
if gjson.Valid(tool) && gjson.Parse(tool).IsObject() {
|
if gjson.Valid(tool) && gjson.Parse(tool).IsObject() {
|
||||||
if !hasTools {
|
if !hasTools {
|
||||||
out, _ = sjson.SetRaw(out, "request.tools", `[{"functionDeclarations":[]}]`)
|
out, _ = sjson.SetRaw(out, "request.tools", `[{"functionDeclarations":[]}]`)
|
||||||
|
|||||||
@@ -111,6 +111,23 @@ func ConvertGeminiRequestToGeminiCLI(_ string, inputRawJSON []byte, _ bool) []by
|
|||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Filter out contents with empty parts to avoid Gemini API error:
|
||||||
|
// "required oneof field 'data' must have one initialized field"
|
||||||
|
filteredContents := "[]"
|
||||||
|
hasFiltered := false
|
||||||
|
gjson.GetBytes(rawJSON, "request.contents").ForEach(func(_, content gjson.Result) bool {
|
||||||
|
parts := content.Get("parts")
|
||||||
|
if !parts.IsArray() || len(parts.Array()) == 0 {
|
||||||
|
hasFiltered = true
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
filteredContents, _ = sjson.SetRaw(filteredContents, "-1", content.Raw)
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
if hasFiltered {
|
||||||
|
rawJSON, _ = sjson.SetRawBytes(rawJSON, "request.contents", []byte(filteredContents))
|
||||||
|
}
|
||||||
|
|
||||||
return common.AttachDefaultSafetySettings(rawJSON, "request.safetySettings")
|
return common.AttachDefaultSafetySettings(rawJSON, "request.safetySettings")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -915,7 +915,7 @@ func (s *Service) registerModelsForAuth(a *coreauth.Auth) {
|
|||||||
models = registry.GetCodexProModels()
|
models = registry.GetCodexProModels()
|
||||||
case "plus":
|
case "plus":
|
||||||
models = registry.GetCodexPlusModels()
|
models = registry.GetCodexPlusModels()
|
||||||
case "team":
|
case "team", "business", "go":
|
||||||
models = registry.GetCodexTeamModels()
|
models = registry.GetCodexTeamModels()
|
||||||
case "free":
|
case "free":
|
||||||
models = registry.GetCodexFreeModels()
|
models = registry.GetCodexFreeModels()
|
||||||
|
|||||||
Reference in New Issue
Block a user